[dart2wasm] Add support for reading wasm files to package:wasm_builder
This adds a wasm binary reader that produces an `ir.Module`.
We also make a few changes to existing code
* Represent the import section with an `ir.Imports` object (similar to
`ir.Exports`, `ir.Functions`, ...)
* We make a bunch of data structures allocatable in uninitialized state
(the fields being usually uninitialized `late final` fields) where the
deserializer can create those objects and then fill in details later.
=> This comes partly due to the way wasm binaries are structured
themselves: The "data count" section comes first so a reader knows
how many data sections there will be, then the "code section" can
refer to those data sections. Then afterwards the actual "data
segment" comes that fills in the data of the section.
* We make names consistently optional: Wasm objects don't have to have
names, so the names should be optional, so we make them `String?`. We
also make them non-final as that's consistent with other names.
* We make the `ir.Types`, `ir.Functions`, ... objects have `operator[]`
and the index used is the same index used e.g. in wasm instructions.
* We make static constants for section ids and custom section names.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I5394d6b82cf4dc68d24cea1dee66c5b33eb2f60f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452144
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This commit is contained in:
committed by
Commit Queue
parent
3ab6859973
commit
8322e6af37
@@ -395,7 +395,7 @@ class DynamicModuleInfo {
|
||||
DynamicModuleInfo(this.translator, this.metadata);
|
||||
|
||||
void initSubmodule() {
|
||||
submodule.functions.start = initFunction = submodule.functions.define(
|
||||
submodule.startFunction = initFunction = submodule.functions.define(
|
||||
translator.typesBuilder.defineFunction(const [], const []), "#init");
|
||||
|
||||
// Make sure the exception tag is exported from the main module.
|
||||
|
||||
@@ -549,7 +549,7 @@ class Translator with KernelNodes {
|
||||
_initModules(sourceMapUrlGenerator);
|
||||
initFunction = mainModule.functions
|
||||
.define(typesBuilder.defineFunction(const [], const []), "#init");
|
||||
mainModule.functions.start = initFunction;
|
||||
mainModule.startFunction = initFunction;
|
||||
|
||||
closureLayouter.collect();
|
||||
classInfoCollector.collect();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@@ -39,14 +40,7 @@ Future main() async {
|
||||
final wasmBytes = outFile.readAsBytesSync();
|
||||
outFile.renameSync(outDart2WasmFilename);
|
||||
|
||||
if (vmBytes.length != wasmBytes.length) {
|
||||
throw 'Mismatch in length ${vmBytes.length} vs ${wasmBytes.length}';
|
||||
}
|
||||
for (int i = 0; i < vmBytes.length; ++i) {
|
||||
if (vmBytes[i] != wasmBytes[i]) {
|
||||
throw 'Mismatch at offset $i ${vmBytes[i]} vs ${wasmBytes[i]}';
|
||||
}
|
||||
}
|
||||
expectEqualBytes(vmBytes, wasmBytes);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +55,17 @@ Future run(List<String> command) async {
|
||||
}
|
||||
}
|
||||
|
||||
void expectEqualBytes(Uint8List a, Uint8List b) {
|
||||
if (a.length != b.length) {
|
||||
throw 'Mismatch in length ${a.length} vs ${b.length}';
|
||||
}
|
||||
for (int i = 0; i < a.length; ++i) {
|
||||
if (a[i] != b[i]) {
|
||||
throw 'Mismatch at offset $i ${a[i]} vs ${b[i]}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future withTempDir(Future Function(String directory) fun) async {
|
||||
final dir = Directory.systemTemp.createTempSync('dart2wasm_self_compile');
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2025, 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:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:wasm_builder/wasm_builder.dart';
|
||||
|
||||
import 'self_compile_test.dart' show withTempDir, run, expectEqualBytes;
|
||||
|
||||
Future main() async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) return;
|
||||
|
||||
await withTempDir((String tempDir) async {
|
||||
final dartFilename = 'third_party/flute/benchmarks/lib/complex.dart';
|
||||
|
||||
final wasmFilename = path.join(tempDir, 'flute.wasm');
|
||||
final wasmFile = File(wasmFilename);
|
||||
|
||||
await run([
|
||||
Platform.executable,
|
||||
'compile',
|
||||
'wasm',
|
||||
'-O0',
|
||||
dartFilename,
|
||||
'-o',
|
||||
wasmFilename,
|
||||
]);
|
||||
final wasmBytes = wasmFile.readAsBytesSync();
|
||||
expectEqualBytes(wasmBytes, readWrite(wasmBytes));
|
||||
// Temporary files will be deleted when returning to [withTempDir].
|
||||
});
|
||||
}
|
||||
|
||||
Uint8List readWrite(Uint8List wasmBytes) {
|
||||
final deserializer = Deserializer(wasmBytes);
|
||||
final module = Module.deserialize(deserializer);
|
||||
|
||||
final serializer = Serializer();
|
||||
module.serialize(serializer);
|
||||
return serializer.data;
|
||||
}
|
||||
@@ -12,15 +12,9 @@ class FunctionsBuilder with Builder<ir.Functions> {
|
||||
final _functionBuilders = <FunctionBuilder>[];
|
||||
final _importedFunctions = <ir.ImportedFunction>[];
|
||||
final _declaredFunctions = <ir.BaseFunction>{};
|
||||
ir.BaseFunction? _start;
|
||||
|
||||
FunctionsBuilder(this._moduleBuilder);
|
||||
|
||||
set start(ir.BaseFunction init) {
|
||||
assert(_start == null);
|
||||
_start = init;
|
||||
}
|
||||
|
||||
void collectUsedTypes(Set<ir.DefType> usedTypes) {
|
||||
for (final f in _functionBuilders) {
|
||||
usedTypes.add(f.type);
|
||||
@@ -62,7 +56,6 @@ class FunctionsBuilder with Builder<ir.Functions> {
|
||||
ir.Functions forceBuild() {
|
||||
final built = finalizeImportsAndBuilders<ir.DefinedFunction>(
|
||||
_importedFunctions, _functionBuilders);
|
||||
return ir.Functions(
|
||||
_start, _importedFunctions, built, [..._declaredFunctions]);
|
||||
return ir.Functions(_importedFunctions, built, [..._declaredFunctions]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,7 +789,7 @@ class InstructionsBuilder with Builder<ir.Instructions> {
|
||||
void select(ir.ValueType type) {
|
||||
assert(_verifyTypes([type, type, ir.NumType.i32], [type],
|
||||
trace: ['select', type]));
|
||||
_add(ir.Select(type));
|
||||
_add(type is ir.NumType ? ir.Select() : ir.SelectWithType(type));
|
||||
}
|
||||
|
||||
// Variable instructions
|
||||
|
||||
@@ -32,6 +32,7 @@ class ModuleBuilder with Builder<ir.Module> {
|
||||
final dataSegments = DataSegmentsBuilder();
|
||||
late final globals = GlobalsBuilder(this);
|
||||
final exports = ExportsBuilder();
|
||||
ir.BaseFunction? _startFunction;
|
||||
|
||||
/// Create a new, initially empty, module.
|
||||
///
|
||||
@@ -44,6 +45,11 @@ class ModuleBuilder with Builder<ir.Module> {
|
||||
types = TypesBuilder(this, parent: parent?.types);
|
||||
}
|
||||
|
||||
set startFunction(ir.BaseFunction init) {
|
||||
assert(_startFunction == null);
|
||||
_startFunction = init;
|
||||
}
|
||||
|
||||
@override
|
||||
ir.Module forceBuild() {
|
||||
final finalFunctions = functions.build();
|
||||
@@ -51,10 +57,18 @@ class ModuleBuilder with Builder<ir.Module> {
|
||||
final finalMemories = memories.build();
|
||||
final finalGlobals = globals.build();
|
||||
final finalTags = tags.build();
|
||||
final imports = ir.Imports(
|
||||
finalFunctions.imported,
|
||||
finalTags.imported,
|
||||
finalGlobals.imported,
|
||||
finalTables.imported,
|
||||
finalMemories.imported,
|
||||
);
|
||||
return module
|
||||
..initialize(
|
||||
moduleName,
|
||||
finalFunctions,
|
||||
_startFunction,
|
||||
finalTables,
|
||||
finalTags,
|
||||
finalMemories,
|
||||
@@ -62,13 +76,7 @@ class ModuleBuilder with Builder<ir.Module> {
|
||||
finalGlobals,
|
||||
types.build(),
|
||||
dataSegments.build(),
|
||||
<ir.Import>[
|
||||
...finalFunctions.imported,
|
||||
...finalTables.imported,
|
||||
...finalMemories.imported,
|
||||
...finalGlobals.imported,
|
||||
...finalTags.imported,
|
||||
],
|
||||
imports,
|
||||
watchPoints,
|
||||
sourceMapUrl);
|
||||
}
|
||||
|
||||
@@ -8,18 +8,20 @@ import '../serialize/serialize.dart';
|
||||
import 'ir.dart';
|
||||
|
||||
class BaseDataSegment {
|
||||
final int index;
|
||||
final Memory? memory;
|
||||
final int? offset;
|
||||
late final int index;
|
||||
late final Memory? memory;
|
||||
late final int? offset;
|
||||
|
||||
BaseDataSegment(this.index, this.memory, this.offset);
|
||||
BaseDataSegment.uninitialized();
|
||||
}
|
||||
|
||||
/// A data segment in a module.
|
||||
class DataSegment extends BaseDataSegment implements Serializable {
|
||||
final Uint8List content;
|
||||
late final Uint8List content;
|
||||
|
||||
DataSegment(super.index, this.content, super.memory, super.offset);
|
||||
DataSegment.uninitialized() : super.uninitialized();
|
||||
|
||||
@override
|
||||
void serialize(Serializer s) {
|
||||
|
||||
@@ -21,12 +21,12 @@ abstract class BaseFunction with Indexable, Exportable {
|
||||
@override
|
||||
final FinalizableIndex finalizableIndex;
|
||||
final FunctionType type;
|
||||
final String? functionName;
|
||||
String? functionName;
|
||||
@override
|
||||
final Module enclosingModule;
|
||||
|
||||
BaseFunction(this.enclosingModule, this.finalizableIndex, this.type,
|
||||
this.functionName);
|
||||
[this.functionName]);
|
||||
|
||||
@override
|
||||
String get name => functionName ?? super.name;
|
||||
@@ -40,7 +40,7 @@ abstract class BaseFunction with Indexable, Exportable {
|
||||
|
||||
/// A function defined in a module.
|
||||
class DefinedFunction extends BaseFunction implements Serializable {
|
||||
final Instructions body;
|
||||
late final Instructions body;
|
||||
|
||||
/// All local variables defined in the function, including its inputs.
|
||||
List<Local> get locals => body.locals;
|
||||
@@ -51,6 +51,10 @@ class DefinedFunction extends BaseFunction implements Serializable {
|
||||
super.enclosingModule, this.body, super.finalizableIndex, super.type,
|
||||
[super.functionName]);
|
||||
|
||||
DefinedFunction.withoutBody(
|
||||
super.enclosingModule, super.finalizableIndex, super.type,
|
||||
[super.functionName]);
|
||||
|
||||
@override
|
||||
void serialize(Serializer s) {
|
||||
// Serialize locals internally first in order to compute the total size of
|
||||
|
||||
@@ -6,9 +6,6 @@ import 'function.dart';
|
||||
|
||||
/// The interface for the functions in a module.
|
||||
class Functions {
|
||||
/// The start function.
|
||||
final BaseFunction? start;
|
||||
|
||||
/// Imported functions.
|
||||
final List<ImportedFunction> imported;
|
||||
|
||||
@@ -16,7 +13,15 @@ class Functions {
|
||||
final List<DefinedFunction> defined;
|
||||
|
||||
/// Declared functions.
|
||||
final List<BaseFunction> declared;
|
||||
late final List<BaseFunction> declared;
|
||||
|
||||
Functions(this.start, this.imported, this.defined, this.declared);
|
||||
Functions(this.imported, this.defined, this.declared);
|
||||
|
||||
Functions.withoutDeclared(this.imported, this.defined);
|
||||
|
||||
BaseFunction operator [](int index) => index < imported.length
|
||||
? imported[index]
|
||||
: defined[index - imported.length];
|
||||
|
||||
int get length => imported.length + defined.length;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ abstract class Global with Indexable, Exportable {
|
||||
final Module enclosingModule;
|
||||
|
||||
/// Name of the global in the names section.
|
||||
final String? globalName;
|
||||
String? globalName;
|
||||
|
||||
Global(
|
||||
this.enclosingModule, this.finalizableIndex, this.type, this.globalName);
|
||||
Global(this.enclosingModule, this.finalizableIndex, this.type,
|
||||
[this.globalName]);
|
||||
|
||||
@override
|
||||
String toString() => globalName ?? "$finalizableIndex";
|
||||
|
||||
@@ -12,4 +12,10 @@ class Globals {
|
||||
final List<DefinedGlobal> defined;
|
||||
|
||||
Globals(this.imported, this.defined);
|
||||
|
||||
Global operator [](int index) => index < imported.length
|
||||
? imported[index]
|
||||
: defined[index - imported.length];
|
||||
|
||||
int get length => imported.length + defined.length;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,32 @@
|
||||
import '../serialize/serialize.dart';
|
||||
import 'ir.dart';
|
||||
|
||||
class Imports {
|
||||
late final List<Import> all;
|
||||
|
||||
final List<ImportedFunction> functions;
|
||||
final List<ImportedTag> tags;
|
||||
final List<ImportedGlobal> globals;
|
||||
final List<ImportedTable> tables;
|
||||
final List<ImportedMemory> memories;
|
||||
|
||||
Imports(this.functions, this.tags, this.globals, this.tables, this.memories) {
|
||||
all = [
|
||||
...functions,
|
||||
...tags,
|
||||
...globals,
|
||||
...tables,
|
||||
...memories,
|
||||
];
|
||||
}
|
||||
|
||||
Imports.deserialized(this.all, this.functions, this.tags, this.globals,
|
||||
this.tables, this.memories) {
|
||||
assert(all.length ==
|
||||
(functions.length + tags.length + globals.length + tables.length));
|
||||
}
|
||||
}
|
||||
|
||||
/// Any import (function, table, memory or global).
|
||||
abstract class Import implements Indexable, Serializable {
|
||||
String get module;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,4 +68,41 @@ class Instructions implements Serializable {
|
||||
|
||||
s.sourceMapSerializer.addMapping(s.offset, null);
|
||||
}
|
||||
|
||||
static Instructions deserializeConst(
|
||||
Deserializer d,
|
||||
Types types,
|
||||
Functions functions,
|
||||
Globals globals,
|
||||
) {
|
||||
final instructions = <Instruction>[];
|
||||
while (true) {
|
||||
final instruction =
|
||||
Instruction.deserializeConst(d, types, functions, globals);
|
||||
instructions.add(instruction);
|
||||
if (instruction is End) break;
|
||||
}
|
||||
return Instructions([], {}, instructions, null, [], null);
|
||||
}
|
||||
|
||||
static Instructions deserialize(
|
||||
Deserializer d,
|
||||
Module module,
|
||||
Types types,
|
||||
Functions functions,
|
||||
Tables tables,
|
||||
Memories memories,
|
||||
Tags tags,
|
||||
Globals globals,
|
||||
DataSegments dataSegments,
|
||||
) {
|
||||
final instructions = <Instruction>[];
|
||||
while (true) {
|
||||
final instruction = Instruction.deserialize(
|
||||
d, types, tables, tags, globals, dataSegments, memories, functions);
|
||||
instructions.add(instruction);
|
||||
if (instruction is End) break;
|
||||
}
|
||||
return Instructions([], {}, instructions, null, [], null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ export 'data_segment.dart' show BaseDataSegment, DataSegment;
|
||||
export 'exports.dart' show Export, Exportable, Exports;
|
||||
export 'finalizable.dart' show Finalizable, FinalizableIndex;
|
||||
export 'indexable.dart' show Indexable;
|
||||
export 'imports.dart' show Import;
|
||||
export 'imports.dart' show Import, Imports;
|
||||
export 'globals.dart' show Globals;
|
||||
export 'global.dart' show DefinedGlobal, Global, ImportedGlobal;
|
||||
export 'global.dart' show DefinedGlobal, Global, ImportedGlobal, GlobalExport;
|
||||
export 'functions.dart' show Functions;
|
||||
export 'function.dart'
|
||||
show BaseFunction, DefinedFunction, ImportedFunction, Local;
|
||||
show BaseFunction, DefinedFunction, ImportedFunction, Local, FunctionExport;
|
||||
export 'memories.dart' show Memories;
|
||||
export 'memory.dart' show DefinedMemory, ImportedMemory, Memory;
|
||||
export 'memory.dart' show DefinedMemory, ImportedMemory, Memory, MemoryExport;
|
||||
export 'module.dart' show Module;
|
||||
export 'tables.dart' show Tables;
|
||||
export 'table.dart' show DefinedTable, ImportedTable, Table;
|
||||
export 'tags.dart' show DefinedTag, ImportedTag, Tag, Tags;
|
||||
export 'table.dart' show DefinedTable, ImportedTable, Table, TableExport;
|
||||
export 'tags.dart' show DefinedTag, ImportedTag, Tag, Tags, TagExport;
|
||||
export 'types.dart' show Types;
|
||||
export 'instructions.dart' show Instructions;
|
||||
export 'instruction.dart';
|
||||
|
||||
@@ -12,4 +12,8 @@ class Memories {
|
||||
final List<DefinedMemory> defined;
|
||||
|
||||
Memories(this.imported, this.defined);
|
||||
|
||||
Memory operator [](int index) => index < imported.length
|
||||
? imported[index]
|
||||
: defined[index - imported.length];
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ class Module implements Serializable {
|
||||
// module with the constitutents.
|
||||
bool _initialized = false;
|
||||
|
||||
late final String _moduleName;
|
||||
late final String? _moduleName;
|
||||
late final Functions _functions;
|
||||
late final BaseFunction? _start;
|
||||
late final Tables _tables;
|
||||
late final Tags _tags;
|
||||
late final Memories _memories;
|
||||
@@ -30,15 +31,16 @@ class Module implements Serializable {
|
||||
late final Globals _globals;
|
||||
late final Types _types;
|
||||
late final DataSegments _dataSegments;
|
||||
late final List<Import> _imports;
|
||||
late final Imports _imports;
|
||||
late final List<int> _watchPoints;
|
||||
late final Uri? _sourceMapUrl;
|
||||
|
||||
Module.uninitialized() : _initialized = false;
|
||||
|
||||
void initialize(
|
||||
String moduleName,
|
||||
String? moduleName,
|
||||
Functions functions,
|
||||
BaseFunction? start,
|
||||
Tables tables,
|
||||
Tags tags,
|
||||
Memories memories,
|
||||
@@ -46,7 +48,7 @@ class Module implements Serializable {
|
||||
Globals globals,
|
||||
Types types,
|
||||
DataSegments dataSegments,
|
||||
List<Import> imports,
|
||||
Imports imports,
|
||||
List<int> watchPoints,
|
||||
Uri? sourceMapUrl,
|
||||
) {
|
||||
@@ -55,6 +57,7 @@ class Module implements Serializable {
|
||||
_initialized = true;
|
||||
_moduleName = moduleName;
|
||||
_functions = functions;
|
||||
_start = start;
|
||||
_tables = tables;
|
||||
_tags = tags;
|
||||
_memories = memories;
|
||||
@@ -67,8 +70,9 @@ class Module implements Serializable {
|
||||
_sourceMapUrl = sourceMapUrl;
|
||||
}
|
||||
|
||||
String get moduleName => _moduleName;
|
||||
String? get moduleName => _moduleName;
|
||||
Functions get functions => _functions;
|
||||
BaseFunction? get start => _start;
|
||||
Tables get tables => _tables;
|
||||
Tags get tags => _tags;
|
||||
Memories get memories => _memories;
|
||||
@@ -76,7 +80,7 @@ class Module implements Serializable {
|
||||
Globals get globals => _globals;
|
||||
Types get types => _types;
|
||||
DataSegments get dataSegments => _dataSegments;
|
||||
List<Import> get imports => _imports;
|
||||
Imports get imports => _imports;
|
||||
List<int> get watchPoints => _watchPoints;
|
||||
Uri? get sourceMapUrl => _sourceMapUrl;
|
||||
|
||||
@@ -97,7 +101,7 @@ class Module implements Serializable {
|
||||
TagSection(tags.defined, watchPoints).serialize(s);
|
||||
GlobalSection(globals.defined, watchPoints).serialize(s);
|
||||
ExportSection(exports.exported, watchPoints).serialize(s);
|
||||
StartSection(functions.start, watchPoints).serialize(s);
|
||||
StartSection(start, watchPoints).serialize(s);
|
||||
ElementSection(
|
||||
tables.defined, tables.imported, functions.declared, watchPoints)
|
||||
.serialize(s);
|
||||
@@ -113,4 +117,120 @@ class Module implements Serializable {
|
||||
.serialize(s);
|
||||
SourceMapSection(sourceMapUrl).serialize(s);
|
||||
}
|
||||
|
||||
static Module deserialize(Deserializer d) {
|
||||
final preamble = d.readBytes(8);
|
||||
if (preamble[0] != 0x00 ||
|
||||
preamble[1] != 0x61 ||
|
||||
preamble[2] != 0x73 ||
|
||||
preamble[3] != 0x6D ||
|
||||
preamble[4] != 0x01 ||
|
||||
preamble[5] != 0x00 ||
|
||||
preamble[6] != 0x00 ||
|
||||
preamble[7] != 0x00) {
|
||||
throw 'Invalid Wasm preamble';
|
||||
}
|
||||
|
||||
// Although we expect sections in a specific order, we discover all of them
|
||||
// here. This makes the code below that handles the presence/absense of a
|
||||
// section easier.
|
||||
final sections = <int, List<Deserializer>>{};
|
||||
final customSections = <String, List<Deserializer>>{};
|
||||
while (!d.isAtEnd) {
|
||||
final id = d.readByte();
|
||||
final size = d.readUnsigned();
|
||||
final deserializer = Deserializer(d.readBytes(size));
|
||||
|
||||
if (id == CustomSection.sectionId) {
|
||||
// Custom section
|
||||
final name = deserializer.readName();
|
||||
customSections.putIfAbsent(name, () => []).add(deserializer);
|
||||
} else {
|
||||
sections.putIfAbsent(id, () => []).add(deserializer);
|
||||
}
|
||||
}
|
||||
|
||||
final Module module = Module.uninitialized();
|
||||
|
||||
// We read the sections in the order they should be in the binary.
|
||||
|
||||
final typeSections = sections[TypeSection.sectionId];
|
||||
final types = TypeSection.deserialize(typeSections?.single);
|
||||
|
||||
final importSections = sections[ImportSection.sectionId];
|
||||
final imports =
|
||||
ImportSection.deserialize(importSections?.single, module, types);
|
||||
|
||||
final functionSections = sections[FunctionSection.sectionId];
|
||||
final functions = FunctionSection.deserialize(
|
||||
functionSections?.single, module, types, imports.functions);
|
||||
|
||||
final tablesSections = sections[TableSection.sectionId];
|
||||
final tables = TableSection.deserialize(
|
||||
tablesSections?.single, module, types, imports.tables);
|
||||
|
||||
final memorySections = sections[MemorySection.sectionId];
|
||||
final memories = MemorySection.deserialize(
|
||||
memorySections?.single, module, imports.memories);
|
||||
|
||||
final tagSections = sections[TagSection.sectionId];
|
||||
final tags = TagSection.deserialize(
|
||||
tagSections?.single, module, types, imports.tags);
|
||||
|
||||
final globalSections = sections[GlobalSection.sectionId];
|
||||
final globals = GlobalSection.deserialize(
|
||||
globalSections?.single, module, types, functions, imports.globals);
|
||||
|
||||
final exportSections = sections[ExportSection.sectionId];
|
||||
final exports = ExportSection.deserialize(
|
||||
exportSections?.single, functions, tables, memories, globals, tags);
|
||||
|
||||
final startFunctionSections = sections[StartSection.sectionId];
|
||||
final start =
|
||||
StartSection.deserialize(startFunctionSections?.single, functions);
|
||||
|
||||
final elementSections = sections[ElementSection.sectionId];
|
||||
// As side-effect initializes [Table.elements]
|
||||
// As side-effect initializes [ImprotedTable.setElements]
|
||||
// As side-effect initializes [Functions.declaredFunctions]
|
||||
ElementSection.deserialize(
|
||||
elementSections?.single, module, types, functions, tables, globals);
|
||||
|
||||
final dataCountSections = sections[DataCountSection.sectionId];
|
||||
final dataSegments =
|
||||
DataCountSection.deserialize(dataCountSections?.single);
|
||||
|
||||
final codeSections = sections[CodeSection.sectionId];
|
||||
CodeSection.deserialize(codeSections?.single, functions.defined, module,
|
||||
types, functions, tables, memories, tags, globals, dataSegments);
|
||||
|
||||
final dataSections = sections[DataSection.sectionId];
|
||||
// As side-effect initializes [dataSegments.defined]
|
||||
DataSection.deserialize(dataSections?.single, dataSegments, memories);
|
||||
|
||||
final moduleName = NameSection.deserialize(
|
||||
customSections[NameSection.customSectionName]?.single,
|
||||
functions,
|
||||
types,
|
||||
globals);
|
||||
final sourceMapUrl = SourceMapSection.deserialize(
|
||||
customSections[SourceMapSection.customSectionName]?.single);
|
||||
|
||||
return module
|
||||
..initialize(
|
||||
moduleName ?? '',
|
||||
functions,
|
||||
start,
|
||||
tables,
|
||||
tags,
|
||||
memories,
|
||||
exports,
|
||||
globals,
|
||||
types,
|
||||
dataSegments,
|
||||
imports,
|
||||
[],
|
||||
sourceMapUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,8 @@ class Tables {
|
||||
final List<DefinedTable> defined;
|
||||
|
||||
Tables(this.imported, this.defined);
|
||||
|
||||
Table operator [](int index) => index < imported.length
|
||||
? imported[index]
|
||||
: defined[index - imported.length];
|
||||
}
|
||||
|
||||
@@ -80,4 +80,8 @@ class Tags {
|
||||
final List<ImportedTag> imported;
|
||||
|
||||
Tags(this.defined, this.imported);
|
||||
|
||||
Tag operator [](int index) => index < imported.length
|
||||
? imported[index]
|
||||
: defined[index - imported.length];
|
||||
}
|
||||
|
||||
@@ -21,6 +21,17 @@ abstract class StorageType implements Serializable {
|
||||
|
||||
/// For primitive types: the size in bytes of a value of this type.
|
||||
int get byteSize;
|
||||
|
||||
static StorageType deserialize(Deserializer d, List<DefType> types) {
|
||||
final code = d.peekByte();
|
||||
switch (code) {
|
||||
case 0x78: // -0x8
|
||||
case 0x77: // -0x9
|
||||
return PackedType.deserialize(d);
|
||||
default:
|
||||
return ValueType.deserialize(d, types);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A *value type*.
|
||||
@@ -51,6 +62,20 @@ abstract class ValueType implements StorageType {
|
||||
/// Used by the type builder to determine the set of [DefType]s referenced in
|
||||
/// a module.
|
||||
DefType? get containedDefType => null;
|
||||
|
||||
static ValueType deserialize(Deserializer d, List<DefType> types) {
|
||||
final code = d.peekByte();
|
||||
switch (code) {
|
||||
case 0x7F: // -0x01
|
||||
case 0x7E: // -0x02
|
||||
case 0x7D: // -0x03
|
||||
case 0x7C: // -0x04
|
||||
case 0x7B: // -0x05
|
||||
return NumType.deserialize(d);
|
||||
default:
|
||||
return RefType.deserialize(d, types);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NumTypeKind { i32, i64, f32, f64, v128 }
|
||||
@@ -117,6 +142,24 @@ class NumType extends ValueType {
|
||||
}
|
||||
}
|
||||
|
||||
static NumType deserialize(Deserializer d) {
|
||||
final code = d.readByte();
|
||||
switch (code) {
|
||||
case 0x7F: // -0x01
|
||||
return i32;
|
||||
case 0x7E: // -0x02
|
||||
return i64;
|
||||
case 0x7D: // -0x03
|
||||
return f32;
|
||||
case 0x7C: // -0x04
|
||||
return f64;
|
||||
case 0x7B: // -0x05
|
||||
return v128;
|
||||
default:
|
||||
throw "Invalid NumType code: $code";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (kind) {
|
||||
@@ -226,6 +269,30 @@ class RefType extends ValueType {
|
||||
s.write(heapType);
|
||||
}
|
||||
|
||||
static RefType deserialize(Deserializer d, List<DefType> types) {
|
||||
final code = d.peekByte();
|
||||
bool nullable;
|
||||
HeapType heapType;
|
||||
switch (code) {
|
||||
case 0x63: // -0x1d
|
||||
d.readByte();
|
||||
nullable = true;
|
||||
heapType = HeapType.deserialize(d, types);
|
||||
break;
|
||||
case 0x64: // -0x1c
|
||||
d.readByte();
|
||||
nullable = false;
|
||||
heapType = HeapType.deserialize(d, types);
|
||||
break;
|
||||
default:
|
||||
heapType = HeapType.deserialize(d, types);
|
||||
nullable = heapType.nullableByDefault!;
|
||||
assert(heapType is! UnresolvedDefType);
|
||||
break;
|
||||
}
|
||||
return RefType._(heapType, nullable);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (nullable == heapType.nullableByDefault) {
|
||||
@@ -306,6 +373,46 @@ abstract class HeapType implements Serializable {
|
||||
bool isStructuralSubtypeOf(HeapType other) => isSubtypeOf(other);
|
||||
|
||||
String get shorthandName => toString();
|
||||
|
||||
static HeapType deserialize(Deserializer d, List<DefType> types) {
|
||||
final code = d.readSigned();
|
||||
if (code >= 0) {
|
||||
if (code < types.length) {
|
||||
return types[code];
|
||||
}
|
||||
// This happens in wasm type section reading if circular types are
|
||||
// involved.
|
||||
return UnresolvedDefType(code);
|
||||
}
|
||||
switch (code) {
|
||||
case -0x11: // 0x6F
|
||||
return extern;
|
||||
case -0x12: // 0x6E
|
||||
return any;
|
||||
case -0x13: // 0x6D
|
||||
return eq;
|
||||
case -0x10: // 0x70
|
||||
return func;
|
||||
case -0x15: // 0x6B
|
||||
return struct;
|
||||
case -0x16: // 0x6A
|
||||
return array;
|
||||
case -0x14: // 0x6C
|
||||
return i31;
|
||||
case -0x0f: // 0x71
|
||||
return none;
|
||||
case -0x0e: // 0x72
|
||||
return noextern;
|
||||
case -0x0d: // 0x73
|
||||
return nofunc;
|
||||
case -0x17: // 0x69
|
||||
return exn;
|
||||
case -0x0c: // 0x74
|
||||
return noexn;
|
||||
default:
|
||||
throw "Invalid heap type code: $code";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal supertype above any, func and extern. This is only used to specify
|
||||
@@ -668,6 +775,137 @@ abstract class DefType extends HeapType {
|
||||
|
||||
// Serialize the type for the type section, excluding supertype references.
|
||||
void serializeDefinitionInner(Serializer s);
|
||||
|
||||
static DefType deserializeAllocate(Deserializer d, List<DefType> existing) {
|
||||
final code = d.peekByte();
|
||||
DefType? superType;
|
||||
bool hasSubtypes;
|
||||
switch (code) {
|
||||
case 0x50: // -0x30
|
||||
d.readByte();
|
||||
hasSubtypes = true;
|
||||
final count = d.readUnsigned();
|
||||
if (count == 1) {
|
||||
final superTypeIndex = d.readUnsigned();
|
||||
superType = existing[superTypeIndex];
|
||||
} else {
|
||||
assert(count == 0);
|
||||
}
|
||||
break;
|
||||
case 0x4F: // -0x31
|
||||
d.readByte();
|
||||
hasSubtypes = false;
|
||||
final count = d.readUnsigned();
|
||||
if (count == 1) {
|
||||
final superTypeIndex = d.readUnsigned();
|
||||
superType = existing[superTypeIndex];
|
||||
} else {
|
||||
assert(count == 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
hasSubtypes = false;
|
||||
break;
|
||||
}
|
||||
final code2 = d.readByte();
|
||||
DefType result;
|
||||
switch (code2) {
|
||||
case 0x60: // -0x20
|
||||
result = FunctionType.deserializeAllocate(d, superType, existing);
|
||||
case 0x5F: // -0x21
|
||||
result = StructType.deserializeAllocate(d, superType, existing);
|
||||
case 0x5E: // -0x22
|
||||
result = ArrayType.deserializeAllocate(d, superType, existing);
|
||||
default:
|
||||
throw "Invalid DefType code: $code2";
|
||||
}
|
||||
result.hasAnySubtypes = hasSubtypes;
|
||||
return result;
|
||||
}
|
||||
|
||||
void deserializeFill(Deserializer d, List<DefType> existing) {
|
||||
final code = d.peekByte();
|
||||
DefType? superType;
|
||||
bool hasSubtypes;
|
||||
switch (code) {
|
||||
case 0x50: // -0x30
|
||||
d.readByte();
|
||||
hasSubtypes = true;
|
||||
final count = d.readUnsigned();
|
||||
if (count == 1) {
|
||||
final superTypeIndex = d.readUnsigned();
|
||||
superType = existing[superTypeIndex];
|
||||
} else {
|
||||
assert(count == 0);
|
||||
}
|
||||
break;
|
||||
case 0x4F: // -0x31
|
||||
d.readByte();
|
||||
hasSubtypes = false;
|
||||
final count = d.readUnsigned();
|
||||
if (count == 1) {
|
||||
final superTypeIndex = d.readUnsigned();
|
||||
superType = existing[superTypeIndex];
|
||||
} else {
|
||||
assert(count == 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
hasSubtypes = false;
|
||||
break;
|
||||
}
|
||||
if (!identical(superType, this.superType) ||
|
||||
hasSubtypes != hasAnySubtypes) {
|
||||
throw 'Mismatch between Allocate+Fill implementation.';
|
||||
}
|
||||
final code2 = d.readByte();
|
||||
switch (code2) {
|
||||
case 0x60: // -0x20
|
||||
assert(this is FunctionType);
|
||||
case 0x5F: // -0x21
|
||||
assert(this is StructType);
|
||||
case 0x5E: // -0x22
|
||||
assert(this is ArrayType);
|
||||
default:
|
||||
throw "Invalid DefType code: $code2";
|
||||
}
|
||||
deserializeFillInner(d, existing);
|
||||
}
|
||||
|
||||
void deserializeFillInner(Deserializer d, List<DefType> existing);
|
||||
}
|
||||
|
||||
class UnresolvedDefType extends DefType {
|
||||
final int typeIndex;
|
||||
|
||||
UnresolvedDefType(this.typeIndex);
|
||||
|
||||
@override
|
||||
bool get nullableByDefault =>
|
||||
throw 'Cannot obtain nullableByDefault of unresolved type';
|
||||
|
||||
@override
|
||||
HeapType get abstractSuperType =>
|
||||
throw 'Cannot obtain abstractSuperType of unresolved type';
|
||||
|
||||
@override
|
||||
Iterable<StorageType> get constituentTypes =>
|
||||
throw 'Cannot obtain constituentTypes of unresolved type';
|
||||
|
||||
@override
|
||||
HeapType get topType => throw 'Cannot obtain topType of unresolved type';
|
||||
|
||||
@override
|
||||
HeapType get bottomType =>
|
||||
throw 'Cannot obtain bottomType of unresolved type';
|
||||
|
||||
@override
|
||||
void serializeDefinitionInner(Serializer s) =>
|
||||
throw 'Cannot serialize unresolved type';
|
||||
|
||||
@override
|
||||
void deserializeFillInner(Deserializer d, List<DefType> existing) =>
|
||||
throw 'Cannot deserialize unresolved type';
|
||||
}
|
||||
|
||||
/// The `exn` heap type.
|
||||
@@ -779,6 +1017,19 @@ class FunctionType extends DefType {
|
||||
s.writeList(outputs);
|
||||
}
|
||||
|
||||
static FunctionType deserializeAllocate(
|
||||
Deserializer d, DefType? superType, List<DefType> existing) {
|
||||
d.readList((d) => ValueType.deserialize(d, existing));
|
||||
d.readList((d) => ValueType.deserialize(d, existing));
|
||||
return FunctionType([], [], superType: superType);
|
||||
}
|
||||
|
||||
@override
|
||||
void deserializeFillInner(Deserializer d, List<DefType> existing) {
|
||||
inputs.addAll(d.readList((d) => ValueType.deserialize(d, existing)));
|
||||
outputs.addAll(d.readList((d) => ValueType.deserialize(d, existing)));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "(${inputs.join(", ")}) -> (${outputs.join(", ")})";
|
||||
}
|
||||
@@ -851,6 +1102,17 @@ class StructType extends DataType {
|
||||
s.writeByte(0x5F); // -0x21
|
||||
s.writeList(fields);
|
||||
}
|
||||
|
||||
static StructType deserializeAllocate(
|
||||
Deserializer d, DefType? superType, List<DefType> existing) {
|
||||
d.readList((d) => FieldType.deserialize(d, existing));
|
||||
return StructType(null, fields: [], superType: superType);
|
||||
}
|
||||
|
||||
@override
|
||||
void deserializeFillInner(Deserializer d, List<DefType> existing) {
|
||||
fields.addAll(d.readList((d) => FieldType.deserialize(d, existing)));
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom `array` type.
|
||||
@@ -884,6 +1146,17 @@ class ArrayType extends DataType {
|
||||
s.writeByte(0x5E); // -0x22
|
||||
s.write(elementType);
|
||||
}
|
||||
|
||||
static ArrayType deserializeAllocate(
|
||||
Deserializer d, DefType? superType, List<DefType> existing) {
|
||||
FieldType.deserialize(d, existing);
|
||||
return ArrayType(null, elementType: null, superType: superType);
|
||||
}
|
||||
|
||||
@override
|
||||
void deserializeFillInner(Deserializer d, List<DefType> existing) {
|
||||
elementType = FieldType.deserialize(d, existing);
|
||||
}
|
||||
}
|
||||
|
||||
class _WithMutability<T extends StorageType> implements Serializable {
|
||||
@@ -898,6 +1171,13 @@ class _WithMutability<T extends StorageType> implements Serializable {
|
||||
s.writeByte(mutable ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
static (T, bool) deserialize<T extends StorageType>(
|
||||
Deserializer d, T Function(Deserializer) fun) {
|
||||
final type = fun(d);
|
||||
final mutable = d.readByte() == 0x01;
|
||||
return (type, mutable);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "${mutable ? "var " : "const "}$type";
|
||||
}
|
||||
@@ -907,6 +1187,12 @@ class _WithMutability<T extends StorageType> implements Serializable {
|
||||
/// It consists of a type and a mutability.
|
||||
class GlobalType extends _WithMutability<ValueType> {
|
||||
GlobalType(super.type, {super.mutable = true});
|
||||
|
||||
static GlobalType deserialize(Deserializer d, List<DefType> types) {
|
||||
final (type, mutable) =
|
||||
_WithMutability.deserialize(d, (d) => ValueType.deserialize(d, types));
|
||||
return GlobalType(type, mutable: mutable);
|
||||
}
|
||||
}
|
||||
|
||||
/// A type for a struct field or an array element.
|
||||
@@ -931,6 +1217,12 @@ class FieldType extends _WithMutability<StorageType> {
|
||||
return type.isSubtypeOf(other.type);
|
||||
}
|
||||
}
|
||||
|
||||
static FieldType deserialize(Deserializer d, List<DefType> existing) {
|
||||
final (type, mutable) = _WithMutability.deserialize(
|
||||
d, (d) => StorageType.deserialize(d, existing));
|
||||
return FieldType(type, mutable: mutable);
|
||||
}
|
||||
}
|
||||
|
||||
enum PackedTypeKind { i8, i16 }
|
||||
@@ -978,6 +1270,18 @@ class PackedType implements StorageType {
|
||||
}
|
||||
}
|
||||
|
||||
static PackedType deserialize(Deserializer d) {
|
||||
final code = d.readByte();
|
||||
switch (code) {
|
||||
case 0x78: // -0x8
|
||||
return i8;
|
||||
case 0x77: // -0x9
|
||||
return i16;
|
||||
default:
|
||||
throw "Invalid PackedType code: $code";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (kind) {
|
||||
|
||||
@@ -8,5 +8,12 @@ class Types {
|
||||
/// Types defined in this module.
|
||||
final List<List<DefType>> recursionGroups;
|
||||
|
||||
Types(this.recursionGroups);
|
||||
late final List<DefType> defined;
|
||||
|
||||
Types(this.recursionGroups)
|
||||
: defined = recursionGroups.expand((g) => g).toList();
|
||||
|
||||
DefType operator [](int index) => defined[index];
|
||||
|
||||
int get length => defined.length;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2025, 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:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class Deserializer {
|
||||
final Uint8List _data;
|
||||
int offset = 0;
|
||||
|
||||
Deserializer(this._data);
|
||||
|
||||
int get length => _data.length;
|
||||
|
||||
bool get isAtEnd => offset >= _data.length;
|
||||
|
||||
int readByte() {
|
||||
return _data[offset++];
|
||||
}
|
||||
|
||||
int peekByte() {
|
||||
return _data[offset];
|
||||
}
|
||||
|
||||
Uint8List readBytes(int length) {
|
||||
final bytes = Uint8List.sublistView(_data, offset, offset + length);
|
||||
offset += length;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int readSigned() {
|
||||
int result = 0;
|
||||
int shift = 0;
|
||||
int byte;
|
||||
do {
|
||||
byte = readByte();
|
||||
result |= (byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while ((byte & 0x80) != 0);
|
||||
|
||||
if ((shift < 64) && ((byte & 0x40) != 0)) {
|
||||
result |= (~0 << shift);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int readUnsigned() {
|
||||
int result = 0;
|
||||
int shift = 0;
|
||||
int byte;
|
||||
do {
|
||||
byte = readByte();
|
||||
result |= (byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while ((byte & 0x80) != 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
double readF32() {
|
||||
final bd = ByteData.sublistView(_data, offset, offset + 4);
|
||||
offset += 4;
|
||||
return bd.getFloat32(0, Endian.little);
|
||||
}
|
||||
|
||||
double readF64() {
|
||||
final bd = ByteData.sublistView(_data, offset, offset + 8);
|
||||
offset += 8;
|
||||
return bd.getFloat64(0, Endian.little);
|
||||
}
|
||||
|
||||
String readName() {
|
||||
final length = readUnsigned();
|
||||
return utf8.decode(readBytes(length));
|
||||
}
|
||||
|
||||
List<T> readList<T>(T Function(Deserializer) fun) {
|
||||
final length = readUnsigned();
|
||||
final list = <T>[];
|
||||
for (int i = 0; i < length; i++) {
|
||||
list.add(fun(this));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import '../ir/ir.dart' as ir;
|
||||
import 'deserializer.dart';
|
||||
import 'serializer.dart';
|
||||
|
||||
abstract class Section implements Serializable {
|
||||
@@ -30,6 +31,8 @@ abstract class Section implements Serializable {
|
||||
}
|
||||
|
||||
class TypeSection extends Section {
|
||||
static const sectionId = 1;
|
||||
|
||||
final ir.Types types;
|
||||
|
||||
TypeSection(this.types, super.watchPoints);
|
||||
@@ -37,68 +40,187 @@ class TypeSection extends Section {
|
||||
List<List<ir.DefType>> get recursionGroups => types.recursionGroups;
|
||||
|
||||
@override
|
||||
int get id => 1;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
if (types.recursionGroups.isNotEmpty) {
|
||||
s.writeUnsigned(types.recursionGroups.length);
|
||||
int typeIndex = 0;
|
||||
if (types.recursionGroups.isEmpty) return;
|
||||
|
||||
// Set all the indices first since types can be referenced before they are
|
||||
// serialized.
|
||||
for (final group in recursionGroups) {
|
||||
assert(group.isNotEmpty, 'Empty groups are not allowed.');
|
||||
s.writeUnsigned(types.recursionGroups.length);
|
||||
int typeIndex = 0;
|
||||
|
||||
for (final type in group) {
|
||||
type.index = typeIndex++;
|
||||
}
|
||||
}
|
||||
for (final group in recursionGroups) {
|
||||
s.writeByte(0x4E); // -0x32
|
||||
s.writeUnsigned(group.length);
|
||||
for (final type in group) {
|
||||
assert(
|
||||
type.superType == null ||
|
||||
type.superType!.index <= group.last.index,
|
||||
"Type '$type' has a supertype in a later recursion group");
|
||||
assert(
|
||||
type.constituentTypes
|
||||
.whereType<ir.RefType>()
|
||||
.map((t) => t.heapType)
|
||||
.whereType<ir.DefType>()
|
||||
.every((d) => d.index <= group.last.index),
|
||||
"Type '$type' depends on a type in a later recursion group");
|
||||
type.serializeDefinition(s);
|
||||
}
|
||||
// Set all the indices first since types can be referenced before they are
|
||||
// serialized.
|
||||
for (final group in recursionGroups) {
|
||||
assert(group.isNotEmpty, 'Empty groups are not allowed.');
|
||||
|
||||
for (final type in group) {
|
||||
type.index = typeIndex++;
|
||||
}
|
||||
}
|
||||
for (final group in recursionGroups) {
|
||||
if (group.length > 1) {
|
||||
s.writeByte(0x4E); // -0x32
|
||||
s.writeUnsigned(group.length);
|
||||
}
|
||||
for (final type in group) {
|
||||
assert(
|
||||
type.superType == null || type.superType!.index <= group.last.index,
|
||||
"Type '$type' has a supertype in a later recursion group");
|
||||
assert(
|
||||
type.constituentTypes
|
||||
.whereType<ir.RefType>()
|
||||
.map((t) => t.heapType)
|
||||
.whereType<ir.DefType>()
|
||||
.every((d) => d.index <= group.last.index),
|
||||
"Type '$type' depends on a type in a later recursion group");
|
||||
type.serializeDefinition(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Types deserialize(Deserializer? d) {
|
||||
if (d == null) {
|
||||
return ir.Types([]);
|
||||
}
|
||||
|
||||
final List<ir.DefType> definedTypes = [];
|
||||
final List<List<ir.DefType>> recursionGroups = [];
|
||||
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
late int recursionGroupMemberCount;
|
||||
if (d.peekByte() == 0x4E) {
|
||||
d.readByte();
|
||||
// We may have more than one type in the recursion group.
|
||||
recursionGroupMemberCount = d.readUnsigned();
|
||||
} else {
|
||||
// Old type encoding. The type becomes it's own recursion group.
|
||||
recursionGroupMemberCount = 1;
|
||||
}
|
||||
|
||||
// As types can form cycles within a recursion group, we construct them in
|
||||
// two phases:
|
||||
//
|
||||
// 1) allocate the type objects and fixed parts of them
|
||||
// 2) fill in the composite type references
|
||||
//
|
||||
// So for example we'd create a [ir.StructType] in phase 1) and then in
|
||||
// phase 2) we'd populate the struct field types.
|
||||
final typesInGroup = <ir.DefType>[];
|
||||
final startOffset = d.offset;
|
||||
for (int j = 0; j < recursionGroupMemberCount; j++) {
|
||||
final type = ir.DefType.deserializeAllocate(d, definedTypes);
|
||||
typesInGroup.add(type);
|
||||
definedTypes.add(type);
|
||||
}
|
||||
d.offset = startOffset;
|
||||
for (int j = 0; j < recursionGroupMemberCount; j++) {
|
||||
typesInGroup[j].deserializeFill(d, definedTypes);
|
||||
}
|
||||
recursionGroups.add(typesInGroup);
|
||||
}
|
||||
return ir.Types(recursionGroups);
|
||||
}
|
||||
}
|
||||
|
||||
class ImportSection extends Section {
|
||||
final List<ir.Import> imports;
|
||||
static const int sectionId = 2;
|
||||
|
||||
final ir.Imports imports;
|
||||
|
||||
ImportSection(this.imports, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 2;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
if (imports.isNotEmpty) {
|
||||
s.writeList(imports);
|
||||
if (imports.all.isNotEmpty) {
|
||||
s.writeList(imports.all);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Imports deserialize(
|
||||
Deserializer? d, ir.Module module, ir.Types types) {
|
||||
final imports = <ir.Import>[];
|
||||
final importedMemories = <ir.ImportedMemory>[];
|
||||
final importedGlobals = <ir.ImportedGlobal>[];
|
||||
final importedTags = <ir.ImportedTag>[];
|
||||
final importedTables = <ir.ImportedTable>[];
|
||||
final importedFunctions = <ir.ImportedFunction>[];
|
||||
|
||||
if (d != null) {
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final moduleName = d.readName();
|
||||
final name = d.readName();
|
||||
final kind = d.readByte();
|
||||
switch (kind) {
|
||||
case 0x00: // Function
|
||||
final typeIndex = d.readUnsigned();
|
||||
final type = types[typeIndex] as ir.FunctionType;
|
||||
final import = ir.ImportedFunction(
|
||||
module, moduleName, name, ir.FinalizableIndex(), type);
|
||||
import.finalizableIndex.value = importedFunctions.length;
|
||||
importedFunctions.add(import);
|
||||
imports.add(import);
|
||||
break;
|
||||
case 0x01: // Table
|
||||
final type = ir.RefType.deserialize(d, types.defined);
|
||||
final limits = d.readByte();
|
||||
final minSize = d.readUnsigned();
|
||||
final maxSize = limits == 0x01 ? d.readUnsigned() : null;
|
||||
final import = ir.ImportedTable(module, moduleName, name,
|
||||
ir.FinalizableIndex(), type, minSize, maxSize);
|
||||
import.finalizableIndex.value = importedTables.length;
|
||||
importedTables.add(import);
|
||||
imports.add(import);
|
||||
break;
|
||||
case 0x02: // Memory
|
||||
final limits = d.readByte();
|
||||
final shared = limits == 0x03;
|
||||
final minSize = d.readUnsigned();
|
||||
final maxSize =
|
||||
limits == 0x01 || limits == 0x03 ? d.readUnsigned() : null;
|
||||
final import = ir.ImportedMemory(module, moduleName, name,
|
||||
ir.FinalizableIndex(), shared, minSize, maxSize);
|
||||
import.finalizableIndex.value = importedMemories.length;
|
||||
importedMemories.add(import);
|
||||
imports.add(import);
|
||||
break;
|
||||
case 0x03: // Global
|
||||
final type = ir.GlobalType.deserialize(d, types.defined);
|
||||
final import = ir.ImportedGlobal(
|
||||
module, moduleName, name, ir.FinalizableIndex(), type);
|
||||
import.finalizableIndex.value = importedGlobals.length;
|
||||
importedGlobals.add(import);
|
||||
imports.add(import);
|
||||
break;
|
||||
case 0x04: // Tag
|
||||
final exceptionByte = d.readByte();
|
||||
if (exceptionByte != 0x00) throw 'unexpected';
|
||||
d.readUnsigned(); // typeIndex
|
||||
throw 'runtimeType';
|
||||
default:
|
||||
throw "Invalid import kind: $kind";
|
||||
}
|
||||
}
|
||||
}
|
||||
return ir.Imports.deserialized(imports, importedFunctions, importedTags,
|
||||
importedGlobals, importedTables, importedMemories);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionSection extends Section {
|
||||
static const int sectionId = 3;
|
||||
|
||||
final List<ir.DefinedFunction> functions;
|
||||
|
||||
FunctionSection(this.functions, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 3;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -109,15 +231,35 @@ class FunctionSection extends Section {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Functions deserialize(Deserializer? d, ir.Module module,
|
||||
ir.Types types, List<ir.ImportedFunction> imported) {
|
||||
if (d == null) {
|
||||
return ir.Functions.withoutDeclared(imported, []);
|
||||
}
|
||||
|
||||
final List<ir.DefinedFunction> defined = [];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final typeIndex = d.readUnsigned();
|
||||
final type = types[typeIndex] as ir.FunctionType;
|
||||
final function = ir.DefinedFunction.withoutBody(
|
||||
module, ir.FinalizableIndex()..value = imported.length + i, type);
|
||||
defined.add(function);
|
||||
}
|
||||
return ir.Functions.withoutDeclared(imported, defined);
|
||||
}
|
||||
}
|
||||
|
||||
class TableSection extends Section {
|
||||
static const int sectionId = 4;
|
||||
|
||||
final List<ir.DefinedTable> tables;
|
||||
|
||||
TableSection(this.tables, super.watchPOints);
|
||||
|
||||
@override
|
||||
int get id => 4;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -125,15 +267,40 @@ class TableSection extends Section {
|
||||
s.writeList(tables);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Tables deserialize(Deserializer? d, ir.Module module,
|
||||
ir.Types types, List<ir.ImportedTable> imported) {
|
||||
if (d == null) return ir.Tables(imported, []);
|
||||
|
||||
final defined = <ir.DefinedTable>[];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final type = ir.RefType.deserialize(d, types.defined);
|
||||
final limits = d.readByte();
|
||||
final minSize = d.readUnsigned();
|
||||
final maxSize = limits == 0x01 ? d.readUnsigned() : null;
|
||||
final table = ir.DefinedTable(
|
||||
module,
|
||||
[],
|
||||
ir.FinalizableIndex()..value = imported.length + i,
|
||||
type,
|
||||
minSize,
|
||||
maxSize);
|
||||
defined.add(table);
|
||||
}
|
||||
return ir.Tables(imported, defined);
|
||||
}
|
||||
}
|
||||
|
||||
class MemorySection extends Section {
|
||||
static const int sectionId = 5;
|
||||
|
||||
final List<ir.DefinedMemory> memories;
|
||||
|
||||
MemorySection(this.memories, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 5;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -141,15 +308,40 @@ class MemorySection extends Section {
|
||||
s.writeList(memories);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Memories deserialize(
|
||||
Deserializer? d, ir.Module module, List<ir.ImportedMemory> imported) {
|
||||
if (d == null) return ir.Memories(imported, []);
|
||||
|
||||
final defined = <ir.DefinedMemory>[];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final limits = d.readByte();
|
||||
final shared = limits == 0x03;
|
||||
final minSize = d.readUnsigned();
|
||||
final maxSize =
|
||||
limits == 0x01 || limits == 0x03 ? d.readUnsigned() : null;
|
||||
final memory = ir.DefinedMemory(
|
||||
module,
|
||||
ir.FinalizableIndex()..value = imported.length + i,
|
||||
shared,
|
||||
minSize,
|
||||
maxSize);
|
||||
defined.add(memory);
|
||||
}
|
||||
return ir.Memories(imported, defined);
|
||||
}
|
||||
}
|
||||
|
||||
class TagSection extends Section {
|
||||
static const int sectionId = 13;
|
||||
|
||||
final List<ir.DefinedTag> tags;
|
||||
|
||||
TagSection(this.tags, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 13;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -157,15 +349,36 @@ class TagSection extends Section {
|
||||
s.writeList(tags);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Tags deserialize(Deserializer? d, ir.Module module, ir.Types types,
|
||||
List<ir.ImportedTag> imported) {
|
||||
if (d == null) return ir.Tags([], imported);
|
||||
|
||||
final defined = <ir.DefinedTag>[];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final attribute = d.readByte();
|
||||
if (attribute != 0) {
|
||||
throw "Invalid tag attribute: $attribute";
|
||||
}
|
||||
final type = types[d.readUnsigned()] as ir.FunctionType;
|
||||
final tag = ir.DefinedTag(
|
||||
module, ir.FinalizableIndex()..value = imported.length + i, type);
|
||||
defined.add(tag);
|
||||
}
|
||||
return ir.Tags(defined, []);
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalSection extends Section {
|
||||
static const int sectionId = 6;
|
||||
|
||||
final List<ir.DefinedGlobal> globals;
|
||||
|
||||
GlobalSection(this.globals, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 6;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -173,15 +386,41 @@ class GlobalSection extends Section {
|
||||
s.writeList(globals);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Globals deserialize(
|
||||
Deserializer? d,
|
||||
ir.Module module,
|
||||
ir.Types types,
|
||||
ir.Functions functions,
|
||||
List<ir.ImportedGlobal> imported) {
|
||||
if (d == null) {
|
||||
return ir.Globals(imported, []);
|
||||
}
|
||||
|
||||
final globals = ir.Globals(imported, []);
|
||||
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final type = ir.GlobalType.deserialize(d, types.defined);
|
||||
final initializer =
|
||||
ir.Instructions.deserializeConst(d, types, functions, globals);
|
||||
final global = ir.DefinedGlobal(module, initializer,
|
||||
ir.FinalizableIndex()..value = globals.length, type);
|
||||
globals.defined.add(global);
|
||||
}
|
||||
return globals;
|
||||
}
|
||||
}
|
||||
|
||||
class ExportSection extends Section {
|
||||
static const int sectionId = 7;
|
||||
|
||||
final List<ir.Export> exports;
|
||||
|
||||
ExportSection(this.exports, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 7;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -189,15 +428,57 @@ class ExportSection extends Section {
|
||||
s.writeList(exports);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.Exports deserialize(
|
||||
Deserializer? d,
|
||||
ir.Functions functions,
|
||||
ir.Tables tables,
|
||||
ir.Memories memories,
|
||||
ir.Globals globals,
|
||||
ir.Tags tags) {
|
||||
if (d == null) {
|
||||
return ir.Exports([]);
|
||||
}
|
||||
|
||||
final exports = <ir.Export>[];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final name = d.readName();
|
||||
final kind = d.readByte();
|
||||
final index = d.readUnsigned();
|
||||
switch (kind) {
|
||||
case 0x00:
|
||||
exports.add(ir.FunctionExport(name, functions[index]));
|
||||
break;
|
||||
case 0x01:
|
||||
exports.add(ir.TableExport(name, tables[index]));
|
||||
break;
|
||||
case 0x02:
|
||||
exports.add(ir.MemoryExport(name, memories[index]));
|
||||
break;
|
||||
case 0x03:
|
||||
exports.add(ir.GlobalExport(name, globals[index]));
|
||||
break;
|
||||
case 0x04:
|
||||
exports.add(ir.TagExport(name, tags[index]));
|
||||
break;
|
||||
default:
|
||||
throw "Invalid export kind: $kind";
|
||||
}
|
||||
}
|
||||
return ir.Exports(exports);
|
||||
}
|
||||
}
|
||||
|
||||
class StartSection extends Section {
|
||||
static const int sectionId = 8;
|
||||
|
||||
final ir.BaseFunction? startFunction;
|
||||
|
||||
StartSection(this.startFunction, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 8;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -205,6 +486,13 @@ class StartSection extends Section {
|
||||
s.writeUnsigned(startFunction!.index);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.BaseFunction? deserialize(Deserializer? d, ir.Functions functions) {
|
||||
if (d == null) {
|
||||
return null;
|
||||
}
|
||||
return functions[d.readUnsigned()];
|
||||
}
|
||||
}
|
||||
|
||||
sealed class _Element implements Serializable {}
|
||||
@@ -262,6 +550,8 @@ class _DeclaredElement implements _Element {
|
||||
}
|
||||
|
||||
class ElementSection extends Section {
|
||||
static const int sectionId = 9;
|
||||
|
||||
final List<ir.DefinedTable> definedTables;
|
||||
final List<ir.ImportedTable> importedTables;
|
||||
final List<ir.BaseFunction> declaredFunctions;
|
||||
@@ -270,7 +560,7 @@ class ElementSection extends Section {
|
||||
this.declaredFunctions, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 9;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -309,22 +599,97 @@ class ElementSection extends Section {
|
||||
lastIndex = index;
|
||||
}
|
||||
}
|
||||
for (final func in declaredFunctions) {
|
||||
elements.add(_DeclaredElement([func]));
|
||||
if (declaredFunctions.isNotEmpty) {
|
||||
elements.add(_DeclaredElement(declaredFunctions));
|
||||
}
|
||||
if (elements.isNotEmpty) {
|
||||
s.writeList(elements);
|
||||
}
|
||||
}
|
||||
|
||||
static void deserialize(
|
||||
Deserializer? d,
|
||||
ir.Module module,
|
||||
ir.Types types,
|
||||
ir.Functions functions,
|
||||
ir.Tables tables,
|
||||
ir.Globals globals,
|
||||
) {
|
||||
if (d == null) {
|
||||
functions.declared = [];
|
||||
return;
|
||||
}
|
||||
final declaredFunctions = <ir.BaseFunction>[];
|
||||
final count = d.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final kind = d.readByte();
|
||||
int tableIndex;
|
||||
switch (kind) {
|
||||
case 0x00:
|
||||
tableIndex = 0;
|
||||
break;
|
||||
case 0x06:
|
||||
tableIndex = d.readUnsigned();
|
||||
break;
|
||||
case 0x03:
|
||||
final elemkind = d.readByte();
|
||||
if (elemkind != 0x00) throw "unsupported elemkind";
|
||||
final funcs = d.readList((d) => functions[d.readUnsigned()]);
|
||||
declaredFunctions.addAll(funcs);
|
||||
continue;
|
||||
default:
|
||||
throw "unsupported element segment kind $kind";
|
||||
}
|
||||
|
||||
final offsetInitializer =
|
||||
ir.Instructions.deserializeConst(d, types, functions, globals);
|
||||
final instructions = offsetInitializer.instructions;
|
||||
assert(instructions.length == 2 &&
|
||||
instructions[0] is ir.I32Const &&
|
||||
instructions[1] is ir.End);
|
||||
final offset = (instructions[0] as ir.I32Const).value;
|
||||
|
||||
if (kind == 0x06) {
|
||||
ir.RefType.deserialize(d, types.defined);
|
||||
}
|
||||
|
||||
final table = tables[tableIndex];
|
||||
if (table is ir.DefinedTable) {
|
||||
final count = d.readUnsigned();
|
||||
for (int j = 0; j < count; j++) {
|
||||
late ir.BaseFunction func;
|
||||
if (tableIndex == 0) {
|
||||
final funcIndex = d.readUnsigned();
|
||||
func = functions[funcIndex];
|
||||
} else {
|
||||
final funcInitializer =
|
||||
ir.Instructions.deserializeConst(d, types, functions, globals);
|
||||
final refFunc = funcInitializer.instructions.single as ir.RefFunc;
|
||||
func = refFunc.function;
|
||||
}
|
||||
if (table.elements.length <= offset + j) {
|
||||
table.elements.length = offset + j + 1;
|
||||
}
|
||||
table.elements[offset + j] = func;
|
||||
}
|
||||
} else {
|
||||
throw "unsupported table type";
|
||||
}
|
||||
}
|
||||
|
||||
functions.declared = declaredFunctions;
|
||||
}
|
||||
}
|
||||
|
||||
class DataCountSection extends Section {
|
||||
static const int sectionId = 12;
|
||||
|
||||
final List<ir.DataSegment> dataSegments;
|
||||
|
||||
DataCountSection(this.dataSegments, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 12;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -332,15 +697,28 @@ class DataCountSection extends Section {
|
||||
s.writeUnsigned(dataSegments.length);
|
||||
}
|
||||
}
|
||||
|
||||
static ir.DataSegments deserialize(Deserializer? d) {
|
||||
if (d == null) {
|
||||
return ir.DataSegments([]);
|
||||
}
|
||||
final count = d.readUnsigned();
|
||||
final uninitializedSegments = [
|
||||
for (int i = 0; i < count; ++i) ir.DataSegment.uninitialized()
|
||||
];
|
||||
return ir.DataSegments(uninitializedSegments);
|
||||
}
|
||||
}
|
||||
|
||||
class CodeSection extends Section {
|
||||
static const int sectionId = 10;
|
||||
|
||||
final List<ir.DefinedFunction> functions;
|
||||
|
||||
CodeSection(this.functions, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 10;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -348,15 +726,69 @@ class CodeSection extends Section {
|
||||
s.writeList(functions);
|
||||
}
|
||||
}
|
||||
|
||||
static void deserialize(
|
||||
Deserializer? d,
|
||||
List<ir.DefinedFunction> definedFunctions,
|
||||
ir.Module module,
|
||||
ir.Types types,
|
||||
ir.Functions functions,
|
||||
ir.Tables tables,
|
||||
ir.Memories memories,
|
||||
ir.Tags tags,
|
||||
ir.Globals globals,
|
||||
ir.DataSegments dataSegments,
|
||||
) {
|
||||
if (d == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final count = d.readUnsigned();
|
||||
if (count != functions.defined.length) {
|
||||
throw "Code count mismatch";
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
final function = definedFunctions[i];
|
||||
final type = function.type;
|
||||
|
||||
final locals = <ir.Local>[
|
||||
// Parameters
|
||||
for (int i = 0; i < type.inputs.length; ++i)
|
||||
ir.Local(i, type.inputs[i]),
|
||||
];
|
||||
final instructions = <ir.Instruction>[];
|
||||
|
||||
final bodySize = d.readUnsigned();
|
||||
final bodyDeserializer = Deserializer(d.readBytes(bodySize));
|
||||
|
||||
final localDeclCount = bodyDeserializer.readUnsigned();
|
||||
for (int j = 0; j < localDeclCount; j++) {
|
||||
final localCount = bodyDeserializer.readUnsigned();
|
||||
final type = ir.ValueType.deserialize(bodyDeserializer, types.defined);
|
||||
for (int k = 0; k < localCount; k++) {
|
||||
locals.add(ir.Local(locals.length, type));
|
||||
}
|
||||
}
|
||||
while (!bodyDeserializer.isAtEnd) {
|
||||
final instruction = ir.Instruction.deserialize(bodyDeserializer, types,
|
||||
tables, tags, globals, dataSegments, memories, functions);
|
||||
instructions.add(instruction);
|
||||
}
|
||||
|
||||
function.body = ir.Instructions(locals, {}, instructions, null, [], []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DataSection extends Section {
|
||||
static const int sectionId = 11;
|
||||
|
||||
final List<ir.DataSegment> dataSegments;
|
||||
|
||||
DataSection(this.dataSegments, super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 11;
|
||||
int get id => sectionId;
|
||||
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
@@ -364,17 +796,76 @@ class DataSection extends Section {
|
||||
s.writeList(dataSegments);
|
||||
}
|
||||
}
|
||||
|
||||
static void deserialize(
|
||||
Deserializer? d, ir.DataSegments dataSegments, ir.Memories memories) {
|
||||
final defined = dataSegments.defined;
|
||||
if (d == null) {
|
||||
assert(defined.isEmpty);
|
||||
return;
|
||||
}
|
||||
|
||||
final count = d.readUnsigned();
|
||||
if (defined.length != count) {
|
||||
throw "Mismatch number of data segments";
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
final mode = d.readByte();
|
||||
if (mode == 0x1) {
|
||||
// Passive segment.
|
||||
final length = d.readUnsigned();
|
||||
final content = d.readBytes(length);
|
||||
defined[i]
|
||||
..index = i
|
||||
..memory = null
|
||||
..offset = null
|
||||
..content = content;
|
||||
continue;
|
||||
}
|
||||
|
||||
ir.Memory? memory;
|
||||
int? offset;
|
||||
if (mode == 0x00 || mode == 0x02) {
|
||||
if (mode == 0x00) {
|
||||
memory = memories[0];
|
||||
} else if (mode == 0x02) {
|
||||
// Active segment
|
||||
final memoryIndex = d.readUnsigned();
|
||||
memory = memories[memoryIndex];
|
||||
}
|
||||
|
||||
final i32ConstByte = d.readByte();
|
||||
if (i32ConstByte != 0x41) throw 'bad encoding';
|
||||
offset = d.readSigned();
|
||||
final endByte = d.readByte();
|
||||
if (endByte != 0x0B) throw 'bad encoding';
|
||||
|
||||
// final offsetInitializer = ir.Instructions.deserialize(d, module);
|
||||
// offset = (offsetInitializer.instructions.single as ir.I32Const).value;
|
||||
}
|
||||
final content = d.readBytes(d.readUnsigned());
|
||||
defined[i]
|
||||
..index = i
|
||||
..memory = memory
|
||||
..offset = offset
|
||||
..content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CustomSection extends Section {
|
||||
static const int sectionId = 0;
|
||||
|
||||
CustomSection(super.watchPoints);
|
||||
|
||||
@override
|
||||
int get id => 0;
|
||||
int get id => sectionId;
|
||||
}
|
||||
|
||||
class NameSection extends CustomSection {
|
||||
final String moduleName;
|
||||
static const String customSectionName = 'name';
|
||||
|
||||
final String? moduleName;
|
||||
final List<ir.BaseFunction> functions;
|
||||
final List<List<ir.DefType>> types;
|
||||
final List<ir.Global> globals;
|
||||
@@ -390,7 +881,9 @@ class NameSection extends CustomSection {
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
final moduleNameSubsection = Serializer();
|
||||
moduleNameSubsection.writeName(moduleName);
|
||||
if (moduleName != null) {
|
||||
moduleNameSubsection.writeName(moduleName!);
|
||||
}
|
||||
|
||||
int functionNameCount = 0;
|
||||
final functionNames = Serializer();
|
||||
@@ -458,11 +951,13 @@ class NameSection extends CustomSection {
|
||||
}
|
||||
}
|
||||
|
||||
s.writeName("name"); // Name of the custom section.
|
||||
s.writeName(customSectionName);
|
||||
|
||||
s.writeByte(0); // Module name subsection
|
||||
s.writeUnsigned(moduleNameSubsection.data.length);
|
||||
s.writeData(moduleNameSubsection);
|
||||
if (moduleNameSubsection.offset > 0) {
|
||||
s.writeUnsigned(moduleNameSubsection.data.length);
|
||||
s.writeData(moduleNameSubsection);
|
||||
}
|
||||
|
||||
if (functionNameCount > 0) {
|
||||
s.writeByte(1); // Function names subsection
|
||||
@@ -504,9 +999,98 @@ class NameSection extends CustomSection {
|
||||
s.writeData(fieldNames);
|
||||
}
|
||||
}
|
||||
|
||||
static String? deserialize(Deserializer? d, ir.Functions functions,
|
||||
ir.Types types, ir.Globals globals) {
|
||||
String? moduleName;
|
||||
|
||||
if (d == null) {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
while (!d.isAtEnd) {
|
||||
final subsectionId = d.readByte();
|
||||
final subsectionSize = d.readUnsigned();
|
||||
final subsectionDeserializer = Deserializer(d.readBytes(subsectionSize));
|
||||
switch (subsectionId) {
|
||||
case 0: // Module name
|
||||
moduleName = subsectionDeserializer.readName();
|
||||
break;
|
||||
case 1: // Function names
|
||||
final count = subsectionDeserializer.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final funcIndex = subsectionDeserializer.readUnsigned();
|
||||
final funcName = subsectionDeserializer.readName();
|
||||
final func = functions[funcIndex];
|
||||
func.functionName = funcName;
|
||||
}
|
||||
break;
|
||||
case 2: // Local names
|
||||
final funcCount = subsectionDeserializer.readUnsigned();
|
||||
for (int i = 0; i < funcCount; i++) {
|
||||
final funcIndex = subsectionDeserializer.readUnsigned();
|
||||
final localCount = subsectionDeserializer.readUnsigned();
|
||||
final func = functions[funcIndex];
|
||||
if (func is ir.DefinedFunction) {
|
||||
for (int j = 0; j < localCount; j++) {
|
||||
final localIndex = subsectionDeserializer.readUnsigned();
|
||||
final localName = subsectionDeserializer.readName();
|
||||
func.body.localNames[localIndex] = localName;
|
||||
}
|
||||
} else {
|
||||
// Skip local names for imported functions
|
||||
for (int j = 0; j < localCount; j++) {
|
||||
subsectionDeserializer.readUnsigned();
|
||||
subsectionDeserializer.readName();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4: // Type names
|
||||
final count = subsectionDeserializer.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final typeIndex = subsectionDeserializer.readUnsigned();
|
||||
final typeName = subsectionDeserializer.readName();
|
||||
final type = types[typeIndex];
|
||||
if (type is ir.DataType) {
|
||||
type.name = typeName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 7: // Global names
|
||||
final count = subsectionDeserializer.readUnsigned();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final globalIndex = subsectionDeserializer.readUnsigned();
|
||||
final globalName = subsectionDeserializer.readName();
|
||||
globals[globalIndex].globalName = globalName;
|
||||
}
|
||||
break;
|
||||
case 10: // Field names
|
||||
final typeCount = subsectionDeserializer.readUnsigned();
|
||||
for (int i = 0; i < typeCount; i++) {
|
||||
final typeIndex = subsectionDeserializer.readUnsigned();
|
||||
final fieldCount = subsectionDeserializer.readUnsigned();
|
||||
final type = types[typeIndex];
|
||||
if (type is ir.StructType) {
|
||||
for (int j = 0; j < fieldCount; j++) {
|
||||
final fieldIndex = subsectionDeserializer.readUnsigned();
|
||||
final fieldName = subsectionDeserializer.readName();
|
||||
type.fieldNames[fieldIndex] = fieldName;
|
||||
}
|
||||
} else {
|
||||
throw 'unexpected field name of non struct';
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return moduleName;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceMapSection extends CustomSection {
|
||||
static const String customSectionName = 'sourceMappingURL';
|
||||
|
||||
final Uri? url;
|
||||
|
||||
SourceMapSection(this.url) : super([]);
|
||||
@@ -514,8 +1098,15 @@ class SourceMapSection extends CustomSection {
|
||||
@override
|
||||
void serializeContents(Serializer s) {
|
||||
if (url != null) {
|
||||
s.writeName("sourceMappingURL");
|
||||
s.writeName(customSectionName);
|
||||
s.writeName(url!.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static Uri? deserialize(Deserializer? d) {
|
||||
if (d == null) {
|
||||
return null;
|
||||
}
|
||||
return Uri.parse(d.readName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,5 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
export 'serializer.dart' show Serializable, Serializer;
|
||||
export 'sections.dart'
|
||||
show
|
||||
CodeSection,
|
||||
DataCountSection,
|
||||
DataSection,
|
||||
ElementSection,
|
||||
ExportSection,
|
||||
FunctionSection,
|
||||
GlobalSection,
|
||||
ImportSection,
|
||||
MemorySection,
|
||||
NameSection,
|
||||
SourceMapSection,
|
||||
StartSection,
|
||||
TableSection,
|
||||
TagSection,
|
||||
TypeSection;
|
||||
export 'deserializer.dart' show Deserializer;
|
||||
export 'sections.dart';
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
"third_party/devtools/",
|
||||
"third_party/webdriver/",
|
||||
"third_party/pkg/",
|
||||
"third_party/flute/",
|
||||
"tests/.dart_tool/package_config.json",
|
||||
"tests/angular/",
|
||||
"tests/co19/co19-analyzer.status",
|
||||
|
||||
Reference in New Issue
Block a user