[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:
committed by
Commit Queue
parent
87032377ee
commit
3030208174
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user