blob: 0999c81f25ffe2904cb6fceee3a95cad0ddfe292 [file] [edit]
// Copyright (c) 2022, 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:kernel/ast.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import 'code_generator.dart' show EagerStaticFieldInitializerCodeGenerator;
import 'reference_extensions.dart';
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;
/// 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 WasmGlobalImporter _globalsModuleMap;
Globals(this.translator)
: _globalsModuleMap = WasmGlobalImporter(translator, 'global');
void declareMainAppGlobalExportWithName(String name, w.Global exportable) {
_globalsModuleMap.exportDefinitionWithName(name, exportable);
}
/// Reads the value of [w.Global] onto the stack in [b].
///
/// Takes into account the calling module and the module the global belongs
/// to. If they are not the same then accesses the global indirectly, either
/// through an import or a getter call.
w.ValueType readGlobal(w.InstructionsBuilder b, w.Global global) {
final owningModule = translator.moduleToBuilder[global.enclosingModule]!;
final callingModule = b.moduleBuilder;
if (owningModule == callingModule) {
b.global_get(global);
} else if (translator.isMainModule(owningModule)) {
final importedGlobal = _globalsModuleMap.get(global, callingModule);
b.global_get(importedGlobal);
} else {
final getter = _globalGetters.putIfAbsent(global, () {
final getterType = owningModule.types.defineFunction(const [], [
global.type.type,
]);
final getterFunction = owningModule.functions.define(getterType)
..isPure = true;
final getterBody = getterFunction.body;
getterBody.global_get(global);
getterBody.end();
return getterFunction;
});
translator.callFunction(getter, b);
}
return global.type.type;
}
/// Dual to [readGlobal]
void writeGlobal(w.InstructionsBuilder b, w.Global global) {
final owningModule = translator.moduleToBuilder[global.enclosingModule]!;
final callingModule = b.moduleBuilder;
if (owningModule == callingModule) {
b.global_set(global);
} else if (translator.isMainModule(owningModule)) {
final importedGlobal = _globalsModuleMap.get(global, callingModule);
b.global_set(importedGlobal);
} else {
final setter = _globalSetters.putIfAbsent(global, () {
final setterType = owningModule.types.defineFunction([
global.type.type,
], const []);
final setterFunction = owningModule.functions.define(setterType);
final setterBody = setterFunction.body;
setterBody.local_get(setterFunction.locals.single);
setterBody.global_set(global);
setterBody.end();
return setterFunction;
});
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.
DartGlobalDefinition getDefinitionForStaticField(Field field) {
assert(!field.isLate);
return _definitions.putIfAbsent(field, () {
final fieldType = translator.translateTypeOfField(field);
final numberOfFieldsWithSameType = _fieldTypeCount[fieldType]!;
final useTableSlot =
numberOfFieldsWithSameType >= dartFieldTableUseCutoff &&
fieldType is w.RefType;
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,
)) {
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,
// that's preferred as we don't need to pay for lazy-init check on each
// access.
final initializer = field.initializer;
if (initializer != null && _initializeAtStartup(field)) {
final definition = _defineGlobalBasedField(
field,
fieldType,
module,
true,
init,
null,
);
// We have to initialize the global field in the same module as where
// the field value is defined in.
EagerStaticFieldInitializerCodeGenerator(
translator,
field,
definition.global,
).generate(module.startFunction.body, [], null);
return definition;
}
// Add initializer function to the compilation queue.
translator.functions.getFunction(field.staticFieldInitializer);
// 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,
);
});
}
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 {
translator
.getDummyValuesCollectorForModule(module)
.instantiateLocalDummyValue(global.initializer, fieldType);
}
global.initializer.end();
return WasmGlobalDartGlobal(global, initializedFlag: initializerFlag);
}
String _memberName(Field field) => field.toString();
bool _initializeAtStartup(Annotatable node) =>
util.getPragma<bool>(
translator.coreTypes,
node,
'wasm:initialize-at-startup',
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));
}
}