From f503281917b74ac0571bd4bc5c37789006cdef8e Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Fri, 22 Jul 2022 19:45:07 +0000 Subject: [PATCH] [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 Reviewed-by: Sigmund Cherem --- pkg/compiler/doc/pragmas.md | 211 ++++++++++++++++++ .../lib/src/js_backend/annotations.dart | 82 +++---- pkg/compiler/test/annotations/data/basic.dart | 10 + 3 files changed, 265 insertions(+), 38 deletions(-) create mode 100644 pkg/compiler/doc/pragmas.md diff --git a/pkg/compiler/doc/pragmas.md b/pkg/compiler/doc/pragmas.md new file mode 100644 index 00000000000..60fde4f6ab6 --- /dev/null +++ b/pkg/compiler/doc/pragmas.md @@ -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(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(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. diff --git a/pkg/compiler/lib/src/js_backend/annotations.dart b/pkg/compiler/lib/src/js_backend/annotations.dart index 048ec241f3d..91fa09e498e 100644 --- a/pkg/compiler/lib/src/js_backend/annotations.dart +++ b/pkg/compiler/lib/src/js_backend/annotations.dart @@ -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 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 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, diff --git a/pkg/compiler/test/annotations/data/basic.dart b/pkg/compiler/test/annotations/data/basic.dart index 5ffc8709148..4b9fea0107a 100644 --- a/pkg/compiler/test/annotations/data/basic.dart +++ b/pkg/compiler/test/annotations/data/basic.dart @@ -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() {}