blob: 05fa64c1bdae3432be6cc53094b701f71db42c48 [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 '../ir/ir.dart' as ir;
import 'serializer.dart';
abstract class Section implements Serializable {
final List<int> watchPoints;
Section(this.watchPoints);
@override
void serialize(Serializer s) {
if (isNotEmpty) {
final contents = Serializer();
serializeContents(contents);
s.writeByte(id);
s.writeUnsigned(contents.data.length);
s.sourceMapSerializer
.copyMappings(contents.sourceMapSerializer, s.offset);
s.writeData(contents, watchPoints);
}
}
int get id;
bool get isNotEmpty;
void serializeContents(Serializer s);
}
class TypeSection extends Section {
final ir.Types types;
TypeSection(this.types, super.watchPoints);
List<List<ir.DefType>> get recursionGroups => types.recursionGroups;
@override
int get id => 1;
@override
bool get isNotEmpty => recursionGroups.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeUnsigned(types.recursionGroups.length);
int typeIndex = 0;
// 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) {
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);
}
}
}
}
class ImportSection extends Section {
final List<ir.Import> imports;
ImportSection(this.imports, super.watchPoints);
@override
int get id => 2;
@override
bool get isNotEmpty => imports.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(imports);
}
}
class FunctionSection extends Section {
final List<ir.DefinedFunction> functions;
FunctionSection(this.functions, super.watchPoints);
@override
int get id => 3;
@override
bool get isNotEmpty => functions.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeUnsigned(functions.length);
for (final function in functions) {
s.writeUnsigned(function.type.index);
}
}
}
class TableSection extends Section {
final List<ir.DefinedTable> tables;
TableSection(this.tables, super.watchPOints);
@override
int get id => 4;
@override
bool get isNotEmpty => tables.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(tables);
}
}
class MemorySection extends Section {
final List<ir.DefinedMemory> memories;
MemorySection(this.memories, super.watchPoints);
@override
int get id => 5;
@override
bool get isNotEmpty => memories.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(memories);
}
}
class TagSection extends Section {
final List<ir.DefinedTag> tags;
TagSection(this.tags, super.watchPoints);
@override
int get id => 13;
@override
bool get isNotEmpty => tags.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(tags);
}
}
class GlobalSection extends Section {
final List<ir.DefinedGlobal> globals;
GlobalSection(this.globals, super.watchPoints);
@override
int get id => 6;
@override
bool get isNotEmpty => globals.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(globals);
}
}
class ExportSection extends Section {
final List<ir.Export> exports;
ExportSection(this.exports, super.watchPoints);
@override
int get id => 7;
@override
bool get isNotEmpty => exports.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(exports);
}
}
class StartSection extends Section {
final ir.BaseFunction? startFunction;
StartSection(this.startFunction, super.watchPoints);
@override
int get id => 8;
@override
bool get isNotEmpty => startFunction != null;
@override
void serializeContents(Serializer s) {
s.writeUnsigned(startFunction!.index);
}
}
sealed class _Element implements Serializable {}
class _TableElement implements _Element {
final ir.Table table;
final int startIndex;
final List<ir.BaseFunction> entries = [];
_TableElement(this.table, this.startIndex);
@override
void serialize(Serializer s) {
if (table.index != 0) {
s.writeByte(0x06);
s.writeUnsigned(table.index);
} else {
s.writeByte(0x00);
}
s.writeByte(0x41); // i32.const
s.writeSigned(startIndex);
s.writeByte(0x0B); // end
if (table.index != 0) {
s.write(table.type);
}
s.writeUnsigned(entries.length);
for (var entry in entries) {
if (table.index == 0) {
s.writeUnsigned(entry.index);
} else {
s.writeByte(0xD2); // ref.func
s.writeSigned(entry.index);
s.writeByte(0x0B); // end
}
}
}
}
class _DeclaredElement implements _Element {
final List<ir.BaseFunction> entries;
_DeclaredElement(this.entries);
@override
void serialize(Serializer s) {
if (entries.isEmpty) return;
s.writeByte(0x03);
s.writeByte(0x00);
s.writeUnsigned(entries.length);
for (final entry in entries) {
s.writeUnsigned(entry.index);
}
}
}
class ElementSection extends Section {
final List<ir.DefinedTable> definedTables;
final List<ir.ImportedTable> importedTables;
final List<ir.BaseFunction> declaredFunctions;
ElementSection(this.definedTables, this.importedTables,
this.declaredFunctions, super.watchPoints);
@override
int get id => 9;
@override
bool get isNotEmpty =>
definedTables.any((table) => table.elements.any((e) => e != null)) ||
importedTables.any((table) => table.setElements.isNotEmpty) ||
declaredFunctions.isNotEmpty;
@override
void serializeContents(Serializer s) {
// Group nonempty element entries into contiguous stretches and serialize
// each stretch as an element.
List<_Element> elements = [];
for (final table in definedTables) {
_TableElement? current;
for (int i = 0; i < table.elements.length; i++) {
ir.BaseFunction? function = table.elements[i];
if (function != null) {
if (current == null) {
current = _TableElement(table, i);
elements.add(current);
}
current.entries.add(function);
} else {
current = null;
}
}
}
for (final table in importedTables) {
final entries = [...table.setElements.entries]
..sort((a, b) => a.key.compareTo(b.key));
_TableElement? current;
int lastIndex = -2;
for (final entry in entries) {
final index = entry.key;
final function = entry.value;
if (index != lastIndex + 1) {
current = _TableElement(table, index);
elements.add(current);
}
current!.entries.add(function);
lastIndex = index;
}
}
for (final func in declaredFunctions) {
elements.add(_DeclaredElement([func]));
}
s.writeList(elements);
}
}
class DataCountSection extends Section {
final List<ir.DataSegment> dataSegments;
DataCountSection(this.dataSegments, super.watchPoints);
@override
int get id => 12;
@override
bool get isNotEmpty => dataSegments.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeUnsigned(dataSegments.length);
}
}
class CodeSection extends Section {
final List<ir.DefinedFunction> functions;
CodeSection(this.functions, super.watchPoints);
@override
int get id => 10;
@override
bool get isNotEmpty => functions.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(functions);
}
}
class DataSection extends Section {
final List<ir.DataSegment> dataSegments;
DataSection(this.dataSegments, super.watchPoints);
@override
int get id => 11;
@override
bool get isNotEmpty => dataSegments.isNotEmpty;
@override
void serializeContents(Serializer s) {
s.writeList(dataSegments);
}
}
abstract class CustomSection extends Section {
CustomSection(super.watchPoints);
@override
int get id => 0;
}
class NameSection extends CustomSection {
final String moduleName;
final List<ir.BaseFunction> functions;
final List<List<ir.DefType>> types;
final List<ir.Global> globals;
final int functionNameCount;
final int typeNameCount;
final int globalNameCount;
final int typesWithNamedFieldsCount;
NameSection(this.moduleName, this.functions, this.types, this.globals,
super.watchPoints,
{required this.functionNameCount,
required this.typeNameCount,
required this.globalNameCount,
required this.typesWithNamedFieldsCount});
@override
bool get isNotEmpty => functionNameCount > 0 || typeNameCount > 0;
@override
void serializeContents(Serializer s) {
s.writeName("name");
final moduleNameSubsection = Serializer();
moduleNameSubsection.writeName(moduleName);
final functionNameSubsection = Serializer();
functionNameSubsection.writeUnsigned(functionNameCount);
for (int i = 0; i < functions.length; i++) {
String? functionName = functions[i].functionName;
if (functionName != null) {
functionNameSubsection.writeUnsigned(i);
functionNameSubsection.writeName(functionName);
}
}
final typeNameSubsection = Serializer();
typeNameSubsection.writeUnsigned(typeNameCount);
{
int typeIndex = 0;
for (final recursionGroup in types) {
for (final defType in recursionGroup) {
if (defType is ir.DataType) {
typeNameSubsection.writeUnsigned(typeIndex);
typeNameSubsection.writeName(defType.name);
}
typeIndex += 1;
}
}
}
final globalNameSubsection = Serializer();
globalNameSubsection.writeUnsigned(globalNameCount);
for (int i = 0; i < globals.length; i++) {
final globalName = globals[i].globalName;
if (globalName != null) {
globalNameSubsection.writeUnsigned(i);
globalNameSubsection.writeName(globalName);
}
}
final fieldNameSubsection = Serializer();
fieldNameSubsection.writeUnsigned(typesWithNamedFieldsCount);
if (typesWithNamedFieldsCount != 0) {
int typeIndex = 0;
for (final recursionGroup in types) {
for (final defType in recursionGroup) {
if (defType is ir.StructType && defType.fieldNames.isNotEmpty) {
fieldNameSubsection.writeUnsigned(typeIndex);
fieldNameSubsection.writeUnsigned(defType.fieldNames.length);
for (final entry in defType.fieldNames.entries) {
fieldNameSubsection.writeUnsigned(entry.key);
fieldNameSubsection.writeName(entry.value);
}
}
typeIndex += 1;
}
}
}
final localNameSubsection = Serializer();
List<ir.DefinedFunction> functionsWithLocalNames = [];
for (final function in functions) {
if (function is ir.DefinedFunction) {
if (function.localNames.isNotEmpty) {
functionsWithLocalNames.add(function);
}
}
}
localNameSubsection.writeUnsigned(functionsWithLocalNames.length);
if (functionsWithLocalNames.isNotEmpty) {
for (final function in functionsWithLocalNames) {
localNameSubsection.writeUnsigned(function.finalizableIndex.value);
localNameSubsection.writeUnsigned(function.localNames.length);
for (final entry in function.localNames.entries) {
localNameSubsection.writeUnsigned(entry.key);
localNameSubsection.writeName(entry.value);
}
}
}
s.writeByte(0); // Module name subsection
s.writeUnsigned(moduleNameSubsection.data.length);
s.writeData(moduleNameSubsection);
s.writeByte(1); // Function names subsection
s.writeUnsigned(functionNameSubsection.data.length);
s.writeData(functionNameSubsection);
s.writeByte(2); // Local names substion
s.writeUnsigned(localNameSubsection.data.length);
s.writeData(localNameSubsection);
s.writeByte(4); // Type names subsection
s.writeUnsigned(typeNameSubsection.data.length);
s.writeData(typeNameSubsection);
s.writeByte(7); // Global names subsection
s.writeUnsigned(globalNameSubsection.data.length);
s.writeData(globalNameSubsection);
s.writeByte(10); // Field names subsection
s.writeUnsigned(fieldNameSubsection.data.length);
s.writeData(fieldNameSubsection);
}
}
class SourceMapSection extends CustomSection {
final String url;
SourceMapSection(this.url) : super([]);
@override
bool get isNotEmpty => true;
@override
void serializeContents(Serializer s) {
s.writeName("sourceMappingURL");
s.writeName(url);
}
}