diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart index f6928ab5810..81f17612313 100644 --- a/pkg/compiler/lib/src/ssa/codegen.dart +++ b/pkg/compiler/lib/src/ssa/codegen.dart @@ -414,7 +414,8 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { assert(graph.isValid(), 'Graph not valid after ${phase.name}'); } - runPhase(new SsaInstructionSelection(_options, _closedWorld)); + runPhase( + new SsaInstructionSelection(_options, _closedWorld, _interceptorData)); runPhase(new SsaTypeKnownRemover()); runPhase(new SsaTrustedCheckRemover(_options)); runPhase(new SsaAssignmentChaining(_closedWorld)); diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart index 5bf162b9da2..d6439369dab 100644 --- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart +++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart @@ -5,6 +5,7 @@ import '../constants/values.dart'; import '../elements/entities.dart'; import '../inferrer/abstract_value_domain.dart'; +import '../js_backend/interceptor_data.dart'; import '../options.dart'; import '../universe/selector.dart' show Selector; import '../world.dart' show JClosedWorld; @@ -26,14 +27,21 @@ bool canUseAliasedSuperMember(MemberEntity member, Selector selector) { /// /// - Remove NullChecks where the next instruction would fail on the operand. /// +/// - Dummy receiver optimization. +/// +/// - One-shot interceptor optimization. +/// /// - Combine read/modify/write sequences into HReadModifyWrite instructions to /// simplify codegen of expressions like `a.x += y`. + class SsaInstructionSelection extends HBaseVisitor with CodegenPhase { final JClosedWorld _closedWorld; + final InterceptorData _interceptorData; final CompilerOptions _options; HGraph graph; - SsaInstructionSelection(this._options, this._closedWorld); + SsaInstructionSelection( + this._options, this._closedWorld, this._interceptorData); AbstractValueDomain get _abstractValueDomain => _closedWorld.abstractValueDomain; @@ -220,6 +228,128 @@ class SsaInstructionSelection extends HBaseVisitor with CodegenPhase { .isInterceptor(_abstractValueDomain.excludeNull(type)) .isPotentiallyTrue; + @override + HInstruction visitInvokeDynamic(HInvokeDynamic node) { + if (!node.isInterceptedCall) return node; + + tryReplaceExplicitReceiverWithDummy( + node, node.selector, node.element, node.receiverType); + + // Try to replace + // + // getInterceptor(o).method(o, ...) + // + // with a 'one shot interceptor' which is a call to a synthesized static + // helper function that combines the two operations. + // + // oneShotMethod(o, 1, 2) + // + // This saves code size and makes the receiver of an intercepted call a + // candidate for being generated at use site. + // + // Avoid combining a hoisted interceptor back into a loop, and the faster + // almost-constant kind of interceptor. + + HInstruction interceptor = node.inputs[0]; + if (interceptor is HInterceptor && + interceptor.usedBy.length == 1 && + !interceptor.isConditionalConstantInterceptor && + interceptor.hasSameLoopHeaderAs(node)) { + // Copy inputs and replace interceptor with `null`. + List inputs = List.of(node.inputs); + inputs[0] = graph.addConstantNull(_closedWorld); + + HOneShotInterceptor oneShot = HOneShotInterceptor( + node.selector, + node.receiverType, + inputs, + node.instructionType, + node.typeArguments, + interceptor.interceptedClasses); + oneShot.sourceInformation = node.sourceInformation; + oneShot.sourceElement = node.sourceElement; + oneShot.sideEffects.setTo(node.sideEffects); + + HBasicBlock block = node.block; + block.addAfter(node, oneShot); + block.rewrite(node, oneShot); + block.remove(node); + interceptor.block.remove(interceptor); + return null; + } + + return node; + } + + @override + HInstruction visitInvokeSuper(HInvokeSuper node) { + tryReplaceExplicitReceiverWithDummy( + node, node.selector, node.element, null); + return node; + } + + @override + HInstruction visitOneShotInterceptor(HOneShotInterceptor node) { + throw StateError('Should not see HOneShotInterceptor: $node'); + } + + void tryReplaceExplicitReceiverWithDummy(HInvoke node, Selector selector, + MemberEntity target, AbstractValue mask) { + // Calls of the form + // + // a.foo$1(a, x) + // + // where the interceptor calling convention is used come from recognizing + // that 'a' is a 'self-interceptor'. If the selector matches only methods + // that ignore the explicit receiver parameter, replace occurrences of the + // receiver argument with a dummy receiver '0': + // + // a.foo$1(a, x) ---> a.foo$1(0, x) + // + // This often reduces the number of references to 'a' to one, allowing 'a' + // to be generated at use to avoid a temporary, e.g. + // + // t1 = b.get$thing(); + // t1.foo$1(t1, x) + // ---> + // b.get$thing().foo$1(0, x) + // + assert(target != null || mask != null); + + if (!node.isInterceptedCall) return; + + // TODO(15933): Make automatically generated property extraction closures + // work with the dummy receiver optimization. + if (selector.isGetter) return; + + // This assignment of inputs is uniform for HInvokeDynamic and HInvokeSuper. + HInstruction interceptor = node.inputs[0]; + HInstruction receiverArgument = node.inputs[1]; + + // A 'self-interceptor'? + if (interceptor.nonCheck() != receiverArgument.nonCheck()) return; + + // TODO(sra): Should this be an assert? + if (!_interceptorData.isInterceptedSelector(selector)) return; + + if (target != null) { + // A call that resolves to a single instance method (element) requires the + // calling convention consistent with the method. + ClassEntity cls = target.enclosingClass; + assert(_interceptorData.isInterceptedMethod(target)); + if (_interceptorData.isInterceptedClass(cls)) return; + } else if (_interceptorData.isInterceptedMixinSelector( + selector, mask, _closedWorld)) { + return; + } + + ConstantValue constant = DummyInterceptorConstantValue(); + HConstant dummy = graph.addConstant(constant, _closedWorld); + receiverArgument.usedBy.remove(node); + node.inputs[1] = dummy; + dummy.usedBy.add(node); + } + @override HInstruction visitFieldSet(HFieldSet setter) { // Pattern match diff --git a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart deleted file mode 100644 index 9ec829d8d94..00000000000 --- a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import '../constants/values.dart'; -import '../elements/entities.dart'; -import '../inferrer/abstract_value_domain.dart'; -import '../js_backend/interceptor_data.dart'; -import '../universe/selector.dart' show Selector; -import '../world.dart' show JClosedWorld; -import 'nodes.dart'; -import 'optimize.dart'; - -/// SsaFinalizeInterceptors makes adjustments for the interceptor calling -/// convention. -/// -/// 1. If the method cannot be invoked with an intercepted receiver, the -/// receiver and interceptor are the same. In this case ignore the explicit -/// receiver argument and use the interceptor (this) as the receiver. -/// -/// 2. The call-site dual of the above is if a method ignores the explicit -/// receiver, it can be replaced with a dummy value, i.e. a dummy receiver -/// optimization. -/// -/// 3. If an interceptor is used once for a call, replace the -/// getInterceptor-call pair with a call to a 'one-shot interceptor' outlined -/// method. -/// -class SsaFinalizeInterceptors extends HBaseVisitor - implements OptimizationPhase { - @override - String get name => "SsaFinalizeInterceptors"; - final JClosedWorld _closedWorld; - HGraph _graph; - - SsaFinalizeInterceptors(this._closedWorld); - - InterceptorData get _interceptorData => _closedWorld.interceptorData; - - @override - void visitGraph(HGraph graph) { - _graph = graph; - MemberEntity element = graph.element; - - if (usesSelfInterceptor(element)) { - _redirectReceiver(); - } - visitDominatorTree(graph); - } - - @override - visitBasicBlock(HBasicBlock node) { - HInstruction instruction = node.first; - while (instruction != null) { - final next = instruction.next; - instruction.accept(this); - instruction = next; - } - } - - /// Returns `true` if [element] is an instance method that uses the - /// interceptor calling convention but the instance and interceptor arguments - /// will always be the same value. - bool usesSelfInterceptor(MemberEntity element) { - if (!_interceptorData.isInterceptedMethod(element)) return false; - ClassEntity cls = element.enclosingClass; - return !_interceptorData.isInterceptedClass(cls); - } - - void _redirectReceiver() { - // The entry block contains the parameters in order, starting with `this`, - // and then the explicit receiver. There are other instructions in the - // block, like constants, which we ignore. - HThis thisParameter; - HParameterValue receiverParameter; - for (HInstruction node = _graph.entry.first; - node != null; - node = node.next) { - if (node is HParameterValue) { - if (node is HThis) { - thisParameter = node; - } else { - receiverParameter = node; - break; - } - } - } - assert(thisParameter != null, - '`this` parameter should be before other parameters'); - assert(receiverParameter != null, - 'Intercepted convention requires explicit receiver'); - thisParameter.instructionType = receiverParameter.instructionType; - receiverParameter.block.rewrite(receiverParameter, thisParameter); - receiverParameter.sourceElement = const _RenameToUnderscore(); - } - - @override - void visitInvokeDynamic(HInvokeDynamic node) { - if (!node.isInterceptedCall) return; - - if (node.element != null) { - tryReplaceExplicitReceiverForTargetWithDummy( - node, node.selector, node.element); - } else { - tryReplaceExplicitReceiverForSelectorWithDummy( - node, node.selector, node.receiverType); - } - - // Try to replace - // - // getInterceptor(o).method(o, ...) - // - // with a 'one shot interceptor' which is a call to a synthesized static - // helper function that combines the two operations. - // - // oneShotMethod(o, 1, 2) - // - // This saves code size and makes the receiver of an intercepted call a - // candidate for being generated at use site. - // - // Avoid combining a hoisted interceptor back into a loop, and the faster - // almost-constant kind of interceptor. - - HInstruction interceptor = node.inputs[0]; - if (interceptor is HInterceptor && - interceptor.usedBy.length == 1 && - !interceptor.isConditionalConstantInterceptor && - interceptor.hasSameLoopHeaderAs(node)) { - // Copy inputs and replace interceptor with `null`. - List inputs = List.of(node.inputs); - inputs[0] = _graph.addConstantNull(_closedWorld); - - HOneShotInterceptor oneShot = HOneShotInterceptor( - node.selector, - node.receiverType, - inputs, - node.instructionType, - node.typeArguments, - interceptor.interceptedClasses); - oneShot.sourceInformation = node.sourceInformation; - oneShot.sourceElement = node.sourceElement; - oneShot.sideEffects.setTo(node.sideEffects); - - HBasicBlock block = node.block; - block.addAfter(node, oneShot); - block.rewrite(node, oneShot); - block.remove(node); - interceptor.block.remove(interceptor); - } - } - - @override - void visitInvokeSuper(HInvokeSuper node) { - if (!node.isInterceptedCall) return; - tryReplaceExplicitReceiverForTargetWithDummy( - node, node.selector, node.element); - } - - @override - void visitInvokeGeneratorBody(HInvokeGeneratorBody node) { - tryReplaceExplicitReceiverForTargetWithDummy(node, null, node.element); - } - - @override - void visitOneShotInterceptor(HOneShotInterceptor node) { - throw StateError('Should not see HOneShotInterceptor: $node'); - } - - void tryReplaceExplicitReceiverForTargetWithDummy( - HInvoke node, Selector selector, MemberEntity target) { - assert(target != null); - - // TODO(15933): Make automatically generated property extraction closures - // work with the dummy receiver optimization. - if (selector != null && selector.isGetter) return; - - if (usesSelfInterceptor(target)) { - _replaceReceiverArgumentWithDummy(node, 1); - } - } - - void tryReplaceExplicitReceiverForSelectorWithDummy( - HInvoke node, Selector selector, AbstractValue mask) { - assert(mask != null); - // Calls of the form - // - // a.foo$1(a, x) - // - // where the interceptor calling convention is used come from recognizing - // that 'a' is a 'self-interceptor'. If the selector matches only methods - // that ignore the explicit receiver parameter, replace occurrences of the - // receiver argument with a dummy receiver '0': - // - // a.foo$1(a, x) ---> a.foo$1(0, x) - // - // This often reduces the number of references to 'a' to one, allowing 'a' - // to be generated at use to avoid a temporary, e.g. - // - // t1 = b.get$thing(); - // t1.foo$1(t1, x) - // ---> - // b.get$thing().foo$1(0, x) - // - - // TODO(15933): Make automatically generated property extraction closures - // work with the dummy receiver optimization. - if (selector.isGetter) return; - - // This assignment of inputs is uniform for HInvokeDynamic and HInvokeSuper. - HInstruction interceptor = node.inputs[0]; - HInstruction receiverArgument = node.inputs[1]; - - // A 'self-interceptor'? - if (interceptor.nonCheck() != receiverArgument.nonCheck()) return; - - // TODO(sra): Should this be an assert? - if (!_interceptorData.isInterceptedSelector(selector)) return; - - if (!_interceptorData.isInterceptedMixinSelector( - selector, mask, _closedWorld)) { - _replaceReceiverArgumentWithDummy(node, 1); - } - } - - void _replaceReceiverArgumentWithDummy(HInvoke node, int receiverIndex) { - HInstruction receiverArgument = node.inputs[receiverIndex]; - ConstantValue constant = DummyInterceptorConstantValue(); - HConstant dummy = _graph.addConstant(constant, _closedWorld); - receiverArgument.usedBy.remove(node); - node.inputs[receiverIndex] = dummy; - dummy.usedBy.add(node); - } -} - -/// A simple Entity to rename the unused receiver to `_` in non-minified code. -class _RenameToUnderscore implements Entity { - const _RenameToUnderscore(); - @override - String get name => '_'; -} diff --git a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart index c0b6254e01f..968cf4f1eaf 100644 --- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart +++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart @@ -12,16 +12,20 @@ import '../world.dart' show JClosedWorld; import 'nodes.dart'; import 'optimize.dart'; -/// This phase computes the set of classes dispatched by an interceptor, and -/// simplifies interceptors in multiple ways: +/// This phase simplifies interceptors in multiple ways: /// /// 1) If the interceptor is for an object whose type is known, it -/// tries to use a constant interceptor instead. +/// tries to use a constant interceptor instead. /// /// 2) Interceptors are specialized based on the selector it is used with. /// /// 3) If we know the object is not intercepted, we just use the object -/// instead. +/// instead. +/// +/// 4) Single use interceptors at dynamic invoke sites are replaced with 'one +/// shot interceptors' which are synthesized static helper functions that fetch +/// the interceptor and then call the method. This saves code size and makes the +/// receiver of an intercepted call a candidate for being generated at use site. /// class SsaSimplifyInterceptors extends HBaseVisitor implements OptimizationPhase { diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart index 502d335c9e8..ea01915193c 100644 --- a/pkg/compiler/lib/src/ssa/locals_handler.dart +++ b/pkg/compiler/lib/src/ssa/locals_handler.dart @@ -288,7 +288,9 @@ class LocalsHandler { element.isGenerativeConstructor && _nativeData.isNativeOrExtendsNative(cls); if (_interceptorData.isInterceptedMethod(element)) { - SyntheticLocal parameter = createLocal('receiver'); + bool isInterceptedClass = _interceptorData.isInterceptedClass(cls); + String name = isInterceptedClass ? 'receiver' : '_'; + SyntheticLocal parameter = createLocal(name); HParameterValue value = new HParameterValue(parameter, getTypeOfThis()); builder.graph.explicitReceiverParameter = value; builder.graph.entry.addAfter(directLocals[scopeInfo.thisLocal], value); @@ -296,7 +298,10 @@ class LocalsHandler { // If this is the first parameter inserted, make sure it stays first. builder.lastAddedParameter = value; } - directLocals[scopeInfo.thisLocal] = value; + if (isInterceptedClass) { + // Only use the extra parameter in intercepted classes. + directLocals[scopeInfo.thisLocal] = value; + } } else if (isNativeUpgradeFactory) { SyntheticLocal parameter = createLocal('receiver'); // Unlike `this`, receiver is nullable since direct calls to generative diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart index 8a819a19ed2..e0f7369a095 100644 --- a/pkg/compiler/lib/src/ssa/optimize.dart +++ b/pkg/compiler/lib/src/ssa/optimize.dart @@ -34,7 +34,6 @@ import '../universe/use.dart' show StaticUse; import '../util/util.dart'; import '../world.dart' show JClosedWorld; import 'interceptor_simplifier.dart'; -import 'interceptor_finalizer.dart'; import 'logging.dart'; import 'nodes.dart'; import 'types.dart'; @@ -130,9 +129,9 @@ class SsaOptimizerTask extends CompilerTask { ]; phases.forEach(runPhase); - // Simplifying interceptors is just an optimization, it is required for - // implementation correctness because the code generator assumes it is - // always performed to compute the intercepted classes sets. + // Simplifying interceptors is not strictly just an optimization, it is + // required for implementation correctness because the code generator + // assumes it is always performed. runPhase(new SsaSimplifyInterceptors(closedWorld, member.enclosingClass)); SsaDeadCodeEliminator dce = new SsaDeadCodeEliminator(closedWorld, this); @@ -164,13 +163,6 @@ class SsaOptimizerTask extends CompilerTask { } phases.forEach(runPhase); }); - - // SsaFinalizeInterceptors must always be run to ensure consistent calling - // conventions between SSA-generated code and other code fragments generated - // by the emitter. - // TODO(sra): Generate these other fragments via SSA, then this phase - // becomes an opt-in optimization. - runPhase(SsaFinalizeInterceptors(closedWorld)); } }