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