From 303020817411b316fca610d31e9a37e7322869e2 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Thu, 11 Dec 2025 04:50:39 -0800 Subject: [PATCH] [dart2wasm] Implement fine grained deferred module splitting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 65 +--- pkg/dart2wasm/lib/compile.dart | 25 -- pkg/dart2wasm/lib/constants.dart | 2 +- .../lib/deferred_load/dependencies.dart | 275 +++++++++++++ .../lib/deferred_load/import_set.dart | 241 ++++++++++++ .../lib/deferred_load/partition.dart | 291 ++++++++++++++ pkg/dart2wasm/lib/deferred_loading.dart | 256 +++--------- pkg/dart2wasm/lib/dynamic_modules.dart | 19 +- .../lib/library_dependencies_pruner.dart | 365 ------------------ pkg/dart2wasm/lib/modules.dart | 87 ++++- pkg/dart2wasm/lib/translator.dart | 24 +- .../data/deferred_load_ids.golden.json | 4 +- .../deferred.constant.multi_module_use.wat | 3 + ...rred.constant.multi_module_use_module1.wat | 45 +-- ...rred.constant.multi_module_use_module2.wat | 22 ++ ...rred.constant.multi_module_use_module3.wat | 45 +-- .../test/ir_tests/deferred.fine_grained.dart | 195 ++++++++++ .../test/ir_tests/deferred.fine_grained.wat | 82 ++++ .../deferred.fine_grained_module1.wat | 145 +++++++ .../deferred.fine_grained_module10.wat | 2 + .../deferred.fine_grained_module2.wat | 54 +++ .../deferred.fine_grained_module3.wat | 54 +++ .../deferred.fine_grained_module4.wat | 54 +++ .../deferred.fine_grained_module5.wat | 54 +++ .../deferred.fine_grained_module6.wat | 2 + .../deferred.fine_grained_module7.wat | 2 + .../deferred.fine_grained_module8.wat | 2 + .../deferred.fine_grained_module9.wat | 2 + pkg/dart2wasm/test/ir_tests/import_name.wat | 21 - .../test/ir_tests/import_name_module1.wat | 21 +- pkg/wasm_builder/lib/src/ir/imports.dart | 6 +- .../lib/src/serialize/sections.dart | 1 + 32 files changed, 1702 insertions(+), 764 deletions(-) create mode 100644 pkg/dart2wasm/lib/deferred_load/dependencies.dart create mode 100644 pkg/dart2wasm/lib/deferred_load/import_set.dart create mode 100644 pkg/dart2wasm/lib/deferred_load/partition.dart delete mode 100644 pkg/dart2wasm/lib/library_dependencies_pruner.dart create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained.dart create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module10.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module6.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module8.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 40ba2aa1928..1caff71dc30 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -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() - // in - // - // - // Then we know that the constant use in can only happen after the - // given was loaded, i.e. the constant use is deferred-load-guarded - // by . - 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); } } } diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index d6052e2f502..c28a983f2fc 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart @@ -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 _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(); diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index f9bed9a1409..ad588faf977 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -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 diff --git a/pkg/dart2wasm/lib/deferred_load/dependencies.dart b/pkg/dart2wasm/lib/deferred_load/dependencies.dart new file mode 100644 index 00000000000..b1c07ee46bb --- /dev/null +++ b/pkg/dart2wasm/lib/deferred_load/dependencies.dart @@ -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.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() + // in + // + // + // 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 _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 _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 references; + final Map> deferredReferences; + final Set constants; + final Map> deferredConstants; + + DirectReferenceDependencies(this.references, this.deferredReferences, + this.constants, this.deferredConstants); +} + +class DirectConstantDependencies { + final Set constants; + + DirectConstantDependencies(this.constants); +} diff --git a/pkg/dart2wasm/lib/deferred_load/import_set.dart b/pkg/dart2wasm/lib/deferred_load/import_set.dart new file mode 100644 index 00000000000..2fa7eee952c --- /dev/null +++ b/pkg/dart2wasm/lib/deferred_load/import_set.dart @@ -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 initialSets = {}; + + /// Index of deferred imports that defines the canonical order used by the + /// operations below. + final Map _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] to an [ImportSet]. + ImportSet setOfImportsToImportSet(Set 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 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]. + /// Note: Not for performance sensitive code. + Set 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 imports; + + Part(this.isRoot, this.imports); +} diff --git a/pkg/dart2wasm/lib/deferred_load/partition.dart b/pkg/dart2wasm/lib/deferred_load/partition.dart new file mode 100644 index 00000000000..b18232473cb --- /dev/null +++ b/pkg/dart2wasm/lib/deferred_load/partition.dart @@ -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 roots) { + final allDeferredImports = []; + 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 parts; + final Map referenceToPart; + final Map constantToPart; + final Map> deferredImportToParts; + + Partitioning(this.root, this.parts, this.referenceToPart, this.constantToPart, + this.deferredImportToParts); +} + +class _Algorithm { + final Component component; + final DependenciesCollector depsCollector; + final List allDeferredImports; + + final ImportSetLattice importSets = ImportSetLattice(); + + // The work queues for propagating import set additions. + late final _WorkQueue referenceQueue = _WorkQueue(importSets); + late final _WorkQueue constantQueue = _WorkQueue(importSets); + + // Caches of direct dependencies of [Reference]s/[Constants]s. + final Map + directReferenceDependencies = {}; + final Map directConstantDependencies = + {}; + + // The [ImportSet] the given [Reference]/[Constant]s are needed for. + final Map referenceToImportSet = {}; + final Map constantToImportSet = {}; + + _Algorithm(this.component, this.depsCollector, this.allDeferredImports); + + Partitioning run(Set 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 = {}; + final constantToPart = {}; + final parts = [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 = >{}; + 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 { + final ImportSetLattice _importSets; + final Queue _queue = Queue(); + final Map _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); + } +} diff --git a/pkg/dart2wasm/lib/deferred_loading.dart b/pkg/dart2wasm/lib/deferred_loading.dart index cc5e59115e7..4567fb1cca6 100644 --- a/pkg/dart2wasm/lib/deferred_loading.dart +++ b/pkg/dart2wasm/lib/deferred_loading.dart @@ -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 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 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> 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 = {}; + for (final part in partition.parts) { + moduleMetadata[part] = builder.buildModuleMetadata(); + } + final referenceToModuleMetadata = {}; + partition.referenceToPart.forEach((reference, output) { + referenceToModuleMetadata[reference] = moduleMetadata[output]!; + }); + final constantToModuleMetadata = {}; + 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 = { - ...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 _findWasmRoots() { + final exports = {}; + final trueConstant = BoolConstant(true); + + bool check(Annotatable node) { + if (getPragma(coreTypes, node, 'wasm:export') != null || + getPragma(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, Map>) - _buildLibraryToImports() { - final entryPoint = component.mainMethod!.enclosingLibrary; - final deferredRootStack = [entryPoint]; - final enqueuedDeferredRoots = {entryPoint}; - final libraryToRootSet = {}; - final importTargetMap = >{}; - bool isMainRoot = true; - - while (deferredRootStack.isNotEmpty) { - final currentRoot = deferredRootStack.removeLast(); - final eagerWorkStack = [currentRoot]; - final enqueuedEagerLibraries = {currentRoot}; - final newDeferredRoots = []; - 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 = []; final importMap = >{}; final internalLib = coreTypes.index.getLibrary('dart:_internal'); // Put each library in a separate module. + final libraryMap = {}; 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 diff --git a/pkg/dart2wasm/lib/dynamic_modules.dart b/pkg/dart2wasm/lib/dynamic_modules.dart index 42d9d5de367..27ba28f10a0 100644 --- a/pkg/dart2wasm/lib/dynamic_modules.dart +++ b/pkg/dart2wasm/lib/dynamic_modules.dart @@ -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 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 = {}; 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 diff --git a/pkg/dart2wasm/lib/library_dependencies_pruner.dart b/pkg/dart2wasm/lib/library_dependencies_pruner.dart deleted file mode 100644 index 9e01f62e69a..00000000000 --- a/pkg/dart2wasm/lib/library_dependencies_pruner.dart +++ /dev/null @@ -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 usedLibraries; - final Library library; - final additionalDeferredImports = - >{}; - - 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 = {}; - - // Maps a library to all the deferred imports that made this library - // available. - final libraryToDeferredImports = >{}; - - // The new set of imports (possibly smaller - removing unused dependencies, - // possibly larger - adding used but not yet imported dependencies). - final prunedDependencies = []; - 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 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 = >{}; - - /// 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 librariesFor(Constant constant) { - final existing = _constantToTransitiveLibraries[constant]; - if (existing != null) return existing; - - final transitiveLibraries = { - 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 {} : transitiveLibraries; - } -} - -class _ChildConstantCollector extends RecursiveVisitor { - /// Returns the set of constants referred to by the (possibly composed) - /// [constant]. - static Set directChildrenOf(Constant constant) { - final children = {}; - constant.visitChildren(_ChildConstantCollector._(children)); - return children; - } - - final Set _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 _transitiveLibrarySet(Library library) { - final transitiveLibrarySet = {library}; - final worklist = [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; -} diff --git a/pkg/dart2wasm/lib/modules.dart b/pkg/dart2wasm/lib/modules.dart index 5ee80a914cd..c753176116a 100644 --- a/pkg/dart2wasm/lib/modules.dart +++ b/pkg/dart2wasm/lib/modules.dart @@ -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 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 modules; - /// Maps the [Library] to the corresponding [ModuleMetadata]. - late final Map _libraryToModuleMetadata = { - for (final metadata in modules) - for (final library in metadata.libraries) library: metadata, - }; + /// Maps the [Reference] to the corresponding [ModuleMetadata]. + final Map? referenceToModuleMetadata; - ModuleOutputData(this.modules) : assert(modules[0].isMain); + /// Maps the [Constant] to the corresponding [ModuleMetadata]. + final Map? constantToModuleMetadata; + + /// Maps the [Library] to the corresponding [ModuleMetadata]. + final Map? 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 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 loadId2ImportedLibrary; + // Maps the unique load id to the deferred import. + final List loadIdToDeferredImport; // Maps (library, import-name)-id to list of needed modules. final List> 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 = []; + final loadIdToDeferredImport = []; final moduleMap = >[]; 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( diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 64572748046..c10b2274838 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -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 modulesForLoadId(Library enclosingLibrary, int loadId) { + return [ + for (final moduleMetadata in loadingMap.moduleMap[loadId]) + _outputToBuilder[moduleMetadata]!, + ]; } String nameForModule(w.ModuleBuilder module) => diff --git a/pkg/dart2wasm/test/deferred_loading/data/deferred_load_ids.golden.json b/pkg/dart2wasm/test/deferred_loading/data/deferred_load_ids.golden.json index 03988c79665..dcdef07bd49 100644 --- a/pkg/dart2wasm/test/deferred_loading/data/deferred_load_ids.golden.json +++ b/pkg/dart2wasm/test/deferred_loading/data/deferred_load_ids.golden.json @@ -3,7 +3,7 @@ "name": "", "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": { diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat index d0acbf70ed4..81471646ff0 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat @@ -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 " (param $var0 i32) (local $var1 (ref $MyConstClass)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat index d8d06886a6e..18cddca9110 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat @@ -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 " (param $var0 i32) (result (ref $MyConstClass)) - (local $var1 (ref $JSStringImpl)) - (local $var2 (ref $MyConstClass)) + (func $"modH0Use " (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 ) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat index e7a73780556..9111751cbea 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat @@ -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)) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat index 5c3b89820ef..e87e7f7aa9e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat @@ -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 " (param $var0 i32) (result (ref $MyConstClass)) - (local $var1 (ref $JSStringImpl)) - (local $var2 (ref $MyConstClass)) + (func $"modH1Use " (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 ) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.dart b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.dart new file mode 100644 index 00000000000..e1854626d55 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.dart @@ -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 = [ + 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'; diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat new file mode 100644 index 00000000000..70aa3a46057 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat @@ -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 " (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)) <...>) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat new file mode 100644 index 00000000000..d7307ca2342 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat @@ -0,0 +1,145 @@ +(module $module1 + (type $#Top <...>) + (type $Array <...>) + (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 " (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)) (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 " (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 " + 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 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 + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module10.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module10.wat new file mode 100644 index 00000000000..9134c81d96c --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module10.wat @@ -0,0 +1,2 @@ +(module $module10 +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat new file mode 100644 index 00000000000..8e045f53076 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat @@ -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 " (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 + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat new file mode 100644 index 00000000000..0ca7150b3fd --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat @@ -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 " (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 + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat new file mode 100644 index 00000000000..330bbd3caba --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat @@ -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 " (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 + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat new file mode 100644 index 00000000000..96384e343fc --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat @@ -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 " (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 + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module6.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module6.wat new file mode 100644 index 00000000000..49808ca76af --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module6.wat @@ -0,0 +1,2 @@ +(module $module6 +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat new file mode 100644 index 00000000000..b529d5a9041 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat @@ -0,0 +1,2 @@ +(module $module7 +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module8.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module8.wat new file mode 100644 index 00000000000..3a17fabea1e --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module8.wat @@ -0,0 +1,2 @@ +(module $module8 +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat new file mode 100644 index 00000000000..5000dece59d --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat @@ -0,0 +1,2 @@ +(module $module9 +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/import_name.wat b/pkg/dart2wasm/test/ir_tests/import_name.wat index 1d527084256..261787ca3c9 100644 --- a/pkg/dart2wasm/test/ir_tests/import_name.wat +++ b/pkg/dart2wasm/test/ir_tests/import_name.wat @@ -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 " (export "func0") (result (ref null $#Top)) - global.get $"C375 \"hello world\"" - call $print - ref.null none - ) - (func $print (param $var0 (ref $#Top)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat index 300a7e85eb8..baa9067c129 100644 --- a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat @@ -1,10 +1,27 @@ (module $module1 (type $#Top (struct (field $field0 i32))) - (func $"mainFoo " (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 " (result (ref null $#Top)) call $"mainFoo " - drop ref.null none ) + (func $"mainFoo " + global.get $"C460 \"hello world\"" + call $print + drop + ) ) \ No newline at end of file diff --git a/pkg/wasm_builder/lib/src/ir/imports.dart b/pkg/wasm_builder/lib/src/ir/imports.dart index 1492beb128d..6749590e21c 100644 --- a/pkg/wasm_builder/lib/src/ir/imports.dart +++ b/pkg/wasm_builder/lib/src/ir/imports.dart @@ -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)); } } diff --git a/pkg/wasm_builder/lib/src/serialize/sections.dart b/pkg/wasm_builder/lib/src/serialize/sections.dart index 6b73699b47c..9e49c805e32 100644 --- a/pkg/wasm_builder/lib/src/serialize/sections.dart +++ b/pkg/wasm_builder/lib/src/serialize/sections.dart @@ -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"; }