[dart2js] Document some dart2js @pragma annotations

Also add `never-inline` and `prefer-inline` aliases.

Change-Id: If5d932e3b86b78b2177abac79ac6c49fb9b8ed6f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252500
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Stephen Adams 2022-07-22 19:45:07 +00:00 committed by Commit Bot
parent 92bd9a4604
commit f503281917
3 changed files with 265 additions and 38 deletions

211
pkg/compiler/doc/pragmas.md Normal file
View file

@ -0,0 +1,211 @@
# Pragma Annotations understood by dart2js
## Pragmas for general use
| Pragma | Meaning |
| --- | --- |
| `dart2js:noInline` | [Never inline a function or method](#requesting-a-function-never-be-inlined) |
| `dart2js:never-inline` | Alias for `dart2js:noInline` |
| `dart2js:tryInline` | [Inline a function or method when possible](#requesting-a-function-be-inlined) |
| `dart2js:prefer-inline` | Alias for `dart2js:tryInline` |
| `dart2js:disable-inlining` | [Disable inlining within a method](#disabling-inlining) |
| `dart2js:noElision` | Disables an optimization whereby unused fields or unused parameters are removed |
## Unsafe pragmas for general use
These pragmas are available for use in third-party code but are potentially
unsafe. The use of these pragmas is discouraged unless the developer fully
understands potential repercussions.
| Pragma | Meaning |
| --- | --- |
| `dart2js:as:check` | [Check `as` casts](#casts) |
| `dart2js:as:trust` | [Trust `as` casts](#casts) |
| `dart2js:downcast:check` | [Check downcasts](#downcasts) |
| `dart2js:downcast:trust` | [Trust downcasts](#downcasts) |
| `dart2js:index-bounds:check` | TBD |
| `dart2js:index-bounds:trust` | TBD |
| `dart2js:late:check` | [Check late fields are used correctly](#late-checks) |
| `dart2js:late:trust` | [Trust late fields are used correctly](#late-checks) |
| `dart2js:parameter:check` | TBD |
| `dart2js:parameter:trust` | TBD |
| `dart2js:types:check` | TBD |
| `dart2js:types:trust` | TBD |
## Pragmas for internal use
These pragmas can cause unsound behavior if used incorrectly and therefore are
only allowed within the core SDK libraries.
| Pragma | Meaning |
| --- | --- |
| `dart2js:assumeDynamic` | TBD |
| `dart2js:disableFinal` | TBD |
| `dart2js:noSideEffects` | Requires `dart2js:noInline` to work properly |
| `dart2js:noThrows` | Requires `dart2js:noInline` to work properly |
## Detailed descriptions
### Annotations related to function inlining
Function (method) inlining is a compiler optimization where a call to a function
is replaced with the body of the function. To perform function inlining, the
compiler needs to determine that the call site calls exactly one function, the
target. This is trivial for top-level methods, static methods and
constructors. For calls to instance methods, the compiler does an analysis of
the possible types of the receiver and uses that to reduce the set of potential
targets. If there is a single target, it can potentially be inlined.
Not all functions can be inlined. For example, a recursive function cannot be
expanded by inlining indefinitely. `dart2js` will not inline functions complex
control flow, such as methods with exception handling (`try`-`catch`-`finally`)
or many return or throw exit points.
We say a function is a _viable inlining candidate_ when it is the single target
and it is possible to perform the inlining.
One benefit of inlining is that the execution cost of performing the call is
avoided, which can be a substantial part of the total cost of the call when the
body of the callee is simple. Copying instructions from the callee into the
caller can create more opportunities for optimization, for example, it becomes
possible to recognize and remove repeated operations.
The compiler automatically makes a decision whether or not to inline a function
or method based on heuristics. One heuristic is to inline if the the inlined
code is likely to be smaller that the call, as this results in a smaller _and_
faster program. Another heuristic is to inline even if the code is likely to be
slightly larger when the call is in a loop, as loops here is a chance that some
of the code can be hoisted out of the loop.
The annotations described below allow the developer to override the default
decisions. They should be used sparingly since it is likely that over time
manual overrides will become increasingly out of date and mismatched with the
evolving capabilities of the compiler.
#### Requesting a function be inlined
```dart
@pragma('dart2js:tryInline')
```
```dart
@pragma('dart2js:prefer-inline) // Alias for the above annotation.
```
This annotation may be placed on a function or method.
The compiler will inline the annotated function wherever it is a viable inlining
candidate.
#### Requesting a function never be inlined
```dart
@pragma('dart2js:noInline')
```
```dart
@pragma('dart2js:never-inline) // Alias for the above annotation.
```
This annotation may be placed on a function or method to prevent the function
from being inlined.
#### Disabling inlining
```dart
@pragma('dart2js:disable-inlining')
```
This annotation may be placed on a function or method.
Function inlining is disabled at call sites within the annotated function.
Inlining is disabled even when the call site has a viable inlining candidate
that is annotated with `@pragma('dart2js:tryInline')`.
### Annotations related to run-time checks
The Dart language and runtime libraries mandate checks in various places. Checks
result in some kind of `Error` exception being thrown. If a program has a high
degree of test coverage, the developer might have some confidence that the
checks will never fail. If this is the case, the checks can be disabled via
command line options or annotations. Annotations override the command line
settings.
Trusting (i.e. disabling) checks can lead to a smaller and faster program. The
cost is highly confusing unspecified behavior in place of the `Error`s that
would otherwise have been thrown. The unspecified behavior is not necessarily
consistent between runs and includes the program execution reaching statements
that are 'impossible' to reach and variables being assigned values of an
'impossible' type.
#### Casts
```dart
@pragma('dart2js:as:check')
@pragma('dart2js:as:trust')
```
These annotations may be placed on a function or method to control whether `as`
casts in the body of the function are checked.
One use of `dart2js:as:trust` is to construct an `unsafeCast` method.
```dart
@pragma('dart2js:tryInline')
@pragma('dart2js:as:trust')
T unsafeCast<T>(Object? o) => o as T;
```
The `tryInline` pragma ensures that the function is inlined, removing the cost
of the call and passing the type parameter `T`, and the `as:trust` pragma
removes the code that does the check.
#### Downcasts
```dart
@pragma('dart2js:downcast:check')
@pragma('dart2js:downcast:trust')
```
These annotations may be placed on a function or method to control whether
implicit downcasts in the body of the function are checked.
This is similar to the `dart2js:as:check` and `dart2js:as:trust` pragmas except
it applies to implicit downcasts. Implicit downcasts are `as` checks that are
inserted to cast from `dynamic`.
The `unsafeCast` method described above could also be written by trusting
implicit downcasts.
```dart
@pragma('dart2js:tryInline')
@pragma('dart2js:downcast:trust')
T unsafeCast<T>(dynamic o) => o; // implicit downcast `as T`.
```
Trusting implicit downcasts is part of the `-O3` and `-O4` optimization level
command line options. `dart2js:downcast:check` can be used to enable checking of
implicit downcasts in a method when it would otherwise be trusted due to the
command line options.
#### Late checks
Late checks - checking whether a late variable has been initialized - occur on
all late variables. The checks on late instance variables (i.e. late fields)
can be controlled via the following annotations.
```dart
@pragma('dart2js:late:check')
@pragma('dart2js:late:trust')
```
These annotations may be placed on the declaration of a late field, class, or
library. When placed on a class, the annotation applies to all late fields of
the class. When placed on a library, the annotation applies to all late fields
of all classes in the library. `dart2js:late` annotations are _scoped_: when
there are multiple annotations, the one nearest the late field wins.
In the future this annotation might be extended to apply to `late` local
variables, static variables, and top-level variables.

View file

@ -15,6 +15,10 @@ import '../options.dart';
import '../serialization/serialization.dart';
import '../util/enumset.dart';
/// `@pragma('dart2js:...')` annotations understood by dart2js.
///
/// Some of these annotations are (documented
/// elsewhere)[pkg/compiler/doc/pragmas.md].
class PragmaAnnotation {
final int _index;
final String name;
@ -171,6 +175,13 @@ class PragmaAnnotation {
noThrows: {noInline},
noSideEffects: {noInline},
};
static final Map<String, PragmaAnnotation> lookupMap = {
for (final annotation in values) annotation.name: annotation,
// Aliases
'never-inline': noInline,
'prefer-inline': tryInline,
};
}
ir.Library _enclosingLibrary(ir.TreeNode node) {
@ -196,46 +207,41 @@ EnumSet<PragmaAnnotation> processMemberAnnotations(
for (PragmaAnnotationData data in pragmaAnnotationData) {
String name = data.name;
String suffix = data.suffix;
bool found = false;
for (PragmaAnnotation annotation in PragmaAnnotation.values) {
if (annotation.name == suffix) {
found = true;
annotations.add(annotation);
final annotation = PragmaAnnotation.lookupMap[suffix];
if (annotation != null) {
annotations.add(annotation);
if (data.hasOptions) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
if (annotation.forFunctionsOnly) {
if (node is! ir.Procedure && node is! ir.Constructor) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for methods and constructors."
});
}
}
if (annotation.forFieldsOnly) {
if (node is! ir.Field) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for fields."
});
}
}
if (annotation.internalOnly && !platformAnnotationsAllowed) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "Unrecognized dart2js pragma @pragma('$name')"});
}
break;
if (data.hasOptions) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
}
if (!found) {
if (annotation.forFunctionsOnly) {
if (node is! ir.Procedure && node is! ir.Constructor) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for methods and constructors."
});
}
}
if (annotation.forFieldsOnly) {
if (node is! ir.Field) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node), MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for fields."
});
}
}
if (annotation.internalOnly && !platformAnnotationsAllowed) {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,
{'text': "Unrecognized dart2js pragma @pragma('$name')"});
}
} else {
reporter.reportErrorMessage(
computeSourceSpanFromTreeNode(node),
MessageKind.GENERIC,

View file

@ -7,6 +7,8 @@
main() {
noInline();
tryInline();
noInline2();
tryInline2();
noElision();
noThrows();
noSideEffects();
@ -23,6 +25,14 @@ noInline() {}
@pragma('dart2js:tryInline')
tryInline() {}
/*member: noInline2:noInline*/
@pragma('dart2js:never-inline')
noInline2() {}
/*member: tryInline2:tryInline*/
@pragma('dart2js:prefer-inline')
tryInline2() {}
/*member: noElision:noElision*/
@pragma('dart2js:noElision')
noElision() {}