[dart2wasm] Implement fine grained deferred module splitting

This shrinks essentials main module by 13% (-1.4 MB)

Before this CL the splitting of the application into wasm modules
was done on a library granularity level.

Now we split the application based on "static element" granularity -
those elements are:

  * Static fields
  * Static getters, setters, methods
  * Constructors
  * Class (all instance fields & methods)

This means that moving a class or static methods/fields from one library
to another will have no effect on the partitioning.

Differences to dart2js:

  * No tracking of local functions
  * No tracking of types
  * No split constraint support (yet)
  * (Works on Kernel AST instead of dart2js element/entity model)

The code is organized into

* `pkg/dart2wasm/lib/deferred_load/import_set.dart`
   This is almost identical to the dartj2s version with minor differences:
    - works on `LibraryDependency` objects
    - does not assign names to parts
    - no split constraint support (yet)

* `pkg/dart2wasm/lib/deferred_load/dependencies.dart`
   This is a new implementation that collects dependencies of
   `Reference`s/`Constant`s and in case of `Reference` whether the
   dependencies are deferred or not.

* `pkg/dart2wasm/lib/deferred_load/partition.dart`
   This is the main algorithm (core logic is the same as in dart2js)
   `Reference`s/`Constant`s and (in case of `Reference`) whether the
   dependencies are accessed under deferred loading guard or not.

