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