blob: 720589d9ff7d36813a4d9ded2c263232938a5a11 [file] [log] [blame]
// Copyright (c) 2018, 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.
part of 'serialization.dart';
/// Interface handling [DataSinkWriter] low-level data serialization.
///
/// Each implementation of [DataSink] should have a corresponding
/// [DataSource] that deserializes data serialized by that implementation.
abstract class DataSink {
int get length;
/// Serialization of a non-negative integer value.
void writeInt(int value);
/// Serialization of a non-negative 32 bit integer value. [value] might not be
/// compacted as with [writeInt].
void writeUint32(int value);
/// Serialization of an enum value.
void writeEnum<E extends Enum>(E value);
/// Serialization of a String value.
void writeString(String value);
/// Serialization of a section begin tag. May be omitted by some writers.
void beginTag(String tag);
/// Serialization of a section end tag. May be omitted by some writers.
void endTag(String tag);
/// Writes a deferred entity which can be skipped when reading and read later
/// via an offset read.
void writeDeferred(void writer());
/// Begins a block of data that can later be read as a deferred block.
/// [endDeferred] must eventually be called to end the block. This creates a
/// block similar to [writeDeferred] but does not require the data to be
/// written in a single closure.
void startDeferred();
/// End a block of data that can later be read as a deferred block.
/// [startDeferred] must be called before this to start the block. This
/// creates a block similar to [writeDeferred] but does not require the data
/// to be written in a single closure.
void endDeferred();
/// Closes any underlying data sinks.
void close();
}
/// Serialization writer
///
/// To be used with [DataSourceReader] to read and write serialized data.
/// Serialization format is deferred to provided [DataSink].
class DataSinkWriter {
final DataSink _sinkWriter;
/// If `true`, serialization of every data kind is preceded by a [DataKind]
/// value.
///
/// This is used for debugging data inconsistencies between serialization
/// and deserialization.
final bool useDataKinds;
final SerializationIndices importedIndices;
/// Visitor used for serializing [ir.DartType]s.
late final DartTypeNodeWriter _dartTypeNodeWriter;
/// Stack of tags used when [useDataKinds] is `true` to help debugging section
/// inconsistencies between serialization and deserialization.
List<String>? _tags;
/// Map of [MemberData] object for serialized kernel member nodes.
final Map<ir.Member, MemberData> _memberData = {};
late final IndexedSink<String> _stringIndex;
late final IndexedSink<Uri> _uriIndex;
late final IndexedSink<ir.Member> _memberNodeIndex;
late final IndexedSink<ImportEntity> _importIndex;
late final IndexedSink<ConstantValue> _constantIndex;
final Map<Type, IndexedSink> _generalCaches = {};
late AbstractValueDomain _abstractValueDomain;
js.DeferredExpressionRegistry? _deferredExpressionRegistry;
final Map<String, int>? tagFrequencyMap;
ir.Member? _currentMemberContext;
MemberData? _currentMemberData;
DataSinkWriter(
this._sinkWriter, CompilerOptions options, this.importedIndices,
{this.useDataKinds = false, this.tagFrequencyMap}) {
_dartTypeNodeWriter = DartTypeNodeWriter(this);
_stringIndex = importedIndices.getIndexedSink<String>();
_uriIndex = importedIndices.getIndexedSink<Uri>();
_memberNodeIndex = importedIndices
.getMappedIndexedSink<MemberData, ir.Member>((data) => data.node);
_importIndex = importedIndices.getIndexedSink<ImportEntity>();
_constantIndex = importedIndices.getIndexedSink<ConstantValue>();
}
/// The amount of data written to this data sink.
///
/// The units is based on the underlying data structure for this data sink.
int get length => _sinkWriter.length;
/// Flushes any pending data and closes this data sink.
///
/// The data sink can no longer be written to after closing.
void close() {
_sinkWriter.close();
}
/// Registers that the section [tag] starts.
///
/// This is used for debugging to verify that sections are correctly aligned
/// between serialization and deserialization.
void begin(String tag) {
tagFrequencyMap?.update(tag, (count) => count + 1, ifAbsent: () => 1);
if (useDataKinds) {
(_tags ??= <String>[]).add(tag);
_sinkWriter.beginTag(tag);
}
}
/// Registers that the section [tag] ends.
///
/// This is used for debugging to verify that sections are correctly aligned
/// between serialization and deserialization.
void end(String tag) {
if (useDataKinds) {
_sinkWriter.endTag(tag);
String existingTag = _tags!.removeLast();
assert(existingTag == tag,
"Unexpected tag end. Expected $existingTag, found $tag.");
}
}
void writeDeferrable(void f()) {
_sinkWriter.writeDeferred(f);
}
void startDeferrable() {
_sinkWriter.startDeferred();
}
void endDeferrable() {
_sinkWriter.endDeferred();
}
/// Writes a reference to [value] to this data sink. If [value] has not yet
/// been serialized, [f] is called to serialize the value itself. If
/// [identity] is true then the cache is backed by a [Map] created using
/// [Map.identity]. (i.e. comparisons are done using [identical] rather than
/// `==`)
void writeIndexed<E extends Object>(E? value, void f(E value),
{bool identity = false}) {
IndexedSink<E> sink = (_generalCaches[E] ??=
importedIndices.getIndexedSink<E>(identity: identity))
as IndexedSink<E>;
sink.write(this, value, f);
}
/// Writes the potentially `null` [value] to this data sink. If [value] is
/// non-null [f] is called to write the non-null value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readValueOrNull].
void writeValueOrNull<E>(E? value, void f(E value)) {
writeBool(value != null);
if (value != null) {
f(value);
}
}
/// Writes the [values] to this data sink calling [f] to write each value to
/// the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readList].
void writeList<E>(Iterable<E> values, void f(E value)) {
writeInt(values.length);
values.forEach(f);
}
/// Writes the [values] to this data sink calling [f] to write each value to
/// the data sink. Treats a null [values] as an empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readListOrNull].
void writeListOrNull<E>(Iterable<E>? values, void f(E value)) {
writeList<E>(values ?? const [], f);
}
/// Writes the [map] to this data sink calling [k] to write each key and [v]
/// to write each value to the data sink.
void writeMap<K, V>(Map<K, V> map, void k(K key), void v(V value)) {
writeInt(map.length);
map.forEach((K key, V value) {
k(key);
v(value);
});
}
/// Writes the [map] to this data sink calling [k] to write each key and [v]
/// to write each value to the data sink. Treats a null [map] as an empty
/// map.
void writeMapOrNull<K, V>(Map<K, V>? map, void k(K key), void v(V value)) {
writeMap<K, V>(map ?? const {}, k, v);
}
/// Writes the boolean [value] to this data sink.
void writeBool(bool value) {
_writeDataKind(DataKind.bool);
_writeBool(value);
}
void _writeBool(bool value) {
_sinkWriter.writeInt(value ? 1 : 0);
}
/// Writes the non-negative 30 bit integer [value] to this data sink.
void writeInt(int value) {
assert(value >= 0 && value >> 30 == 0);
_writeDataKind(DataKind.uint30);
_sinkWriter.writeInt(value);
}
/// Writes the non-negative 32 bit integer [value] to this data sink. [value]
/// might not be compacted as with [writeInt].
void writeUint32(int value) {
_writeDataKind(DataKind.uint32);
_sinkWriter.writeUint32(value);
}
/// Writes the potentially `null` non-negative [value] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readIntOrNull].
void writeIntOrNull(int? value) {
writeBool(value != null);
if (value != null) {
writeInt(value);
}
}
/// Writes the string [value] to this data sink.
void writeString(String value) {
_writeDataKind(DataKind.string);
_writeString(value);
}
void _writeString(String value) {
_stringIndex.write(this, value, _sinkWriter.writeString);
}
/// Writes the potentially `null` string [value] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStringOrNull].
void writeStringOrNull(String? value) {
writeBool(value != null);
if (value != null) {
writeString(value);
}
}
/// Writes the [map] from string to [V] values to this data sink, calling [f]
/// to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStringMap].
void writeStringMap<V>(Map<String, V> map, void f(V value)) {
writeMap(map, writeString, f);
}
/// Writes the [map] from string to [V] values to this data sink, calling [f]
/// to write each value to the data sink. Treats a null [map] as an empty
/// map.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStringMapOrNull].
void writeStringMapOrNull<V>(Map<String, V>? map, void f(V value)) {
writeMapOrNull(map, writeString, f);
}
/// Writes the [map] from [Name] to [V] values to this data sink, calling [f]
/// to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readNameMap].
void writeNameMap<V>(Map<Name, V> map, void f(V value)) {
writeMap(map, writeMemberName, f);
}
/// Writes the string [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStrings].
void writeStrings(Iterable<String> values) {
writeList(values, writeString);
}
/// Writes the string [values] to this data sink. Treats a null [values] as an
/// empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStringsOrNull].
void writeStringsOrNull(Iterable<String>? values) {
writeListOrNull(values, writeString);
}
/// Writes the enum value [value] to this data sink.
void writeEnum<E extends Enum>(E value) {
_writeDataKind(DataKind.enumValue);
_sinkWriter.writeEnum(value);
}
/// Writes the URI [value] to this data sink.
void writeUri(Uri value) {
_writeDataKind(DataKind.uri);
_writeUri(value);
}
void _writeUri(Uri value) {
_uriIndex.write(this, value, _doWriteUri);
}
void _doWriteUri(Uri value) {
_writeString(value.toString());
}
/// Writes a reference to the kernel library node [value] to this data sink.
void writeLibraryNode(ir.Library value) {
_writeDataKind(DataKind.libraryNode);
_writeLibraryNode(value);
}
void _writeLibraryNode(ir.Library value) {
_writeUri(value.importUri);
}
/// Writes a reference to the kernel class node [value] to this data sink.
void writeClassNode(ir.Class value) {
_writeDataKind(DataKind.classNode);
_writeClassNode(value);
}
void _writeClassNode(ir.Class value) {
_writeLibraryNode(value.enclosingLibrary);
_writeString(value.name);
}
/// Writes a reference to the kernel extension type declaration node [value]
/// to this data sink.
void writeExtensionTypeDeclarationNode(ir.ExtensionTypeDeclaration value) {
_writeDataKind(DataKind.extensionTypeDeclarationNode);
_writeExtensionTypeDeclarationNode(value);
}
void _writeExtensionTypeDeclarationNode(ir.ExtensionTypeDeclaration value) {
_writeLibraryNode(value.enclosingLibrary);
_writeString(value.name);
}
/// Writes a reference to the kernel typedef node [value] to this data sink.
void writeTypedefNode(ir.Typedef value) {
_writeDataKind(DataKind.typedefNode);
_writeTypedefNode(value);
}
void _writeTypedefNode(ir.Typedef value) {
_writeLibraryNode(value.enclosingLibrary);
_writeString(value.name);
}
/// Writes a reference to the kernel member node [value] to this data sink.
void writeMemberNode(ir.Member value) {
_writeDataKind(DataKind.memberNode);
_writeMemberNode(value);
}
void _writeMemberNode(ir.Member value) {
_memberNodeIndex.write(this, value, _writeMemberNodeInternal);
}
void _writeMemberNodeInternal(ir.Member value) {
ir.Class? cls = value.enclosingClass;
if (cls != null) {
_sinkWriter.writeEnum(MemberContextKind.cls);
_writeClassNode(cls);
_writeString(computeMemberName(value));
} else {
_sinkWriter.writeEnum(MemberContextKind.library);
_writeLibraryNode(value.enclosingLibrary);
_writeString(computeMemberName(value));
}
}
/// Writes references to the kernel member node [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodes].
void writeMemberNodes(Iterable<ir.Member> values) {
writeList(values, writeMemberNode);
}
/// Writes references to the kernel member node [values] to this data sink.
/// Treats a null [values] as an empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodesOrNull].
void writeMemberNodesOrNull(Iterable<ir.Member>? values) {
writeListOrNull(values, writeMemberNode);
}
/// Writes the [map] from references to kernel member nodes to [V] values to
/// this data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodeMap].
void writeMemberNodeMap<V>(Map<ir.Member, V> map, void f(V value)) {
writeMap(map, writeMemberNode, f);
}
/// Writes the [map] from references to kernel member nodes to [V] values to
/// this data sink, calling [f] to write each value to the data sink. `null`
/// is treated as an empty map.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodeMapOrNull].
void writeMemberNodeMapOrNull<V>(Map<ir.Member, V>? map, void f(V value)) {
writeMapOrNull(map, writeMemberNode, f);
}
/// Writes a [Name] to this data sink.
void writeMemberName(Name value) {
writeString(value.text);
writeValueOrNull(value.uri, writeUri);
writeBool(value.isSetter);
}
/// Writes a reference to the kernel tree node [value] to this data sink.
void writeTreeNode(ir.TreeNode value) {
_writeDataKind(DataKind.treeNode);
_writeTreeNode(value, null);
}
void _writeTreeNode(ir.TreeNode value, MemberData? memberData) {
if (value is ir.Class) {
_sinkWriter.writeEnum(_TreeNodeKind.cls);
_writeClassNode(value);
} else if (value is ir.Member) {
_sinkWriter.writeEnum(_TreeNodeKind.member);
_writeMemberNode(value);
} else if (value is ir.VariableDeclaration &&
value.parent is ir.FunctionDeclaration) {
_sinkWriter.writeEnum(_TreeNodeKind.functionDeclarationVariable);
_writeTreeNode(value.parent!, memberData);
} else if (value is ir.FunctionNode) {
_sinkWriter.writeEnum(_TreeNodeKind.functionNode);
_writeFunctionNode(value, memberData);
} else if (value is ir.TypeParameter) {
_sinkWriter.writeEnum(_TreeNodeKind.typeParameter);
_writeTypeParameter(value, memberData);
} else if (value is ConstantReference) {
_sinkWriter.writeEnum(_TreeNodeKind.constant);
memberData ??= _getMemberData(value.expression);
_writeTreeNode(value.expression, memberData);
int index =
memberData.getIndexByConstant(value.expression, value.constant);
_sinkWriter.writeInt(index);
} else {
_sinkWriter.writeEnum(_TreeNodeKind.node);
memberData ??= _getMemberData(value);
int index = memberData.getIndexByTreeNode(value);
_sinkWriter.writeInt(index);
}
}
/// Writes a reference to the potentially `null` kernel tree node [value]
/// to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeOrNull].
void writeTreeNodeOrNull(ir.TreeNode? value) {
writeBool(value != null);
if (value != null) {
writeTreeNode(value);
}
}
/// Writes references to the kernel tree node [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodes].
void writeTreeNodes(Iterable<ir.TreeNode> values) {
writeList(values, writeTreeNode);
}
/// Writes references to the kernel tree node [values] to this data sink.
/// Treats `null` [values] as an empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodesOrNull].
void writeTreeNodesOrNull(Iterable<ir.TreeNode>? values) {
writeListOrNull(values, writeTreeNode);
}
/// Writes the [map] from references to kernel tree nodes to [V] values to
/// this data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeMap].
void writeTreeNodeMap<V>(Map<ir.TreeNode, V> map, void f(V value)) {
writeMap(map, writeTreeNode, f);
}
/// Writes a reference to the kernel tree node [value] in the known [context]
/// to this data sink.
void writeTreeNodeInContext(ir.TreeNode value) {
writeTreeNodeInContextInternal(value, currentMemberData);
}
void writeTreeNodeInContextInternal(
ir.TreeNode value, MemberData memberData) {
_writeDataKind(DataKind.treeNode);
_writeTreeNode(value, memberData);
}
/// Writes a reference to the potentially `null` kernel tree node [value] in
/// the known [context] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeOrNullInContext].
void writeTreeNodeOrNullInContext(ir.TreeNode? value) {
writeBool(value != null);
if (value != null) {
writeTreeNodeInContextInternal(value, currentMemberData);
}
}
/// Writes the [map] from references to kernel tree nodes to [V] values in the
/// known [context] to this data sink, calling [f] to write each value to the
/// data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeMapInContext].
void writeTreeNodeMapInContext<V>(Map<ir.TreeNode, V> map, void f(V value)) {
writeMap(map, writeTreeNodeInContext, f);
}
/// Writes the [map] from references to kernel tree nodes to [V] values in the
/// known [context] to this data sink, calling [f] to write each value to the
/// data sink. Treats a null [map] as an empty map.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeMapInContextOrNull].
void writeTreeNodeMapInContextOrNull<V>(
Map<ir.TreeNode, V>? map, void f(V value)) {
writeMapOrNull(map, writeTreeNodeInContext, f);
}
/// Writes a reference to the kernel type parameter node [value] to this data
/// sink.
void writeTypeParameterNode(ir.TypeParameter value) {
_writeDataKind(DataKind.typeParameterNode);
_writeTypeParameter(value, null);
}
void _writeTypeParameter(ir.TypeParameter value, MemberData? memberData) {
ir.GenericDeclaration declaration = value.declaration!;
// TODO(fishythefish): Use exhaustive pattern switch.
if (declaration is ir.Class) {
_sinkWriter.writeEnum(_TypeParameterKind.cls);
_writeClassNode(declaration);
_sinkWriter.writeInt(declaration.typeParameters.indexOf(value));
} else if (declaration is ir.Procedure) {
_sinkWriter.writeEnum(_TypeParameterKind.functionNode);
_writeFunctionNode(declaration.function, memberData);
_sinkWriter.writeInt(declaration.typeParameters.indexOf(value));
} else if (declaration is ir.LocalFunction) {
_sinkWriter.writeEnum(_TypeParameterKind.functionNode);
_writeFunctionNode(declaration.function, memberData);
_sinkWriter.writeInt(declaration.typeParameters.indexOf(value));
} else {
throw UnsupportedError(
"Unsupported TypeParameter declaration ${declaration.runtimeType}");
}
}
/// Writes references to the kernel type parameter node [values] to this data
/// sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTypeParameterNodes].
void writeTypeParameterNodes(Iterable<ir.TypeParameter> values) {
writeList(values, writeTypeParameterNode);
}
/// Writes the type [value] to this data sink.
void writeDartType(DartType value) {
_writeDataKind(DataKind.dartType);
value.writeToDataSink(this, []);
}
/// Writes the optional type [value] to this data sink.
void writeDartTypeOrNull(DartType? value) {
_writeDataKind(DataKind.dartType);
if (value == null) {
writeEnum(DartTypeKind.none);
} else {
value.writeToDataSink(this, []);
}
}
/// Writes the types [values] to this data sink. If [values] is null, write a
/// zero-length iterable.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readDartTypesOrNull].
void writeDartTypesOrNull(Iterable<DartType>? values) {
if (values == null) {
writeInt(0);
} else {
writeDartTypes(values);
}
}
/// Writes the types [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readDartTypes].
void writeDartTypes(Iterable<DartType> values) {
writeList(values, writeDartType);
}
/// Writes the kernel type node [value] to this data sink.
void writeDartTypeNode(ir.DartType value) {
_writeDataKind(DataKind.dartTypeNode);
_writeDartTypeNode(value, [], allowNull: false);
}
/// Writes the kernel type node [value] to this data sink, `null` permitted.
void writeDartTypeNodeOrNull(ir.DartType? value) {
_writeDataKind(DataKind.dartTypeNode);
_writeDartTypeNode(value, [], allowNull: true);
}
void _writeDartTypeNode(
ir.DartType? value, List<ir.StructuralParameter> functionTypeVariables,
{bool allowNull = false}) {
if (value == null) {
if (!allowNull) {
throw UnsupportedError("Missing ir.DartType node is not allowed.");
}
writeEnum(DartTypeNodeKind.none);
} else {
value.accept1(_dartTypeNodeWriter, functionTypeVariables);
}
}
/// Writes the kernel type node [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readDartTypeNodes].
void writeDartTypeNodes(Iterable<ir.DartType> values) {
writeList(values, writeDartTypeNode);
}
/// Writes the source span [value] to this data sink.
void writeSourceSpan(SourceSpan value) {
_writeDataKind(DataKind.sourceSpan);
_writeUri(value.uri);
_sinkWriter.writeInt(value.begin);
_sinkWriter.writeInt(value.end);
}
/// Writes a reference to the library entity [value] to this data sink.
void writeLibrary(LibraryEntity value) {
if (value is JLibrary) {
writeIndexed<LibraryEntity>(value, (_) => value.writeToDataSink(this));
} else {
failedAt(value, 'Unexpected library entity type ${value.runtimeType}');
}
}
/// Writes a reference to the potentially `null` library entities [value]
/// to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLibraryOrNull].
void writeLibraryOrNull(LibraryEntity? value) {
writeBool(value != null);
if (value != null) {
writeLibrary(value);
}
}
/// Writes the [map] from references to library entities to [V] values to
/// this data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLibraryMap].
void writeLibraryMap<V>(Map<LibraryEntity, V> map, void f(V value)) {
writeMap(map, writeLibrary, f);
}
/// Writes a reference to the class entity [value] to this data sink.
void writeClass(ClassEntity value) {
if (value is JClass) {
writeIndexed<ClassEntity>(value, (_) => value.writeToDataSink(this));
} else {
failedAt(value, 'Unexpected class entity type ${value.runtimeType}');
}
}
/// Writes a reference to the potentially `null` class entity [value]
/// to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClassOrNull].
void writeClassOrNull(ClassEntity? value) {
writeBool(value != null);
if (value != null) {
writeClass(value);
}
}
/// Writes references to the class entity [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClasses].
void writeClasses(Iterable<ClassEntity> values) {
writeList(values, writeClass);
}
/// Writes references to the class entity [values] to this data sink. Treats a
/// null [values] as an empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClassesOrNull].
void writeClassesOrNull(Iterable<ClassEntity>? values) {
writeListOrNull(values, writeClass);
}
/// Writes the [map] from references to class entities to [V] values to this
/// data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClassMap].
void writeClassMap<V>(Map<ClassEntity, V> map, void f(V value)) {
writeMap(map, writeClass, f);
}
/// Writes a reference to the member entity [value] to this data sink.
void writeMember(MemberEntity value) {
if (value is JMember) {
writeIndexed<MemberEntity>(value, (_) => value.writeToDataSink(this));
} else {
failedAt(value, 'Unexpected member entity type ${value.runtimeType}');
}
}
/// Writes a reference to the potentially `null` member entities [value]
/// to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberOrNull].
void writeMemberOrNull(MemberEntity? value) {
writeBool(value != null);
if (value != null) {
writeMember(value);
}
}
/// Writes references to the member entities [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMembers].
void writeMembers(Iterable<MemberEntity> values) {
writeList(values, writeMember);
}
/// Writes references to the member entities [values] to this data sink.
/// Treats a null [values] as an empty list.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMembersOrNull].
void writeMembersOrNull(Iterable<MemberEntity>? values) {
writeListOrNull(values, writeMember);
}
/// Writes the [map] from references to member entities to [V] values to this
/// data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberMap].
void writeMemberMap<V>(
Map<MemberEntity, V> map, void f(MemberEntity member, V value)) {
writeInt(map.length);
map.forEach((MemberEntity member, V value) {
writeMember(member);
f(member, value);
});
}
/// Writes a reference to the type variable entity [value] to this data sink.
void writeTypeVariable(TypeVariableEntity value) {
if (value is JTypeVariable) {
writeIndexed<TypeVariableEntity>(
value, (_) => value.writeToDataSink(this));
} else {
failedAt(
value, 'Unexpected type variable entity type ${value.runtimeType}');
}
}
/// Writes the [map] from references to type variable entities to [V] values
/// to this data sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTypeVariableMap].
void writeTypeVariableMap<V>(
Map<TypeVariableEntity, V> map, void f(V value)) {
writeMap(map, writeTypeVariable, f);
}
/// Writes a reference to the local [local] to this data sink.
void writeLocal(Local local) {
if (local is JLocal) {
writeEnum(LocalKind.jLocal);
writeIndexed<Local>(local, (_) => local.writeToDataSink(this));
} else if (local is ThisLocal) {
writeEnum(LocalKind.thisLocal);
writeClass(local.enclosingClass);
} else if (local is BoxLocal) {
writeEnum(LocalKind.boxLocal);
writeClass(local.container);
} else if (local is AnonymousClosureLocal) {
writeEnum(LocalKind.anonymousClosureLocal);
writeClass(local.closureClass);
} else if (local is TypeVariableLocal) {
writeEnum(LocalKind.typeVariableLocal);
writeTypeVariable(local.typeVariable);
} else {
throw UnsupportedError("Unsupported local ${local.runtimeType}");
}
}
/// Writes a reference to the potentially `null` local [value]
/// to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLocalOrNull].
void writeLocalOrNull(Local? value) {
writeBool(value != null);
if (value != null) {
writeLocal(value);
}
}
/// Writes the [map] from references to locals to [V] values to this data
/// sink, calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLocalMap].
void writeLocalMap<V>(Map<Local, V> map, void f(V value)) {
writeMap(map, writeLocal, f);
}
/// Writes the constant [value] to this data sink.
void writeConstant(ConstantValue value) {
_writeDataKind(DataKind.constant);
_writeConstant(value);
}
void _writeConstant(ConstantValue value) {
_constantIndex.write(this, value, _writeConstantInternal);
}
void _writeConstantInternal(ConstantValue value) {
_sinkWriter.writeEnum(value.kind);
switch (value.kind) {
case ConstantValueKind.BOOL:
final constant = value as BoolConstantValue;
writeBool(constant.boolValue);
break;
case ConstantValueKind.INT:
final constant = value as IntConstantValue;
_writeBigInt(constant.intValue);
break;
case ConstantValueKind.DOUBLE:
final constant = value as DoubleConstantValue;
_writeDoubleValue(constant.doubleValue);
break;
case ConstantValueKind.STRING:
final constant = value as StringConstantValue;
writeString(constant.stringValue);
break;
case ConstantValueKind.NULL:
break;
case ConstantValueKind.FUNCTION:
final constant = value as FunctionConstantValue;
writeMember(constant.element);
writeDartType(constant.type);
break;
case ConstantValueKind.LIST:
final constant = value as ListConstantValue;
writeDartType(constant.type);
writeConstants(constant.entries);
break;
case ConstantValueKind.SET:
final constant = value as constant_system.JavaScriptSetConstant;
writeDartType(constant.type);
writeConstants(constant.values);
writeConstantOrNull(constant.indexObject);
break;
case ConstantValueKind.MAP:
final constant = value as constant_system.JavaScriptMapConstant;
writeDartType(constant.type);
writeConstant(constant.keyList);
writeConstant(constant.valueList);
writeBool(constant.onlyStringKeys);
if (constant.onlyStringKeys) writeConstant(constant.indexObject!);
break;
case ConstantValueKind.CONSTRUCTED:
final constant = value as ConstructedConstantValue;
writeDartType(constant.type);
writeMemberMap(constant.fields,
(MemberEntity member, ConstantValue value) => writeConstant(value));
break;
case ConstantValueKind.RECORD:
final constant = value as RecordConstantValue;
constant.shape.writeToDataSink(this);
writeConstants(constant.values);
break;
case ConstantValueKind.TYPE:
final constant = value as TypeConstantValue;
writeDartType(constant.representedType);
writeDartType(constant.type);
break;
case ConstantValueKind.INSTANTIATION:
final constant = value as InstantiationConstantValue;
writeDartTypes(constant.typeArguments);
writeConstant(constant.function);
break;
case ConstantValueKind.INTERCEPTOR:
final constant = value as InterceptorConstantValue;
writeClass(constant.cls);
break;
case ConstantValueKind.JAVASCRIPT_OBJECT:
final constant = value as JavaScriptObjectConstantValue;
writeConstants(constant.keys);
writeConstants(constant.values);
break;
case ConstantValueKind.DEFERRED_GLOBAL:
final constant = value as DeferredGlobalConstantValue;
writeConstant(constant.referenced);
writeOutputUnitReference(constant.unit);
break;
case ConstantValueKind.DUMMY_INTERCEPTOR:
break;
case ConstantValueKind.LATE_SENTINEL:
break;
case ConstantValueKind.UNREACHABLE:
break;
case ConstantValueKind.JS_NAME:
final constant = value as JsNameConstantValue;
writeJsNode(constant.name);
break;
}
}
/// Writes the potentially `null` constant [value] to this data sink.
void writeConstantOrNull(ConstantValue? value) {
writeBool(value != null);
if (value != null) {
writeConstant(value);
}
}
/// Writes constant [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readConstants].
void writeConstants(Iterable<ConstantValue> values) {
writeList(values, writeConstant);
}
/// Writes the [map] from constant values to [V] values to this data sink,
/// calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readConstantMap].
void writeConstantMap<V>(Map<ConstantValue, V> map, void f(V value)) {
writeMap(map, writeConstant, f);
}
/// Writes a double value to this data sink.
void writeDoubleValue(double value) {
_writeDataKind(DataKind.double);
_writeDoubleValue(value);
}
void _writeDoubleValue(double value) {
ByteData data = ByteData(8);
data.setFloat64(0, value);
writeInt(data.getUint16(0));
writeInt(data.getUint16(2));
writeInt(data.getUint16(4));
writeInt(data.getUint16(6));
}
/// Writes an integer of arbitrary value to this data sink.
///
/// This is should only when the value is not known to be a non-negative
/// 30 bit integer. Otherwise [writeInt] should be used.
void writeIntegerValue(int value) {
_writeDataKind(DataKind.int);
_writeBigInt(BigInt.from(value));
}
void _writeBigInt(BigInt value) {
writeString(value.toString());
}
/// Writes the import [value] to this data sink.
void writeImport(ImportEntity value) {
_writeDataKind(DataKind.import);
_writeImport(value);
}
void _writeImport(ImportEntity value) {
_importIndex.write(this, value, _writeImportInternal);
}
void _writeImportInternal(ImportEntity value) {
// TODO(johnniwinther): Do we need to serialize non-deferred imports?
writeStringOrNull(value.name);
_writeUri(value.uri);
_writeUri(value.enclosingLibraryUri);
_writeBool(value.isDeferred);
}
/// Writes the potentially `null` import [value] to this data sink.
void writeImportOrNull(ImportEntity? value) {
writeBool(value != null);
if (value != null) {
writeImport(value);
}
}
/// Writes import [values] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readImports].
void writeImports(Iterable<ImportEntity> values) {
writeList(values, writeImport);
}
/// Writes the [map] from imports to [V] values to this data sink,
/// calling [f] to write each value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readImportMap].
void writeImportMap<V>(Map<ImportEntity, V> map, void f(V value)) {
writeMap(map, writeImport, f);
}
/// Writes an abstract [value] to this data sink.
///
/// This feature is only available a [AbstractValueDomain] has been
/// registered.
void writeAbstractValue(AbstractValue value) {
_abstractValueDomain.writeAbstractValueToDataSink(this, value);
}
/// Writes a reference to the output unit [value] to this data sink.
void writeOutputUnitReference(OutputUnit value) {
writeIndexed<OutputUnit>(value, (v) => v.writeToDataSink(this));
}
void withDeferredExpressionRegistry(
js.DeferredExpressionRegistry registry, void Function() f) {
_deferredExpressionRegistry = registry;
f();
_deferredExpressionRegistry = null;
}
/// Writes a js node [value] to this data sink.
void writeJsNode(js.Node value) {
JsNodeSerializer.writeToDataSink(this, value, _deferredExpressionRegistry);
}
/// Writes a potentially `null` js node [value] to this data sink.
void writeJsNodeOrNull(js.Node? value) {
writeBool(value != null);
if (value != null) {
writeJsNode(value);
}
}
/// Writes TypeRecipe [value] to this data sink.
void writeTypeRecipe(TypeRecipe value) {
value.writeToDataSink(this);
}
/// Register a [AbstractValueDomain] with this data sink to support
/// serialization of abstract values.
void registerAbstractValueDomain(AbstractValueDomain domain) {
_abstractValueDomain = domain;
}
/// Invoke [f] in the context of [member]. This sets up support for
/// serialization of `ir.TreeNode`s using the `writeTreeNode*InContext`
/// methods.
void inMemberContext(ir.Member? context, void f()) {
ir.Member? oldMemberContext = _currentMemberContext;
MemberData? oldMemberData = _currentMemberData;
_currentMemberContext = context;
_currentMemberData = null;
f();
_currentMemberData = oldMemberData;
_currentMemberContext = oldMemberContext;
}
MemberData get currentMemberData {
final currentMemberContext = _currentMemberContext!;
return _currentMemberData ??=
_memberData[currentMemberContext] ??= MemberData(currentMemberContext);
}
MemberData _getMemberData(ir.TreeNode node) {
ir.TreeNode? member = node;
while (member is! ir.Member) {
if (member == null) {
throw UnsupportedError("No enclosing member of TreeNode "
"$node (${node.runtimeType})");
}
member = member.parent;
}
_writeMemberNode(member);
return _memberData[member] ??= MemberData(member);
}
void _writeFunctionNode(ir.FunctionNode value, MemberData? memberData) {
ir.TreeNode parent = value.parent!;
if (parent is ir.Procedure) {
_sinkWriter.writeEnum(_FunctionNodeKind.procedure);
_writeMemberNode(parent);
} else if (parent is ir.Constructor) {
_sinkWriter.writeEnum(_FunctionNodeKind.constructor);
_writeMemberNode(parent);
} else if (parent is ir.FunctionExpression) {
_sinkWriter.writeEnum(_FunctionNodeKind.functionExpression);
_writeTreeNode(parent, memberData);
} else if (parent is ir.FunctionDeclaration) {
_sinkWriter.writeEnum(_FunctionNodeKind.functionDeclaration);
_writeTreeNode(parent, memberData);
} else {
throw UnsupportedError(
"Unsupported FunctionNode parent ${parent.runtimeType}");
}
}
void _writeDataKind(DataKind kind) {
if (useDataKinds) _sinkWriter.writeEnum(kind);
}
}