[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 <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2026-01-30 03:34:42 -08:00
committed by Commit Queue
parent 27d4f2c79d
commit ea6fb0a16f
14 changed files with 949 additions and 107 deletions
+29 -18
View File
@@ -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);
+193 -67
View File
@@ -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<Field, w.GlobalBuilder> _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<w.Global, w.BaseFunction> _globalGetters = {};
final Map<w.Global, w.BaseFunction> _globalSetters = {};
final Map<Field, w.Global> _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<w.ValueType, int> _fieldTypeCount = {};
final Map<Field, DartGlobalDefinition> _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<bool>(
@@ -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));
}
}
@@ -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<w.HeapType, TypeSpecificGlobalTable> _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<Object, (int, w.InstructionsBuilder?)> _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;
}
}
}
+7 -1
View File
@@ -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;
@@ -94,9 +94,9 @@
global.get $"C515 FooConst5"
array.new_fixed $Array<Object?> 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
+120
View File
@@ -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)';
}
+418
View File
@@ -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)) <...>)
)
+5 -3
View File
@@ -35,8 +35,9 @@
(func $"testBoolValueNullable <noInline>"
(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)
@@ -33,8 +33,9 @@
(func $"testDoubleValueNullable <noInline>"
(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)
+5 -3
View File
@@ -35,8 +35,9 @@
(func $"testIntValueNullable <noInline>"
(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)
+5 -3
View File
@@ -43,19 +43,21 @@
(func $"testNumValueNullable <noInline>"
(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)
@@ -35,26 +35,29 @@
)
(func $"testStringValueNullable <noInline>"
(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)"
+1 -1
View File
@@ -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
+52 -1
View File
@@ -11,13 +11,15 @@ import 'builder.dart';
class ElementsBuilder with Builder<ir.Elements> {
final ModuleBuilder _moduleBuilder;
final _functionTableBuilders = <ir.Table, ActiveFunctionSegmentBuilder>{};
final _expressionTableBuilders = <ir.Table, ActiveExpressionSegmentBuilder>{};
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<ir.Elements> {
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 = <ir.ElementSegment>[];
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<List<ir.ActiveElementSegment>> {
return segments;
}
}
class ActiveExpressionSegmentBuilder
with Builder<List<ir.ActiveElementSegment>> {
final ir.Table table;
final Map<int, InstructionsBuilder> _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<ir.ActiveElementSegment> forceBuild() {
final entries = _expressions.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
final segments = <ir.ActiveElementSegment>[];
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;
}
}