[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:
Martin Kustermann
2025-10-01 03:37:31 -07:00
committed by Commit Queue
parent 3ab6859973
commit 8322e6af37
26 changed files with 2672 additions and 135 deletions
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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();
+13 -8
View File
@@ -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
+15 -7
View File
@@ -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) {
+7 -3
View File
@@ -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
+10 -5
View File
@@ -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;
}
+3 -3
View File
@@ -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";
+6
View File
@@ -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;
}
+26
View File
@@ -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);
}
}
+6 -6
View File
@@ -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];
}
+127 -7
View File
@@ -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,
);
}
}
+4
View File
@@ -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];
}
+4
View File
@@ -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];
}
+304
View File
@@ -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 -1
View File
@@ -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;
}
}
+644 -53
View File
@@ -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';
+1
View File
@@ -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",