From e3cf529f87979999b784201412b5f4f5a62b73a1 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 5 Jun 2026 00:50:46 -0700 Subject: [PATCH] [dart2wasm] More compact encoding of deferred load lists Measured on size of e main module (baseline is we don't embed it in application code): * embedding before: +16.5% uncompressed / +9.1% compressed * embedding with this CL: +4% uncompressed / +4.3% compressed When embeddeding deferred load list information into the app (as opposed to a separate json file) we now use a more compact encoding. Specifically: Instead of encoding it as an array of an array of strings (which are module names), we encode it as an array of an array of module ids and construct the module name from the id. To make the array of module ids more compact we utilize the fact that we can sort them and encode in delta encoding (i.e. instead of absolute module ids, encode the diff between previous module id in the list). We put the encoded module id lists in a data section and create `WasmArray`s from them at startup. When we trigger a load we then decode them into the list of module names. There's more opportunity to optimize it, but it's good to do this as a first step. Change-Id: I293fb8879d992fc370786f6c9b258ccd27e1559b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508980 Reviewed-by: Srujan Gaddam Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/compile.dart | 3 +- pkg/dart2wasm/lib/compiler_options.dart | 41 ++++-- pkg/dart2wasm/lib/io_util.dart | 9 +- pkg/dart2wasm/lib/kernel_nodes.dart | 6 + pkg/dart2wasm/lib/modules.dart | 6 +- pkg/dart2wasm/lib/translator.dart | 135 +++++++++++++----- .../fuse_with_and.constraints.txt | 1 - .../custom_split/fuse_with_and.default.txt | 1 - .../custom_split/fuse_with_or.constraints.txt | 1 - .../custom_split/fuse_with_or.default.txt | 1 - .../custom_split/just_fuse.constraints.txt | 1 - .../custom_split/just_fuse.default.txt | 1 - ...rred.constant.multi_module_use_module1.wat | 2 +- ...rred.constant.multi_module_use_module2.wat | 2 +- ...rred.constant.multi_module_use_module3.wat | 2 +- .../deferred.constant.type_use_module1.wat | 4 +- .../ir_tests/deferred.constant_module1.wat | 4 +- .../ir_tests/deferred.constant_module2.wat | 2 +- ...red.fine_grained.devirtualized_module1.wat | 6 +- .../test/ir_tests/deferred.fine_grained.wat | 2 +- .../deferred.fine_grained_module2.wat | 2 +- .../deferred.fine_grained_module3.wat | 6 +- .../deferred.fine_grained_module5.wat | 2 +- .../deferred.fine_grained_module7.wat | 2 +- .../deferred.fine_grained_module9.wat | 2 +- .../ir_tests/deferred.init_at_startup.wat | 10 ++ .../ir_tests/dispatch_table_reuse_module1.wat | 8 +- .../test/ir_tests/import_name_module1.wat | 2 +- .../wasm/js_common/deferred_patch.dart | 59 ++++++-- 29 files changed, 232 insertions(+), 91 deletions(-) diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index abeeedc1452..0b4456e9db9 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart @@ -40,6 +40,7 @@ import 'compiler_options.dart' as compiler; import 'constant_evaluator.dart'; import 'deferred_loading.dart'; import 'dry_run.dart'; +import 'generate_wasm.dart'; import 'io_util.dart'; import 'js/runtime_generator.dart' as js; import 'modules.dart'; @@ -666,7 +667,7 @@ Future _runCodegenPhase( final wasmOutputFilename = path.basename(options.outputFile); final moduleIds = modules.keys .map( - (moduleMetadata) => options.idForModuleName( + (moduleMetadata) => WasmCompilerOptions.idForModuleName( wasmOutputFilename, moduleMetadata.moduleName, )!, diff --git a/pkg/dart2wasm/lib/compiler_options.dart b/pkg/dart2wasm/lib/compiler_options.dart index 9642d0d77e3..e92c61a0044 100644 --- a/pkg/dart2wasm/lib/compiler_options.dart +++ b/pkg/dart2wasm/lib/compiler_options.dart @@ -75,12 +75,19 @@ class WasmCompilerOptions { translatorOptions.enableDeferredLoading || translatorOptions.enableMultiModuleStressTestMode; - String moduleNameForId(String filePath, int id, {bool emitAsMain = false}) => - emitAsMain || id == mainModuleId - ? path.basename(filePath) - : path.basename(path.setExtension(filePath, '_module$id.wasm')); + static String moduleNameForId( + String filePath, + int id, { + bool emitAsMain = false, + }) { + final basename = path.basename(filePath); + if (emitAsMain || id == mainModuleId) { + return basename; + } + return '${deferredModuleFilenamePrefix(basename)}$id.wasm'; + } - int? idForModuleName(String mainWasmFilename, String moduleFilename) { + static int? idForModuleName(String mainWasmFilename, String moduleFilename) { assert( mainWasmFilename.endsWith('.wasm') && !mainWasmFilename.contains(path.separator), @@ -93,14 +100,28 @@ class WasmCompilerOptions { !moduleFilename.endsWith('.wasm')) { return null; } - return int.tryParse( - moduleFilename.substring( - prefix.length, - moduleFilename.length - '.wasm'.length, - ), + return idFromDeferredModuleFilename(moduleFilename); + } + + /// Given a deferred module filename returns the module id. + /// + /// (i.e. returns `` for `test_module.wasm`). + static int idFromDeferredModuleFilename(String moduleName) { + // The name has pattern: "..._module.wasm" + assert(moduleName.endsWith('.wasm') && moduleName.contains('_module')); + final offset = moduleName.lastIndexOf('_module') + '_module'.length; + return int.parse( + moduleName.substring(offset, moduleName.length - '.wasm'.length), ); } + /// The prefix of all deferred module filenames. + /// + /// (i.e. returns `test_module` for main module `test.wasm` and + /// deferred modules `test_module.wasm`). + static String deferredModuleFilenamePrefix(String mainModuleFilename) => + path.basename(path.setExtension(mainModuleFilename, '_module')); + static int _defaultMaxActiveWasmOptProcesses() { try { return Platform.numberOfProcessors; diff --git a/pkg/dart2wasm/lib/io_util.dart b/pkg/dart2wasm/lib/io_util.dart index 9f470382102..5209fc19e7e 100644 --- a/pkg/dart2wasm/lib/io_util.dart +++ b/pkg/dart2wasm/lib/io_util.dart @@ -135,9 +135,12 @@ class CompilerPhaseInputOutputManager { int moduleId, List flags, ) async { - final inputModuleName = options.moduleNameForId(mainWasmModule, moduleId); + final inputModuleName = WasmCompilerOptions.moduleNameForId( + mainWasmModule, + moduleId, + ); - final outputModuleName = options.moduleNameForId( + final outputModuleName = WasmCompilerOptions.moduleNameForId( options.outputFile, moduleId, ); @@ -224,7 +227,7 @@ class CompilerPhaseInputOutputManager { final moduleIds = {}; for (final file in files) { if (file is! File) continue; - final moduleId = options.idForModuleName( + final moduleId = WasmCompilerOptions.idForModuleName( mainWasmFilename, path.basename(file.path), ); diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index d72abd0fd9b..fc089b429ca 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -774,6 +774,12 @@ mixin KernelNodes { LibraryIndex.topLevel, 'get:_loadingMap', ); + late final Procedure? dartInternalModuleNamePrefixGetter = index + .tryGetProcedure( + 'dart:_internal', + LibraryIndex.topLevel, + 'get:_moduleNamePrefix', + ); late final Procedure? dartInternalLoadingMapNamesGetter = index .tryGetProcedure( 'dart:_internal', diff --git a/pkg/dart2wasm/lib/modules.dart b/pkg/dart2wasm/lib/modules.dart index 89d4ec0087b..cbb43298b14 100644 --- a/pkg/dart2wasm/lib/modules.dart +++ b/pkg/dart2wasm/lib/modules.dart @@ -37,7 +37,11 @@ class ModuleMetadataBuilder { : 'module$id'; return ModuleMetadata._( moduleImportName, - options.moduleNameForId(options.outputFile, id, emitAsMain: emitAsMain), + WasmCompilerOptions.moduleNameForId( + options.outputFile, + id, + emitAsMain: emitAsMain, + ), skipEmit: skipEmit, isMain: id == WasmCompilerOptions.mainModuleId, ); diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 06ac6521be6..728a7470474 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -28,6 +28,7 @@ import 'dispatch_table.dart'; import 'dynamic_dispatch_table.dart'; import 'dynamic_dispatchers.dart'; import 'functions.dart'; +import 'generate_wasm.dart'; import 'globals.dart'; import 'kernel_nodes.dart'; import 'modules.dart'; @@ -645,40 +646,85 @@ class Translator with KernelNodes { // NOTE: We do this after code generation is complete. So the code generation // phase has the opportunity to generate more wasm modules and add them to the // loading map. + // + // Keep in sync with sdk/lib/_internal/wasm/js_common/deferred_patch.dart's + // `_decodeEncodedModuleIds` and `_loadLibraryViaEmbedderModuleNames` void _patchLoadingMapGetter(w.FunctionBuilder function) { - final externRef = w.RefType.extern(nullable: false); - final arrayExternRef = wasmArrayType( - externRef, - externRef.toString(), - mutable: false, - ); - final arrayArrayString = wasmArrayType( - w.RefType(arrayExternRef, nullable: false), - arrayExternRef.toString(), - mutable: false, + final moduleMap = loadingMap.moduleMap; + final byteArrayType = wasmArrayType(w.PackedType.i8, 'WasmI8'); + final arrayOfNullableByteArray = wasmArrayType( + w.RefType(byteArrayType, nullable: true), + 'WasmArray', ); - _lazyInitializeGlobal( - function, - w.RefType(arrayArrayString, nullable: false), - 'loadIdModuleNames', - (b) { - final moduleMap = loadingMap.moduleMap; - for (int i = 0; i < moduleMap.length; ++i) { - final moduleNames = moduleMap[i]; - for (int k = 0; k < moduleNames.length; ++k) { - b.global_get( - getInternalizedStringGlobal( - function.moduleBuilder, - moduleNames[k].moduleName, - ), - ); - } - b.array_new_fixed(arrayExternRef, moduleNames.length); - } - b.array_new_fixed(arrayArrayString, moduleMap.length); - }, + // Make a global containing the load id -> module id list table. + final loadingMapGlobal = mainModule.globals.define( + w.GlobalType(w.RefType(arrayOfNullableByteArray, nullable: false)), ); + loadingMapGlobal.initializer + ..i32_const(moduleMap.length) + ..array_new_default(arrayOfNullableByteArray) + ..end(); + + // Make the getter return that array. + _replaceBody(function) + ..global_get(loadingMapGlobal) + ..end(); + + // Emit code to initialize the load id -> module id list table. + final startFunction = mainModule.startFunction.body; + final encodedSegment = mainModule.dataSegments.define(); + for (int i = 0; i < moduleMap.length; ++i) { + final moduleNames = moduleMap[i]; + if (moduleNames.isEmpty) continue; + + // We sort the module ids increasingly, thereby allowing us to encode them + // via delta to previous module id. + final moduleIds = []; + for (int k = 0; k < moduleNames.length; ++k) { + final moduleId = WasmCompilerOptions.idFromDeferredModuleFilename( + moduleNames[k].moduleName, + ); + moduleIds.add(moduleId); + } + moduleIds.sort(); + + // Make the encoded list of module ids. + final moduleIdsEncoded = BytesBuilder(); + moduleIdsEncoded.writeULEB128(moduleNames.length); + int lastId = 0; + for (int i = 0; i < moduleIds.length; ++i) { + final moduleId = moduleIds[i]; + final diff = moduleId - lastId; + moduleIdsEncoded.writeULEB128(diff); + lastId = moduleId; + } + + // Append the encoded module id list to the data segment & make start + // function patch the runtime with the list. + startFunction.global_get(loadingMapGlobal); + startFunction.i32_const(i); + { + startFunction.i32_const(encodedSegment.length); + startFunction.i32_const(moduleIdsEncoded.length); + startFunction.array_new_data(byteArrayType, encodedSegment); + encodedSegment.append(moduleIdsEncoded.takeBytes()); + } + startFunction.array_set(arrayOfNullableByteArray); + } + + final mainModuleOutput = _builderToOutput[mainModule]!; + final prefix = WasmCompilerOptions.deferredModuleFilenamePrefix( + mainModuleOutput.moduleName, + ); + final prefixGetter = + functions.getExistingFunction( + dartInternalModuleNamePrefixGetter!.reference, + ) + as w.FunctionBuilder; + _replaceBody(prefixGetter) + ..global_get(getInternalizedStringGlobal(mainModule, prefix)) + ..end(); } void _patchLoadingMapNamesGetter(w.FunctionBuilder function) { @@ -724,12 +770,7 @@ class Translator with KernelNodes { ..ref_null(w.HeapType.none) ..end(); - final b = w.InstructionsBuilder( - f.moduleBuilder, - f.type.inputs, - f.type.outputs, - ); - f.replaceBody(b); + final b = _replaceBody(f); final label = b.block(const [], [type]); b.global_get(global); @@ -743,6 +784,16 @@ class Translator with KernelNodes { b.end(); } + w.InstructionsBuilder _replaceBody(w.FunctionBuilder function) { + final newBody = w.InstructionsBuilder( + function.moduleBuilder, + function.type.inputs, + function.type.outputs, + ); + function.replaceBody(newBody); + return newBody; + } + void _printFunction(w.BaseFunction function, Object name) { if (options.printWasm) { print("#${function.name}: $name"); @@ -3968,3 +4019,15 @@ class SingleClosureTarget { SingleClosureTarget._(this.callTarget, this.paramInfo); } + +extension on BytesBuilder { + void writeULEB128(int value) { + assert(value >= 0); + do { + int byte = value & 0x7F; + value >>>= 7; + if (value != 0) byte |= 0x80; + addByte(byte); + } while (value != 0); + } +} diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt index 06027de75a4..db7cc27a906 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.constraints.txt @@ -55,7 +55,6 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_and/lib_010_0.dart::@methods::g_010_0 - pkg/compiler/test/custom_split/data/fuse_with_and/lib_100_0.dart::@methods::g_100_0 Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt index 949df457003..a9be2a91bcc 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_and.default.txt @@ -39,7 +39,6 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_and/libImport.dart::@methods::f_111_1 - pkg/compiler/test/custom_split/data/fuse_with_and/libImport.dart::@methods::v Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt index e8a0bd51f83..454a2915094 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.constraints.txt @@ -49,7 +49,6 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_or/libImport.dart::@methods::v - pkg/compiler/test/custom_split/data/fuse_with_or/lib_001_0.dart::@methods::g_001_0 Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt index 64fe2643f8c..a5aa9fec2e1 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/fuse_with_or.default.txt @@ -39,7 +39,6 @@ Part 0 - pkg/compiler/test/custom_split/data/fuse_with_or/libImport.dart::@methods::f_111_1 - pkg/compiler/test/custom_split/data/fuse_with_or/libImport.dart::@methods::v Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt index d8533f0f1cd..c6a25c84ec3 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.constraints.txt @@ -41,7 +41,6 @@ Part 0 - pkg/compiler/test/custom_split/data/just_fuse/libImport.dart::@methods::f_111_1 - pkg/compiler/test/custom_split/data/just_fuse/libImport.dart::@methods::v Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) diff --git a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt index fa1755cd53b..99b0495ef3e 100644 --- a/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt +++ b/pkg/dart2wasm/test/deferred_loading/partition_tests_dart2js/custom_split/just_fuse.default.txt @@ -39,7 +39,6 @@ Part 0 - pkg/compiler/test/custom_split/data/just_fuse/libImport.dart::@methods::f_111_1 - pkg/compiler/test/custom_split/data/just_fuse/libImport.dart::@methods::v Constants - - IntConstant(127) - IntConstant(20) - IntConstant(256) - IntConstant(36) 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 fcb344a4470..84f5203cfbf 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 @@ -16,7 +16,7 @@ (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 17 funcref) (global $"\"bad\"" (ref $JSExternWrapper) <...>) (global $MyConstClass (ref $MyConstClass) - (i32.const 108) + (i32.const 109) (i32.const 0) (i32.const 60) (i32.const 0) 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 7da3ac8a14b..0c371d831ba 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 @@ -15,7 +15,7 @@ (global $.h0-nonshared-const (import "" "h0-nonshared-const") (ref extern)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 17 funcref) (global $MyConstClass (ref $MyConstClass) - (i32.const 108) + (i32.const 109) (i32.const 0) (i32.const 60) (i32.const 0) 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 1c54c08596b..31caaef9927 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 @@ -14,7 +14,7 @@ (field $field1 (mut i32))))) (global $.shared-const (import "" "shared-const") (ref extern)) (global $MyConstClass (ref $MyConstClass) - (i32.const 108) + (i32.const 109) (i32.const 0) (i32.const 60) (i32.const 0) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat index 87304e7697e..6f979101a1e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat @@ -10,7 +10,7 @@ (global $".Foo called " (import "" "Foo called ") (ref extern)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 4 funcref) (global $"\"Foo called \"" (ref $JSExternWrapper) - (i32.const 58) + (i32.const 59) (i32.const 0) (global.get $".Foo called ") (struct.new $JSExternWrapper)) @@ -27,7 +27,7 @@ ) (func $"useFooAsObject " (local $var0 (ref $Foo)) - i32.const 106 + i32.const 107 i32.const 0 i64.const 0 struct.new $Foo diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index 4f931842ac4..9c2ca81fd74 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -58,7 +58,7 @@ (local $var1 (ref $_FunctionType)) (local $var2 (ref $#Closure-0-1)) (local $var3 (ref $H1)) - i32.const 106 + i32.const 107 i32.const 0 block $label0 (result (ref $#Closure-0-1)) global.get $"InstantiationConstant(globalH1Foo)" @@ -144,7 +144,7 @@ struct.get $H1 $fun local.tee $var0 struct.get $#Closure-0-1 $context - i32.const 86 + i32.const 88 i64.const 1 struct.new $BoxedInt local.get $var0 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index f8fc0efc04e..e9b9009d8c9 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -39,7 +39,7 @@ (local $var0 (ref $_FunctionType)) (local $var1 (ref $#Closure-0-1)) (local $var2 (ref $H0)) - i32.const 107 + i32.const 108 i32.const 0 block $label0 (result (ref $#Closure-0-1)) global.get $"globalH0Foo tear-off" diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat index 51a13f85e41..71e479d1ff1 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat @@ -11,7 +11,7 @@ (global $1 (import "module0" "global2") (ref $BoxedInt)) (global $2 (import "module0" "global3") (ref $BoxedInt)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 18 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 654 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 657 funcref) (global $"\"Foo0.doitDispatch(\"" (ref $JSExternWrapper) (i32.const 60) (i32.const 0) @@ -42,7 +42,7 @@ (func $"foo0 " call $"runtimeTrue implicit getter" if (result (ref $Object)) - i32.const 108 + i32.const 109 i32.const 0 struct.new $Object else @@ -71,7 +71,7 @@ global.get $1 local.get $var0 struct.get $Object $field0 - i32.const 365 + i32.const 399 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) block $label2 (result (ref $Object)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat index 49689cd4414..8a3076f7b5b 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat @@ -22,7 +22,7 @@ (global $"\"foo0Code(\"" (ref $JSExternWrapper) <...>) (global $0 (ref $BoxedInt) <...>) (global $FooConst0 (ref $Object) - (i32.const 108) + (i32.const 109) (i32.const 0) (struct.new $Object)) (global $fooGlobal0 (mut (ref null $#Top)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat index 702a07795ab..32ab0f730d0 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat @@ -8,7 +8,7 @@ (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) (global $"\"foo1Code(\"" (ref $JSExternWrapper) <...>) (global $FooConst1 (ref $Object) - (i32.const 109) + (i32.const 110) (i32.const 0) (struct.new $Object)) (global $fooGlobal1 (mut (ref null $#Top)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat index bdc638c970d..2c8761e2395 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat @@ -22,7 +22,7 @@ (global $FooConst0 (import "module0" "global7") (ref $Object)) (global $fooGlobal0 (import "module0" "global16") (ref null $#Top)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 673 funcref) (global $"\"0\"" (ref $JSExternWrapper) <...>) (global $"\"1\"" (ref $JSExternWrapper) <...>) (global $"\"2\"" (ref $JSExternWrapper) <...>) @@ -65,7 +65,7 @@ (struct.new $JSExternWrapper)) (global $"\"foo5Code(\"" (ref $JSExternWrapper) <...>) (global $FooConst5 (ref $Object) - (i32.const 113) + (i32.const 114) (i32.const 0) (struct.new $Object)) (global $_InterfaceType (ref $_InterfaceType) <...>) @@ -222,7 +222,7 @@ call $"fooGlobal5 implicit getter" local.get $var2 struct.get $Object $field0 - i32.const 378 + i32.const 382 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) ) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat index 3bd1dfd66cd..5a945546f5d 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat @@ -8,7 +8,7 @@ (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) (global $"\"foo2Code(\"" (ref $JSExternWrapper) <...>) (global $FooConst2 (ref $Object) - (i32.const 110) + (i32.const 111) (i32.const 0) (struct.new $Object)) (global $fooGlobal2 (mut (ref null $#Top)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat index 91d84286c92..1c5535f048f 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module7.wat @@ -8,7 +8,7 @@ (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) (global $"\"foo3Code(\"" (ref $JSExternWrapper) <...>) (global $FooConst3 (ref $Object) - (i32.const 111) + (i32.const 112) (i32.const 0) (struct.new $Object)) (global $fooGlobal3 (mut (ref null $#Top)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat index 52ff5f56959..661f8fb5d1c 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module9.wat @@ -8,7 +8,7 @@ (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref) (global $"\"foo4Code(\"" (ref $JSExternWrapper) <...>) (global $FooConst4 (ref $Object) - (i32.const 112) + (i32.const 113) (i32.const 0) (struct.new $Object)) (global $fooGlobal4 (mut (ref null $#Top)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat index 87dcbc86c7d..29b2ce9b1fa 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat @@ -3,6 +3,8 @@ (type $ArgumentError <...>) (type $Array <...>) (type $Array <...>) + (type $Array?> <...>) + (type $Array <...>) (type $Array <...>) (type $JSExternWrapper <...>) (type $Object <...>) @@ -16,6 +18,7 @@ (global $BoxedDouble._cacheKeys (mut (ref $Array)) <...>) (global $BoxedDouble._cacheValues (mut (ref $Array)) <...>) (global $_deletedDataMarker (mut (ref $#Top)) <...>) + (global $global0 (ref $Array?>) <...>) (elem $cross-module-funcs-0 (set 3 (ref.func $"wasm:js-string.length (import)")) (set 4 (ref.func $JSStringImpl._interpolate)) @@ -49,6 +52,12 @@ i32.const 0 struct.new $Object global.set $_deletedDataMarker + global.get $global0 + i32.const 0 + i32.const 0 + i32.const 2 + array.new_data $Array$data0 + array.set $Array?> ) (func $ArgumentError (param $var0 (ref null $#Top)) (param $var1 (ref null $JSExternWrapper)) (result (ref $ArgumentError)) <...>) (func $IntegerDivisionByZeroException (result (ref $Object)) <...>) @@ -58,4 +67,5 @@ (func $JSStringImpl.fromRefUnchecked (param $var0 externref) (result (ref $JSExternWrapper)) <...>) (func $JSStringImpl.substring (param $var0 (ref $JSExternWrapper)) (param $var1 i64) (param $var2 i64) (result (ref $JSExternWrapper)) <...>) (func $_jsBigIntToString (param $var0 i64) (param $var1 i64) (result (ref $JSExternWrapper)) <...>) + (data $data0 <... 2 bytes ...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat b/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat index bfa645d89b8..aee296059d5 100644 --- a/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/dispatch_table_reuse_module1.wat @@ -7,7 +7,7 @@ (type $WasmListBase <...>) (type $_Type <...>) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 12 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 673 funcref) (elem $module0.cross-module-funcs-0 (set 0 (ref.func $"runTest "))) (func $"runTest " @@ -83,19 +83,19 @@ ref.cast $Object local.tee $var2 struct.get $Object $field0 - i32.const 109 + i32.const 110 i32.eq if local.get $var2 local.get $var5 - i32.const 487 + i32.const 492 call_indirect $module0.dispatch0 (param (ref $Object) i64) else local.get $var2 local.get $var5 local.get $var2 struct.get $Object $field0 - i32.const 378 + i32.const 382 i32.add call_indirect $module0.dispatch0 (param (ref $Object) i64) end diff --git a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat index 051bb9de685..a2c22eb15f0 100644 --- a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat @@ -11,7 +11,7 @@ (global $".hello world" (import "" "hello world") (ref extern)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 2 funcref) (global $"\"hello world\"" (ref $JSExternWrapper) - (i32.const 57) + (i32.const 59) (i32.const 0) (global.get $".hello world") (struct.new $JSExternWrapper)) diff --git a/sdk/lib/_internal/wasm/js_common/deferred_patch.dart b/sdk/lib/_internal/wasm/js_common/deferred_patch.dart index 78e5b72d97f..a0041efcc28 100644 --- a/sdk/lib/_internal/wasm/js_common/deferred_patch.dart +++ b/sdk/lib/_internal/wasm/js_common/deferred_patch.dart @@ -22,11 +22,19 @@ final Map> _loading = {}; final Set _loaded = {}; /// Only used when loading modules directly, will get populated by the compiler. -external ImmutableWasmArray> get _loadingMap; +/// +/// Maps a loading id (aka deferred prefix) to the set of module ids that have +/// to be loaded. +external WasmArray?> get _loadingMap; /// Maps load id to (import uri, import prefix). external ImmutableWasmArray get _loadingMapNames; +/// The prefix of all module names. +/// +/// For `test_module.wasm` it will be `test_module`. +external WasmExternRef get _moduleNamePrefix; + @pragma("wasm:import", "moduleLoadingHelper.loadDeferredModules") external WasmExternRef _loadDeferredModules(WasmExternRef moduleNames); @@ -94,8 +102,10 @@ Future loadLibraryFromLoadId(int loadId) { 'Error loading load ID: ${_loadIdInJson(loadId)}\n$e', ); } - return 'Error loading ${_prefixName(loadId)} of library ' - '${_importUri(loadId)}\n$e'; + throw DeferredLoadException( + 'Error loading ${_prefixName(loadId)} of library ' + '${_importUri(loadId)}\n$e', + ); }, ); } @@ -108,18 +118,47 @@ Future _loadLibraryViaEmbedderLoadId(int loadId) { Future _loadLibraryViaEmbedderModuleNames(int loadId) { assert(loadId < _loadingMap.length); - - final ImmutableWasmArray moduleNames = _loadingMap[loadId]; - if (moduleNames.length == 0) { + final WasmArray? encodedModuleIds = _loadingMap[loadId]; + if (encodedModuleIds == null) { // No modules to load. return Future.value(); } - final moduleNamesAsList = []; - for (int i = 0; i < moduleNames.length; ++i) { - moduleNamesAsList.add(JSValue(moduleNames[i]) as JSString); - } + final moduleNamesAsList = _decodeEncodedModuleIds('test', encodedModuleIds); + final promise = (_loadDeferredModules(moduleNamesAsList.toJS.toExternRef!).toJS as JSPromise); return promise.toDart; } + +/// Keep in sync with pkg/dart2wasm/lib/translator.dart:Translator._patchLoadingMapGetter` +List _decodeEncodedModuleIds( + String prefix, + WasmArray encoded, +) { + int offset = 0; + + int nextULEB128() { + int result = 0; + int shift = 0; + while (true) { + final byte = encoded.readUnsigned(offset++); + result |= (byte & 0x7F) << shift; + shift += 7; + if ((byte & 0x80) == 0) break; + } + return result; + } + + final length = nextULEB128(); + final moduleIds = []; + int previousModuleId = 0; + final prefix = JSStringImpl.fromRefUnchecked(_moduleNamePrefix); + for (int i = 0; i < length; ++i) { + int diff = nextULEB128(); + final moduleId = previousModuleId + diff; + moduleIds.add('$prefix${moduleId.toString()}.wasm'.toJS); + previousModuleId = moduleId; + } + return moduleIds; +}