| // 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 Function() 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 Function() 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 Function(E value) f, { | |
| 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 Function(E value) f) { | |
| 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 Function(E value) f) { | |
| 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 Function(E value) f) { | |
| 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 Function(K key) k, | |
| void Function(V value) v, | |
| ) { | |
| 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 Function(K key) k, | |
| void Function(V value) v, | |
| ) { | |
| 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 Function(V value) f) { | |
| 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 Function(V value) f) { | |
| 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 Function(V value) f) { | |
| 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 Function(V value) f) { | |
| 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 Function(V value) f, | |
| ) { | |
| 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.Variable && 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 Function(V value) f) { | |
| 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 Function(V value) f, | |
| ) { | |
| 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 Function(V value) f, | |
| ) { | |
| 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 Function(V value) f) { | |
| 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 Function(V value) f) { | |
| 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 Function(MemberEntity member, V value) f, | |
| ) { | |
| 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 Function(V value) f, | |
| ) { | |
| 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 Function(V value) f) { | |
| writeMap(map, writeLocal, f); | |
| } | |
| /// Writes the [map] from selectors 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.readSelectorMap]. | |
| void writeSelectorMap<V>(Map<Selector, V> map, void Function(V value) f) { | |
| writeMap(map, (Selector selector) { | |
| selector.writeToDataSink(this); | |
| }, 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.javaScriptObject: | |
| final constant = value as JavaScriptObjectConstantValue; | |
| writeConstants(constant.keys); | |
| writeConstants(constant.values); | |
| break; | |
| case ConstantValueKind.deferredGlobal: | |
| final constant = value as DeferredGlobalConstantValue; | |
| writeConstant(constant.referenced); | |
| writeOutputUnitReference(constant.unit); | |
| break; | |
| case ConstantValueKind.dummy: | |
| break; | |
| case ConstantValueKind.lateSentinel: | |
| break; | |
| case ConstantValueKind.unreachable: | |
| break; | |
| case ConstantValueKind.jsName: | |
| 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 Function(V value) f, | |
| ) { | |
| 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 Function(V value) f) { | |
| 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 Function() 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); | |
| } | |
| } |