From 4fb43199800fae0accb5ebc19e56aa504831196f Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Tue, 4 Nov 2025 04:58:14 -0800 Subject: [PATCH] [dart2wasm] Group all lazily initialized multi-module use constant globals into a table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces ACX gallery main module by 3.3%. Instead of defining a global for every lazily initialized constant in the main module we define a table per constant type. This shrinks the main module as we pay little overhead per additional lazily initialized global as we save many exported globals. Deferred modules will then use the table slot instead of a global. Change-Id: I3d2de40a3a49d405f6ba8b2f106af6b8bdbdf45c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458861 Commit-Queue: Martin Kustermann Reviewed-by: Ömer Ağacan --- pkg/dart2wasm/lib/constants.dart | 168 +++++++++++++----- .../deferred.constant.multi_module_use.wat | 4 - ...rred.constant.multi_module_use_module1.wat | 16 +- ...rred.constant.multi_module_use_module3.wat | 16 +- .../ir_tests/deferred.constant_module1.wat | 10 +- .../ir_tests/deferred.constant_module2.wat | 8 +- 6 files changed, 158 insertions(+), 64 deletions(-) diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index f3b898ab8c6..fed4b731e64 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -25,24 +25,47 @@ const int maxArrayNewFixedLength = 10000; /// the main module). const bool forceDelayedConstantDefinition = false; -class ConstantDefinition { +/// Describes where the constant slot is defined and how to access it. +sealed class ConstantDefinition { + bool get isLazy; + w.BaseFunction initializer(w.ModuleBuilder usingModule); +} + +/// The value for the constant is stored in a global slot. +/// +/// We use this mechanism if the constant is accessed only within a given +/// module. +final class GlobalBasedConstantDefinition extends ConstantDefinition { final w.Global global; final w.BaseFunction? _initFunction; - final Map? _initFunctionPerUsingModule; - ConstantDefinition( - this.global, this._initFunction, this._initFunctionPerUsingModule) { - assert(_initFunction == null || _initFunctionPerUsingModule == null); - } + GlobalBasedConstantDefinition(this.global, this._initFunction); - bool get isLazy => - _initFunction != null || _initFunctionPerUsingModule != null; + @override + bool get isLazy => _initFunction != null; + @override + w.BaseFunction initializer(w.ModuleBuilder usingModule) => _initFunction!; +} + +/// The value for the (lazy) constant is stored in a table slot. +/// +/// We use this mechanism if the constant is accessed across modules and each +/// module will bring it's own copy of the initializer function. +final class TableBasedConstantDefinition extends ConstantDefinition { + final w.Table table; + final int tableIndex; + final Map _initFunctionPerUsingModule; + + TableBasedConstantDefinition( + this.table, this.tableIndex, this._initFunctionPerUsingModule); + + @override + bool get isLazy => true; + + @override w.BaseFunction initializer(w.ModuleBuilder usingModule) { - if (_initFunctionPerUsingModule != null) { - return _initFunctionPerUsingModule[usingModule]!; - } - return _initFunction!; + return _initFunctionPerUsingModule[usingModule]!; } } @@ -95,11 +118,21 @@ class ConstantInfo { void Function(w.Global) printLazyInitializer) { final definition = _definition; if (definition != null) { - final initFunction = definition._initFunction; - if (initFunction != null) { - printFunction(initFunction); - } else { - printLazyInitializer(definition.global); + switch (definition) { + case GlobalBasedConstantDefinition(): + final initFunction = definition._initFunction; + if (initFunction != null) { + printFunction(initFunction); + } else { + printLazyInitializer(definition.global); + } + break; + case TableBasedConstantDefinition(): + for (final initFunction + in definition._initFunctionPerUsingModule.values) { + printFunction(initFunction); + } + break; } } } @@ -264,7 +297,7 @@ class Constants { _constantAccessor._defineConstantInModuleRecursive(baseModule, info); } - if (definition != null && !definition.isLazy) { + if (definition is GlobalBasedConstantDefinition && !definition.isLazy) { final definingModule = definition.global.enclosingModule; if (definingModule == usingModule.module) return true; if (definingModule == baseModule.module) return true; @@ -1484,6 +1517,12 @@ class _ConstantAccessor { /// transitively refers to. final Map> moduleUses = {}; + /// We maintain a table for lazily initialized constants that are used across + /// modules. This avoids having many invidiual globals of the same type with + /// null initializer. + final Map lazySlotTables = {}; + late final tableImporter = WasmTableImporter(translator, 'constant-table'); + _ConstantAccessor(this.translator); /// Reads a constant. @@ -1571,25 +1610,36 @@ class _ConstantAccessor { w.ValueType _readDefinedConstant(w.InstructionsBuilder b, ConstantInfo info, ConstantDefinition definition) { - final globalDefinition = definition.global; - final globalInitializer = - definition.isLazy ? definition.initializer(b.moduleBuilder) : null; - // Eagerly initialized constant. - if (globalInitializer == null) { - translator.globals.readGlobal(b, globalDefinition); - return globalDefinition.type.type; + if (definition is GlobalBasedConstantDefinition && !definition.isLazy) { + translator.globals.readGlobal(b, definition.global); + return definition.global.type.type; } // Lazily initialized constant. - w.ValueType type = globalDefinition.type.type.withNullability(false); - w.Label done = b.block(const [], [type]); - translator.globals.readGlobal(b, globalDefinition); - b.br_on_non_null(done); + assert(definition.isLazy); - translator.callFunction(globalInitializer, b); - b.end(); - return type; + switch (definition) { + case GlobalBasedConstantDefinition(): + // Use global & lazy initializer function. + w.Label done = b.block(const [], [info.type]); + translator.globals.readGlobal(b, definition.global); + b.br_on_non_null(done); + translator.callFunction(definition.initializer(b.moduleBuilder), b); + b.end(); + break; + case TableBasedConstantDefinition(): + // Use table & lazy initializer function. + w.Label done = b.block(const [], [info.type]); + b.i32_const(definition.tableIndex); + b.table_get(tableImporter.get(definition.table, b.moduleBuilder)); + b.br_on_non_null(done); + translator.callFunction(definition.initializer(b.moduleBuilder), b); + b.end(); + break; + } + + return info.type; } /// If [assignedModule] is not null assigns all undefined constants to that @@ -1658,6 +1708,10 @@ class _ConstantAccessor { break; } + // The child isn't lazy, so it cannot be a table-based constant + // definition. + definition as GlobalBasedConstantDefinition; + // If we place the constant in a module that may be loaded before the // constants of children, it must get initialized lazily. final childModule = definition.global.enclosingModule; @@ -1685,27 +1739,35 @@ class _ConstantAccessor { final ConstantDefinition definition; if (lazy) { if (targetModule == null) { + final w.TableBuilder table = lazySlotTables.putIfAbsent(info.type, () { + return translator.mainModule.tables + .define(info.type.withNullability(true), 0); + }); + final tableIndex = table.minSize++; final name = _constantName(info.constant); - final global = _createLazyGlobal(translator.mainModule, name, info); final initFunctions = { for (final usingModule in deferredUses!) - usingModule: - _createLazyInitializer(usingModule, global, name, info), + usingModule: _createLazyTableInitializer( + usingModule, table, tableIndex, name, info), }; - definition = ConstantDefinition(global, null, initFunctions); + definition = + TableBasedConstantDefinition(table, tableIndex, initFunctions); } else { final (global, initFunction) = _createLazyConstant(targetModule, info); - definition = ConstantDefinition(global, initFunction, null); + definition = GlobalBasedConstantDefinition(global, initFunction); } } else { final global = _createNonLazyConstant(targetModule!, info); - definition = ConstantDefinition(global, null, null); + definition = GlobalBasedConstantDefinition(global, null); } info.setDefinition(definition); if (info.exportByMainApp) { assert(translator.dynamicModuleSupportEnabled && !translator.isDynamicSubmodule); + // Current dynamic module implementation requires main module to be + // monolitic. + definition as GlobalBasedConstantDefinition; translator.exporter.exportDynamicConstant( targetModule!, constant, definition.global, initializer: definition._initFunction); @@ -1739,7 +1801,8 @@ class _ConstantAccessor { globalName, fakeInitializer); } - info._definition = ConstantDefinition(fakeGlobal, fakeInitializer, null); + info._definition = + GlobalBasedConstantDefinition(fakeGlobal, fakeInitializer); } (w.GlobalBuilder, w.FunctionBuilder) _createLazyConstant( @@ -1748,7 +1811,7 @@ class _ConstantAccessor { final definedGlobal = _createLazyGlobal(targetModule, name, info); final initFunction = - _createLazyInitializer(targetModule, definedGlobal, name, info); + _createLazyGlobalInitializer(targetModule, definedGlobal, name, info); return (definedGlobal, initFunction); } @@ -1762,7 +1825,7 @@ class _ConstantAccessor { return definedGlobal; } - w.FunctionBuilder _createLazyInitializer(w.ModuleBuilder module, + w.FunctionBuilder _createLazyGlobalInitializer(w.ModuleBuilder module, w.GlobalBuilder definedGlobal, String name, ConstantInfo info) { final type = info.type; final initFunctionType = @@ -1784,6 +1847,29 @@ class _ConstantAccessor { return initFunction; } + w.FunctionBuilder _createLazyTableInitializer(w.ModuleBuilder module, + w.TableBuilder table, int tableIndex, String name, ConstantInfo info) { + final type = info.type; + final initFunctionType = + translator.typesBuilder.defineFunction(const [], [type]); + final initFunction = + module.functions.define(initFunctionType, '$name (lazy initializer)}'); + final b = initFunction.body; + b.i32_const(tableIndex); + info._codeGen(info, b, true); + if (info.needsRuntimeCanonicalization) { + final valueLocal = b.addLocal(type); + info.constant.accept(ConstantCanonicalizer(translator, b, valueLocal)); + } + w.Local temp = b.addLocal(type); + b.local_tee(temp); + b.table_set(tableImporter.get(table, module)); + b.local_get(temp); + b.end(); + + return initFunction; + } + w.GlobalBuilder _createNonLazyConstant( w.ModuleBuilder targetModule, ConstantInfo info) { final constants = translator.constants; 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 36006f27221..5afbaf4c356 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 @@ -16,10 +16,6 @@ (param $var0 i32) (result (ref $MyConstClass)))) (global $"C370 \"bad\"" (ref $JSStringImpl) <...>) - (global $"C489 \"shared-const\"" (mut (ref null $JSStringImpl)) - (ref.null none)) - (global $"C490 MyConstClass" (mut (ref null $MyConstClass)) - (ref.null none)) (table $static0-0 (export "static0-0") 2 (ref null $type1)) (func $Error._throwWithCurrentStackTrace (param $var0 (ref $#Top)) <...>) (func $"mainImpl " (param $var0 i32) 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 a1102cf8bda..4a7e2f63f1a 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 @@ -14,8 +14,8 @@ (field $b (ref $JSStringImpl))))) (global $S.h1-nonshared-const (import "S" "h1-nonshared-const") (ref extern)) (global $S.shared-const (import "S" "shared-const") (ref extern)) - (global $"C489 \"shared-const\"" (import "module0" "global0") (ref null $JSStringImpl)) - (global $"C490 MyConstClass" (import "module0" "global1") (ref null $MyConstClass)) + (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 $"C488 MyConstClass" (ref $MyConstClass) (i32.const 107) (i32.const 0) @@ -32,24 +32,28 @@ global.get $"C488 MyConstClass" else block $label0 (result (ref $MyConstClass)) - global.get $"C490 MyConstClass" + i32.const 0 + table.get $module0.constant-table1 br_on_non_null $label0 + i32.const 0 i32.const 107 i32.const 0 block $label1 (result (ref $JSStringImpl)) - global.get $"C489 \"shared-const\"" + i32.const 0 + table.get $module0.constant-table0 br_on_non_null $label1 + i32.const 0 i32.const 4 i32.const 0 global.get $S.shared-const struct.new $JSStringImpl local.tee $var1 - global.set $"C489 \"shared-const\"" + table.set $module0.constant-table0 local.get $var1 end $label1 struct.new $MyConstClass local.tee $var2 - global.set $"C490 MyConstClass" + table.set $module0.constant-table1 local.get $var2 end $label0 end 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 7b5e2ccbdf9..396d683144f 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 @@ -13,9 +13,9 @@ (field $field1 (mut i32)) (field $b (ref $JSStringImpl))))) (global $S.shared-const (import "S" "shared-const") (ref extern)) - (global $"C489 \"shared-const\"" (import "module0" "global0") (ref null $JSStringImpl)) - (global $"C490 MyConstClass" (import "module0" "global1") (ref null $MyConstClass)) (global $S.h0-nonshared-const (import "S" "h0-nonshared-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 $"C492 MyConstClass" (ref $MyConstClass) (i32.const 107) (i32.const 0) @@ -32,24 +32,28 @@ global.get $"C492 MyConstClass" else block $label0 (result (ref $MyConstClass)) - global.get $"C490 MyConstClass" + i32.const 0 + table.get $module0.constant-table1 br_on_non_null $label0 + i32.const 0 i32.const 107 i32.const 0 block $label1 (result (ref $JSStringImpl)) - global.get $"C489 \"shared-const\"" + i32.const 0 + table.get $module0.constant-table0 br_on_non_null $label1 + i32.const 0 i32.const 4 i32.const 0 global.get $S.shared-const struct.new $JSStringImpl local.tee $var1 - global.set $"C489 \"shared-const\"" + table.set $module0.constant-table0 local.get $var1 end $label1 struct.new $MyConstClass local.tee $var2 - global.set $"C490 MyConstClass" + table.set $module0.constant-table1 local.get $var2 end $label0 end diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index 07affdcc11f..6675b56fd7f 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -29,9 +29,9 @@ (global $"C331 _TopType" (import "module0" "global2") (ref $_TopType)) (global $"C62 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) (global $"C306 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) - (global $"C455 _FunctionType" (import "module0" "global5") (ref null $_FunctionType)) - (global $"C28 _InterfaceType" (import "module0" "global8") (ref $_InterfaceType)) + (global $"C28 _InterfaceType" (import "module0" "global7") (ref $_InterfaceType)) (global $S.globalH1Bar< (import "S" "globalH1Bar<") (ref extern)) + (table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType) <...>) (global $global7 (ref $#Vtable-1-1) <...>) (global $global4 (ref $#DummyStruct) <...>) (global $"C459 _FunctionType" (ref $_FunctionType) <...>) @@ -133,8 +133,10 @@ ref.func $"instantiation constant trampoline" struct.new $#Vtable-0-1 block $label2 (result (ref $_FunctionType)) - global.get $"C455 _FunctionType" + i32.const 0 + table.get $module0.constant-table0 br_on_non_null $label2 + i32.const 0 i32.const 12 i32.const 0 i32.const 0 @@ -147,7 +149,7 @@ global.get $"C306 WasmArray<_NamedParameter>[0]" struct.new $_FunctionType local.tee $var1 - global.set $"C455 _FunctionType" + table.set $module0.constant-table0 local.get $var1 end $label2 struct.new $#Closure-0-1 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index 0743efaac66..cc2df20601b 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -20,8 +20,8 @@ (global $"C331 _TopType" (import "module0" "global2") (ref $_TopType)) (global $"C62 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) (global $"C306 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) - (global $"C455 _FunctionType" (import "module0" "global5") (ref null $_FunctionType)) (global $S.globalH0Foo (import "S" "globalH0Foo") (ref extern)) + (table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType) <...>) (global $global6 (ref $#Vtable-0-1) <...>) (global $global3 (ref $#DummyStruct) <...>) (global $"C465 globalH0Foo tear-off" (mut (ref null $#Closure-0-1)) @@ -59,8 +59,10 @@ global.get $global3 global.get $global6 block $label1 (result (ref $_FunctionType)) - global.get $"C455 _FunctionType" + i32.const 0 + table.get $module0.constant-table0 br_on_non_null $label1 + i32.const 0 i32.const 12 i32.const 0 i32.const 0 @@ -73,7 +75,7 @@ global.get $"C306 WasmArray<_NamedParameter>[0]" struct.new $_FunctionType local.tee $var0 - global.set $"C455 _FunctionType" + table.set $module0.constant-table0 local.get $var0 end $label1 struct.new $#Closure-0-1