[dart2js] Track, rather than compute, isCallOnInterceptor

By tracking `isCallOnInterceptor`, we avoid the need for class context
information in the `sourceElement` to compute the value. `sourceElement`
is now purely advisory to the choice of local names in codegen, and
can't be the wrong kind of element as in issue #60793.

Bug: #60793
Change-Id: I8bb68b6bf864a3a6f9f2beb40f68a6254431d49a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/432003
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
This commit is contained in:
Stephen Adams
2025-05-30 11:38:22 -07:00
committed by Commit Queue
parent 74f74d7157
commit d49cd3c552
9 changed files with 103 additions and 107 deletions
+1 -1
View File
@@ -2480,7 +2480,7 @@ class SsaCodeGenerator implements HVisitor<void>, HBlockInformationVisitor {
if (superElement is FieldEntity) {
// TODO(sra): We can lower these in the simplifier.
js.Name fieldName = _namer.instanceFieldPropertyName(superElement);
use(node.getDartReceiver(_closedWorld));
use(node.getDartReceiver());
js.PropertyAccess access =
js.PropertyAccess(
pop(),
@@ -111,7 +111,7 @@ class SsaInstructionSelection extends HBaseVisitor<HInstruction?>
// The instructionType of [nullCheck] is not nullable (since it is the
// (not) null check!) This means that if we do need to check the type, we
// should test against nullCheck.checkedInput, not the direct input.
if (current!.getDartReceiver(_closedWorld) == nullCheck) {
if (current!.getDartReceiver() == nullCheck) {
if (current is HFieldGet) return current;
if (current is HFieldSet) return current;
if (current is HGetLength) return current;
@@ -62,8 +62,8 @@ class SsaFinalizeInterceptors extends HBaseVisitor<void>
}
/// 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.
/// interceptor calling convention but the interceptor argument will always be
/// the instance.
bool usesSelfInterceptor(MemberEntity element) {
if (!_interceptorData.isInterceptedMethod(element)) return false;
ClassEntity cls = element.enclosingClass!;
@@ -101,6 +101,12 @@ class SsaFinalizeInterceptors extends HBaseVisitor<void>
thisParameter!.instructionType = receiverParameter!.instructionType;
receiverParameter.block!.rewrite(receiverParameter, thisParameter);
receiverParameter.sourceElement = const _RenameToUnderscore();
for (final instruction in thisParameter.usedBy) {
if (instruction is HInvoke) {
instruction.updateIsCallOnInterceptor();
}
}
}
@override
@@ -262,6 +268,7 @@ class SsaFinalizeInterceptors extends HBaseVisitor<void>
}
void _replaceReceiverArgumentWithDummy(HInvoke node, int receiverIndex) {
assert(!node.isCallOnInterceptor, 'node: $node');
ConstantValue constant = DummyInterceptorConstantValue();
HConstant dummy = _graph.addConstant(constant, _closedWorld);
node.replaceInput(receiverIndex, dummy);
@@ -266,7 +266,7 @@ class SsaSimplifyInterceptors extends HBaseVisitor<bool>
// If there is a call that dominates all other uses, we can use just the
// selector of that instruction.
if (dominator is HInvokeDynamic &&
dominator.isCallOnInterceptor(_closedWorld) &&
dominator.isCallOnInterceptor &&
node == dominator.receiver &&
useCount(dominator, node) == 1) {
interceptedClasses = _interceptorData.getInterceptedClassesOn(
@@ -303,7 +303,7 @@ class SsaSimplifyInterceptors extends HBaseVisitor<bool>
interceptedClasses = {};
for (HInstruction user in node.usedBy) {
if (user is HInvokeDynamic &&
user.isCallOnInterceptor(_closedWorld) &&
user.isCallOnInterceptor &&
node == user.receiver &&
useCount(user, node) == 1) {
interceptedClasses.addAll(
@@ -313,7 +313,7 @@ class SsaSimplifyInterceptors extends HBaseVisitor<bool>
),
);
} else if (user is HInvokeSuper &&
user.isCallOnInterceptor(_closedWorld) &&
user.isCallOnInterceptor &&
node == user.receiver &&
useCount(user, node) == 1) {
interceptedClasses.addAll(
@@ -402,7 +402,7 @@ class SsaSimplifyInterceptors extends HBaseVisitor<bool>
// }
void finishInvoke(HInvoke invoke, Selector selector) {
HInstruction callReceiver = invoke.getDartReceiver(_closedWorld)!;
HInstruction callReceiver = invoke.getDartReceiver()!;
if (receiver.nonCheck() == callReceiver.nonCheck()) {
Set<ClassEntity> interceptedClasses = _interceptorData
.getInterceptedClassesOn(selector.name, _closedWorld);
@@ -412,19 +412,20 @@ class SsaSimplifyInterceptors extends HBaseVisitor<bool>
interceptedClasses: interceptedClasses,
)) {
invoke.changeUse(node, callReceiver);
invoke.updateIsCallOnInterceptor();
}
}
}
for (HInstruction user in node.usedBy.toList()) {
if (user is HInvokeDynamic) {
if (user.isCallOnInterceptor(_closedWorld) &&
if (user.isCallOnInterceptor &&
node == user.inputs[0] &&
useCount(user, node) == 1) {
finishInvoke(user, user.selector);
}
} else if (user is HInvokeSuper) {
if (user.isCallOnInterceptor(_closedWorld) &&
if (user.isCallOnInterceptor &&
node == user.inputs[0] &&
useCount(user, node) == 1) {
finishInvoke(user, user.selector);
@@ -361,7 +361,7 @@ class IndexSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
var abstractValueDomain = closedWorld.abstractValueDomain;
if (receiver.isIndexablePrimitive(abstractValueDomain).isPotentiallyFalse) {
return null;
@@ -419,7 +419,7 @@ class CodeUnitAtSpecializer extends InvokeDynamicSpecializer {
OptimizationTestLog? log,
) {
final abstractValueDomain = closedWorld.abstractValueDomain;
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
if (receiver.isStringOrNull(abstractValueDomain).isPotentiallyFalse) {
return null;
}
@@ -460,7 +460,7 @@ class RemoveLastSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
final abstractValueDomain = closedWorld.abstractValueDomain;
if (receiver.isGrowableArray(abstractValueDomain).isPotentiallyFalse) {
return null;
@@ -876,7 +876,7 @@ class ModuloSpecializer extends BinaryArithmeticSpecializer {
// track -0.0 precisely, we have to syntactically filter inputs that cannot
// generate -0.0.
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
if (inputsArePositiveIntegers(instruction, closedWorld) &&
!canBeNegativeZero(receiver)) {
return HRemainder(
@@ -1840,7 +1840,7 @@ class CompareToSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
// `compareTo` has no side-effect (other than throwing) and can be GVN'ed
// for some known types.
if (receiver
@@ -1890,7 +1890,7 @@ abstract class IdempotentStringOperationSpecializer
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
if (receiver
.isStringOrNull(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
@@ -1937,7 +1937,7 @@ class PatternMatchSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
HInstruction pattern = instruction.inputs[2];
if (receiver
.isStringOrNull(closedWorld.abstractValueDomain)
@@ -1969,7 +1969,7 @@ class RoundSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
if (receiver
.isNumberOrNull(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
@@ -1997,7 +1997,7 @@ class ToIntSpecializer extends InvokeDynamicSpecializer {
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction receiver = instruction.getDartReceiver();
// We would like to reduce `x.toInt()` to `x`. The web platform considers
// infinities to be `int` values, but it is too hard to tell if an input is
+47 -64
View File
@@ -1334,7 +1334,7 @@ abstract class HInstruction implements SpannableWithEntity {
AbstractBool isPrimitiveOrNull(AbstractValueDomain domain) =>
domain.isPrimitiveOrNull(instructionType);
HInstruction? getDartReceiver(JClosedWorld closedWorld) => null;
HInstruction? getDartReceiver() => null;
bool onlyThrowsNSM() => false;
bool isInBasicBlock() => block != null;
@@ -1481,8 +1481,6 @@ abstract class HInstruction implements SpannableWithEntity {
bool isConstantFalse() => false;
bool isConstantTrue() => false;
bool isInterceptor(JClosedWorld closedWorld) => false;
bool isValid() {
HValidator validator = HValidator();
validator.currentBlock = block;
@@ -1914,6 +1912,12 @@ abstract class HInvoke extends HInstruction
/// input is the Dart receiver.
bool isInterceptedCall = false;
/// [_isCallOnInterceptor] is true if this invocation uses the interceptor
/// calling convention *and* the interceptor input is an interceptor, and not
/// the receiver. A call has `isInterceptedCall == true` and
/// `_isCallOnInterceptor == false` after the 'self interceptor' optimization.
bool _isCallOnInterceptor = false;
HInvoke(super.inputs, super.type) : super() {
sideEffects.setAllSideEffects();
sideEffects.setDependsOnSomething();
@@ -1929,6 +1933,30 @@ abstract class HInvoke extends HInstruction
void setAllocation(bool value) {
_isAllocation = value;
}
bool get isCallOnInterceptor => _isCallOnInterceptor;
/// Update 'isCallOnInterceptor'. An intercepted call can go through
/// refinements that drop references to unneeded values or arguments:
///
/// interceptor.foo(receiver, ...); // isCallOnInterceptor = true
/// -->
/// receiver.foo(receiver, ...); // isCallOnInterceptor = false
/// -->
/// receiver.foo(dummy, ...); // isCallOnInterceptor = false
void updateIsCallOnInterceptor() {
if (isInterceptedCall && _isCallOnInterceptor) {
final interceptor = inputs[0].nonCheck();
final receiver = inputs[1].nonCheck();
if (interceptor == receiver) {
_isCallOnInterceptor = false;
} else if (receiver case HConstant(
constant: DummyInterceptorConstantValue(),
)) {
_isCallOnInterceptor = false;
}
}
}
}
abstract class HInvokeDynamic extends HInvoke implements InstructionContext {
@@ -1978,6 +2006,8 @@ abstract class HInvokeDynamic extends HInvoke implements InstructionContext {
: const InvokeDynamicSpecializer(),
super(inputs, resultType) {
isInterceptedCall = isIntercepted;
_isCallOnInterceptor = isIntercepted;
updateIsCallOnInterceptor();
}
Selector get selector => _selector;
@@ -2047,18 +2077,13 @@ abstract class HInvokeDynamic extends HInvoke implements InstructionContext {
HInstruction get receiver => inputs[0];
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) {
return isCallOnInterceptor(closedWorld) ? inputs[1] : inputs[0];
HInstruction getDartReceiver() {
return _isCallOnInterceptor ? inputs[1] : inputs[0];
}
/// The type arguments passed in this dynamic invocation.
List<DartType> get typeArguments;
/// Returns whether this call is on an interceptor object.
bool isCallOnInterceptor(JClosedWorld closedWorld) {
return isInterceptedCall && receiver.isInterceptor(closedWorld);
}
@override
_GvnType get _gvnType => _GvnType.invokeDynamic;
@@ -2238,6 +2263,8 @@ class HInvokeStatic extends HInvoke {
bool isIntercepted = false,
}) : super(inputs, type) {
isInterceptedCall = isIntercepted;
_isCallOnInterceptor = isIntercepted;
updateIsCallOnInterceptor();
}
@override
@@ -2282,13 +2309,8 @@ class HInvokeSuper extends HInvokeStatic {
HInstruction get receiver => inputs[0];
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) {
return isCallOnInterceptor(closedWorld) ? inputs[1] : inputs[0];
}
/// Returns whether this call is on an interceptor object.
bool isCallOnInterceptor(JClosedWorld closedWorld) {
return isInterceptedCall && receiver.isInterceptor(closedWorld);
HInstruction getDartReceiver() {
return isCallOnInterceptor ? inputs[1] : inputs[0];
}
@override
@@ -2374,28 +2396,12 @@ class HFieldGet extends HFieldAccess {
}
}
@override
bool isInterceptor(JClosedWorld closedWorld) {
final entity = sourceElement;
// In case of a closure inside an interceptor class, JavaScript `this`, the
// interceptor, is stored in the generated closure class, and accessed
// through a [HFieldGet].
// TODO(sra): It would be better to track this as an explicit property
// rather than recover it from `sourceElement`.
if (entity is ThisLocal) {
return closedWorld.interceptorData.isInterceptedClass(
entity.enclosingClass,
);
}
return false;
}
@override
bool canThrow(AbstractValueDomain domain) =>
receiver.isNull(domain).isPotentiallyTrue;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -2425,7 +2431,7 @@ class HFieldSet extends HFieldAccess {
receiver.isNull(domain).isPotentiallyTrue;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -2478,7 +2484,7 @@ class HGetLength extends HInstruction {
receiver.isNull(domain).isPotentiallyTrue;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -2553,7 +2559,7 @@ class HReadModifyWrite extends HInstruction implements HLateInstruction {
receiver.isNull(domain).isPotentiallyTrue;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -3253,10 +3259,6 @@ class HConstant extends HInstruction {
@override
bool isConstantTrue() => constant is TrueConstantValue;
@override
bool isInterceptor(JClosedWorld closedWorld) =>
constant is InterceptorConstantValue;
// Maybe avoid this if the literal is big?
@override
bool isCodeMotionInvariant() => true;
@@ -3337,27 +3339,12 @@ class HThis extends HParameterValue {
// [element] can be null for some synthetic members, e.g. `$signature`.
HThis(ThisLocal? element, AbstractValue type) : super(element, type);
@override
ThisLocal? get sourceElement => super.sourceElement as ThisLocal?;
@override
set sourceElement(covariant ThisLocal? local) {
super.sourceElement = local;
}
@override
R accept<R>(HVisitor<R> visitor) => visitor.visitThis(this);
@override
bool isCodeMotionInvariant() => true;
@override
bool isInterceptor(JClosedWorld closedWorld) {
return closedWorld.interceptorData.isInterceptedClass(
sourceElement!.enclosingClass,
);
}
@override
String toString() => 'this';
}
@@ -3633,9 +3620,6 @@ class HInterceptor extends HInstruction {
inputs.add(constant);
}
@override
bool isInterceptor(JClosedWorld closedWorld) => true;
@override
_GvnType get _gvnType => _GvnType.interceptor;
@override
@@ -3670,9 +3654,8 @@ class HOneShotInterceptor extends HInvokeDynamic {
) : super(selector, receiverType, null, inputs, true, resultType) {
assert(inputs[0].isConstantNull());
assert(selector.callStructure.typeArgumentCount == typeArguments.length);
_isCallOnInterceptor = true;
}
@override
bool isCallOnInterceptor(JClosedWorld closedWorld) => true;
@override
String toString() =>
@@ -3767,7 +3750,7 @@ class HIndex extends HInstruction {
bool get isMovable => false;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
@@ -3806,7 +3789,7 @@ class HIndexAssign extends HInstruction {
bool get isMovable => false;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
@@ -3832,7 +3815,7 @@ class HCharCodeAt extends HInstruction {
bool get isMovable => false;
@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
+14 -23
View File
@@ -1042,7 +1042,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
HInstruction receiver = node.getDartReceiver();
AbstractValue receiverType = receiver.instructionType;
final element = _closedWorld.locateSingleMember(
node.selector,
@@ -1090,8 +1090,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
if (element is FieldEntity && element.name == node.selector.name) {
FieldEntity field = element;
if (!_nativeData.isNativeMember(field) &&
!node.isCallOnInterceptor(_closedWorld)) {
if (!_nativeData.isNativeMember(field) && !node.isCallOnInterceptor) {
// Insertion point for the closure call.
HInstruction insertionPoint = node;
HInstruction load;
@@ -1991,7 +1990,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
HInstruction folded = handleInterceptedCall(node);
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
HInstruction receiver = node.getDartReceiver();
AbstractValue receiverType = receiver.instructionType;
Selector selector = node.selector;
@@ -2102,7 +2101,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
HInstruction receiver = node.getDartReceiver();
AbstractValue receiverType = receiver.instructionType;
final member = node.element ??= _closedWorld.locateSingleMember(
node.selector,
@@ -2175,7 +2174,7 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
@override
HInstruction visitInvokeClosure(HInvokeClosure node) {
HInstruction closure = node.getDartReceiver(_closedWorld);
HInstruction closure = node.getDartReceiver();
// Replace indirect call to static method tear-off closure with direct call
// to static method.
@@ -3169,10 +3168,10 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
if (!instruction.onlyThrowsNSM()) return false;
final receiver = instruction.getDartReceiver(closedWorld);
final receiver = instruction.getDartReceiver();
HInstruction? current = instruction.next;
do {
if ((current!.getDartReceiver(closedWorld) == receiver) &&
if ((current!.getDartReceiver() == receiver) &&
current.canThrow(_abstractValueDomain)) {
return true;
}
@@ -3218,9 +3217,9 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
if (use is HFieldSet) {
// The use must be the receiver. Even if the use is also the argument,
// i.e. a.x = a, the store is still dead if all other uses are dead.
if (use.getDartReceiver(closedWorld) == instruction) return true;
if (use.getDartReceiver() == instruction) return true;
} else if (use is HFieldGet) {
assert(use.getDartReceiver(closedWorld) == instruction);
assert(use.getDartReceiver() == instruction);
if (isDeadCode(use)) return true;
}
return false;
@@ -3236,7 +3235,7 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
bool isTrivialDeadStore(HInstruction instruction) {
return instruction is HFieldSet &&
isTrivialDeadStoreReceiver(instruction.getDartReceiver(closedWorld));
isTrivialDeadStoreReceiver(instruction.getDartReceiver());
}
bool isDeadCode(HInstruction instruction) {
@@ -3335,11 +3334,7 @@ class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
final phiBlock = phi.block!;
phiBlock.rewrite(phi, replacement);
phiBlock.removePhi(phi);
if (replacement.sourceElement == null &&
phi.sourceElement != null &&
replacement is! HThis) {
replacement.sourceElement = phi.sourceElement;
}
replacement.sourceElement ??= phi.sourceElement;
return;
}
}
@@ -3714,11 +3709,7 @@ class SsaRedundantPhiEliminator implements OptimizationPhase {
final phiBlock = phi.block!;
phiBlock.rewrite(phi, candidate);
phiBlock.removePhi(phi);
if (candidate.sourceElement == null &&
phi.sourceElement != null &&
candidate is! HThis) {
candidate.sourceElement = phi.sourceElement;
}
candidate.sourceElement ??= phi.sourceElement;
}
}
@@ -4508,7 +4499,7 @@ class SsaLoadElimination extends HBaseVisitor<void>
@override
void visitFieldGet(HFieldGet node) {
FieldEntity element = node.element;
HInstruction receiver = node.getDartReceiver(_closedWorld).nonCheck();
HInstruction receiver = node.getDartReceiver().nonCheck();
_visitFieldGet(element, receiver, node);
}
@@ -4545,7 +4536,7 @@ class SsaLoadElimination extends HBaseVisitor<void>
@override
void visitFieldSet(HFieldSet node) {
FieldEntity element = node.element;
HInstruction receiver = node.getDartReceiver(_closedWorld).nonCheck();
HInstruction receiver = node.getDartReceiver().nonCheck();
if (memorySet.registerFieldValueUpdate(element, receiver, node.value)) {
node.block!.remove(node);
}
@@ -438,7 +438,7 @@ class SsaTypePropagator extends HBaseVisitor<AbstractValue>
pendingOptimizations.putIfAbsent(node, () => checkInputs);
}
HInstruction receiver = node.getDartReceiver(closedWorld);
HInstruction receiver = node.getDartReceiver();
AbstractValue receiverType = receiver.instructionType;
node.updateReceiverType(abstractValueDomain, receiverType);
+14
View File
@@ -0,0 +1,14 @@
// Copyright (c) 2025, 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.
void main() {
void foo() {
final f = switch (null) {
_ => foo,
};
print(f);
}
foo();
}