[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<WasmI8>`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 <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2026-06-05 00:50:46 -07:00
parent 9d41f28545
commit e3cf529f87
29 changed files with 232 additions and 91 deletions
+2 -1
View File
@@ -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<CompilationResult> _runCodegenPhase(
final wasmOutputFilename = path.basename(options.outputFile);
final moduleIds = modules.keys
.map<int>(
(moduleMetadata) => options.idForModuleName(
(moduleMetadata) => WasmCompilerOptions.idForModuleName(
wasmOutputFilename,
moduleMetadata.moduleName,
)!,
+31 -10
View File
@@ -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 `<id>` for `test_module<id>.wasm`).
static int idFromDeferredModuleFilename(String moduleName) {
// The name has pattern: "..._module<moduleId>.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<id>.wasm`).
static String deferredModuleFilenamePrefix(String mainModuleFilename) =>
path.basename(path.setExtension(mainModuleFilename, '_module'));
static int _defaultMaxActiveWasmOptProcesses() {
try {
return Platform.numberOfProcessors;
+6 -3
View File
@@ -135,9 +135,12 @@ class CompilerPhaseInputOutputManager {
int moduleId,
List<String> 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 = <int>{};
for (final file in files) {
if (file is! File) continue;
final moduleId = options.idForModuleName(
final moduleId = WasmCompilerOptions.idForModuleName(
mainWasmFilename,
path.basename(file.path),
);
+6
View File
@@ -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',
+5 -1
View File
@@ -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,
);
+99 -36
View File
@@ -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<WasmI8>',
);
_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 = <int>[];
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);
}
}
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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 <noInline>"
(local $var0 (ref $Foo))
i32.const 106
i32.const 107
i32.const 0
i64.const 0
struct.new $Foo
@@ -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<int>)"
@@ -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
@@ -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"
@@ -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 <noInline>"
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))
@@ -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))
@@ -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))
@@ -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))
)
@@ -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))
@@ -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))
@@ -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))
@@ -3,6 +3,8 @@
(type $ArgumentError <...>)
(type $Array<Object?> <...>)
(type $Array<String> <...>)
(type $Array<WasmArray<WasmI8>?> <...>)
(type $Array<WasmI8> <...>)
(type $Array<int> <...>)
(type $JSExternWrapper <...>)
(type $Object <...>)
@@ -16,6 +18,7 @@
(global $BoxedDouble._cacheKeys (mut (ref $Array<int>)) <...>)
(global $BoxedDouble._cacheValues (mut (ref $Array<String>)) <...>)
(global $_deletedDataMarker (mut (ref $#Top)) <...>)
(global $global0 (ref $Array<WasmArray<WasmI8>?>) <...>)
(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<WasmI8>$data0
array.set $Array<WasmArray<WasmI8>?>
)
(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 ...>)
)
@@ -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 <noInline>")))
(func $"runTest <noInline>"
@@ -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
@@ -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))
@@ -22,11 +22,19 @@ final Map<int, Future<void>> _loading = {};
final Set<int> _loaded = {};
/// Only used when loading modules directly, will get populated by the compiler.
external ImmutableWasmArray<ImmutableWasmArray<WasmExternRef>> get _loadingMap;
///
/// Maps a loading id (aka deferred prefix) to the set of module ids that have
/// to be loaded.
external WasmArray<WasmArray<WasmI8>?> get _loadingMap;
/// Maps load id to (import uri, import prefix).
external ImmutableWasmArray<WasmExternRef> get _loadingMapNames;
/// The prefix of all module names.
///
/// For `test_module<id>.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<void> 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<void> _loadLibraryViaEmbedderLoadId(int loadId) {
Future<void> _loadLibraryViaEmbedderModuleNames(int loadId) {
assert(loadId < _loadingMap.length);
final ImmutableWasmArray<WasmExternRef> moduleNames = _loadingMap[loadId];
if (moduleNames.length == 0) {
final WasmArray<WasmI8>? encodedModuleIds = _loadingMap[loadId];
if (encodedModuleIds == null) {
// No modules to load.
return Future.value();
}
final moduleNamesAsList = <JSString>[];
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<JSString> _decodeEncodedModuleIds(
String prefix,
WasmArray<WasmI8> 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 = <JSString>[];
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;
}