Skip to content

Commit b00c777

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] allow-cse and allow-dce annotations
Add `@pragma('dart2js:allow-cse')` and `@pragma('dart2js:allow-dce')`. These annotations allow the compiler to do common subexpression elimination and dead code elimination on calls to getters and methods. Change-Id: Ie4091466e4782a4d28a7f9bfbfdb60a5de45a384 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426360 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Stephen Adams <[email protected]>
1 parent e688981 commit b00c777

File tree

9 files changed

+510
-35
lines changed

9 files changed

+510
-35
lines changed

pkg/compiler/doc/pragmas.md

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ understands potential repercussions.
3535
| `dart2js:parameter:trust` | TBD |
3636
| `dart2js:types:check` | TBD |
3737
| `dart2js:types:trust` | TBD |
38+
| `dart2js:allow-cse` | [Allow common subexpression elimination (CSE)](#allow-cse) |
39+
| `dart2js:allow-dce` | [Allow dead code elimination (DCE)](#allow-dce) |
3840

3941
## Pragmas for internal use
4042

@@ -45,8 +47,8 @@ only allowed within the core SDK libraries.
4547
| --- | --- |
4648
| `dart2js:assumeDynamic` | TBD |
4749
| `dart2js:disableFinal` | TBD |
48-
| `dart2js:noSideEffects` | Requires `dart2js:noInline` to work properly |
49-
| `dart2js:noThrows` | Requires `dart2js:noInline` to work properly |
50+
| `dart2js:noSideEffects` | Requires `dart2js:never-inline` to work properly |
51+
| `dart2js:noThrows` | Requires `dart2js:never-inline` to work properly |
5052

5153
## Detailed descriptions
5254

@@ -125,7 +127,7 @@ This annotation may be placed on a function or method.
125127

126128
Function inlining is disabled at call sites within the annotated function.
127129
Inlining is disabled even when the call site has a viable inlining candidate
128-
that is annotated with `@pragma('dart2js:tryInline')`.
130+
that is annotated with `@pragma('dart2js:prefer-inline')`.
129131

130132

131133
### Annotations related to run-time checks
@@ -157,13 +159,13 @@ casts in the body of the function are checked.
157159
One use of `dart2js:as:trust` is to construct an `unsafeCast` method.
158160

159161
```dart
160-
@pragma('dart2js:tryInline')
162+
@pragma('dart2js:prefer-inline')
161163
@pragma('dart2js:as:trust')
162164
T unsafeCast<T>(Object? o) => o as T;
163165
```
164166

165-
The `tryInline` pragma ensures that the function is inlined, removing the cost
166-
of the call and passing the type parameter `T`, and the `as:trust` pragma
167+
The `prefer-inline` pragma ensures that the function is inlined, removing the
168+
cost of the call and passing the type parameter `T`, and the `as:trust` pragma
167169
removes the code that does the check.
168170

169171
#### Downcasts
@@ -184,7 +186,7 @@ The `unsafeCast` method described above could also be written by trusting
184186
implicit downcasts.
185187

186188
```dart
187-
@pragma('dart2js:tryInline')
189+
@pragma('dart2js:prefer-inline')
188190
@pragma('dart2js:downcast:trust')
189191
T unsafeCast<T>(dynamic o) => o; // implicit downcast `as T`.
190192
```
@@ -230,6 +232,41 @@ is opt-in via the following annotation.
230232

231233
This annotation can be placed on a method, class or library.
232234

235+
### Annotations related to compiler optimizations
236+
237+
#### Allow CSE
238+
239+
**EXPERIMENTAL**: This annotation may be removed without notice.
240+
241+
This annotation may be placed on a method or getter. If, after some
242+
optimization, there are two calls to the same annotated method or getter, and
243+
the calls have the same input values, and the first call will always have
244+
happened by the time control flow reaches the second call, the compiler may
245+
choose to remove the second call and use the value returned by the first call
246+
instead.
247+
248+
```dart
249+
@pragma('dart2js:allow-cse')
250+
```
251+
252+
This annotation is intended for an `external` getter or method that always
253+
refers to the same thing.
254+
255+
#### Allow DCE
256+
257+
**EXPERIMENTAL**: This annotated may be removed without notice.
258+
259+
This annotation may be placed on a method or getter. If the result of the call
260+
to the method or getter is unused (or becomes unused due to other
261+
optimizations), the compiler may choose to remove the call.
262+
263+
```dart
264+
@pragma('dart2js:allow-dce')
265+
```
266+
267+
This annotation is intended for and `external` getter or method that makes no
268+
observable change.
269+
233270
### Annotations related to deferred library loading
234271

235272
#### Load priority

pkg/compiler/lib/src/js_backend/annotations.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ enum PragmaAnnotation {
9393
loadLibraryPriority('load-priority', hasOption: true),
9494
resourceIdentifier('resource-identifier'),
9595

96-
throwWithoutHelperFrame('stack-starts-at-throw');
96+
throwWithoutHelperFrame('stack-starts-at-throw'),
97+
98+
allowCSE('allow-cse'),
99+
allowDCE('allow-dce');
97100

98101
final String name;
99102
final bool forFunctionsOnly;
@@ -364,6 +367,14 @@ abstract class AnnotationsData {
364367
/// expression generates extra code to avoid having a runtime helper on the
365368
/// stack?
366369
bool throwWithoutHelperFrame(ir.TreeNode node);
370+
371+
/// Returns `true` if [member] has a `@pragma('dart2js:allow-cse')`
372+
/// annotation.
373+
bool allowCSE(MemberEntity member);
374+
375+
/// Returns `true` if [member] has a `@pragma('dart2js:allow-dce')`
376+
/// annotation.
377+
bool allowDCE(MemberEntity member);
367378
}
368379

369380
class AnnotationsDataImpl implements AnnotationsData {
@@ -668,6 +679,14 @@ class AnnotationsDataImpl implements AnnotationsData {
668679
}
669680
return false;
670681
}
682+
683+
@override
684+
bool allowCSE(MemberEntity member) =>
685+
_hasPragma(member, PragmaAnnotation.allowCSE);
686+
687+
@override
688+
bool allowDCE(MemberEntity member) =>
689+
_hasPragma(member, PragmaAnnotation.allowDCE);
671690
}
672691

673692
class AnnotationsDataBuilder {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6804,6 +6804,8 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
68046804
);
68056805
}
68066806
instruction.sideEffects = _inferredData.getSideEffectsOfElement(target);
6807+
instruction.allowCSE = closedWorld.annotationsData.allowCSE(target);
6808+
instruction.allowDCE = closedWorld.annotationsData.allowDCE(target);
68076809
push(instruction);
68086810
}
68096811

