[kernel, vm] Revise how metadata is written in kernel binaries

Metadata is no longer written ahead of all nodes. Instead, metadata for
each node is written in the same context as the node itself (into a separate
buffer). This allows metadata to contain (serialize) arbitrary nodes
(for example, arbitrary DartTypes) and use serialization context of parent
nodes (such as declared type parameters).

However, with this change metadata looses the ability to reference
arbitrary AST nodes. This ability was overly restricted and had no
practical uses. (It was not possible to reference nodes which are not
reachable from root Component. As a consequence, it was not possible to
write references to arbitrary DartTypes.)

This change aligns the serialization capabilities of metadata with
how kernel AST nodes are serialized.

Change-Id: I027299a33b599b62572eccd4aa7083ad1dd2b3b3
Reviewed-on: https://dart-review.googlesource.com/54481
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/kernel_metadata.dart b/pkg/analyzer/lib/src/dart/analysis/kernel_metadata.dart
index f96c5c6..6962a80 100644
--- a/pkg/analyzer/lib/src/dart/analysis/kernel_metadata.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/kernel_metadata.dart
@@ -79,14 +79,16 @@
       <kernel.TreeNode, AnalyzerMetadata>{};
 
   @override
-  AnalyzerMetadata readFromBinary(kernel.BinarySource source) {
+  AnalyzerMetadata readFromBinary(
+      kernel.Node node, kernel.BinarySource source) {
     return new AnalyzerMetadata()
       ..constructorNameOffset = _readOffset(source)
       ..documentationComment = _readOptionalString(source);
   }
 
   @override
-  void writeToBinary(AnalyzerMetadata metadata, kernel.BinarySink sink) {
+  void writeToBinary(
+      AnalyzerMetadata metadata, kernel.Node node, kernel.BinarySink sink) {
     _writeOffset(sink, metadata.constructorNameOffset);
     _writeOptionalString(sink, metadata.documentationComment);
   }
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 7bb45af..13b28bf 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -131,11 +131,11 @@
 
 type ComponentFile {
   UInt32 magic = 0x90ABCDEF;
-  UInt32 formatVersion;
-  MetadataPayload[] metadataPayloads;
+  UInt32 formatVersion = 6;
   Library[] libraries;
   UriSource sourceMap;
   List<CanonicalName> canonicalNames;
+  MetadataPayload[] metadataPayloads;
   RList<MetadataMapping> metadataMappings;
   StringTable strings;
   List<Constant> constants;
@@ -149,9 +149,8 @@
 
 type MetadataMapping {
   UInt32 tag;  // StringReference of a fixed size.
+  // Node offsets are absolute, while metadata offsets are relative to metadataPayloads.
   RList<Pair<UInt32, UInt32>> nodeOffsetToMetadataOffset;
-  RList<UInt32> nodeReferences;  // If metadata payload references nodes
-                              // they are encoded as indices in this array.
 }
 
 // Component index with all fixed-size-32-bit integers.
@@ -162,6 +161,8 @@
 type ComponentIndex {
   UInt32 binaryOffsetForSourceTable;
   UInt32 binaryOffsetForCanonicalNames;
+  UInt32 binaryOffsetForMetadataPayloads;
+  UInt32 binaryOffsetForMetadataMappings;
   UInt32 binaryOffsetForStringTable;
   UInt32 binaryOffsetForConstantTable;
   UInt32 mainMethodReference; // This is a ProcedureReference with a fixed-size integer.
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index a87b1b4..eb7c8d8a 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -5527,14 +5527,28 @@
   /// Mutable mapping between nodes and their metadata.
   Map<TreeNode, T> get mapping;
 
-  /// Write the given metadata object into the given [BinarySink].
+  /// Write [metadata] object corresponding to the given [Node] into
+  /// the given [BinarySink].
   ///
-  /// Note: [metadata] must be an object owned by this repository.
-  void writeToBinary(T metadata, BinarySink sink);
+  /// Metadata is serialized immediately before serializing [node],
+  /// so implementation of this method can use serialization context of
+  /// [node]'s parents (such as declared type parameters and variables).
+  /// In order to use scope declared by the [node] itself, implementation of
+  /// this method can use [BinarySink.enterScope] and [BinarySink.leaveScope]
+  /// methods.
+  ///
+  /// [metadata] must be an object owned by this repository.
+  void writeToBinary(T metadata, Node node, BinarySink sink);
 
   /// Construct a metadata object from its binary payload read from the
   /// given [BinarySource].
-  T readFromBinary(BinarySource source);
+  ///
+  /// Metadata is deserialized immediately after deserializing [node],
+  /// so it can use deserialization context of [node]'s parents.
+  /// In order to use scope declared by the [node] itself, implementation of
+  /// this method can use [BinarySource.enterScope] and
+  /// [BinarySource.leaveScope] methods.
+  T readFromBinary(Node node, BinarySource source);
 
   /// Method to check whether a node can have metadata attached to it
   /// or referenced from the metadata payload.
@@ -5557,12 +5571,16 @@
 
   void writeCanonicalNameReference(CanonicalName name);
   void writeStringReference(String str);
+  void writeDartType(DartType type);
 
-  /// Write a reference to a given node into the sink.
-  ///
-  /// Note: node must not be [MapEntry] because [MapEntry] and [MapEntry.key]
-  /// have the same offset in the binary and can't be distinguished.
-  void writeNodeReference(Node node);
+  void enterScope(
+      {List<TypeParameter> typeParameters,
+      bool memberScope: false,
+      bool variableScope: false});
+  void leaveScope(
+      {List<TypeParameter> typeParameters,
+      bool memberScope: false,
+      bool variableScope: false});
 }
 
 abstract class BinarySource {
@@ -5578,7 +5596,10 @@
 
   CanonicalName readCanonicalNameReference();
   String readStringReference();
-  Node readNodeReference();
+  DartType readDartType();
+
+  void enterScope({List<TypeParameter> typeParameters});
+  void leaveScope({List<TypeParameter> typeParameters});
 }
 
 // ------------------------------------------------------------------------
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 02ca4f3..5abf6de 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -23,9 +23,13 @@
 }
 
 class _ComponentIndex {
+  static const numberOfFixedFields = 9;
+
   int binaryOffsetForSourceTable;
-  int binaryOffsetForStringTable;
   int binaryOffsetForCanonicalNames;
+  int binaryOffsetForMetadataPayloads;
+  int binaryOffsetForMetadataMappings;
+  int binaryOffsetForStringTable;
   int binaryOffsetForConstantTable;
   int mainMethodReference;
   List<int> libraryOffsets;
@@ -141,19 +145,16 @@
     return string;
   }
 
-  /// Read metadataMappings section from the binary. Return [true] if
-  /// any metadata mapping contains metadata with node references.
-  /// In this case we need to disable lazy loading of the binary.
-  bool _readMetadataSection(Component component) {
+  /// Read metadataMappings section from the binary.
+  void _readMetadataMappings(
+      Component component, int binaryOffsetForMetadataPayloads) {
     // Default reader ignores metadata section entirely.
-    return false;
   }
 
-  /// Process any pending metadata associations. Called once the Component
-  /// is fully deserialized and metadata containing references to nodes can
-  /// be safely parsed.
-  void _processPendingMetadataAssociations(Component component) {
+  /// Reads metadata for the given [node].
+  Node _associateMetadata(Node node, int nodeOffset) {
     // Default reader ignores metadata section entirely.
+    return node;
   }
 
   void readStringTable(List<String> table) {
@@ -445,14 +446,16 @@
     }
 
     // Skip to the start of the index.
-    // There are these fields: file size, library count, library count + 1
-    // offsets, main reference, string table offset, canonical name offset and
-    // source table offset. That's 6 fields + number of libraries.
-    _byteOffset -= (result.libraryCount + 8) * 4;
+    _byteOffset -=
+        ((result.libraryCount + 1) + _ComponentIndex.numberOfFixedFields) * 4;
 
     // Now read the component index.
     result.binaryOffsetForSourceTable = _componentStartOffset + readUint32();
     result.binaryOffsetForCanonicalNames = _componentStartOffset + readUint32();
+    result.binaryOffsetForMetadataPayloads =
+        _componentStartOffset + readUint32();
+    result.binaryOffsetForMetadataMappings =
+        _componentStartOffset + readUint32();
     result.binaryOffsetForStringTable = _componentStartOffset + readUint32();
     result.binaryOffsetForConstantTable = _componentStartOffset + readUint32();
     result.mainMethodReference = readUint32();
@@ -489,9 +492,9 @@
     _byteOffset = index.binaryOffsetForCanonicalNames;
     readLinkTable(component.root);
 
-    _byteOffset = index.binaryOffsetForStringTable;
-    _disableLazyReading =
-        _readMetadataSection(component) || _disableLazyReading;
+    // TODO(alexmarkov): reverse metadata mappings and read forwards
+    _byteOffset = index.binaryOffsetForStringTable; // Read backwards.
+    _readMetadataMappings(component, index.binaryOffsetForMetadataPayloads);
 
     _byteOffset = index.binaryOffsetForSourceTable;
     Map<Uri, Source> uriToSource = readUriToSource();
@@ -510,7 +513,7 @@
         getMemberReferenceFromInt(index.mainMethodReference, allowNull: true);
     component.mainMethodName ??= mainMethod;
 
-    _processPendingMetadataAssociations(component);
+    _associateMetadata(component, _componentStartOffset);
 
     _byteOffset = _componentStartOffset + componentFileSize;
   }
@@ -1847,17 +1850,12 @@
   /// and are awaiting to be parsed and attached to nodes.
   List<_MetadataSubsection> _subsections;
 
-  /// Current mapping from node references to nodes used by readNodeReference.
-  /// Note: each metadata subsection has its own mapping.
-  List<Node> _referencedNodes;
-
   BinaryBuilderWithMetadata(bytes, [filename])
       : super(bytes, filename: filename);
 
   @override
-  bool _readMetadataSection(Component component) {
-    bool containsNodeReferences = false;
-
+  void _readMetadataMappings(
+      Component component, int binaryOffsetForMetadataPayloads) {
     // At the beginning of this function _byteOffset points right past
     // metadataMappings to string table.
 
@@ -1867,15 +1865,10 @@
 
     int endOffset = _byteOffset - 4; // End offset of the current subsection.
     for (var i = 0; i < subSectionCount; i++) {
-      // RList<UInt32> nodeReferences
-      _byteOffset = endOffset - 4;
-      final referencesLength = readUint32();
-      final referencesStart = (endOffset - 4) - 4 * referencesLength;
-
       // RList<Pair<UInt32, UInt32>> nodeOffsetToMetadataOffset
-      _byteOffset = referencesStart - 4;
+      _byteOffset = endOffset - 4;
       final mappingLength = readUint32();
-      final mappingStart = (referencesStart - 4) - 4 * 2 * mappingLength;
+      final mappingStart = (endOffset - 4) - 4 * 2 * mappingLength;
       _byteOffset = mappingStart - 4;
 
       // UInt32 tag (fixed size StringReference)
@@ -1883,73 +1876,49 @@
 
       final repository = component.metadata[tag];
       if (repository != null) {
-        // Read nodeReferences (if any).
-        Map<int, int> offsetToReferenceId;
-        List<Node> referencedNodes;
-        if (referencesLength > 0) {
-          offsetToReferenceId = <int, int>{};
-          _byteOffset = referencesStart;
-          for (var j = 0; j < referencesLength; j++) {
-            final nodeOffset = readUint32();
-            offsetToReferenceId[nodeOffset] = j;
-          }
-          referencedNodes =
-              new List<Node>.filled(referencesLength, null, growable: true);
-          containsNodeReferences = true;
-        }
-
         // Read nodeOffsetToMetadataOffset mapping.
         final mapping = <int, int>{};
         _byteOffset = mappingStart;
         for (var j = 0; j < mappingLength; j++) {
           final nodeOffset = readUint32();
-          final metadataOffset = readUint32();
+          final metadataOffset = binaryOffsetForMetadataPayloads + readUint32();
           mapping[nodeOffset] = metadataOffset;
         }
 
         _subsections ??= <_MetadataSubsection>[];
-        _subsections.add(new _MetadataSubsection(
-            repository, mapping, offsetToReferenceId, referencedNodes));
+        _subsections.add(new _MetadataSubsection(repository, mapping));
       }
 
       // Start of the subsection and the end of the previous one.
       endOffset = mappingStart - 4;
     }
-
-    return containsNodeReferences;
   }
 
-  @override
-  void _processPendingMetadataAssociations(Component component) {
-    if (_subsections == null) {
-      return;
-    }
-
-    _associateMetadata(component, _componentStartOffset);
-
-    for (var subsection in _subsections) {
-      if (subsection.pending == null) {
-        continue;
-      }
-
-      _referencedNodes = subsection.referencedNodes;
-      for (var i = 0; i < subsection.pending.length; i += 2) {
-        final Node node = subsection.pending[i];
-        final int metadataOffset = subsection.pending[i + 1];
-        subsection.repository.mapping[node] =
-            _readMetadata(subsection.repository, metadataOffset);
-      }
-    }
-  }
-
-  Object _readMetadata(MetadataRepository repository, int offset) {
+  Object _readMetadata(Node node, MetadataRepository repository, int offset) {
     final int savedOffset = _byteOffset;
     _byteOffset = offset;
-    final metadata = repository.readFromBinary(this);
+
+    final metadata = repository.readFromBinary(node, this);
+
     _byteOffset = savedOffset;
     return metadata;
   }
 
+  @override
+  void enterScope({List<TypeParameter> typeParameters}) {
+    if (typeParameters != null) {
+      typeParameterStack.addAll(typeParameters);
+    }
+  }
+
+  @override
+  void leaveScope({List<TypeParameter> typeParameters}) {
+    if (typeParameters != null) {
+      typeParameterStack.length -= typeParameters.length;
+    }
+  }
+
+  @override
   Node _associateMetadata(Node node, int nodeOffset) {
     if (_subsections == null) {
       return node;
@@ -1959,28 +1928,8 @@
       // First check if there is any metadata associated with this node.
       final metadataOffset = subsection.mapping[nodeOffset];
       if (metadataOffset != null) {
-        if (subsection.nodeOffsetToReferenceId == null) {
-          // This subsection does not contain any references to nodes from
-          // inside the payload. In this case we can deserialize metadata
-          // eagerly.
-          subsection.repository.mapping[node] =
-              _readMetadata(subsection.repository, metadataOffset);
-        } else {
-          // Metadata payload might contain references to nodes that
-          // are not yet deserialized. Postpone association of metadata
-          // with this node.
-          subsection.pending ??= <Object>[];
-          subsection.pending..add(node)..add(metadataOffset);
-        }
-      }
-
-      // Check if this node is referenced from this section and update
-      // referencedNodes array if that is the case.
-      if (subsection.nodeOffsetToReferenceId != null) {
-        final id = subsection.nodeOffsetToReferenceId[nodeOffset];
-        if (id != null) {
-          subsection.referencedNodes[id] = node;
-        }
+        subsection.repository.mapping[node] =
+            _readMetadata(node, subsection.repository, metadataOffset);
       }
     }
 
@@ -2146,12 +2095,6 @@
 
   @override
   List<int> get bytes => _bytes;
-
-  @override
-  Node readNodeReference() {
-    final id = readUInt();
-    return id == 0 ? null : _referencedNodes[id - 1];
-  }
 }
 
 /// Deserialized MetadataMapping corresponding to the given metadata repository.
@@ -2162,18 +2105,5 @@
   /// Deserialized mapping from node offsets to metadata offsets.
   final Map<int, int> mapping;
 
-  /// Deserialized mapping from node offset to node reference ids.
-  final Map<int, int> nodeOffsetToReferenceId;
-
-  /// Array mapping node reference ids to corresponding nodes.
-  /// Will be gradually filled as nodes are deserialized.
-  final List<Node> referencedNodes;
-
-  /// A list of pairs (Node, int metadataOffset) that describes pending
-  /// metadata associations which will be processed once all nodes
-  /// are parsed.
-  List<Object> pending;
-
-  _MetadataSubsection(this.repository, this.mapping,
-      this.nodeOffsetToReferenceId, this.referencedNodes);
+  _MetadataSubsection(this.repository, this.mapping);
 }
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 0d8a0de..c99b35e 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -8,6 +8,7 @@
 import '../ast.dart';
 import 'tag.dart';
 import 'dart:convert';
+import 'dart:io' show BytesBuilder;
 import 'dart:typed_data';
 
 /// Writes to a binary file.
@@ -28,18 +29,18 @@
 
   List<_MetadataSubsection> _metadataSubsections;
 
-  /// Map used to assign reference ids to nodes contained within metadata
-  /// payloads.
-  Map<Node, int> _nodeReferences;
-
-  final BufferedSink _sink;
+  final BufferedSink _mainSink;
+  final BufferedSink _metadataSink;
+  BufferedSink _sink;
 
   List<int> libraryOffsets;
   List<int> classOffsets;
   List<int> procedureOffsets;
   int _binaryOffsetForSourceTable = -1;
-  int _binaryOffsetForStringTable = -1;
   int _binaryOffsetForLinkTable = -1;
+  int _binaryOffsetForMetadataPayloads = -1;
+  int _binaryOffsetForMetadataMappings = -1;
+  int _binaryOffsetForStringTable = -1;
   int _binaryOffsetForConstantTable = -1;
 
   List<CanonicalName> _canonicalNameList;
@@ -55,9 +56,11 @@
   /// [globalIndexer] may be passed in to avoid rebuilding the same indices
   /// in every printer.
   BinaryPrinter(Sink<List<int>> sink, {StringIndexer stringIndexer})
-      : _sink = new BufferedSink(sink),
+      : _mainSink = new BufferedSink(sink),
+        _metadataSink = new BufferedSink(new BytesSink()),
         stringIndexer = stringIndexer ?? new StringIndexer() {
     _constantIndexer = new ConstantIndexer(this.stringIndexer);
+    _sink = _mainSink;
   }
 
   void _flush() {
@@ -240,7 +243,7 @@
 
   void writeNode(Node node) {
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMapping(node);
+      _writeNodeMetadata(node);
     }
     node.accept(this);
   }
@@ -311,11 +314,9 @@
     writeUInt32(Tag.BinaryFormatVersion);
     indexLinkTable(component);
     indexUris(component);
-    // Note: must write metadata payloads before any other node in the component
-    // to collect references to nodes contained within metadata payloads.
-    _writeMetadataPayloads(component);
+    _collectMetadata(component);
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMappingImpl(component, componentOffset);
+      _writeNodeMetadataImpl(component, componentOffset);
     }
     libraryOffsets = <int>[];
     CanonicalName main = getCanonicalNameOfMember(component.mainMethod);
@@ -325,7 +326,7 @@
     writeLibraries(component);
     writeUriToSource(component.uriToSource);
     writeLinkTable(component);
-    _writeMetadataMappingSection(component);
+    _writeMetadataSection(component);
     writeStringTable(stringIndexer);
     writeConstantTable(_constantIndexer);
     writeComponentIndex(component, component.libraries);
@@ -333,87 +334,95 @@
     _flush();
   }
 
-  @override
-  void writeNodeReference(Node node) {
-    if (!MetadataRepository.isSupported(node)) {
-      throw "Can't reference nodes of type ${node.runtimeType} from metadata.";
-    }
-
-    if (node == null) {
-      writeUInt30(0);
-    } else {
-      final id =
-          _nodeReferences.putIfAbsent(node, () => _nodeReferences.length);
-      writeUInt30(id + 1);
-    }
-  }
-
-  /// Collect and write out all metadata contained in metadata repositories
-  /// associated with the component.
-  ///
-  /// Non-empty metadata subsections will be collected in [_metadataSubsections]
-  /// and used to generate metadata mappings after all nodes in the component
-  /// are written and all node offsets are known.
-  ///
-  /// Note: must write metadata payloads before any other node in the component
-  /// to collect references to nodes contained within metadata payloads.
-  void _writeMetadataPayloads(Component component) {
+  /// Collect non-empty metadata repositories associated with the component.
+  void _collectMetadata(Component component) {
     component.metadata.forEach((tag, repository) {
       if (repository.mapping.isEmpty) {
         return;
       }
 
-      // Write all payloads collecting outgoing node references and remembering
-      // metadata offset for each node that had associated metadata.
-      _nodeReferences = <Node, int>{};
-      final metadataOffsets = <Node, int>{};
-      repository.mapping.forEach((node, value) {
-        if (!MetadataRepository.isSupported(node)) {
-          throw "Nodes of type ${node.runtimeType} can't have metadata.";
-        }
-
-        metadataOffsets[node] = getBufferOffset();
-        repository.writeToBinary(value, this);
-      });
-
       _metadataSubsections ??= <_MetadataSubsection>[];
-      _metadataSubsections.add(new _MetadataSubsection(
-          repository, metadataOffsets, _nodeReferences));
-
-      _nodeReferences = null;
+      _metadataSubsections.add(new _MetadataSubsection(repository));
     });
   }
 
-  /// If the given [Node] has any metadata associated with it or is referenced
-  /// from some metadata payload then we need to record its offset.
-  void _recordNodeOffsetForMetadataMapping(Node node) {
-    _recordNodeOffsetForMetadataMappingImpl(node, getBufferOffset());
+  /// Writes metadata associated with the given [Node].
+  void _writeNodeMetadata(Node node) {
+    _writeNodeMetadataImpl(node, getBufferOffset());
   }
 
-  void _recordNodeOffsetForMetadataMappingImpl(Node node, int nodeOffset) {
+  void _writeNodeMetadataImpl(Node node, int nodeOffset) {
     for (var subsection in _metadataSubsections) {
-      final metadataOffset = subsection.metadataOffsets[node];
-      if (metadataOffset != null) {
-        subsection.metadataMapping..add(nodeOffset)..add(metadataOffset);
+      final repository = subsection.repository;
+      final value = repository.mapping[node];
+      if (value == null) {
+        continue;
       }
-      if (subsection.nodeToReferenceId != null) {
-        final id = subsection.nodeToReferenceId[node];
-        if (id != null) {
-          subsection.offsetsOfReferencedNodes[id] = nodeOffset;
-        }
+
+      if (!MetadataRepository.isSupported(node)) {
+        throw "Nodes of type ${node.runtimeType} can't have metadata.";
       }
+
+      if (!identical(_sink, _mainSink)) {
+        throw "Node written into metadata can't have metadata "
+            "(metadata: ${repository.tag}, node: ${node.runtimeType} $node)";
+      }
+
+      _sink = _metadataSink;
+      subsection.metadataMapping.add(nodeOffset);
+      subsection.metadataMapping.add(getBufferOffset());
+      repository.writeToBinary(value, node, this);
+      _sink = _mainSink;
     }
   }
 
-  void _writeMetadataMappingSection(Component component) {
+  @override
+  void enterScope(
+      {List<TypeParameter> typeParameters,
+      bool memberScope: false,
+      bool variableScope: false}) {
+    if (typeParameters != null) {
+      _typeParameterIndexer.enter(typeParameters);
+    }
+    if (memberScope) {
+      _variableIndexer = new VariableIndexer();
+    }
+    if (variableScope) {
+      _variableIndexer.pushScope();
+    }
+  }
+
+  @override
+  void leaveScope(
+      {List<TypeParameter> typeParameters,
+      bool memberScope: false,
+      bool variableScope: false}) {
+    if (variableScope) {
+      _variableIndexer.popScope();
+    }
+    if (memberScope) {
+      _variableIndexer = null;
+    }
+    if (typeParameters != null) {
+      _typeParameterIndexer.exit(typeParameters);
+    }
+  }
+
+  void _writeMetadataSection(Component component) {
+    _binaryOffsetForMetadataPayloads = getBufferOffset();
+
     if (_metadataSubsections == null) {
+      _binaryOffsetForMetadataMappings = getBufferOffset();
       writeUInt32(0); // Empty section.
       return;
     }
 
-    _recordNodeOffsetForMetadataMappingImpl(component, 0);
+    assert(identical(_sink, _mainSink));
+    _metadataSink.flushAndDestroy();
+    writeBytes((_metadataSink._sink as BytesSink).builder.takeBytes());
 
     // RList<MetadataMapping> metadataMappings
+    _binaryOffsetForMetadataMappings = getBufferOffset();
     for (var subsection in _metadataSubsections) {
       // UInt32 tag
       writeUInt32(stringIndexer.put(subsection.repository.tag));
@@ -425,32 +434,6 @@
         writeUInt32(subsection.metadataMapping[i + 1]); // metadata offset
       }
       writeUInt32(mappingLength ~/ 2);
-
-      // RList<UInt32> nodeReferences
-      if (subsection.nodeToReferenceId != null) {
-        final offsets = subsection.offsetsOfReferencedNodes;
-        for (int i = 0; i < offsets.length; ++i) {
-          final nodeOffset = offsets[i];
-          if (nodeOffset < 0) {
-            // Dangling reference.
-            // Find a node which was referenced to report meaningful error.
-            Node referencedNode;
-            subsection.nodeToReferenceId.forEach((node, id) {
-              if (id == i) {
-                referencedNode = node;
-              }
-            });
-            throw 'Unable to write reference to node'
-                ' ${referencedNode.runtimeType} $referencedNode'
-                ' from metadata ${subsection.repository.tag}'
-                ' (node is not written into kernel binary)';
-          }
-          writeUInt32(nodeOffset);
-        }
-        writeUInt32(subsection.offsetsOfReferencedNodes.length);
-      } else {
-        writeUInt32(0);
-      }
     }
     writeUInt32(_metadataSubsections.length);
   }
@@ -466,6 +449,10 @@
     writeUInt32(_binaryOffsetForSourceTable);
     assert(_binaryOffsetForLinkTable >= 0);
     writeUInt32(_binaryOffsetForLinkTable);
+    assert(_binaryOffsetForMetadataPayloads >= 0);
+    writeUInt32(_binaryOffsetForMetadataPayloads);
+    assert(_binaryOffsetForMetadataMappings >= 0);
+    writeUInt32(_binaryOffsetForMetadataMappings);
     assert(_binaryOffsetForStringTable >= 0);
     writeUInt32(_binaryOffsetForStringTable);
     assert(_binaryOffsetForConstantTable >= 0);
@@ -597,7 +584,7 @@
 
   void writeName(Name node) {
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMapping(node);
+      _writeNodeMetadata(node);
     }
     writeStringReference(node.name);
     // TODO: Consider a more compressed format for private names within the
@@ -671,7 +658,7 @@
 
   void writeLibraryDependency(LibraryDependency node) {
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMapping(node);
+      _writeNodeMetadata(node);
     }
     writeOffset(node.fileOffset);
     writeByte(node.flags);
@@ -696,7 +683,7 @@
 
   void writeLibraryPart(LibraryPart node) {
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMapping(node);
+      _writeNodeMetadata(node);
     }
     writeAnnotationList(node.annotations);
     writeStringReference(node.partUri);
@@ -711,10 +698,10 @@
     writeOffset(node.fileOffset);
     writeStringReference(node.name);
     writeAnnotationList(node.annotations);
-    _typeParameterIndexer.enter(node.typeParameters);
+    enterScope(typeParameters: node.typeParameters);
     writeNodeList(node.typeParameters);
     writeNode(node.type);
-    _typeParameterIndexer.exit(node.typeParameters);
+    leaveScope(typeParameters: node.typeParameters);
 
     _activeFileUri = activeFileUriSaved;
   }
@@ -767,7 +754,7 @@
     writeStringReference(node.name ?? '');
 
     writeAnnotationList(node.annotations);
-    _typeParameterIndexer.enter(node.typeParameters);
+    enterScope(typeParameters: node.typeParameters);
     writeNodeList(node.typeParameters);
     writeOptionalNode(node.supertype);
     writeOptionalNode(node.mixedInType);
@@ -778,7 +765,7 @@
     writeNodeList(node.procedures);
     procedureOffsets.add(getBufferOffset());
     writeNodeList(node.redirectingFactoryConstructors);
-    _typeParameterIndexer.exit(node.typeParameters);
+    leaveScope(typeParameters: node.typeParameters);
 
     _activeFileUri = activeFileUriSaved;
 
@@ -796,7 +783,7 @@
     if (node.canonicalName == null) {
       throw 'Missing canonical name for $node';
     }
-    _variableIndexer = new VariableIndexer();
+    enterScope(memberScope: true);
     writeByte(Tag.Constructor);
     writeCanonicalNameReference(getCanonicalNameOfMember(node));
 
@@ -818,7 +805,7 @@
 
     _activeFileUri = activeFileUriSaved;
 
-    _variableIndexer = null;
+    leaveScope(memberScope: true);
   }
 
   @override
@@ -828,7 +815,7 @@
     if (node.canonicalName == null) {
       throw 'Missing canonical name for $node';
     }
-    _variableIndexer = new VariableIndexer();
+    enterScope(memberScope: true);
     writeByte(Tag.Procedure);
     writeCanonicalNameReference(getCanonicalNameOfMember(node));
 
@@ -847,7 +834,7 @@
 
     _activeFileUri = activeFileUriSaved;
 
-    _variableIndexer = null;
+    leaveScope(memberScope: true);
 
     assert((node.forwardingStubSuperTarget != null) ||
         !(node.isForwardingStub && node.function.body != null));
@@ -858,7 +845,7 @@
     if (node.canonicalName == null) {
       throw 'Missing canonical name for $node';
     }
-    _variableIndexer = new VariableIndexer();
+    enterScope(memberScope: true);
     writeByte(Tag.Field);
     writeCanonicalNameReference(getCanonicalNameOfMember(node));
 
@@ -875,7 +862,7 @@
 
     _activeFileUri = activeFileUriSaved;
 
-    _variableIndexer = null;
+    leaveScope(memberScope: true);
   }
 
   @override
@@ -884,9 +871,10 @@
       throw 'Missing canonical name for $node';
     }
     writeByte(Tag.RedirectingFactoryConstructor);
-    _variableIndexer = new VariableIndexer();
-    _variableIndexer.pushScope();
-    _typeParameterIndexer.enter(node.typeParameters);
+    enterScope(
+        typeParameters: node.typeParameters,
+        memberScope: true,
+        variableScope: true);
     writeCanonicalNameReference(getCanonicalNameOfMember(node));
 
     final Uri activeFileUriSaved = _activeFileUri;
@@ -905,12 +893,13 @@
     writeUInt30(node.requiredParameterCount);
     writeVariableDeclarationList(node.positionalParameters);
     writeVariableDeclarationList(node.namedParameters);
-    _typeParameterIndexer.exit(node.typeParameters);
 
     _activeFileUri = activeFileUriSaved;
 
-    _variableIndexer.popScope();
-    _variableIndexer = null;
+    leaveScope(
+        typeParameters: node.typeParameters,
+        memberScope: true,
+        variableScope: true);
   }
 
   @override
@@ -960,14 +949,12 @@
   @override
   void visitFunctionNode(FunctionNode node) {
     writeByte(Tag.FunctionNode);
-    assert(_variableIndexer != null);
-    _variableIndexer.pushScope();
+    enterScope(typeParameters: node.typeParameters, variableScope: true);
     var oldLabels = _labelIndexer;
     _labelIndexer = null;
     var oldCases = _switchCaseIndexer;
     _switchCaseIndexer = null;
     // Note: FunctionNode has no tag.
-    _typeParameterIndexer.enter(node.typeParameters);
     writeOffset(node.fileOffset);
     writeOffset(node.fileEndOffset);
     writeByte(node.asyncMarker.index);
@@ -981,8 +968,7 @@
     writeOptionalNode(node.body);
     _labelIndexer = oldLabels;
     _switchCaseIndexer = oldCases;
-    _typeParameterIndexer.exit(node.typeParameters);
-    _variableIndexer.popScope();
+    leaveScope(typeParameters: node.typeParameters, variableScope: true);
   }
 
   @override
@@ -1600,7 +1586,7 @@
 
   void writeVariableDeclaration(VariableDeclaration node) {
     if (_metadataSubsections != null) {
-      _recordNodeOffsetForMetadataMapping(node);
+      _writeNodeMetadata(node);
     }
     node.binaryOffsetNoTag = getBufferOffset();
     writeOffset(node.fileOffset);
@@ -1692,7 +1678,7 @@
       writeNode(node.returnType);
     } else {
       writeByte(Tag.FunctionType);
-      _typeParameterIndexer.enter(node.typeParameters);
+      enterScope(typeParameters: node.typeParameters);
       writeNodeList(node.typeParameters);
       writeUInt30(node.requiredParameterCount);
       writeUInt30(
@@ -1702,7 +1688,7 @@
       writeStringReferenceList(node.positionalParameterNames);
       writeReference(node.typedefReference);
       writeNode(node.returnType);
-      _typeParameterIndexer.exit(node.typeParameters);
+      leaveScope(typeParameters: node.typeParameters);
     }
   }
 
@@ -2104,9 +2090,11 @@
 
   void exit(List<TypeParameter> typeParameters) {
     stackHeight -= typeParameters.length;
+    typeParameters.forEach(index.remove);
   }
 
-  int operator [](TypeParameter parameter) => index[parameter];
+  int operator [](TypeParameter parameter) =>
+      index[parameter] ?? (throw 'Type parameter $parameter is not indexed');
 }
 
 class StringIndexer {
@@ -2294,29 +2282,27 @@
 class _MetadataSubsection {
   final MetadataRepository<Object> repository;
 
-  /// Offsets of metadata payloads associated with the nodes.
-  final Map<Node, int> metadataOffsets;
-
   /// List of (nodeOffset, metadataOffset) pairs.
   /// Gradually filled by the writer as writing progresses, which by
   /// construction guarantees that pairs are sorted by first component
   /// (nodeOffset) in ascending order.
   final List<int> metadataMapping = <int>[];
 
-  /// Mapping between nodes that are referenced from inside metadata payloads
-  /// and their ids.
-  final Map<Node, int> nodeToReferenceId;
+  _MetadataSubsection(this.repository);
+}
 
-  /// Mapping between reference ids and offsets of referenced nodes.
-  /// Gradually filled by the writer as writing progresses but is not
-  /// guaranteed to be sorted.
-  final List<int> offsetsOfReferencedNodes;
+/// A [Sink] that directly writes data into a byte builder.
+// TODO(dartbug.com/28316): Remove this wrapper class.
+class BytesSink implements Sink<List<int>> {
+  final BytesBuilder builder = new BytesBuilder();
 
-  _MetadataSubsection(
-      this.repository, this.metadataOffsets, Map<Node, int> nodeToReferenceId)
-      : nodeToReferenceId =
-            nodeToReferenceId.isNotEmpty ? nodeToReferenceId : null,
-        offsetsOfReferencedNodes = nodeToReferenceId.isNotEmpty
-            ? new List<int>.filled(nodeToReferenceId.length, -1)
-            : null;
+  @override
+  void add(List<int> data) {
+    builder.add(data);
+  }
+
+  @override
+  void close() {
+    // Nothing to do.
+  }
 }
diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart
index 877657f..1d016af 100644
--- a/pkg/kernel/lib/binary/tag.dart
+++ b/pkg/kernel/lib/binary/tag.dart
@@ -134,8 +134,8 @@
 
   /// Internal version of kernel binary format.
   /// Bump it when making incompatible changes in kernel binaries.
-  /// Keep in sync with runtime/vm/kernel_binary.h.
-  static const int BinaryFormatVersion = 5;
+  /// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
+  static const int BinaryFormatVersion = 6;
 }
 
 abstract class ConstantTag {
diff --git a/pkg/kernel/test/metadata_test.dart b/pkg/kernel/test/metadata_test.dart
index 1fb9777..4607413 100644
--- a/pkg/kernel/test/metadata_test.dart
+++ b/pkg/kernel/test/metadata_test.dart
@@ -14,16 +14,40 @@
     show computePlatformBinariesLocation;
 
 /// Test metadata: to each node we attach a metadata that contains
-/// a reference to this node's parent and this node formatted as string.
+/// * node formatted as string
+/// * reference to its enclosing member
+/// * type representing the first type parameter of its enclosing function
 class Metadata {
-  final TreeNode parent;
-  final String self;
+  final String string;
+  final Reference _memberRef;
+  final DartType type;
+
+  Member get member => _memberRef?.asMember;
 
   Metadata.forNode(TreeNode n)
-      : parent = MetadataRepository.isSupported(n.parent) ? n.parent : null,
-        self = n.toString();
+      : this(n.toString(), getMemberReference(getMemberForMetadata(n)),
+            getTypeForMetadata(n));
 
-  Metadata(this.parent, this.self);
+  Metadata(this.string, this._memberRef, this.type);
+}
+
+Member getMemberForMetadata(TreeNode node) {
+  final parent = node.parent;
+  if (parent == null) return null;
+  if (parent is Member) return parent;
+  return getMemberForMetadata(parent);
+}
+
+DartType getTypeForMetadata(TreeNode node) {
+  final parent = node.parent;
+  if (parent == null) return const VoidType();
+  if (parent is FunctionNode) {
+    if (parent.typeParameters.isEmpty) {
+      return const VoidType();
+    }
+    return new TypeParameterType(parent.typeParameters[0]);
+  }
+  return getTypeForMetadata(parent);
 }
 
 class TestMetadataRepository extends MetadataRepository<Metadata> {
@@ -33,15 +57,21 @@
 
   final Map<TreeNode, Metadata> mapping = <TreeNode, Metadata>{};
 
-  void writeToBinary(Metadata metadata, BinarySink sink) {
-    sink.writeNodeReference(metadata.parent);
-    sink.writeByteList(utf8.encode(metadata.self));
+  void writeToBinary(Metadata metadata, Node node, BinarySink sink) {
+    expect(metadata, equals(mapping[node]));
+    sink.writeByteList(utf8.encode(metadata.string));
+    sink.writeStringReference(metadata.string);
+    sink.writeCanonicalNameReference(metadata.member?.canonicalName);
+    sink.writeDartType(metadata.type);
   }
 
-  Metadata readFromBinary(BinarySource source) {
-    final parent = source.readNodeReference();
-    final string = utf8.decode(source.readByteList());
-    return new Metadata(parent, string);
+  Metadata readFromBinary(Node node, BinarySource source) {
+    final string1 = utf8.decode(source.readByteList());
+    final string2 = source.readStringReference();
+    final memberRef = source.readCanonicalNameReference()?.reference;
+    final type = source.readDartType();
+    expect(string1, equals(string2));
+    return new Metadata(string2, memberRef, type);
   }
 }
 
@@ -92,8 +122,9 @@
       final m = repository.mapping[node];
       final expected = new Metadata.forNode(node);
 
-      expect(m.parent, equals(expected.parent));
-      expect(m.self, equals(expected.self));
+      expect(m.string, equals(expected.string));
+      expect(m.member, equals(expected.member));
+      expect(m.type, equals(expected.type));
     }
   }
 
diff --git a/pkg/vm/lib/bytecode/constant_pool.dart b/pkg/vm/lib/bytecode/constant_pool.dart
index 958c7c6..2e7e511 100644
--- a/pkg/vm/lib/bytecode/constant_pool.dart
+++ b/pkg/vm/lib/bytecode/constant_pool.dart
@@ -95,17 +95,17 @@
 
 type ConstantType extends ConstantPoolEntry {
   Byte tag = 14;
-  NodeReference type;
+  DartType type;
 }
 
 type ConstantTypeArguments extends ConstantPoolEntry {
   Byte tag = 15;
-  List<NodeReference> types;
+  List<DartType> types;
 }
 
 type ConstantList extends ConstantPoolEntry {
   Byte tag = 16;
-  NodeReference typeArg;
+  DartType typeArg;
   List<ConstantIndex> entries;
 }
 
@@ -641,11 +641,11 @@
 
   @override
   void writeValueToBinary(BinarySink sink) {
-    sink.writeNodeReference(type);
+    sink.writeDartType(type);
   }
 
   ConstantType.readFromBinary(BinarySource source)
-      : type = source.readNodeReference() as DartType;
+      : type = source.readDartType();
 
   @override
   String toString() => 'Type $type';
@@ -668,12 +668,12 @@
   @override
   void writeValueToBinary(BinarySink sink) {
     sink.writeUInt30(typeArgs.length);
-    typeArgs.forEach(sink.writeNodeReference);
+    typeArgs.forEach(sink.writeDartType);
   }
 
   ConstantTypeArguments.readFromBinary(BinarySource source)
       : typeArgs = new List<DartType>.generate(
-            source.readUInt(), (_) => source.readNodeReference() as DartType);
+            source.readUInt(), (_) => source.readDartType());
 
   @override
   String toString() => 'TypeArgs $typeArgs';
@@ -698,13 +698,13 @@
 
   @override
   void writeValueToBinary(BinarySink sink) {
-    sink.writeNodeReference(typeArg);
+    sink.writeDartType(typeArg);
     sink.writeUInt30(entries.length);
     entries.forEach(sink.writeUInt30);
   }
 
   ConstantList.readFromBinary(BinarySource source)
-      : typeArg = source.readNodeReference() as DartType,
+      : typeArg = source.readDartType(),
         entries =
             new List<int>.generate(source.readUInt(), (_) => source.readUInt());
 
@@ -868,18 +868,36 @@
     });
   }
 
