blob: 5aad4b23a4a3e866524f3ecf794da6c71e3e6111 [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';
/// 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;
final bool enableDeferredStrategy;
/// 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;
DataSourceIndices? 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 = {};
EntityWriter _entityWriter = const EntityWriter();
late final CodegenWriter _codegenWriter;
final Map<String, int>? tagFrequencyMap;
ir.Member? _currentMemberContext;
MemberData? _currentMemberData;
IndexedSink<T> _createUnorderedSink<T>() {
final indices = importedIndices;
if (indices == null) return UnorderedIndexedSink<T>(this);
final sourceInfo = indices.caches[T];
if (sourceInfo == null) {
return UnorderedIndexedSink<T>(this,
startOffset: indices.previousSourceReader?.endOffset);
}
Map<T, int> cacheCopy = Map.from(sourceInfo.cache);
return UnorderedIndexedSink<T>(this,
cache: cacheCopy, startOffset: indices.previousSourceReader?.endOffset);
}
IndexedSink<T> _createSink<T>() {
final indices = importedIndices;
if (indices == null || !indices.caches.containsKey(T)) {
return OrderedIndexedSink<T>(_sinkWriter);
} else {
Map<T, int> cacheCopy = Map.from(indices.caches[T]!.cache);
return OrderedIndexedSink<T>(_sinkWriter, cache: cacheCopy);
}
}
DataSinkWriter(this._sinkWriter, CompilerOptions options,
{this.useDataKinds = false, this.tagFrequencyMap, this.importedIndices})
: enableDeferredStrategy =
options.features.deferredSerialization.isEnabled {
_dartTypeNodeWriter = DartTypeNodeWriter(this);
if (!enableDeferredStrategy) {
_stringIndex = _createSink<String>();
_uriIndex = _createSink<Uri>();
_memberNodeIndex = _createSink<ir.Member>();
_importIndex = _createSink<ImportEntity>();
_constantIndex = _createSink<ConstantValue>();
return;
}
_stringIndex = _createUnorderedSink<String>();
_uriIndex = _createUnorderedSink<Uri>();
_memberNodeIndex = _createUnorderedSink<ir.Member>();
_importIndex = _createUnorderedSink<ImportEntity>();
_constantIndex = _createUnorderedSink<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()) {
if (enableDeferredStrategy) {
_sinkWriter.writeDeferred(f);
} else {
f();
}
}
/// Writes a reference to [value] to this data sink. If [value] has not yet
/// been serialized, [f] is called to serialize the value itself.
void writeCached<E>(E? value, void f(E value)) {
IndexedSink sink = _generalCaches[E] ??=
(enableDeferredStrategy ? _createUnorderedSink<E>() : _createSink<E>());
sink.write(value, (v) => f(v));
}
/// 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. If [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readList].
void writeList<E>(Iterable<E>? values, void f(E value),
{bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
values.forEach(f);
}
}
/// Writes the boolean [value] to this data sink.
void writeBool(bool value) {
assert((value as dynamic) != null); // TODO(48820): Remove when sound.
_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 as dynamic) != null); // TODO(48820): Remove when sound.
assert(value >= 0 && value >> 30 == 0);
_writeDataKind(DataKind.uint30);
_sinkWriter.writeInt(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) {
assert((value as dynamic) != null); // TODO(48820): Remove when sound.
_writeDataKind(DataKind.string);
_writeString(value);
}
void _writeString(String value) {
_stringIndex.write(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. If [allowNull] is `true`, [map] is
/// allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStringMap].
void writeStringMap<V>(Map<String, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((String key, V value) {
writeString(key);
f(value);
});
}
}
/// Writes the string [values] to this data sink. If [allowNull] is `true`,
/// [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readStrings].
void writeStrings(Iterable<String>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (String value in values) {
writeString(value);
}
}
}
/// Writes the enum value [value] to this data sink.
// TODO(johnniwinther): Change the signature to
// `void writeEnum<E extends Enum<E>>(E value);` when an interface for enums
// is added to the language.
void writeEnum(dynamic value) {
_writeDataKind(DataKind.enumValue);
_sinkWriter.writeEnum(value);
}
/// Writes the URI [value] to this data sink.
void writeUri(Uri value) {
assert((value as dynamic) != null); // TODO(48820): Remove when sound.
_writeDataKind(DataKind.uri);
_writeUri(value);
}
void _writeUri(Uri value) {
_uriIndex.write(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 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(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.
/// If [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodes].
void writeMemberNodes(Iterable<ir.Member>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ir.Member value in values) {
writeMemberNode(value);
}
}
}
/// 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. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMemberNodeMap].
void writeMemberNodeMap<V>(Map<ir.Member, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((ir.Member key, V value) {
writeMemberNode(key);
f(value);
});
}
}
/// Writes a kernel name node to this data sink.
void writeName(ir.Name value) {
writeString(value.text);
writeValueOrNull(value.library, writeLibraryNode);
}
/// Writes a kernel library dependency node [value] to this data sink.
void writeLibraryDependencyNode(ir.LibraryDependency value) {
final library = value.parent as ir.Library;
writeLibraryNode(library);
writeInt(library.dependencies.indexOf(value));
}
/// Writes a potentially `null` kernel library dependency node [value] to
/// this data sink.
void writeLibraryDependencyNodeOrNull(ir.LibraryDependency? value) {
writeValueOrNull(value, writeLibraryDependencyNode);
}
/// 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.
/// If [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodes].
void writeTreeNodes(Iterable<ir.TreeNode>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ir.TreeNode value in values) {
writeTreeNode(value);
}
}
}
/// 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. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeMap].
void writeTreeNodeMap<V>(Map<ir.TreeNode, V> map, void f(V value)) {
writeInt(map.length);
map.forEach((ir.TreeNode key, V value) {
writeTreeNode(key);
f(value);
});
}
/// 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 references to the kernel tree node [values] in the known [context]
/// to this data sink. If [allowNull] is `true`, [values] is allowed to be
/// `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodesInContext].
void writeTreeNodesInContext(Iterable<ir.TreeNode>? values,
{bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ir.TreeNode value in values) {
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. If [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTreeNodeMapInContext].
void writeTreeNodeMapInContext<V>(Map<ir.TreeNode, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((ir.TreeNode key, V value) {
writeTreeNodeInContextInternal(key, currentMemberData);
f(value);
});
}
}
/// 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.TreeNode parent = value.parent!;
if (parent is ir.Class) {
_sinkWriter.writeEnum(_TypeParameterKind.cls);
_writeClassNode(parent);
_sinkWriter.writeInt(parent.typeParameters.indexOf(value));
} else if (parent is ir.FunctionNode) {
_sinkWriter.writeEnum(_TypeParameterKind.functionNode);
_writeFunctionNode(parent, memberData);
_sinkWriter.writeInt(parent.typeParameters.indexOf(value));
} else {
throw UnsupportedError(
"Unsupported TypeParameter parent ${parent.runtimeType}");
}
}
/// Writes references to the kernel type parameter node [values] to this data
/// sink.
/// If [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTypeParameterNodes].
void writeTypeParameterNodes(Iterable<ir.TypeParameter> values) {
writeInt(values.length);
for (ir.TypeParameter value in values) {
writeTypeParameterNode(value);
}
}
/// 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) {
writeInt(values.length);
for (DartType value in values) {
writeDartType(value);
}
}
/// 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.TypeParameter> 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. If [allowNull] is
/// `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readDartTypeNodes].
void writeDartTypeNodes(Iterable<ir.DartType>? values,
{bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ir.DartType value in values) {
writeDartTypeNode(value);
}
}
}
/// 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 IndexedLibrary) {
_entityWriter.writeLibraryToDataSink(this, value);
} 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. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLibraryMap].
void writeLibraryMap<V>(Map<LibraryEntity, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((LibraryEntity library, V value) {
writeLibrary(library);
f(value);
});
}
}
/// Writes a reference to the class entity [value] to this data sink.
void writeClass(ClassEntity value) {
if (value is IndexedClass) {
_entityWriter.writeClassToDataSink(this, value);
} 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. If
/// [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClasses].
void writeClasses(Iterable<ClassEntity>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ClassEntity value in values) {
writeClass(value);
}
}
}
/// 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. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readClassMap].
void writeClassMap<V>(Map<ClassEntity, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((ClassEntity cls, V value) {
writeClass(cls);
f(value);
});
}
}
/// Writes a reference to the member entity [value] to this data sink.
void writeMember(MemberEntity value) {
if (value is IndexedMember) {
_entityWriter.writeMemberToDataSink(this, value);
} 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. If
/// [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readMembers].
void writeMembers(Iterable<MemberEntity>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (MemberEntity value in values) {
writeMember(value);
}
}
}
/// 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. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// 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),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
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 IndexedTypeVariable) {
_entityWriter.writeTypeVariableToDataSink(this, value);
} else {
failedAt(
value, 'Unexpected type variable entity type ${value.runtimeType}');
}
}
/// Writes the [map] from references to type variable entites to [V] values
/// to this data sink, calling [f] to write each value to the data sink. If
/// [allowNull] is `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readTypeVariableMap].
void writeTypeVariableMap<V>(
Map<TypeVariableEntity, V> map, void f(V value)) {
writeInt(map.length);
map.forEach((TypeVariableEntity key, V value) {
writeTypeVariable(key);
f(value);
});
}
/// Writes a reference to the local [value] to this data sink.
void writeLocal(Local local) {
if (local is JLocal) {
writeEnum(LocalKind.jLocal);
writeMember(local.memberContext);
writeInt(local.localIndex);
} 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. If [allowNull] is
/// `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readLocalMap].
void writeLocalMap<V>(Map<Local, V> map, void f(V value)) {
writeInt(map.length);
map.forEach((Local key, V value) {
writeLocal(key);
f(value);
});
}
/// Writes the constant [value] to this data sink.
void writeConstant(ConstantValue value) {
_writeDataKind(DataKind.constant);
_writeConstant(value);
}
void _writeConstant(ConstantValue value) {
_constantIndex.write(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);
writeConstant(constant.entries);
break;
case ConstantValueKind.MAP:
final constant = value as constant_system.JavaScriptMapConstant;
writeDartType(constant.type);
writeConstant(constant.keyList);
writeConstants(constant.values);
writeBool(constant.onlyStringKeys);
break;
case ConstantValueKind.CONSTRUCTED:
final constant = value as ConstructedConstantValue;
writeDartType(constant.type);
writeMemberMap(constant.fields,
(MemberEntity member, ConstantValue value) => writeConstant(value));
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.NON_CONSTANT:
break;
case ConstantValueKind.INTERCEPTOR:
final constant = value as InterceptorConstantValue;
writeClass(constant.cls);
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. If [allowNull] is `true`,
/// [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readConstants].
void writeConstants(Iterable<ConstantValue>? values,
{bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ConstantValue value in values) {
writeConstant(value);
}
}
}
/// Writes the [map] from constant values to [V] values to this data sink,
/// calling [f] to write each value to the data sink. If [allowNull] is
/// `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readConstantMap].
void writeConstantMap<V>(Map<ConstantValue, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((ConstantValue key, V value) {
writeConstant(key);
f(value);
});
}
}
/// 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(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. If [allowNull] is `true`,
/// [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readImports].
void writeImports(Iterable<ImportEntity>? values, {bool allowNull = false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (ImportEntity value in values) {
writeImport(value);
}
}
}
/// Writes the [map] from imports to [V] values to this data sink,
/// calling [f] to write each value to the data sink. If [allowNull] is
/// `true`, [map] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSourceReader.readImportMap].
void writeImportMap<V>(Map<ImportEntity, V>? map, void f(V value),
{bool allowNull = false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((ImportEntity key, V value) {
writeImport(key);
f(value);
});
}
}
/// Writes an abstract [value] to this data sink.
///
/// This feature is only available a [CodegenWriter] has been registered.
void writeAbstractValue(AbstractValue value) {
_codegenWriter.writeAbstractValue(this, value);
}
/// Writes a reference to the output unit [value] to this data sink.
///
/// This feature is only available a [CodegenWriter] has been registered.
void writeOutputUnitReference(OutputUnit value) {
_codegenWriter.writeOutputUnitReference(this, value);
}
/// Writes a js node [value] to this data sink.
///
/// This feature is only available a [CodegenWriter] has been registered.
void writeJsNode(js.Node value) {
_codegenWriter.writeJsNode(this, value);
}
/// Writes a potentially `null` js node [value] to this data sink.
///
/// This feature is only available a [CodegenWriter] has been registered.
void writeJsNodeOrNull(js.Node? value) {
writeBool(value != null);
if (value != null) {
writeJsNode(value);
}
}
/// Writes TypeRecipe [value] to this data sink.
///
/// This feature is only available a [CodegenWriter] has been registered.
void writeTypeRecipe(TypeRecipe value) {
_codegenWriter.writeTypeRecipe(this, value);
}
/// Register an [EntityWriter] with this data sink for non-default encoding
/// of entity references.
void registerEntityWriter(EntityWriter writer) {
assert((writer as dynamic) != null); // TODO(48820): Remove when sound.
_entityWriter = writer;
}
/// Register a [CodegenWriter] with this data sink to support serialization
/// of codegen only data.
void registerCodegenWriter(CodegenWriter writer) {
assert((writer as dynamic) != null); // TODO(48820): Remove when sound.
_codegenWriter = writer;
}
/// 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);
}
}