blob: 45e2c3baccb89c38210160d4b9201e219cf3e0fa [file] [log] [blame]
// 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.
// @dart = 2.10
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
bool validPostcondition(HGraph graph) => true;
@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 (_interceptorIsReceiver(node)) {
if (node.element != null) {
tryReplaceExplicitReceiverForTargetWithDummy(
node, node.selector, node.element);
} else {
tryReplaceExplicitReceiverForSelectorWithDummy(
node, node.selector, node.receiverType);
}
return;
}
// 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<HInstruction> 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;
if (_interceptorIsReceiver(node)) {
tryReplaceExplicitReceiverForTargetWithDummy(
node, node.selector, node.element);
}
}
@override
void visitInvokeGeneratorBody(HInvokeGeneratorBody node) {
// [HInvokeGeneratorBody] does not have an accurate [isInterceptorCall].
// Test the target first to ensure there are enough inputs.
if (usesSelfInterceptor(node.element) && _interceptorIsReceiver(node)) {
tryReplaceExplicitReceiverForTargetWithDummy(node, null, node.element);
}
}
@override
void visitOneShotInterceptor(HOneShotInterceptor node) {
throw StateError('Should not see HOneShotInterceptor: $node');
}
bool _interceptorIsReceiver(HInvoke node) {
// This assignment of inputs is uniform for HInvokeDynamic, HInvokeSuper and
// HInvokeGeneratorBody.
HInstruction interceptor = node.inputs[0];
HInstruction receiverArgument = node.inputs[1];
return interceptor.nonCheck() == receiverArgument.nonCheck();
}
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;
// 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) {
ConstantValue constant = DummyInterceptorConstantValue();
HConstant dummy = _graph.addConstant(constant, _closedWorld);
node.replaceInput(receiverIndex, dummy);
}
}
/// A simple Entity to rename the unused receiver to `_` in non-minified code.
class _RenameToUnderscore implements Entity {
const _RenameToUnderscore();
@override
String get name => '_';
}