-  void writeToBinary(BinarySink sink) {
+  void writeToBinary(Node node, BinarySink sink) {
+    final function = (node as Member).function;
+    if (function != null) {
+      sink.enterScope(typeParameters: function.typeParameters);
+    }
+
     sink.writeUInt30(entries.length);
     entries.forEach((e) {
       e.writeToBinary(sink);
     });
+
+    if (function != null) {
+      sink.leaveScope(typeParameters: function.typeParameters);
+    }
   }
 
-  ConstantPool.readFromBinary(BinarySource source) {
+  ConstantPool.readFromBinary(Node node, BinarySource source) {
+    final function = (node as Member).function;
+    if (function != null) {
+      source.enterScope(typeParameters: function.typeParameters);
+    }
+
     int len = source.readUInt();
     for (int i = 0; i < len; i++) {
       entries.add(new ConstantPoolEntry.readFromBinary(source));
     }
+
+    if (function != null) {
+      source.leaveScope(typeParameters: function.typeParameters);
+    }
   }
 
   @override
diff --git a/pkg/vm/lib/metadata/bytecode.dart b/pkg/vm/lib/metadata/bytecode.dart
index 1c28c0e..6ff238a 100644
--- a/pkg/vm/lib/metadata/bytecode.dart
+++ b/pkg/vm/lib/metadata/bytecode.dart
@@ -30,15 +30,16 @@
       <TreeNode, BytecodeMetadata>{};
 
   @override
-  void writeToBinary(BytecodeMetadata metadata, BinarySink sink) {
+  void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) {
     sink.writeByteList(metadata.bytecodes);
-    metadata.constantPool.writeToBinary(sink);
+    metadata.constantPool.writeToBinary(node, sink);
   }
 
   @override
-  BytecodeMetadata readFromBinary(BinarySource source) {
+  BytecodeMetadata readFromBinary(Node node, BinarySource source) {
     final List<int> bytecodes = source.readByteList();
-    final ConstantPool constantPool = new ConstantPool.readFromBinary(source);
+    final ConstantPool constantPool =
+        new ConstantPool.readFromBinary(node, source);
     return new BytecodeMetadata(bytecodes, constantPool);
   }
 }
diff --git a/pkg/vm/lib/metadata/direct_call.dart b/pkg/vm/lib/metadata/direct_call.dart
index 570e303..dd9348c 100644
--- a/pkg/vm/lib/metadata/direct_call.dart
+++ b/pkg/vm/lib/metadata/direct_call.dart
@@ -34,13 +34,13 @@
       <TreeNode, DirectCallMetadata>{};
 
   @override
-  void writeToBinary(DirectCallMetadata metadata, BinarySink sink) {
+  void writeToBinary(DirectCallMetadata metadata, Node node, BinarySink sink) {
     sink.writeCanonicalNameReference(getCanonicalNameOfMember(metadata.target));
     sink.writeByte(metadata.checkReceiverForNull ? 1 : 0);
   }
 
   @override
-  DirectCallMetadata readFromBinary(BinarySource source) {
+  DirectCallMetadata readFromBinary(Node node, BinarySource source) {
     final targetReference = source.readCanonicalNameReference()?.getReference();
     if (targetReference == null) {
       throw 'DirectCallMetadata should have a non-null target';
diff --git a/pkg/vm/lib/metadata/inferred_type.dart b/pkg/vm/lib/metadata/inferred_type.dart
index dafb370..17daf11 100644
--- a/pkg/vm/lib/metadata/inferred_type.dart
+++ b/pkg/vm/lib/metadata/inferred_type.dart
@@ -32,14 +32,14 @@
   final Map<TreeNode, InferredType> mapping = <TreeNode, InferredType>{};
 
   @override
-  void writeToBinary(InferredType metadata, BinarySink sink) {
+  void writeToBinary(InferredType metadata, Node node, BinarySink sink) {
     sink.writeCanonicalNameReference(
         getCanonicalNameOfClass(metadata.concreteClass));
     sink.writeByte(metadata.nullable ? 1 : 0);
   }
 
   @override
-  InferredType readFromBinary(BinarySource source) {
+  InferredType readFromBinary(Node node, BinarySource source) {
     final concreteClassReference =
         source.readCanonicalNameReference()?.getReference();
     final nullable = (source.readByte() != 0);
diff --git a/pkg/vm/lib/metadata/procedure_attributes.dart b/pkg/vm/lib/metadata/procedure_attributes.dart
index 5581074..531f7d2 100644
--- a/pkg/vm/lib/metadata/procedure_attributes.dart
+++ b/pkg/vm/lib/metadata/procedure_attributes.dart
@@ -39,7 +39,8 @@
       <TreeNode, ProcedureAttributesMetadata>{};
 
   @override
-  void writeToBinary(ProcedureAttributesMetadata metadata, BinarySink sink) {
+  void writeToBinary(
+      ProcedureAttributesMetadata metadata, Node node, BinarySink sink) {
     int flags = 0;
     if (metadata.hasDynamicUses) {
       flags |= kDynamicUsesBit;
@@ -54,7 +55,7 @@
   }
 
   @override
-  ProcedureAttributesMetadata readFromBinary(BinarySource source) {
+  ProcedureAttributesMetadata readFromBinary(Node node, BinarySource source) {
     final int flags = source.readByte();
 
     final bool hasDynamicUses = (flags & kDynamicUsesBit) == kDynamicUsesBit;
diff --git a/pkg/vm/lib/metadata/unreachable.dart b/pkg/vm/lib/metadata/unreachable.dart
index aff77e2..a4c44e0 100644
--- a/pkg/vm/lib/metadata/unreachable.dart
+++ b/pkg/vm/lib/metadata/unreachable.dart
@@ -26,10 +26,10 @@
   final Map<TreeNode, UnreachableNode> mapping = <TreeNode, UnreachableNode>{};
 
   @override
-  void writeToBinary(UnreachableNode metadata, BinarySink sink) {}
+  void writeToBinary(UnreachableNode metadata, Node node, BinarySink sink) {}
 
   @override
-  UnreachableNode readFromBinary(BinarySource source) {
+  UnreachableNode readFromBinary(Node node, BinarySource source) {
     return const UnreachableNode();
   }
 }
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index ba39e0c..4c13e12 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -679,7 +679,7 @@
       intptr_t node_offset = reader.ReadUInt32();
       intptr_t md_offset = reader.ReadUInt32();
 
-      ASSERT((node_offset > 0) && (md_offset > 0));
+      ASSERT((node_offset > 0) && (md_offset >= 0));
       ASSERT(node_offset > prev_node_offset);
       prev_node_offset = node_offset;
     }
@@ -747,7 +747,7 @@
 
   intptr_t index = last_mapping_index_;
   intptr_t mapping_node_offset = 0;
-  intptr_t mapping_md_offset = 0;
+  intptr_t mapping_md_offset = -1;
 
   Reader reader(H.metadata_mappings());
   const intptr_t kUInt32Size = 4;
@@ -766,7 +766,7 @@
   last_node_offset_ = node_offset;
 
   if ((index < mappings_num_) && (mapping_node_offset == node_offset)) {
-    ASSERT(mapping_md_offset > 0);
+    ASSERT(mapping_md_offset >= 0);
     return mapping_md_offset;
   } else {
     return -1;
@@ -782,7 +782,7 @@
   }
 
   AlternativeReadingScope alt(&builder_->reader_, &H.metadata_payloads(),
-                              md_offset - MetadataPayloadOffset);
+                              md_offset);
 
   *target_name = builder_->ReadCanonicalNameReference();
   *check_receiver_for_null = builder_->ReadBool();
@@ -860,7 +860,7 @@
   }
 
   AlternativeReadingScope alt(&builder_->reader_, &H.metadata_payloads(),
-                              md_offset - MetadataPayloadOffset);
+                              md_offset);
 
   const int kDynamicUsesBit = 1 << 0;
   const int kNonThisUsesBit = 1 << 1;
@@ -890,7 +890,7 @@
   }
 
   AlternativeReadingScope alt(&builder_->reader_, &H.metadata_payloads(),
-                              md_offset - MetadataPayloadOffset);
+                              md_offset);
 
   const NameIndex kernel_name = builder_->ReadCanonicalNameReference();
   const bool nullable = builder_->ReadBool();
@@ -923,7 +923,7 @@
   }
 
   AlternativeReadingScope alt(&builder_->reader_, &H.metadata_payloads(),
-                              md_offset - MetadataPayloadOffset);
+                              md_offset);
 
   // Read bytecode.
   intptr_t bytecode_size = builder_->reader_.ReadUInt();
@@ -1084,12 +1084,10 @@
         obj = H.Canonicalize(Instance::Cast(obj));
         break;
       case ConstantPoolTag::kType:
-        UNIMPLEMENTED();  // Encoding is under discussion with CFE team.
         obj = builder_->type_translator_.BuildType().raw();
         ASSERT(obj.IsAbstractType());
         break;
       case ConstantPoolTag::kTypeArguments:
-        UNIMPLEMENTED();  // Encoding is under discussion with CFE team.
         obj = builder_->type_translator_
                   .BuildTypeArguments(builder_->ReadListLength())
                   .raw();
@@ -10941,51 +10939,39 @@
 
   // Read metadataMappings elements.
   for (uint32_t i = 0; i < metadata_num; ++i) {
-    // Read nodeReferences length.
+    // Read nodeOffsetToMetadataOffset length.
     offset -= kUInt32Size;
-    uint32_t node_references_num = reader.ReadUInt32At(offset);
-
-    // Skip nodeReferences and read nodeOffsetToMetadataOffset length.
-    offset -= node_references_num * kUInt32Size + kUInt32Size;
     uint32_t mappings_num = reader.ReadUInt32At(offset);
 
     // Skip nodeOffsetToMetadataOffset and read tag.
     offset -= mappings_num * 2 * kUInt32Size + kUInt32Size;
     StringIndex tag = StringIndex(reader.ReadUInt32At(offset));
 
+    if (mappings_num == 0) {
+      continue;
+    }
+
     // Check recognized metadata
     if (H.StringEquals(tag, DirectCallMetadataHelper::tag())) {
-      ASSERT(node_references_num == 0);
-
-      if (mappings_num > 0) {
-        if (!FLAG_precompiled_mode) {
-          FATAL("DirectCallMetadata is allowed in precompiled mode only");
-        }
-        direct_call_metadata_helper_.SetMetadataMappings(offset + kUInt32Size,
-                                                         mappings_num);
+      if (!FLAG_precompiled_mode) {
+        FATAL("DirectCallMetadata is allowed in precompiled mode only");
       }
+      direct_call_metadata_helper_.SetMetadataMappings(offset + kUInt32Size,
+                                                       mappings_num);
     } else if (H.StringEquals(tag, InferredTypeMetadataHelper::tag())) {
-      ASSERT(node_references_num == 0);
-
-      if (mappings_num > 0) {
-        if (!FLAG_precompiled_mode) {
-          FATAL("InferredTypeMetadata is allowed in precompiled mode only");
-        }
-        inferred_type_metadata_helper_.SetMetadataMappings(offset + kUInt32Size,
-                                                           mappings_num);
+      if (!FLAG_precompiled_mode) {
+        FATAL("InferredTypeMetadata is allowed in precompiled mode only");
       }
+      inferred_type_metadata_helper_.SetMetadataMappings(offset + kUInt32Size,
+                                                         mappings_num);
     } else if (H.StringEquals(tag, ProcedureAttributesMetadataHelper::tag())) {
-      ASSERT(node_references_num == 0);
-
-      if (mappings_num > 0) {
-        if (!FLAG_precompiled_mode) {
-          FATAL(
-              "ProcedureAttributesMetadata is allowed in precompiled mode "
-              "only");
-        }
-        procedure_attributes_metadata_helper_.SetMetadataMappings(
-            offset + kUInt32Size, mappings_num);
+      if (!FLAG_precompiled_mode) {
+        FATAL(
+            "ProcedureAttributesMetadata is allowed in precompiled mode "
+            "only");
       }
+      procedure_attributes_metadata_helper_.SetMetadataMappings(
+          offset + kUInt32Size, mappings_num);
     } else if (H.StringEquals(tag, BytecodeMetadataHelper::tag())) {
       bytecode_metadata_helper_.SetMetadataMappings(offset + kUInt32Size,
                                                     mappings_num);
diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h
index 6057a67..d297dda 100644
--- a/runtime/vm/kernel.h
+++ b/runtime/vm/kernel.h
@@ -91,6 +91,12 @@
   intptr_t source_table_offset() const { return source_table_offset_; }
   intptr_t string_table_offset() const { return string_table_offset_; }
   intptr_t name_table_offset() const { return name_table_offset_; }
+  intptr_t metadata_payloads_offset() const {
+    return metadata_payloads_offset_;
+  }
+  intptr_t metadata_mappings_offset() const {
+    return metadata_mappings_offset_;
+  }
   intptr_t constant_table_offset() { return constant_table_offset_; }
   const uint8_t* kernel_data() { return kernel_data_; }
   intptr_t kernel_data_size() { return kernel_data_size_; }
@@ -117,6 +123,12 @@
   // The offset from the start of the binary to the canonical name table.
   intptr_t name_table_offset_;
 
+  // The offset from the start of the binary to the metadata payloads.
+  intptr_t metadata_payloads_offset_;
+
+  // The offset from the start of the binary to the metadata mappings.
+  intptr_t metadata_mappings_offset_;
+
   // The offset from the start of the binary to the start of the string table.
   intptr_t string_table_offset_;
 
diff --git a/runtime/vm/kernel_binary.cc b/runtime/vm/kernel_binary.cc
index 310bc07..9b97137 100644
--- a/runtime/vm/kernel_binary.cc
+++ b/runtime/vm/kernel_binary.cc
@@ -69,6 +69,8 @@
           SourceTableFieldCountFromFirstLibraryOffset,
       1, 0);
   program->name_table_offset_ = reader->ReadUInt32();
+  program->metadata_payloads_offset_ = reader->ReadUInt32();
+  program->metadata_mappings_offset_ = reader->ReadUInt32();
   program->string_table_offset_ = reader->ReadUInt32();
   program->constant_table_offset_ = reader->ReadUInt32();
 
diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h
index daee540..ba05ff0 100644
--- a/runtime/vm/kernel_binary.h
+++ b/runtime/vm/kernel_binary.h
@@ -16,10 +16,11 @@
 namespace dart {
 namespace kernel {
 
-// Keep in sync with package:kernel/lib/binary/tag.dart.
+// Keep in sync with package:kernel/lib/binary/tag.dart,
+// package:kernel/binary.md.
 
 static const uint32_t kMagicProgramFile = 0x90ABCDEFu;
-static const uint32_t kBinaryFormatVersion = 5;
+static const uint32_t kBinaryFormatVersion = 6;
 
 // Keep in sync with package:kernel/lib/binary/tag.dart
 #define KERNEL_TAG_LIST(V)                                                     \
@@ -156,10 +157,9 @@
 
 static const int SpecializedIntLiteralBias = 3;
 static const int LibraryCountFieldCountFromEnd = 1;
-static const int SourceTableFieldCountFromFirstLibraryOffset = 4;
+static const int SourceTableFieldCountFromFirstLibraryOffset = 6;
 
 static const int HeaderSize = 8;  // 'magic', 'formatVersion'.
-static const int MetadataPayloadOffset = HeaderSize;  // Right after header.
 
 class Reader : public ValueObject {
  public:
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index 6a3358c..192b743 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -293,27 +293,18 @@
     names.SetUint32(i << 2, reader.ReadUInt());
   }
 
-  // Metadata mappings immediately follow names table.
-  const intptr_t metadata_mappings_start = reader.offset();
-
   // Copy metadata payloads into the VM's heap
-  // TODO(alexmarkov): add more info to program index instead of guessing
-  // the end of metadata payloads by offsets of the libraries.
-  intptr_t metadata_payloads_end = program_->source_table_offset();
-  for (intptr_t i = 0; i < program_->library_count(); ++i) {
-    metadata_payloads_end =
-        Utils::Minimum(metadata_payloads_end, library_offset(i));
-  }
-  ASSERT(metadata_payloads_end >= MetadataPayloadOffset);
+  const intptr_t metadata_payloads_start = program_->metadata_payloads_offset();
   const intptr_t metadata_payloads_size =
-      metadata_payloads_end - MetadataPayloadOffset;
+      program_->metadata_mappings_offset() - metadata_payloads_start;
   TypedData& metadata_payloads =
       TypedData::Handle(Z, TypedData::New(kTypedDataUint8ArrayCid,
                                           metadata_payloads_size, Heap::kOld));
-  reader.CopyDataToVMHeap(metadata_payloads, MetadataPayloadOffset,
+  reader.CopyDataToVMHeap(metadata_payloads, metadata_payloads_start,
                           metadata_payloads_size);
 
   // Copy metadata mappings into the VM's heap
+  const intptr_t metadata_mappings_start = program_->metadata_mappings_offset();
   const intptr_t metadata_mappings_size =
       program_->string_table_offset() - metadata_mappings_start;
   TypedData& metadata_mappings =