diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 1c5472b70ad..33314a0f582 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -707,7 +707,15 @@ abstract class AstCodeGenerator } List call(Reference target, {bool useUncheckedEntry = false}) { - return b.invoke(translator.directCallTarget(target, useUncheckedEntry)); + final targetModule = translator.moduleForReference(target); + final isLocalModuleCall = targetModule == b.module; + + if (isLocalModuleCall) { + return b.invoke(translator.directCallTarget(target, useUncheckedEntry)); + } else { + b.comment('Indirect call to $target'); + return translator.callReference(target, b); + } } @override @@ -1912,7 +1920,8 @@ abstract class AstCodeGenerator b.i32_const(offset); b.i32_add(); } - b.call_indirect(selector.signature, translator.dispatchTable.wasmTable); + b.call_indirect( + selector.signature, translator.dispatchTable.getWasmTable(b.module)); translator.functions.recordSelectorUse(selector); } @@ -4516,7 +4525,7 @@ abstract class CallTarget { /// The wasm target function to call. /// - /// This should only be accessed if caller intents to call it, as it will + /// This should only be accessed if caller intends to call it, as it will /// enqueue the function in the compilation queue. w.BaseFunction get function; } diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart index 6e17a75cc00..1491dff3c7a 100644 --- a/pkg/dart2wasm/lib/dispatch_table.dart +++ b/pkg/dart2wasm/lib/dispatch_table.dart @@ -69,8 +69,6 @@ class SelectorInfo { /// class member for this selector. int? offset; - w.ModuleBuilder get m => translator.m; - /// The selector's member's name. String get name => paramInfo.member!.name.text; @@ -215,6 +213,9 @@ class SelectorInfo { /// Builds the dispatch table for member calls. class DispatchTable { + static const _tableName = 'dispatch'; + static const _functionType = w.RefType.func(nullable: true); + final Translator translator; final List _selectorMetadata; final Map _procedureAttributeMetadata; @@ -236,10 +237,14 @@ class DispatchTable { /// member for the selector. late final List _table; - /// The Wasm table for the dispatch table. - late final w.TableBuilder wasmTable; + late final w.TableBuilder _definedWasmTable; + final Map _importedWasmTables = {}; - w.ModuleBuilder get m => translator.m; + /// The Wasm table for the dispatch table. + w.Table getWasmTable(w.ModuleBuilder module) => + translator.isMainModule(module) + ? _definedWasmTable + : _importedWasmTables[module]!; DispatchTable(this.translator) : _selectorMetadata = @@ -499,7 +504,17 @@ class DispatchTable { selectors[i].offset = rows[i].offset; } - wasmTable = m.tables.define(w.RefType.func(nullable: true), _table.length); + _definedWasmTable = + translator.mainModule.tables.define(_functionType, _table.length); + if (translator.hasMultipleModules) { + final mainModuleName = translator.nameForModule(translator.mainModule); + translator.mainModule.exports.export(_tableName, _definedWasmTable); + for (final module in translator.modules) { + if (translator.isMainModule(module)) continue; + _importedWasmTables[module] = module.tables + .import(mainModuleName, _tableName, _functionType, _table.length); + } + } } void output() { @@ -507,8 +522,21 @@ class DispatchTable { Reference? target = _table[i]; if (target != null) { w.BaseFunction? fun = translator.functions.getExistingFunction(target); + // Any call to the dispatch table is guaranteed to hit a target. + // + // If a target is in a deferred module and that deferred module hasn't + // been loaded yet, then the entry is `null`. + // + // Though we can only hit a target if that target's class has been + // allocated. In order for the class to be allocated, the deferred + // module must've been loaded to call the constructor. if (fun != null) { - wasmTable.setElement(i, fun); + final targetModule = translator.moduleForReference(target); + if (translator.isMainModule(targetModule)) { + _definedWasmTable.setElement(i, fun); + } else { + _importedWasmTables[targetModule]!.setElements[fun] = i; + } } } } diff --git a/pkg/dart2wasm/lib/static_dispatch_table.dart b/pkg/dart2wasm/lib/static_dispatch_table.dart new file mode 100644 index 00000000000..bf9a77751c6 --- /dev/null +++ b/pkg/dart2wasm/lib/static_dispatch_table.dart @@ -0,0 +1,99 @@ +// Copyright (c) 2024, 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 'dart:collection'; + +import 'package:wasm_builder/wasm_builder.dart' as w; + +import 'translator.dart'; + +class StaticDispatchTables { + final Translator translator; + + final Map _tables = + LinkedHashMap( + hashCode: (t) => + Object.hash(Object.hashAll(t.inputs), Object.hashAll(t.outputs)), + equals: (t1, t2) => t1.isStructurallyEqualTo(t2)); + + StaticDispatchTables(this.translator); + + StaticDispatchTableForSignature getTableForType(w.FunctionType type) { + return _tables[type] ??= + StaticDispatchTableForSignature(translator, type, _tables.length); + } + + void outputTables() { + for (final table in _tables.values) { + table.output(); + } + } +} + +/// Builds a static dispatch table for a specific function type signature. +/// +/// All calls to this table will have the same signature and so `call_indirect` +/// instructions that reference this table can omit the type check. +class StaticDispatchTableForSignature { + final String _tableName; + final w.FunctionType _functionType; + + final Translator translator; + + /// Contents of wasm table. + final Map _table = {}; + + late final w.TableBuilder _definedWasmTable; + final Map _importedWasmTables = {}; + + StaticDispatchTableForSignature( + this.translator, this._functionType, int nameCounter) + : _tableName = 'static$nameCounter' { + _definedWasmTable = translator.mainModule.tables + .define(w.RefType(_functionType, nullable: true), _table.length); + } + + /// Gets the wasm table used to reference this static dispatch 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) { + if (translator.isMainModule(module)) { + return _definedWasmTable; + } + if (_importedWasmTables.isEmpty) { + translator.mainModule.exports.export(_tableName, _definedWasmTable); + } + return _importedWasmTables.putIfAbsent(module, () { + final mainModuleName = translator.nameForModule(translator.mainModule); + return module.tables.import(mainModuleName, _tableName, + w.RefType(_functionType, nullable: true), _table.length); + }); + } + + /// Returns the index for [function] in the table allocating one if necessary. + int indexForFunction(w.BaseFunction function) { + assert(function.type.isStructurallyEqualTo(function.type)); + return _table[function] ??= _table.length; + } + + void output() { + final importedTables = _importedWasmTables; + _table.forEach((fun, index) { + final targetModule = fun.enclosingModule; + if (translator.isMainModule(targetModule)) { + _definedWasmTable.setElement(index, fun); + } else { + (getWasmTable(targetModule) as w.ImportedTable).setElements[fun] = + index; + } + }); + + _definedWasmTable.minSize = _table.length; + for (final table in importedTables.values) { + table.minSize = _table.length; + } + } +} diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 79b8234874e..34676e6b3ea 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -26,6 +26,7 @@ import 'kernel_nodes.dart'; import 'param_info.dart'; import 'records.dart'; import 'reference_extensions.dart'; +import 'static_dispatch_table.dart'; import 'tags.dart'; import 'types.dart'; import 'util.dart' as util; @@ -99,6 +100,7 @@ class Translator with KernelNodes { final LibraryIndex index; late final ClosureLayouter closureLayouter; late final ClassInfoCollector classInfoCollector; + late final StaticDispatchTables staticTablesPerType; late final DispatchTable dispatchTable; late final Globals globals; late final Constants constants; @@ -305,6 +307,7 @@ class Translator with KernelNodes { subtypes = hierarchy.computeSubtypesInformation(); closureLayouter = ClosureLayouter(this); classInfoCollector = ClassInfoCollector(this); + staticTablesPerType = StaticDispatchTables(this); dispatchTable = DispatchTable(this); compilationQueue = CompilationQueue(); functions = FunctionCollector(this); @@ -337,6 +340,7 @@ class Translator with KernelNodes { constructorClosures.clear(); dispatchTable.output(); + staticTablesPerType.outputTables(); initFunction.body.end(); for (ConstantInfo info in constants.constantInfo.values) { @@ -375,14 +379,36 @@ class Translator with KernelNodes { return callFunction(functions.getFunction(reference), b); } + final Map> + _importedFunctions = {}; + /// Generates a set of instructions to call [function] adding indirection /// if the call crosses a module boundary. Calls the function directly if it /// is local. Imports the function and calls it directly if is in the main /// module. Otherwise does an indirect call through the static dispatch table. List callFunction( w.BaseFunction function, w.InstructionsBuilder b) { - // TODO(natebiggs): Add indirect call. - b.call(function); + final targetModule = function.enclosingModule; + // TODO(natebiggs): Consider inlining function body in some scenarios. + if (targetModule == b.module) { + b.call(function); + } else if (isMainModule(targetModule)) { + final importedFunctions = _importedFunctions.putIfAbsent(function, () { + final importName = 'func${_importedFunctions.length}'; + targetModule.exports.export(importName, function); + return {}; + }); + final importedFunction = importedFunctions[b.module] ??= + b.module.functions.import(nameForModule(targetModule), + function.exportedName!, function.type); + b.call(importedFunction); + } else { + final staticTable = staticTablesPerType.getTableForType(function.type); + b.i32_const(staticTable.indexForFunction(function)); + b.table_get(staticTable.getWasmTable(b.module)); + b.ref_as_non_null(); + b.call_ref(function.type); + } return function.type.outputs; } @@ -1774,7 +1800,8 @@ class PolymorphicDispatcherCodeGenerator implements CodeGenerator { b.struct_get(translator.topInfo.struct, FieldIndex.classId); b.i32_const(selector.offset!); b.i32_add(); - b.call_indirect(signature, translator.dispatchTable.wasmTable); + b.call_indirect( + signature, translator.dispatchTable.getWasmTable(b.module)); translator.functions.recordSelectorUse(selector); } diff --git a/pkg/wasm_builder/lib/src/builder/function.dart b/pkg/wasm_builder/lib/src/builder/function.dart index 77b55545059..5be1e5ab263 100644 --- a/pkg/wasm_builder/lib/src/builder/function.dart +++ b/pkg/wasm_builder/lib/src/builder/function.dart @@ -13,14 +13,14 @@ class FunctionBuilder extends ir.BaseFunction /// The body of the function. late final InstructionsBuilder body; - FunctionBuilder(ModuleBuilder module, super.index, super.type, + FunctionBuilder(super.enclosingModule, super.index, super.type, [super.functionName]) { - body = InstructionsBuilder(module, type.inputs, type.outputs); + body = InstructionsBuilder(enclosingModule, type.inputs, type.outputs); } @override - ir.DefinedFunction forceBuild() => - ir.DefinedFunction(body.build(), finalizableIndex, type, functionName); + ir.DefinedFunction forceBuild() => ir.DefinedFunction( + enclosingModule, body.build(), finalizableIndex, type, functionName); @override String toString() => exportedName ?? "#$finalizableIndex"; diff --git a/pkg/wasm_builder/lib/src/builder/functions.dart b/pkg/wasm_builder/lib/src/builder/functions.dart index 11d35fcec70..2969a5f815f 100644 --- a/pkg/wasm_builder/lib/src/builder/functions.dart +++ b/pkg/wasm_builder/lib/src/builder/functions.dart @@ -55,7 +55,7 @@ class FunctionsBuilder with Builder { ir.ImportedFunction import(String module, String name, ir.FunctionType type, [String? functionName]) { final function = ir.ImportedFunction( - module, name, ir.FinalizableIndex(), type, functionName); + _module, module, name, ir.FinalizableIndex(), type, functionName); _importedFunctions.add(function); _addName(functionName, function); return function; diff --git a/pkg/wasm_builder/lib/src/builder/global.dart b/pkg/wasm_builder/lib/src/builder/global.dart index d5d7f9026e9..920f952f7c5 100644 --- a/pkg/wasm_builder/lib/src/builder/global.dart +++ b/pkg/wasm_builder/lib/src/builder/global.dart @@ -8,11 +8,11 @@ part of 'globals.dart'; class GlobalBuilder extends ir.Global with IndexableBuilder { final InstructionsBuilder initializer; - GlobalBuilder(ModuleBuilder module, super.index, super.type, + GlobalBuilder(super.enclosingModule, super.index, super.type, [super.globalName]) - : initializer = InstructionsBuilder(module, [], [type.type]); + : initializer = InstructionsBuilder(enclosingModule, [], [type.type]); @override - ir.DefinedGlobal forceBuild() => - ir.DefinedGlobal(initializer.build(), finalizableIndex, type, globalName); + ir.DefinedGlobal forceBuild() => ir.DefinedGlobal( + enclosingModule, initializer.build(), finalizableIndex, type, globalName); } diff --git a/pkg/wasm_builder/lib/src/builder/globals.dart b/pkg/wasm_builder/lib/src/builder/globals.dart index 259f5d9e318..25eb5284f23 100644 --- a/pkg/wasm_builder/lib/src/builder/globals.dart +++ b/pkg/wasm_builder/lib/src/builder/globals.dart @@ -42,7 +42,8 @@ class GlobalsBuilder with Builder { /// Imports a global variable into this module. ir.ImportedGlobal import(String module, String name, ir.GlobalType type) { - final global = ir.ImportedGlobal(module, name, ir.FinalizableIndex(), type); + final global = + ir.ImportedGlobal(_module, module, name, ir.FinalizableIndex(), type); _importedGlobals.add(global); return global; } diff --git a/pkg/wasm_builder/lib/src/builder/instructions.dart b/pkg/wasm_builder/lib/src/builder/instructions.dart index 548f979b75f..01155f78aa2 100644 --- a/pkg/wasm_builder/lib/src/builder/instructions.dart +++ b/pkg/wasm_builder/lib/src/builder/instructions.dart @@ -635,6 +635,7 @@ class InstructionsBuilder with Builder { void call(ir.BaseFunction function) { assert(_verifyTypes(function.type.inputs, function.type.outputs, trace: ['call', function])); + assert(function.enclosingModule == module); _add(ir.Call(function)); } diff --git a/pkg/wasm_builder/lib/src/builder/module.dart b/pkg/wasm_builder/lib/src/builder/module.dart index 9a58e3fe149..f2ff5e68b97 100644 --- a/pkg/wasm_builder/lib/src/builder/module.dart +++ b/pkg/wasm_builder/lib/src/builder/module.dart @@ -10,7 +10,7 @@ import 'builder.dart'; class ModuleBuilder with Builder { final Uri? sourceMapUrl; final List watchPoints; - late final types = TypesBuilder(this); + late final TypesBuilder types; late final functions = FunctionsBuilder(this); final tables = TablesBuilder(); final memories = MemoriesBuilder(); @@ -25,7 +25,10 @@ class ModuleBuilder with Builder { /// bytes to watch. When the module is serialized, the stack traces leading /// to the production of all watched bytes are printed. This can be used to /// debug runtime errors happening at specific offsets within the module. - ModuleBuilder(this.sourceMapUrl, {this.watchPoints = const []}); + ModuleBuilder(this.sourceMapUrl, + {ModuleBuilder? parent, this.watchPoints = const []}) { + types = TypesBuilder(this, parent: parent?.types); + } @override ir.Module forceBuild() { diff --git a/pkg/wasm_builder/lib/src/builder/table.dart b/pkg/wasm_builder/lib/src/builder/table.dart index 7eae81fcfb9..2b6e04d2403 100644 --- a/pkg/wasm_builder/lib/src/builder/table.dart +++ b/pkg/wasm_builder/lib/src/builder/table.dart @@ -12,7 +12,7 @@ class TableBuilder extends ir.Table with IndexableBuilder { : elements = List.filled(minSize, null, growable: true); void setElement(int index, ir.BaseFunction function) { - assert(type == ir.RefType.func(nullable: true), + assert(type.isSubtypeOf(ir.RefType.func(nullable: true)), "Elements are only supported for funcref tables"); assert(maxSize == null || index < maxSize!, 'Index $index greater than max table size $maxSize'); diff --git a/pkg/wasm_builder/lib/src/builder/tables.dart b/pkg/wasm_builder/lib/src/builder/tables.dart index fe58dd5958a..22f231f2b76 100644 --- a/pkg/wasm_builder/lib/src/builder/tables.dart +++ b/pkg/wasm_builder/lib/src/builder/tables.dart @@ -36,4 +36,15 @@ class TablesBuilder with Builder { _importedTables, _tableBuilders); return ir.Tables(_importedTables, built); } + + void collectUsedTypes(Set types) { + for (final table in _tableBuilders) { + final defType = table.type.containedDefType; + if (defType != null) types.add(defType); + } + for (final table in _importedTables) { + final defType = table.type.containedDefType; + if (defType != null) types.add(defType); + } + } } diff --git a/pkg/wasm_builder/lib/src/builder/types.dart b/pkg/wasm_builder/lib/src/builder/types.dart index 2aa26fa57c3..94ce6d5cacd 100644 --- a/pkg/wasm_builder/lib/src/builder/types.dart +++ b/pkg/wasm_builder/lib/src/builder/types.dart @@ -235,10 +235,11 @@ class _RecGroupBuilder { class TypesBuilder with Builder { final ModuleBuilder _module; - late final Map<_FunctionTypeKey, ir.FunctionType> _functionTypeMap = {}; - late final _RecGroupBuilder _recGroupBuilder = _RecGroupBuilder(); + final Map<_FunctionTypeKey, ir.FunctionType> _functionTypeMap = {}; + final _RecGroupBuilder _recGroupBuilder; - TypesBuilder(this._module); + TypesBuilder(this._module, {TypesBuilder? parent}) + : _recGroupBuilder = parent?._recGroupBuilder ?? _RecGroupBuilder(); /// Add a new function type to the module. /// @@ -287,6 +288,7 @@ class TypesBuilder with Builder { Set _collectUsedTypes() { final usedTypes = {}; + _module.tables.collectUsedTypes(usedTypes); _module.functions.collectUsedTypes(usedTypes); _module.globals.collectUsedTypes(usedTypes); _module.tags.collectUsedTypes(usedTypes); diff --git a/pkg/wasm_builder/lib/src/ir/function.dart b/pkg/wasm_builder/lib/src/ir/function.dart index e39f23af8c8..5ccfd9691e9 100644 --- a/pkg/wasm_builder/lib/src/ir/function.dart +++ b/pkg/wasm_builder/lib/src/ir/function.dart @@ -21,9 +21,11 @@ abstract class BaseFunction with Indexable implements Exportable { final FinalizableIndex finalizableIndex; final FunctionType type; final String? functionName; + final ModuleBuilder enclosingModule; String? exportedName; - BaseFunction(this.finalizableIndex, this.type, this.functionName); + BaseFunction(this.enclosingModule, this.finalizableIndex, this.type, + this.functionName); @override String get name => functionName ?? super.name; @@ -44,7 +46,8 @@ class DefinedFunction extends BaseFunction implements Serializable { /// All local variables defined in the function, including its inputs. List get locals => body.locals; - DefinedFunction(this.body, super.finalizableIndex, super.type, + DefinedFunction( + super.enclosingModule, this.body, super.finalizableIndex, super.type, [super.functionName]); @override @@ -85,7 +88,8 @@ class ImportedFunction extends BaseFunction implements Import { @override final String name; - ImportedFunction(this.module, this.name, super.finalizableIndex, super.type, + ImportedFunction(super.enclosingModule, this.module, this.name, + super.finalizableIndex, super.type, [super.functionName]); @override diff --git a/pkg/wasm_builder/lib/src/ir/functions.dart b/pkg/wasm_builder/lib/src/ir/functions.dart index 759405ef512..7ccc03550cc 100644 --- a/pkg/wasm_builder/lib/src/ir/functions.dart +++ b/pkg/wasm_builder/lib/src/ir/functions.dart @@ -2,6 +2,7 @@ // 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 '../builder/module.dart'; import '../serialize/serialize.dart'; import 'ir.dart'; diff --git a/pkg/wasm_builder/lib/src/ir/global.dart b/pkg/wasm_builder/lib/src/ir/global.dart index c31b03b3a37..f56b9d31ca0 100644 --- a/pkg/wasm_builder/lib/src/ir/global.dart +++ b/pkg/wasm_builder/lib/src/ir/global.dart @@ -9,11 +9,13 @@ abstract class Global with Indexable implements Exportable { @override final FinalizableIndex finalizableIndex; final GlobalType type; + final ModuleBuilder enclosingModule; /// Name of the global in the names section. final String? globalName; - Global(this.finalizableIndex, this.type, this.globalName); + Global( + this.enclosingModule, this.finalizableIndex, this.type, this.globalName); @override String toString() => globalName ?? "$finalizableIndex"; @@ -26,7 +28,8 @@ abstract class Global with Indexable implements Exportable { class DefinedGlobal extends Global implements Serializable { final Instructions initializer; - DefinedGlobal(this.initializer, super.finalizableIndex, super.type, + DefinedGlobal(super.enclosingModule, this.initializer, super.finalizableIndex, + super.type, [super.globalName]); @override @@ -44,7 +47,8 @@ class ImportedGlobal extends Global implements Import { @override final String name; - ImportedGlobal(this.module, this.name, super.finalizableIndex, super.type, + ImportedGlobal(super.enclosingModule, this.module, this.name, + super.finalizableIndex, super.type, [super.globalName]); @override diff --git a/pkg/wasm_builder/lib/src/ir/globals.dart b/pkg/wasm_builder/lib/src/ir/globals.dart index 4a1fda01926..1f04d32d014 100644 --- a/pkg/wasm_builder/lib/src/ir/globals.dart +++ b/pkg/wasm_builder/lib/src/ir/globals.dart @@ -2,6 +2,7 @@ // 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 '../builder/module.dart'; import '../serialize/serialize.dart'; import 'ir.dart'; diff --git a/pkg/wasm_builder/lib/src/ir/table.dart b/pkg/wasm_builder/lib/src/ir/table.dart index cc2b2ceb9e6..b6dad3289b1 100644 --- a/pkg/wasm_builder/lib/src/ir/table.dart +++ b/pkg/wasm_builder/lib/src/ir/table.dart @@ -9,7 +9,9 @@ class Table with Indexable implements Exportable, Serializable { @override final FinalizableIndex finalizableIndex; final RefType type; - final int minSize; + // Mutable so that a table's size does not need to be known prior to the table + // being instantiated. + int minSize; final int? maxSize; Table(this.finalizableIndex, this.type, this.minSize, this.maxSize); diff --git a/pkg/wasm_builder/lib/src/ir/type.dart b/pkg/wasm_builder/lib/src/ir/type.dart index 4829599d2b9..75ea1faeaa9 100644 --- a/pkg/wasm_builder/lib/src/ir/type.dart +++ b/pkg/wasm_builder/lib/src/ir/type.dart @@ -697,6 +697,18 @@ class FunctionType extends DefType { return true; } + bool isStructurallyEqualTo(FunctionType other) { + if (inputs.length != other.inputs.length) return false; + if (outputs.length != other.outputs.length) return false; + for (int i = 0; i < inputs.length; i++) { + if (inputs[i] != other.inputs[i]) return false; + } + for (int i = 0; i < outputs.length; i++) { + if (outputs[i] != other.outputs[i]) return false; + } + return true; + } + @override void serializeDefinitionInner(Serializer s) { s.writeByte(0x60); // -0x20 diff --git a/pkg/wasm_builder/lib/src/serialize/sections.dart b/pkg/wasm_builder/lib/src/serialize/sections.dart index bbfe3243f98..0bf13a956a9 100644 --- a/pkg/wasm_builder/lib/src/serialize/sections.dart +++ b/pkg/wasm_builder/lib/src/serialize/sections.dart @@ -226,7 +226,7 @@ class _Element implements Serializable { @override void serialize(Serializer s) { if (table.index != 0) { - s.writeByte(0x02); + s.writeByte(0x06); s.writeUnsigned(table.index); } else { s.writeByte(0x00); @@ -235,11 +235,17 @@ class _Element implements Serializable { s.writeSigned(startIndex); s.writeByte(0x0B); // end if (table.index != 0) { - s.writeByte(0x00); // elemkind + s.write(table.type); } s.writeUnsigned(entries.length); for (var entry in entries) { - s.writeUnsigned(entry.index); + if (table.index == 0) { + s.writeUnsigned(entry.index); + } else { + s.writeByte(0xD2); // ref.func + s.writeSigned(entry.index); + s.writeByte(0x0B); // end + } } } }