[dart2js] loadLibrary priority annotation

Add annotations

    @pragma('dart2js:load-priority:normal')
    @pragma('dart2js:load-priority:high')

The test shows that these annotations are scoped.

This CL is just plumbing the annotation through as an argument to the runtime call to the code that implements `loadLibrary()`. Actual prioritization is not yet implemented.

Change-Id: Iff1404baf34192139dab95e2dbb01c2d4e8dae45
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270283
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2022-11-18 19:22:29 +00:00 committed by Commit Queue
parent e7c0280487
commit b8d85b7ad3
14 changed files with 382 additions and 7 deletions

View file

@ -10,6 +10,8 @@
| `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 |
| `dart2js:load-priority:normal` | [Affects deferred library loading](#load-priority) |
| `dart2js:load-priority:high` | [Affects deferred library loading](#load-priority) |
## Unsafe pragmas for general use
@ -209,3 +211,40 @@ 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.
### Annotations related to deferred library loading
#### Load priority
**This is not fully implemented.** The annotation exists but **has no effect**.
```dart
@pragma('dart2js:load-priority:normal')
@pragma('dart2js:load-priority:high)
```
By default, a call to `prefix.loadLibrary()` loads the library with 'normal'
priority. These annotations may be placed on the import specification to change
the priority for all calls to `prefix.loadLibrary()`.
The annotation my also be placed closer to the `loadLibrary()` call. When
placed on a method, the annotation affects all calls to
`prefix.loadLibrary()` inside the method.
When placed on a local variable, the annotation affects all calls to
`prefix.loadLibrary()` in the initializer of the local variable. In the
following example, only `prefix2` is loaded with high priority because of the
annotation on the variable called "`_`":
```dart
await prefix1.loadLibrary();
@pragma('dart2js:load-priority:high')
final _ = await prefix2.loadLibrary();
await prefix3.loadLibrary();
```
`dart2js:load-priority` annotations are _scoped_: when there are multiple
annotations, the one on the nearest element enclosing the call to `loadLibary()`
is in effect.

View file

@ -5,6 +5,7 @@
library js_backend.backend.annotations;
import 'package:kernel/ast.dart' as ir;
import 'package:js_runtime/synced/load_library_priority.dart';
import '../common.dart';
import '../elements/entities.dart';
@ -130,6 +131,12 @@ class PragmaAnnotation {
// TODO(45682): Make this annotation apply to local and static late variables.
static const PragmaAnnotation lateCheck = PragmaAnnotation(19, 'late:check');
static const PragmaAnnotation loadLibraryPriorityNormal =
PragmaAnnotation(20, 'load-priority:normal');
static const PragmaAnnotation loadLibraryPriorityHigh =
PragmaAnnotation(21, 'load-priority:high');
static const List<PragmaAnnotation> values = [
noInline,
tryInline,
@ -151,6 +158,8 @@ class PragmaAnnotation {
indexBoundsCheck,
lateTrust,
lateCheck,
loadLibraryPriorityNormal,
loadLibraryPriorityHigh,
];
static const Map<PragmaAnnotation, Set<PragmaAnnotation>> implies = {
@ -170,6 +179,8 @@ class PragmaAnnotation {
asCheck: {asTrust},
lateTrust: {lateCheck},
lateCheck: {lateTrust},
loadLibraryPriorityNormal: {loadLibraryPriorityHigh},
loadLibraryPriorityHigh: {loadLibraryPriorityNormal},
};
static const Map<PragmaAnnotation, Set<PragmaAnnotation>> requires = {
noThrows: {noInline},
@ -374,6 +385,9 @@ abstract class AnnotationsData {
// If we change our late field lowering to happen later, [node] could be the
// [ir.Field].
CheckPolicy getLateVariableCheckPolicyAt(ir.TreeNode node);
///
LoadLibraryPriority getLoadLibraryPriorityAt(ir.LoadLibrary node);
}
class AnnotationsDataImpl implements AnnotationsData {
@ -596,6 +610,28 @@ class AnnotationsDataImpl implements AnnotationsData {
processMemberAnnotations(_options, _reporter, node,
computePragmaAnnotationDataFromIr(node)));
}
@override
LoadLibraryPriority getLoadLibraryPriorityAt(ir.LoadLibrary node) {
// Annotation may be on enclosing declaration or on the import.
return _getLoadLibraryPriorityAt(_findContext(node)) ??
_getLoadLibraryPriorityAt(_findContext(node.import)) ??
LoadLibraryPriority.normal;
}
LoadLibraryPriority? _getLoadLibraryPriorityAt(DirectivesContext? context) {
while (context != null) {
EnumSet<PragmaAnnotation>? annotations = context.annotations;
if (annotations.contains(PragmaAnnotation.loadLibraryPriorityHigh)) {
return LoadLibraryPriority.high;
} else if (annotations
.contains(PragmaAnnotation.loadLibraryPriorityNormal)) {
return LoadLibraryPriority.normal;
}
context = context.parent;
}
return null;
}
}
class AnnotationsDataBuilder {
@ -659,7 +695,7 @@ class AnnotationsDataBuilder {
/// the library, but the shared root might be a good place to put a set of
/// annotations derived from the command-line.
///
/// If we ever introduce a singled annotation that means something different in
/// If we ever introduce a single annotation that means something different in
/// different positions (e.g. on a class vs. on a method), we might want to make
/// the [DirectivesContext] have a 'scope-kind'.
class DirectivesContext {

View file

@ -269,6 +269,7 @@ const requiredLibraries = <String, List<String>>{
'dart:_js_primitives',
'dart:_js_shared_embedded_names',
'dart:_late_helper',
'dart:_load_library_priority',
'dart:_metadata',
'dart:_native_typed_data',
'dart:_recipe_syntax',
@ -305,6 +306,7 @@ const requiredLibraries = <String, List<String>>{
'dart:_js_primitives',
'dart:_js_shared_embedded_names',
'dart:_late_helper',
'dart:_load_library_priority',
'dart:_native_typed_data',
'dart:_recipe_syntax',
'dart:_rti',

View file

@ -1719,9 +1719,17 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
_elementMap.getSpannable(targetElement, loadLibrary),
_elementMap.getImport(loadLibrary.import));
// TODO(efortuna): Source information!
final priority =
closedWorld.annotationsData.getLoadLibraryPriorityAt(loadLibrary);
final flag = priority.index;
push(HInvokeStatic(
_commonElements.loadDeferredLibrary,
[graph.addConstantString(loadId, closedWorld)],
[
graph.addConstantString(loadId, closedWorld),
graph.addConstantInt(flag, closedWorld)
],
_abstractValueDomain.nonNullType,
const <DartType>[],
targetCanThrow: false));

View file

@ -98,6 +98,9 @@ class AnnotationIrComputer extends IrDataExtractor<String> {
ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
return getMemberValue(info.callMethod);
}
if (node is ir.LoadLibrary) {
return _annotationData.getLoadLibraryPriorityAt(node).name;
}
return null;
}
}

View file

@ -0,0 +1,63 @@
// Copyright (c) 2022, 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.
import 'dart:collection' deferred as normal;
@pragma('dart2js:load-priority:high')
import 'dart:collection' deferred as high;
test1() async {
await normal. /*spec.invoke: normal*/ loadLibrary();
await high. /*spec.invoke: high*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _1 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _2 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _3 = await normal. /*spec.invoke: high*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _4 = await normal. /*spec.invoke: high*/ loadLibrary();
}
@pragma('dart2js:load-priority:high')
/*spec.member: testHigh:load-priority:high*/
testHigh() async {
await normal. /*spec.invoke: high*/ loadLibrary();
await high. /*spec.invoke: high*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _1 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _2 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _3 = await normal. /*spec.invoke: high*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _4 = await normal. /*spec.invoke: high*/ loadLibrary();
}
@pragma('dart2js:load-priority:normal')
/*spec.member: testNormal:load-priority:normal*/
testNormal() async {
await normal. /*spec.invoke: normal*/ loadLibrary();
await high. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _1 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:normal')
final _2 = await normal. /*spec.invoke: normal*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _3 = await normal. /*spec.invoke: high*/ loadLibrary();
@pragma('dart2js:load-priority:high')
final _4 = await normal. /*spec.invoke: high*/ loadLibrary();
}
main() async {
await test1();
await testHigh();
await testNormal();
}

View file

@ -77,6 +77,30 @@
"size": 59,
"outputUnit": "outputUnit/main",
"code": "B.JavaScriptObject_methods = J.JavaScriptObject.prototype;\n"
},
{
"id": "constant/B.List_gQW = A._setArrayType(makeConstList([B.LoadLibraryPriority_0, B.LoadLibraryPriority_1]), A.findType(\"JSArray<LoadLibraryPriority*>\"));\n",
"kind": "constant",
"name": "",
"size": 142,
"outputUnit": "outputUnit/main",
"code": "B.List_gQW = A._setArrayType(makeConstList([B.LoadLibraryPriority_0, B.LoadLibraryPriority_1]), A.findType(\"JSArray<LoadLibraryPriority*>\"));\n"
},
{
"id": "constant/B.LoadLibraryPriority_0 = new A.LoadLibraryPriority(0, \"normal\");\n",
"kind": "constant",
"name": "",
"size": 66,
"outputUnit": "outputUnit/main",
"code": "B.LoadLibraryPriority_0 = new A.LoadLibraryPriority(0, \"normal\");\n"
},
{
"id": "constant/B.LoadLibraryPriority_1 = new A.LoadLibraryPriority(1, \"high\");\n",
"kind": "constant",
"name": "",
"size": 64,
"outputUnit": "outputUnit/main",
"code": "B.LoadLibraryPriority_1 = new A.LoadLibraryPriority(1, \"high\");\n"
}],
deferredFiles=[{
"main.dart": {
@ -93,7 +117,7 @@
"id": "library/memory:sdk/tests/web/native/main.dart::",
"kind": "library",
"name": "<unnamed>",
"size": 301,
"size": 304,
"children": [
"function/memory:sdk/tests/web/native/main.dart::main"
],
@ -236,7 +260,80 @@
import 'lib.dart' deferred as lib;
/*member: main:
/*spec.member: main:
closure=[{
"id": "closure/memory:sdk/tests/web/native/main.dart::main.main_closure",
"kind": "closure",
"name": "main_closure",
"size": 201,
"outputUnit": "outputUnit/main",
"parent": "function/memory:sdk/tests/web/native/main.dart::main",
"function": "function/memory:sdk/tests/web/native/main.dart::main.main_closure.call"
}],
function=[
{
"id": "function/memory:sdk/tests/web/native/main.dart::main",
"kind": "function",
"name": "main",
"size": 304,
"outputUnit": "outputUnit/main",
"parent": "library/memory:sdk/tests/web/native/main.dart::",
"children": [
"closure/memory:sdk/tests/web/native/main.dart::main.main_closure"
],
"modifiers": {
"static": false,
"const": false,
"factory": false,
"external": false
},
"returnType": "dynamic",
"inferredReturnType": "[exact=_Future]",
"parameters": [],
"sideEffects": "SideEffects(reads anything; writes anything)",
"inlinedCount": 0,
"code": "main() {\n return A.loadDeferredLibrary(\"lib\", 0).then$1$1(new A.main_closure(), type$.Null);\n }",
"type": "dynamic Function()",
"functionKind": 0
},
{
"id": "function/memory:sdk/tests/web/native/main.dart::main.main_closure.call",
"kind": "function",
"name": "call",
"size": 84,
"outputUnit": "outputUnit/main",
"parent": "closure/memory:sdk/tests/web/native/main.dart::main.main_closure",
"children": [],
"modifiers": {
"static": false,
"const": false,
"factory": false,
"external": false
},
"returnType": "Null",
"inferredReturnType": "[null]",
"parameters": [
{
"name": "_",
"type": "[null|subclass=Object]",
"declaredType": "dynamic"
}
],
"sideEffects": "SideEffects(reads anything; writes anything)",
"inlinedCount": 0,
"code": "call$1(_) {\n A.checkDeferredIsLoaded(\"lib\");\n C.C_Deferred.call$0();\n }",
"type": "Null Function(dynamic)",
"functionKind": 2
}],
holding=[
{"id":"function/dart:_js_helper::loadDeferredLibrary","mask":null},
{"id":"function/dart:_rti::_setArrayType","mask":null},
{"id":"function/dart:_rti::findType","mask":null},
{"id":"function/dart:async::_Future.then","mask":"[exact=_Future]"},
{"id":"function/memory:sdk/tests/web/native/main.dart::main.main_closure.call","mask":null},
{"id":"function/memory:sdk/tests/web/native/main.dart::main.main_closure.call","mask":null}]
*/
/*canary.member: main:
closure=[{
"id": "closure/memory:sdk/tests/web/native/main.dart::main.main_closure",
"kind": "closure",

View file

@ -83,6 +83,30 @@
"size": 59,
"outputUnit": "outputUnit/main",
"code": "B.JavaScriptObject_methods = J.JavaScriptObject.prototype;\n"
},
{
"id": "constant/B.List_gQW = A._setArrayType(makeConstList([B.LoadLibraryPriority_0, B.LoadLibraryPriority_1]), A.findType(\"JSArray<LoadLibraryPriority*>\"));\n",
"kind": "constant",
"name": "",
"size": 142,
"outputUnit": "outputUnit/main",
"code": "B.List_gQW = A._setArrayType(makeConstList([B.LoadLibraryPriority_0, B.LoadLibraryPriority_1]), A.findType(\"JSArray<LoadLibraryPriority*>\"));\n"
},
{
"id": "constant/B.LoadLibraryPriority_0 = new A.LoadLibraryPriority(0, \"normal\");\n",
"kind": "constant",
"name": "",
"size": 66,
"outputUnit": "outputUnit/main",
"code": "B.LoadLibraryPriority_0 = new A.LoadLibraryPriority(0, \"normal\");\n"
},
{
"id": "constant/B.LoadLibraryPriority_1 = new A.LoadLibraryPriority(1, \"high\");\n",
"kind": "constant",
"name": "",
"size": 64,
"outputUnit": "outputUnit/main",
"code": "B.LoadLibraryPriority_1 = new A.LoadLibraryPriority(1, \"high\");\n"
}],
deferredFiles=[{
"main.dart": {
@ -99,7 +123,7 @@
"id": "library/memory:sdk/tests/web/native/main.dart::",
"kind": "library",
"name": "<unnamed>",
"size": 857,
"size": 860,
"children": [
"function/memory:sdk/tests/web/native/main.dart::main"
],
@ -254,7 +278,45 @@ import 'dart:async';
import 'lib1.dart' deferred as lib1;
import 'lib2.dart' as lib2;
/*member: main:
/*spec.member: main:
function=[{
"id": "function/memory:sdk/tests/web/native/main.dart::main",
"kind": "function",
"name": "main",
"size": 860,
"outputUnit": "outputUnit/main",
"parent": "library/memory:sdk/tests/web/native/main.dart::",
"children": [],
"modifiers": {
"static": false,
"const": false,
"factory": false,
"external": false
},
"returnType": "dynamic",
"inferredReturnType": "[exact=_Future]",
"parameters": [],
"sideEffects": "SideEffects(reads nothing; writes nothing)",
"inlinedCount": 0,
"code": "main() {\n var $async$goto = 0,\n $async$completer = A._makeAsyncAwaitCompleter(type$.dynamic);\n var $async$main = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {\n if ($async$errorCode === 1)\n return A._asyncRethrow($async$result, $async$completer);\n while (true)\n switch ($async$goto) {\n case 0:\n // Function start\n $async$goto = 2;\n return A._asyncAwait(A.loadDeferredLibrary(\"lib1\", 0), $async$main);\n case 2:\n // returning from await.\n A.checkDeferredIsLoaded(\"lib1\");\n A.checkDeferredIsLoaded(\"lib1\");\n // implicit return\n return A._asyncReturn(null, $async$completer);\n }\n });\n return A._asyncStartSync($async$main, $async$completer);\n }",
"type": "dynamic Function()",
"functionKind": 0
}],
holding=[
{"id":"function/dart:_js_helper::checkDeferredIsLoaded","mask":null},
{"id":"function/dart:_js_helper::loadDeferredLibrary","mask":null},
{"id":"function/dart:_rti::findType","mask":null},
{"id":"function/dart:async::StreamIterator.StreamIterator","mask":null},
{"id":"function/dart:async::_asyncAwait","mask":null},
{"id":"function/dart:async::_asyncRethrow","mask":null},
{"id":"function/dart:async::_asyncReturn","mask":null},
{"id":"function/dart:async::_asyncStartSync","mask":null},
{"id":"function/dart:async::_makeAsyncAwaitCompleter","mask":null},
{"id":"function/dart:async::_wrapJsFunctionForAsync","mask":null},
{"id":"function/memory:sdk/tests/web/native/lib2.dart::A.method","mask":"inlined"},
{"id":"function/memory:sdk/tests/web/native/lib2.dart::A.method","mask":null}]
*/
/*canary.member: main:
function=[{
"id": "function/memory:sdk/tests/web/native/main.dart::main",
"kind": "function",

View file

@ -0,0 +1,19 @@
// Copyright (c) 2022, 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.
/// Annotation values for `@pragma('dart2js:load-priority:xxx')` annotations.
enum LoadLibraryPriority {
// Order is important as it is the `index` of the enum that is passed to the
// runtime helper.
// Normal priority, if there is no `dart2js:load-priority:xxx` annotation.
normal,
// High priority.
high,
// TODO(sra): Do we want more priorities, e.g. "background", where the loader
// defers any work until the next microtask, and is conservative about
// initialization jank.
}

View file

@ -68,6 +68,8 @@ import 'dart:_rti' as newRti
instantiatedGenericFunctionType,
throwTypeError;
import 'dart:_load_library_priority';
part 'annotations.dart';
part 'constant_map.dart';
part 'instantiation.dart';
@ -2670,7 +2672,21 @@ typedef void DeferredLoadCallback();
// Function that will be called every time a new deferred import is loaded.
DeferredLoadCallback? deferredLoadHook;
Future<Null> loadDeferredLibrary(String loadId) {
/// Loads a deferred library. The compiler generates a call to this method to
/// implement `import.loadLibrary()`. The [priority] argument is the index of
/// one of the [LoadLibraryPriority] enum's members.
///
/// - `0` for `LoadLibraryPriority.normal`
/// - `1` for `LoadLibraryPriority.high`
Future<Null> loadDeferredLibrary(String loadId, int priority) {
// Convert [priority] to the enum value as form of validation:
final unusedPriorityEnum = LoadLibraryPriority.values[priority];
// The enum's values may be checked via the `index`:
assert(priority == LoadLibraryPriority.normal.index ||
priority == LoadLibraryPriority.high.index);
// TODO(sra): Implement prioritization.
// For each loadId there is a list of parts to load. The parts are represented
// by an index. There are two arrays, one that maps the index into a Uri and
// another that maps the index to a hash.

View file

@ -0,0 +1,19 @@
// Copyright (c) 2022, 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.
/// Annotation values for `@pragma('dart2js:load-priority:xxx')` annotations.
enum LoadLibraryPriority {
// Order is important as it is the `index` of the enum that is passed to the
// runtime helper.
// Normal priority, if there is no `dart2js:load-priority:xxx` annotation.
normal,
// High priority.
high,
// TODO(sra): Do we want more priorities, e.g. "background", where the loader
// defers any work until the next microtask, and is conservative about
// initialization jank.
}

View file

@ -191,6 +191,11 @@ const Map<String, LibraryInfo> libraries = const {
categories: "",
documented: false,
platforms: DART2JS_PLATFORM),
"_load_library_priority": const LibraryInfo(
"_internal/js_runtime/lib/synced/load_library_priority.dart",
categories: "",
documented: false,
platforms: DART2JS_PLATFORM),
"_metadata": const LibraryInfo("html/html_common/metadata.dart",
categories: "", documented: false, platforms: DART2JS_PLATFORM),
"_js_annotations": const LibraryInfo("js/_js_annotations.dart",

View file

@ -364,6 +364,9 @@
"_async_await_error_codes": {
"uri": "_internal/js_runtime/lib/synced/async_await_error_codes.dart"
},
"_load_library_priority": {
"uri": "_internal/js_runtime/lib/synced/load_library_priority.dart"
},
"_recipe_syntax": {
"uri": "_internal/js_shared/lib/synced/recipe_syntax.dart"
}

View file

@ -319,6 +319,9 @@ _dart2js_common:
_async_await_error_codes:
uri: "_internal/js_runtime/lib/synced/async_await_error_codes.dart"
_load_library_priority:
uri: "_internal/js_runtime/lib/synced/load_library_priority.dart"
_recipe_syntax:
uri: "_internal/js_shared/lib/synced/recipe_syntax.dart"