blob: 8ea4981caff03739d487eca86c4b23cfbf2ff998 [file] [log] [blame]
// Copyright (c) 2023, 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:typed_data';
import '../serialize/serialize.dart';
import '../serialize/printer.dart';
import 'ir.dart';
/// A logically const wasm module ready to encode. Created with `ModuleBuilder`.
class Module implements Serializable {
// The [Module] object represents a collection of many wasm objects. Some of
// those will have back pointers to this [Module]. That means the data
// structures are cyclic.
//
// (This is similar to how Kernel AST nodes have children and those children
// have back pointers to the parent).
//
// To break the cycle when constructing the objects, we create the [Module] in
// uninitialized form, then create the constitutents and finally initialize the
// module with the constitutents.
bool _initialized = false;
late final String? _moduleName;
late final Functions _functions;
late final BaseFunction? _start;
late final Tables _tables;
late final Elements _elements;
late final Tags _tags;
late final Memories _memories;
late final Exports _exports;
late final Globals _globals;
late final Types _types;
late final DataSegments _dataSegments;
late final Imports _imports;
late final List<int> _watchPoints;
late final Uri? _sourceMapUrl;
Module.uninitialized() : _initialized = false;
void initialize(
String? moduleName,
Functions functions,
BaseFunction? start,
Tables tables,
Elements elements,
Tags tags,
Memories memories,
Exports exports,
Globals globals,
Types types,
DataSegments dataSegments,
Imports imports,
List<int> watchPoints,
Uri? sourceMapUrl,
) {
if (_initialized) throw 'Already initialized';
_initialized = true;
_moduleName = moduleName;
_functions = functions;
_start = start;
_tables = tables;
_elements = elements;
_tags = tags;
_memories = memories;
_exports = exports;
_globals = globals;
_types = types;
_dataSegments = dataSegments;
_imports = imports;
_watchPoints = watchPoints;
_sourceMapUrl = sourceMapUrl;
}
String? get moduleName => _moduleName;
Functions get functions => _functions;
BaseFunction? get start => _start;
Tables get tables => _tables;
Elements get elements => _elements;
Tags get tags => _tags;
Memories get memories => _memories;
Exports get exports => _exports;
Globals get globals => _globals;
Types get types => _types;
DataSegments get dataSegments => _dataSegments;
Imports get imports => _imports;
List<int> get watchPoints => _watchPoints;
Uri? get sourceMapUrl => _sourceMapUrl;
/// Serialize a module to its binary representation.
@override
void serialize(Serializer s) {
if (watchPoints.isNotEmpty) {
Serializer.traceEnabled = true;
}
// Wasm module preamble: magic number, version 1.
s.writeBytes(Uint8List.fromList(
const [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]));
TypeSection(types, watchPoints).serialize(s);
ImportSection(imports, watchPoints).serialize(s);
FunctionSection(functions.defined, watchPoints).serialize(s);
TableSection(tables.defined, watchPoints).serialize(s);
MemorySection(memories.defined, watchPoints).serialize(s);
TagSection(tags.defined, watchPoints).serialize(s);
GlobalSection(globals.defined, watchPoints).serialize(s);
ExportSection(exports.exported, watchPoints).serialize(s);
StartSection(start, watchPoints).serialize(s);
ElementSection(elements, watchPoints).serialize(s);
DataCountSection(dataSegments.defined, watchPoints).serialize(s);
CodeSection(functions.defined, watchPoints).serialize(s);
DataSection(dataSegments.defined, watchPoints).serialize(s);
NameSection(
moduleName,
<BaseFunction>[...functions.imported, ...functions.defined],
types.recursionGroups,
<Global>[...globals.imported, ...globals.defined],
watchPoints)
.serialize(s);
SourceMapSection(sourceMapUrl).serialize(s);
}
static (Map<int, List<Deserializer>>, Map<String, List<Deserializer>>)
_deserializeTopLevel(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);
}
}
return (sections, customSections);
}
static Module deserialize(Deserializer d) {
final (sections, customSections) = _deserializeTopLevel(d);
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];
final elements = 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,
elements,
tags,
memories,
exports,
globals,
types,
dataSegments,
imports,
[],
sourceMapUrl,
);
}
/// Deserialize just the `sourceMapUrl` section of a module as a [Uri].
static Uri? deserializeSourceMapUrl(Deserializer d) {
final (sections, customSections) = _deserializeTopLevel(d);
final sourceMapUrl = SourceMapSection.deserialize(
customSections[SourceMapSection.customSectionName]?.single);
return sourceMapUrl;
}
String printAsWat(
{ModulePrintSettings settings = const ModulePrintSettings()}) {
final mp = ModulePrinter(this, settings: settings);
if (settings.hasFilters) {
// If we have any filters, we treat those as roots.
if (settings.typeFilters.isNotEmpty) {
for (final type in types.defined) {
final name = mp.typeNamer
.nameDefType(type, activateOnReferenceCallback: false);
if (settings.printTypeConstituents(name)) {
mp.enqueueType(type);
}
}
}
if (settings.globalFilters.isNotEmpty) {
for (final global in globals.defined) {
final name = mp.globalNamer
.nameGlobal(global, activateOnReferenceCallback: false);
if (settings.printGlobalInitializer(name)) {
mp.enqueueGlobal(global);
}
}
}
if (settings.functionFilters.isNotEmpty) {
for (final function in functions.defined) {
final name = mp.functionNamer
.nameFunction(function, activateOnReferenceCallback: false);
if (settings.printFunctionBody(name)) {
mp.enqueueFunction(function);
}
}
}
if (settings.tableFilters.isNotEmpty) {
for (final table in tables.defined) {
final name = mp.tableNamer
.nameTable(table, activateOnReferenceCallback: false);
if (settings.printFunctionBody(name)) {
mp.enqueueTable(table);
}
}
}
} else {
// Enqueue all types, tags, globals, functions thereby making the
// printed module contain most things we care about.
for (final type in types.defined) {
if (type is! FunctionType) {
mp.enqueueType(type);
}
}
for (final table in [...tables.imported, ...tables.defined]) {
mp.enqueueTable(table);
}
for (final tag in [...tags.imported, ...tags.defined]) {
mp.enqueueTag(tag);
}
for (final global in [...globals.imported, ...globals.defined]) {
mp.enqueueGlobal(global);
}
for (final function in [...functions.imported, ...functions.defined]) {
mp.enqueueFunction(function);
}
}
return mp.print();
}
}