From ea6fb0a16f26beb3f73b13019eea1feceb6848ee Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 30 Jan 2026 03:34:42 -0800 Subject: [PATCH] [dart2wasm] Allow using table slots for storing values of dart globals This seems to result in -0.6% compressed main module and a bit less in uncompressed mode. Sometimes we have many fields with lazy initializers of the same type. That led us to emit 1 nullable wasm global for each such field. For example all proto classes have a `static BuilderInfo i_` field. This has led to thousands of `(mut (ref null $BuilderInfo))` wasm globals. Now we use a wasm table for this, which is a O(1) in the binary as they all get `null` by default, saving us all these globals. The downside is that accesses have an extra instruction now, but overall this is a win. The CL also cleans up `globals.dart` by separating the concept of a wasm global and reading/writing to it from the concept of a Dart global - as Dart globals can now be backed by wasm globals or table slots. This CL uses the new capability made possible by the refactoring in [0] - namely to emit element sections which initialize wasm table slots with non-function expressions. [0] https://dart-review.googlesource.com/c/sdk/+/459440 Change-Id: Ie57206dff8c0a57a1df5e48c0808167f822bc4a2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475800 Reviewed-by: Nate Biggs Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 47 +- pkg/dart2wasm/lib/globals.dart | 260 ++++++++--- pkg/dart2wasm/lib/table_based_globals.dart | 99 +++++ pkg/dart2wasm/lib/translator.dart | 8 +- .../deferred.fine_grained_module1.wat | 4 +- pkg/dart2wasm/test/ir_tests/globals.dart | 120 +++++ pkg/dart2wasm/test/ir_tests/globals.wat | 418 ++++++++++++++++++ pkg/dart2wasm/test/ir_tests/interop.bool.wat | 8 +- .../test/ir_tests/interop.double.wat | 8 +- pkg/dart2wasm/test/ir_tests/interop.int.wat | 8 +- pkg/dart2wasm/test/ir_tests/interop.num.wat | 8 +- .../test/ir_tests/interop.string.wat | 13 +- pkg/dart2wasm/tool/compile_benchmark | 2 +- .../lib/src/builder/elements.dart | 53 ++- 14 files changed, 949 insertions(+), 107 deletions(-) create mode 100644 pkg/dart2wasm/lib/table_based_globals.dart create mode 100644 pkg/dart2wasm/test/ir_tests/globals.dart create mode 100644 pkg/dart2wasm/test/ir_tests/globals.wat diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index e96f354d8ac..f44e8ceafc4 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -13,6 +13,7 @@ import 'class_info.dart'; import 'closures.dart'; import 'dispatch_table.dart'; import 'dynamic_forwarders.dart'; +import 'globals.dart'; import 'intrinsics.dart'; import 'param_info.dart'; import 'records.dart'; @@ -4050,16 +4051,21 @@ class StaticFieldInitializerCodeGenerator extends AstCodeGenerator { // Static field initializer function closures = translator.getClosures(field); - w.Global global = translator.globals.getGlobalForStaticField(field); - w.Global? flag = translator.globals.getGlobalInitializedFlag(field); - translateExpression(field.initializer!, global.type.type); - translator.globals.writeGlobal(b, global); + final globalDefinition = + translator.dartGlobals.getDefinitionForStaticField(field); + final flag = globalDefinition.initializedFlag; + + final local = b.addLocal(globalDefinition.type); + globalDefinition.write(translator, b, (b) { + translateExpression(field.initializer!, local.type); + b.local_tee(local); + }); + b.local_get(local); + translator.convertType(b, local.type, outputs.single); if (flag != null) { b.i32_const(1); translator.globals.writeGlobal(b, flag); } - translator.globals.readGlobal(b, global); - translator.convertType(b, global.type.type, outputs.single); b.end(); } } @@ -4094,36 +4100,39 @@ class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator { @override void generateInternal() { - final global = translator.globals.getGlobalForStaticField(field); - final flag = translator.globals.getGlobalInitializedFlag(field); + final globalDefinition = + translator.dartGlobals.getDefinitionForStaticField(field); if (isImplicitGetter) { final initFunction = translator.functions.getExistingFunction(field.fieldReference); - _generateGetter(global, flag, initFunction); + _generateGetter(globalDefinition, initFunction); } else { - _generateSetter(global, flag); + _generateSetter(globalDefinition); } b.end(); } void _generateGetter( - w.Global global, w.Global? flag, w.BaseFunction? initFunction) { + DartGlobalDefinition definition, w.BaseFunction? initFunction) { + final flag = definition.initializedFlag; + if (initFunction == null) { // Statically initialized - translator.globals.readGlobal(b, global); + definition.read(translator, b); + // b.ref_cast(functionType.outputs.single as w.RefType); } else { if (flag != null) { // Explicit initialization flag translator.globals.readGlobal(b, flag); - b.if_(const [], [global.type.type]); - translator.globals.readGlobal(b, global); + b.if_(const [], [definition.type]); + definition.read(translator, b); b.else_(); translator.callFunction(initFunction, b); b.end(); } else { // Null signals uninitialized w.Label block = b.block(const [], [initFunction.type.outputs.single]); - translator.globals.readGlobal(b, global); + definition.read(translator, b); b.br_on_non_null(block); translator.callFunction(initFunction, b); b.end(); @@ -4131,9 +4140,11 @@ class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator { } } - void _generateSetter(w.Global global, w.Global? flag) { - b.local_get(paramLocals.single); - translator.globals.writeGlobal(b, global); + void _generateSetter(DartGlobalDefinition definition) { + definition.write(translator, b, (b) { + b.local_get(paramLocals.single); + }); + final flag = definition.initializedFlag; if (flag != null) { b.i32_const(1); // true translator.globals.writeGlobal(b, flag); diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index 9538d261090..2b4638764d8 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart @@ -7,39 +7,29 @@ import 'package:kernel/ast.dart'; import 'package:wasm_builder/wasm_builder.dart' as w; import 'code_generator.dart' show EagerStaticFieldInitializerCodeGenerator; +import 'table_based_globals.dart'; import 'translator.dart'; import 'util.dart' as util; +/// If we have more than this number of fields of the same type, we prefer using +/// a wasm table to hold field values over globals. +const dartFieldTableUseCutoff = 10; + /// Handles lazy initialization of static fields. class Globals { final Translator translator; - /// Maps a static field to its global holding the field value. - final Map _globals = {}; - /// When a global is read from a module other than the module defining it, /// this maps the global to the getter function defined and exported in /// the defining module. final Map _globalGetters = {}; final Map _globalSetters = {}; - final Map _globalInitializedFlag = {}; final WasmGlobalImporter _globalsModuleMap; Globals(this.translator) : _globalsModuleMap = WasmGlobalImporter(translator, 'global'); - Constant? getConstantInitializer(Field variable) { - Expression? init = variable.initializer; - if (init == null || init is NullLiteral) return NullConstant(); - if (init is IntLiteral) return IntConstant(init.value); - if (init is DoubleLiteral) return DoubleConstant(init.value); - if (init is BoolLiteral) return BoolConstant(init.value); - if (init is StringLiteral) return StringConstant(init.value); - if (init is ConstantExpression) return init.constant; - return null; - } - void declareMainAppGlobalExportWithName(String name, w.Global exportable) { _globalsModuleMap.exportDefinitionWithName(name, exportable); } @@ -96,29 +86,69 @@ class Globals { translator.callFunction(setter, b); } } +} + +class DartGlobals { + final Translator translator; + final Map _fieldTypeCount = {}; + + final Map _definitions = {}; + + DartGlobals(this.translator) { + for (final library in translator.component.libraries) { + for (final field in library.fields) { + final wasmType = translator.translateTypeOfField(field); + _fieldTypeCount[wasmType] = (_fieldTypeCount[wasmType] ?? 0) + 1; + } + for (final klass in library.classes) { + for (final field in klass.fields) { + if (field.isInstanceMember) continue; + final wasmType = translator.translateTypeOfField(field); + _fieldTypeCount[wasmType] = (_fieldTypeCount[wasmType] ?? 0) + 1; + } + } + } + } + + Constant? getConstantInitializer(Field variable) { + Expression? init = variable.initializer; + if (init == null || init is NullLiteral) return NullConstant(); + if (init is IntLiteral) return IntConstant(init.value); + if (init is DoubleLiteral) return DoubleConstant(init.value); + if (init is BoolLiteral) return BoolConstant(init.value); + if (init is StringLiteral) return StringConstant(init.value); + if (init is ConstantExpression) return init.constant; + return null; + } /// Return (and if needed create) the Wasm global corresponding to a static /// field. - w.Global getGlobalForStaticField(Field field) { + DartGlobalDefinition getDefinitionForStaticField(Field field) { assert(!field.isLate); - return _globals.putIfAbsent(field, () { - w.ValueType fieldType = translator.translateTypeOfField(field); - final module = translator.moduleForReference(field.fieldReference); - final memberName = field.toString(); + return _definitions.putIfAbsent(field, () { + final fieldType = translator.translateTypeOfField(field); + final numberOfFieldsWithSameType = _fieldTypeCount[fieldType]!; + final useTableSlot = + numberOfFieldsWithSameType >= dartFieldTableUseCutoff && + fieldType is w.RefType; - // Maybe we can emit the initialization in the globals section. If so, - // then that's preferred as we can make the global as non-mutable. + final module = translator.moduleForReference(field.fieldReference); + + // If the initializer expression is a constant expression then the field + // doesn't have to become lazy. + // + // If the type is non-nullable we prefer to use a global as using a table + // can cause null checks on usages. Oterhwise we use [useTableSlot] + // heuristic to determine whether to use a table or not. final Constant? init = getConstantInitializer(field); if (init != null && translator.constants .tryInstantiateEagerlyFrom(module, init, fieldType)) { - // Initialized to a constant - final global = module.globals.define( - w.GlobalType(fieldType, mutable: !field.isFinal), memberName); - translator.constants - .instantiateConstant(global.initializer, init, fieldType); - global.initializer.end(); - return global; + if (useTableSlot && fieldType.nullable) { + return _defineTableBasedField(field, fieldType, module, init, null); + } + return _defineGlobalBasedField( + field, fieldType, module, !field.isFinal, init, null); } // Maybe we can emit the initialization in the start function. If so, @@ -126,60 +156,100 @@ class Globals { // access. final initializer = field.initializer; if (initializer != null && _initializeAtStartup(field)) { - final dummyCollector = - translator.getDummyValuesCollectorForModule(module); - final global = - module.globals.define(w.GlobalType(fieldType), memberName); - dummyCollector.instantiateDummyValue(global.initializer, fieldType); - global.initializer.end(); + final definition = + _defineGlobalBasedField(field, fieldType, module, true, init, null); if (module.module == translator.initFunction.enclosingModule) { // We have to initialize the global field in the same module as where // the field value is defined in. // TODO: Once dynamic modules only compile code for the submodule and // not the main module, we should turn this into an assert. - EagerStaticFieldInitializerCodeGenerator(translator, field, global) + EagerStaticFieldInitializerCodeGenerator( + translator, field, definition.global) .generate(translator.initFunction.body, [], null); } - return global; + return definition; } - // We will have to initialize the global lazily, meaning each access will - // check if it's initialized and if not, cause initialization. - final w.ValueType globalType; - if (fieldType is w.RefType && !fieldType.nullable) { - // Null signals uninitialized - globalType = fieldType.withNullability(true); - } else { - // Explicit initialization flag - globalType = fieldType; - final flag = module.globals - .define(w.GlobalType(w.NumType.i32), "$memberName initialized"); - flag.initializer.i32_const(0); - flag.initializer.end(); - _globalInitializedFlag[field] = flag; - } - - final global = - module.globals.define(w.GlobalType(globalType), memberName); - translator - .getDummyValuesCollectorForModule(module) - .instantiateDummyValue(global.initializer, globalType); - global.initializer.end(); - // Add initializer function to the compilation queue. translator.functions.getFunction(field.fieldReference); - return global; + + // We will have to initialize the global lazily, meaning each access will + // check if it's initialized and if not, cause initialization. + final w.ValueType newFieldType; + final w.GlobalBuilder? initializerFlagGlobal; + if (fieldType is w.RefType && !fieldType.nullable) { + // Null signals uninitialized + newFieldType = fieldType.withNullability(true); + initializerFlagGlobal = null; + } else { + // Explicit initialization flag + newFieldType = fieldType; + initializerFlagGlobal = _defineInitializerFlag(field, module); + } + + if (useTableSlot && newFieldType.nullable) { + return _defineTableBasedField(field, newFieldType as w.RefType, module, + null, initializerFlagGlobal); + } + return _defineGlobalBasedField( + field, newFieldType, module, true, null, initializerFlagGlobal); }); } - /// Return the Wasm global containing the flag indicating whether this static - /// field has been initialized, if such a flag global is needed. - /// - /// Note that [getGlobalForStaticField] must have been called for the field beforehand. - w.Global? getGlobalInitializedFlag(Field variable) => - _globalInitializedFlag[variable]; + w.GlobalBuilder _defineInitializerFlag(Field field, w.ModuleBuilder module) { + final memberName = _memberName(field); + final global = module.globals + .define(w.GlobalType(w.NumType.i32), "$memberName initialized"); + global.initializer.i32_const(0); + global.initializer.end(); + return global; + } + + TableBasedDartGlobal _defineTableBasedField( + Field field, + w.RefType fieldType, + w.ModuleBuilder module, + Constant? init, + w.GlobalBuilder? initializerFlag) { + final table = + translator.tableBasedGlobals.getTableForType(fieldType.heapType); + if (init != null && init is! NullConstant) { + return TableBasedDartGlobal( + table, + table.indexForObject(field, module, (ib) { + translator.constants.instantiateConstant(ib, init, fieldType); + ib.end(); + })); + } + return TableBasedDartGlobal(table, table.indexForObject(field), + initializedFlag: initializerFlag); + } + + WasmGlobalDartGlobal _defineGlobalBasedField( + Field field, + w.ValueType fieldType, + w.ModuleBuilder module, + bool mutable, + Constant? init, + w.GlobalBuilder? initializerFlag) { + final memberName = _memberName(field); + final global = module.globals + .define(w.GlobalType(fieldType, mutable: mutable), memberName); + if (init != null) { + translator.constants + .instantiateConstant(global.initializer, init, fieldType); + } else { + final dummyCollector = + translator.getDummyValuesCollectorForModule(module); + dummyCollector.instantiateDummyValue(global.initializer, fieldType); + } + global.initializer.end(); + return WasmGlobalDartGlobal(global, initializedFlag: initializerFlag); + } + + String _memberName(Field field) => field.toString(); bool _initializeAtStartup(Annotatable node) => util.getPragma( @@ -187,3 +257,59 @@ class Globals { defaultValue: true) ?? false; } + +sealed class DartGlobalDefinition { + final w.Global? initializedFlag; + DartGlobalDefinition({this.initializedFlag}); + + w.ValueType get type; + w.ValueType read(Translator translator, w.InstructionsBuilder b); + void write(Translator translator, w.InstructionsBuilder b, + void Function(w.InstructionsBuilder) pushValue); +} + +final class WasmGlobalDartGlobal extends DartGlobalDefinition { + final w.Global global; + WasmGlobalDartGlobal(this.global, {super.initializedFlag}); + + @override + w.ValueType get type => global.type.type; + + @override + w.ValueType read(Translator translator, w.InstructionsBuilder b) { + return translator.globals.readGlobal(b, global); + } + + @override + void write(Translator translator, w.InstructionsBuilder b, + void Function(w.InstructionsBuilder) pushValue) { + pushValue(b); + translator.globals.writeGlobal(b, global); + } +} + +final class TableBasedDartGlobal extends DartGlobalDefinition { + final TypeSpecificGlobalTable table; + final int index; + + TableBasedDartGlobal(this.table, this.index, {super.initializedFlag}); + + @override + w.ValueType get type => table.type; + + @override + w.RefType read(Translator translator, w.InstructionsBuilder b) { + final wasmTable = table.getWasmTable(b.moduleBuilder); + b.i32_const(index); + b.table_get(wasmTable); + return wasmTable.type; + } + + @override + void write(Translator translator, w.InstructionsBuilder b, + void Function(w.InstructionsBuilder) pushValue) { + b.i32_const(index); + pushValue(b); + b.table_set(table.getWasmTable(b.moduleBuilder)); + } +} diff --git a/pkg/dart2wasm/lib/table_based_globals.dart b/pkg/dart2wasm/lib/table_based_globals.dart new file mode 100644 index 00000000000..13e8ad051a5 --- /dev/null +++ b/pkg/dart2wasm/lib/table_based_globals.dart @@ -0,0 +1,99 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:wasm_builder/wasm_builder.dart' as w; + +import 'translator.dart'; + +class TableBasedGlobals { + final Translator translator; + + final Map _tables = {}; + + TableBasedGlobals(this.translator); + + TypeSpecificGlobalTable getTableForType(w.HeapType type) { + return _tables[type] ??= TypeSpecificGlobalTable( + translator, type, 'global-table-${_tables.length}'); + } + + void outputTables() { + for (final table in _tables.values) { + table.output(); + } + } +} + +class TypeSpecificGlobalTable { + final Translator translator; + final w.HeapType _tableHeapType; + + /// Contents of wasm table. + final Map _table = {}; + + late final w.TableBuilder _definedWasmTable = translator.mainModule.tables + .define(w.RefType(_tableHeapType, nullable: true), _table.length); + final WasmTableImporter _importedWasmTables; + + TypeSpecificGlobalTable( + this.translator, this._tableHeapType, String tableName) + : _importedWasmTables = WasmTableImporter(translator, tableName) { + assert(_tableHeapType.isStructuralSubtypeOf(w.HeapType.any)); + } + + w.RefType get type => w.RefType(_tableHeapType, nullable: true); + + /// Gets the wasm table used to reference this table in [module]. + /// + /// This can either be the table definition itself or an import of it. Imports + /// the table into [module] if it is not imported yet. + w.Table getWasmTable(w.ModuleBuilder module) { + return _importedWasmTables.get(_definedWasmTable, module); + } + + /// Returns the index for [function] in the table allocating one if necessary. + int indexForObject(Object object, + [w.ModuleBuilder? initModule, + void Function(w.InstructionsBuilder)? init]) { + assert((initModule != null) == (init != null)); + final existing = _table[object]; + if (existing != null) return existing.$1; + + w.InstructionsBuilder? expression; + if (initModule != null) { + expression = w.InstructionsBuilder( + initModule, [], [w.RefType(_tableHeapType, nullable: false)], + constantExpression: true); + init!(expression); + } + + return (_table[object] = (_table.length, expression)).$1; + } + + void output() { + final importedTables = _importedWasmTables; + _table.forEach((fun, tuple) { + final (index, expression) = tuple; + if (expression != null) { + final moduleBuilder = expression.moduleBuilder; + if (translator.isMainModule(moduleBuilder)) { + _definedWasmTable.moduleBuilder.elements + .activeExpressionSegmentBuilderFor(_definedWasmTable) + .setExpressionAt(index, expression); + } else { + // This will generate the imported table if it doesn't already exist. + final importedTable = getWasmTable(moduleBuilder) as w.ImportedTable; + moduleBuilder.elements + .activeExpressionSegmentBuilderFor(importedTable) + .setExpressionAt(index, expression); + } + } + }); + + _definedWasmTable.minSize = _table.length; + for (final table in importedTables.imports) { + table.minSize = _table.length; + } + } +} diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 1c9d393c60e..3bd3d724c7a 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -37,6 +37,7 @@ import 'reference_extensions.dart'; import 'serialization.dart'; import 'static_dispatch_table.dart'; import 'symbols.dart'; +import 'table_based_globals.dart'; import 'tags.dart'; import 'types.dart'; import 'util.dart' as util; @@ -194,9 +195,11 @@ class Translator with KernelNodes { late final ClosureLayouter closureLayouter; late final ClassInfoCollector classInfoCollector; late final CrossModuleFunctionTable crossModuleFunctionTable; + late final TableBasedGlobals tableBasedGlobals; late final DispatchTable dispatchTable; DispatchTable? dynamicMainModuleDispatchTable; late final Globals globals; + late final DartGlobals dartGlobals; late final Constants constants; late final Types types; late final ExceptionTags _exceptionTags; @@ -521,6 +524,7 @@ class Translator with KernelNodes { closureLayouter = ClosureLayouter(this); classInfoCollector = ClassInfoCollector(this); crossModuleFunctionTable = CrossModuleFunctionTable(this); + tableBasedGlobals = TableBasedGlobals(this); dispatchTable = DispatchTable(isDynamicSubmoduleTable: isDynamicSubmodule) ..translator = this; if (isDynamicSubmodule) { @@ -595,6 +599,7 @@ class Translator with KernelNodes { classInfoCollector.collect(); globals = Globals(this); + dartGlobals = DartGlobals(this); constants = Constants(this); dispatchTable.build(); @@ -616,6 +621,7 @@ class Translator with KernelNodes { constructorClosures.clear(); dispatchTable.output(); crossModuleFunctionTable.output(); + tableBasedGlobals.outputTables(); for (ConstantInfo info in constants.constantInfo.values) { info.printInitializer((function) { @@ -1970,7 +1976,7 @@ class Translator with KernelNodes { if (target == member.setterReference) return true; // Implicit getter for static fields may invoke lazy static initializer. - if (globals.getConstantInitializer(member) != null) { + if (dartGlobals.getConstantInitializer(member) != null) { // This global will get it's initializer eagerly set, so no lazy init // function to be called. return true; diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat index 770f7c8f623..35a65312215 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat @@ -94,9 +94,9 @@ global.get $"C515 FooConst5" array.new_fixed $Array 6 call $GrowableList._withData + local.tee $var1 global.set $allFooConstants - global.get $allFooConstants - ref.as_non_null + local.get $var1 end $label0 local.tee $var1 struct.get $WasmListBase $_length diff --git a/pkg/dart2wasm/test/ir_tests/globals.dart b/pkg/dart2wasm/test/ir_tests/globals.dart new file mode 100644 index 00000000000..d97037fd363 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/globals.dart @@ -0,0 +1,120 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// functionFilter=DartGlobals +// tableFilter=cross-module-funcs|global-table +// globalFilter=NoMatch +// typeFilter=NoMatch +// compilerOption=--no-minify +// compilerOption=-O0 + +void main() { + // Ensure we read and write the globals. + print(DartGlobals.foo0_constInit); + print(DartGlobals.foo1_newInit); + print(DartGlobals.foo2_newInit_final); + print(DartGlobals.foo3_newInit); + print(DartGlobals.foo4_newInit); + print(DartGlobals.foo5_newInit); + print(DartGlobals.foo6_newInit); + print(DartGlobals.foo7_newInit); + print(DartGlobals.foo8_newInit); + print(DartGlobals.foo9_newInit); + + print(DartGlobals.bar0_constInit); + print(DartGlobals.bar1_newInit); + print(DartGlobals.bar2_newInit_final); + print(DartGlobals.bar3_noInit); + print(DartGlobals.bar4_noInit); + print(DartGlobals.bar5_noInit); + print(DartGlobals.bar6_noInit); + print(DartGlobals.bar7_noInit); + print(DartGlobals.bar8_noInit); + print(DartGlobals.bar9_noInit); + + print(DartGlobals.baz0_constInit); + print(DartGlobals.baz1_newInit); + print(DartGlobals.baz2_newInit_final); + print(DartGlobals.baz3_noInit); + + DartGlobals.foo0_constInit = Foo(''); + DartGlobals.foo1_newInit = Foo(''); + DartGlobals.foo3_newInit = Foo(''); + DartGlobals.foo4_newInit = Foo(''); + DartGlobals.foo5_newInit = Foo(''); + DartGlobals.foo6_newInit = Foo(''); + DartGlobals.foo7_newInit = Foo(''); + DartGlobals.foo8_newInit = Foo(''); + DartGlobals.foo9_newInit = Foo(''); + + DartGlobals.bar0_constInit = null; + DartGlobals.bar1_newInit = null; + DartGlobals.bar3_noInit = Bar(''); + DartGlobals.bar4_noInit = Bar(''); + DartGlobals.bar5_noInit = Bar(''); + DartGlobals.bar6_noInit = Bar(''); + DartGlobals.bar7_noInit = Bar(''); + DartGlobals.bar8_noInit = Bar(''); + DartGlobals.bar9_noInit = Bar(''); + + DartGlobals.baz0_constInit = null; + DartGlobals.baz1_newInit = null; + DartGlobals.baz3_noInit = Baz(''); +} + +class DartGlobals { + // Field type has more than 10 such fields, so it qualifies for table based + // slot. Though depending on which case we may still prefer global. + static Foo foo0_constInit = const Foo('foo0'); + static Foo foo1_newInit = Foo('foo1'); + static final Foo foo2_newInit_final = Foo('foo2'); + static Foo foo3_newInit = Foo('foo3'); + static Foo foo4_newInit = Foo('foo4'); + static Foo foo5_newInit = Foo('foo5'); + static Foo foo6_newInit = Foo('foo6'); + static Foo foo7_newInit = Foo('foo7'); + static Foo foo8_newInit = Foo('foo8'); + static Foo foo9_newInit = Foo('foo9'); + + // Field type has more than 10 such fields, so it qualifies for table based + // slot. Though depending on which case we may still prefer global. + static Bar? bar0_constInit = const Bar('bar0'); + static Bar? bar1_newInit = Bar('bar1'); + static final Bar? bar2_newInit_final = Bar('bar2'); + static Bar? bar3_noInit; + static Bar? bar4_noInit; + static Bar? bar5_noInit; + static Bar? bar6_noInit; + static Bar? bar7_noInit; + static Bar? bar8_noInit; + static Bar? bar9_noInit; + + // Field type has less than 10 such fields, so it doesn't qualify for a table + // based slot, we use global fields. + static Baz? baz0_constInit = const Baz('baz0'); + static Baz? baz1_newInit = Baz('baz1'); + static final Baz? baz2_newInit_final = Baz('baz2'); + static Baz? baz3_noInit; +} + +class Foo { + final String value; + const Foo(this.value); + + String toString() => 'Foo($value)'; +} + +class Bar { + final String value; + const Bar(this.value); + + String toString() => 'Bar($value)'; +} + +class Baz { + final String value; + const Baz(this.value); + + String toString() => 'Baz($value)'; +} diff --git a/pkg/dart2wasm/test/ir_tests/globals.wat b/pkg/dart2wasm/test/ir_tests/globals.wat new file mode 100644 index 00000000000..b116357d0ee --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/globals.wat @@ -0,0 +1,418 @@ +(module $module0 + (type $Bar <...>) + (type $Baz <...>) + (type $Foo <...>) + (type $JSStringImpl <...>) + (table $dtable0 10 (ref null $Bar)) + (table $dtable2 9 (ref null $Foo)) + (global $"C348 \"baz1\"" (ref $JSStringImpl) <...>) + (global $"C352 \"bar1\"" (ref $JSStringImpl) <...>) + (global $"C355 \"foo9\"" (ref $JSStringImpl) <...>) + (global $"C356 \"foo8\"" (ref $JSStringImpl) <...>) + (global $"C357 \"foo7\"" (ref $JSStringImpl) <...>) + (global $"C358 \"foo6\"" (ref $JSStringImpl) <...>) + (global $"C359 \"foo5\"" (ref $JSStringImpl) <...>) + (global $"C360 \"foo4\"" (ref $JSStringImpl) <...>) + (global $"C361 \"foo3\"" (ref $JSStringImpl) <...>) + (global $"C362 \"foo1\"" (ref $JSStringImpl) <...>) + (global $"C366 \"baz2\"" (ref $JSStringImpl) <...>) + (global $"C367 \"bar2\"" (ref $JSStringImpl) <...>) + (global $"C368 \"foo2\"" (ref $JSStringImpl) <...>) + (global $"DartGlobals.bar1_newInit initialized" (mut i32) <...>) + (global $"DartGlobals.bar2_newInit_final initialized" (mut i32) <...>) + (global $"DartGlobals.baz1_newInit initialized" (mut i32) <...>) + (global $"DartGlobals.baz2_newInit_final initialized" (mut i32) <...>) + (global $DartGlobals.baz0_constInit (mut (ref null $Baz)) <...>) + (global $DartGlobals.baz1_newInit (mut (ref null $Baz)) <...>) + (global $DartGlobals.baz2_newInit_final (mut (ref null $Baz)) <...>) + (global $DartGlobals.baz3_noInit (mut (ref null $Baz)) <...>) + (global $DartGlobals.foo0_constInit (mut (ref $Foo)) <...>) + (elem $dtable0 <...>) + (func $"DartGlobals.bar0_constInit implicit getter" (result (ref null $Bar)) + i32.const 8 + table.get $dtable0 + ) + (func $"DartGlobals.bar0_constInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 8 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar1_newInit field initializer" (result (ref null $Bar)) + (local $var0 (ref null $Bar)) + i32.const 7 + global.get $"C352 \"bar1\"" + call $Bar + local.tee $var0 + table.set $dtable0 + local.get $var0 + i32.const 1 + global.set $"DartGlobals.bar1_newInit initialized" + ) + (func $"DartGlobals.bar1_newInit implicit getter" (result (ref null $Bar)) + global.get $"DartGlobals.bar1_newInit initialized" + if (result (ref null $Bar)) + i32.const 7 + table.get $dtable0 + else + call $"DartGlobals.bar1_newInit field initializer" + end + ) + (func $"DartGlobals.bar1_newInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 7 + local.get $var0 + table.set $dtable0 + i32.const 1 + global.set $"DartGlobals.bar1_newInit initialized" + ) + (func $"DartGlobals.bar2_newInit_final field initializer" (result (ref null $Bar)) + (local $var0 (ref null $Bar)) + i32.const 9 + global.get $"C367 \"bar2\"" + call $Bar + local.tee $var0 + table.set $dtable0 + local.get $var0 + i32.const 1 + global.set $"DartGlobals.bar2_newInit_final initialized" + ) + (func $"DartGlobals.bar2_newInit_final implicit getter" (result (ref null $Bar)) + global.get $"DartGlobals.bar2_newInit_final initialized" + if (result (ref null $Bar)) + i32.const 9 + table.get $dtable0 + else + call $"DartGlobals.bar2_newInit_final field initializer" + end + ) + (func $"DartGlobals.bar3_noInit implicit getter" (result (ref null $Bar)) + i32.const 6 + table.get $dtable0 + ) + (func $"DartGlobals.bar3_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 6 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar4_noInit implicit getter" (result (ref null $Bar)) + i32.const 5 + table.get $dtable0 + ) + (func $"DartGlobals.bar4_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 5 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar5_noInit implicit getter" (result (ref null $Bar)) + i32.const 4 + table.get $dtable0 + ) + (func $"DartGlobals.bar5_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 4 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar6_noInit implicit getter" (result (ref null $Bar)) + i32.const 3 + table.get $dtable0 + ) + (func $"DartGlobals.bar6_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 3 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar7_noInit implicit getter" (result (ref null $Bar)) + i32.const 2 + table.get $dtable0 + ) + (func $"DartGlobals.bar7_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 2 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar8_noInit implicit getter" (result (ref null $Bar)) + i32.const 1 + table.get $dtable0 + ) + (func $"DartGlobals.bar8_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 1 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.bar9_noInit implicit getter" (result (ref null $Bar)) + i32.const 0 + table.get $dtable0 + ) + (func $"DartGlobals.bar9_noInit= implicit setter" (param $var0 (ref null $Bar)) + i32.const 0 + local.get $var0 + table.set $dtable0 + ) + (func $"DartGlobals.baz0_constInit implicit getter" (result (ref null $Baz)) + global.get $DartGlobals.baz0_constInit + ) + (func $"DartGlobals.baz0_constInit= implicit setter" (param $var0 (ref null $Baz)) + local.get $var0 + global.set $DartGlobals.baz0_constInit + ) + (func $"DartGlobals.baz1_newInit field initializer" (result (ref null $Baz)) + (local $var0 (ref null $Baz)) + global.get $"C348 \"baz1\"" + call $Baz + local.tee $var0 + global.set $DartGlobals.baz1_newInit + local.get $var0 + i32.const 1 + global.set $"DartGlobals.baz1_newInit initialized" + ) + (func $"DartGlobals.baz1_newInit implicit getter" (result (ref null $Baz)) + global.get $"DartGlobals.baz1_newInit initialized" + if (result (ref null $Baz)) + global.get $DartGlobals.baz1_newInit + else + call $"DartGlobals.baz1_newInit field initializer" + end + ) + (func $"DartGlobals.baz1_newInit= implicit setter" (param $var0 (ref null $Baz)) + local.get $var0 + global.set $DartGlobals.baz1_newInit + i32.const 1 + global.set $"DartGlobals.baz1_newInit initialized" + ) + (func $"DartGlobals.baz2_newInit_final field initializer" (result (ref null $Baz)) + (local $var0 (ref null $Baz)) + global.get $"C366 \"baz2\"" + call $Baz + local.tee $var0 + global.set $DartGlobals.baz2_newInit_final + local.get $var0 + i32.const 1 + global.set $"DartGlobals.baz2_newInit_final initialized" + ) + (func $"DartGlobals.baz2_newInit_final implicit getter" (result (ref null $Baz)) + global.get $"DartGlobals.baz2_newInit_final initialized" + if (result (ref null $Baz)) + global.get $DartGlobals.baz2_newInit_final + else + call $"DartGlobals.baz2_newInit_final field initializer" + end + ) + (func $"DartGlobals.baz3_noInit implicit getter" (result (ref null $Baz)) + global.get $DartGlobals.baz3_noInit + ) + (func $"DartGlobals.baz3_noInit= implicit setter" (param $var0 (ref null $Baz)) + local.get $var0 + global.set $DartGlobals.baz3_noInit + ) + (func $"DartGlobals.foo0_constInit implicit getter" (result (ref $Foo)) + global.get $DartGlobals.foo0_constInit + ) + (func $"DartGlobals.foo0_constInit= implicit setter" (param $var0 (ref $Foo)) + local.get $var0 + global.set $DartGlobals.foo0_constInit + ) + (func $"DartGlobals.foo1_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 7 + global.get $"C362 \"foo1\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo1_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 7 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo1_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo1_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 7 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo2_newInit_final field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 8 + global.get $"C368 \"foo2\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo2_newInit_final implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 8 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo2_newInit_final field initializer" + end $label0 + ) + (func $"DartGlobals.foo3_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 6 + global.get $"C361 \"foo3\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo3_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 6 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo3_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo3_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 6 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo4_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 5 + global.get $"C360 \"foo4\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo4_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 5 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo4_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo4_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 5 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo5_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 4 + global.get $"C359 \"foo5\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo5_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 4 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo5_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo5_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 4 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo6_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 3 + global.get $"C358 \"foo6\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo6_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 3 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo6_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo6_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 3 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo7_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 2 + global.get $"C357 \"foo7\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo7_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 2 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo7_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo7_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 2 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo8_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 1 + global.get $"C356 \"foo8\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo8_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 1 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo8_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo8_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 1 + local.get $var0 + table.set $dtable2 + ) + (func $"DartGlobals.foo9_newInit field initializer" (result (ref $Foo)) + (local $var0 (ref null $Foo)) + i32.const 0 + global.get $"C355 \"foo9\"" + call $Foo + local.tee $var0 + table.set $dtable2 + local.get $var0 + ref.as_non_null + ) + (func $"DartGlobals.foo9_newInit implicit getter" (result (ref $Foo)) + block $label0 (result (ref $Foo)) + i32.const 0 + table.get $dtable2 + br_on_non_null $label0 + call $"DartGlobals.foo9_newInit field initializer" + end $label0 + ) + (func $"DartGlobals.foo9_newInit= implicit setter" (param $var0 (ref $Foo)) + i32.const 0 + local.get $var0 + table.set $dtable2 + ) + (func $Bar (param $value (ref $JSStringImpl)) (result (ref $Bar)) <...>) + (func $Baz (param $value (ref $JSStringImpl)) (result (ref $Baz)) <...>) + (func $Foo (param $value (ref $JSStringImpl)) (result (ref $Foo)) <...>) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/interop.bool.wat b/pkg/dart2wasm/test/ir_tests/interop.bool.wat index ceb68af3f4c..667f9ab0cab 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.bool.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.bool.wat @@ -35,8 +35,9 @@ (func $"testBoolValueNullable " (local $var0 (ref null $#Top)) global.get $"boolValueNullable initialized" - i32.eqz - if + if (result (ref null $#Top)) + global.get $boolValueNullable + else call $"ktrue implicit getter" if (result (ref null $#Top)) global.get $"C40 true" @@ -46,11 +47,12 @@ else ref.null none end + local.tee $var0 global.set $boolValueNullable i32.const 1 global.set $"boolValueNullable initialized" + local.get $var0 end - global.get $boolValueNullable local.tee $var0 ref.is_null if (result externref) diff --git a/pkg/dart2wasm/test/ir_tests/interop.double.wat b/pkg/dart2wasm/test/ir_tests/interop.double.wat index 759445b255e..7389eeb3ac8 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.double.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.double.wat @@ -33,8 +33,9 @@ (func $"testDoubleValueNullable " (local $var0 (ref null $BoxedDouble)) global.get $"doubleValueNullable initialized" - i32.eqz - if + if (result (ref null $BoxedDouble)) + global.get $doubleValueNullable + else call $"ktrue implicit getter" if (result (ref null $BoxedDouble)) i32.const 90 @@ -43,11 +44,12 @@ else ref.null none end + local.tee $var0 global.set $doubleValueNullable i32.const 1 global.set $"doubleValueNullable initialized" + local.get $var0 end - global.get $doubleValueNullable local.tee $var0 ref.is_null if (result externref) diff --git a/pkg/dart2wasm/test/ir_tests/interop.int.wat b/pkg/dart2wasm/test/ir_tests/interop.int.wat index b262fddda41..a8a4e339512 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.int.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.int.wat @@ -35,8 +35,9 @@ (func $"testIntValueNullable " (local $var0 (ref null $BoxedInt)) global.get $"intValueNullable initialized" - i32.eqz - if + if (result (ref null $BoxedInt)) + global.get $intValueNullable + else call $"ktrue implicit getter" if (result (ref null $BoxedInt)) i32.const 69 @@ -45,11 +46,12 @@ else ref.null none end + local.tee $var0 global.set $intValueNullable i32.const 1 global.set $"intValueNullable initialized" + local.get $var0 end - global.get $intValueNullable local.tee $var0 ref.is_null if (result externref) diff --git a/pkg/dart2wasm/test/ir_tests/interop.num.wat b/pkg/dart2wasm/test/ir_tests/interop.num.wat index d6b712e6964..86ba5d9eb93 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.num.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.num.wat @@ -43,19 +43,21 @@ (func $"testNumValueNullable " (local $var0 (ref null $#Top)) global.get $"numValueNullable initialized" - i32.eqz - if + if (result (ref null $#Top)) + global.get $numValueNullable + else call $"ktrue implicit getter" if (result (ref null $#Top)) call $"numValue implicit getter" else ref.null none end + local.tee $var0 global.set $numValueNullable i32.const 1 global.set $"numValueNullable initialized" + local.get $var0 end - global.get $numValueNullable local.tee $var0 ref.is_null if (result externref) diff --git a/pkg/dart2wasm/test/ir_tests/interop.string.wat b/pkg/dart2wasm/test/ir_tests/interop.string.wat index 937c0257823..fd9e6788995 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.string.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.string.wat @@ -35,26 +35,29 @@ ) (func $"testStringValueNullable " (local $var0 (ref null $JSStringImpl)) + (local $var1 (ref null $JSStringImpl)) global.get $"stringValueNullable initialized" - i32.eqz - if + if (result (ref null $JSStringImpl)) + global.get $stringValueNullable + else call $"ktrue implicit getter" if (result (ref null $JSStringImpl)) call $"stringValue implicit getter" else ref.null none end + local.tee $var0 global.set $stringValueNullable i32.const 1 global.set $"stringValueNullable initialized" + local.get $var0 end - global.get $stringValueNullable - local.tee $var0 + local.tee $var1 ref.is_null if (result externref) ref.null noextern else - local.get $var0 + local.get $var1 call $jsifyRaw end call $"dart2wasm._299 (import)" diff --git a/pkg/dart2wasm/tool/compile_benchmark b/pkg/dart2wasm/tool/compile_benchmark index 2d13743cbb0..5ecf3f39fe1 100755 --- a/pkg/dart2wasm/tool/compile_benchmark +++ b/pkg/dart2wasm/tool/compile_benchmark @@ -93,7 +93,7 @@ while [ $# -gt 0 ]; do shift ;; - -O0 | --optimization-level=0) + -O0 | --optimization-level=0 | --extra-compiler-option=-O0 ) DART2WASM_ARGS+=("-O0") RUN_BINARYEN=0 shift diff --git a/pkg/wasm_builder/lib/src/builder/elements.dart b/pkg/wasm_builder/lib/src/builder/elements.dart index 0ee9814dd0a..ee108bc18dc 100644 --- a/pkg/wasm_builder/lib/src/builder/elements.dart +++ b/pkg/wasm_builder/lib/src/builder/elements.dart @@ -11,13 +11,15 @@ import 'builder.dart'; class ElementsBuilder with Builder { final ModuleBuilder _moduleBuilder; final _functionTableBuilders = {}; + final _expressionTableBuilders = {}; late final declarativeSegmentBuilder = DeclarativeSegmentBuilder(_moduleBuilder); ElementsBuilder(this._moduleBuilder); - bool get hasActiveElementSegments => _functionTableBuilders.isNotEmpty; + bool get hasActiveElementSegments => + _functionTableBuilders.isNotEmpty || _expressionTableBuilders.isNotEmpty; ActiveFunctionSegmentBuilder activeFunctionSegmentBuilderFor(ir.Table table) { assert(table.type.isSubtypeOf(ir.RefType.func(nullable: true))); @@ -26,12 +28,22 @@ class ElementsBuilder with Builder { table, () => ActiveFunctionSegmentBuilder(table)); } + ActiveExpressionSegmentBuilder activeExpressionSegmentBuilderFor( + ir.Table table) { + assert(table.enclosingModule == _moduleBuilder.module); + return _expressionTableBuilders.putIfAbsent( + table, () => ActiveExpressionSegmentBuilder(table)); + } + @override ir.Elements forceBuild() { final segments = []; for (final b in _functionTableBuilders.values) { segments.addAll(b.build()); } + for (final b in _expressionTableBuilders.values) { + segments.addAll(b.build()); + } if (declarativeSegmentBuilder._declaredFunctions.isNotEmpty) { segments.add(declarativeSegmentBuilder.build()); } @@ -107,3 +119,42 @@ class ActiveFunctionSegmentBuilder with Builder> { return segments; } } + +class ActiveExpressionSegmentBuilder + with Builder> { + final ir.Table table; + final Map _expressions = {}; + + ActiveExpressionSegmentBuilder(this.table); + + void setExpressionAt(int index, InstructionsBuilder init) { + assert(init.moduleBuilder.module == table.enclosingModule); + assert(table.maxSize == null || index < table.maxSize!, + 'Index $index greater than max table size ${table.maxSize}'); + _expressions[index] = init; + table.minSize = math.max(table.minSize, index + 1); + } + + @override + List forceBuild() { + final entries = _expressions.entries.toList() + ..sort((a, b) => a.key.compareTo(b.key)); + + final segments = []; + + ir.ActiveExpressionElementSegment? current; + int lastIndex = -2; + for (final entry in entries) { + final index = entry.key; + final expression = entry.value; + if (index != lastIndex + 1) { + current = ir.ActiveExpressionElementSegment(table, table.type, index); + segments.add(current); + } + current!.expressions.add(expression.build().instructions); + lastIndex = index; + } + + return segments; + } +}