@@ -6973,6 +6975,11 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
69736975
}
69746976
}
69756977

6978+
if (element != null) {
6979+
invoke.allowCSE = closedWorld.annotationsData.allowCSE(element);
6980+
invoke.allowDCE = closedWorld.annotationsData.allowDCE(element);
6981+
}
6982+
69766983
if (node is ir.InstanceInvocation ||
69776984
node is ir.FunctionInvocation ||
69786985
node is ir.InstanceGet) {
@@ -7893,6 +7900,14 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
78937900
return false;
78947901
}
78957902

7903+
// Don't inline functions marked with 'allow-cse' and 'allow-dce' since we
7904+
// need the call instruction to do these optimizations. We might be able to
7905+
// inline simple methods afterwards.
7906+
if (closedWorld.annotationsData.allowCSE(function) ||
7907+
closedWorld.annotationsData.allowDCE(function)) {
7908+
return false;
7909+
}
7910+
78967911
bool insideLoop = loopDepth > 0 || graph.calledInLoop;
78977912

78987913
// Bail out early if the inlining decision is in the cache and we can't

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

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,11 @@ abstract class HInstruction implements SpannableWithEntity {
11751175
SideEffects sideEffects = SideEffects.empty();
11761176
bool _useGvn = false;
11771177

1178+
// TODO(sra): Consider whether to reduce instruction size by collecting all
1179+
// these instruction flags into a bitmask.
1180+
bool _allowCSE = false;
1181+
bool _allowDCE = false;
1182+
11781183
// Main constructor copies the list of inputs to ensure ownership.
11791184
HInstruction(List<HInstruction> initialInputs, this.instructionType)
11801185
: inputs = [...initialInputs];
@@ -1219,6 +1224,26 @@ abstract class HInstruction implements SpannableWithEntity {
12191224
!canThrow(domain);
12201225
}
12211226

1227+
/// `true` if an instruction can be eliminated as a common subexpression -
1228+
/// when the instruction is equivalent to an earlier instruction may be
1229+
/// replaced by the value of the earlier instruction. Equivalent means that
1230+
/// the instruction has exactly the same inputs.
1231+
///
1232+
/// This property is set on function invocations on the basis of annotations
1233+
/// and program analysis. Usually, `allowCSE` means that the instruction is
1234+
/// idempotent - a second equivalent instruction returns the same value as the
1235+
/// first instruction without additional observable effects.
1236+
///
1237+
/// If an instruction is pure, it should be marked as `useGvn` instead.
1238+
bool get allowCSE => _allowCSE;
1239+
1240+
/// `true` if the instruction may be removed when the value is unused.
1241+
///
1242+
/// This property is set on function invocations on the basis of annotations
1243+
/// and program analysis. Usually `allowDCE` means that the instruction has
1244+
/// no observable effect.
1245+
bool get allowDCE => _allowDCE;
1246+
12221247
/// An instruction is an 'allocation' is it is the sole alias for an object.
12231248
/// This applies to instructions that allocate new objects and can be extended
12241249
/// to methods that return other allocations without escaping them.
@@ -1315,11 +1340,11 @@ abstract class HInstruction implements SpannableWithEntity {
13151340
bool isInBasicBlock() => block != null;
13161341

13171342
bool gvnEquals(HInstruction other) {
1318-
assert(useGvn() && other.useGvn());
13191343
// Check that the type and the sideEffects match.
13201344
bool hasSameType = typeEquals(other);
13211345
assert(hasSameType == (_gvnType == other._gvnType));
13221346
if (!hasSameType) return false;
1347+
assert((useGvn() && other.useGvn()) || (allowCSE && other.allowCSE));
13231348
if (sideEffects != other.sideEffects) return false;
13241349
// Check that the inputs match.
13251350
final int inputsLength = inputs.length;
@@ -1344,7 +1369,7 @@ abstract class HInstruction implements SpannableWithEntity {
13441369
}
13451370

13461371
// These methods should be overwritten by instructions that
1347-
// participate in global value numbering.
1372+
// participate in global value numbering or allowCSE.
13481373
_GvnType get _gvnType => _GvnType.undefined;
13491374
bool typeEquals(covariant HInstruction other) => false;
13501375
bool dataEquals(covariant HInstruction other) => false;
@@ -1496,6 +1521,18 @@ abstract class HInstruction implements SpannableWithEntity {
14961521
String toString() => '$runtimeType()';
14971522
}
14981523

1524+
mixin HasSettableAllowCSE on HInstruction {
1525+
set allowCSE(bool value) {
1526+
_allowCSE = value;
1527+
}
1528+
}
1529+
1530+
mixin HasSettableAllowDCE on HInstruction {
1531+
set allowDCE(bool value) {
1532+
_allowDCE = value;
1533+
}
1534+
}
1535+
14991536
/// An interface implemented by certain kinds of [HInstruction]. This makes it
15001537
/// possible to discover which annotations were in force in the code from which
15011538
/// the instruction originated.
@@ -1868,7 +1905,8 @@ class HCreateBox extends HInstruction {
18681905
String toString() => 'HCreateBox()';
18691906
}
18701907

1871-
abstract class HInvoke extends HInstruction {
1908+
abstract class HInvoke extends HInstruction
1909+
with HasSettableAllowCSE, HasSettableAllowDCE {
18721910
bool _isAllocation = false;
18731911

18741912
/// [isInterceptedCall] is true if this invocation uses the interceptor
@@ -2207,6 +2245,10 @@ class HInvokeStatic extends HInvoke {
22072245

22082246
@override
22092247
_GvnType get _gvnType => _GvnType.invokeStatic;
2248+
@override
2249+
bool typeEquals(other) => other is HInvokeStatic;
2250+
@override
2251+
bool dataEquals(HInvokeStatic other) => element == other.element;
22102252

22112253
@override
22122254
String toString() => 'invoke static: $element';
@@ -3938,9 +3980,10 @@ class HNullCheck extends HCheck {
39383980

39393981
@override
39403982
String toString() {
3983+
String stickyString = sticky ? 'sticky, ' : '';
39413984
String fieldString = field == null ? '' : ', $field';
39423985
String selectorString = selector == null ? '' : ', $selector';
3943-
return 'HNullCheck($checkedInput$fieldString$selectorString)';
3986+
return 'HNullCheck($stickyString$checkedInput$fieldString$selectorString)';
39443987
}
39453988
}
39463989

0 commit comments

Comments
 (0)