Skip to content

Commit e962350

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Simply is-test when type parameters not needed
Sometimes the type expression of a is-test can be widened to one that does not use type variables: ```dart void addAll(Iterable<E> items) { if (items is List<E>) { ... items[i] ... // code specialized to lists } ... } ``` Changing `is List<E>` to `is List` is more efficient, but not possible at the source level since the explicit type parameter is needed for type promotion. This change uses a predicate provided by the Kernel package to widen an interface type when generating the CFG instructions for the is-test. This can lead to knock-on optimizations like compiling `is X` to `instanceof`. Bug: #54998 Change-Id: I956b9d9b8a31ae40aee86e31def475baa9ab5cfe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408124 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent 7349ef5 commit e962350

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

pkg/compiler/lib/src/ssa/builder.dart

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7717,7 +7717,53 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
77177717
void visitIsExpression(ir.IsExpression node) {
77187718
node.operand.accept(this);
77197719
HInstruction expression = pop();
7720-
_pushIsTest(node.type, expression, _sourceInformationBuilder.buildIs(node));
7720+
_pushIsTest(
7721+
_widenCheckedTypeForOperand(node.type, node.operand),
7722+
expression,
7723+
_sourceInformationBuilder.buildIs(node),
7724+
);
7725+
}
7726+
7727+
/// Returns a type that is equivalent to `checkedType`, but possibly simpler.
7728+
/// For example, if operand has static type `Iterable<E>` and checkedType is
7729+
/// `List<E>`, returns `List<dynamic>` since that is an easier test that does
7730+
/// not reference any type variables.
7731+
ir.DartType _widenCheckedTypeForOperand(
7732+
ir.DartType checkedType,
7733+
ir.Expression operand,
7734+
) {
7735+
if (checkedType is ir.InterfaceType) {
7736+
if (checkedType.typeArguments.isEmpty) return checkedType;
7737+
final operandType = operand.getStaticType(
7738+
_currentFrame!.staticTypeContext!,
7739+
);
7740+
final sufficiency = closedWorld.elementMap.typeEnvironment
7741+
.computeTypeShapeCheckSufficiency(
7742+
expressionStaticType: operandType,
7743+
// Add `?` in case operand is nullable:
7744+
checkTargetType: checkedType.withDeclaredNullability(
7745+
ir.Nullability.nullable,
7746+
),
7747+
subtypeCheckMode: ir.SubtypeCheckMode.withNullabilities,
7748+
);
7749+
7750+
// If `true` the caller only needs to check nullabillity and the actual
7751+
// concrete class, no need to check [testedAgainstType] arguments.
7752+
if (sufficiency == ir.TypeShapeCheckSufficiency.interfaceShape) {
7753+
return ir.InterfaceType(
7754+
checkedType.classNode,
7755+
(operandType is ir.InterfaceType &&
7756+
operandType.nullability == ir.Nullability.nonNullable)
7757+
? ir.Nullability.nonNullable
7758+
: checkedType.nullability,
7759+
[
7760+
for (final parameter in checkedType.classNode.typeParameters)
7761+
parameter.defaultType,
7762+
],
7763+
);
7764+
}
7765+
}
7766+
return checkedType;
77217767
}
77227768

77237769
void _pushIsTest(
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// `is` tests against some parameterized interface types are equivalent to a
6+
// weaker test for just the interace. The weaker test is usually more efficient,
7+
// sometimes being compiled to `instanceof`.
8+
9+
class Base<T> {
10+
/*member: Base.test1:function(other) {
11+
if (other instanceof A.D1)
12+
return other.foo$0();
13+
return "other";
14+
}*/
15+
@pragma('dart2js:parameter:trust')
16+
String test1(Base<T> other) {
17+
if (other is D1<T>) return other.foo();
18+
return 'other';
19+
}
20+
21+
/*member: Base.test1qn:function(other) {
22+
if (other instanceof A.D1)
23+
return other.foo$0();
24+
return "other";
25+
}*/
26+
@pragma('dart2js:parameter:trust')
27+
String test1qn(Base<T>? other) {
28+
if (other is D1<T>) return other.foo();
29+
return 'other';
30+
}
31+
32+
/*member: Base.test1nq:function(other) {
33+
if (other instanceof A.D1)
34+
return other.method$0();
35+
return "other";
36+
}*/
37+
@pragma('dart2js:parameter:trust')
38+
String? test1nq(Base<T> other) {
39+
if (other is D1<T>?) return other.method(); // No promotion, so can't foo().
40+
return 'other';
41+
}
42+
43+
/*member: Base.test1qq:function(other) {
44+
var t1;
45+
if (type$.nullable_D1_dynamic._is(other)) {
46+
t1 = other.foo$0();
47+
return t1;
48+
}
49+
return "other";
50+
}*/
51+
@pragma('dart2js:parameter:trust')
52+
String? test1qq(Base<T>? other) {
53+
if (other is D1<T>?) return other?.foo();
54+
return 'other';
55+
}
56+
57+
/*member: Base.test2:function(other) {
58+
if (other instanceof A.D2)
59+
return other.bar$0();
60+
return "other";
61+
}*/
62+
@pragma('dart2js:parameter:trust')
63+
String test2(Base<T> other) {
64+
if (other is D2<T>) return other.bar();
65+
return 'other';
66+
}
67+
68+
@pragma('dart2js:never-inline')
69+
/*member: Base.method:ignore*/
70+
String method() => 'Base.method';
71+
}
72+
73+
class D1<T> extends Base<T> {
74+
@pragma('dart2js:never-inline')
75+
/*member: D1.foo:ignore*/
76+
String foo() => 'D1<$T>.foo';
77+
}
78+
79+
class D2<T> extends D1<T> {
80+
@pragma('dart2js:never-inline')
81+
/*member: D2.bar:ignore*/
82+
String bar() => 'D2.bar';
83+
}
84+
85+
/*member: main:ignore*/
86+
main() {
87+
final items = [Base<int>(), D1<int>(), D2<int>()];
88+
89+
for (final item in items) {
90+
print(item.test1(items.first));
91+
print(item.test1(item));
92+
93+
print(item.test1qn(items.first));
94+
print(item.test1qn(item));
95+
print(item.test1nq(items.first));
96+
print(item.test1nq(item));
97+
print(item.test1qq(items.first));
98+
print(item.test1qq(item));
99+
100+
print(item.test2(items.first));
101+
print(item.test2(item));
102+
}
103+
}

0 commit comments

Comments
 (0)