| // 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); |
| } |
| } |