[dart2wasm] Group all lazily initialized multi-module use constant globals into a table

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 <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Martin Kustermann
2025-11-04 04:58:14 -08:00
committed by Commit Queue
parent 4bc9d31957
commit 4fb4319980
6 changed files with 158 additions and 64 deletions
+127 -41
View File
@@ -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<w.ModuleBuilder, w.BaseFunction>? _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<w.ModuleBuilder, w.BaseFunction> _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<ConstantInfo, Set<w.ModuleBuilder>> 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<w.RefType, w.TableBuilder> 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;
@@ -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 <noInline>" (param $var0 i32)
@@ -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
@@ -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
@@ -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
@@ -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