diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart index 81f17612313..f6928ab5810 100644 --- a/pkg/compiler/lib/src/ssa/codegen.dart +++ b/pkg/compiler/lib/src/ssa/codegen.dart @@ -414,8 +414,7 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { assert(graph.isValid(), 'Graph not valid after ${phase.name}'); } - runPhase( - new SsaInstructionSelection(_options, _closedWorld, _interceptorData)); + runPhase(new SsaInstructionSelection(_options, _closedWorld)); 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 d6439369dab..5bf162b9da2 100644 --- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart +++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart @@ -5,7 +5,6 @@ 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; @@ -27,21 +26,14 @@ 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, this._interceptorData); + SsaInstructionSelection(this._options, this._closedWorld); AbstractValueDomain get _abstractValueDomain => _closedWorld.abstractValueDomain; @@ -228,128 +220,6 @@ 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 new file mode 100644 index 00000000000..9ec829d8d94 --- /dev/null +++ b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart @@ -0,0 +1,240 @@ +// 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 968cf4f1eaf..c0b6254e01f 100644 --- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart +++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart @@ -12,20 +12,16 @@ import '../world.dart' show JClosedWorld; import 'nodes.dart'; import 'optimize.dart'; -/// This phase simplifies interceptors in multiple ways: +/// This phase computes the set of classes dispatched by an interceptor, and +/// 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. -/// -/// 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. +/// instead. /// 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 ea01915193c..502d335c9e8 100644 --- a/pkg/compiler/lib/src/ssa/locals_handler.dart +++ b/pkg/compiler/lib/src/ssa/locals_handler.dart @@ -288,9 +288,7 @@ class LocalsHandler { element.isGenerativeConstructor && _nativeData.isNativeOrExtendsNative(cls); if (_interceptorData.isInterceptedMethod(element)) { - bool isInterceptedClass = _interceptorData.isInterceptedClass(cls); - String name = isInterceptedClass ? 'receiver' : '_'; - SyntheticLocal parameter = createLocal(name); + SyntheticLocal parameter = createLocal('receiver'); HParameterValue value = new HParameterValue(parameter, getTypeOfThis()); builder.graph.explicitReceiverParameter = value; builder.graph.entry.addAfter(directLocals[scopeInfo.thisLocal], value); @@ -298,10 +296,7 @@ class LocalsHandler { // If this is the first parameter inserted, make sure it stays first. builder.lastAddedParameter = value; } - if (isInterceptedClass) { - // Only use the extra parameter in intercepted classes. - directLocals[scopeInfo.thisLocal] = value; - } + 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 e0f7369a095..8a819a19ed2 100644 --- a/pkg/compiler/lib/src/ssa/optimize.dart +++ b/pkg/compiler/lib/src/ssa/optimize.dart @@ -34,6 +34,7 @@ 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'; @@ -129,9 +130,9 @@ class SsaOptimizerTask extends CompilerTask { ]; phases.forEach(runPhase); - // Simplifying interceptors is not strictly just an optimization, it is - // required for implementation correctness because the code generator - // assumes it is always performed. + // 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. runPhase(new SsaSimplifyInterceptors(closedWorld, member.enclosingClass)); SsaDeadCodeEliminator dce = new SsaDeadCodeEliminator(closedWorld, this); @@ -163,6 +164,13 @@ 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)); } }