Change-Id: I0fcdf86f5226060738671a1f69bb14cfffd631e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467041
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2025-12-11 04:50:39 -08:00
committed by Commit Queue
parent 87032377ee
commit 3030208174
32 changed files with 1702 additions and 764 deletions
+12 -53
View File
@@ -1538,20 +1538,10 @@ abstract class AstCodeGenerator
return translateExpression(node.value, expectedType);
}
w.ModuleBuilder? _activeDeferredLoadingGuard;
@override
w.ValueType visitLet(Let node, w.ValueType expectedType) {
translateStatement(node.variable);
final oldGuard = _activeDeferredLoadingGuard;
final newGuard = _recognizeDeferredModuleGuard(node);
if (newGuard != null) {
_activeDeferredLoadingGuard = newGuard;
}
final result = translateExpression(node.body, expectedType);
_activeDeferredLoadingGuard = oldGuard;
return result;
return translateExpression(node.body, expectedType);
}
@override
@@ -2744,37 +2734,6 @@ abstract class AstCodeGenerator
return expectedType;
}
w.ModuleBuilder? _recognizeDeferredModuleGuard(Let let) {
if (!translator.options.enableDeferredLoading &&
!translator.options.enableMultiModuleStressTestMode) {
return null;
}
// TODO(http://dartbug.com/61764): Find better way to do this.
//
// If we have somewhere in the parent chain of [node] a
//
// let
// _ = checkLibraryIsLoadedFromLoadId(<id>)
// in
// <body>
//
// Then we know that the constant use in <body> can only happen after the
// given <id> was loaded, i.e. the constant use is deferred-load-guarded
// by <id>.
final init = let.variable.initializer;
if (init is StaticInvocation) {
final target = init.target;
if (target == translator.checkLibraryIsLoadedFromLoadId) {
final args = init.arguments.positional;
final loadId = (args[0] as IntLiteral).value;
return translator.moduleForLoadId(
enclosingMember.enclosingLibrary, loadId);
}
}
return null;
}
@override
w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
instantiateConstant(NullConstant(), expectedType);
@@ -3175,7 +3134,7 @@ abstract class AstCodeGenerator
b,
constant,
expectedType,
deferredModuleGuard: _activeDeferredLoadingGuard,
deferredModuleGuard: translator.moduleForConstant(constant),
);
}
}
@@ -4059,12 +4018,12 @@ class StaticFieldInitializerCodeGenerator extends AstCodeGenerator {
w.Global global = translator.globals.getGlobalForStaticField(field);
w.Global? flag = translator.globals.getGlobalInitializedFlag(field);
translateExpression(field.initializer!, global.type.type);
b.global_set(global);
translator.globals.writeGlobal(b, global);
if (flag != null) {
b.i32_const(1);
b.global_set(flag);
translator.globals.writeGlobal(b, flag);
}
b.global_get(global);
translator.globals.readGlobal(b, global);
translator.convertType(b, global.type.type, outputs.single);
b.end();
}
@@ -4086,7 +4045,7 @@ class EagerStaticFieldInitializerCodeGenerator extends AstCodeGenerator {
setSourceMapSourceAndFileOffset(source, field.fileOffset);
translateExpression(field.initializer!, global.type.type);
b.global_set(global);
translator.globals.writeGlobal(b, global);
}
}
@@ -4116,20 +4075,20 @@ class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator {
w.Global global, w.Global? flag, w.BaseFunction? initFunction) {
if (initFunction == null) {
// Statically initialized
b.global_get(global);
translator.globals.readGlobal(b, global);
} else {
if (flag != null) {
// Explicit initialization flag
b.global_get(flag);
translator.globals.readGlobal(b, flag);
b.if_(const [], [global.type.type]);
b.global_get(global);
translator.globals.readGlobal(b, global);
b.else_();
translator.callFunction(initFunction, b);
b.end();
} else {
// Null signals uninitialized
w.Label block = b.block(const [], [initFunction.type.outputs.single]);
b.global_get(global);
translator.globals.readGlobal(b, global);
b.br_on_non_null(block);
translator.callFunction(initFunction, b);
b.end();
@@ -4139,10 +4098,10 @@ class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator {
void _generateSetter(w.Global global, w.Global? flag) {
b.local_get(paramLocals.single);
b.global_set(global);
translator.globals.writeGlobal(b, global);
if (flag != null) {
b.i32_const(1); // true
b.global_set(flag);
translator.globals.writeGlobal(b, flag);
}
}
}
-25
View File
@@ -39,7 +39,6 @@ import 'dynamic_modules.dart';
import 'io_util.dart';
import 'js/method_collector.dart' show JSMethods;
import 'js/runtime_generator.dart' as js;
import 'library_dependencies_pruner.dart';
import 'modules.dart';
import 'record_class_generator.dart';
import 'records.dart';
@@ -549,30 +548,6 @@ Future<CompilationResult> _runTfaPhase(
libraryIndex = LibraryIndex(component, _librariesToIndex);
}
// NOTE: The [Library.dependencies] will be in a weird state after TFA:
//
// * a library may use members of other libraries without an import that
// provides the member
//
// * a library may have many unused imports
//
// -> See https://dartbug.com/62112 & https://dartbug.com/62111 for details.
//
// At this point dart2wasm uses library dependencies only for one purpose,
// namely for partitioning all libraries into deferred wasm modules. So we now
// perform a dart2wasm specific pruning of library imports.
//
// See [pruneLibraryDependencies] for more information.
//
// NOTE: In stress test mode the component is manually split into one wasm
// module per library without making imports of libraries deferred. To ensure
// all modules get loaded before main it injects dummy [LoadLibrary]
// expressions that would be optimized out by [pruneLibraryDependencies]. So
// we disable the pruning in this case.
if (!options.translatorOptions.enableMultiModuleStressTestMode) {
pruneLibraryDependencies(libraryIndex, component);
}
if (options.emitTfa) {
// Store metadata needed for codegen so that it can be serialized.
final recordClassesRepo = _RecordClassesRepository();
+1 -1
View File
@@ -1573,7 +1573,7 @@ class _ConstantAccessor {
usingModule == translator.mainModule) {
final definition =
_defineConstantInModuleRecursive(translator.mainModule, info);
return _readDefinedConstant(b, usingModule, info, definition);
return _readDefinedConstant(b, translator.mainModule, info, definition);
}
// Remember for the transitive DAG of [constant] that we use it in this
@@ -0,0 +1,275 @@
// Copyright (c) 2025, 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 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
import '../modules.dart';
class DependenciesCollector {
final CoreTypes _coreTypes;
final DeferredModuleLoadingMap _loadingMap;
late final _checkLibraryIsLoadedFromLoadId = _coreTypes.index.getProcedure(
'dart:_internal',
LibraryIndex.topLevel,
'checkLibraryIsLoadedFromLoadId');
DependenciesCollector(this._coreTypes, this._loadingMap);
/// Returns the set of constants referred to by the (possibly composed)
/// [constant].
DirectConstantDependencies directConstantDependencies(Constant constant) {
final children = <Constant>{};
constant.visitChildren(_ConstantDependenciesCollector._(children));
return DirectConstantDependencies(children);
}
DirectReferenceDependencies directReferenceDependencies(Reference reference) {
final TreeNode node = reference.node!;
final deps = DirectReferenceDependencies({}, {}, {}, {});
if (node is Class) {
_enqueueInstanceMembers(node, deps);
return deps;
}
final collector = _ReferenceDependenciesCollector._(
_recognizeDeferredLoadingGuard, reference, deps);
if (node is Procedure) {
node.accept(collector);
return deps;
}
if (node is Constructor) {
node.accept(collector);
collector.addReference(node.enclosingClass.reference);
return deps;
}
if (node is Field) {
node.accept(collector);
if (node.fieldReference != reference) {
collector.addReference(node.fieldReference);
}
if (node.getterReference != reference) {
collector.addReference(node.getterReference);
}
if (node.setterReference case final setterReference?) {
if (setterReference != reference) {
collector.addReference(setterReference);
}
}
return deps;
}
throw UnsupportedError('Unexpected reference: $reference');
}
LibraryDependency? _recognizeDeferredLoadingGuard(Let let) {
// TODO(http://dartbug.com/61764): Find better way to do this.
//
// If we have
//
// let
// _ = checkLibraryIsLoadedFromLoadId(<id>)
// in
// <body>
//
// Then we know that the body will only be executed once the deferred prefix
// `D` was loaded.
final init = let.variable.initializer;
if (init is StaticInvocation) {
final target = init.target;
if (target == _checkLibraryIsLoadedFromLoadId) {
final args = init.arguments.positional;
final loadId = (args[0] as IntLiteral).value;
return _loadingMap.loadIdToDeferredImport[loadId];
}
}
return null;
}
static void _enqueueInstanceMembers(
Class klass, DirectReferenceDependencies deps) {
final superReference = klass.superclass?.reference;
if (superReference != null) {
deps.references.add(superReference);
}
for (final m in klass.members) {
if (m.isInstanceMember) {
if (m is Field) {
deps.references.add(m.fieldReference);
continue;
}
deps.references.add(m.reference);
}
}
}
}
class _ConstantDependenciesCollector extends RecursiveVisitor {
final Set<Constant> _directChildren;
_ConstantDependenciesCollector._(this._directChildren);
@override
void defaultConstantReference(Constant node) {
_directChildren.add(node);
}
}
class _ReferenceDependenciesCollector extends RecursiveVisitor {
final LibraryDependency? Function(Let node) recognizeDeferredLoadingGuard;
final Reference reference;
final DirectReferenceDependencies deps;
final List<LibraryDependency> _activeLoadGuards = [];
_ReferenceDependenciesCollector._(
this.recognizeDeferredLoadingGuard, this.reference, this.deps);
@override
void visitLet(Let node) {
node.variable.accept(this);
final guard = recognizeDeferredLoadingGuard(node);
if (guard != null) {
_activeLoadGuards.add(guard);
}
node.body.accept(this);
if (guard != null) {
final last = _activeLoadGuards.removeLast();
assert(guard == last);
}
}
@override
void visitStaticGet(StaticGet node) {
super.visitStaticGet(node);
addReference(node.targetReference);
}
@override
void visitStaticSet(StaticSet node) {
super.visitStaticSet(node);
addReference(node.targetReference);
}
@override
void visitStaticInvocation(StaticInvocation node) {
super.visitStaticInvocation(node);
addReference(node.targetReference);
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
super.visitConstructorInvocation(node);
addReference(node.targetReference);
}
@override
void visitSuperInitializer(SuperInitializer node) {
super.visitSuperInitializer(node);
addReference(node.targetReference);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
super.visitRedirectingInitializer(node);
addReference(node.targetReference);
}
@override
void visitStaticTearOff(StaticTearOff node) {
super.visitStaticTearOff(node);
addReference(node.targetReference);
}
@override
void defaultDartType(DartType node) {
// Ignore: Dart2wasm doesn't defer RTI information atm.
}
@override
void visitSupertype(Supertype node) {
// Ignore: Dart2wasm doesn't defer RTI information atm.
}
@override
void visitNullLiteral(NullLiteral node) {
addConstant(NullConstant());
}
@override
void visitStringLiteral(StringLiteral node) {
addConstant(StringConstant(node.value));
}
@override
void visitBoolLiteral(BoolLiteral node) {
addConstant(BoolConstant(node.value));
}
@override
void visitIntLiteral(IntLiteral node) {
addConstant(IntConstant(node.value));
}
@override
void visitDoubleLiteral(DoubleLiteral node) {
addConstant(DoubleConstant(node.value));
}
@override
void visitConstantExpression(ConstantExpression node) {
addConstant(node.constant);
}
void addReference(Reference used) {
if (_activeLoadGuards.isEmpty) {
if (deps.references.add(used)) {
deps.deferredReferences.remove(used);
}
return;
}
if (!deps.references.contains(used)) {
if (deps.deferredReferences[used] case final existingGuards?) {
existingGuards.add(_activeLoadGuards.last);
return;
}
deps.deferredReferences[used] = {_activeLoadGuards.last};
}
}
void addConstant(Constant used) {
if (_activeLoadGuards.isEmpty) {
if (deps.constants.add(used)) {
deps.deferredConstants.remove(used);
}
return;
}
if (!deps.constants.contains(used)) {
if (deps.deferredConstants[used] case final existingGuards?) {
existingGuards.add(_activeLoadGuards.last);
return;
}
deps.deferredConstants[used] = {_activeLoadGuards.last};
}
}
}
class DirectReferenceDependencies {
final Set<Reference> references;
final Map<Reference, Set<LibraryDependency>> deferredReferences;
final Set<Constant> constants;
final Map<Constant, Set<LibraryDependency>> deferredConstants;
DirectReferenceDependencies(this.references, this.deferredReferences,
this.constants, this.deferredConstants);
}
class DirectConstantDependencies {
final Set<Constant> constants;
DirectConstantDependencies(this.constants);
}
@@ -0,0 +1,241 @@
// Copyright (c) 2025, 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 'package:kernel/kernel.dart';
/// Indirectly represents a deferred import in an [ImportSet].
///
/// We could directly store the [declaration] in [ImportSet], but adding this
/// class makes some of the import set operations more efficient.
class _DeferredImport {
final LibraryDependency declaration;
/// Canonical index associated with [declaration]. This is used to efficiently
/// implement [ImportSetLattice.union].
final int index;
_DeferredImport(this.declaration, this.index);
}
/// A compact lattice representation of import sets and subsets.
///
/// We use a graph of nodes to represent elements of the lattice, but only
/// create new nodes on-demand as they are needed by the deferred loading
/// algorithm.
///
/// The constructions of nodes is carefully done by storing imports in a
/// specific order. This ensures that we have a unique and canonical
/// representation for each subset.
class ImportSetLattice {
/// A map of [LibraryDependency] to its initial [ImportSet].
final Map<LibraryDependency, ImportSet> initialSets = {};
/// Index of deferred imports that defines the canonical order used by the
/// operations below.
final Map<LibraryDependency, _DeferredImport> _importIndex = {};
/// The canonical instance representing the empty import set.
final ImportSet emptySet = _EmptyImportSet();
/// The [ImportSet] representing the root output unit.
late ImportSet _rootSet;
ImportSet get rootSet {
assert(_rootSet.part != null);
return _rootSet;
}
/// Get the smallest [ImportSet] that contains [import]. When
/// unconstrained, this [ImportSet] is a singleton [ImportSet] containing
/// only the supplied [LibraryDependency]. However, when constrained the returned
/// [ImportSet] may contain multiple [LibraryDependency]s.
ImportSet initialSetOf(LibraryDependency import) =>
initialSets[import] ??= _singleton(import);
/// A private method to generate a true singleton [ImportSet] for a given
/// [LibraryDependency].
ImportSet _singleton(LibraryDependency import) {
// Ensure we have import in the index.
return emptySet._add(_wrap(import));
}
/// A helper method to convert a [Set<LibraryDependency>] to an [ImportSet].
ImportSet setOfImportsToImportSet(Set<LibraryDependency> setOfImports) {
List<_DeferredImport> imports = setOfImports.map(_wrap).toList();
imports.sort((a, b) => a.index - b.index);
var result = emptySet;
for (var import in imports) {
result = result._add(import);
}
return result;
}
/// Builds the [rootSet] which contains transitions for all other deferred
/// imports as well as [rootImport].
void buildRootSet(
LibraryDependency rootImport,
Part rootPart,
Iterable<LibraryDependency> allDeferredImports,
) {
_rootSet = setOfImportsToImportSet({rootImport, ...allDeferredImports});
_rootSet.part = rootPart;
initialSets[rootImport] = _rootSet;
}
/// Get the import set that includes the union of [a] and [b].
ImportSet union(ImportSet a, ImportSet b) {
if (a is _EmptyImportSet) return b;
if (b is _EmptyImportSet) return a;
// Create the union by merging the imports in canonical order. The sets are
// basically lists linked by the `_previous` field in reverse order. We do a
// merge-like scan 'backwards' removing the biggest element until we hit an
// empty set or a common prefix, and the add the 'merge-sorted' elements
// back onto the prefix.
ImportSet result;
// 'removed' imports in decreasing canonical order.
List<_DeferredImport> imports = [];
while (true) {
if (a is! _NonEmptyImportSet) {
result = b;
break;
}
if (b is! _NonEmptyImportSet || identical(a, b)) {
result = a;
break;
}
if (a._import.index > b._import.index) {
imports.add(a._import);
a = a._previous;
} else if (b._import.index > a._import.index) {
imports.add(b._import);
b = b._previous;
} else {
assert(identical(a._import, b._import));
imports.add(a._import);
a = a._previous;
b = b._previous;
}
}
// Add merged elements back in reverse order. It is tempting to pop them off
// with `removeLast()` but that causes measurable shrinking reallocations.
for (int i = imports.length - 1; i >= 0; i--) {
result = result._add(imports[i]);
}
return result;
}
/// Get the index for an [import] according to the canonical order.
_DeferredImport _wrap(LibraryDependency import) {
return _importIndex[import] ??= _DeferredImport(
import,
_importIndex.length,
);
}
}
/// A canonical set of deferred imports.
abstract class ImportSet {
/// Links to other import sets in the lattice by adding one import.
final Map<_DeferredImport, _NonEmptyImportSet> _transitions = {};
/// The output unit corresponding to this set of imports, if any.
Part? part;
int get length;
/// Returns an iterable over the imports in this set in canonical order.
Iterable<_DeferredImport> _collectImports() {
List<_DeferredImport> result = [];
ImportSet current = this;
while (current is _NonEmptyImportSet) {
result.add(current._import);
current = current._previous;
}
assert(result.length == length);
return result.reversed;
}
/// Returns true if this [ImportSet] contains all of [other].
bool containsAll(ImportSet other) {
var current = this;
while (true) {
if (other is! _NonEmptyImportSet) return true;
if (current is! _NonEmptyImportSet) return false;
if (current._import.index > other._import.index) {
current = current._previous;
} else if (other._import.index > current._import.index) {
return false;
} else {
assert(current._import.index == other._import.index);
current = current._previous;
other = other._previous;
}
}
}
/// Create an import set that adds [import] to all the imports on this set.
/// This assumes that import's canonical order comes after all imports in
/// this current set. This should only be called from [ImportSetLattice],
/// since it is where we preserve this invariant.
ImportSet _add(_DeferredImport import) {
var self = this;
assert(self is! _NonEmptyImportSet || import.index > self._import.index);
return _transitions[import] ??= _NonEmptyImportSet(
import,
this,
length + 1,
);
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('ImportSet(size: $length, ');
for (var import in _collectImports()) {
sb.write('${import.declaration.name} ');
}
sb.write(')');
return '$sb';
}
/// Converts an [ImportSet] to a [Set<LibraryDependency>].
/// Note: Not for performance sensitive code.
Set<LibraryDependency> toSet() =>
_collectImports().map((i) => i.declaration).toSet();
}
class _NonEmptyImportSet extends ImportSet {
/// Last element added to set.
///
/// This set comprises [_import] appended onto [_previous]. *Note*: [_import]
/// is the last element in the set in the canonical order imposed by
/// [ImportSetLattice].
final _DeferredImport _import;
/// The set containing all previous elements.
final ImportSet _previous;
@override
final int length;
_NonEmptyImportSet(this._import, this._previous, this.length);
}
class _EmptyImportSet extends ImportSet {
@override
int get length => 0;
}
class Part {
/// Whether this [Part] contains the roots.
final bool isRoot;
/// The deferred imports that use the elements in this output unit.
final Set<LibraryDependency> imports;
Part(this.isRoot, this.imports);
}
@@ -0,0 +1,291 @@
// Copyright (c) 2025, 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';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import '../modules.dart' show DeferredModuleLoadingMap;
import 'dependencies.dart';
import 'import_set.dart';
export 'import_set.dart' show Part;
Partitioning partitionAppplication(CoreTypes coreTypes, Component component,
DeferredModuleLoadingMap loadingMap, Set<Reference> roots) {
final allDeferredImports = <LibraryDependency>[];
for (final lib in component.libraries) {
for (final dep in lib.dependencies) {
if (dep.isDeferred) {
allDeferredImports.add(dep);
}
}
}
final depsCollector = DependenciesCollector(coreTypes, loadingMap);
final algorithm = _Algorithm(component, depsCollector, allDeferredImports);
return algorithm.run(roots);
}
class Partitioning {
final Part root;
final List<Part> parts;
final Map<Reference, Part> referenceToPart;
final Map<Constant, Part> constantToPart;
final Map<LibraryDependency, List<Part>> deferredImportToParts;
Partitioning(this.root, this.parts, this.referenceToPart, this.constantToPart,
this.deferredImportToParts);
}
class _Algorithm {
final Component component;
final DependenciesCollector depsCollector;
final List<LibraryDependency> allDeferredImports;
final ImportSetLattice importSets = ImportSetLattice();
// The work queues for propagating import set additions.
late final _WorkQueue<Reference> referenceQueue = _WorkQueue(importSets);
late final _WorkQueue<Constant> constantQueue = _WorkQueue(importSets);
// Caches of direct dependencies of [Reference]s/[Constants]s.
final Map<Reference, DirectReferenceDependencies>
directReferenceDependencies = {};
final Map<Constant, DirectConstantDependencies> directConstantDependencies =
{};
// The [ImportSet] the given [Reference]/[Constant]s are needed for.
final Map<Reference, ImportSet> referenceToImportSet = {};
final Map<Constant, ImportSet> constantToImportSet = {};
_Algorithm(this.component, this.depsCollector, this.allDeferredImports);
Partitioning run(Set<Reference> roots) {
// Sentinel used to represent the artificial import of all roots.
final rootImport = LibraryDependency.import(Library(Uri(), fileUri: Uri()));
final rootPart = Part(true, {});
importSets.buildRootSet(
rootImport,
rootPart,
allDeferredImports,
);
// Main algorithm: Enqueue roots and propagate.
for (final root in roots) {
referenceQueue.enqueue(root, importSets.rootSet);
}
while (referenceQueue.isNotEmpty || constantQueue.isNotEmpty) {
while (referenceQueue.isNotEmpty) {
final (reference, importsToAdd) = referenceQueue.dequeue();
ensureReferenceDependencies(reference);
final oldSet = referenceToImportSet[reference] ?? importSets.emptySet;
final newSet = importSets.union(oldSet, importsToAdd);
updateReference(reference, oldSet, newSet);
}
while (constantQueue.isNotEmpty) {
final (constant, importsToAdd) = constantQueue.dequeue();
ensureConstantDependencies(constant);
final oldSet = constantToImportSet[constant] ?? importSets.emptySet;
final newSet = importSets.union(oldSet, importsToAdd);
updateConstant(constant, oldSet, newSet);
}
}
// Map [Reference]s/[Constant]s to the [Part] they were assigned to.
final referenceToPart = <Reference, Part>{};
final constantToPart = <Constant, Part>{};
final parts = <Part>[rootPart];
referenceToImportSet.forEach((reference, importSet) {
Part? part = importSet.part;
if (part == null) {
part = Part(false, importSet.toSet());
parts.add(importSet.part = part);
}
referenceToPart[reference] = part;
});
constantToImportSet.forEach((constant, importSet) {
Part? part = importSet.part;
if (part == null) {
part = Part(false, importSet.toSet());
parts.add(importSet.part = part);
}
constantToPart[constant] = part;
});
final deferredInputLoadingList = <LibraryDependency, List<Part>>{};
for (final part in parts) {
for (final deferredImport in part.imports) {
(deferredInputLoadingList[deferredImport] ??= []).add(part);
}
}
return Partitioning(rootPart, parts, referenceToPart, constantToPart,
deferredInputLoadingList);
}
/// Ensures we have all transitive direct dependencies of [reference]
/// cached and all transitive deferred dependencies of [reference] enqueued.
void ensureReferenceDependencies(Reference reference) {
if (directReferenceDependencies.containsKey(reference)) return;
final deps = depsCollector.directReferenceDependencies(reference);
directReferenceDependencies[reference] = deps;
deps.references.forEach(ensureReferenceDependencies);
deps.deferredReferences.forEach((reference, imports) {
ensureReferenceDependencies(reference);
for (final import in imports) {
referenceQueue.enqueue(reference, importSets.initialSetOf(import));
}
});
deps.constants.forEach(ensureConstantDependencies);
deps.deferredConstants.forEach((constant, imports) {
ensureConstantDependencies(constant);
for (final import in imports) {
constantQueue.enqueue(constant, importSets.initialSetOf(import));
}
});
}
/// Ensures we have all transitive dependencies of [constant] cached.
void ensureConstantDependencies(Constant constant) {
if (directConstantDependencies.containsKey(constant)) return;
if (constant is InstanceConstant) {
ensureReferenceDependencies(constant.classReference);
} else if (constant is TearOffConstant) {
ensureReferenceDependencies(constant.targetReference);
}
final deps = depsCollector.directConstantDependencies(constant);
directConstantDependencies[constant] = deps;
deps.constants.forEach(ensureConstantDependencies);
}
/// Given an [Reference], an [oldSet] and a [newSet], either ignore the
/// update, apply the update immediately if we can avoid unions, or apply the
/// update later if we cannot. For more detail on [oldSet] and [newSet],
/// please see the comment in [dart2js].
///
/// [dart2js] pkg/compiler/lib/src/deferred_load/deferred_load.dart
void updateReference(
Reference reference, ImportSet oldSet, ImportSet newSet) {
final currentSet = referenceToImportSet[reference] ?? importSets.emptySet;
// If [currentSet] == [newSet], then currentSet must include all of newSet.
if (currentSet == newSet) return;
// Elements in the main output unit always remain there.
if (currentSet == importSets.rootSet) return;
// If [currentSet] == [oldSet], then we can safely update the
// [entityToSet] map for [entityData] to [newSet] in a single assignment.
// If not, then if we are supposed to update [entityData] recursively, we add
// it back to the queue so that we can re-enter [update] later after
// performing a union. If we aren't supposed to update recursively, we just
// perform the union inline.
if (currentSet == oldSet) {
// Continue recursively updating from [oldSet] to [newSet].
referenceToImportSet[reference] = newSet;
_updateReferenceDependencies(reference, oldSet, newSet);
} else {
assert(
// Invariant: we must mark main before we mark any deferred import.
newSet != importSets.rootSet || oldSet != importSets.emptySet,
"Tried to assign to the main output unit, but it was assigned "
"to $currentSet.",
);
// Recursively enqueue [reference].
referenceQueue.enqueue(reference, newSet);
}
}
void updateConstant(Constant constant, ImportSet oldSet, ImportSet newSet) {
final currentSet = constantToImportSet[constant] ?? importSets.emptySet;
// If [currentSet] == [newSet], then currentSet must include all of newSet.
if (currentSet == newSet) return;
// Elements in the main output unit always remain there.
if (currentSet == importSets.rootSet) return;
// If [currentSet] == [oldSet], then we can safely update the
// [entityToSet] map for [entityData] to [newSet] in a single assignment.
// If not, then if we are supposed to update [entityData] recursively, we add
// it back to the queue so that we can re-enter [update] later after
// performing a union. If we aren't supposed to update recursively, we just
// perform the union inline.
if (currentSet == oldSet) {
// Continue recursively updating from [oldSet] to [newSet].
constantToImportSet[constant] = newSet;
_updateConstantDependencies(constant, oldSet, newSet);
} else {
assert(
// Invariant: we must mark main before we mark any deferred import.
newSet != importSets.rootSet || oldSet != importSets.emptySet,
"Tried to assign to the main output unit, but it was assigned "
"to $currentSet.",
);
// Recursively enqueue [constant].
constantQueue.enqueue(constant, newSet);
}
}
/// Updates the dependencies of a given [Reference] from [oldSet] to
/// [newSet].
void _updateReferenceDependencies(
Reference reference, ImportSet oldSet, ImportSet newSet) {
final deps = directReferenceDependencies[reference]!;
for (final reference in deps.references) {
updateReference(reference, oldSet, newSet);
}
for (final constant in deps.constants) {
updateConstant(constant, oldSet, newSet);
}
}
void _updateConstantDependencies(
Constant constant, ImportSet oldSet, ImportSet newSet) {
if (constant is InstanceConstant) {
updateReference(constant.classReference, oldSet, newSet);
} else if (constant is TearOffConstant) {
updateReference(constant.targetReference, oldSet, newSet);
}
final childConstants = directConstantDependencies[constant]!;
for (final constant in childConstants.constants) {
updateConstant(constant, oldSet, newSet);
}
}
}
/// Keeps track of a worklist of objects that need additional imports to be
/// added to them.
class _WorkQueue<T extends Object> {
final ImportSetLattice _importSets;
final Queue<T> _queue = Queue();
final Map<T, ImportSet> _pendingWork = {};
_WorkQueue(this._importSets);
bool get isNotEmpty => _queue.isNotEmpty;
void enqueue(T key, ImportSet importSet) {
final existingImportSet = _pendingWork[key];
if (existingImportSet != null) {
_pendingWork[key] = _importSets.union(existingImportSet, importSet);
return;
}
_pendingWork[key] = importSet;
_queue.add(key);
}
(T, ImportSet) dequeue() {
assert(isNotEmpty);
final object = _queue.removeFirst();
final importSet = _pendingWork.remove(object)!;
return (object, importSet);
}
}
+63 -193
View File
@@ -7,80 +7,17 @@ import 'dart:io' show File;
import 'package:_fe_analyzer_shared/src/util/relativize.dart'
show relativizeUri;
import 'package:collection/collection.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'await_transformer.dart' as await_transformer;
import 'compiler_options.dart';
import 'library_dependencies_pruner.dart';
import 'deferred_load/partition.dart';
import 'modules.dart';
import 'target.dart';
import 'util.dart' show addPragma;
import 'util.dart' show addPragma, getPragma;
/// The root of a deferred import subgraph.
///
/// Two [_RootSet] objects are considered equivalent if they contain the same
/// libraries.
class _RootSet {
final List<Library> libraries = [];
final bool containsEntryPoint;
_RootSet({required this.containsEntryPoint});
void addLibrary(Library library) {
libraries.add(library);
}
@override
String toString() => libraries.toString();
@override
int get hashCode => const ListEquality().hash(libraries);
@override
bool operator ==(Object other) {
return other is _RootSet &&
const ListEquality().equals(libraries, other.libraries);
}
}
/// Generates a deferred import graph given a kernel [Component].
///
/// This implementation generates a modules at the granularity level of
/// dart libraries.
///
/// A library is considered imported 'eagerly' if it is imported without the
/// `deferred` keyword. A 'deferred root' is a library explicitly included in
/// a `deferred` import. A deferred root will have a 'load list' which is the
/// list of modules containing all the libraries eagerly reachable from that
/// root library.
///
/// The module assignment algorithm proceeds as follows:
///
/// We maintain a queue of discovered deferred roots which we initialize with
/// the main library.
///
/// From each deferred root in the queue we crawl the import graph and capture
/// all the eagerly imported libraries. These tell us the libraries that included
/// in the load list for that root. Any newly discovered deferred roots are
/// added to the queue.
///
/// At the same time, for each library we keep a [_RootSet] which tracks all
/// deferred roots that eagerly require that library. Two libraries have an
/// equal [_RootSet] if they are required by the same set of deferred roots.
/// Having an equal [_RootSet] means that the libraries will always need to be
/// loaded together so we include them in the same [ModuleMetadata].
///
/// Once we've visited all the deferred roots we create one [ModuleMetadata] per
/// unique [_RootSet] and include all libraries with that [_RootSet] in the
/// [ModuleMetadata]. Finally, [ModuleMetadata] is added to the load list of every
/// deferred root in the [_RootSet].
///
/// To support the actual process of loading the deferred wasm modules, we also
/// collect a mapping from each import site (i.e. a library and deferred import
/// name pair) to the load list needed at that import site.
class DeferredLoadingModuleStrategy extends ModuleStrategy {
final Component component;
final WasmCompilerOptions options;
@@ -100,50 +37,32 @@ class DeferredLoadingModuleStrategy extends ModuleStrategy {
@override
Future<void> processComponentAfterTfa(
DeferredModuleLoadingMap loadingMap) async {
final (libraryToRootSet, importTargetMap) = _buildLibraryToImports();
final partition = partitionAppplication(
coreTypes, component, loadingMap, _findWasmRoots());
final builder = ModuleMetadataBuilder(options);
// Dedupe root sets combining equal sets into a single ModuleMetadata.
final mainModule = builder.buildModuleMetadata();
final Map<_RootSet, ModuleMetadata> rootSetToModule = {};
final Map<Library, List<ModuleMetadata>> rootToModules = {};
libraryToRootSet.forEach((targetLibrary, rootSet) {
// If the libary is used by the entryPoint root, then assign it to the
// main module immediately. It should not be split into its own module,
// even if another root depends on it.
ModuleMetadata? module =
rootSet.containsEntryPoint ? mainModule : rootSetToModule[rootSet];
if (module != null) {
// We've already seen a library required by the same roots so added it
// to the same module.
module.libraries.add(targetLibrary);
return;
}
// This library is used by a new set of roots so create a new module for
// it. Each root that needs this library should depend on this module.
module = rootSetToModule[rootSet] = builder.buildModuleMetadata();
module.libraries.add(targetLibrary);
for (final root in rootSet.libraries) {
(rootToModules[root] ??= []).add(module);
}
final moduleMetadata = <Part, ModuleMetadata>{};
for (final part in partition.parts) {
moduleMetadata[part] = builder.buildModuleMetadata();
}
final referenceToModuleMetadata = <Reference, ModuleMetadata>{};
partition.referenceToPart.forEach((reference, output) {
referenceToModuleMetadata[reference] = moduleMetadata[output]!;
});
final constantToModuleMetadata = <Constant, ModuleMetadata>{};
partition.constantToPart.forEach((constant, output) {
constantToModuleMetadata[constant] = moduleMetadata[output]!;
});
partition.deferredImportToParts.forEach((deferredImport, parts) {
final wasmModules = [for (final o in parts) moduleMetadata[o]!];
loadingMap.addModuleToLibraryImport(
deferredImport.enclosingLibrary, deferredImport.name!, wasmModules);
});
importTargetMap.forEach((enclosingLibrary, nameToTarget) {
nameToTarget.forEach((importName, targetLibrary) {
final modules = rootToModules[targetLibrary];
if (modules != null) {
loadingMap.addModuleToLibraryImport(
enclosingLibrary, importName, modules);
}
});
});
// Some libraries may not have gotten a module assigned in the above
// Some elements may not have gotten a module assigned in the above
// procedure. This can have a varity of reasons:
//
// - A class that's never really used but still in the program because TFA
// - A class that's never really used but still in the AST because TFA
// left it there (this happens occasionally because we enable RTA before
// TFA, RTA is less precised and may mark a class as allocated but TFA
// later on optimizes usages away which leave the class as non-abstract
@@ -153,97 +72,44 @@ class DeferredLoadingModuleStrategy extends ModuleStrategy {
//
// The code generator still requires every library to have a corresponding
// module, so we make an artificial one here.
final assignedLibraries = <Library>{
...mainModule.libraries,
for (final module in rootSetToModule.values) ...module.libraries,
};
final unassignedLibraries = component.libraries.toSet()
..removeAll(assignedLibraries);
final dummyModule = builder.buildModuleMetadata();
moduleOutputData = ModuleOutputData([
mainModule,
...rootSetToModule.values,
if (unassignedLibraries.isNotEmpty)
builder.buildModuleMetadata()..libraries.addAll(unassignedLibraries)
]);
moduleOutputData = ModuleOutputData.fineGrainedSplit([
...moduleMetadata.values,
dummyModule,
], referenceToModuleMetadata, constantToModuleMetadata, dummyModule);
}
Set<Reference> _findWasmRoots() {
final exports = <Reference>{};
final trueConstant = BoolConstant(true);
bool check(Annotatable node) {
if (getPragma<StringConstant>(coreTypes, node, 'wasm:export') != null ||
getPragma<Constant>(coreTypes, node, 'wasm:entry-point',
defaultValue: trueConstant) !=
null) {
return true;
}
return false;
}
for (final library in component.libraries) {
for (final member in library.members) {
if (check(member)) exports.add(member.reference);
}
for (final klass in library.classes) {
if (check(klass)) exports.add(klass.reference);
for (final member in klass.members) {
if (check(member)) exports.add(member.reference);
}
}
}
return exports;
}
@override
ModuleOutputData buildModuleOutputData() => moduleOutputData;
bool _isRequiredLibrary(Library lib) {
final importUri = lib.importUri;
if (importUri.scheme == 'dart' && importUri.path == 'core') return true;
// The compiler creates implicit usages of some classes/functions without
// the compiled libraries explicitly importing them. E.g.
// * `dart:_boxed_int` for integer boxing
return kernelTarget.extraRequiredLibraries.contains('$importUri');
}
(Map<Library, _RootSet>, Map<Library, Map<String, Library>>)
_buildLibraryToImports() {
final entryPoint = component.mainMethod!.enclosingLibrary;
final deferredRootStack = [entryPoint];
final enqueuedDeferredRoots = <Library>{entryPoint};
final libraryToRootSet = <Library, _RootSet>{};
final importTargetMap = <Library, Map<String, Library>>{};
bool isMainRoot = true;
while (deferredRootStack.isNotEmpty) {
final currentRoot = deferredRootStack.removeLast();
final eagerWorkStack = [currentRoot];
final enqueuedEagerLibraries = <Library>{currentRoot};
final newDeferredRoots = <Library>[];
if (isMainRoot) {
// Add required libraries because the compiler has implicit
// dependencies on these. Also add libraries containing 'wasm:export'
// since embedders might need access to these from the main module.
for (final lib in component.libraries) {
if (containsWasmExport(coreTypes, lib) || _isRequiredLibrary(lib)) {
if (enqueuedEagerLibraries.add(lib)) {
eagerWorkStack.add(lib);
}
}
}
}
while (eagerWorkStack.isNotEmpty) {
final currentLibrary = eagerWorkStack.removeLast();
// We visit the entryPoint root first, so we'll be creating the _RootSet
// for anything reachable from it and can set `containsEntryPoint`
// correctly.
//
// TODO(natebiggs): Avoid processing the same eager library across
// multiple deferred roots.
(libraryToRootSet[currentLibrary] ??= _RootSet(
containsEntryPoint: identical(currentRoot, entryPoint)))
.addLibrary(currentRoot);
for (final dependency in currentLibrary.dependencies) {
final targetLibrary = dependency.importedLibraryReference.asLibrary;
if (dependency.isDeferred) {
if (dependency.name!.startsWith(unusedDeferredLibraryPrefix)) {
continue;
}
newDeferredRoots.add(targetLibrary);
(importTargetMap[currentLibrary] ??= {})[dependency.name!] =
targetLibrary;
} else {
if (enqueuedEagerLibraries.add(targetLibrary)) {
eagerWorkStack.add(targetLibrary);
}
}
}
}
for (final newRoot in newDeferredRoots) {
if (enqueuedEagerLibraries.contains(newRoot)) continue;
if (enqueuedDeferredRoots.add(newRoot)) {
deferredRootStack.add(newRoot);
}
}
isMainRoot = false;
}
return (libraryToRootSet, importTargetMap);
}
}
class StressTestModuleStrategy extends ModuleStrategy {
@@ -316,24 +182,28 @@ class StressTestModuleStrategy extends ModuleStrategy {
final moduleBuilder = ModuleMetadataBuilder(options);
final mainModule = moduleBuilder.buildModuleMetadata();
final initLibraries = _testModeMainLibraries;
mainModule.libraries.addAll(initLibraries);
final modules = <ModuleMetadata>[];
final importMap = <String, List<ModuleMetadata>>{};
final internalLib = coreTypes.index.getLibrary('dart:_internal');
// Put each library in a separate module.
final libraryMap = <Library, ModuleMetadata>{};
for (final library in component.libraries) {
if (initLibraries.contains(library)) continue;
if (initLibraries.contains(library)) {
libraryMap[library] = mainModule;
continue;
}
final module = moduleBuilder.buildModuleMetadata();
modules.add(module);
module.libraries.add(library);
libraryMap[library] = module;
final importName = '${library.importUri}';
importMap[importName] = [module];
loadingMap.addModuleToLibraryImport(internalLib, importName, [module]);
}
moduleOutputData = ModuleOutputData([mainModule, ...modules]);
moduleOutputData = ModuleOutputData.librarySplit(
[mainModule, ...modules], libraryMap, null);
}
@override
+12 -7
View File
@@ -94,8 +94,10 @@ extension DynamicModuleMember on Member {
class DynamicSubmoduleOutputData extends ModuleOutputData {
final CoreTypes coreTypes;
final ModuleMetadata _submodule;
DynamicSubmoduleOutputData(this.coreTypes, super.modules)
: _submodule = modules[1];
DynamicSubmoduleOutputData(this.coreTypes, ModuleMetadata mainModule,
this._submodule, Map<Library, ModuleMetadata> libraryToModuleMetadata)
: super.librarySplit(
[mainModule, _submodule], libraryToModuleMetadata, null);
@override
ModuleMetadata moduleForReference(Reference reference) {
@@ -150,9 +152,9 @@ class DynamicMainModuleStrategy extends ModuleStrategy with KernelNodes {
ModuleOutputData buildModuleOutputData() {
final builder = ModuleMetadataBuilder(options);
final mainModule = builder.buildModuleMetadata();
mainModule.libraries.addAll(component.libraries);
final placeholderModule = builder.buildModuleMetadata(skipEmit: true);
return ModuleOutputData([mainModule, placeholderModule]);
final placeholderModule = builder.buildModuleMetadata();
return ModuleOutputData.librarySplit(
[mainModule, placeholderModule], {}, mainModule);
}
void _addImplicitPragmas() {
@@ -337,14 +339,17 @@ class DynamicSubmoduleStrategy extends ModuleStrategy {
final builder = ModuleMetadataBuilder(options);
final mainModule = builder.buildModuleMetadata(skipEmit: true);
final submodule = builder.buildModuleMetadata(emitAsMain: true);
final libraryToModuleMetadata = <Library, ModuleMetadata>{};
for (final library in component.libraries) {
final module = hasPragma(coreTypes, library, _mainModLibPragma)
? mainModule
: submodule;
module.libraries.add(library);
libraryToModuleMetadata[library] = module;
}
return DynamicSubmoduleOutputData(coreTypes, [mainModule, submodule]);
return DynamicSubmoduleOutputData(
coreTypes, mainModule, submodule, libraryToModuleMetadata);
}
@override
@@ -1,365 +0,0 @@
// Copyright (c) 2025, 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 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
/// If a deferred library import has this name prefix it isn't used to load
/// anything. It only serves to maintain that any [CheckLibraryIsLoaded] throws
/// if the [LoadLibrary] call was not called.
const unusedDeferredLibraryPrefix = 'unused-';
/// Prunes [Library.dependencies] to contain precisely those imports needed.
///
/// Dart2wasm only uses library dependencies for one purpose, namely for
/// computing deferred loading units. This computation is done based on the
/// import graph and the granularity is on a library level.
///
/// We make the following overvations:
///
/// a) Using a type from a import
/// -> The main wasm module has all Dart runtime type information atm
/// -> No need to import a library to use classes from it in types.
///
/// b) Using a constant from an import
/// -> If we use a [InstanceConstant] / [TearOffConstant] in a library then
/// we have to ensure the enclosing library of the
/// [InstanceConstant.classNode] / [TearOffConstant.target] is imported.
/// -> This ensures that the code for closure/method is available when
/// invoking it.
/// -> Any other constant doesn't require an import.
///
/// c) Using static elements from an import
/// -> If we invoke a constructor, a static method, static getter, super
/// constructor, etc we need to import the target's enclosing library.
/// -> This will guarantee we have the code for target loaded when we perform
/// the call.
///
/// d) Instance/Dynamic invocations, other uses of [Reference]s
/// -> Does not require an import of the [Reference]s enclosing library.
/// -> We have sound type system: If the call is executed we know that the
/// receiver was allocated (and whoever allocated it has ensured - via c)
/// above - that the code for methods of the receiver is loaded).
///
/// So we establish the following invariants
///
/// * We only have imports for [Reference]s which are used for "static"-like
/// calls
///
/// * We import the library containing the definition of a [Reference] and not
/// e.g. a library that may re-export it
///
/// * If a [Reference] was usable via a deferred import (possibly via
/// a deferred library that re-exported the [Reference]) we ensure the newly
/// inserted import will also be deferred.
///
/// The transform will
///
/// * prune [Library.dependencies] to be exact, i.e. have a import iff
/// something is used from the imported library
///
/// * will remove all exports
///
/// * may insert more precise [LoadLibrary]/[CheckLibraryIsLoaded] if we don't
/// use a deferred library directly but things it re-exported
///
void pruneLibraryDependencies(LibraryIndex libraryIndex, Component component) {
final constantToLibrarySet = _ConstantToLibrarySet();
for (final library in component.libraries) {
final usedLibraries =
_Collector(library, constantToLibrarySet).usedLibraries;
_ImportPruner(libraryIndex, usedLibraries, library);
}
for (final library in component.libraries) {
library.dependencies.removeWhere((dep) {
if (dep.isExport) {
dep.parent = null;
return true;
}
return false;
});
}
}
class _ImportPruner extends Transformer {
final LibraryIndex libraryIndex;
final Set<Library> usedLibraries;
final Library library;
final additionalDeferredImports =
<LibraryDependency, List<LibraryDependency>>{};
late final futureImmediate =
libraryIndex.getConstructor('dart:async', '_Future', 'immediate');
_ImportPruner(this.libraryIndex, this.usedLibraries, this.library) {
// Step 1) Prune existing library imports.
// The set of libraries that we need to import and are already covered via
// an existing library import.
final librariesOfExistingImports = <Library>{};
// Maps a library to all the deferred imports that made this library
// available.
final libraryToDeferredImports = <Library, List<LibraryDependency>>{};
// The new set of imports (possibly smaller - removing unused dependencies,
// possibly larger - adding used but not yet imported dependencies).
final prunedDependencies = <LibraryDependency>[];
for (final dep in library.dependencies) {
if (dep.isExport) {
prunedDependencies.add(dep);
continue;
}
if (usedLibraries.contains(dep.targetLibrary)) {
librariesOfExistingImports.add(dep.targetLibrary);
prunedDependencies.add(dep);
continue;
}
if (dep.isDeferred) {
// Although the deferred dependency isn't used, for making sure
// [CheckLibraryIsLoaded] nodes throw if no preceding
// [LoadLibrary] was called we have to maintain a dummy import. This
// will also ensure the exception mentions the right name.
prunedDependencies.add(dep);
dep.name = '$unusedDeferredLibraryPrefix${dep.name!}';
// Loop over all libraries available via the deferred import that are
// used. The transformer will then issue individual [LoadLibrary] calls
// to them.
for (final available in _transitiveLibrarySet(dep.targetLibrary)) {
if (usedLibraries.contains(available)) {
libraryToDeferredImports.putIfAbsent(available, () => []).add(dep);
}
}
continue;
}
// The [dep] isn't directly used, remove it.
assert(!dep.isDeferred);
dep.parent = null;
}
library.dependencies = prunedDependencies;
// Step 2) Add missing imports.
for (final used in usedLibraries) {
// Maybe we already import the [used] library.
if (librariesOfExistingImports.contains(used)) {
continue;
}
// Never emit a library import to `dart:core`, it's special.
if (used.importUri.scheme == 'dart' && used.importUri.path == 'core') {
continue;
}
// We need to inject a new import of the [used] library.
final oldDeferredImports = libraryToDeferredImports[used];
if (oldDeferredImports == null) {
// This library was not accessible via old deferred imports, so we emit
// a normal import.
library.addDependency(LibraryDependency.import(used));
continue;
}
// The library was accessible (via a re-export) from an deferred
// import. Let's make a new deferred import for that particular library.
final newDep = LibraryDependency.deferredImport(
used, 'PreciseDeferredDep-${used.dependencies.length}');
library.addDependency(newDep);
for (final oldImport in oldDeferredImports) {
// Any [LoadLibrary] or [CheckLibraryIsLoaded] node that operated on the
// old (unused) deferred import needs to cover the [newDep] (possibly in
// addition to the existing dep (if used) and others).
additionalDeferredImports.putIfAbsent(oldImport, () => []).add(newDep);
}
}
// We only have to transform the body of the library if any [LoadLibrary] or
// [CheckLibraryIsLoaded] has to be modified.
if (additionalDeferredImports.isNotEmpty) {
library.transformChildren(this);
}
}
@override
TreeNode visitLoadLibrary(LoadLibrary node) {
node = super.visitLoadLibrary(node) as LoadLibrary;
final additional = additionalDeferredImports[node.import];
if (additional == null) return node;
return BlockExpression(
Block([
// This may be a dummy/unused which we only omit for throwing correct
// errors if a access (e.g. of a type) is used before the load call.
ExpressionStatement(node),
for (final replacement in additional.skip(1))
ExpressionStatement(LoadLibrary(replacement)),
]),
LoadLibrary(additional.last));
}
@override
TreeNode visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
node = super.visitCheckLibraryIsLoaded(node) as CheckLibraryIsLoaded;
final additional = additionalDeferredImports[node.import];
if (additional == null) return node;
return BlockExpression(
Block([
// This may be a dummy/unused which we only omit for throwing correct
// errors if a access (e.g. of a type) is used before the load call.
ExpressionStatement(node),
for (final replacement in additional.skip(1))
ExpressionStatement(CheckLibraryIsLoaded(replacement)),
]),
CheckLibraryIsLoaded(additional.last));
}
}
/// Traverses the AST of a [Library] and collects the set of libraries we need
/// to import due to accessing elements "statically" (see
/// [pruneLibraryDependencies] for more information)
class _Collector extends RecursiveVisitor {
final Library library;
final _ConstantToLibrarySet constantToLibrarySet;
/// The libraries that need to be imported.
final Set<Library> usedLibraries = {};
_Collector(this.library, this.constantToLibrarySet) {
library.accept(this);
}
@override
void visitStaticGet(StaticGet node) {
super.visitStaticGet(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitStaticSet(StaticSet node) {
super.visitStaticSet(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitStaticInvocation(StaticInvocation node) {
super.visitStaticInvocation(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
super.visitConstructorInvocation(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitSuperInitializer(SuperInitializer node) {
super.visitSuperInitializer(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
super.visitRedirectingInitializer(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void visitStaticTearOff(StaticTearOff node) {
super.visitStaticTearOff(node);
addLibrary(node.target.enclosingLibrary);
}
@override
void defaultDartType(DartType node) {
// Ignore due to compiler always able to construct runtime type objects when
// needed (see also [pruneLibraryDependencies]).
}
@override
void visitSupertype(Supertype node) {
// Ignore due to compiler always able to construct runtime type objects when
// needed (see also [pruneLibraryDependencies]).
}
@override
void visitConstantExpression(ConstantExpression node) {
usedLibraries.addAll(constantToLibrarySet.librariesFor(node.constant));
}
void addLibrary(Library used) {
if (used != library) {
usedLibraries.add(used);
}
}
}
class _ConstantToLibrarySet {
final _constantToTransitiveLibraries = <Constant, Set<Library>>{};
/// Collects the set of libraries one needs to import when accessing
/// [constant].
///
/// This include enclosing libraries of all [InstanceConstant]s
/// and [TearOffConstant]s of the transitive constant graph of [constant].
Set<Library> librariesFor(Constant constant) {
final existing = _constantToTransitiveLibraries[constant];
if (existing != null) return existing;
final transitiveLibraries = <Library>{
if (constant is InstanceConstant) constant.classNode.enclosingLibrary,
if (constant is TearOffConstant) constant.target.enclosingLibrary,
// Collect all transitive libraries for direct child constants.
for (final childConstant
in _ChildConstantCollector.directChildrenOf(constant))
...librariesFor(childConstant),
};
return _constantToTransitiveLibraries[constant] =
transitiveLibraries.isEmpty ? const <Library>{} : transitiveLibraries;
}
}
class _ChildConstantCollector extends RecursiveVisitor {
/// Returns the set of constants referred to by the (possibly composed)
/// [constant].
static Set<Constant> directChildrenOf(Constant constant) {
final children = <Constant>{};
constant.visitChildren(_ChildConstantCollector._(children));
return children;
}
final Set<Constant> _directChildren;
_ChildConstantCollector._(this._directChildren);
@override
void defaultConstantReference(Constant node) {
_directChildren.add(node);
}
}
/// Collects the set of libraries transitively imported via importing [library].
///
/// This includes [library] and any other library it transitively re-exports.
Set<Library> _transitiveLibrarySet(Library library) {
final transitiveLibrarySet = <Library>{library};
final worklist = <Library>[library];
while (worklist.isNotEmpty) {
final toBeExpanded = worklist.removeLast();
assert(transitiveLibrarySet.contains(toBeExpanded));
for (final dep in toBeExpanded.dependencies) {
if (dep.isExport) {
final reExportedLibrary = dep.targetLibrary;
if (transitiveLibrarySet.add(reExportedLibrary)) {
worklist.add(reExportedLibrary);
}
}
}
}
return transitiveLibrarySet;
}
+68 -19
View File
@@ -6,6 +6,7 @@ import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'compiler_options.dart';
import 'reference_extensions.dart';
import 'target.dart';
import 'util.dart';
@@ -47,9 +48,6 @@ class ModuleMetadataBuilder {
/// by library, by class or neither. [containsReference] should be used to
/// determine if a module contains a given class/member reference.
class ModuleMetadata {
/// The set of libraries contained in this module.
final Set<Library> libraries = {};
final bool isMain;
/// The name used to import and export this module.
@@ -65,7 +63,7 @@ class ModuleMetadata {
{this.skipEmit = false, this.isMain = false});
@override
String toString() => '$moduleImportName($libraries)';
String toString() => moduleImportName;
}
/// Data needed to create deferred modules.
@@ -73,13 +71,39 @@ class ModuleOutputData {
/// All [ModuleMetadata]s generated for the program.
final List<ModuleMetadata> modules;
/// Maps the [Library] to the corresponding [ModuleMetadata].
late final Map<Library, ModuleMetadata> _libraryToModuleMetadata = {
for (final metadata in modules)
for (final library in metadata.libraries) library: metadata,
};
/// Maps the [Reference] to the corresponding [ModuleMetadata].
final Map<Reference, ModuleMetadata>? referenceToModuleMetadata;
ModuleOutputData(this.modules) : assert(modules[0].isMain);
/// Maps the [Constant] to the corresponding [ModuleMetadata].
final Map<Constant, ModuleMetadata>? constantToModuleMetadata;
/// Maps the [Library] to the corresponding [ModuleMetadata].
final Map<Library, ModuleMetadata>? libraryToModuleMetadata;
/// Module for any unassigned reference.
final ModuleMetadata? defaultModule;
ModuleOutputData.fineGrainedSplit(
this.modules,
this.referenceToModuleMetadata,
this.constantToModuleMetadata,
this.defaultModule)
: libraryToModuleMetadata = null,
assert(modules[0].isMain);
ModuleOutputData.librarySplit(
this.modules, this.libraryToModuleMetadata, this.defaultModule)
: referenceToModuleMetadata = null,
constantToModuleMetadata = null,
assert(modules[0].isMain);
ModuleOutputData.monolitic(ModuleMetadata module)
: modules = [module],
libraryToModuleMetadata = null,
referenceToModuleMetadata = null,
constantToModuleMetadata = null,
defaultModule = module,
assert(module.isMain);
ModuleMetadata get mainModule => modules[0];
Iterable<ModuleMetadata> get deferredModules => modules.skip(1);
@@ -88,7 +112,33 @@ class ModuleOutputData {
/// Returns the module that contains [reference].
ModuleMetadata moduleForReference(Reference reference) {
return _libraryToModuleMetadata[_enclosingLibraryForReference(reference)]!;
// Turn artificial [Reference]s used in dart2wasm to the normal Kernel AST
// [Reference]s.
if (reference.isTypeCheckerReference ||
reference.isCheckedEntryReference ||
reference.isUncheckedEntryReference ||
reference.isBodyReference ||
reference.isInitializerReference ||
reference.isConstructorBodyReference ||
reference.isTearOffReference) {
reference = reference.asMember.reference;
}
// We may have fine-grained partitioning of the application.
if (referenceToModuleMetadata != null) {
return referenceToModuleMetadata![reference] ?? defaultModule!;
}
// We may have coarse-grained library-based partitioning of the application.
if (libraryToModuleMetadata != null) {
final library = _enclosingLibraryForReference(reference);
return libraryToModuleMetadata![library] ?? defaultModule!;
}
// We put the entire application into the same wasm module.
return defaultModule!;
}
ModuleMetadata? moduleForConstant(Constant constant) {
return constantToModuleMetadata?[constant];
}
}
@@ -106,8 +156,7 @@ class DefaultModuleStrategy extends ModuleStrategy {
// module.
final builder = ModuleMetadataBuilder(options);
final mainModule = builder.buildModuleMetadata(emitAsMain: true);
mainModule.libraries.addAll(component.libraries);
return ModuleOutputData([mainModule]);
return ModuleOutputData.monolitic(mainModule);
}
@override
@@ -159,31 +208,31 @@ class DeferredModuleLoadingMap {
// Maps each (library, deferred import) to a unique id.
final Map<(Library, String), int> loadIds;
// Maps the unique load id to the imported library.
final List<Library> loadId2ImportedLibrary;
// Maps the unique load id to the deferred import.
final List<LibraryDependency> loadIdToDeferredImport;
// Maps (library, import-name)-id to list of needed modules.
final List<List<ModuleMetadata>> moduleMap;
DeferredModuleLoadingMap._(
this.loadIds, this.moduleMap, this.loadId2ImportedLibrary);
this.loadIds, this.moduleMap, this.loadIdToDeferredImport);
factory DeferredModuleLoadingMap.fromComponent(Component c) {
int nextLoadId = 0;
final loadIds = <(Library, String), int>{};
final loadId2ImportedLibrary = <Library>[];
final loadIdToDeferredImport = <LibraryDependency>[];
final moduleMap = <List<ModuleMetadata>>[];
for (final library in c.libraries) {
for (final dep in library.dependencies) {
if (!dep.isDeferred) continue;
final name = dep.name!;
loadIds[(library, name)] = nextLoadId++;
loadId2ImportedLibrary.add(dep.targetLibrary);
loadIdToDeferredImport.add(dep);
moduleMap.add([]);
}
}
return DeferredModuleLoadingMap._(
loadIds, moduleMap, loadId2ImportedLibrary);
loadIds, moduleMap, loadIdToDeferredImport);
}
void addModuleToLibraryImport(
+19 -5
View File
@@ -457,12 +457,26 @@ class Translator with KernelNodes {
bool get isDynamicSubmodule => dynamicModuleInfo?.isSubmodule ?? false;
w.ModuleBuilder get dynamicSubmodule => dynamicModuleInfo!.submodule;
w.ModuleBuilder moduleForReference(Reference reference) =>
_outputToBuilder[_moduleOutputData.moduleForReference(reference)]!;
w.ModuleBuilder moduleForReference(Reference reference) {
final module = _moduleOutputData.moduleForReference(reference);
return _outputToBuilder[module]!;
}
w.ModuleBuilder moduleForLoadId(Library enclosingLibrary, int loadId) {
return moduleForReference(
loadingMap.loadId2ImportedLibrary[loadId].reference);
/// The module where [constant] should be placed
///
/// NOTE: This may return `null` for constants that are e.g. synthesized by
/// the backend. In that case the backend decides where to place the constant.
w.ModuleBuilder? moduleForConstant(Constant constant) {
final module = _moduleOutputData.moduleForConstant(constant);
if (module == null) return null;
return _outputToBuilder[module];
}
List<w.ModuleBuilder> modulesForLoadId(Library enclosingLibrary, int loadId) {
return [
for (final moduleMetadata in loadingMap.moduleMap[loadId])
_outputToBuilder[moduleMetadata]!,
];
}
String nameForModule(w.ModuleBuilder module) =>
@@ -3,7 +3,7 @@
"name": "<unnamed>",
"imports": {
"1": [
"out_module3.wasm"
"out_module4.wasm"
],
"2": [
"out_module1.wasm",
@@ -24,7 +24,7 @@
],
"4": [
"out_module2.wasm",
"out_module4.wasm"
"out_module3.wasm"
]
},
"importPrefixToLoadId": {
@@ -15,7 +15,10 @@
(type $type0 (func
(param $var0 i32)
(result (ref $MyConstClass))))
(type $type2 (func
(result (ref $MyConstClass))))
(table $static0-0 (export "static0-0") 2 (ref null $type0))
(table $static1-0 (export "static1-0") 1 (ref null $type2))
(global $"C378 \"bad\"" (ref $JSStringImpl) <...>)
(func $"mainImpl <noInline>" (param $var0 i32)
(local $var1 (ref $MyConstClass))
@@ -12,50 +12,25 @@
(type $Object (sub $#Top (struct
(field $field0 i32)
(field $field1 (mut i32)))))
(global $.h1-nonshared-const (import "" "h1-nonshared-const") (ref extern))
(global $.shared-const (import "" "shared-const") (ref extern))
(table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $JSStringImpl))
(table $module0.constant-table1 (import "module0" "constant-table1") 1 (ref null $MyConstClass))
(global $"C496 MyConstClass" (ref $MyConstClass)
(type $type0 (func
(result (ref $MyConstClass))))
(global $.h0-nonshared-const (import "" "h0-nonshared-const") (ref extern))
(table $module0.static1-0 (import "module0" "static1-0") 1 (ref null $type0))
(global $"C500 MyConstClass" (ref $MyConstClass)
(i32.const 116)
(i32.const 0)
(i32.const 4)
(i32.const 0)
(global.get $.h1-nonshared-const)
(global.get $.h0-nonshared-const)
(struct.new $JSStringImpl)
(struct.new $MyConstClass))
(func $"modH1Use <noInline>" (param $var0 i32) (result (ref $MyConstClass))
(local $var1 (ref $JSStringImpl))
(local $var2 (ref $MyConstClass))
(func $"modH0Use <noInline>" (param $var0 i32) (result (ref $MyConstClass))
local.get $var0
if (result (ref $MyConstClass))
global.get $"C496 MyConstClass"
global.get $"C500 MyConstClass"
else
block $label0 (result (ref $MyConstClass))
i32.const 0
table.get $module0.constant-table1
br_on_non_null $label0
i32.const 0
i32.const 116
i32.const 0
block $label1 (result (ref $JSStringImpl))
i32.const 0
table.get $module0.constant-table0
br_on_non_null $label1
i32.const 0
i32.const 4
i32.const 0
global.get $.shared-const
struct.new $JSStringImpl
local.tee $var1
table.set $module0.constant-table0
local.get $var1
end $label1
struct.new $MyConstClass
local.tee $var2
table.set $module0.constant-table1
local.get $var2
end $label0
i32.const 0
call_indirect $module0.static1-0 (result (ref $MyConstClass))
end
)
)
@@ -1,2 +1,24 @@
(module $module2
(type $#Top (struct
(field $field0 i32)))
(type $JSStringImpl (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $_ref externref))))
(type $MyConstClass (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $b (ref $JSStringImpl)))))
(type $Object (sub $#Top (struct
(field $field0 i32)
(field $field1 (mut i32)))))
(global $.shared-const (import "" "shared-const") (ref extern))
(global $"C498 MyConstClass" (ref $MyConstClass)
(i32.const 116)
(i32.const 0)
(i32.const 4)
(i32.const 0)
(global.get $.shared-const)
(struct.new $JSStringImpl)
(struct.new $MyConstClass))
)
@@ -12,50 +12,25 @@
(type $Object (sub $#Top (struct
(field $field0 i32)
(field $field1 (mut i32)))))
(global $.h0-nonshared-const (import "" "h0-nonshared-const") (ref extern))
(global $.shared-const (import "" "shared-const") (ref extern))
(table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $JSStringImpl))
(table $module0.constant-table1 (import "module0" "constant-table1") 1 (ref null $MyConstClass))
(global $"C500 MyConstClass" (ref $MyConstClass)
(type $type0 (func
(result (ref $MyConstClass))))
(global $.h1-nonshared-const (import "" "h1-nonshared-const") (ref extern))
(table $module0.static1-0 (import "module0" "static1-0") 1 (ref null $type0))
(global $"C496 MyConstClass" (ref $MyConstClass)
(i32.const 116)
(i32.const 0)
(i32.const 4)
(i32.const 0)
(global.get $.h0-nonshared-const)
(global.get $.h1-nonshared-const)
(struct.new $JSStringImpl)
(struct.new $MyConstClass))
(func $"modH0Use <noInline>" (param $var0 i32) (result (ref $MyConstClass))
(local $var1 (ref $JSStringImpl))
(local $var2 (ref $MyConstClass))
(func $"modH1Use <noInline>" (param $var0 i32) (result (ref $MyConstClass))
local.get $var0
if (result (ref $MyConstClass))
global.get $"C500 MyConstClass"
global.get $"C496 MyConstClass"
else
block $label0 (result (ref $MyConstClass))
i32.const 0
table.get $module0.constant-table1
br_on_non_null $label0
i32.const 0
i32.const 116
i32.const 0
block $label1 (result (ref $JSStringImpl))
i32.const 0
table.get $module0.constant-table0
br_on_non_null $label1
i32.const 0
i32.const 4
i32.const 0
global.get $.shared-const
struct.new $JSStringImpl
local.tee $var1
table.set $module0.constant-table0
local.get $var1
end $label1
struct.new $MyConstClass
local.tee $var2
table.set $module0.constant-table1
local.get $var2
end $label0
i32.const 0
call_indirect $module0.static1-0 (result (ref $MyConstClass))
end
)
)
@@ -0,0 +1,195 @@
// Copyright (c) 2025, 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.
// functionFilter=foo.*Code
// functionFilter=Foo.*doit
// tableFilter=static[0-9]+
// globalFilter=fooGlobal
// globalFilter=FooConst
// typeFilter=NoMatch
// compilerOption=--enable-deferred-loading
// compilerOption=--no-minify
// We import ourselves here \\o//
import 'deferred.fine_grained.dart' deferred as D1;
import 'deferred.fine_grained.dart' deferred as D2;
import 'deferred.fine_grained.dart' deferred as D3;
import 'deferred.fine_grained.dart' deferred as D4;
import 'deferred.fine_grained.dart' deferred as D5;
void main() async {
await foo0();
}
Future foo0() async {
foo0Code(0);
await D1.loadLibrary();
await D1.foo1();
}
Future foo1() async {
foo1Code(0);
await D2.loadLibrary();
await D2.foo2();
}
Future foo2() async {
foo2Code(0);
await D3.loadLibrary();
await D3.foo3();
}
Future foo3() async {
foo3Code(0);
await D4.loadLibrary();
await D4.foo4();
}
Future foo4() async {
foo4Code(0);
await D5.loadLibrary();
await D5.foo5();
}
Future foo5() async {
foo5Code(fooGlobal5);
}
@pragma('wasm:never-inline')
void foo0Code(dynamic a) {
print(const FooConst0());
print('foo0Code($a)');
fooGlobal0 = 0;
}
@pragma('wasm:never-inline')
void foo1Code(dynamic a) {
print(const FooConst1());
print('foo1Code($a)');
fooGlobal1 = 1;
}
@pragma('wasm:never-inline')
void foo2Code(dynamic a) {
print(const FooConst2());
print('foo2Code($a)');
fooGlobal2 = 2;
}
@pragma('wasm:never-inline')
void foo3Code(dynamic a) {
print(const FooConst3());
print('foo3Code($a)');
fooGlobal3 = 3;
}
@pragma('wasm:never-inline')
void foo4Code(dynamic a) {
print(const FooConst4());
print('foo4Code($a)');
fooGlobal4 = 4;
}
@pragma('wasm:never-inline')
void foo5Code(dynamic a) {
print(const FooConst5());
print('foo5Code($a)');
fooGlobal5 = 5;
// Also refer things from other modules, which will make us put them into
// separate modules.
foo0Code(fooGlobal0);
foo1Code(fooGlobal1);
foo2Code(fooGlobal2);
foo3Code(fooGlobal3);
foo4Code(fooGlobal4);
// We invoke `doit()` here which will cause all modules with const/new
// instances of the `FooConst*` classes to include `doit()` in it's module
// even though they don't call `doit()` (only last module does).
allFooConstants[0].doit(fooGlobal5);
}
final allFooConstants = <FooConstBase>[
const FooConst0(),
const FooConst1(),
const FooConst2(),
const FooConst3(),
const FooConst4(),
const FooConst5(),
];
class FooConstBase {
const FooConstBase();
void doit(dynamic a) {
print('FooConstBase($a)');
}
}
class FooConst0 extends FooConstBase {
const FooConst0();
@override
void doit(dynamic a) {
print('FooConst0($a)');
super.doit(a);
}
}
class FooConst1 extends FooConstBase {
const FooConst1();
@override
void doit(dynamic a) {
print('FooConst1($a)');
super.doit(a);
}
}
class FooConst2 extends FooConstBase {
const FooConst2();
@override
void doit(dynamic a) {
print('FooConst2($a)');
super.doit(a);
}
}
class FooConst3 extends FooConstBase {
const FooConst3();
@override
void doit(dynamic a) {
print('FooConst3($a)');
super.doit(a);
}
}
class FooConst4 extends FooConstBase {
const FooConst4();
@override
void doit(dynamic a) {
print('FooConst4($a)');
super.doit(a);
}
}
class FooConst5 extends FooConstBase {
const FooConst5();
@override
void doit(dynamic a) {
print('FooConst5($a)');
super.doit(a);
}
}
Object fooGlobal0 = int.parse('0') == 1 ? 1 : '1';
Object fooGlobal1 = int.parse('1') == 1 ? 1 : '1';
Object fooGlobal2 = int.parse('2') == 1 ? 1 : '1';
Object fooGlobal3 = int.parse('3') == 1 ? 1 : '1';
Object fooGlobal4 = int.parse('4') == 1 ? 1 : '1';
Object fooGlobal5 = int.parse('5') == 1 ? 1 : '1';
@@ -0,0 +1,82 @@
(module $module0
(type $#Top <...>)
(type $BoxedInt <...>)
(type $FooConst0 <...>)
(type $FooConstBase <...>)
(type $JSStringImpl <...>)
(type $type0 <...>)
(type $type10 <...>)
(type $type12 <...>)
(type $type2 <...>)
(type $type4 <...>)
(type $type6 <...>)
(type $type8 <...>)
(global $".FooConst0(" (import "" "FooConst0(") (ref extern))
(global $".FooConstBase(" (import "" "FooConstBase(") (ref extern))
(table $static0-0 (export "static0-0") 5 (ref null $type0))
(table $static1-0 (export "static1-0") 4 (ref null $type2))
(table $static2-0 (export "static2-0") 4 (ref null $type4))
(table $static3-0 (export "static3-0") 1 (ref null $type6))
(table $static4-0 (export "static4-0") 1 (ref null $type8))
(table $static5-0 (export "static5-0") 1 (ref null $type10))
(table $static6-0 (export "static6-0") 1 (ref null $type12))
(global $"C12 0" (ref $BoxedInt) <...>)
(global $"C387 \"FooConstBase(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConstBase(")
(struct.new $JSStringImpl))
(global $"C388 FooConst0" (ref $FooConst0)
(i32.const 116)
(i32.const 0)
(struct.new $FooConst0))
(global $"C389 \"FooConst0(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst0(")
(struct.new $JSStringImpl))
(global $"C505 \"foo0Code(\"" (ref $JSStringImpl) <...>)
(global $"C8 \")\"" (ref $JSStringImpl) <...>)
(global $fooGlobal0 (mut (ref null $#Top))
(ref.null none))
(func $"foo0Code <noInline>" (export "func12") (param $var0 (ref null $#Top)) (result (ref null $#Top))
global.get $"C388 FooConst0"
call $print
drop
global.get $"C505 \"foo0Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C12 0"
global.set $fooGlobal0
ref.null none
)
(func $FooConst0.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst0))
local.get $var0
ref.cast $FooConst0
global.get $"C389 \"FooConst0(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
(func $FooConstBase.doit (export "func14") (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
global.get $"C387 \"FooConstBase(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
ref.null none
)
(func $JSStringImpl._interpolate3 (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref $JSStringImpl)) <...>)
(func $print (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>)
)
@@ -0,0 +1,145 @@
(module $module1
(type $#Top <...>)
(type $Array<Object?> <...>)
(type $BoxedInt <...>)
(type $FooConst0 <...>)
(type $FooConst1 <...>)
(type $FooConst2 <...>)
(type $FooConst3 <...>)
(type $FooConst4 <...>)
(type $FooConst5 <...>)
(type $FooConstBase <...>)
(type $GrowableList <...>)
(type $JSStringImpl <...>)
(type $Object <...>)
(type $_InterfaceType <...>)
(type $_Type <...>)
(type $type0 <...>)
(type $type10 <...>)
(type $type2 <...>)
(type $type4 <...>)
(type $type6 <...>)
(type $type8 <...>)
(func $"WasmListBase.[]" (import "module0" "func13") (param (ref $Object) i64) (result (ref null $#Top)))
(func $"foo0Code <noInline>" (import "module0" "func12") (param (ref null $#Top)) (result (ref null $#Top)))
(func $"fooGlobal0 implicit getter" (import "module0" "func11") (result (ref $#Top)))
(func $FooConstBase.doit (import "module0" "func14") (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top)))
(func $GrowableList._withData (import "module0" "func15") (param (ref $_Type) (ref $Array<Object?>)) (result (ref $GrowableList)))
(func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl)))
(func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".FooConst5(" (import "" "FooConst5(") (ref extern))
(global $"C386 5" (import "module0" "global5") (ref $BoxedInt))
(global $"C388 FooConst0" (import "module0" "global6") (ref $FooConst0))
(global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl))
(table $module0.dispatch0 (import "module0" "dispatch0") 793 funcref)
(table $module0.static1-0 (import "module0" "static1-0") 4 (ref null $type2))
(table $module0.static2-0 (import "module0" "static2-0") 4 (ref null $type0))
(table $module0.static3-0 (import "module0" "static3-0") 1 (ref null $type4))
(table $module0.static4-0 (import "module0" "static4-0") 1 (ref null $type6))
(table $module0.static5-0 (import "module0" "static5-0") 1 (ref null $type8))
(table $module0.static6-0 (import "module0" "static6-0") 1 (ref null $type10))
(global $"C507 FooConst5" (ref $FooConst5)
(i32.const 121)
(i32.const 0)
(struct.new $FooConst5))
(global $"C508 \"foo5Code(\"" (ref $JSStringImpl) <...>)
(global $"C509 \"FooConst5(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst5(")
(struct.new $JSStringImpl))
(global $"C510 _InterfaceType" (ref $_InterfaceType) <...>)
(global $allFooConstants (mut (ref null $GrowableList))
(ref.null none))
(global $fooGlobal5 (mut (ref null $#Top))
(ref.null none))
(elem $module0.dispatch0 <...>)
(func $"foo5Code <noInline>" (param $var0 (ref $#Top))
(local $var1 (ref $FooConstBase))
global.get $"C507 FooConst5"
call $print
drop
global.get $"C508 \"foo5Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C386 5"
global.set $fooGlobal5
call $"fooGlobal0 implicit getter"
call $"foo0Code <noInline>"
drop
i32.const 0
call_indirect $module0.static2-0 (result (ref $#Top))
i32.const 0
call_indirect $module0.static1-0 (param (ref null $#Top)) (result (ref null $#Top))
drop
i32.const 1
call_indirect $module0.static2-0 (result (ref $#Top))
i32.const 1
call_indirect $module0.static1-0 (param (ref null $#Top)) (result (ref null $#Top))
drop
i32.const 2
call_indirect $module0.static2-0 (result (ref $#Top))
i32.const 2
call_indirect $module0.static1-0 (param (ref null $#Top)) (result (ref null $#Top))
drop
i32.const 3
call_indirect $module0.static2-0 (result (ref $#Top))
i32.const 3
call_indirect $module0.static1-0 (param (ref null $#Top)) (result (ref null $#Top))
drop
block $label0 (result (ref $GrowableList))
global.get $allFooConstants
br_on_non_null $label0
global.get $"C510 _InterfaceType"
global.get $"C388 FooConst0"
i32.const 0
call_indirect $module0.static3-0 (result (ref $FooConst1))
i32.const 0
call_indirect $module0.static4-0 (result (ref $FooConst2))
i32.const 0
call_indirect $module0.static5-0 (result (ref $FooConst3))
i32.const 0
call_indirect $module0.static6-0 (result (ref $FooConst4))
global.get $"C507 FooConst5"
array.new_fixed $Array<Object?> 6
call $GrowableList._withData
global.set $allFooConstants
global.get $allFooConstants
ref.as_non_null
end $label0
i64.const 0
call $"WasmListBase.[]"
ref.cast $FooConstBase
local.tee $var1
block $label1 (result (ref $#Top))
global.get $fooGlobal5
br_on_non_null $label1
call $"fooGlobal5 implicit getter"
end $label1
local.get $var1
struct.get $FooConstBase $field0
i32.const 413
i32.add
call_indirect $module0.dispatch0 (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top))
drop
)
(func $fooGlobal5 implicit getter (result (ref $#Top)) <...>)
(func $FooConst5.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst5))
local.get $var0
ref.cast $FooConst5
global.get $"C509 \"FooConst5(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
)
@@ -0,0 +1,2 @@
(module $module10
)
@@ -0,0 +1,54 @@
(module $module2
(type $#Top <...>)
(type $BoxedInt <...>)
(type $FooConst1 <...>)
(type $FooConstBase <...>)
(type $JSStringImpl <...>)
(func $FooConstBase.doit (import "module0" "func14") (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top)))
(func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl)))
(func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".FooConst1(" (import "" "FooConst1(") (ref extern))
(global $"C316 1" (import "module0" "global7") (ref $BoxedInt))
(global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl))
(global $"C511 FooConst1" (ref $FooConst1)
(i32.const 117)
(i32.const 0)
(struct.new $FooConst1))
(global $"C518 \"FooConst1(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst1(")
(struct.new $JSStringImpl))
(global $"C526 \"foo1Code(\"" (ref $JSStringImpl) <...>)
(global $fooGlobal1 (mut (ref null $#Top))
(ref.null none))
(func $"foo1Code <noInline>" (param $var0 (ref null $#Top)) (result (ref null $#Top))
global.get $"C511 FooConst1"
call $print
drop
global.get $"C526 \"foo1Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C316 1"
global.set $fooGlobal1
ref.null none
)
(func $FooConst1.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst1))
local.get $var0
ref.cast $FooConst1
global.get $"C518 \"FooConst1(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
)
@@ -0,0 +1,54 @@
(module $module3
(type $#Top <...>)
(type $BoxedInt <...>)
(type $FooConst2 <...>)
(type $FooConstBase <...>)
(type $JSStringImpl <...>)
(func $FooConstBase.doit (import "module0" "func14") (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top)))
(func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl)))
(func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".FooConst2(" (import "" "FooConst2(") (ref extern))
(global $"C345 2" (import "module0" "global11") (ref $BoxedInt))
(global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl))
(global $"C512 FooConst2" (ref $FooConst2)
(i32.const 118)
(i32.const 0)
(struct.new $FooConst2))
(global $"C517 \"FooConst2(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst2(")
(struct.new $JSStringImpl))
(global $"C525 \"foo2Code(\"" (ref $JSStringImpl) <...>)
(global $fooGlobal2 (mut (ref null $#Top))
(ref.null none))
(func $"foo2Code <noInline>" (param $var0 (ref null $#Top)) (result (ref null $#Top))
global.get $"C512 FooConst2"
call $print
drop
global.get $"C525 \"foo2Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C345 2"
global.set $fooGlobal2
ref.null none
)
(func $FooConst2.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst2))
local.get $var0
ref.cast $FooConst2
global.get $"C517 \"FooConst2(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
)
@@ -0,0 +1,54 @@
(module $module4
(type $#Top <...>)
(type $BoxedInt <...>)
(type $FooConst3 <...>)
(type $FooConstBase <...>)
(type $JSStringImpl <...>)
(func $FooConstBase.doit (import "module0" "func14") (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top)))
(func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl)))
(func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".FooConst3(" (import "" "FooConst3(") (ref extern))
(global $"C424 3" (import "module0" "global10") (ref $BoxedInt))
(global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl))
(global $"C513 FooConst3" (ref $FooConst3)
(i32.const 119)
(i32.const 0)
(struct.new $FooConst3))
(global $"C516 \"FooConst3(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst3(")
(struct.new $JSStringImpl))
(global $"C524 \"foo3Code(\"" (ref $JSStringImpl) <...>)
(global $fooGlobal3 (mut (ref null $#Top))
(ref.null none))
(func $"foo3Code <noInline>" (param $var0 (ref null $#Top)) (result (ref null $#Top))
global.get $"C513 FooConst3"
call $print
drop
global.get $"C524 \"foo3Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C424 3"
global.set $fooGlobal3
ref.null none
)
(func $FooConst3.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst3))
local.get $var0
ref.cast $FooConst3
global.get $"C516 \"FooConst3(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
)
@@ -0,0 +1,54 @@
(module $module5
(type $#Top <...>)
(type $BoxedInt <...>)
(type $FooConst4 <...>)
(type $FooConstBase <...>)
(type $JSStringImpl <...>)
(func $FooConstBase.doit (import "module0" "func14") (param (ref $FooConstBase) (ref null $#Top)) (result (ref null $#Top)))
(func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl)))
(func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".FooConst4(" (import "" "FooConst4(") (ref extern))
(global $"C364 4" (import "module0" "global9") (ref $BoxedInt))
(global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl))
(global $"C514 FooConst4" (ref $FooConst4)
(i32.const 120)
(i32.const 0)
(struct.new $FooConst4))
(global $"C515 \"FooConst4(\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".FooConst4(")
(struct.new $JSStringImpl))
(global $"C523 \"foo4Code(\"" (ref $JSStringImpl) <...>)
(global $fooGlobal4 (mut (ref null $#Top))
(ref.null none))
(func $"foo4Code <noInline>" (param $var0 (ref null $#Top)) (result (ref null $#Top))
global.get $"C514 FooConst4"
call $print
drop
global.get $"C523 \"foo4Code(\""
local.get $var0
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
global.get $"C364 4"
global.set $fooGlobal4
ref.null none
)
(func $FooConst4.doit (param $var0 (ref $FooConstBase)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
(local $var2 (ref $FooConst4))
local.get $var0
ref.cast $FooConst4
global.get $"C515 \"FooConst4(\""
local.get $var1
global.get $"C8 \")\""
call $JSStringImpl._interpolate3
call $print
drop
local.get $var1
call $FooConstBase.doit
drop
ref.null none
)
)
@@ -0,0 +1,2 @@
(module $module6
)
@@ -0,0 +1,2 @@
(module $module7
)
@@ -0,0 +1,2 @@
(module $module8
)
@@ -0,0 +1,2 @@
(module $module9
)
@@ -1,23 +1,2 @@
(module $module0
(type $#Top (struct
(field $field0 i32)))
(type $JSStringImpl (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $_ref externref))))
(type $Object (sub $#Top (struct
(field $field0 i32)
(field $field1 (mut i32)))))
(global $".hello world" (import "" "hello world") (ref extern))
(global $"C375 \"hello world\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".hello world")
(struct.new $JSStringImpl))
(func $"mainFoo <noInline>" (export "func0") (result (ref null $#Top))
global.get $"C375 \"hello world\""
call $print
ref.null none
)
(func $print (param $var0 (ref $#Top)) <...>)
)
@@ -1,10 +1,27 @@
(module $module1
(type $#Top (struct
(field $field0 i32)))
(func $"mainFoo <noInline>" (import "module0" "func0") (result (ref null $#Top)))
(type $JSStringImpl (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
(field $_ref externref))))
(type $Object (sub $#Top (struct
(field $field0 i32)
(field $field1 (mut i32)))))
(func $print (import "module0" "func0") (param (ref null $#Top)) (result (ref null $#Top)))
(global $".hello world" (import "" "hello world") (ref extern))
(global $"C460 \"hello world\"" (ref $JSStringImpl)
(i32.const 4)
(i32.const 0)
(global.get $".hello world")
(struct.new $JSStringImpl))
(func $"deferredFoo <noInline>" (result (ref null $#Top))
call $"mainFoo <noInline>"
drop
ref.null none
)
(func $"mainFoo <noInline>"
global.get $"C460 \"hello world\""
call $print
drop
)
)
+5 -1
View File
@@ -27,7 +27,11 @@ class Imports {
Imports.deserialized(this.all, this.functions, this.tags, this.globals,
this.tables, this.memories) {
assert(all.length ==
(functions.length + tags.length + globals.length + tables.length));
(functions.length +
tags.length +
globals.length +
tables.length +
memories.length));
}
}
@@ -207,6 +207,7 @@ class ImportSection extends Section {
module, moduleName, name, ir.FinalizableIndex(), type);
tag.finalizableIndex.value = importedTags.length;
importedTags.add(tag);
imports.add(tag);
default:
throw "Invalid import kind: $kind";
}