[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>
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 9be7a50..8a7efbe 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -2480,7 +2480,7 @@
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(),
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index b0a2214..cc0d880 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -111,7 +111,7 @@
// 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;
diff --git a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
index fa4df4c..c429a5e 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
@@ -62,8 +62,8 @@
}
/// 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 @@
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 @@
}
void _replaceReceiverArgumentWithDummy(HInvoke node, int receiverIndex) {
+ assert(!node.isCallOnInterceptor, 'node: $node');
ConstantValue constant = DummyInterceptorConstantValue();
HConstant dummy = _graph.addConstant(constant, _closedWorld);
node.replaceInput(receiverIndex, dummy);
diff --git a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
index e3a6282..96a9bac 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
@@ -266,7 +266,7 @@
// 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 @@
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 @@
),
);
} else if (user is HInvokeSuper &&
- user.isCallOnInterceptor(_closedWorld) &&
+ user.isCallOnInterceptor &&
node == user.receiver &&
useCount(user, node) == 1) {
interceptedClasses.addAll(
@@ -402,7 +402,7 @@
// }
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 @@
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);
diff --git a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
index 300749e..5cd54c1 100644
--- a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
+++ b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
@@ -361,7 +361,7 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
- HInstruction receiver = instruction.getDartReceiver(closedWorld);
+ HInstruction receiver = instruction.getDartReceiver();
if (receiver
.isStringOrNull(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
@@ -1937,7 +1937,7 @@
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 @@
JClosedWorld closedWorld,
OptimizationTestLog? log,
) {
- HInstruction receiver = instruction.getDartReceiver(closedWorld);
+ HInstruction receiver = instruction.getDartReceiver();
if (receiver
.isNumberOrNull(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
@@ -1997,7 +1997,7 @@
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
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 73faa76..636be70 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -1334,7 +1334,7 @@
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 @@
bool isConstantFalse() => false;
bool isConstantTrue() => false;
- bool isInterceptor(JClosedWorld closedWorld) => false;
-
bool isValid() {
HValidator validator = HValidator();
validator.currentBlock = block;
@@ -1914,6 +1912,12 @@
/// 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 @@
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 @@
: const InvokeDynamicSpecializer(),
super(inputs, resultType) {
isInterceptedCall = isIntercepted;
+ _isCallOnInterceptor = isIntercepted;
+ updateIsCallOnInterceptor();
}
Selector get selector => _selector;
@@ -2047,18 +2077,13 @@
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 @@
bool isIntercepted = false,
}) : super(inputs, type) {
isInterceptedCall = isIntercepted;
+ _isCallOnInterceptor = isIntercepted;
+ updateIsCallOnInterceptor();
}
@override
@@ -2282,13 +2309,8 @@
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
@@ -2375,27 +2397,11 @@
}
@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 @@
receiver.isNull(domain).isPotentiallyTrue;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -2478,7 +2484,7 @@
receiver.isNull(domain).isPotentiallyTrue;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -2553,7 +2559,7 @@
receiver.isNull(domain).isPotentiallyTrue;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@@ -3253,10 +3259,6 @@
@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;
@@ -3338,27 +3340,12 @@
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';
}
@@ -3634,9 +3621,6 @@
}
@override
- bool isInterceptor(JClosedWorld closedWorld) => true;
-
- @override
_GvnType get _gvnType => _GvnType.interceptor;
@override
bool typeEquals(other) => other is HInterceptor;
@@ -3670,9 +3654,8 @@
) : 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 @@
bool get isMovable => false;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
@@ -3806,7 +3789,7 @@
bool get isMovable => false;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
@@ -3832,7 +3815,7 @@
bool get isMovable => false;
@override
- HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
+ HInstruction getDartReceiver() => receiver;
@override
bool onlyThrowsNSM() => true;
@override
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 4936e8a..7eebfbf 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -1042,7 +1042,7 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
bool isTrivialDeadStore(HInstruction instruction) {
return instruction is HFieldSet &&
- isTrivialDeadStoreReceiver(instruction.getDartReceiver(closedWorld));
+ isTrivialDeadStoreReceiver(instruction.getDartReceiver());
}
bool isDeadCode(HInstruction instruction) {
@@ -3335,11 +3334,7 @@
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 @@
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 @@
@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 @@
@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);
}
diff --git a/pkg/compiler/lib/src/ssa/types_propagation.dart b/pkg/compiler/lib/src/ssa/types_propagation.dart
index f6692b9..8921a96 100644
--- a/pkg/compiler/lib/src/ssa/types_propagation.dart
+++ b/pkg/compiler/lib/src/ssa/types_propagation.dart
@@ -438,7 +438,7 @@
pendingOptimizations.putIfAbsent(node, () => checkInputs);
}
- HInstruction receiver = node.getDartReceiver(closedWorld);
+ HInstruction receiver = node.getDartReceiver();
AbstractValue receiverType = receiver.instructionType;
node.updateReceiverType(abstractValueDomain, receiverType);
diff --git a/tests/web/regress/issue/60793_test.dart b/tests/web/regress/issue/60793_test.dart
new file mode 100644
index 0000000..bdfdd19
--- /dev/null
+++ b/tests/web/regress/issue/60793_test.dart
@@ -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();
+}