Version 2.14.0-4.0.dev

Merge commit '61fda382c2342e4b042df5cbbfffa5bbb37f9945' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3441a82..1a4558d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,14 @@
     daylight saving changes that are not precisely one hour.
     (No change on the Web which uses the JavaScript `Date` object.)
 
+### Dart VM
+
+*   **Breaking Change** [#45071][]: `Dart_NewWeakPersistentHandle`'s and
+    `Dart_NewFinalizableHandle`'s `object` parameter no longer accepts
+    `Pointer`s and subtypes of `Struct`. Expandos no longer accept
+    `Pointer`s and subtypes of `Struct`s.
+
+[#45071]: https://github.com/dart-lang/sdk/issues/45071
 
 ### Tools
 
diff --git a/DEPS b/DEPS
index 62dd712..5c44d05 100644
--- a/DEPS
+++ b/DEPS
@@ -54,6 +54,10 @@
   # Checkout Android dependencies only on Mac and Linux.
   "download_android_deps": 'host_os == "mac" or host_os == "linux"',
 
+  # Checkout extra javascript engines for testing or benchmarking.
+  # d8, the V8 shell, is always checked out.
+  "checkout_javascript_engines": False,
+
   # As Flutter does, we use Fuchsia's GN and Clang toolchain. These revision
   # should be kept up to date with the revisions pulled by the Flutter engine.
   # The list of revisions for these tools comes from Fuchsia, here:
@@ -662,6 +666,7 @@
       "--directory",
       Var('dart_root') + "/third_party/firefox_jsshell",
     ],
+    "condition": "checkout_javascript_engines",
   },
   {
     # Pull Debian sysroot for i386 Linux
diff --git a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
index 9c7e2b3..63bfa14 100644
--- a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
+++ b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
@@ -2,18 +2,16 @@
 // 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.
 
-// @dart = 2.9
-
 import 'package:dart_style/dart_style.dart';
-import 'package:meta/meta.dart';
 
 import 'typescript.dart';
 import 'typescript_parser.dart';
 
 final formatter = DartFormatter();
 Map<String, Interface> _interfaces = {};
+
+/// TODO(dantup): Rename namespaces -> enums since they're always that now.
 Map<String, Namespace> _namespaces = {};
-// TODO(dantup): Rename namespaces -> enums since they're always that now.
 Map<String, List<String>> _subtypes = {};
 Map<String, TypeAlias> _typeAliases = {};
 
@@ -63,10 +61,11 @@
 }
 
 TypeBase resolveTypeAlias(TypeBase type, {resolveEnumClasses = false}) {
-  if (type is Type && _typeAliases.containsKey(type.name)) {
+  if (type is Type) {
     final alias = _typeAliases[type.name];
     // Only follow the type if we're not an enum, or we wanted to follow enums.
-    if (!_namespaces.containsKey(alias.name) || resolveEnumClasses) {
+    if (alias != null &&
+        (!_namespaces.containsKey(alias.name) || resolveEnumClasses)) {
       return alias.baseType;
     }
   }
@@ -83,7 +82,7 @@
 }
 
 /// Recursively gets all members from superclasses.
-List<Field> _getAllFields(Interface interface) {
+List<Field> _getAllFields(Interface? interface) {
   // Handle missing interfaces (such as special cased interfaces that won't
   // be included in this model).
   if (interface == null) {
@@ -99,8 +98,8 @@
 }
 
 /// Returns a copy of the list sorted by name with duplicates (by name+type) removed.
-List<AstNode> _getSortedUnique(List<AstNode> items) {
-  final uniqueByName = <String, AstNode>{};
+List<N> _getSortedUnique<N extends AstNode>(List<N> items) {
+  final uniqueByName = <String, N>{};
   items.forEach((item) {
     // It's fine to have the same name used for different types (eg. namespace +
     // type alias) but some types are just duplicated entirely in the spec in
@@ -325,10 +324,11 @@
   var comment = node.commentText?.trim();
   if (comment != null && comment.isNotEmpty) {
     comment = _rewriteCommentReference(comment);
-    Iterable<String> lines = comment.split('\n');
+    var originalLines = comment.split('\n');
     // Wrap at 80 - 4 ('/// ') - indent characters.
-    lines = _wrapLines(lines, (80 - 4 - buffer.totalIndent).clamp(0, 80));
-    lines.forEach((l) => buffer.writeIndentedln('/// $l'.trim()));
+    var wrappedLines =
+        _wrapLines(originalLines, (80 - 4 - buffer.totalIndent).clamp(0, 80));
+    wrappedLines.forEach((l) => buffer.writeIndentedln('/// $l'.trim()));
   }
   // Marking LSP-deprecated fields as deprecated in Dart results in a lot
   // of warnings because we still often populate these fields for clients that
@@ -466,7 +466,7 @@
 
 void _writeFromJsonCode(
     IndentableStringBuffer buffer, TypeBase type, String valueCode,
-    {bool allowsNull, bool requiresBracesInInterpolation = false}) {
+    {required bool allowsNull, bool requiresBracesInInterpolation = false}) {
   type = resolveTypeAlias(type);
 
   if (_isSimpleType(type)) {
@@ -506,7 +506,7 @@
 
 void _writeFromJsonCodeForLiteralUnion(
     IndentableStringBuffer buffer, LiteralUnionType union, String valueCode,
-    {bool allowsNull}) {
+    {required bool allowsNull}) {
   final allowedValues = [
     if (allowsNull) null,
     ...union.literalTypes.map((t) => t.literal)
@@ -518,7 +518,7 @@
 
 void _writeFromJsonCodeForUnion(
     IndentableStringBuffer buffer, UnionType union, String valueCode,
-    {bool allowsNull, @required bool requiresBracesInInterpolation}) {
+    {required bool allowsNull, required bool requiresBracesInInterpolation}) {
   // Write a check against each type, eg.:
   // x is y ? new Either.tx(x) : (...)
   var hasIncompleteCondition = false;
@@ -575,7 +575,7 @@
     ..indent();
   // First check whether any of our subclasses can deserialise this.
   for (final subclassName in _subtypes[interface.name] ?? const <String>[]) {
-    final subclass = _interfaces[subclassName];
+    final subclass = _interfaces[subclassName]!;
     buffer
       ..writeIndentedln(
           'if (${subclass.name}.canParse(json, nullLspJsonReporter)) {')
@@ -792,7 +792,7 @@
 }
 
 void _writeTypeCheckCondition(IndentableStringBuffer buffer,
-    Interface interface, String valueCode, TypeBase type, String reporter) {
+    Interface? interface, String valueCode, TypeBase type, String reporter) {
   type = resolveTypeAlias(type);
 
   final dartType = type.dartType;
@@ -840,7 +840,6 @@
     }
     buffer.write(')');
   } else if (interface != null &&
-      interface.typeArgs != null &&
       interface.typeArgs.any((typeArg) => typeArg.lexeme == fullDartType)) {
     final comment = '/* $fullDartType.canParse($valueCode) */';
     print(
diff --git a/pkg/analysis_server/tool/lsp_spec/typescript.dart b/pkg/analysis_server/tool/lsp_spec/typescript.dart
index da58944..268b1f8 100644
--- a/pkg/analysis_server/tool/lsp_spec/typescript.dart
+++ b/pkg/analysis_server/tool/lsp_spec/typescript.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'typescript_parser.dart';
 
 /// Removes types that are in the spec that we don't want in other signatures.
@@ -22,10 +20,6 @@
 }
 
 String cleanComment(String comment) {
-  if (comment == null) {
-    return null;
-  }
-
   // Remove the start/end comment markers.
   if (comment.startsWith('/**') && comment.endsWith('*/')) {
     comment = comment.substring(3, comment.length - 2);
@@ -51,7 +45,7 @@
 
 /// Improves comments in generated code to support where types may have been
 /// altered (for ex. with [getImprovedType] above).
-String getImprovedComment(String interfaceName, String fieldName) {
+String? getImprovedComment(String interfaceName, String fieldName) {
   const _improvedComments = <String, Map<String, String>>{
     'ResponseError': {
       'data':
@@ -73,7 +67,7 @@
 /// - Narrows unions to single types where they're only generated on the server
 ///   and we know we always use a specific type. This avoids wrapping a lot
 ///   of code in `EitherX<Y,Z>.tX()` and simplifies the testing of them.
-String getImprovedType(String interfaceName, String fieldName) {
+String? getImprovedType(String interfaceName, String? fieldName) {
   const _improvedTypeMappings = <String, Map<String, String>>{
     'Diagnostic': {
       'severity': 'DiagnosticSeverity',
diff --git a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
index b18b819..9dc61e0 100644
--- a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
+++ b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'dart:math';
 
 import 'package:analysis_server/src/utilities/strings.dart' show capitalize;
@@ -64,14 +62,14 @@
   String get typeArgsString => '<${elementType.dartTypeWithTypeArgs}>';
 }
 
-class AstNode {
-  final Comment commentNode;
+abstract class AstNode {
+  final Comment? commentNode;
   final bool isDeprecated;
   AstNode(this.commentNode)
-      : isDeprecated = commentNode?.text?.contains('@deprecated') ?? false;
-  String get commentText => commentNode?.text;
+      : isDeprecated = commentNode?.text.contains('@deprecated') ?? false;
+  String? get commentText => commentNode?.text;
 
-  String get name => null;
+  String get name;
 }
 
 class Comment extends AstNode {
@@ -81,13 +79,16 @@
   Comment(this.token)
       : text = cleanComment(token.lexeme),
         super(null);
+
+  @override
+  String get name => throw UnsupportedError('Comments do not have a name.');
 }
 
 class Const extends Member {
   Token nameToken;
   TypeBase type;
   Token valueToken;
-  Const(Comment comment, this.nameToken, this.type, this.valueToken)
+  Const(Comment? comment, this.nameToken, this.type, this.valueToken)
       : super(comment);
 
   @override
@@ -111,7 +112,7 @@
   final bool allowsNull;
   final bool allowsUndefined;
   Field(
-    Comment comment,
+    Comment? comment,
     this.nameToken,
     this.type,
     this.allowsNull,
@@ -125,7 +126,7 @@
 class FixedValueField extends Field {
   final Token valueToken;
   FixedValueField(
-    Comment comment,
+    Comment? comment,
     Token nameToken,
     this.valueToken,
     TypeBase type,
@@ -138,7 +139,7 @@
   final TypeBase indexType;
   final TypeBase valueType;
   Indexer(
-    Comment comment,
+    Comment? comment,
     this.indexType,
     this.valueType,
   ) : super(comment);
@@ -161,7 +162,7 @@
   final List<Member> members;
 
   Interface(
-    Comment comment,
+    Comment? comment,
     this.nameToken,
     this.typeArgs,
     this.baseTypes,
@@ -221,15 +222,15 @@
       '<${indexType.dartTypeWithTypeArgs}, ${valueType.dartTypeWithTypeArgs}>';
 }
 
-class Member extends AstNode {
-  Member(Comment comment) : super(comment);
+abstract class Member extends AstNode {
+  Member(Comment? comment) : super(comment);
 }
 
 class Namespace extends AstNode {
   final Token nameToken;
   final List<Member> members;
   Namespace(
-    Comment comment,
+    Comment? comment,
     this.nameToken,
     this.members,
   ) : super(comment);
@@ -241,14 +242,13 @@
 class Parser {
   final List<Token> _tokens;
   int _current = 0;
-  List<AstNode> _nodes;
+  final List<AstNode> _nodes = [];
   Parser(this._tokens);
 
   bool get _isAtEnd => _peek().type == TokenType.EOF;
 
   List<AstNode> parse() {
-    if (_nodes == null) {
-      _nodes = <AstNode>[];
+    if (_nodes.isEmpty) {
       while (!_isAtEnd) {
         _nodes.add(_topLevel());
       }
@@ -262,17 +262,17 @@
   /// Checks if the next token is [type] without advancing.
   bool _check(TokenType type) => !_isAtEnd && _peek().type == type;
 
-  Comment _comment() {
+  Comment? _comment() {
     if (_peek().type != TokenType.COMMENT) {
       return null;
     }
     return Comment(_advance());
   }
 
-  Const _const(String containerName, Comment leadingComment) {
+  Const _const(String containerName, Comment? leadingComment) {
     _eatUnwantedKeywords();
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
-    TypeBase type;
+    TypeBase? type;
     if (_match([TokenType.COLON])) {
       type = _type(containerName, name.lexeme);
     }
@@ -283,7 +283,7 @@
     }
 
     _consume(TokenType.SEMI_COLON, 'Expected ;');
-    return Const(leadingComment, name, type, value);
+    return Const(leadingComment, name, type!, value!);
   }
 
   /// Ensures the next token is [type] and moves to the next, throwing [message]
@@ -302,7 +302,7 @@
     // but we have a keyword token, then treat it as an identifier.
     if (type == TokenType.IDENTIFIER) {
       final next = !_isAtEnd ? _peek() : null;
-      if (_isKeyword(next?.type)) {
+      if (next != null && _isKeyword(next.type)) {
         _advance();
         return Token(TokenType.IDENTIFIER, next.lexeme);
       }
@@ -316,7 +316,7 @@
     _match([TokenType.READONLY_KEYWORD]);
   }
 
-  Namespace _enum(Comment leadingComment) {
+  Namespace _enum(Comment? leadingComment) {
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
     _consume(TokenType.LEFT_BRACE, 'Expected {');
     final consts = <Const>[];
@@ -333,7 +333,7 @@
   Const _enumValue(String enumName) {
     final leadingComment = _comment();
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
-    TypeBase type;
+    TypeBase? type;
     if (_match([TokenType.COLON])) {
       type = _type(enumName, name.lexeme);
     }
@@ -342,16 +342,16 @@
     if (type == null && value != null) {
       type = typeOfLiteral(value.type);
     }
-    return Const(leadingComment, name, type, value);
+    return Const(leadingComment, name, type!, value!);
   }
 
-  Field _field(String containerName, Comment leadingComment) {
+  Field _field(String containerName, Comment? leadingComment) {
     _eatUnwantedKeywords();
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
     var canBeUndefined = _match([TokenType.QUESTION]);
     _consume(TokenType.COLON, 'Expected :');
     TypeBase type;
-    Token value;
+    Token? value;
     type = _type(containerName, name.lexeme,
         includeUndefined: canBeUndefined, improveTypes: true);
 
@@ -370,7 +370,7 @@
       final _linkTypePattern = RegExp(r'See \{@link (\w+)\}\.?');
       final linkTypeMatch = _linkTypePattern.firstMatch(commentText);
       if (linkTypeMatch != null) {
-        type = Type.identifier(linkTypeMatch.group(1));
+        type = Type.identifier(linkTypeMatch.group(1)!);
         leadingComment = Comment(Token(TokenType.COMMENT,
             '// ' + commentText.replaceAll(_linkTypePattern, '')));
       }
@@ -388,14 +388,13 @@
 
     var canBeNull = false;
     if (type is UnionType) {
-      UnionType union = type;
       // Since undefined and null can appear in the union type list but we want to
       // handle it specially in the code generation, we promote them to fields on
       // the Field.
-      canBeUndefined |= union.types.any(isUndefinedType);
-      canBeNull = union.types.any((t) => isNullType(t) || isAnyType(t));
+      canBeUndefined |= type.types.any(isUndefinedType);
+      canBeNull = type.types.any((t) => isNullType(t) || isAnyType(t));
       // Finally, we need to remove them from the union.
-      final remainingTypes = union.types
+      final remainingTypes = type.types
           .where((t) => !isNullType(t) && !isUndefinedType(t))
           .toList();
 
@@ -413,7 +412,7 @@
     return Field(leadingComment, name, type, canBeNull, canBeUndefined);
   }
 
-  Indexer _indexer(String containerName, Comment leadingComment) {
+  Indexer _indexer(String containerName, Comment? leadingComment) {
     final indexer = _field(containerName, leadingComment);
     _consume(TokenType.RIGHT_BRACKET, 'Expected ]');
     _consume(TokenType.COLON, 'Expected :');
@@ -427,7 +426,7 @@
     return Indexer(leadingComment, indexer.type, type);
   }
 
-  Interface _interface(Comment leadingComment) {
+  Interface _interface(Comment? leadingComment) {
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
     final typeArgs = <Token>[];
     if (_match([TokenType.LESS])) {
@@ -493,7 +492,7 @@
     }
   }
 
-  Namespace _namespace(Comment leadingComment) {
+  Namespace _namespace(Comment? leadingComment) {
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
     _consume(TokenType.LEFT_BRACE, 'Expected {');
     final members = <Member>[];
@@ -560,7 +559,7 @@
 
   TypeBase _type(
     String containerName,
-    String fieldName, {
+    String? fieldName, {
     bool includeUndefined = false,
     bool improveTypes = false,
   }) {
@@ -585,7 +584,7 @@
 
         // If we have a single member that is an indexer type, we can use a Map.
         if (members.length == 1 && members.single is Indexer) {
-          Indexer indexer = members.single;
+          var indexer = members.single as Indexer;
           type = MapType(indexer.indexType, indexer.valueType);
         } else {
           // Add a synthetic interface to the parsers list of nodes to represent this type.
@@ -628,7 +627,7 @@
         type = ArrayType(tupleType);
       } else {
         var typeName = _consume(TokenType.IDENTIFIER, 'Expected identifier');
-        final typeArgs = <Type>[];
+        final typeArgs = <TypeBase>[];
         if (_match([TokenType.LESS])) {
           while (true) {
             typeArgs.add(_type(containerName, fieldName));
@@ -679,7 +678,7 @@
     return type;
   }
 
-  TypeAlias _typeAlias(Comment leadingComment) {
+  TypeAlias _typeAlias(Comment? leadingComment) {
     final name = _consume(TokenType.IDENTIFIER, 'Expected identifier');
     _consume(TokenType.EQUAL, 'Expected =');
     final type = _type(name.lexeme, null);
@@ -732,16 +731,18 @@
     }
 
     final string = _source.substring(_startOfToken, _currentPos);
-    if (_keywords.containsKey(string)) {
-      _addToken(_keywords[string]);
+    var keyword = _keywords[string];
+    if (keyword != null) {
+      _addToken(keyword);
     } else {
       _addToken(TokenType.IDENTIFIER);
     }
   }
 
-  bool _isAlpha(String s) => _validIdentifierCharacters.hasMatch(s);
+  bool _isAlpha(String? s) =>
+      s != null && _validIdentifierCharacters.hasMatch(s);
 
-  bool _isDigit(String s) => s != null && (s.codeUnitAt(0) ^ 0x30) <= 9;
+  bool _isDigit(String? s) => s != null && (s.codeUnitAt(0) ^ 0x30) <= 9;
 
   bool _match(String expected) {
     if (_isAtEnd || _source[_currentPos] != expected) {
@@ -771,9 +772,9 @@
     _addToken(TokenType.NUMBER);
   }
 
-  String _peek() => _isAtEnd ? null : _source[_currentPos];
+  String? _peek() => _isAtEnd ? null : _source[_currentPos];
 
-  String _peekNext() => _isNextAtEnd ? null : _source[_currentPos + 1];
+  String? _peekNext() => _isNextAtEnd ? null : _source[_currentPos + 1];
 
   void _scanToken() {
     const singleCharTokens = <String, TokenType>{
@@ -795,8 +796,9 @@
     };
 
     final c = _advance();
-    if (singleCharTokens.containsKey(c)) {
-      _addToken(singleCharTokens[c]);
+    var token = singleCharTokens[c];
+    if (token != null) {
+      _addToken(token);
       return;
     }
     switch (c) {
@@ -979,7 +981,7 @@
   final Token nameToken;
   final TypeBase baseType;
   TypeAlias(
-    Comment comment,
+    Comment? comment,
     this.nameToken,
     this.baseType,
   ) : super(comment);
diff --git a/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart b/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
index 76c675b..d478a69 100644
--- a/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
@@ -141,7 +141,7 @@
       int charEndOffset,
       Member referenceFrom,
       [String nativeMethodName])
-      : _constructor = new Constructor(null,
+      : _constructor = new Constructor(new FunctionNode(null),
             name: new Name(name, compilationUnit.library),
             fileUri: compilationUnit.fileUri,
             reference: referenceFrom?.reference)
@@ -161,6 +161,8 @@
   @override
   Member get invokeTarget => constructor;
 
+  FunctionNode get function => _constructor.function;
+
   @override
   Iterable<Member> get exportedMembers => [constructor];
 
@@ -202,8 +204,7 @@
   @override
   Constructor build(SourceLibraryBuilder libraryBuilder) {
     if (!_hasBeenBuilt) {
-      _constructor.function = buildFunction(libraryBuilder);
-      _constructor.function.parent = _constructor;
+      buildFunction(libraryBuilder);
       _constructor.function.fileOffset = charOpenParenOffset;
       _constructor.function.fileEndOffset = _constructor.fileEndOffset;
       _constructor.function.typeParameters = const <TypeParameter>[];
@@ -263,10 +264,10 @@
   }
 
   @override
-  FunctionNode buildFunction(SourceLibraryBuilder library) {
+  void buildFunction(SourceLibraryBuilder library) {
     // According to the specification §9.3 the return type of a constructor
     // function is its enclosing class.
-    FunctionNode functionNode = super.buildFunction(library);
+    super.buildFunction(library);
     ClassBuilder enclosingClassBuilder = parent;
     Class enclosingClass = enclosingClassBuilder.cls;
     List<DartType> typeParameterTypes = <DartType>[];
@@ -276,9 +277,8 @@
           new TypeParameterType.withDefaultNullabilityForLibrary(
               typeParameter, library.library));
     }
-    functionNode.returnType = new InterfaceType(
+    function.returnType = new InterfaceType(
         enclosingClass, library.nonNullable, typeParameterTypes);
-    return functionNode;
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/builder/function_builder.dart b/pkg/front_end/lib/src/fasta/builder/function_builder.dart
index 15880c6..7d11b18 100644
--- a/pkg/front_end/lib/src/fasta/builder/function_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/function_builder.dart
@@ -293,9 +293,6 @@
   @override
   final String nativeMethodName;
 
-  @override
-  FunctionNode function;
-
   Statement bodyInternal;
 
   @override
@@ -308,18 +305,16 @@
 //      }
 //    }
     bodyInternal = newBody;
-    if (function != null) {
-      // A forwarding semi-stub is a method that is abstract in the source code,
-      // but which needs to have a forwarding stub body in order to ensure that
-      // covariance checks occur.  We don't want to replace the forwarding stub
-      // body with null.
-      TreeNode parent = function.parent;
-      if (!(newBody == null &&
-          parent is Procedure &&
-          parent.isForwardingSemiStub)) {
-        function.body = newBody;
-        newBody?.parent = function;
-      }
+    // A forwarding semi-stub is a method that is abstract in the source code,
+    // but which needs to have a forwarding stub body in order to ensure that
+    // covariance checks occur.  We don't want to replace the forwarding stub
+    // body with null.
+    TreeNode parent = function.parent;
+    if (!(newBody == null &&
+        parent is Procedure &&
+        parent.isForwardingSemiStub)) {
+      function.body = newBody;
+      newBody?.parent = function;
     }
   }
 
@@ -342,9 +337,10 @@
   @override
   bool get isNative => nativeMethodName != null;
 
-  FunctionNode buildFunction(SourceLibraryBuilder library) {
-    assert(function == null);
-    FunctionNode result = new FunctionNode(body, asyncMarker: asyncModifier);
+  void buildFunction(SourceLibraryBuilder library) {
+    function.asyncMarker = asyncModifier;
+    function.body = body;
+    body?.parent = function;
     IncludesTypeParametersNonCovariantly needsCheckVisitor;
     if (!isConstructor && !isFactory && parent is ClassBuilder) {
       ClassBuilder enclosingClassBuilder = parent;
@@ -360,14 +356,14 @@
     if (typeVariables != null) {
       for (TypeVariableBuilder t in typeVariables) {
         TypeParameter parameter = t.parameter;
-        result.typeParameters.add(parameter);
+        function.typeParameters.add(parameter);
         if (needsCheckVisitor != null) {
           if (parameter.bound.accept(needsCheckVisitor)) {
             parameter.isGenericCovariantImpl = true;
           }
         }
       }
-      setParents(result.typeParameters, result);
+      setParents(function.typeParameters, function);
     }
     if (formals != null) {
       for (FormalParameterBuilder formal in formals) {
@@ -379,13 +375,13 @@
           }
         }
         if (formal.isNamed) {
-          result.namedParameters.add(parameter);
+          function.namedParameters.add(parameter);
         } else {
-          result.positionalParameters.add(parameter);
+          function.positionalParameters.add(parameter);
         }
-        parameter.parent = result;
+        parameter.parent = function;
         if (formal.isRequired) {
-          result.requiredParameterCount++;
+          function.requiredParameterCount++;
         }
 
         if (library.isNonNullableByDefault) {
@@ -409,14 +405,14 @@
       // assumes that parameters are built, even if illegal in number.
       VariableDeclaration parameter =
           new VariableDeclarationImpl("#synthetic", 0);
-      result.positionalParameters.clear();
-      result.positionalParameters.add(parameter);
-      parameter.parent = result;
-      result.namedParameters.clear();
-      result.requiredParameterCount = 1;
+      function.positionalParameters.clear();
+      function.positionalParameters.add(parameter);
+      parameter.parent = function;
+      function.namedParameters.clear();
+      function.requiredParameterCount = 1;
     }
     if (returnType != null) {
-      result.returnType = returnType.build(
+      function.returnType = returnType.build(
           library, null, !isConstructor && !isDeclarationInstanceMember);
     }
     if (!isConstructor && !isDeclarationInstanceMember) {
@@ -444,33 +440,32 @@
         }
 
         Set<TypeParameter> set = typeParameters.toSet();
-        for (VariableDeclaration parameter in result.positionalParameters) {
+        for (VariableDeclaration parameter in function.positionalParameters) {
           if (containsTypeVariable(parameter.type, set)) {
             parameter.type = removeTypeVariables(parameter.type);
           }
         }
-        for (VariableDeclaration parameter in result.namedParameters) {
+        for (VariableDeclaration parameter in function.namedParameters) {
           if (containsTypeVariable(parameter.type, set)) {
             parameter.type = removeTypeVariables(parameter.type);
           }
         }
-        if (containsTypeVariable(result.returnType, set)) {
-          result.returnType = removeTypeVariables(result.returnType);
+        if (containsTypeVariable(function.returnType, set)) {
+          function.returnType = removeTypeVariables(function.returnType);
         }
       }
     }
     if (isExtensionInstanceMember) {
       ExtensionBuilder extensionBuilder = parent;
-      _extensionThis = result.positionalParameters.first;
+      _extensionThis = function.positionalParameters.first;
       if (extensionBuilder.typeParameters != null) {
         int count = extensionBuilder.typeParameters.length;
         _extensionTypeParameters = new List<TypeParameter>.filled(count, null);
         for (int index = 0; index < count; index++) {
-          _extensionTypeParameters[index] = result.typeParameters[index];
+          _extensionTypeParameters[index] = function.typeParameters[index];
         }
       }
     }
-    return function = result;
   }
 
   @override
diff --git a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
index 498826b..087e96d 100644
--- a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
@@ -107,15 +107,20 @@
         this.isExtensionInstanceMember = isInstanceMember && isExtensionMember,
         super(metadata, modifiers, returnType, name, typeVariables, formals,
             libraryBuilder, charOffset, nativeMethodName) {
-    _procedure = new Procedure(procedureNameScheme.getName(kind, name),
-        isExtensionInstanceMember ? ProcedureKind.Method : kind, null,
-        fileUri: libraryBuilder.fileUri, reference: procedureReference)
+    _procedure = new Procedure(
+        procedureNameScheme.getName(kind, name),
+        isExtensionInstanceMember ? ProcedureKind.Method : kind,
+        new FunctionNode(null),
+        fileUri: libraryBuilder.fileUri,
+        reference: procedureReference)
       ..startFileOffset = startCharOffset
       ..fileOffset = charOffset
       ..fileEndOffset = charEndOffset
       ..isNonNullableByDefault = libraryBuilder.isNonNullableByDefault;
   }
 
+  FunctionNode get function => _procedure.function;
+
   @override
   ProcedureBuilder get origin => actualOrigin ?? this;
 
@@ -136,11 +141,8 @@
   @override
   void set asyncModifier(AsyncMarker newModifier) {
     actualAsyncModifier = newModifier;
-    if (function != null) {
-      // No parent, it's an enum.
-      function.asyncMarker = actualAsyncModifier;
-      function.dartAsyncMarker = actualAsyncModifier;
-    }
+    function.asyncMarker = actualAsyncModifier;
+    function.dartAsyncMarker = actualAsyncModifier;
   }
 
   @override
@@ -266,7 +268,7 @@
       _extensionTearOff ??= new Procedure(
           procedureNameScheme.getName(ProcedureKind.Getter, name),
           ProcedureKind.Method,
-          null,
+          new FunctionNode(null),
           isStatic: true,
           isExtensionMember: true,
           reference: _tearOffReference)
@@ -379,8 +381,7 @@
 
   @override
   Procedure build(SourceLibraryBuilder libraryBuilder) {
-    _procedure.function = buildFunction(libraryBuilder);
-    _procedure.function.parent = _procedure;
+    buildFunction(libraryBuilder);
     _procedure.function.fileOffset = charOpenParenOffset;
     _procedure.function.fileEndOffset = _procedure.fileEndOffset;
     _procedure.isAbstract = isAbstract;
@@ -747,8 +748,7 @@
 
   @override
   Procedure build(SourceLibraryBuilder libraryBuilder) {
-    _procedure.function = buildFunction(libraryBuilder);
-    _procedure.function.parent = _procedure;
+    buildFunction(libraryBuilder);
     _procedure.function.fileOffset = charOpenParenOffset;
     _procedure.function.fileEndOffset = _procedure.fileEndOffset;
     _procedure.isAbstract = isAbstract;
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 50db82b..1f26ed0 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -4999,8 +4999,8 @@
     }
     push(new FunctionDeclarationImpl(
         variable,
-        // The function node is created later.
-        null)
+        // The real function node is created later.
+        dummyFunctionNode)
       ..fileOffset = beginToken.charOffset);
     declareVariable(variable, scope.parent);
   }
diff --git a/pkg/front_end/lib/src/fasta/kernel/member_covariance.dart b/pkg/front_end/lib/src/fasta/kernel/member_covariance.dart
index b33dbcd..03c89ce 100644
--- a/pkg/front_end/lib/src/fasta/kernel/member_covariance.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/member_covariance.dart
@@ -95,7 +95,7 @@
   /// Computes the covariance for the [setter].
   factory Covariance.fromSetter(Procedure setter) {
     int covariance =
-        covarianceFromParameter(setter.function!.positionalParameters.first);
+        covarianceFromParameter(setter.function.positionalParameters.first);
     if (covariance == 0) {
       return const Covariance.empty();
     }
@@ -104,7 +104,7 @@
 
   /// Computes the covariance for the [procedure].
   factory Covariance.fromMethod(Procedure procedure) {
-    FunctionNode function = procedure.function!;
+    FunctionNode function = procedure.function;
     List<int>? positionalParameters;
     if (function.positionalParameters.isNotEmpty) {
       for (int index = 0;
@@ -269,7 +269,7 @@
   void applyCovariance(Member member) {
     if (isEmpty) return;
     if (member is Procedure) {
-      FunctionNode function = member.function!;
+      FunctionNode function = member.function;
       List<int>? positionalParameters = _positionalParameters;
       if (positionalParameters != null) {
         for (int index = 0; index < positionalParameters.length; index++) {
diff --git a/pkg/front_end/test/flutter_gallery_leak_tester.dart b/pkg/front_end/test/flutter_gallery_leak_tester.dart
index 37b66be..b3f57e9 100644
--- a/pkg/front_end/test/flutter_gallery_leak_tester.dart
+++ b/pkg/front_end/test/flutter_gallery_leak_tester.dart
@@ -216,10 +216,10 @@
       },
       stderrReceiver: (s) => print("err> $s"));
 
-  await sendAndWait(heapHelper.process, ['compile package:gallery/main.dart']);
   Stopwatch stopwatch = new Stopwatch()..start();
-  await pauseAndWait(heapHelper);
+  await sendAndWait(heapHelper.process, ['compile package:gallery/main.dart']);
   print("First compile took ${stopwatch.elapsedMilliseconds} ms");
+  await pauseAndWait(heapHelper);
 
   await recompileAndWait(heapHelper.process, "package:gallery/main.dart", []);
   await accept(heapHelper);
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index b594898..20ee23e 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -147,7 +147,7 @@
 
 type ComponentFile {
   UInt32 magic = 0x90ABCDEF;
-  UInt32 formatVersion = 60;
+  UInt32 formatVersion = 61;
   Byte[10] shortSdkHash;
   List<String> problemsAsJson; // Described in problems.md.
   Library[] libraries;
@@ -425,8 +425,7 @@
   Name name;
   List<Expression> annotations;
   MemberReference stubTarget; // May be NullReference.
-  // Can only be absent if abstract, but tag is there anyway.
-  Option<FunctionNode> function;
+  FunctionNode function;
 }
 
 type RedirectingFactoryConstructor extends Member {
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index a81d28f..9e1ce0f 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -1988,9 +1988,8 @@
 
   int flags = 0;
 
-  // TODO(johnniwinther): Make this non-nullable.
   @override
-  FunctionNode? function;
+  FunctionNode function;
 
   List<Initializer> initializers;
 
@@ -2004,8 +2003,10 @@
       Uri? fileUri,
       Reference? reference})
       : this.initializers = initializers ?? <Initializer>[],
+        // ignore: unnecessary_null_comparison
+        assert(function != null),
         super(name, fileUri, reference) {
-    function?.parent = this;
+    function.parent = this;
     setParents(this.initializers, this);
     this.isConst = isConst;
     this.isExternal = isExternal;
@@ -2082,16 +2083,17 @@
     visitList(annotations, v);
     name.accept(v);
     visitList(initializers, v);
-    function?.accept(v);
+    function.accept(v);
   }
 
   @override
   void transformChildren(Transformer v) {
     v.transformList(annotations, this);
     v.transformList(initializers, this);
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transform(function!);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
@@ -2099,9 +2101,10 @@
   void transformOrRemoveChildren(RemovingTransformer v) {
     v.transformExpressionList(annotations, this);
     v.transformInitializerList(initializers, this);
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transformOrRemove(function!, dummyFunctionNode);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
@@ -2504,16 +2507,15 @@
   final ProcedureKind kind;
   int flags = 0;
 
-  // TODO(johnniwinther): Make this non-nullable.
   @override
-  FunctionNode? function;
+  FunctionNode function;
 
   // The function node's body might be lazily loaded, meaning that this value
   // might not be set correctly yet. Make sure the body is loaded before
   // returning anything.
   @override
   int get transformerFlags {
-    function?.body;
+    function.body;
     return super.transformerFlags;
   }
 
@@ -2522,7 +2524,7 @@
   // body now and only set the value afterwards.
   @override
   void set transformerFlags(int newValue) {
-    function?.body;
+    function.body;
     super.transformerFlags = newValue;
   }
 
@@ -2536,7 +2538,7 @@
   ProcedureStubKind stubKind;
   Reference? stubTargetReference;
 
-  Procedure(Name name, ProcedureKind kind, FunctionNode? function,
+  Procedure(Name name, ProcedureKind kind, FunctionNode function,
       {bool isAbstract: false,
       bool isStatic: false,
       bool isExternal: false,
@@ -2576,15 +2578,17 @@
       this.stubTargetReference})
       // ignore: unnecessary_null_comparison
       : assert(kind != null),
+        // ignore: unnecessary_null_comparison
+        assert(function != null),
         super(name, fileUri, reference) {
-    function?.parent = this;
+    function.parent = this;
     this.isAbstract = isAbstract;
     this.isStatic = isStatic;
     this.isExternal = isExternal;
     this.isConst = isConst;
     this.isExtensionMember = isExtensionMember;
     this.isSynthetic = isSynthetic;
-    this.transformerFlags = transformerFlags;
+    setTransformerFlagsWithoutLazyLoading(transformerFlags);
     assert(!(isMemberSignature && stubTargetReference == null),
         "No member signature origin for member signature $this.");
     assert(
@@ -2753,38 +2757,40 @@
   void visitChildren(Visitor v) {
     visitList(annotations, v);
     name.accept(v);
-    function?.accept(v);
+    function.accept(v);
   }
 
   @override
   void transformChildren(Transformer v) {
     v.transformList(annotations, this);
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transform(function!);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
   @override
   void transformOrRemoveChildren(RemovingTransformer v) {
     v.transformExpressionList(annotations, this);
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transformOrRemove(function!, dummyFunctionNode);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
   @override
   DartType get getterType {
     return isGetter
-        ? function!.returnType
-        : function!.computeFunctionType(enclosingLibrary.nonNullable);
+        ? function.returnType
+        : function.computeFunctionType(enclosingLibrary.nonNullable);
   }
 
   @override
   DartType get setterType {
     return isSetter
-        ? function!.positionalParameters[0].type
+        ? function.positionalParameters[0].type
         : const NeverType.nonNullable();
   }
 
@@ -6025,9 +6031,9 @@
         .getTypeArgumentsAsInstanceOf(context.thisType!, superclass);
     DartType returnType = Substitution.fromPairs(
             superclass.typeParameters, receiverTypeArguments!)
-        .substituteType(interfaceTarget.function!.returnType);
+        .substituteType(interfaceTarget.function.returnType);
     return Substitution.fromPairs(
-            interfaceTarget.function!.typeParameters, arguments.types)
+            interfaceTarget.function.typeParameters, arguments.types)
         .substituteType(returnType);
   }
 
@@ -6114,8 +6120,8 @@
   @override
   DartType getStaticTypeInternal(StaticTypeContext context) {
     return Substitution.fromPairs(
-            target.function!.typeParameters, arguments.types)
-        .substituteType(target.function!.returnType);
+            target.function.typeParameters, arguments.types)
+        .substituteType(target.function.returnType);
   }
 
   @override
@@ -8077,8 +8083,7 @@
 
 /// Common super-interface for [FunctionExpression] and [FunctionDeclaration].
 abstract class LocalFunction implements TreeNode {
-  // TODO(johnniwinther): Make this non-nullable.
-  FunctionNode? get function;
+  FunctionNode get function;
 }
 
 /// Expression of form `(x,y) => ...` or `(x,y) { ... }`
@@ -10118,11 +10123,13 @@
   VariableDeclaration variable; // Is final and has no initializer.
 
   @override
-  FunctionNode? function;
+  FunctionNode function;
 
-  FunctionDeclaration(this.variable, this.function) {
+  FunctionDeclaration(this.variable, this.function)
+      // ignore: unnecessary_null_comparison
+      : assert(function != null) {
     variable.parent = this;
-    function?.parent = this;
+    function.parent = this;
   }
 
   @override
@@ -10135,7 +10142,7 @@
   @override
   void visitChildren(Visitor v) {
     variable.accept(v);
-    function?.accept(v);
+    function.accept(v);
   }
 
   @override
@@ -10145,9 +10152,10 @@
       variable = v.transform(variable);
       variable.parent = this;
     }
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transform(function!);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
@@ -10158,9 +10166,10 @@
       variable = v.transform(variable);
       variable.parent = this;
     }
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      function = v.transformOrRemove(function!, dummyFunctionNode);
-      function?.parent = this;
+      function = v.transform(function);
+      function.parent = this;
     }
   }
 
@@ -10171,9 +10180,10 @@
 
   @override
   void toTextInternal(AstPrinter printer) {
+    // ignore: unnecessary_null_comparison
     if (function != null) {
-      printer.writeFunctionNode(function!, printer.getVariableName(variable));
-      if (function!.body is ReturnStatement) {
+      printer.writeFunctionNode(function, printer.getVariableName(variable));
+      if (function.body is ReturnStatement) {
         printer.write(';');
       }
     }
@@ -12624,7 +12634,7 @@
   }
 
   FunctionType getType(StaticTypeContext context) {
-    return procedure.function!.computeFunctionType(context.nonNullable);
+    return procedure.function.computeFunctionType(context.nonNullable);
   }
 }
 
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 7c1de52..0d99e60 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -499,7 +499,7 @@
     }
   }
 
-  List<Expression> readAnnotationList(TreeNode? parent) {
+  List<Expression> readAnnotationList([TreeNode? parent]) {
     int length = readUInt30();
     if (length == 0) return const <Expression>[];
     return new List<Expression>.generate(
@@ -1493,15 +1493,15 @@
     int fileEndOffset = readOffset();
     int flags = readByte();
     Name name = readName();
-    if (node == null) {
-      node = new Constructor(null, reference: reference, name: name);
-    }
-    List<Expression> annotations = readAnnotationList(node);
+    List<Expression> annotations = readAnnotationList();
     assert(() {
       debugPath.add(name.text);
       return true;
     }());
     FunctionNode function = readFunctionNode();
+    if (node == null) {
+      node = new Constructor(function, reference: reference, name: name);
+    }
     pushVariableDeclarations(function.positionalParameters);
     pushVariableDeclarations(function.namedParameters);
     _fillTreeNodeList(node.initializers, (index) => readInitializer(), node);
@@ -1515,6 +1515,7 @@
     node.name = name;
     node.fileUri = fileUri;
     node.annotations = annotations;
+    setParents(annotations, node);
     node.function = function..parent = node;
     node.transformerFlags = transformerFlags;
     return node;
@@ -1538,12 +1539,7 @@
     ProcedureStubKind stubKind = ProcedureStubKind.values[readByte()];
     int flags = readUInt30();
     Name name = readName();
-    if (node == null) {
-      node = new Procedure(name, kind, null, reference: reference);
-    } else {
-      assert(node.kind == kind);
-    }
-    List<Expression> annotations = readAnnotationList(node);
+    List<Expression> annotations = readAnnotationList();
     assert(() {
       debugPath.add(name.text);
       return true;
@@ -1554,8 +1550,13 @@
         (kind == ProcedureKind.Factory && functionNodeSize <= 50) ||
             _disableLazyReading;
     Reference? stubTargetReference = readNullableMemberReference();
-    FunctionNode? function =
-        readFunctionNodeOption(!readFunctionNodeNow, endOffset);
+    FunctionNode function = readFunctionNode(
+        lazyLoadBody: !readFunctionNodeNow, outerEndOffset: endOffset);
+    if (node == null) {
+      node = new Procedure(name, kind, function, reference: reference);
+    } else {
+      assert(node.kind == kind);
+    }
     int transformerFlags = getAndResetTransformerFlags();
     assert(((_) => true)(debugPath.removeLast()));
     node.startFileOffset = startFileOffset;
@@ -1565,15 +1566,15 @@
     node.name = name;
     node.fileUri = fileUri;
     node.annotations = annotations;
-    node.function = function;
-    function?.parent = node;
+    setParents(annotations, node);
+    node.function = function..parent = node;
     node.setTransformerFlagsWithoutLazyLoading(transformerFlags);
     node.stubKind = stubKind;
     node.stubTargetReference = stubTargetReference;
 
     assert((node.stubKind == ProcedureStubKind.ConcreteForwardingStub &&
             node.stubTargetReference != null) ||
-        !(node.isForwardingStub && node.function!.body != null));
+        !(node.isForwardingStub && node.function.body != null));
     assert(!(node.isMemberSignature && node.stubTargetReference == null),
         "No member signature origin for member signature $node.");
     return node;
@@ -1686,13 +1687,6 @@
     return new AssertInitializer(readStatement() as AssertStatement);
   }
 
-  FunctionNode? readFunctionNodeOption(bool lazyLoadBody, int outerEndOffset) {
-    return readAndCheckOptionTag()
-        ? readFunctionNode(
-            lazyLoadBody: lazyLoadBody, outerEndOffset: outerEndOffset)
-        : null;
-  }
-
   FunctionNode readFunctionNode(
       {bool lazyLoadBody: false, int outerEndOffset: -1}) {
     int tag = readByte();
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 5652534..e7271c5 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -488,15 +488,6 @@
     }
   }
 
-  void writeOptionalFunctionNode(FunctionNode? node) {
-    if (node == null) {
-      writeByte(Tag.Nothing);
-    } else {
-      writeByte(Tag.Something);
-      writeFunctionNode(node);
-    }
-  }
-
   void writeLinkTable(Component component) {
     _binaryOffsetForLinkTable = getBufferOffset();
     writeList(_canonicalNameList, writeCanonicalNameEntry);
@@ -1209,12 +1200,12 @@
     writeName(node.name);
 
     writeAnnotationList(node.annotations);
-    assert(node.function!.typeParameters.isEmpty);
-    writeFunctionNode(node.function!);
+    assert(node.function.typeParameters.isEmpty);
+    writeFunctionNode(node.function);
     // Parameters are in scope in the initializers.
     _variableIndexer ??= new VariableIndexer();
-    _variableIndexer!.restoreScope(node.function!.positionalParameters.length +
-        node.function!.namedParameters.length);
+    _variableIndexer!.restoreScope(node.function.positionalParameters.length +
+        node.function.namedParameters.length);
     writeNodeList(node.initializers);
 
     leaveScope(memberScope: true);
@@ -1273,13 +1264,13 @@
     writeName(node.name);
     writeAnnotationList(node.annotations);
     writeNullAllowedReference(node.stubTargetReference);
-    writeOptionalFunctionNode(node.function);
+    writeFunctionNode(node.function);
     leaveScope(memberScope: true);
 
     _currentlyInNonimplementation = currentlyInNonimplementationSaved;
     assert(
         (node.concreteForwardingStubTarget != null) ||
-            !(node.isForwardingStub && node.function!.body != null),
+            !(node.isForwardingStub && node.function.body != null),
         "Invalid forwarding stub $node.");
   }
 
@@ -2251,7 +2242,7 @@
     writeByte(Tag.FunctionDeclaration);
     writeOffset(node.fileOffset);
     writeVariableDeclaration(node.variable);
-    writeFunctionNode(node.function!);
+    writeFunctionNode(node.function);
   }
 
   @override
diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart
index fca44c6..523a5a2 100644
--- a/pkg/kernel/lib/binary/tag.dart
+++ b/pkg/kernel/lib/binary/tag.dart
@@ -174,7 +174,7 @@
   /// Internal version of kernel binary format.
   /// Bump it when making incompatible changes in kernel binaries.
   /// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
-  static const int BinaryFormatVersion = 60;
+  static const int BinaryFormatVersion = 61;
 }
 
 abstract class ConstantTag {
diff --git a/pkg/kernel/lib/clone.dart b/pkg/kernel/lib/clone.dart
index 3f09922..6c76c2f 100644
--- a/pkg/kernel/lib/clone.dart
+++ b/pkg/kernel/lib/clone.dart
@@ -485,8 +485,8 @@
     // Create the declaration before cloning the body to support recursive
     // [LocalFunctionInvocation] nodes.
     FunctionDeclaration declaration =
-        new FunctionDeclaration(newVariable, null);
-    FunctionNode functionNode = clone(node.function!);
+        new FunctionDeclaration(newVariable, dummyFunctionNode);
+    FunctionNode functionNode = clone(node.function);
     declaration.function = functionNode..parent = declaration;
     return declaration;
   }
@@ -757,7 +757,7 @@
     _activeFileUri = node.fileUri ?? _activeFileUri;
 
     Constructor result = new Constructor(
-      super.clone(node.function!),
+      super.clone(node.function),
       name: node.name,
       isConst: node.isConst,
       isExternal: node.isExternal,
@@ -781,7 +781,7 @@
     final Uri? activeFileUriSaved = _activeFileUri;
     _activeFileUri = node.fileUri ?? _activeFileUri;
     Procedure result = new Procedure(
-        node.name, node.kind, super.clone(node.function!),
+        node.name, node.kind, super.clone(node.function),
         reference: reference,
         transformerFlags: node.transformerFlags,
         fileUri: _activeFileUri,
diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart
index 3565196..27edbc1 100644
--- a/pkg/kernel/lib/text/ast_to_text.dart
+++ b/pkg/kernel/lib/text/ast_to_text.dart
@@ -1206,13 +1206,13 @@
       case ProcedureStubKind.ConcreteForwardingStub:
       case ProcedureStubKind.NoSuchMethodForwarder:
       case ProcedureStubKind.ConcreteMixinStub:
-        writeFunction(node.function!, name: getMemberName(node));
+        writeFunction(node.function, name: getMemberName(node));
         break;
       case ProcedureStubKind.MemberSignature:
       case ProcedureStubKind.AbstractMixinStub:
-        writeFunction(node.function!,
+        writeFunction(node.function,
             name: getMemberName(node), terminateLine: false);
-        if (node.function!.body is ReturnStatement) {
+        if (node.function.body is ReturnStatement) {
           writeSymbol(';');
         }
         writeSymbol(' -> ');
@@ -1241,7 +1241,7 @@
     if (features.isNotEmpty) {
       writeWord("/*${features.join(',')}*/");
     }
-    writeFunction(node.function!,
+    writeFunction(node.function,
         name: node.name, initializers: node.initializers);
   }
 
@@ -2289,8 +2289,9 @@
     writeAnnotationList(node.variable.annotations);
     writeIndentation();
     writeWord('function');
+    // ignore: unnecessary_null_comparison
     if (node.function != null) {
-      writeFunction(node.function!, name: getVariableName(node.variable));
+      writeFunction(node.function, name: getVariableName(node.variable));
     } else {
       writeWord(getVariableName(node.variable));
       endLine('...;');
diff --git a/pkg/kernel/lib/text/text_serializer.dart b/pkg/kernel/lib/text/text_serializer.dart
index 0899fe8..f544010 100644
--- a/pkg/kernel/lib/text/text_serializer.dart
+++ b/pkg/kernel/lib/text/text_serializer.dart
@@ -1750,7 +1750,7 @@
 
 TextSerializer<FunctionDeclaration> functionDeclarationSerializer =
     Wrapped<Tuple2<VariableDeclaration, FunctionNode>, FunctionDeclaration>(
-        (w) => Tuple2(w.variable, w.function!),
+        (w) => Tuple2(w.variable, w.function),
         (u) => FunctionDeclaration(u.first, u.second),
         Rebind(variableDeclarationSerializer, functionNodeSerializer));
 
@@ -1983,7 +1983,7 @@
 
 TextSerializer<Procedure> methodSerializer =
     Wrapped<Tuple3<Name, int, FunctionNode>, Procedure>(
-        (w) => Tuple3(w.name, w.flags, w.function!),
+        (w) => Tuple3(w.name, w.flags, w.function),
         (u) =>
             Procedure(u.first, ProcedureKind.Method, u.third)..flags = u.second,
         Tuple3Serializer(
@@ -1991,7 +1991,7 @@
 
 TextSerializer<Procedure> getterSerializer =
     Wrapped<Tuple3<Name, int, FunctionNode>, Procedure>(
-        (w) => Tuple3(w.name, w.flags, w.function!),
+        (w) => Tuple3(w.name, w.flags, w.function),
         (u) =>
             Procedure(u.first, ProcedureKind.Getter, u.third)..flags = u.second,
         Tuple3Serializer(
@@ -1999,7 +1999,7 @@
 
 TextSerializer<Procedure> setterSerializer =
     Wrapped<Tuple3<Name, int, FunctionNode>, Procedure>(
-        (w) => Tuple3(w.name, w.flags, w.function!),
+        (w) => Tuple3(w.name, w.flags, w.function),
         (u) =>
             Procedure(u.first, ProcedureKind.Setter, u.third)..flags = u.second,
         Tuple3Serializer(
@@ -2007,7 +2007,7 @@
 
 TextSerializer<Procedure> operatorSerializer =
     Wrapped<Tuple3<Name, int, FunctionNode>, Procedure>(
-        (w) => Tuple3(w.name, w.flags, w.function!),
+        (w) => Tuple3(w.name, w.flags, w.function),
         (u) => Procedure(u.first, ProcedureKind.Operator, u.third)
           ..flags = u.second,
         Tuple3Serializer(
@@ -2015,7 +2015,7 @@
 
 TextSerializer<Procedure> factorySerializer =
     Wrapped<Tuple3<Name, int, FunctionNode>, Procedure>(
-        (w) => Tuple3(w.name, w.flags, w.function!),
+        (w) => Tuple3(w.name, w.flags, w.function),
         (u) => Procedure(u.first, ProcedureKind.Factory, u.third)
           ..flags = u.second,
         Tuple3Serializer(
@@ -2024,7 +2024,7 @@
 TextSerializer<Constructor> constructorSerializer = Wrapped<
         Tuple3<Name, int, Tuple2<FunctionNode, List<Initializer>?>>,
         Constructor>(
-    (w) => Tuple3(w.name, w.flags, Tuple2(w.function!, w.initializers)),
+    (w) => Tuple3(w.name, w.flags, Tuple2(w.function, w.initializers)),
     (u) =>
         Constructor(u.third.first, name: u.first, initializers: u.third.second)
           ..flags = u.second,
diff --git a/pkg/kernel/lib/transformations/continuation.dart b/pkg/kernel/lib/transformations/continuation.dart
index c477db5..2404a99 100644
--- a/pkg/kernel/lib/transformations/continuation.dart
+++ b/pkg/kernel/lib/transformations/continuation.dart
@@ -189,7 +189,7 @@
     assert(const [
       Nullability.nonNullable,
       Nullability.legacy
-    ].contains(coreTypes.iterableGetIterator.function!.returnType.nullability));
+    ].contains(coreTypes.iterableGetIterator.function.returnType.nullability));
 
     final DartType elementType = stmt.getElementType(staticTypeContext);
     final iteratorType = InterfaceType(
@@ -1144,7 +1144,7 @@
 
   TreeNode visitFunctionDeclaration(
       FunctionDeclaration stmt, TreeNode? removalSentinel) {
-    stmt.function = transform(stmt.function!)..parent = stmt;
+    stmt.function = transform(stmt.function)..parent = stmt;
     statements.add(stmt);
     return removalSentinel ?? EmptyStatement();
   }
diff --git a/pkg/kernel/lib/transformations/mixin_full_resolution.dart b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
index b1a72a8..c9b5c39 100644
--- a/pkg/kernel/lib/transformations/mixin_full_resolution.dart
+++ b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
@@ -138,7 +138,7 @@
         if (setter != null) {
           setters.remove(field.name);
           VariableDeclaration parameter =
-              setter.function!.positionalParameters.first;
+              setter.function.positionalParameters.first;
           clone.isCovariant = parameter.isCovariant;
           clone.isGenericCovariantImpl = parameter.isGenericCovariantImpl;
         }
@@ -180,8 +180,8 @@
         var originalProcedure = class_.procedures[i];
         if (originalProcedure.name == procedure.name &&
             originalProcedure.kind == procedure.kind) {
-          FunctionNode src = originalProcedure.function!;
-          FunctionNode dst = procedure.function!;
+          FunctionNode src = originalProcedure.function;
+          FunctionNode dst = procedure.function;
 
           if (src.positionalParameters.length !=
                   dst.positionalParameters.length ||
@@ -200,8 +200,8 @@
       Procedure clone = cloner.cloneProcedure(procedure, reference);
       if (originalIndex != null) {
         Procedure originalProcedure = class_.procedures[originalIndex];
-        FunctionNode src = originalProcedure.function!;
-        FunctionNode dst = clone.function!;
+        FunctionNode src = originalProcedure.function;
+        FunctionNode dst = clone.function;
         assert(src.typeParameters.length == dst.typeParameters.length);
         for (int j = 0; j < src.typeParameters.length; ++j) {
           dst.typeParameters[j].flags = src.typeParameters[j].flags;
diff --git a/pkg/kernel/lib/transformations/scanner.dart b/pkg/kernel/lib/transformations/scanner.dart
index ee9b660..c49a934 100644
--- a/pkg/kernel/lib/transformations/scanner.dart
+++ b/pkg/kernel/lib/transformations/scanner.dart
@@ -2,36 +2,36 @@
 // 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.
 
-// @dart = 2.9
-
 library kernel.transformations.scanner;
 
 import '../ast.dart';
 import '../kernel.dart';
 
-abstract class Scanner<X extends TreeNode, Y extends TreeNode> {
-  final Scanner<Y, TreeNode> next;
+abstract class Scanner<X extends TreeNode?, Y extends TreeNode?> {
+  final Scanner<Y, TreeNode?>? next;
   Scanner(this.next);
   bool predicate(X x);
   ScanResult<X, Y> scan(TreeNode node);
 }
 
-class ScanResult<X extends TreeNode, Y extends TreeNode> {
-  Map<X, ScanResult<Y, TreeNode>> targets = new Map();
-  Map<X, ScanError> errors;
+class ScanResult<X extends TreeNode?, Y extends TreeNode?> {
+  Map<X, ScanResult<Y, TreeNode?>?> targets = new Map();
+  Map<X, ScanError>? errors;
+
+  String toString() => 'ScanResult<$X,$Y>';
 }
 
 class ScanError {}
 
-abstract class ClassScanner<Y extends TreeNode> implements Scanner<Class, Y> {
-  final Scanner<Y, TreeNode> next;
+abstract class ClassScanner<Y extends TreeNode?> implements Scanner<Class, Y> {
+  final Scanner<Y, TreeNode?>? next;
 
   ClassScanner(this.next);
 
   bool predicate(Class node);
 
   ScanResult<Class, Y> scan(TreeNode node) {
-    ScanResult<Class, Y> result = new ScanResult();
+    ScanResult<Class, Y> result = new ScanResult<Class, Y>();
 
     if (node is Class) {
       if (predicate(node)) {
@@ -64,14 +64,14 @@
 }
 
 abstract class FieldScanner<Y extends TreeNode> implements Scanner<Field, Y> {
-  final Scanner<Y, TreeNode> next;
+  final Scanner<Y, TreeNode>? next;
 
   FieldScanner(this.next);
 
   bool predicate(Field node);
 
   ScanResult<Field, Y> scan(TreeNode node) {
-    ScanResult<Field, Y> result = new ScanResult();
+    ScanResult<Field, Y> result = new ScanResult<Field, Y>();
 
     if (node is Field) {
       _scanField(node, result);
@@ -115,14 +115,14 @@
 }
 
 abstract class MemberScanner<Y extends TreeNode> implements Scanner<Member, Y> {
-  final Scanner<Y, TreeNode> next;
+  final Scanner<Y, TreeNode?>? next;
 
   MemberScanner(this.next);
 
   bool predicate(Member node);
 
   ScanResult<Member, Y> scan(TreeNode node) {
-    ScanResult<Member, Y> result = new ScanResult();
+    ScanResult<Member, Y> result = new ScanResult<Member, Y>();
 
     if (node is Member) {
       _scanMember(node, result);
@@ -165,16 +165,16 @@
   }
 }
 
-abstract class ProcedureScanner<Y extends TreeNode>
+abstract class ProcedureScanner<Y extends TreeNode?>
     implements Scanner<Procedure, Y> {
-  final Scanner<Y, TreeNode> next;
+  final Scanner<Y, TreeNode>? next;
 
   ProcedureScanner(this.next);
 
   bool predicate(Procedure node);
 
   ScanResult<Procedure, Y> scan(TreeNode node) {
-    ScanResult<Procedure, Y> result = new ScanResult();
+    ScanResult<Procedure, Y> result = new ScanResult<Procedure, Y>();
 
     if (node is Procedure) {
       _scanProcedure(node, result);
@@ -219,15 +219,16 @@
 
 abstract class ExpressionScanner<Y extends TreeNode>
     extends RecursiveResultVisitor<void> implements Scanner<Expression, Y> {
-  final Scanner<Y, TreeNode> next;
-  ScanResult<Expression, Y> _result;
+  final Scanner<Y, TreeNode>? next;
+  ScanResult<Expression, Y>? _result;
 
   ExpressionScanner(this.next);
 
   bool predicate(Expression node);
 
   ScanResult<Expression, Y> scan(TreeNode node) {
-    ScanResult<Expression, Y> result = _result = new ScanResult();
+    ScanResult<Expression, Y> result =
+        _result = new ScanResult<Expression, Y>();
     node.accept(this);
     _result = null;
     return result;
@@ -235,23 +236,24 @@
 
   void visitExpression(Expression node) {
     if (predicate(node)) {
-      _result.targets[node] = next?.scan(node);
+      _result!.targets[node] = next?.scan(node);
       // TODO: Update result.errors.
     }
   }
 }
 
-abstract class MethodInvocationScanner<Y extends TreeNode>
+abstract class MethodInvocationScanner<Y extends TreeNode?>
     extends RecursiveVisitor implements Scanner<MethodInvocation, Y> {
-  final Scanner<Y, TreeNode> next;
-  ScanResult<MethodInvocation, Y> _result;
+  final Scanner<Y, TreeNode>? next;
+  ScanResult<MethodInvocation, Y>? _result;
 
   MethodInvocationScanner(this.next);
 
   bool predicate(MethodInvocation node);
 
   ScanResult<MethodInvocation, Y> scan(TreeNode node) {
-    ScanResult<MethodInvocation, Y> result = _result = new ScanResult();
+    ScanResult<MethodInvocation, Y> result =
+        _result = new ScanResult<MethodInvocation, Y>();
     node.accept(this);
     _result = null;
     return result;
@@ -259,7 +261,7 @@
 
   void visitMethodInvocation(MethodInvocation node) {
     if (predicate(node)) {
-      _result.targets[node] = next?.scan(node);
+      _result!.targets[node] = next?.scan(node);
       // TODO: Update result.errors.
     }
   }
diff --git a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
index 7e81c8d..6538a7b 100644
--- a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
+++ b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
@@ -208,7 +208,7 @@
       return node;
     }
 
-    _addLocationArgument(node, target.function!, constructedClass,
+    _addLocationArgument(node, target.function, constructedClass,
         isConst: node.isConst);
     return node;
   }
@@ -234,7 +234,7 @@
       return node;
     }
 
-    _addLocationArgument(node, constructor.function!, constructedClass,
+    _addLocationArgument(node, constructor.function, constructedClass,
         isConst: node.isConst);
     return node;
   }
@@ -252,7 +252,7 @@
         // constant expression.
         !isConst) {
       final VariableDeclaration? creationLocationParameter = _getNamedParameter(
-        _currentFactory!.function!,
+        _currentFactory!.function,
         _creationLocationParameterName,
       );
       if (creationLocationParameter != null) {
@@ -401,7 +401,7 @@
         return;
       }
       assert(!_hasNamedParameter(
-        constructor.function!,
+        constructor.function,
         _creationLocationParameterName,
       ));
       final VariableDeclaration variable = new VariableDeclaration(
@@ -409,7 +409,7 @@
           type: new InterfaceType(
               _locationClass, clazz.enclosingLibrary.nullable),
           initializer: new NullLiteral());
-      if (!_maybeAddNamedParameter(constructor.function!, variable)) {
+      if (!_maybeAddNamedParameter(constructor.function, variable)) {
         return;
       }
 
@@ -424,7 +424,7 @@
           }
           _maybeAddCreationLocationArgument(
             initializer.arguments,
-            initializer.target.function!,
+            initializer.target.function,
             new VariableGet(variable),
             _locationClass,
           );
@@ -548,7 +548,7 @@
     for (Procedure procedure in clazz.procedures) {
       if (procedure.isFactory) {
         _maybeAddNamedParameter(
-          procedure.function!,
+          procedure.function,
           new VariableDeclaration(_creationLocationParameterName,
               type: new InterfaceType(
                   _locationClass, clazz.enclosingLibrary.nullable),
@@ -578,12 +578,12 @@
               _locationClass, clazz.enclosingLibrary.nullable),
           initializer: new NullLiteral());
       if (_hasNamedParameter(
-          constructor.function!, _creationLocationParameterName)) {
+          constructor.function, _creationLocationParameterName)) {
         // Constructor was already rewritten.
         // TODO(jacobr): is this case actually hit?
         return;
       }
-      if (!_maybeAddNamedParameter(constructor.function!, variable)) {
+      if (!_maybeAddNamedParameter(constructor.function, variable)) {
         return;
       }
       for (Initializer initializer in constructor.initializers) {
@@ -597,7 +597,7 @@
 
           _maybeAddCreationLocationArgument(
             initializer.arguments,
-            initializer.target.function!,
+            initializer.target.function,
             new VariableGet(variable),
             _locationClass,
           );
@@ -605,7 +605,7 @@
             _isSubclassOfWidget(initializer.target.enclosingClass)) {
           _maybeAddCreationLocationArgument(
             initializer.arguments,
-            initializer.target.function!,
+            initializer.target.function,
             new VariableGet(variable),
             _locationClass,
           );
diff --git a/pkg/kernel/lib/transformations/value_class.dart b/pkg/kernel/lib/transformations/value_class.dart
index 5a5f424..b50c56f 100644
--- a/pkg/kernel/lib/transformations/value_class.dart
+++ b/pkg/kernel/lib/transformations/value_class.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 library kernel.transformations.value_class;
 
 import 'package:kernel/type_environment.dart';
@@ -21,7 +19,7 @@
 }
 
 class JenkinsClassScanner extends ClassScanner<Procedure> {
-  JenkinsClassScanner(Scanner<Procedure, TreeNode> next) : super(next);
+  JenkinsClassScanner(Scanner<Procedure, TreeNode?> next) : super(next);
 
   bool predicate(Class node) {
     return node.name == "JenkinsSmiHash";
@@ -37,7 +35,7 @@
 }
 
 class AllMemberScanner extends MemberScanner<MethodInvocation> {
-  AllMemberScanner(Scanner<MethodInvocation, TreeNode> next) : super(next);
+  AllMemberScanner(Scanner<MethodInvocation, TreeNode?> next) : super(next);
 
   bool predicate(Member member) => true;
 }
@@ -78,7 +76,7 @@
 
 void transformValueClass(Class cls, CoreTypes coreTypes,
     ClassHierarchy hierarchy, TypeEnvironment typeEnvironment) {
-  Constructor syntheticConstructor = null;
+  Constructor? syntheticConstructor = null;
   for (Constructor constructor in cls.constructors) {
     if (constructor.isSynthetic) {
       syntheticConstructor = constructor;
@@ -87,9 +85,9 @@
 
   List<VariableDeclaration> allVariables = queryAllInstanceVariables(cls);
   List<VariableDeclaration> allVariablesList = allVariables.toList();
-  allVariablesList.sort((a, b) => a.name.compareTo(b.name));
+  allVariablesList.sort((a, b) => a.name!.compareTo(b.name!));
 
-  addConstructor(cls, coreTypes, syntheticConstructor);
+  addConstructor(cls, coreTypes, syntheticConstructor!);
   addEqualsOperator(cls, coreTypes, hierarchy, allVariablesList);
   addHashCode(cls, coreTypes, hierarchy, allVariablesList);
   addToString(cls, coreTypes, hierarchy, allVariablesList);
@@ -99,13 +97,13 @@
 
 void addConstructor(
     Class cls, CoreTypes coreTypes, Constructor syntheticConstructor) {
-  Constructor superConstructor = null;
-  for (Constructor constructor in cls.superclass.constructors) {
+  Constructor? superConstructor = null;
+  for (Constructor constructor in cls.superclass!.constructors) {
     if (constructor.name.text == "") {
       superConstructor = constructor;
     }
   }
-  List<VariableDeclaration> superParameters = superConstructor
+  List<VariableDeclaration> superParameters = superConstructor!
       .function.namedParameters
       .map<VariableDeclaration>((e) => VariableDeclaration(e.name, type: e.type)
         ..parent = syntheticConstructor.function)
@@ -118,7 +116,7 @@
 
   List<Initializer> initializersConstructor = cls.fields
       .map<Initializer>((f) =>
-          FieldInitializer(f, VariableGet(ownFields[f.name.text]))
+          FieldInitializer(f, VariableGet(ownFields[f.name.text]!))
             ..parent = syntheticConstructor)
       .toList();
 
@@ -157,8 +155,8 @@
     DartType fieldsType = variable.type;
     if (fieldsType is InterfaceType) {
       targetEquals =
-          hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="));
-      target = hierarchy.getInterfaceMember(cls, Name(variable.name));
+          hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="))!;
+      target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
     }
     targetsEquals[variable] = targetEquals;
     targets[variable] = target;
@@ -170,17 +168,17 @@
       FunctionNode(
           ReturnStatement(allVariables
               .map((f) => MethodInvocation(
-                  PropertyGet(ThisExpression(), Name(f.name), targets[f]),
+                  PropertyGet(ThisExpression(), Name(f.name!), targets[f]),
                   Name("=="),
                   Arguments([
                     PropertyGet(
-                        VariableGet(other, myType), Name(f.name), targets[f])
+                        VariableGet(other, myType), Name(f.name!), targets[f])
                   ]),
                   targetsEquals[f]))
               .fold(
                   IsExpression(VariableGet(other), myType),
                   (previousValue, element) => LogicalExpression(
-                      previousValue, LogicalExpressionOperator.AND, element))),
+                      previousValue!, LogicalExpressionOperator.AND, element))),
           returnType: returnType,
           positionalParameters: [other]),
       fileUri: cls.fileUri)
@@ -200,17 +198,22 @@
   }
   DartType returnType = coreTypes.intRawType(cls.enclosingLibrary.nonNullable);
 
-  Procedure hashCombine, hashFinish;
+  Procedure? hashCombine, hashFinish;
   HashCombineMethodsScanner hashCombineMethodsScanner =
       new HashCombineMethodsScanner();
   JenkinsClassScanner jenkinsScanner =
       new JenkinsClassScanner(hashCombineMethodsScanner);
   ScanResult<Class, Procedure> hashMethodsResult =
-      jenkinsScanner.scan(cls.enclosingLibrary.enclosingComponent);
+      jenkinsScanner.scan(cls.enclosingLibrary.enclosingComponent!);
   for (Class clazz in hashMethodsResult.targets.keys) {
-    for (Procedure procedure in hashMethodsResult.targets[clazz].targets.keys) {
-      if (procedure.name.text == "combine") hashCombine = procedure;
-      if (procedure.name.text == "finish") hashFinish = procedure;
+    for (Procedure procedure
+        in hashMethodsResult.targets[clazz]!.targets.keys) {
+      if (procedure.name.text == "combine") {
+        hashCombine = procedure;
+      }
+      if (procedure.name.text == "finish") {
+        hashFinish = procedure;
+      }
     }
   }
 
@@ -222,8 +225,8 @@
     DartType fieldsType = variable.type;
     if (fieldsType is InterfaceType) {
       targetHashcode =
-          hierarchy.getInterfaceMember(fieldsType.classNode, Name("hashCode"));
-      target = hierarchy.getInterfaceMember(cls, Name(variable.name));
+          hierarchy.getInterfaceMember(fieldsType.classNode, Name("hashCode"))!;
+      target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
     }
     targetsHashcode[variable] = targetHashcode;
     targets[variable] = target;
@@ -233,11 +236,12 @@
       ProcedureKind.Getter,
       FunctionNode(
           ReturnStatement(StaticInvocation(
-              hashFinish,
+              hashFinish!,
               Arguments([
                 allVariables
                     .map((f) => (PropertyGet(
-                        PropertyGet(ThisExpression(), Name(f.name), targets[f]),
+                        PropertyGet(
+                            ThisExpression(), Name(f.name!), targets[f]),
                         Name("hashCode"),
                         targetsHashcode[f])))
                     .fold(
@@ -249,7 +253,7 @@
                             hierarchy.getInterfaceMember(
                                 coreTypes.stringClass, Name("hashCode"))),
                         (previousValue, element) => StaticInvocation(
-                            hashCombine, Arguments([previousValue, element])))
+                            hashCombine!, Arguments([previousValue, element])))
               ]))),
           returnType: returnType),
       fileUri: cls.fileUri)
@@ -263,8 +267,8 @@
   for (VariableDeclaration variable in allVariablesList) {
     wording.add(StringLiteral("${variable.name}: "));
     wording.add(MethodInvocation(
-        PropertyGet(ThisExpression(), Name(variable.name),
-            hierarchy.getInterfaceMember(cls, Name(variable.name))),
+        PropertyGet(ThisExpression(), Name(variable.name!),
+            hierarchy.getInterfaceMember(cls, Name(variable.name!))),
         Name("toString"),
         Arguments([]),
         (variable.type is InterfaceType)
@@ -306,8 +310,8 @@
     DartType fieldsType = variable.type;
     if (fieldsType is InterfaceType) {
       targetEquals =
-          hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="));
-      target = hierarchy.getInterfaceMember(cls, Name(variable.name));
+          hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="))!;
+      target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
     }
     targetsEquals[variable] = targetEquals;
     targets[variable] = target;
@@ -321,7 +325,7 @@
               syntheticConstructor,
               Arguments([],
                   named: allVariables
-                      .map((f) => NamedExpression(f.name, VariableGet(f)))
+                      .map((f) => NamedExpression(f.name!, VariableGet(f)))
                       .toList()))),
           namedParameters: allVariables),
       fileUri: cls.fileUri)
@@ -329,13 +333,13 @@
 }
 
 List<VariableDeclaration> queryAllInstanceVariables(Class cls) {
-  Constructor superConstructor = null;
-  for (Constructor constructor in cls.superclass.constructors) {
+  Constructor? superConstructor = null;
+  for (Constructor constructor in cls.superclass!.constructors) {
     if (constructor.name.text == "") {
       superConstructor = constructor;
     }
   }
-  return superConstructor.function.namedParameters
+  return superConstructor!.function.namedParameters
       .map<VariableDeclaration>(
           (f) => VariableDeclaration(f.name, type: f.type))
       .toList()
@@ -344,20 +348,20 @@
 }
 
 void removeValueClassAnnotation(Class cls) {
-  int valueClassAnnotationIndex;
+  int? valueClassAnnotationIndex;
   for (int annotationIndex = 0;
       annotationIndex < cls.annotations.length;
       annotationIndex++) {
     Expression annotation = cls.annotations[annotationIndex];
     if (annotation is ConstantExpression &&
         annotation.constant is StringConstant) {
-      StringConstant constant = annotation.constant;
+      StringConstant constant = annotation.constant as StringConstant;
       if (constant.value == 'valueClass') {
         valueClassAnnotationIndex = annotationIndex;
       }
     }
   }
-  cls.annotations.removeAt(valueClassAnnotationIndex);
+  cls.annotations.removeAt(valueClassAnnotationIndex!);
 }
 
 void treatCopyWithCallSites(Component component, CoreTypes coreTypes,
@@ -368,11 +372,12 @@
   ScanResult<Member, MethodInvocation> copyWithCallSites =
       copyWithScanner.scan(component);
   for (Member memberWithCopyWith in copyWithCallSites.targets.keys) {
-    if (copyWithCallSites.targets[memberWithCopyWith].targets != null) {
+    Map<MethodInvocation, ScanResult<TreeNode?, TreeNode?>?>? targets =
+        copyWithCallSites.targets[memberWithCopyWith]?.targets;
+    if (targets != null) {
       StaticTypeContext staticTypeContext =
           StaticTypeContext(memberWithCopyWith, typeEnvironment);
-      for (MethodInvocation copyWithCall
-          in copyWithCallSites.targets[memberWithCopyWith].targets.keys) {
+      for (MethodInvocation copyWithCall in targets.keys) {
         AsExpression receiver = copyWithCall.receiver as AsExpression;
 
         Expression valueClassInstance = receiver.operand;
@@ -396,14 +401,14 @@
   for (NamedExpression argument in copyWithCall.arguments.named) {
     preTransformationArguments[argument.name] = argument.value;
   }
-  Constructor syntheticConstructor;
+  Constructor? syntheticConstructor;
   for (Constructor constructor in valueClass.constructors) {
     if (constructor.isSynthetic) {
       syntheticConstructor = constructor;
     }
   }
   List<VariableDeclaration> allArguments =
-      syntheticConstructor.function.namedParameters;
+      syntheticConstructor!.function.namedParameters;
 
   VariableDeclaration letVariable =
       VariableDeclaration.forValue(copyWithCall.receiver);
@@ -411,11 +416,11 @@
   for (VariableDeclaration argument in allArguments) {
     if (preTransformationArguments.containsKey(argument.name)) {
       postTransformationArguments.named.add(NamedExpression(
-          argument.name, preTransformationArguments[argument.name])
+          argument.name!, preTransformationArguments[argument.name]!)
         ..parent = postTransformationArguments);
     } else {
-      postTransformationArguments.named.add(NamedExpression(argument.name,
-          PropertyGet(VariableGet(letVariable), Name(argument.name)))
+      postTransformationArguments.named.add(NamedExpression(argument.name!,
+          PropertyGet(VariableGet(letVariable), Name(argument.name!)))
         ..parent = postTransformationArguments);
     }
   }
@@ -429,7 +434,7 @@
   for (Expression annotation in node.annotations) {
     if (annotation is ConstantExpression &&
         annotation.constant is StringConstant) {
-      StringConstant constant = annotation.constant;
+      StringConstant constant = annotation.constant as StringConstant;
       if (constant.value == "valueClass") {
         return true;
       }
diff --git a/pkg/kernel/lib/verifier.dart b/pkg/kernel/lib/verifier.dart
index a263a04..9431e8e 100644
--- a/pkg/kernel/lib/verifier.dart
+++ b/pkg/kernel/lib/verifier.dart
@@ -363,7 +363,7 @@
           "Only forwarding stubs can have a forwarding stub super target "
           "$node.");
     }
-    node.function!.accept(this);
+    node.function.accept(this);
     classTypeParametersAreInScope = false;
     visitList(node.annotations, this);
     exitParent(oldParent);
@@ -377,7 +377,7 @@
     // in scope in the initializer list.
     TreeNode? oldParent = enterParent(node);
     int stackHeight = enterLocalScope();
-    visitChildren(node.function!);
+    visitChildren(node.function);
     visitList(node.initializers, this);
     if (!isOutline) {
       checkInitializers(node);
diff --git a/pkg/kernel/test/verify_test.dart b/pkg/kernel/test/verify_test.dart
index 097200d..f8b1718 100644
--- a/pkg/kernel/test/verify_test.dart
+++ b/pkg/kernel/test/verify_test.dart
@@ -258,8 +258,8 @@
     "Incorrect parent pointer on FunctionNode:"
         " expected 'Procedure', but found: 'Null'.",
     (TestHarness test) {
-      var procedure =
-          new Procedure(new Name('bar'), ProcedureKind.Method, null);
+      var procedure = new Procedure(
+          new Name('bar'), ProcedureKind.Method, dummyFunctionNode);
       procedure.function = new FunctionNode(new EmptyStatement());
       test.addNode(procedure);
     },
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 52075ce..44d56f1 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -820,27 +820,41 @@
     throw _FfiStaticTypeError();
   }
 
+  /// Returns the class that should not be implemented or extended.
+  ///
+  /// If the superclass is not sealed, returns `null`.
   Class _extendsOrImplementsSealedClass(Class klass) {
-    final Class superClass = klass.superclass;
+    // Classes in dart:ffi themselves can extend FFI classes.
+    if (klass == arrayClass ||
+        klass == arraySizeClass ||
+        klass == compoundClass ||
+        klass == opaqueClass ||
+        klass == structClass ||
+        nativeTypesClasses.contains(klass)) {
+      return null;
+    }
 
     // The Opaque and Struct classes can be extended, but subclasses
     // cannot be (nor implemented).
-    if (klass != opaqueClass &&
-        klass != structClass &&
-        (hierarchy.isSubtypeOf(klass, opaqueClass) ||
-            hierarchy.isSubtypeOf(klass, compoundClass))) {
-      return superClass != opaqueClass && superClass != structClass
-          ? superClass
-          : null;
-    }
-
-    if (!nativeTypesClasses.contains(klass) && klass != arrayClass) {
-      for (final parent in nativeTypesClasses) {
-        if (hierarchy.isSubtypeOf(klass, parent)) {
-          return parent;
+    final onlyDirectExtendsClasses = [opaqueClass, structClass];
+    final superClass = klass.superclass;
+    for (final onlyDirectExtendsClass in onlyDirectExtendsClasses) {
+      if (hierarchy.isSubtypeOf(klass, onlyDirectExtendsClass)) {
+        if (superClass == onlyDirectExtendsClass) {
+          // Directly extending is fine.
+          return null;
+        } else {
+          return superClass;
         }
       }
     }
+
+    for (final parent in nativeTypesClasses) {
+      if (hierarchy.isSubtypeOf(klass, parent)) {
+        return parent;
+      }
+    }
+
     return null;
   }
 
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index c8a530d..b22fa57 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -373,8 +373,8 @@
   }
 
   /// Marker for noSuchMethod() invocation in the map of invocation targets.
-  static final Member kNoSuchMethodMarker =
-      new Procedure(new Name('noSuchMethod&&'), ProcedureKind.Method, null);
+  static final Member kNoSuchMethodMarker = new Procedure(
+      new Name('noSuchMethod&&'), ProcedureKind.Method, new FunctionNode(null));
 
   _DispatchableInvocation(Selector selector, Args<Type> args)
       : super(selector, args) {
diff --git a/pkg/vm_snapshot_analysis/lib/v8_profile.dart b/pkg/vm_snapshot_analysis/lib/v8_profile.dart
index 5ef79af..cbbe717 100644
--- a/pkg/vm_snapshot_analysis/lib/v8_profile.dart
+++ b/pkg/vm_snapshot_analysis/lib/v8_profile.dart
@@ -88,6 +88,25 @@
         m['strings'],
         edgesStartIndexForNode);
   }
+
+  @override
+  String toString() {
+    final buffer = StringBuffer();
+    buffer
+      ..write("Node count: ")
+      ..writeln(nodeCount)
+      ..write("Edge count: ")
+      ..writeln(edgeCount);
+    buffer.write("Nodes:");
+    for (final node in nodes) {
+      buffer
+        ..writeln()
+        ..write(node.index)
+        ..write(': ')
+        ..writeln(node);
+    }
+    return buffer.toString();
+  }
 }
 
 /// Meta-information about the serialized snapshot.
@@ -228,12 +247,6 @@
     }.toString();
   }
 
-  /// Returns the target of an outgoing edge with the given name (if any),
-  /// but first checks for a corresponding artificial edge indicating a dropped
-  /// object.
-  Node possiblyDroppedTarget(String edgeName) =>
-      this[':$edgeName'] ?? this[edgeName];
-
   /// Returns the target of an outgoing edge with the given name (if any).
   Node operator [](String edgeName) => this
       .edges
@@ -404,7 +417,7 @@
   ProgramInfoNode createInfoNodeFor(Node node) {
     switch (node.type) {
       case 'Code':
-        var owner = node.possiblyDroppedTarget('owner_');
+        var owner = node['owner_'];
         if (owner.type != 'Type') {
           final ownerNode =
               owner.type == 'Null' ? program.stubs : getInfoNodeFor(owner);
@@ -428,7 +441,7 @@
           // Artificial nodes may not have a data_ field.
           var data = node['data_'];
           if (data?.type == 'ClosureData') {
-            owner = data.possiblyDroppedTarget('parent_function_');
+            owner = data['parent_function_'];
           }
           return makeInfoNode(node.index,
               name: node.name,
diff --git a/pkg/vm_snapshot_analysis/test/utils.dart b/pkg/vm_snapshot_analysis/test/utils.dart
index bdc14e7..830ca06 100644
--- a/pkg/vm_snapshot_analysis/test/utils.dart
+++ b/pkg/vm_snapshot_analysis/test/utils.dart
@@ -60,17 +60,21 @@
       if (flag != null) '$flag=${snapshot.sizesJson}',
     ];
 
-    // Compile input.dart to native and output instruction sizes.
-    final result = await Process.run(dart2native, [
+    final args = [
       '-o',
       snapshot.outputBinary,
       '--packages=$packages',
       '--extra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}',
       mainDart,
-    ]);
+    ];
+
+    // Compile input.dart to native and output instruction sizes.
+    final result = await Process.run(dart2native, args);
 
     expect(result.exitCode, equals(0), reason: '''
-Compilation completed successfully.
+Compilation completed with exit code ${result.exitCode}.
+
+Command line: $dart2native ${args.join(' ')}
 
 stdout: ${result.stdout}
 stderr: ${result.stderr}
@@ -86,13 +90,18 @@
   });
 }
 
+const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES';
+
 Future withTempDir(Future Function(String dir) f) async {
   final tempDir =
       Directory.systemTemp.createTempSync('instruction-sizes-test-');
   try {
     await f(tempDir.path);
   } finally {
-    tempDir.deleteSync(recursive: true);
+    if (!Platform.environment.containsKey(keepTempKey) ||
+        Platform.environment[keepTempKey].isEmpty) {
+      tempDir.deleteSync(recursive: true);
+    }
   }
 }
 
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index b4415cf..3fce5e6 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -475,7 +475,7 @@
  *
  * Requires there to be a current isolate.
  *
- * \param object An object.
+ * \param object An object with identity.
  * \param peer A pointer to a native object or NULL.  This value is
  *   provided to callback when it is invoked.
  * \param external_allocation_size The number of externally allocated
@@ -531,7 +531,7 @@
  *
  * Requires there to be a current isolate.
  *
- * \param object An object.
+ * \param object An object with identity.
  * \param peer A pointer to a native object or NULL.  This value is
  *   provided to callback when it is invoked.
  * \param external_allocation_size The number of externally allocated
diff --git a/runtime/vm/canonical_tables.h b/runtime/vm/canonical_tables.h
index 004b07f..b0e25b5 100644
--- a/runtime/vm/canonical_tables.h
+++ b/runtime/vm/canonical_tables.h
@@ -30,12 +30,12 @@
     }
     return other.Equals(data_, len_);
   }
-  intptr_t Hash() const { return hash_; }
+  uword Hash() const { return hash_; }
 
  private:
   const CharType* data_;
   intptr_t len_;
-  intptr_t hash_;
+  uword hash_;
 };
 typedef CharArray<uint8_t> Latin1Array;
 typedef CharArray<uint16_t> UTF16Array;
@@ -55,14 +55,14 @@
     }
     return other.Equals(str_, begin_index_, len_);
   }
-  intptr_t Hash() const { return hash_; }
+  uword Hash() const { return hash_; }
 
  private:
   bool is_all() const { return begin_index_ == 0 && len_ == str_.Length(); }
   const String& str_;
   intptr_t begin_index_;
   intptr_t len_;
-  intptr_t hash_;
+  uword hash_;
 };
 
 class ConcatString {
@@ -77,12 +77,12 @@
     }
     return other.EqualsConcat(str1_, str2_);
   }
-  intptr_t Hash() const { return hash_; }
+  uword Hash() const { return hash_; }
 
  private:
   const String& str1_;
   const String& str2_;
-  intptr_t hash_;
+  uword hash_;
 };
 
 class SymbolTraits {
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 12f1e10..8841d37b 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -1045,12 +1045,6 @@
   }
 };
 
-// If DROPPED_NAME(name) is used for a v8 snapshot profile edge, then
-// possiblyDroppedTarget() should be used to retrieve the edge target in
-// pkg/vm_snapshot_analysis/v8_profile.dart so the artificial node is found
-// instead of its in-snapshot replacement.
-#define DROPPED_NAME(name) (":" #name)
-
 #if !defined(DART_PRECOMPILED_RUNTIME)
 class ClosureDataSerializationCluster : public SerializationCluster {
  public:
@@ -1097,25 +1091,6 @@
     }
   }
 
-  // Some closure data objects have their parent functions dropped from the
-  // snapshot, which makes it is impossible to recover program structure when
-  // analysing snapshot profile. To facilitate analysis of snapshot profiles
-  // we include artificial nodes into profile representing such dropped
-  // parent functions.
-  void WriteDroppedParentFunctionsIntoProfile(Serializer* s) {
-    ASSERT(s->profile_writer() != nullptr);
-
-    for (auto data : objects_) {
-      ObjectPtr parent_function =
-          WeakSerializationReference::Unwrap(data->untag()->parent_function());
-      if (s->CreateArtificialNodeIfNeeded(parent_function)) {
-        AutoTraceObject(data);
-        s->AttributePropertyRef(parent_function, DROPPED_NAME(parent_function_),
-                                /*permit_artificial_ref=*/true);
-      }
-    }
-  }
-
  private:
   GrowableArray<ClosureDataPtr> objects_;
 };
@@ -1958,38 +1933,19 @@
       if (kind == Snapshot::kFullAOT && FLAG_use_bare_instructions &&
           code->untag()->object_pool_ != ObjectPool::null()) {
         ObjectPoolPtr pool = code->untag()->object_pool_;
-
-        for (intptr_t i = 0; i < pool->untag()->length_; i++) {
-          uint8_t bits = pool->untag()->entry_bits()[i];
-          if (ObjectPool::TypeBits::decode(bits) ==
-              ObjectPool::EntryType::kTaggedObject) {
-            s->AttributeElementRef(pool->untag()->data()[i].raw_obj_, i);
-          }
-        }
+        // Non-empty per-code object pools should not be reachable in this mode.
+        ASSERT(!s->HasRef(pool) || pool == Object::empty_object_pool().ptr());
+        s->CreateArtificialNodeIfNeeded(pool);
+        s->AttributePropertyRef(pool, "object_pool_");
       }
-      if (code->untag()->static_calls_target_table_ != Array::null()) {
-        array_ = code->untag()->static_calls_target_table_;
-        intptr_t index = code->untag()->object_pool_ != ObjectPool::null()
-                             ? code->untag()->object_pool_->untag()->length_
-                             : 0;
-        for (auto entry : StaticCallsTable(array_)) {
-          auto kind = Code::KindField::decode(
-              Smi::Value(entry.Get<Code::kSCallTableKindAndOffset>()));
-          switch (kind) {
-            case Code::kCallViaCode:
-              // Code object in the pool.
-              continue;
-            case Code::kPcRelativeTTSCall:
-              // TTS will be reachable through type object which itself is
-              // in the pool.
-              continue;
-            case Code::kPcRelativeCall:
-            case Code::kPcRelativeTailCall:
-              auto destination = entry.Get<Code::kSCallTableCodeOrTypeTarget>();
-              ASSERT(destination->IsHeapObject() && destination->IsCode());
-              s->AttributeElementRef(destination, index++);
-          }
-        }
+      if (kind != Snapshot::kFullJIT &&
+          code->untag()->static_calls_target_table_ != Array::null()) {
+        auto const table = code->untag()->static_calls_target_table_;
+        // Non-empty static call target tables shouldn't be reachable in this
+        // mode.
+        ASSERT(!s->HasRef(table) || table == Object::empty_array().ptr());
+        s->CreateArtificialNodeIfNeeded(table);
+        s->AttributePropertyRef(table, "static_calls_target_table_");
       }
     }
 #endif  // defined(DART_PRECOMPILER)
@@ -1999,6 +1955,15 @@
       // for the discarded Code objects.
       ASSERT(kind == Snapshot::kFullAOT && FLAG_use_bare_instructions &&
              FLAG_dwarf_stack_traces_mode && !FLAG_retain_code_objects);
+#if defined(DART_PRECOMPILER)
+      if (FLAG_write_v8_snapshot_profile_to != nullptr) {
+        // Keep the owner as a (possibly artificial) node for snapshot analysis.
+        const auto& owner = code->untag()->owner_;
+        s->CreateArtificialNodeIfNeeded(owner);
+        s->AttributePropertyRef(owner, "owner_");
+      }
+#endif
+
       return;
     }
 
@@ -2047,25 +2012,6 @@
   GrowableArray<CodePtr>* objects() { return &objects_; }
   GrowableArray<CodePtr>* deferred_objects() { return &deferred_objects_; }
 
-  // Some code objects would have their owners dropped from the snapshot,
-  // which makes it is impossible to recover program structure when
-  // analysing snapshot profile. To facilitate analysis of snapshot profiles
-  // we include artificial nodes into profile representing such dropped
-  // owners.
-  void WriteDroppedOwnersIntoProfile(Serializer* s) {
-    ASSERT(s->profile_writer() != nullptr);
-
-    for (auto code : objects_) {
-      ObjectPtr owner =
-          WeakSerializationReference::Unwrap(code->untag()->owner_);
-      if (s->CreateArtificialNodeIfNeeded(owner) || Code::IsDiscarded(code)) {
-        AutoTraceObject(code);
-        s->AttributePropertyRef(owner, DROPPED_NAME(owner_),
-                                /*permit_artificial_ref=*/true);
-      }
-    }
-  }
-
  private:
   static const char* MakeDisambiguatedCodeName(Serializer* s, CodePtr c) {
     if (s->profile_writer() == nullptr) {
@@ -2417,43 +2363,28 @@
 
   void Trace(Serializer* s, ObjectPtr object) {
     ASSERT(s->kind() == Snapshot::kFullAOT);
-    WeakSerializationReferencePtr weak =
-        WeakSerializationReference::RawCast(object);
-    objects_.Add(weak);
+    objects_.Add(WeakSerializationReference::RawCast(object));
   }
 
   void RetraceEphemerons(Serializer* s) {
     for (intptr_t i = 0; i < objects_.length(); i++) {
       WeakSerializationReferencePtr weak = objects_[i];
-      if (!s->HasRef(weak->untag()->target())) {
+      if (!s->IsReachable(weak->untag()->target())) {
         s->Push(weak->untag()->replacement());
       }
     }
   }
 
-  intptr_t FinalizeWeak(Serializer* s) { return objects_.length(); }
+  intptr_t Count(Serializer* s) { return objects_.length(); }
 
   void WriteAlloc(Serializer* s) {
-    s->WriteCid(kWeakSerializationReferenceCid);
+    UNREACHABLE();  // No WSRs are serialized, and so this cluster is not added.
   }
 
-  void ForwardWeakRefs(Serializer* s) {
-    Heap* heap = s->heap();
-    for (intptr_t i = 0; i < objects_.length(); i++) {
-      WeakSerializationReferencePtr weak = objects_[i];
-
-      intptr_t id = heap->GetObjectId(weak->untag()->target());
-      if (id == kUnreachableReference) {
-        id = heap->GetObjectId(weak->untag()->replacement());
-        ASSERT(id != kUnreachableReference);
-      }
-      ASSERT(IsAllocatedReference(id));
-      heap->SetObjectId(weak, id);
-    }
+  void WriteFill(Serializer* s) {
+    UNREACHABLE();  // No WSRs are serialized, and so this cluster is not added.
   }
 
-  void WriteFill(Serializer* s) {}
-
  private:
   GrowableArray<WeakSerializationReferencePtr> objects_;
 };
@@ -2748,11 +2679,9 @@
     for (intptr_t i = 0; i < count; i++) {
       ObjectPtr object = objects_[i];
       s->AssignRef(object);
-      if (is_string_cluster) {
-        s->TraceStartWritingObject(type_, object, String::RawCast(object));
-      } else {
-        s->TraceStartWritingObject(type_, object, nullptr);
-      }
+      const StringPtr name =
+          is_string_cluster ? String::RawCast(object) : nullptr;
+      Serializer::WritingObjectScope scope(s, type_, object, name);
       uint32_t offset = s->GetDataOffset(object);
       s->TraceDataOffset(offset);
       ASSERT(Utils::IsAligned(
@@ -2761,7 +2690,6 @@
       s->WriteUnsigned((offset - running_offset) >>
                        compiler::target::ObjectAlignment::kObjectAlignmentLog2);
       running_offset = offset;
-      s->TraceEndWritingObject();
     }
     WriteCanonicalSetLayout(s);
   }
@@ -5050,7 +4978,7 @@
   void RetraceEphemerons(Serializer* s) {
     for (intptr_t i = 0; i < objects_.length(); i++) {
       WeakPropertyPtr property = objects_[i];
-      if (s->HasRef(property->untag()->key())) {
+      if (s->IsReachable(property->untag()->key())) {
         s->Push(property->untag()->value());
       }
     }
@@ -5650,18 +5578,18 @@
     if (!should_write_symbols_ && s->profile_writer() != nullptr) {
       // If writing V8 snapshot profile create an artifical node representing
       // VM isolate symbol table.
-      auto symbols_ref = s->AssignArtificialRef(symbols_.ptr());
-      const V8SnapshotProfileWriter::ObjectId symbols_snapshot_id(
-          V8SnapshotProfileWriter::kSnapshot, symbols_ref);
+      s->AssignArtificialRef(symbols_.ptr());
+      const auto& symbols_snapshot_id = s->GetProfileId(symbols_.ptr());
       s->profile_writer()->AddRoot(symbols_snapshot_id, "vm_symbols");
-      s->profile_writer()->SetObjectTypeAndName(symbols_snapshot_id, "Symbols",
-                                                nullptr);
+      s->profile_writer()->SetObjectType(symbols_snapshot_id, "Symbols");
       for (intptr_t i = 0; i < symbols_.Length(); i++) {
-        const V8SnapshotProfileWriter::ObjectId code_id(
-            V8SnapshotProfileWriter::kSnapshot, s->RefId(symbols_.At(i)));
         s->profile_writer()->AttributeReferenceTo(
             symbols_snapshot_id,
-            {code_id, V8SnapshotProfileWriter::Reference::kElement, i});
+            {
+                V8SnapshotProfileWriter::Reference::kElement,
+                {.offset = i},
+            },
+            s->GetProfileId(symbols_.At(i)));
       }
     }
   }
@@ -6156,16 +6084,16 @@
 void Serializer::AddBaseObject(ObjectPtr base_object,
                                const char* type,
                                const char* name) {
-  intptr_t ref = AssignRef(base_object);
+  AssignRef(base_object);
   num_base_objects_++;
 
   if ((profile_writer_ != nullptr) && (type != nullptr)) {
     if (name == nullptr) {
       name = "<base object>";
     }
-    profile_writer_->SetObjectTypeAndName(
-        {V8SnapshotProfileWriter::kSnapshot, ref}, type, name);
-    profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, ref});
+    const auto& profile_id = GetProfileId(base_object);
+    profile_writer_->SetObjectTypeAndName(profile_id, type, name);
+    profile_writer_->AddRoot(profile_id);
   }
 }
 
@@ -6184,7 +6112,8 @@
 }
 
 intptr_t Serializer::AssignArtificialRef(ObjectPtr object) {
-  ASSERT(object.IsHeapObject());
+  ASSERT(!object.IsHeapObject() || !object.IsInstructions());
+  ASSERT(heap_->GetObjectId(object) == kUnreachableReference);
   const intptr_t ref = -(next_ref_index_++);
   ASSERT(IsArtificialReference(ref));
   heap_->SetObjectId(object, ref);
@@ -6192,120 +6121,248 @@
   return ref;
 }
 
-void Serializer::FlushBytesWrittenToRoot() {
-#if defined(DART_PRECOMPILER)
-  if (profile_writer_ != nullptr) {
-    ASSERT(object_currently_writing_.id_ == 0);
-    // All bytes between objects are attributed into root node.
-    profile_writer_->AttributeBytesTo(
-        V8SnapshotProfileWriter::ArtificialRootId(),
-        stream_->Position() - object_currently_writing_.stream_start_);
-    object_currently_writing_.stream_start_ = stream_->Position();
-  }
-#endif
+void Serializer::FlushProfile() {
+  if (profile_writer_ == nullptr) return;
+  const intptr_t bytes =
+      stream_->Position() - object_currently_writing_.last_stream_position_;
+  profile_writer_->AttributeBytesTo(object_currently_writing_.id_, bytes);
+  object_currently_writing_.last_stream_position_ = stream_->Position();
 }
 
-void Serializer::TraceStartWritingObject(const char* type,
-                                         ObjectPtr obj,
-                                         StringPtr name) {
+V8SnapshotProfileWriter::ObjectId Serializer::GetProfileId(
+    ObjectPtr object) const {
+  // Instructions are handled separately.
+  ASSERT(!object->IsHeapObject() || !object->IsInstructions());
+  intptr_t heap_id = UnsafeRefId(object);
+  if (IsArtificialReference(heap_id)) {
+    return {V8SnapshotProfileWriter::kArtificial, -heap_id};
+  }
+  ASSERT(IsAllocatedReference(heap_id));
+  return {V8SnapshotProfileWriter::kSnapshot, heap_id};
+}
+
+void Serializer::AttributeReference(
+    ObjectPtr object,
+    const V8SnapshotProfileWriter::Reference& reference) {
   if (profile_writer_ == nullptr) return;
 
+#if defined(DART_PRECOMPILER)
+  // Make artificial nodes for dropped targets in WSRs.
+  if (object->IsHeapObject() && object->IsWeakSerializationReference()) {
+    const auto& wsr = WeakSerializationReference::RawCast(object);
+    const auto& target = wsr->untag()->target();
+    if (!CreateArtificialNodeIfNeeded(wsr) && HasArtificialRef(target)) {
+      // The target has artificial information used for snapshot analysis and
+      // the replacement is part of the snapshot, so write information for both.
+      const auto& replacement = wsr->untag()->replacement();
+      profile_writer_->AttributeDroppedReferenceTo(
+          object_currently_writing_.id_, reference, GetProfileId(target),
+          GetProfileId(replacement));
+      return;
+    }
+    // Either the target of the WSR is strongly referenced or the WSR itself is
+    // unreachable, in which case it shares an artificial object ID with the
+    // target due to CreateArtificialNodeIfNeeded, so fall through.
+    ASSERT(HasRef(target) || HasArtificialRef(wsr));
+  } else if (object_currently_writing_.id_.first ==
+             V8SnapshotProfileWriter::kArtificial) {
+    // We may need to recur when writing members of artificial nodes in
+    // CreateArtificialNodeIfNeeded.
+    CreateArtificialNodeIfNeeded(object);
+  }
+#endif
+  profile_writer_->AttributeReferenceTo(object_currently_writing_.id_,
+                                        reference, GetProfileId(object));
+}
+
+Serializer::WritingObjectScope::WritingObjectScope(
+    Serializer* serializer,
+    const V8SnapshotProfileWriter::ObjectId& id,
+    ObjectPtr object)
+    : serializer_(serializer),
+      old_object_(serializer->object_currently_writing_.object_),
+      old_id_(serializer->object_currently_writing_.id_),
+      old_cid_(serializer->object_currently_writing_.cid_) {
+  if (serializer_->profile_writer_ == nullptr) return;
+  // The ID should correspond to one already added appropriately to the
+  // profile writer.
+  ASSERT(serializer_->profile_writer_->HasId(id));
+  serializer_->FlushProfile();
+  serializer_->object_currently_writing_.object_ = object;
+  serializer_->object_currently_writing_.id_ = id;
+  serializer_->object_currently_writing_.cid_ =
+      object == nullptr ? -1 : object->GetClassIdMayBeSmi();
+}
+
+Serializer::WritingObjectScope::~WritingObjectScope() {
+  if (serializer_->profile_writer_ == nullptr) return;
+  serializer_->FlushProfile();
+  serializer_->object_currently_writing_.object_ = old_object_;
+  serializer_->object_currently_writing_.id_ = old_id_;
+  serializer_->object_currently_writing_.cid_ = old_cid_;
+}
+
+V8SnapshotProfileWriter::ObjectId Serializer::WritingObjectScope::ReserveId(
+    Serializer* s,
+    const char* type,
+    ObjectPtr obj,
+    StringPtr name) {
   const char* name_str = nullptr;
   if (name != nullptr) {
-    REUSABLE_STRING_HANDLESCOPE(thread());
+    REUSABLE_STRING_HANDLESCOPE(s->thread());
     String& str = reused_string_handle.Handle();
     str = name;
     name_str = str.ToCString();
   }
-
-  TraceStartWritingObject(type, obj, name_str);
+  return ReserveId(s, type, obj, name_str);
 }
 
-void Serializer::TraceStartWritingObject(const char* type,
-                                         ObjectPtr obj,
-                                         const char* name) {
-  if (profile_writer_ == nullptr) return;
-
-  intptr_t id = heap_->GetObjectId(obj);
-  intptr_t cid = obj->GetClassIdMayBeSmi();
-  if (IsArtificialReference(id)) {
-    id = -id;
+V8SnapshotProfileWriter::ObjectId Serializer::WritingObjectScope::ReserveId(
+    Serializer* s,
+    const char* type,
+    ObjectPtr obj,
+    const char* name) {
+  if (s->profile_writer_ == nullptr) {
+    return V8SnapshotProfileWriter::kArtificialRootId;
   }
-  ASSERT(IsAllocatedReference(id));
-
-  FlushBytesWrittenToRoot();
-  object_currently_writing_.object_ = obj;
-  object_currently_writing_.id_ = id;
-  object_currently_writing_.stream_start_ = stream_->Position();
-  object_currently_writing_.cid_ = cid;
-  profile_writer_->SetObjectTypeAndName(
-      {V8SnapshotProfileWriter::kSnapshot, id}, type, name);
-}
-
-void Serializer::TraceEndWritingObject() {
-  if (profile_writer_ != nullptr) {
-    ASSERT(IsAllocatedReference(object_currently_writing_.id_));
-    profile_writer_->AttributeBytesTo(
-        {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
-        stream_->Position() - object_currently_writing_.stream_start_);
-    object_currently_writing_ = ProfilingObject();
-    object_currently_writing_.stream_start_ = stream_->Position();
+  if (name == nullptr) {
+    // Handle some cases where there are obvious names to assign.
+    switch (obj->GetClassIdMayBeSmi()) {
+      case kSmiCid: {
+        name = OS::SCreate(s->zone(), "%" Pd "", Smi::Value(Smi::RawCast(obj)));
+        break;
+      }
+      case kMintCid: {
+        name = OS::SCreate(s->zone(), "%" Pd64 "",
+                           Mint::RawCast(obj)->untag()->value_);
+        break;
+      }
+      case kOneByteStringCid:
+      case kTwoByteStringCid: {
+        REUSABLE_STRING_HANDLESCOPE(s->thread());
+        String& str = reused_string_handle.Handle();
+        str = String::RawCast(obj);
+        name = str.ToCString();
+        break;
+      }
+    }
   }
+  const auto& obj_id = s->GetProfileId(obj);
+  s->profile_writer_->SetObjectTypeAndName(obj_id, type, name);
+  return obj_id;
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
 bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) {
   ASSERT(profile_writer() != nullptr);
 
-  if (obj->GetClassId() == kWeakSerializationReferenceCid) {
-    auto wsr = static_cast<WeakSerializationReferencePtr>(obj);
-    return CreateArtificialNodeIfNeeded(wsr->untag()->target());
-  }
-
-  intptr_t id = heap_->GetObjectId(obj);
-  if (IsAllocatedReference(id)) {
-    return false;
-  }
+  // UnsafeRefId will do lazy reference allocation for WSRs.
+  intptr_t id = UnsafeRefId(obj);
+  ASSERT(id != kUnallocatedReference);
   if (IsArtificialReference(id)) {
     return true;
   }
+  if (obj->IsHeapObject() && obj->IsWeakSerializationReference()) {
+    // The object ID for the WSR may need lazy resolution.
+    if (id == kUnallocatedReference) {
+      id = UnsafeRefId(obj);
+    }
+    ASSERT(id != kUnallocatedReference);
+    // Create an artificial node for an unreachable target at this point,
+    // whether or not the WSR itself is reachable.
+    const auto& target =
+        WeakSerializationReference::RawCast(obj)->untag()->target();
+    CreateArtificialNodeIfNeeded(target);
+    if (id == kUnreachableReference) {
+      ASSERT(HasArtificialRef(target));
+      // We can safely set the WSR's object ID to the target's artificial one,
+      // as that won't make it look reachable.
+      heap_->SetObjectId(obj, heap_->GetObjectId(target));
+      return true;
+    }
+    // The WSR is reachable, so continue to the IsAllocatedReference behavior.
+  }
+  if (IsAllocatedReference(id)) {
+    return false;
+  }
   ASSERT_EQUAL(id, kUnreachableReference);
   id = AssignArtificialRef(obj);
 
+  auto property = [](const char* name) -> V8SnapshotProfileWriter::Reference {
+    return {V8SnapshotProfileWriter::Reference::kProperty, {.name = name}};
+  };
+  auto element = [](intptr_t index) -> V8SnapshotProfileWriter::Reference {
+    return {V8SnapshotProfileWriter::Reference::kElement, {.offset = index}};
+  };
+
   const char* type = nullptr;
   StringPtr name_string = nullptr;
   const char* name = nullptr;
-  GrowableArray<std::pair<ObjectPtr, const char*>> links;
-  switch (obj->GetClassId()) {
+  GrowableArray<std::pair<ObjectPtr, V8SnapshotProfileWriter::Reference>> links;
+  switch (obj->GetClassIdMayBeSmi()) {
+    // For profiling static call target tables in AOT mode.
+    case kSmiCid: {
+      type = "Smi";
+      break;
+    }
+    // For profiling per-code object pools in bare instructions mode.
+    case kObjectPoolCid: {
+      type = "ObjectPool";
+      auto const pool = ObjectPool::RawCast(obj);
+      for (intptr_t i = 0; i < pool->untag()->length_; i++) {
+        uint8_t bits = pool->untag()->entry_bits()[i];
+        if (ObjectPool::TypeBits::decode(bits) ==
+            ObjectPool::EntryType::kTaggedObject) {
+          auto const elem = pool->untag()->data()[i].raw_obj_;
+          // Elements should be reachable from the global object pool.
+          ASSERT(HasRef(elem));
+          links.Add({elem, element(i)});
+        }
+      }
+      break;
+    }
+    // For profiling static call target tables in AOT mode.
+    case kArrayCid: {
+      type = "Array";
+      auto const array = Array::RawCast(obj);
+      for (intptr_t i = 0, n = Smi::Value(array->untag()->length()); i < n;
+           i++) {
+        ObjectPtr elem = array->untag()->data()[i];
+        links.Add({elem, element(i)});
+      }
+      break;
+    }
     case kFunctionCid: {
       FunctionPtr func = static_cast<FunctionPtr>(obj);
       type = "Function";
       name = FunctionSerializationCluster::MakeDisambiguatedFunctionName(this,
                                                                          func);
-      links.Add({func->untag()->owner(), "owner_"});
+      links.Add({func->untag()->owner(), property("owner_")});
       ObjectPtr data = func->untag()->data();
       if (data->GetClassId() == kClosureDataCid) {
-        links.Add({func->untag()->data(), "data_"});
+        links.Add({func->untag()->data(), property("data_")});
       }
       break;
     }
     case kClosureDataCid: {
       auto data = static_cast<ClosureDataPtr>(obj);
       type = "ClosureData";
-      links.Add({data->untag()->parent_function(), "parent_function_"});
+      links.Add(
+          {data->untag()->parent_function(), property("parent_function_")});
       break;
     }
     case kClassCid: {
       ClassPtr cls = static_cast<ClassPtr>(obj);
       type = "Class";
       name_string = cls->untag()->name();
-      links.Add({cls->untag()->library(), "library_"});
+      links.Add({cls->untag()->library(), property("library_")});
       break;
     }
     case kPatchClassCid: {
       PatchClassPtr patch_cls = static_cast<PatchClassPtr>(obj);
       type = "PatchClass";
-      links.Add({patch_cls->untag()->patched_class(), "patched_class_"});
+      links.Add(
+          {patch_cls->untag()->patched_class(), property("patched_class_")});
       break;
     }
     case kLibraryCid: {
@@ -6325,23 +6382,57 @@
     name = str.ToCString();
   }
 
-  // CreateArtificialNodeIfNeeded might call TraceStartWritingObject
-  // and these calls don't nest, so we need to call this outside
-  // of the tracing scope created below.
+  Serializer::WritingObjectScope scope(this, type, obj, name);
   for (const auto& link : links) {
-    CreateArtificialNodeIfNeeded(link.first);
+    AttributeReference(link.first, link.second);
   }
-
-  TraceStartWritingObject(type, obj, name);
-  for (const auto& link : links) {
-    AttributePropertyRef(link.first, link.second,
-                         /*permit_artificial_ref=*/true);
-  }
-  TraceEndWritingObject();
   return true;
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
+intptr_t Serializer::RefId(ObjectPtr object) const {
+  auto const id = UnsafeRefId(object);
+  if (IsAllocatedReference(id)) {
+    return id;
+  }
+  ASSERT(id == kUnreachableReference || IsArtificialReference(id));
+  REUSABLE_OBJECT_HANDLESCOPE(thread());
+  auto& handle = thread()->ObjectHandle();
+  handle = object;
+  FATAL("Reference to unreachable object %s", handle.ToCString());
+}
+
+intptr_t Serializer::UnsafeRefId(ObjectPtr object) const {
+  // The object id weak table holds image offsets for Instructions instead
+  // of ref indices.
+  ASSERT(!object->IsHeapObject() || !object->IsInstructions());
+  if (!Snapshot::IncludesCode(kind_) &&
+      object->GetClassIdMayBeSmi() == kCodeCid) {
+    return RefId(Object::null());
+  }
+  auto id = heap_->GetObjectId(object);
+  if (id != kUnallocatedReference) {
+    return id;
+  }
+  // This is the only case where we may still see unallocated references after
+  // WriteAlloc is finished.
+  if (object->IsWeakSerializationReference()) {
+    // Lazily set the object ID of the WSR to the object which will replace
+    // it in the snapshot.
+    auto const wsr = static_cast<WeakSerializationReferencePtr>(object);
+    // Either the target or the replacement must be allocated, since the
+    // WSR is reachable.
+    id = HasRef(wsr->untag()->target()) ? RefId(wsr->untag()->target())
+                                        : RefId(wsr->untag()->replacement());
+    heap_->SetObjectId(wsr, id);
+    return id;
+  }
+  REUSABLE_OBJECT_HANDLESCOPE(thread());
+  auto& handle = thread()->ObjectHandle();
+  handle = object;
+  FATAL("Reference for object %s is unallocated", handle.ToCString());
+}
+
 const char* Serializer::ReadOnlyObjectType(intptr_t cid) {
   switch (cid) {
     case kPcDescriptorsCid:
@@ -6606,15 +6697,17 @@
   const intptr_t offset = image_writer_->GetTextOffsetFor(instr, code);
 #if defined(DART_PRECOMPILER)
   if (profile_writer_ != nullptr) {
-    ASSERT(IsAllocatedReference(object_currently_writing_.id_));
+    ASSERT(IsAllocatedReference(object_currently_writing_.id_.second));
     const auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmText
                                   : V8SnapshotProfileWriter::kIsolateText;
     const V8SnapshotProfileWriter::ObjectId to_object(offset_space, offset);
-    const V8SnapshotProfileWriter::ObjectId from_object(
-        V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_);
     profile_writer_->AttributeReferenceTo(
-        from_object, {to_object, V8SnapshotProfileWriter::Reference::kProperty,
-                      profile_writer_->EnsureString("<instructions>")});
+        object_currently_writing_.id_,
+        {
+            V8SnapshotProfileWriter::Reference::kProperty,
+            {.name = "<instructions>"},
+        },
+        to_object);
   }
 
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
@@ -6646,17 +6739,19 @@
 void Serializer::TraceDataOffset(uint32_t offset) {
   if (profile_writer_ != nullptr) {
     // ROData cannot be roots.
-    ASSERT(IsAllocatedReference(object_currently_writing_.id_));
+    ASSERT(IsAllocatedReference(object_currently_writing_.id_.second));
     auto offset_space = vm_ ? V8SnapshotProfileWriter::kVmData
                             : V8SnapshotProfileWriter::kIsolateData;
-    V8SnapshotProfileWriter::ObjectId from_object = {
-        V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_};
     V8SnapshotProfileWriter::ObjectId to_object = {offset_space, offset};
     // TODO(sjindel): Give this edge a more appropriate type than element
     // (internal, maybe?).
     profile_writer_->AttributeReferenceTo(
-        from_object,
-        {to_object, V8SnapshotProfileWriter::Reference::kElement, 0});
+        object_currently_writing_.id_,
+        {
+            V8SnapshotProfileWriter::Reference::kElement,
+            {.offset = 0},
+        },
+        to_object);
   }
 }
 
@@ -6797,13 +6892,37 @@
   }
 }
 
+#define CID_CLUSTER(Type)                                                      \
+  reinterpret_cast<Type##SerializationCluster*>(clusters_by_cid_[k##Type##Cid])
+
 ZoneGrowableArray<Object*>* Serializer::Serialize(SerializationRoots* roots) {
+  // While object_currently_writing_ is initialized to the artificial root, we
+  // set up a scope to ensure proper flushing to the profile.
+  Serializer::WritingObjectScope scope(
+      this, V8SnapshotProfileWriter::kArtificialRootId);
   roots->AddBaseObjects(this);
 
   NoSafepointScope no_safepoint;
 
   roots->PushRoots(this);
 
+  // Resolving WeakSerializationReferences and WeakProperties may cause new
+  // objects to be pushed on the stack, and handling the changes to the stack
+  // may cause the targets of WeakSerializationReferences and keys of
+  // WeakProperties to become reachable, so we do this as a fixed point
+  // computation. Note that reachability is computed monotonically (an object
+  // can change from not reachable to reachable, but never the reverse), which
+  // is technically a conservative approximation for WSRs, but doing a strict
+  // analysis that allows non-motonoic reachability may not halt.
+  //
+  // To see this, take a WSR whose replacement causes the target of another WSR
+  // to become reachable, which then causes the target of the first WSR to
+  // become reachable, but the only way to reach the target is through the
+  // target of the second WSR, which was only reachable via the replacement
+  // the first.
+  //
+  // In practice, this case doesn't come up as replacements tend to be either
+  // null, smis, or singleton objects that do not contain WSRs currently.
   while (stack_.length() > 0) {
     // Strong references.
     while (stack_.length() > 0) {
@@ -6812,19 +6931,23 @@
 
     // Ephemeron references.
 #if defined(DART_PRECOMPILER)
-    if (auto const cluster =
-            reinterpret_cast<WeakSerializationReferenceSerializationCluster*>(
-                clusters_by_cid_[kWeakSerializationReferenceCid])) {
+    if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) {
       cluster->RetraceEphemerons(this);
     }
 #endif
-    if (auto const cluster =
-            reinterpret_cast<WeakPropertySerializationCluster*>(
-                clusters_by_cid_[kWeakPropertyCid])) {
+    if (auto const cluster = CID_CLUSTER(WeakProperty)) {
       cluster->RetraceEphemerons(this);
     }
   }
 
+#if defined(DART_PRECOMPILER)
+  if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) {
+    // Now that we have computed the reachability fixpoint, we remove the
+    // count of now-reachable WSRs as they are not actually serialized.
+    num_written_objects_ -= cluster->Count(this);
+  }
+#endif
+
   GrowableArray<SerializationCluster*> canonical_clusters;
   // The order that PostLoad runs matters for some classes because of
   // assumptions during canonicalization of some classes about what is already
@@ -6857,19 +6980,16 @@
     clusters.Add(clusters_by_cid_[kCodeCid]);
   }
   for (intptr_t cid = 0; cid < num_cids_; cid++) {
-    if (clusters_by_cid_[cid] != nullptr && cid != kCodeCid) {
+    // We don't actually have any WSR objects, references to them are replaced
+    // either with the target or replacement.
+    if (cid == kWeakSerializationReferenceCid) continue;
+    // The code serialization cluster is already handled above.
+    if (cid == kCodeCid) continue;
+    if (clusters_by_cid_[cid] != nullptr) {
       clusters.Add(clusters_by_cid_[cid]);
     }
   }
 
-#if defined(DART_PRECOMPILER)
-  if (auto const cluster =
-          reinterpret_cast<WeakSerializationReferenceSerializationCluster*>(
-              clusters_by_cid_[kWeakSerializationReferenceCid])) {
-    num_written_objects_ -= cluster->FinalizeWeak(this);
-  }
-#endif
-
   instructions_table_len_ = PrepareInstructions();
 
   intptr_t num_objects = num_base_objects_ + num_written_objects_;
@@ -6913,26 +7033,6 @@
   // And recorded them all in [objects_].
   ASSERT(objects_->length() == num_objects);
 
-#if defined(DART_PRECOMPILER)
-  if (auto cluster =
-          reinterpret_cast<WeakSerializationReferenceSerializationCluster*>(
-              clusters_by_cid_[kWeakSerializationReferenceCid])) {
-    cluster->ForwardWeakRefs(this);
-  }
-
-  // When writing snapshot profile, we want to retain some of the program
-  // structure information (e.g. information about libraries, classes and
-  // functions - even if it was dropped when writing snapshot itself).
-  if (FLAG_write_v8_snapshot_profile_to != nullptr) {
-    static_cast<CodeSerializationCluster*>(clusters_by_cid_[kCodeCid])
-        ->WriteDroppedOwnersIntoProfile(this);
-    if (auto const cluster = static_cast<ClosureDataSerializationCluster*>(
-            clusters_by_cid_[kClosureDataCid])) {
-      cluster->WriteDroppedParentFunctionsIntoProfile(this);
-    }
-  }
-#endif
-
   for (SerializationCluster* cluster : canonical_clusters) {
     cluster->WriteAndMeasureFill(this);
 #if defined(DEBUG)
@@ -6952,9 +7052,6 @@
   Write<int32_t>(kSectionMarker);
 #endif
 
-  FlushBytesWrittenToRoot();
-  object_currently_writing_.stream_start_ = stream_->Position();
-
   PrintSnapshotSizes();
 
   heap()->ResetObjectIdTable();
@@ -6989,6 +7086,14 @@
 #if defined(DART_PRECOMPILER)
   if (kind() != Snapshot::kFullAOT) return;
 
+  AssignArtificialRef(entries.ptr());
+  const auto& dispatch_table_snapshot_id = GetProfileId(entries.ptr());
+  if (profile_writer_ != nullptr) {
+    profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table");
+    profile_writer_->SetObjectType(dispatch_table_snapshot_id, "DispatchTable");
+  }
+  WritingObjectScope scope(this, dispatch_table_snapshot_id);
+
   const intptr_t bytes_before = bytes_written();
   const intptr_t table_length = entries.IsNull() ? 0 : entries.Length();
 
@@ -6999,8 +7104,7 @@
     return;
   }
 
-  auto const code_cluster =
-      reinterpret_cast<CodeSerializationCluster*>(clusters_by_cid_[kCodeCid]);
+  auto const code_cluster = CID_CLUSTER(Code);
   ASSERT(code_cluster != nullptr);
   // Reference IDs in a cluster are allocated sequentially, so we can use the
   // first code object's reference ID to calculate the cluster index.
@@ -7079,29 +7183,19 @@
   }
   dispatch_table_size_ = bytes_written() - bytes_before;
 
-  object_currently_writing_.stream_start_ = stream_->Position();
-  // If any bytes were written for the dispatch table, add it to the profile.
-  if (dispatch_table_size_ > 0 && profile_writer_ != nullptr) {
-    // Grab an unused ref index for a unique object id for the dispatch table.
-    const auto dispatch_table_id = next_ref_index_++;
-    const V8SnapshotProfileWriter::ObjectId dispatch_table_snapshot_id(
-        V8SnapshotProfileWriter::kSnapshot, dispatch_table_id);
-    profile_writer_->AddRoot(dispatch_table_snapshot_id, "dispatch_table");
-    profile_writer_->SetObjectTypeAndName(dispatch_table_snapshot_id,
-                                          "DispatchTable", nullptr);
-    profile_writer_->AttributeBytesTo(dispatch_table_snapshot_id,
-                                      dispatch_table_size_);
-
-    if (!entries.IsNull()) {
-      for (intptr_t i = 0; i < entries.Length(); i++) {
-        auto const code = Code::RawCast(entries.At(i));
-        if (code == Code::null()) continue;
-        const V8SnapshotProfileWriter::ObjectId code_id(
-            V8SnapshotProfileWriter::kSnapshot, RefId(code));
-        profile_writer_->AttributeReferenceTo(
-            dispatch_table_snapshot_id,
-            {code_id, V8SnapshotProfileWriter::Reference::kElement, i});
-      }
+  // If any bytes were written for the dispatch table, add the elements of
+  // the dispatch table in the profile.
+  if (profile_writer_ != nullptr && !entries.IsNull()) {
+    for (intptr_t i = 0; i < entries.Length(); i++) {
+      auto const code = Code::RawCast(entries.At(i));
+      if (code == Code::null()) continue;
+      profile_writer_->AttributeReferenceTo(
+          dispatch_table_snapshot_id,
+          {
+              V8SnapshotProfileWriter::Reference::kElement,
+              {.offset = i},
+          },
+          GetProfileId(code));
     }
   }
 
diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h
index cbb34de..ebaa3a4 100644
--- a/runtime/vm/clustered_snapshot.h
+++ b/runtime/vm/clustered_snapshot.h
@@ -251,12 +251,52 @@
   intptr_t bytes_written() { return stream_->bytes_written(); }
   intptr_t bytes_heap_allocated() { return bytes_heap_allocated_; }
 
-  void FlushBytesWrittenToRoot();
-  void TraceStartWritingObject(const char* type, ObjectPtr obj, StringPtr name);
-  void TraceStartWritingObject(const char* type,
-                               ObjectPtr obj,
-                               const char* name);
-  void TraceEndWritingObject();
+  class WritingObjectScope : ValueObject {
+   public:
+    WritingObjectScope(Serializer* serializer,
+                       const char* type,
+                       ObjectPtr object,
+                       StringPtr name)
+        : WritingObjectScope(serializer,
+                             ReserveId(serializer, type, object, name),
+                             object) {}
+
+    WritingObjectScope(Serializer* serializer,
+                       const char* type,
+                       ObjectPtr object,
+                       const char* name)
+        : WritingObjectScope(serializer,
+                             ReserveId(serializer, type, object, name),
+                             object) {}
+
+    WritingObjectScope(Serializer* serializer,
+                       const V8SnapshotProfileWriter::ObjectId& id,
+                       ObjectPtr object = nullptr);
+
+    WritingObjectScope(Serializer* serializer, ObjectPtr object)
+        : WritingObjectScope(serializer,
+                             serializer->GetProfileId(object),
+                             object) {}
+
+    ~WritingObjectScope();
+
+   private:
+    static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer,
+                                                       const char* type,
+                                                       ObjectPtr object,
+                                                       StringPtr name);
+
+    static V8SnapshotProfileWriter::ObjectId ReserveId(Serializer* serializer,
+                                                       const char* type,
+                                                       ObjectPtr object,
+                                                       const char* name);
+
+   private:
+    Serializer* const serializer_;
+    const ObjectPtr old_object_;
+    const V8SnapshotProfileWriter::ObjectId old_id_;
+    const classid_t old_cid_;
+  };
 
   // Writes raw data to the stream (basic type).
   // sizeof(T) must be in {1,2,4,8}.
@@ -276,72 +316,50 @@
   }
   void Align(intptr_t alignment) { stream_->Align(alignment); }
 
+  V8SnapshotProfileWriter::ObjectId GetProfileId(ObjectPtr object) const;
+
   void WriteRootRef(ObjectPtr object, const char* name = nullptr) {
     intptr_t id = RefId(object);
     WriteUnsigned(id);
     if (profile_writer_ != nullptr) {
-      profile_writer_->AddRoot({V8SnapshotProfileWriter::kSnapshot, id}, name);
+      profile_writer_->AddRoot(GetProfileId(object), name);
     }
   }
 
+  // Record a reference from the currently written object to the given object
+  // and return reference id for the given object.
+  void AttributeReference(ObjectPtr object,
+                          const V8SnapshotProfileWriter::Reference& reference);
+
+  void AttributeElementRef(ObjectPtr object, intptr_t index) {
+    AttributeReference(object, {V8SnapshotProfileWriter::Reference::kElement,
+                                {.offset = index}});
+  }
+
   void WriteElementRef(ObjectPtr object, intptr_t index) {
-    WriteUnsigned(AttributeElementRef(object, index));
+    AttributeElementRef(object, index);
+    WriteUnsigned(RefId(object));
   }
 
-  // Record a reference from the currently written object to the given object
-  // and return reference id for the given object.
-  intptr_t AttributeElementRef(ObjectPtr object,
-                               intptr_t index,
-                               bool permit_artificial_ref = false) {
-    intptr_t id = RefId(object, permit_artificial_ref);
-    if (profile_writer_ != nullptr) {
-      profile_writer_->AttributeReferenceTo(
-          {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
-          {{V8SnapshotProfileWriter::kSnapshot, id},
-           V8SnapshotProfileWriter::Reference::kElement,
-           index});
-    }
-    return id;
+  void AttributePropertyRef(ObjectPtr object, const char* property) {
+    AttributeReference(object, {V8SnapshotProfileWriter::Reference::kProperty,
+                                {.name = property}});
   }
 
   void WritePropertyRef(ObjectPtr object, const char* property) {
-    WriteUnsigned(AttributePropertyRef(object, property));
-  }
-
-  // Record a reference from the currently written object to the given object
-  // and return reference id for the given object.
-  intptr_t AttributePropertyRef(ObjectPtr object,
-                                const char* property,
-                                bool permit_artificial_ref = false) {
-    intptr_t id = RefId(object, permit_artificial_ref);
-    if (profile_writer_ != nullptr) {
-      profile_writer_->AttributeReferenceTo(
-          {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
-          {{V8SnapshotProfileWriter::kSnapshot, id},
-           V8SnapshotProfileWriter::Reference::kProperty,
-           profile_writer_->EnsureString(property)});
-    }
-    return id;
+    AttributePropertyRef(object, property);
+    WriteUnsigned(RefId(object));
   }
 
   void WriteOffsetRef(ObjectPtr object, intptr_t offset) {
     intptr_t id = RefId(object);
     WriteUnsigned(id);
     if (profile_writer_ != nullptr) {
-      const char* property = offsets_table_->FieldNameForOffset(
-          object_currently_writing_.cid_, offset);
-      if (property != nullptr) {
-        profile_writer_->AttributeReferenceTo(
-            {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
-            {{V8SnapshotProfileWriter::kSnapshot, id},
-             V8SnapshotProfileWriter::Reference::kProperty,
-             profile_writer_->EnsureString(property)});
+      if (auto const property = offsets_table_->FieldNameForOffset(
+              object_currently_writing_.cid_, offset)) {
+        AttributePropertyRef(object, property);
       } else {
-        profile_writer_->AttributeReferenceTo(
-            {V8SnapshotProfileWriter::kSnapshot, object_currently_writing_.id_},
-            {{V8SnapshotProfileWriter::kSnapshot, id},
-             V8SnapshotProfileWriter::Reference::kElement,
-             offset});
+        AttributeElementRef(object, offset);
       }
     }
   }
@@ -417,26 +435,29 @@
   // Returns the reference ID for the object. Fails for objects that have not
   // been allocated a reference ID yet, so should be used only after all
   // WriteAlloc calls.
-  intptr_t RefId(ObjectPtr object, bool permit_artificial_ref = false) {
-    // The object id weak table holds image offsets for Instructions instead
-    // of ref indices.
-    ASSERT(!object->IsHeapObject() || !object->IsInstructions());
-    auto const id = heap_->GetObjectId(object);
-    if (permit_artificial_ref && IsArtificialReference(id)) {
-      return -id;
-    }
-    ASSERT(!IsArtificialReference(id));
-    if (IsAllocatedReference(id)) {
-      return id;
-    }
-    if (object->IsCode() && !Snapshot::IncludesCode(kind_)) {
-      return RefId(Object::null());
-    }
-    FATAL("Missing ref");
-  }
+  intptr_t RefId(ObjectPtr object) const;
 
+  // Same as RefId, but allows artificial and unreachable references. Still
+  // fails for unallocated references.
+  intptr_t UnsafeRefId(ObjectPtr object) const;
+
+  // Whether the object is reachable.
+  bool IsReachable(ObjectPtr object) const {
+    return IsReachableReference(heap_->GetObjectId(object));
+  }
+  // Whether the object has an allocated reference.
   bool HasRef(ObjectPtr object) const {
-    return heap_->GetObjectId(object) != kUnreachableReference;
+    return IsAllocatedReference(heap_->GetObjectId(object));
+  }
+  // Whether the object only appears in the V8 snapshot profile.
+  bool HasArtificialRef(ObjectPtr object) const {
+    return IsArtificialReference(heap_->GetObjectId(object));
+  }
+  // Whether a node for the object already has been added to the V8 snapshot
+  // profile.
+  bool HasProfileNode(ObjectPtr object) const {
+    ASSERT(profile_writer_ != nullptr);
+    return profile_writer_->HasId(GetProfileId(object));
   }
   bool IsWritten(ObjectPtr object) const {
     return heap_->GetObjectId(object) > num_base_objects_;
@@ -444,6 +465,7 @@
 
  private:
   const char* ReadOnlyObjectType(intptr_t cid);
+  void FlushProfile();
 
   Heap* heap_;
   Zone* zone_;
@@ -471,8 +493,11 @@
   V8SnapshotProfileWriter* profile_writer_ = nullptr;
   struct ProfilingObject {
     ObjectPtr object_ = nullptr;
-    intptr_t id_ = 0;
-    intptr_t stream_start_ = 0;
+    // Unless within a WritingObjectScope, any bytes written are attributed to
+    // the artificial root.
+    V8SnapshotProfileWriter::ObjectId id_ =
+        V8SnapshotProfileWriter::kArtificialRootId;
+    intptr_t last_stream_position_ = 0;
     intptr_t cid_ = -1;
   } object_currently_writing_;
   OffsetsTable* offsets_table_ = nullptr;
@@ -494,10 +519,10 @@
 };
 
 #define AutoTraceObject(obj)                                                   \
-  SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr)
+  Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, nullptr)
 
 #define AutoTraceObjectName(obj, str)                                          \
-  SerializerWritingObjectScope scope_##__COUNTER__(s, name(), obj, str)
+  Serializer::WritingObjectScope scope_##__COUNTER__(s, name(), obj, str)
 
 #define WriteFieldValue(field, value) s->WritePropertyRef(value, #field);
 
@@ -509,29 +534,6 @@
 #define WriteCompressedField(obj, name)                                        \
   s->WritePropertyRef(obj->untag()->name(), #name "_")
 
-class SerializerWritingObjectScope {
- public:
-  SerializerWritingObjectScope(Serializer* serializer,
-                               const char* type,
-                               ObjectPtr object,
-                               StringPtr name)
-      : serializer_(serializer) {
-    serializer_->TraceStartWritingObject(type, object, name);
-  }
-
-  SerializerWritingObjectScope(Serializer* serializer,
-                               const char* type,
-                               ObjectPtr object,
-                               const char* name)
-      : serializer_(serializer) {
-    serializer_->TraceStartWritingObject(type, object, name);
-  }
-
-  ~SerializerWritingObjectScope() { serializer_->TraceEndWritingObject(); }
-
- private:
-  Serializer* serializer_;
-};
 
 // This class can be used to read version and features from a snapshot before
 // the VM has been initialized.
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 8dacf84..e638bf2 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -255,7 +255,7 @@
 
     static Value ValueOf(Pair kv) { return kv.value; }
 
-    static inline intptr_t Hashcode(Key key) {
+    static inline uword Hash(Key key) {
       if (key->IsFunction()) {
         return Function::Cast(*key).Hash();
       }
diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h
index 2af72af..315c712 100644
--- a/runtime/vm/compiler/aot/precompiler.h
+++ b/runtime/vm/compiler/aot/precompiler.h
@@ -42,7 +42,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key; }
+  static inline uword Hash(Key key) { return key; }
 
   static inline bool IsKeyEqual(Pair pair, Key key) { return pair == key; }
 };
@@ -60,7 +60,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -92,7 +92,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     const TokenPosition token_pos = key->token_pos();
     if (token_pos.IsReal()) {
       return token_pos.Hash();
@@ -118,7 +118,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->token_pos().Hash(); }
+  static inline uword Hash(Key key) { return key->token_pos().Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -138,7 +138,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -158,7 +158,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -178,7 +178,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -198,7 +198,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
@@ -218,7 +218,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->GetClassId(); }
+  static inline uword Hash(Key key) { return key->GetClassId(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->ptr() == key->ptr();
diff --git a/runtime/vm/compiler/aot/precompiler_tracer.h b/runtime/vm/compiler/aot/precompiler_tracer.h
index 70a9359..324e82c 100644
--- a/runtime/vm/compiler/aot/precompiler_tracer.h
+++ b/runtime/vm/compiler/aot/precompiler_tracer.h
@@ -54,7 +54,7 @@
   struct CString {
     const char* str;
     const intptr_t length;
-    intptr_t hash;
+    uword hash;
   };
 
   struct StringTableTraits {
diff --git a/runtime/vm/compiler/assembler/assembler_base.cc b/runtime/vm/compiler/assembler/assembler_base.cc
index 7034652..9d50186 100644
--- a/runtime/vm/compiler/assembler/assembler_base.cc
+++ b/runtime/vm/compiler/assembler/assembler_base.cc
@@ -259,7 +259,7 @@
   Breakpoint();
 }
 
-intptr_t ObjIndexPair::Hashcode(Key key) {
+uword ObjIndexPair::Hash(Key key) {
   if (key.type() != ObjectPoolBuilderEntry::kTaggedObject) {
     return key.raw_value_;
   }
diff --git a/runtime/vm/compiler/assembler/object_pool_builder.h b/runtime/vm/compiler/assembler/object_pool_builder.h
index 2bc2add..db77eb8 100644
--- a/runtime/vm/compiler/assembler/object_pool_builder.h
+++ b/runtime/vm/compiler/assembler/object_pool_builder.h
@@ -95,7 +95,7 @@
 
   static Value ValueOf(Pair kv) { return kv.value_; }
 
-  static intptr_t Hashcode(Key key);
+  static uword Hash(Key key);
 
   static inline bool IsKeyEqual(Pair kv, Key key) {
     if (kv.key_.entry_bits_ != key.entry_bits_) return false;
diff --git a/runtime/vm/compiler/backend/flow_graph.h b/runtime/vm/compiler/backend/flow_graph.h
index 09e3caf..b028847 100644
--- a/runtime/vm/compiler/backend/flow_graph.h
+++ b/runtime/vm/compiler/backend/flow_graph.h
@@ -58,7 +58,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     if (key.IsSmi()) {
       return Smi::Cast(key).Value();
     }
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 449eeaf..74532bb 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -649,14 +649,13 @@
   return result;
 }
 
-intptr_t Instruction::Hashcode() const {
-  intptr_t result = tag();
+uword Instruction::Hash() const {
+  uword result = tag();
   for (intptr_t i = 0; i < InputCount(); ++i) {
     Value* value = InputAt(i);
-    intptr_t j = value->definition()->ssa_temp_index();
-    result = result * 31 + j;
+    result = CombineHashes(result, value->definition()->ssa_temp_index());
   }
-  return result;
+  return FinalizeHash(result, kBitsPerInt32 - 1);
 }
 
 bool Instruction::Equals(Instruction* other) const {
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 3516f74..c4ff302 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -1077,7 +1077,7 @@
   virtual bool has_inlining_id() const { return inlining_id_ >= 0; }
 
   // Returns a hash code for use with hash maps.
-  virtual intptr_t Hashcode() const;
+  virtual uword Hash() const;
 
   // Compares two instructions.  Returns true, iff:
   // 1. They have the same tag.
@@ -2486,7 +2486,7 @@
     return CompilerState::Current().is_aot() ? kNotSpeculative : kGuardInputs;
   }
 
-  virtual intptr_t Hashcode() const {
+  virtual uword Hash() const {
     UNREACHABLE();
     return 0;
   }
@@ -2587,7 +2587,7 @@
 
   virtual bool HasUnknownSideEffects() const { return false; }
 
-  virtual intptr_t Hashcode() const {
+  virtual uword Hash() const {
     UNREACHABLE();
     return 0;
   }
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index fb3d09c..6edc0f3 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -428,9 +428,10 @@
     }
   }
 
-  intptr_t Hashcode() const {
-    return (flags_ * 63 + reinterpret_cast<intptr_t>(instance_)) * 31 +
-           FieldHashcode();
+  uword Hash() const {
+    return FinalizeHash(
+        CombineHashes(flags_, reinterpret_cast<uword>(instance_)),
+        kBitsPerInt32 - 1);
   }
 
   bool Equals(const Place* other) const {
@@ -460,7 +461,7 @@
                : (raw_selector_ == other->raw_selector_);
   }
 
-  intptr_t FieldHashcode() const {
+  uword FieldHash() const {
     return (kind() == kStaticField)
                ? String::Handle(Field::Handle(static_field().Original()).name())
                      .Hash()
diff --git a/runtime/vm/compiler/backend/slot.cc b/runtime/vm/compiler/backend/slot.cc
index 75fccf8..d0c4d9e 100644
--- a/runtime/vm/compiler/backend/slot.cc
+++ b/runtime/vm/compiler/backend/slot.cc
@@ -408,8 +408,8 @@
   }
 }
 
-intptr_t Slot::Hashcode() const {
-  intptr_t result = (static_cast<int8_t>(kind_) * 63 + offset_in_bytes_) * 31;
+uword Slot::Hash() const {
+  uword result = (static_cast<int8_t>(kind_) * 63 + offset_in_bytes_) * 31;
   if (IsDartField()) {
     result += String::Handle(DataAs<const Field>()->name()).Hash();
   } else if (IsLocalVariable()) {
diff --git a/runtime/vm/compiler/backend/slot.h b/runtime/vm/compiler/backend/slot.h
index 8a776de..c2078b7 100644
--- a/runtime/vm/compiler/backend/slot.h
+++ b/runtime/vm/compiler/backend/slot.h
@@ -265,7 +265,7 @@
   }
 
   bool Equals(const Slot* other) const;
-  intptr_t Hashcode() const;
+  uword Hash() const;
 
   bool IsIdentical(const Slot& other) const { return this == &other; }
 
diff --git a/runtime/vm/compiler/frontend/kernel_fingerprints.cc b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
index ca9ebbf..aff6de7 100644
--- a/runtime/vm/compiler/frontend/kernel_fingerprints.cc
+++ b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
@@ -778,9 +778,7 @@
   procedure_helper.SetJustRead(ProcedureHelper::kName);
 
   procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction);
-  if (ReadTag() == kSomething) {
-    CalculateFunctionNodeFingerprint();
-  }
+  CalculateFunctionNodeFingerprint();
 
   BuildHash(procedure_helper.kind_);
   BuildHash(procedure_helper.flags_);
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index c83e6cb..4ede221 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -1116,9 +1116,7 @@
       if (++next_read_ == field) return;
       FALL_THROUGH;
     case kFunction:
-      if (helper_->ReadTag() == kSomething) {
-        helper_->SkipFunctionNode();  // read function node.
-      }
+      helper_->SkipFunctionNode();  // read function node.
       if (++next_read_ == field) return;
       FALL_THROUGH;
     case kEnd:
@@ -2105,10 +2103,6 @@
   if (tag == kProcedure) {
     ProcedureHelper procedure_helper(this);
     procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction);
-    if (ReadTag() == kNothing) {  // read function node tag.
-      // Running a procedure without a function node doesn't make sense.
-      UNREACHABLE();
-    }
     // Now at start of FunctionNode.
   } else if (tag == kConstructor) {
     ConstructorHelper constructor_helper(this);
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index f55386b..e2be4a9 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -512,9 +512,7 @@
 void ScopeBuilder::VisitProcedure() {
   ProcedureHelper procedure_helper(&helper_);
   procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction);
-  if (helper_.ReadTag() == kSomething) {
-    VisitFunctionNode();
-  }
+  VisitFunctionNode();
 }
 
 void ScopeBuilder::VisitField() {
diff --git a/runtime/vm/compiler/relocation.h b/runtime/vm/compiler/relocation.h
index e9b6d44..948dffc 100644
--- a/runtime/vm/compiler/relocation.h
+++ b/runtime/vm/compiler/relocation.h
@@ -117,8 +117,8 @@
 
   static Key KeyOf(Pair kv) { return kv.instructions; }
   static ValueType ValueOf(Pair kv) { return kv.value; }
-  static inline intptr_t Hashcode(Key key) {
-    return static_cast<intptr_t>(key);
+  static inline uword Hash(Key key) {
+    return Utils::WordHash(static_cast<intptr_t>(key));
   }
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair.instructions == key;
diff --git a/runtime/vm/compiler/write_barrier_elimination.cc b/runtime/vm/compiler/write_barrier_elimination.cc
index 84b000f..855cffb 100644
--- a/runtime/vm/compiler/write_barrier_elimination.cc
+++ b/runtime/vm/compiler/write_barrier_elimination.cc
@@ -2,8 +2,6 @@
 // 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.
 
-#include <functional>
-
 #include "vm/compiler/backend/flow_graph.h"
 #include "vm/compiler/compiler_pass.h"
 #include "vm/compiler/write_barrier_elimination.h"
@@ -31,7 +29,9 @@
 
   static Key KeyOf(Pair kv) { return kv.definition; }
   static Value ValueOf(Pair kv) { return kv.index; }
-  static inline intptr_t Hashcode(Key key) { return std::hash<Key>()(key); }
+  static inline uword Hash(Key key) {
+    return Utils::WordHash(reinterpret_cast<intptr_t>(key));
+  }
   static inline bool IsKeyEqual(Pair kv, Key key) {
     return kv.definition == key;
   }
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 206aad7..a87928a 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -991,6 +991,23 @@
   obj1_ref->set_ptr(obj2_ref);
 }
 
+// TODO(https://dartbug.com/38491): Reject Unions here as well.
+static bool IsFfiStruct(Thread* T, const Object& obj) {
+  if (obj.IsNull()) {
+    return false;
+  }
+
+  // CFE guarantees we can only have direct subclasses of `Struct`
+  // (no implementations or indirect subclasses are allowed).
+  const auto& klass = Class::Handle(Z, obj.clazz());
+  const auto& super_klass = Class::Handle(Z, klass.SuperClass());
+  if (super_klass.Name() != Symbols::Struct().ptr()) {
+    return false;
+  }
+  const auto& library = Library::Handle(Z, super_klass.library());
+  return library.url() == Symbols::DartFfi().ptr();
+}
+
 static Dart_WeakPersistentHandle AllocateWeakPersistentHandle(
     Thread* thread,
     const Object& ref,
@@ -1000,6 +1017,13 @@
   if (!ref.ptr()->IsHeapObject()) {
     return NULL;
   }
+  if (ref.IsPointer()) {
+    return NULL;
+  }
+  if (IsFfiStruct(thread, ref)) {
+    return NULL;
+  }
+
   FinalizablePersistentHandle* finalizable_ref =
       FinalizablePersistentHandle::New(thread->isolate_group(), ref, peer,
                                        callback, external_allocation_size,
@@ -1008,16 +1032,14 @@
 }
 
 static Dart_WeakPersistentHandle AllocateWeakPersistentHandle(
-    Thread* thread,
+    Thread* T,
     Dart_Handle object,
     void* peer,
     intptr_t external_allocation_size,
     Dart_HandleFinalizer callback) {
-  REUSABLE_OBJECT_HANDLESCOPE(thread);
-  Object& ref = thread->ObjectHandle();
-  ref = Api::UnwrapHandle(object);
-  return AllocateWeakPersistentHandle(thread, ref, peer,
-                                      external_allocation_size, callback);
+  const auto& ref = Object::Handle(Z, Api::UnwrapHandle(object));
+  return AllocateWeakPersistentHandle(T, ref, peer, external_allocation_size,
+                                      callback);
 }
 
 static Dart_FinalizableHandle AllocateFinalizableHandle(
@@ -1029,6 +1051,12 @@
   if (!ref.ptr()->IsHeapObject()) {
     return NULL;
   }
+  if (ref.IsPointer()) {
+    return NULL;
+  }
+  if (IsFfiStruct(thread, ref)) {
+    return NULL;
+  }
 
   FinalizablePersistentHandle* finalizable_ref =
       FinalizablePersistentHandle::New(thread->isolate_group(), ref, peer,
@@ -1038,15 +1066,13 @@
 }
 
 static Dart_FinalizableHandle AllocateFinalizableHandle(
-    Thread* thread,
+    Thread* T,
     Dart_Handle object,
     void* peer,
     intptr_t external_allocation_size,
     Dart_HandleFinalizer callback) {
-  REUSABLE_OBJECT_HANDLESCOPE(thread);
-  Object& ref = thread->ObjectHandle();
-  ref = Api::UnwrapHandle(object);
-  return AllocateFinalizableHandle(thread, ref, peer, external_allocation_size,
+  const auto& ref = Object::Handle(Z, Api::UnwrapHandle(object));
+  return AllocateFinalizableHandle(T, ref, peer, external_allocation_size,
                                    callback);
 }
 
@@ -1055,15 +1081,13 @@
                              void* peer,
                              intptr_t external_allocation_size,
                              Dart_HandleFinalizer callback) {
-  Thread* thread = Thread::Current();
-  CHECK_ISOLATE(thread->isolate());
+  DARTSCOPE(Thread::Current());
   if (callback == NULL) {
     return NULL;
   }
-  TransitionNativeToVM transition(thread);
 
-  return AllocateWeakPersistentHandle(thread, object, peer,
-                                      external_allocation_size, callback);
+  return AllocateWeakPersistentHandle(T, object, peer, external_allocation_size,
+                                      callback);
 }
 
 DART_EXPORT Dart_FinalizableHandle
@@ -1071,14 +1095,13 @@
                           void* peer,
                           intptr_t external_allocation_size,
                           Dart_HandleFinalizer callback) {
-  Thread* thread = Thread::Current();
-  CHECK_ISOLATE(thread->isolate());
+  DARTSCOPE(Thread::Current());
   if (callback == nullptr) {
     return nullptr;
   }
-  TransitionNativeToVM transition(thread);
-  return AllocateFinalizableHandle(thread, object, peer,
-                                   external_allocation_size, callback);
+
+  return AllocateFinalizableHandle(T, object, peer, external_allocation_size,
+                                   callback);
 }
 
 DART_EXPORT void Dart_UpdateExternalSize(Dart_WeakPersistentHandle object,
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index d77bbcf..6046c6e 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -3368,6 +3368,35 @@
       Dart_NewWeakPersistentHandle(obj2, NULL, 0, NopCallback);
   EXPECT_EQ(ref2, static_cast<void*>(NULL));
 
+  // Pointer object.
+  Dart_Handle ffi_lib = Dart_LookupLibrary(NewString("dart:ffi"));
+  Dart_Handle pointer_type =
+      Dart_GetNonNullableType(ffi_lib, NewString("Pointer"), 0, NULL);
+  Dart_Handle obj3 = Dart_Allocate(pointer_type);
+  EXPECT_VALID(obj3);
+  Dart_WeakPersistentHandle ref3 =
+      Dart_NewWeakPersistentHandle(obj3, nullptr, 0, FinalizableHandleCallback);
+  EXPECT_EQ(ref3, static_cast<void*>(nullptr));
+
+  // Subtype of Struct object.
+  const char* kScriptChars = R"(
+      import 'dart:ffi';
+
+      class MyStruct extends Struct {
+        external Pointer notEmpty;
+      }
+  )";
+  Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL);
+  Dart_Handle my_struct_type =
+      Dart_GetNonNullableType(lib, NewString("MyStruct"), 0, NULL);
+  Dart_Handle obj4 = Dart_Allocate(my_struct_type);
+  EXPECT_VALID(obj4);
+  Dart_WeakPersistentHandle ref4 =
+      Dart_NewWeakPersistentHandle(obj4, nullptr, 0, FinalizableHandleCallback);
+  EXPECT_EQ(ref4, static_cast<void*>(nullptr));
+
+  // TODO(https://dartbug.com/38491): Reject Unions here as well.
+
   Dart_ExitScope();
 }
 
@@ -3388,6 +3417,33 @@
       Dart_NewFinalizableHandle(obj2, nullptr, 0, FinalizableHandleCallback);
   EXPECT_EQ(ref2, static_cast<void*>(nullptr));
 
+  // Pointer object.
+  Dart_Handle ffi_lib = Dart_LookupLibrary(NewString("dart:ffi"));
+  Dart_Handle pointer_type =
+      Dart_GetNonNullableType(ffi_lib, NewString("Pointer"), 0, NULL);
+  Dart_Handle obj3 = Dart_Allocate(pointer_type);
+  EXPECT_VALID(obj3);
+  Dart_FinalizableHandle ref3 =
+      Dart_NewFinalizableHandle(obj3, nullptr, 0, FinalizableHandleCallback);
+  EXPECT_EQ(ref3, static_cast<void*>(nullptr));
+
+  // Subtype of Struct object.
+  const char* kScriptChars = R"(
+      import 'dart:ffi';
+
+      class MyStruct extends Struct {
+        external Pointer notEmpty;
+      }
+  )";
+  Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL);
+  Dart_Handle my_struct_type =
+      Dart_GetNonNullableType(lib, NewString("MyStruct"), 0, NULL);
+  Dart_Handle obj4 = Dart_Allocate(my_struct_type);
+  EXPECT_VALID(obj4);
+  Dart_FinalizableHandle ref4 =
+      Dart_NewFinalizableHandle(obj4, nullptr, 0, FinalizableHandleCallback);
+  EXPECT_EQ(ref4, static_cast<void*>(nullptr));
+
   Dart_ExitScope();
 }
 
diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h
index b9d99de..6dd2515 100644
--- a/runtime/vm/debugger.h
+++ b/runtime/vm/debugger.h
@@ -548,7 +548,9 @@
 
   static Key KeyOf(Pair kv) { return kv.key; }
   static Value ValueOf(Pair kv) { return kv.value; }
-  static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); }
+  static uword Hash(Key key) {
+    return Utils::WordHash(reinterpret_cast<intptr_t>(key));
+  }
   static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
 };
 
diff --git a/runtime/vm/dwarf.h b/runtime/vm/dwarf.h
index ccf9c83..66851c4 100644
--- a/runtime/vm/dwarf.h
+++ b/runtime/vm/dwarf.h
@@ -28,7 +28,7 @@
 
   static Value ValueOf(Pair kv) { return kv.index_; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     return String::Handle(key->url()).Hash();
   }
 
@@ -61,7 +61,7 @@
 
   static Value ValueOf(Pair kv) { return kv.index_; }
 
-  static inline intptr_t Hashcode(Key key) { return key->token_pos().Hash(); }
+  static inline uword Hash(Key key) { return key->token_pos().Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair.function_->ptr() == key->ptr();
@@ -110,9 +110,9 @@
 
   static Value ValueOf(Pair kv) { return kv.value; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     // Instructions are always allocated in old space, so they don't move.
-    return FinalizeHash(key->PayloadStart(), 32);
+    return Utils::WordHash(key->PayloadStart());
   }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
diff --git a/runtime/vm/hash_map.h b/runtime/vm/hash_map.h
index 5af4c15..77a3389 100644
--- a/runtime/vm/hash_map.h
+++ b/runtime/vm/hash_map.h
@@ -155,7 +155,7 @@
   const typename KeyValueTrait::Value kNoValue =
       KeyValueTrait::ValueOf(typename KeyValueTrait::Pair());
 
-  uword hash = static_cast<uword>(KeyValueTrait::Hashcode(key));
+  uword hash = KeyValueTrait::Hash(key);
   uword pos = Bound(hash);
   if (KeyValueTrait::ValueOf(array_[pos].kv) != kNoValue) {
     if (KeyValueTrait::IsKeyEqual(array_[pos].kv, key)) {
@@ -301,8 +301,7 @@
   if (count_ >= array_size_ >> 1) Resize(array_size_ << 1);
   ASSERT(count_ < array_size_);
   count_++;
-  uword pos = Bound(
-      static_cast<uword>(KeyValueTrait::Hashcode(KeyValueTrait::KeyOf(kv))));
+  uword pos = Bound(KeyValueTrait::Hash(KeyValueTrait::KeyOf(kv)));
   if (KeyValueTrait::ValueOf(array_[pos].kv) == kNoValue) {
     array_[pos].kv = kv;
     array_[pos].next = kNil;
@@ -341,7 +340,7 @@
   const typename KeyValueTrait::Value kNoValue =
       KeyValueTrait::ValueOf(typename KeyValueTrait::Pair());
 
-  uword pos = Bound(static_cast<uword>(KeyValueTrait::Hashcode(key)));
+  uword pos = Bound(KeyValueTrait::Hash(key));
 
   // Check to see if the first element in the bucket is the one we want to
   // remove.
@@ -465,7 +464,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hashcode(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair kv, Key key) { return kv->Equals(key); }
 };
@@ -479,7 +478,7 @@
 
   static intptr_t KeyOf(Pair kv) { return kv.first(); }
   static T ValueOf(Pair kv) { return kv; }
-  static inline intptr_t Hashcode(Key key) { return key; }
+  static inline uword Hash(Key key) { return key; }
   static inline bool IsKeyEqual(Pair kv, Key key) { return kv.first() == key; }
 };
 
@@ -500,7 +499,7 @@
 
   static Key KeyOf(Pair kv) { return kv.key; }
   static Value ValueOf(Pair kv) { return kv.value; }
-  static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); }
+  static uword Hash(Key key) { return reinterpret_cast<intptr_t>(key); }
   static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
 };
 
@@ -510,7 +509,7 @@
   using Value = PointerKeyValueTrait<const char>::Value;
   using Pair = PointerKeyValueTrait<const char>::Pair;
 
-  static intptr_t Hashcode(Key key) {
+  static uword Hash(Key key) {
     ASSERT(key != nullptr);
     return Utils::StringHash(key, strlen(key));
   }
@@ -550,7 +549,7 @@
   typedef typename RawPointerKeyValueTrait<const char, V>::Value Value;
   typedef typename RawPointerKeyValueTrait<const char, V>::Pair Pair;
 
-  static intptr_t Hashcode(Key key) {
+  static uword Hash(Key key) {
     ASSERT(key != nullptr);
     return Utils::StringHash(key, strlen(key));
   }
@@ -603,7 +602,7 @@
 
   static Key KeyOf(Pair kv) { return kv.key; }
   static Value ValueOf(Pair kv) { return kv.value; }
-  static intptr_t Hashcode(Key key) { return key; }
+  static uword Hash(Key key) { return key; }
   static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
 };
 
@@ -653,8 +652,8 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) {
-    return reinterpret_cast<intptr_t>(key);
+  static inline uword Hash(Key key) {
+    return Utils::WordHash(reinterpret_cast<intptr_t>(key));
   }
 
   static inline bool IsKeyEqual(Pair pair, Key key) { return pair == key; }
diff --git a/runtime/vm/hash_map_test.cc b/runtime/vm/hash_map_test.cc
index 27385d9..6d02c08 100644
--- a/runtime/vm/hash_map_test.cc
+++ b/runtime/vm/hash_map_test.cc
@@ -11,7 +11,7 @@
 class TestValue {
  public:
   explicit TestValue(intptr_t x) : x_(x) {}
-  intptr_t Hashcode() const { return x_ & 1; }
+  uword Hash() const { return x_ & 1; }
   bool Equals(TestValue* other) { return x_ == other->x_; }
 
  private:
diff --git a/runtime/vm/heap/weak_table.h b/runtime/vm/heap/weak_table.h
index c72d773..d5ddc2e 100644
--- a/runtime/vm/heap/weak_table.h
+++ b/runtime/vm/heap/weak_table.h
@@ -193,9 +193,7 @@
 
   void Rehash();
 
-  static intptr_t Hash(ObjectPtr key) {
-    return static_cast<uintptr_t>(key) * 92821;
-  }
+  static uword Hash(ObjectPtr key) { return static_cast<uword>(key) * 92821; }
 
   Mutex mutex_;
 
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index f20022e..73cc3d6 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -142,7 +142,7 @@
 #endif
 }
 
-intptr_t ObjectOffsetTrait::Hashcode(Key key) {
+uword ObjectOffsetTrait::Hash(Key key) {
   ObjectPtr obj = key;
   ASSERT(!obj->IsSmi());
 
@@ -493,7 +493,7 @@
   if (profile_writer_ != nullptr) {
     const intptr_t end_position = stream->Position();
     profile_writer_->AttributeBytesTo(
-        V8SnapshotProfileWriter::ArtificialRootId(),
+        V8SnapshotProfileWriter::kArtificialRootId,
         end_position - start_position);
   }
 #endif
@@ -683,7 +683,11 @@
       const intptr_t element_offset = id.second - parent_id.second;
       profile_writer_->AttributeReferenceTo(
           parent_id,
-          {id, V8SnapshotProfileWriter::Reference::kElement, element_offset});
+          {
+              V8SnapshotProfileWriter::Reference::kElement,
+              {.offset = element_offset},
+          },
+          id);
       // Later objects will have the InstructionsSection as a parent if in
       // bare instructions mode, otherwise the image.
       if (bare_instruction_payloads) {
@@ -747,7 +751,11 @@
       const intptr_t element_offset = id.second - parent_id.second;
       profile_writer_->AttributeReferenceTo(
           parent_id,
-          {id, V8SnapshotProfileWriter::Reference::kElement, element_offset});
+          {
+              V8SnapshotProfileWriter::Reference::kElement,
+              {.offset = element_offset},
+          },
+          id);
     }
 #endif
 
diff --git a/runtime/vm/image_snapshot.h b/runtime/vm/image_snapshot.h
index a11356f..c234dd8 100644
--- a/runtime/vm/image_snapshot.h
+++ b/runtime/vm/image_snapshot.h
@@ -178,7 +178,7 @@
 
   static Key KeyOf(Pair kv) { return kv.object; }
   static Value ValueOf(Pair kv) { return kv.offset; }
-  static intptr_t Hashcode(Key key);
+  static uword Hash(Key key);
   static inline bool IsKeyEqual(Pair pair, Key key);
 };
 
@@ -472,13 +472,14 @@
         stream_(ASSERT_NOTNULL(stream)),
         section_offset_(section_offset),
         start_offset_(stream_->Position() - section_offset),
-        object_type_(writer->ObjectTypeForProfile(object)) {}
+        object_type_(writer->ObjectTypeForProfile(object)),
+        object_name_(object.IsString() ? object.ToCString() : nullptr) {}
 
   ~TraceImageObjectScope() {
     if (writer_->profile_writer_ == nullptr) return;
     ASSERT(writer_->IsROSpace());
     writer_->profile_writer_->SetObjectTypeAndName(
-        {writer_->offset_space_, start_offset_}, object_type_, nullptr);
+        {writer_->offset_space_, start_offset_}, object_type_, object_name_);
     writer_->profile_writer_->AttributeBytesTo(
         {writer_->offset_space_, start_offset_},
         stream_->Position() - section_offset_ - start_offset_);
@@ -490,6 +491,7 @@
   const intptr_t section_offset_;
   const intptr_t start_offset_;
   const char* const object_type_;
+  const char* const object_name_;
 
   DISALLOW_COPY_AND_ASSIGN(TraceImageObjectScope);
 };
diff --git a/runtime/vm/isolate_reload.h b/runtime/vm/isolate_reload.h
index a09f604..69a399c 100644
--- a/runtime/vm/isolate_reload.h
+++ b/runtime/vm/isolate_reload.h
@@ -256,7 +256,7 @@
 
     static Key KeyOf(Pair kv) { return kv->cid(); }
     static Value ValueOf(Pair kv) { return kv; }
-    static intptr_t Hashcode(Key key) { return key; }
+    static uword Hash(Key key) { return Utils::WordHash(key); }
     static bool IsKeyEqual(Pair kv, Key key) { return kv->cid() == key; }
   };
 
diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h
index 29eae67..2a78176 100644
--- a/runtime/vm/kernel_binary.h
+++ b/runtime/vm/kernel_binary.h
@@ -20,8 +20,8 @@
 static const uint32_t kMagicProgramFile = 0x90ABCDEFu;
 
 // Both version numbers are inclusive.
-static const uint32_t kMinSupportedKernelFormatVersion = 60;
-static const uint32_t kMaxSupportedKernelFormatVersion = 60;
+static const uint32_t kMinSupportedKernelFormatVersion = 61;
+static const uint32_t kMaxSupportedKernelFormatVersion = 61;
 
 // Keep in sync with package:kernel/lib/binary/tag.dart
 #define KERNEL_TAG_LIST(V)                                                     \
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index e5220d1..26329ce 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -1367,7 +1367,8 @@
           "runtime");
     }
     if (!Api::IsFfiEnabled() &&
-        target_library.url() == Symbols::DartFfi().ptr()) {
+        target_library.url() == Symbols::DartFfi().ptr() &&
+        library->url() != Symbols::DartCore().ptr()) {
       H.ReportError(
           "import of dart:ffi is not supported in the current Dart runtime");
     }
@@ -2032,8 +2033,6 @@
 
   procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction);
 
-  Tag function_node_tag = helper_.ReadTag();
-  ASSERT(function_node_tag == kSomething);
   FunctionNodeHelper function_node_helper(&helper_);
   function_node_helper.ReadUntilIncluding(FunctionNodeHelper::kDartAsyncMarker);
   function.set_is_debuggable(function_node_helper.dart_async_marker_ ==
diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h
index dec4c98..1943dc8 100644
--- a/runtime/vm/kernel_loader.h
+++ b/runtime/vm/kernel_loader.h
@@ -168,7 +168,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->uri->Hash(); }
+  static inline uword Hash(Key key) { return key->uri->Hash(); }
 
   static inline bool IsKeyEqual(Pair kv, Key key) {
     // Only compare uri.
diff --git a/runtime/vm/malloc_hooks_tcmalloc.cc b/runtime/vm/malloc_hooks_tcmalloc.cc
index ff9bed6..787a429 100644
--- a/runtime/vm/malloc_hooks_tcmalloc.cc
+++ b/runtime/vm/malloc_hooks_tcmalloc.cc
@@ -180,7 +180,9 @@
 
   static Key KeyOf(Pair kv) { return kv.key; }
   static Value ValueOf(Pair kv) { return kv.value; }
-  static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); }
+  static uword Hash(Key key) {
+    return Utils::WordHash(reinterpret_cast<intptr_t>(key));
+  }
   static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
 };
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index ec54a16..2272cf7 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -6012,7 +6012,7 @@
   untag()->set_nullability(Smi::New(value));
 }
 
-intptr_t TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const {
+uword TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const {
   if (IsNull()) return kAllDynamicHash;
   if (IsRaw(from_index, len)) return kAllDynamicHash;
   uint32_t result = 0;
@@ -6045,10 +6045,9 @@
   return result;
 }
 
-intptr_t TypeArguments::ComputeHash() const {
+uword TypeArguments::ComputeHash() const {
   if (IsNull()) return kAllDynamicHash;
-  const intptr_t num_types = Length();
-  const uint32_t result = HashForRange(0, num_types);
+  const uword result = HashForRange(0, Length());
   if (result != 0) {
     SetHash(result);
   }
@@ -6819,7 +6818,7 @@
   untag()->set_library_kernel_data(data.ptr());
 }
 
-intptr_t Function::Hash() const {
+uword Function::Hash() const {
   return String::HashRawSymbol(name());
 }
 
@@ -14567,7 +14566,7 @@
   return "CodeSourceMap";
 }
 
-intptr_t CompressedStackMaps::Hashcode() const {
+uword CompressedStackMaps::Hash() const {
   NoSafepointScope scope;
   uint8_t* data = UnsafeMutableNonPointer(&untag()->data()[0]);
   uint8_t* end = data + payload_size();
@@ -15111,7 +15110,7 @@
   StoreNonPointer(&untag()->can_patch_to_monomorphic_, value);
 }
 
-intptr_t UnlinkedCall::Hashcode() const {
+uword UnlinkedCall::Hash() const {
   return String::Handle(target_name()).Hash();
 }
 
@@ -19955,7 +19954,7 @@
   return false;
 }
 
-intptr_t AbstractType::Hash() const {
+uword AbstractType::Hash() const {
   // AbstractType is an abstract class.
   UNREACHABLE();
   return 0;
@@ -20662,7 +20661,7 @@
   type_args.EnumerateURIs(uris);
 }
 
-intptr_t Type::ComputeHash() const {
+uword Type::ComputeHash() const {
   ASSERT(IsFinalized());
   uint32_t result = type_class_id();
   // A legacy type should have the same hash as its non-nullable version to be
@@ -20695,7 +20694,7 @@
   return result;
 }
 
-intptr_t FunctionType::ComputeHash() const {
+uword FunctionType::ComputeHash() const {
   ASSERT(IsFinalized());
   uint32_t result = packed_fields();
   // A legacy type should have the same hash as its non-nullable version to be
@@ -21121,7 +21120,7 @@
   // Break cycle by not printing type arguments.
 }
 
-intptr_t TypeRef::Hash() const {
+uword TypeRef::Hash() const {
   // Do not use hash of the referenced type because
   //  - we could be in process of calculating it (as TypeRef is used to
   //    represent recursive references to types).
@@ -21614,7 +21613,7 @@
 }
 #endif  // DEBUG
 
-intptr_t TypeParameter::ComputeHash() const {
+uword TypeParameter::ComputeHash() const {
   ASSERT(IsFinalized() || IsBeingFinalized());  // Bound may not be finalized.
   uint32_t result = parameterized_class_id();
   // Hashing the bound reduces collisions, but may also create cycles.
@@ -22308,20 +22307,20 @@
   }
 }
 
-intptr_t String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
+uword String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
   StringHasher hasher;
   hasher.Add(str, begin_index, len);
   return hasher.Finalize();
 }
 
-intptr_t String::HashConcat(const String& str1, const String& str2) {
+uword String::HashConcat(const String& str1, const String& str2) {
   StringHasher hasher;
   hasher.Add(str1, 0, str1.Length());
   hasher.Add(str2, 0, str2.Length());
   return hasher.Finalize();
 }
 
-intptr_t String::Hash(StringPtr raw) {
+uword String::Hash(StringPtr raw) {
   StringHasher hasher;
   uword length = Smi::Value(raw->untag()->length());
   if (raw->IsOneByteString() || raw->IsExternalOneByteString()) {
@@ -22347,19 +22346,19 @@
   }
 }
 
-intptr_t String::Hash(const char* characters, intptr_t len) {
+uword String::Hash(const char* characters, intptr_t len) {
   StringHasher hasher;
   hasher.Add(reinterpret_cast<const uint8_t*>(characters), len);
   return hasher.Finalize();
 }
 
-intptr_t String::Hash(const uint8_t* characters, intptr_t len) {
+uword String::Hash(const uint8_t* characters, intptr_t len) {
   StringHasher hasher;
   hasher.Add(characters, len);
   return hasher.Finalize();
 }
 
-intptr_t String::Hash(const uint16_t* characters, intptr_t len) {
+uword String::Hash(const uint16_t* characters, intptr_t len) {
   StringHasher hasher;
   hasher.Add(characters, len);
   return hasher.Finalize();
@@ -24767,7 +24766,7 @@
   return buffer.buffer();
 }
 
-int64_t Closure::ComputeHash() const {
+uword Closure::ComputeHash() const {
   Thread* thread = Thread::Current();
   DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
   Zone* zone = thread->zone();
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 8c000c9..e5c69e7 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1918,7 +1918,7 @@
     return RoundedAllocationSize(sizeof(UntaggedUnlinkedCall));
   }
 
-  intptr_t Hashcode() const;
+  uword Hash() const;
   bool Equals(const UnlinkedCall& other) const;
 
   static UnlinkedCallPtr New();
@@ -2684,7 +2684,7 @@
     return OFFSET_OF(UntaggedFunction, unchecked_entry_point_);
   }
 
-  virtual intptr_t Hash() const;
+  virtual uword Hash() const;
 
   // Returns true if there is at least one debugger breakpoint
   // set in this function.
@@ -5752,7 +5752,7 @@
 
   // Methods to allow use with PointerKeyValueTrait to create sets of CSMs.
   bool Equals(const CompressedStackMaps* other) const { return Equals(*other); }
-  intptr_t Hashcode() const;
+  uword Hash() const;
 
   static intptr_t HeaderSize() { return sizeof(UntaggedCompressedStackMaps); }
   static intptr_t UnroundedSize(CompressedStackMapsPtr maps) {
@@ -7668,8 +7668,8 @@
     // Hash() is not stable until finalization is done.
     return 0;
   }
-  intptr_t Hash() const;
-  intptr_t HashForRange(intptr_t from_index, intptr_t len) const;
+  uword Hash() const;
+  uword HashForRange(intptr_t from_index, intptr_t len) const;
 
   static TypeArgumentsPtr New(intptr_t len, Heap::Space space = Heap::kOld);
 
@@ -7677,7 +7677,7 @@
   intptr_t ComputeNullability() const;
   void set_nullability(intptr_t value) const;
 
-  intptr_t ComputeHash() const;
+  uword ComputeHash() const;
   void SetHash(intptr_t value) const;
 
   // Check if the subvector of length 'len' starting at 'from_index' of this
@@ -7843,7 +7843,7 @@
   // list and mark ambiguous triplets to be printed.
   virtual void EnumerateURIs(URIs* uris) const;
 
-  virtual intptr_t Hash() const;
+  virtual uword Hash() const;
 
   // The name of this type's class, i.e. without the type argument names of this
   // type.
@@ -8047,8 +8047,8 @@
 #endif  // DEBUG
   virtual void EnumerateURIs(URIs* uris) const;
 
-  virtual intptr_t Hash() const;
-  intptr_t ComputeHash() const;
+  virtual uword Hash() const;
+  uword ComputeHash() const;
 
   static intptr_t InstanceSize() {
     return RoundedAllocationSize(sizeof(UntaggedType));
@@ -8191,8 +8191,8 @@
 #endif  // DEBUG
   virtual void EnumerateURIs(URIs* uris) const;
 
-  virtual intptr_t Hash() const;
-  intptr_t ComputeHash() const;
+  virtual uword Hash() const;
+  uword ComputeHash() const;
 
   bool IsSubtypeOf(const FunctionType& other, Heap::Space space) const;
 
@@ -8447,7 +8447,7 @@
 #endif  // DEBUG
   virtual void EnumerateURIs(URIs* uris) const;
 
-  virtual intptr_t Hash() const;
+  virtual uword Hash() const;
 
   static intptr_t InstanceSize() {
     return RoundedAllocationSize(sizeof(UntaggedTypeRef));
@@ -8561,7 +8561,7 @@
 #endif  // DEBUG
   virtual void EnumerateURIs(URIs* uris) const { return; }
 
-  virtual intptr_t Hash() const;
+  virtual uword Hash() const;
 
   // Returns type corresponding to [this] type parameter from the
   // given [instantiator_type_arguments] and [function_type_arguments].
@@ -8585,7 +8585,7 @@
                               Nullability nullability);
 
  private:
-  intptr_t ComputeHash() const;
+  uword ComputeHash() const;
   void SetHash(intptr_t value) const;
 
   void set_parameterized_class(const Class& value) const;
@@ -8918,8 +8918,8 @@
   }
   static intptr_t length_offset() { return OFFSET_OF(UntaggedString, length_); }
 
-  intptr_t Hash() const {
-    intptr_t result = GetCachedHash(ptr());
+  uword Hash() const {
+    uword result = GetCachedHash(ptr());
     if (result != 0) {
       return result;
     }
@@ -8928,7 +8928,7 @@
     return result;
   }
 
-  static intptr_t Hash(StringPtr raw);
+  static uword Hash(StringPtr raw);
 
   bool HasHash() const {
     ASSERT(Smi::New(0) == nullptr);
@@ -8944,19 +8944,19 @@
     return OFFSET_OF(UntaggedString, hash_);
 #endif
   }
-  static intptr_t Hash(const String& str, intptr_t begin_index, intptr_t len);
-  static intptr_t Hash(const char* characters, intptr_t len);
-  static intptr_t Hash(const uint16_t* characters, intptr_t len);
-  static intptr_t Hash(const int32_t* characters, intptr_t len);
-  static intptr_t HashRawSymbol(const StringPtr symbol) {
+  static uword Hash(const String& str, intptr_t begin_index, intptr_t len);
+  static uword Hash(const char* characters, intptr_t len);
+  static uword Hash(const uint16_t* characters, intptr_t len);
+  static uword Hash(const int32_t* characters, intptr_t len);
+  static uword HashRawSymbol(const StringPtr symbol) {
     ASSERT(symbol->untag()->IsCanonical());
-    intptr_t result = GetCachedHash(symbol);
+    const uword result = GetCachedHash(symbol);
     ASSERT(result != 0);
     return result;
   }
 
   // Returns the hash of str1 + str2.
-  static intptr_t HashConcat(const String& str1, const String& str2);
+  static uword HashConcat(const String& str1, const String& str2);
 
   virtual ObjectPtr HashCode() const { return Integer::New(Hash()); }
 
@@ -9178,7 +9178,7 @@
   // They are protected to avoid mistaking Latin-1 for UTF-8, but used
   // by friendly templated code (e.g., Symbols).
   bool Equals(const uint8_t* characters, intptr_t len) const;
-  static intptr_t Hash(const uint8_t* characters, intptr_t len);
+  static uword Hash(const uint8_t* characters, intptr_t len);
 
   void SetLength(intptr_t value) const {
     // This is only safe because we create a new Smi, which does not cause
@@ -10812,7 +10812,7 @@
   virtual uint32_t CanonicalizeHash() const {
     return Function::Handle(function()).Hash();
   }
-  int64_t ComputeHash() const;
+  uword ComputeHash() const;
 
   static ClosurePtr New(const TypeArguments& instantiator_type_arguments,
                         const TypeArguments& function_type_arguments,
@@ -11536,7 +11536,7 @@
   return array.At((index * kEntryLength) + kTargetFunctionIndex);
 }
 
-inline intptr_t Type::Hash() const {
+inline uword Type::Hash() const {
   ASSERT(IsFinalized());
   intptr_t result = Smi::Value(untag()->hash());
   if (result != 0) {
@@ -11551,7 +11551,7 @@
   untag()->set_hash(Smi::New(value));
 }
 
-inline intptr_t FunctionType::Hash() const {
+inline uword FunctionType::Hash() const {
   ASSERT(IsFinalized());
   intptr_t result = Smi::Value(untag()->hash());
   if (result != 0) {
@@ -11566,7 +11566,7 @@
   untag()->set_hash(Smi::New(value));
 }
 
-inline intptr_t TypeParameter::Hash() const {
+inline uword TypeParameter::Hash() const {
   ASSERT(IsFinalized() || IsBeingFinalized());  // Bound may not be finalized.
   intptr_t result = Smi::Value(untag()->hash());
   if (result != 0) {
@@ -11581,7 +11581,7 @@
   untag()->set_hash(Smi::New(value));
 }
 
-inline intptr_t TypeArguments::Hash() const {
+inline uword TypeArguments::Hash() const {
   if (IsNull()) return kAllDynamicHash;
   intptr_t result = Smi::Value(untag()->hash());
   if (result != 0) {
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index e741716..519ea48 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -236,7 +236,6 @@
   RW(GrowableObjectArray, ffi_callback_functions)                              \
   RW(Class, ffi_pointer_class)                                                 \
   RW(Class, ffi_native_type_class)                                             \
-  RW(Class, ffi_struct_class)                                                  \
   RW(Object, ffi_as_function_internal)                                         \
   // Please remember the last entry must be referred in the 'to' function below.
 
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 8d94071..5417f6d 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -1365,7 +1365,7 @@
   const String& clef = String::Handle(String::FromUTF16(clef_utf16, 2));
   int32_t clef_utf32[] = {0x1D11E};
   EXPECT(clef.Equals(clef_utf32, 1));
-  intptr_t hash32 = String::Hash(String::FromUTF32(clef_utf32, 1));
+  uword hash32 = String::Hash(String::FromUTF32(clef_utf32, 1));
   EXPECT_EQ(hash32, clef.Hash());
   EXPECT_EQ(hash32, String::HashConcat(
                         String::Handle(String::FromUTF16(clef_utf16, 1)),
diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc
index 9cf5d12..a61fb94 100644
--- a/runtime/vm/profiler_service.cc
+++ b/runtime/vm/profiler_service.cc
@@ -540,7 +540,7 @@
 
     static Value ValueOf(Pair kv) { return kv; }
 
-    static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+    static inline uword Hash(Key key) { return key->Hash(); }
 
     static inline bool IsKeyEqual(Pair kv, Key key) {
       return kv->function()->ptr() == key->ptr();
diff --git a/runtime/vm/program_visitor.cc b/runtime/vm/program_visitor.cc
index b8e3de1..2e4bb01 100644
--- a/runtime/vm/program_visitor.cc
+++ b/runtime/vm/program_visitor.cc
@@ -468,7 +468,7 @@
 
   static const intptr_t kHashBits = 30;
 
-  intptr_t Hashcode() {
+  uword Hash() {
     if (hash_ != 0) return hash_;
     uint32_t hash = 0;
     hash = CombineHashes(hash, spill_slot_bit_count_);
@@ -555,7 +555,7 @@
 
   static Key KeyOf(Pair kv) { return kv.key; }
   static Value ValueOf(Pair kv) { return kv.value; }
-  static intptr_t Hashcode(Key key) { return key->Hashcode(); }
+  static uword Hash(Key key) { return key->Hash(); }
   static bool IsKeyEqual(Pair kv, Key key) { return key->Equals(kv.key); }
 };
 
@@ -739,7 +739,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Length(); }
+  static inline uword Hash(Key key) { return Utils::WordHash(key->Length()); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->Equals(*key);
@@ -786,7 +786,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->CanonicalizeHash(); }
+  static inline uword Hash(Key key) { return key->CanonicalizeHash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->CanonicalizeEquals(*key);
@@ -876,7 +876,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hashcode(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->Equals(*key);
@@ -951,9 +951,9 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     ASSERT(!key->IsNull());
-    return key->Length();
+    return Utils::WordHash(key->Length());
   }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
@@ -1001,9 +1001,9 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) {
+  static inline uword Hash(Key key) {
     ASSERT(!key->IsNull());
-    return key->Length();
+    return Utils::WordHash(key->Length());
   }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
@@ -1125,7 +1125,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+  static inline uword Hash(Key key) { return key->Hash(); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     return pair->Equals(*key);
@@ -1157,7 +1157,7 @@
 
   static Value ValueOf(Pair kv) { return kv; }
 
-  static inline intptr_t Hashcode(Key key) { return key->Size(); }
+  static inline uword Hash(Key key) { return Utils::WordHash(key->Size()); }
 
   static inline bool IsKeyEqual(Pair pair, Key key) {
     // In AOT, disabled code objects should not be considered for deduplication.
diff --git a/runtime/vm/raw_object_fields.h b/runtime/vm/raw_object_fields.h
index 54a7a27..2b456be8 100644
--- a/runtime/vm/raw_object_fields.h
+++ b/runtime/vm/raw_object_fields.h
@@ -51,7 +51,9 @@
 
     static Value ValueOf(Pair pair) { return pair.value; }
     static Key KeyOf(Pair pair) { return pair.key; }
-    static size_t Hashcode(Key key) { return key.first ^ key.second; }
+    static uword Hash(Key key) {
+      return Utils::WordHash(key.first ^ key.second);
+    }
     static bool IsKeyEqual(Pair x, Key y) {
       return x.key.first == y.first && x.key.second == y.second;
     }
diff --git a/runtime/vm/source_report.h b/runtime/vm/source_report.h
index 08f0441..a7acf00 100644
--- a/runtime/vm/source_report.h
+++ b/runtime/vm/source_report.h
@@ -105,7 +105,7 @@
 
     static Value ValueOf(Pair kv) { return kv; }
 
-    static inline intptr_t Hashcode(Key key) { return key->key->Hash(); }
+    static inline uword Hash(Key key) { return key->key->Hash(); }
 
     static inline bool IsKeyEqual(Pair kv, Key key) {
       return kv->script->ptr() == key->script->ptr();
diff --git a/runtime/vm/token_position.cc b/runtime/vm/token_position.cc
index 507a528..14f7058 100644
--- a/runtime/vm/token_position.cc
+++ b/runtime/vm/token_position.cc
@@ -4,14 +4,13 @@
 
 #include "vm/token_position.h"
 
-#include "vm/hash.h"
 #include "vm/object.h"
 #include "vm/zone_text_buffer.h"
 
 namespace dart {
 
-intptr_t TokenPosition::Hash() const {
-  return FinalizeHash(value_, 31);
+uword TokenPosition::Hash() const {
+  return Utils::WordHash(value_);
 }
 
 TokenPosition TokenPosition::Deserialize(int32_t value) {
diff --git a/runtime/vm/token_position.h b/runtime/vm/token_position.h
index d15553e..8dcf100 100644
--- a/runtime/vm/token_position.h
+++ b/runtime/vm/token_position.h
@@ -61,7 +61,7 @@
 // by the profiler.
 class TokenPosition {
  public:
-  intptr_t Hash() const;
+  uword Hash() const;
 
   // Returns whether the token positions are equal.  Defined for all token
   // positions.
diff --git a/runtime/vm/type_testing_stubs.h b/runtime/vm/type_testing_stubs.h
index 848ae1a..d8307b8 100644
--- a/runtime/vm/type_testing_stubs.h
+++ b/runtime/vm/type_testing_stubs.h
@@ -287,7 +287,7 @@
 
     static Key KeyOf(Pair kv) { return kv; }
     static Value ValueOf(Pair kv) { return kv; }
-    static inline intptr_t Hashcode(Key key) { return key->Hash(); }
+    static inline uword Hash(Key key) { return key->Hash(); }
   };
 
   class TypeSetTrait : public ObjectSetTrait<const AbstractType> {
diff --git a/runtime/vm/v8_snapshot_writer.cc b/runtime/vm/v8_snapshot_writer.cc
index 4f4c1c1..c828b4f 100644
--- a/runtime/vm/v8_snapshot_writer.cc
+++ b/runtime/vm/v8_snapshot_writer.cc
@@ -2,8 +2,6 @@
 // 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.
 
-#if defined(DART_PRECOMPILER)
-
 #include "vm/v8_snapshot_writer.h"
 
 #include "vm/dart.h"
@@ -11,18 +9,20 @@
 
 namespace dart {
 
-const char* ZoneString(Zone* Z, const char* str) {
-  const intptr_t len = strlen(str) + 1;
-  char* dest = Z->Alloc<char>(len);
-  snprintf(dest, len, "%s", str);
-  return dest;
+const V8SnapshotProfileWriter::ObjectId
+    V8SnapshotProfileWriter::kArtificialRootId{kArtificial, 0};
+
+#if defined(DART_PRECOMPILER)
+
+static const char* ZoneString(Zone* Z, const char* str) {
+  return OS::SCreate(Z, "%s", str);
 }
 
 V8SnapshotProfileWriter::V8SnapshotProfileWriter(Zone* zone)
     : zone_(zone),
       node_types_(zone_),
       edge_types_(zone_),
-      strings_(zone),
+      strings_(zone_),
       roots_(zone_) {
   node_types_.Insert({"Unknown", kUnknown});
   node_types_.Insert({"ArtificialRoot", kArtificialRoot});
@@ -35,28 +35,21 @@
   strings_.Insert({"<unknown>", kUnknownString});
   strings_.Insert({"<artificial root>", kArtificialRootString});
 
-  nodes_.Insert({ArtificialRootId(),
-                 {
-                     kArtificialRoot,
-                     kArtificialRootString,
-                     ArtificialRootId(),
-                     0,
-                     nullptr,
-                     0,
-                 }});
+  nodes_.Insert(NodeInfo(zone_, kArtificialRoot, kArtificialRootString,
+                         kArtificialRootId, 0, 0));
 }
 
 void V8SnapshotProfileWriter::SetObjectTypeAndName(ObjectId object_id,
                                                    const char* type,
                                                    const char* name) {
   ASSERT(type != nullptr);
-  NodeInfo* info = EnsureId(object_id);
 
   if (!node_types_.HasKey(type)) {
     node_types_.Insert({ZoneString(zone_, type), node_types_.Size()});
   }
 
   intptr_t type_id = node_types_.LookupValue(type);
+  NodeInfo* info = EnsureId(object_id);
   ASSERT(info->type == kUnknown || info->type == type_id);
   info->type = type_id;
   if (name != nullptr) {
@@ -72,46 +65,66 @@
   EnsureId(object_id)->self_size += num_bytes;
 }
 
-void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId object_id,
-                                                   Reference reference) {
-  EnsureId(reference.to_object_id);
-  NodeInfo* info = EnsureId(object_id);
+V8SnapshotProfileWriter::ConstantEdgeType
+V8SnapshotProfileWriter::ReferenceTypeToEdgeType(Reference::Type type) {
+  switch (type) {
+    case Reference::kElement:
+      return ConstantEdgeType::kElement;
+    case Reference::kProperty:
+      return ConstantEdgeType::kProperty;
+  }
+}
 
-  ASSERT(reference.offset_or_name >= 0);
-  info->edges->Add({
-      static_cast<intptr_t>(reference.reference_type == Reference::kElement
-                                ? kElement
-                                : kProperty),
-      reference.offset_or_name,
-      reference.to_object_id,
-  });
+void V8SnapshotProfileWriter::AttributeReferenceTo(ObjectId from_object_id,
+                                                   Reference reference,
+                                                   ObjectId to_object_id) {
+  const bool is_element = reference.reference_type == Reference::kElement;
+  ASSERT(is_element ? reference.offset >= 0 : reference.name != nullptr);
+
+  EnsureId(to_object_id);
+  const Edge edge(ReferenceTypeToEdgeType(reference.reference_type),
+                  is_element ? reference.offset : EnsureString(reference.name));
+  EnsureId(from_object_id)->AddEdge(edge, to_object_id);
   ++edge_count_;
 }
 
-V8SnapshotProfileWriter::NodeInfo V8SnapshotProfileWriter::DefaultNode(
-    ObjectId object_id) {
-  return {
-      kUnknown,
-      kUnknownString,
-      object_id,
-      0,
-      new (zone_) ZoneGrowableArray<EdgeInfo>(zone_, 0),
-      -1,
-  };
+void V8SnapshotProfileWriter::AttributeDroppedReferenceTo(
+    ObjectId from_object_id,
+    Reference reference,
+    ObjectId to_object_id,
+    ObjectId replacement_object_id) {
+  ASSERT(to_object_id.first == kArtificial);
+  ASSERT(replacement_object_id.first != kArtificial);
+
+  const bool is_element = reference.reference_type == Reference::kElement;
+  ASSERT(is_element ? reference.offset >= 0 : reference.name != nullptr);
+
+  // The target node is added normally.
+  AttributeReferenceTo(from_object_id, reference, to_object_id);
+
+  // Put the replacement node at an invalid offset or name that can still be
+  // associated with the real one. For offsets, this is the negative offset.
+  // For names, it's the name prefixed with ":replacement_".
+  EnsureId(replacement_object_id);
+  const Edge replacement_edge(
+      ReferenceTypeToEdgeType(reference.reference_type),
+      is_element ? -reference.offset
+                 : EnsureString(
+                       OS::SCreate(zone_, ":replacement_%s", reference.name)));
+  EnsureId(from_object_id)->AddEdge(replacement_edge, replacement_object_id);
+  ++edge_count_;
 }
 
-const V8SnapshotProfileWriter::NodeInfo&
-V8SnapshotProfileWriter::ArtificialRoot() {
-  return nodes_.Lookup(ArtificialRootId())->value;
+bool V8SnapshotProfileWriter::HasId(const ObjectId& object_id) {
+  return nodes_.HasKey(object_id);
 }
 
 V8SnapshotProfileWriter::NodeInfo* V8SnapshotProfileWriter::EnsureId(
     ObjectId object_id) {
-  if (!nodes_.HasKey(object_id)) {
-    NodeInfo info = DefaultNode(object_id);
-    nodes_.Insert({object_id, info});
+  if (!HasId(object_id)) {
+    nodes_.Insert(NodeInfo(zone_, kUnknown, kUnknownString, object_id, 0, -1));
   }
-  return &nodes_.Lookup(object_id)->value;
+  return nodes_.Lookup(object_id);
 }
 
 intptr_t V8SnapshotProfileWriter::EnsureString(const char* str) {
@@ -122,24 +135,23 @@
   return strings_.LookupValue(str);
 }
 
-void V8SnapshotProfileWriter::WriteNodeInfo(JSONWriter* writer,
-                                            const NodeInfo& info) {
+intptr_t V8SnapshotProfileWriter::WriteNodeInfo(JSONWriter* writer,
+                                                const NodeInfo& info) {
   writer->PrintValue(info.type);
   writer->PrintValue(info.name);
   writer->PrintValue(NodeIdFor(info.id));
   writer->PrintValue(info.self_size);
-  // The artificial root has 'nullptr' edges, it actually points to all the
-  // roots.
-  writer->PrintValue64(info.edges != nullptr ? info.edges->length()
-                                             : roots_.Size());
+  writer->PrintValue64(info.edges->Length());
   writer->PrintNewline();
+  return kNumNodeFields;
 }
 
 void V8SnapshotProfileWriter::WriteEdgeInfo(JSONWriter* writer,
-                                            const EdgeInfo& info) {
-  writer->PrintValue64(info.type);
-  writer->PrintValue64(info.name_or_index);
-  writer->PrintValue64(nodes_.LookupValue(info.to_node).offset);
+                                            const Edge& info,
+                                            const ObjectId& target) {
+  writer->PrintValue64(info.first);
+  writer->PrintValue64(info.second);
+  writer->PrintValue64(nodes_.LookupValue(target).offset);
   writer->PrintNewline();
 }
 
@@ -151,11 +163,13 @@
   // (most likely an oversight).
   if (roots_.HasKey(object_id)) return;
 
-  ObjectIdToNodeInfoTraits::Pair pair;
-  pair.key = object_id;
-  pair.value = NodeInfo{
-      0, name != nullptr ? EnsureString(name) : -1, object_id, 0, nullptr, 0};
-  roots_.Insert(pair);
+  auto const info = NodeInfo(
+      zone_, 0, name != nullptr ? EnsureString(name) : -1, object_id, 0, 0);
+  roots_.Insert(info);
+  auto const root = EnsureId(kArtificialRootId);
+  root->AddEdge(info.name != -1 ? Edge(kProperty, info.name)
+                                : Edge(kInternal, root->edges->Length()),
+                object_id);
 }
 
 void V8SnapshotProfileWriter::WriteStringsTable(
@@ -226,50 +240,37 @@
   }
   writer->CloseObject();
 
+  const auto& root = *nodes_.Lookup(kArtificialRootId);
+  auto nodes_it = nodes_.GetIterator();
+
   {
     writer->OpenArray("nodes");
-    // Write the artificial root node.
-    WriteNodeInfo(writer, ArtificialRoot());
-    intptr_t offset = kNumNodeFields;
-    ObjectIdToNodeInfoTraits::Pair* entry = nullptr;
-    auto it = nodes_.GetIterator();
-    while ((entry = it.Next()) != nullptr) {
-      ASSERT(entry->key == entry->value.id);
-      if (entry->value.id == ArtificialRootId()) {
-        continue;  // Written separately above.
-      }
-      entry->value.offset = offset;
-      WriteNodeInfo(writer, entry->value);
-      offset += kNumNodeFields;
+    //  Always write the information for the artificial root first.
+    intptr_t offset = WriteNodeInfo(writer, root);
+    for (auto entry = nodes_it.Next(); entry != nullptr;
+         entry = nodes_it.Next()) {
+      if (entry->id == kArtificialRootId) continue;
+      entry->offset = offset;
+      offset += WriteNodeInfo(writer, *entry);
     }
     writer->CloseArray();
+    nodes_it.Reset();
   }
 
   {
+    auto write_edges = [&](const NodeInfo& info) {
+      auto edges_it = info.edges->GetIterator();
+      while (auto const pair = edges_it.Next()) {
+        WriteEdgeInfo(writer, pair->edge, pair->target);
+      }
+    };
     writer->OpenArray("edges");
-
-    // Write references from the artificial root to the actual roots.
-    ObjectIdToNodeInfoTraits::Pair* entry = nullptr;
-    auto roots_it = roots_.GetIterator();
-    for (int i = 0; (entry = roots_it.Next()) != nullptr; ++i) {
-      if (entry->value.name != -1) {
-        WriteEdgeInfo(writer, {kProperty, entry->value.name, entry->key});
-      } else {
-        WriteEdgeInfo(writer, {kInternal, i, entry->key});
-      }
+    //  Always write the information for the artificial root first.
+    write_edges(root);
+    while (auto const entry = nodes_it.Next()) {
+      if (entry->id == kArtificialRootId) continue;
+      write_edges(*entry);
     }
-
-    auto nodes_it = nodes_.GetIterator();
-    while ((entry = nodes_it.Next()) != nullptr) {
-      if (entry->value.edges == nullptr) {
-        continue;  // Artificial root, its edges are written separately above.
-      }
-
-      for (intptr_t i = 0; i < entry->value.edges->length(); ++i) {
-        WriteEdgeInfo(writer, entry->value.edges->At(i));
-      }
-    }
-
     writer->CloseArray();
   }
 
@@ -308,6 +309,6 @@
   }
 }
 
-}  // namespace dart
-
 #endif
+
+}  // namespace dart
diff --git a/runtime/vm/v8_snapshot_writer.h b/runtime/vm/v8_snapshot_writer.h
index 34486ff..09ff87c 100644
--- a/runtime/vm/v8_snapshot_writer.h
+++ b/runtime/vm/v8_snapshot_writer.h
@@ -31,7 +31,7 @@
 
   static Key KeyOf(Pair pair) { return pair.key; }
 
-  static size_t Hashcode(Key key) { return String::Hash(key, strlen(key)); }
+  static uword Hash(Key key) { return String::Hash(key, strlen(key)); }
 
   static bool IsKeyEqual(Pair x, Key y) { return strcmp(x.key, y) == 0; }
 };
@@ -51,12 +51,14 @@
   typedef std::pair<IdSpace, intptr_t> ObjectId;
 
   struct Reference {
-    ObjectId to_object_id;
-    enum {
+    enum Type {
       kElement,
       kProperty,
     } reference_type;
-    intptr_t offset_or_name;
+    union {
+      intptr_t offset;   // kElement
+      const char* name;  // kProperty
+    };
   };
 
   enum ConstantStrings {
@@ -64,21 +66,35 @@
     kArtificialRootString = 1,
   };
 
+  static const ObjectId kArtificialRootId;
+
 #if !defined(DART_PRECOMPILER)
   explicit V8SnapshotProfileWriter(Zone* zone) {}
   virtual ~V8SnapshotProfileWriter() {}
 
+  void SetObjectType(ObjectId object_id, const char* type) {}
   void SetObjectTypeAndName(ObjectId object_id,
                             const char* type,
                             const char* name) {}
   void AttributeBytesTo(ObjectId object_id, size_t num_bytes) {}
-  void AttributeReferenceTo(ObjectId object_id, Reference reference) {}
+  void AttributeReferenceTo(ObjectId from_object_id,
+                            Reference reference,
+                            ObjectId to_object_id) {}
+  void AttributeWeakReferenceTo(
+      ObjectId from_object_id,
+      Reference reference,
+      ObjectId to_object_id,
+      ObjectId replacement_object_id = kArtificialRootId) {}
   void AddRoot(ObjectId object_id, const char* name = nullptr) {}
-  intptr_t EnsureString(const char* str) { return 0; }
+  bool HasId(const ObjectId& object_id) { return false; }
 #else
   explicit V8SnapshotProfileWriter(Zone* zone);
   virtual ~V8SnapshotProfileWriter() {}
 
+  void SetObjectType(ObjectId object_id, const char* type) {
+    SetObjectTypeAndName(object_id, type, nullptr);
+  }
+
   // Records that the object referenced by 'object_id' has type 'type'. The
   // 'type' for all 'Instance's should be 'Instance', not the user-visible type
   // and use 'name' for the real type instead.
@@ -92,9 +108,22 @@
   void AttributeBytesTo(ObjectId object_id, size_t num_bytes);
 
   // Records that a reference to the object with id 'to_object_id' was written
-  // in order to serialize the object with id 'object_id'. This does not affect
-  // the number of bytes charged to 'object_id'.
-  void AttributeReferenceTo(ObjectId object_id, Reference reference);
+  // in order to serialize the object with id 'from_object_id'. This does not
+  // affect the number of bytes charged to 'from_object_id'.
+  void AttributeReferenceTo(ObjectId from_object_id,
+                            Reference reference,
+                            ObjectId to_object_id);
+
+  // Records that a weak serialization reference to a dropped object
+  // with id 'to_object_id' was written in order to serialize the object with id
+  // 'from_object_id'. 'to_object_id' must be an artificial node and
+  // 'replacement_object_id' is recorded as the replacement for the
+  // dropped object in the snapshot. This does not affect the number of
+  // bytes charged to 'from_object_id'.
+  void AttributeDroppedReferenceTo(ObjectId from_object_id,
+                                   Reference reference,
+                                   ObjectId to_object_id,
+                                   ObjectId replacement_object_id);
 
   // Marks an object as being a root in the graph. Used for analysis of the
   // graph.
@@ -103,26 +132,43 @@
   // Write to a file in the V8 Snapshot Profile (JSON/.heapsnapshot) format.
   void Write(const char* file);
 
-  intptr_t EnsureString(const char* str);
-
-  static ObjectId ArtificialRootId() { return {kArtificial, 0}; }
+  // Whether the given object ID has been added to the profile (via AddRoot,
+  // SetObjectTypeAndName, etc.).
+  bool HasId(const ObjectId& object_id);
 
  private:
   static constexpr intptr_t kNumNodeFields = 5;
   static constexpr intptr_t kNumEdgeFields = 3;
 
-  struct EdgeInfo {
-    intptr_t type;
-    intptr_t name_or_index;
-    ObjectId to_node;
+  using Edge = std::pair<intptr_t, intptr_t>;
+
+  struct EdgeToObjectIdMapTrait {
+    using Key = Edge;
+    using Value = ObjectId;
+
+    struct Pair {
+      Pair() : edge{kContext, -1}, target(kArtificialRootId) {}
+      Pair(Key key, Value value) : edge(key), target(value) {}
+      Edge edge;
+      ObjectId target;
+    };
+
+    static Key KeyOf(Pair kv) { return kv.edge; }
+    static Value ValueOf(Pair kv) { return kv.target; }
+    static uword Hash(Key key) {
+      return FinalizeHash(CombineHashes(key.first, key.second), 30);
+    }
+    static bool IsKeyEqual(Pair kv, Key key) { return kv.edge == key; }
   };
 
+  using EdgeMap = ZoneDirectChainedHashMap<EdgeToObjectIdMapTrait>;
+
   struct NodeInfo {
-    intptr_t type;
-    intptr_t name;
+    intptr_t type = 0;
+    intptr_t name = 0;
     ObjectId id;
-    intptr_t self_size;
-    ZoneGrowableArray<EdgeInfo>* edges = nullptr;
+    intptr_t self_size = 0;
+    EdgeMap* edges = nullptr;
     // Populated during serialization.
     intptr_t offset = -1;
     // 'trace_node_id' isn't supported.
@@ -132,29 +178,36 @@
     bool operator!=(const NodeInfo& other) { return id != other.id; }
     bool operator==(const NodeInfo& other) { return !(*this != other); }
 
-    NodeInfo(intptr_t type,
+    void AddEdge(const Edge& edge, const ObjectId& target) {
+      edges->Insert({edge, target});
+    }
+    bool HasEdge(const Edge& edge) { return edges->HasKey(edge); }
+
+    // To allow NodeInfo to be used as the pair in ObjectIdToNodeInfoTraits.
+    NodeInfo() : id{kSnapshot, -1} {}
+
+    NodeInfo(Zone* zone,
+             intptr_t type,
              intptr_t name,
-             ObjectId id,
+             const ObjectId& id,
              intptr_t self_size,
-             ZoneGrowableArray<EdgeInfo>* edges,
              intptr_t offset)
         : type(type),
           name(name),
           id(id),
           self_size(self_size),
-          edges(edges),
+          edges(new (zone) EdgeMap(zone)),
           offset(offset) {}
   };
 
-  NodeInfo DefaultNode(ObjectId object_id);
-  const NodeInfo& ArtificialRoot();
-
   NodeInfo* EnsureId(ObjectId object_id);
   static intptr_t NodeIdFor(ObjectId id) {
     return (id.second << kIdSpaceBits) | id.first;
   }
 
-  enum ConstantEdgeTypes {
+  intptr_t EnsureString(const char* str);
+
+  enum ConstantEdgeType {
     kContext = 0,
     kElement = 1,
     kProperty = 2,
@@ -165,36 +218,33 @@
     kExtra = 7,
   };
 
-  enum ConstantNodeTypes {
+  static ConstantEdgeType ReferenceTypeToEdgeType(Reference::Type type);
+
+  enum ConstantNodeType {
     kUnknown = 0,
     kArtificialRoot = 1,
   };
 
   struct ObjectIdToNodeInfoTraits {
+    typedef NodeInfo Pair;
     typedef ObjectId Key;
-    typedef NodeInfo Value;
+    typedef Pair Value;
 
-    struct Pair {
-      Key key;
-      Value value;
-      Pair()
-          : key{kSnapshot, -1}, value{0, 0, {kSnapshot, -1}, 0, nullptr, -1} {};
-      Pair(Key k, Value v) : key(k), value(v) {}
-    };
+    static Key KeyOf(const Pair& pair) { return pair.id; }
 
-    static Key KeyOf(const Pair& pair) { return pair.key; }
+    static Value ValueOf(const Pair& pair) { return pair; }
 
-    static Value ValueOf(const Pair& pair) { return pair.value; }
+    static uword Hash(Key key) { return Utils::WordHash(NodeIdFor(key)); }
 
-    static size_t Hashcode(Key key) { return NodeIdFor(key); }
-
-    static bool IsKeyEqual(const Pair& x, Key y) { return x.key == y; }
+    static bool IsKeyEqual(const Pair& x, Key y) { return x.id == y; }
   };
 
   Zone* zone_;
   void Write(JSONWriter* writer);
-  void WriteNodeInfo(JSONWriter* writer, const NodeInfo& info);
-  void WriteEdgeInfo(JSONWriter* writer, const EdgeInfo& info);
+  intptr_t WriteNodeInfo(JSONWriter* writer, const NodeInfo& info);
+  void WriteEdgeInfo(JSONWriter* writer,
+                     const Edge& info,
+                     const ObjectId& target);
   void WriteStringsTable(JSONWriter* writer,
                          const DirectChainedHashMap<StringToIntMapTraits>& map);
 
diff --git a/sdk/lib/_internal/vm/lib/core_patch.dart b/sdk/lib/_internal/vm/lib/core_patch.dart
index 6ffdce8..d79e07d 100644
--- a/sdk/lib/_internal/vm/lib/core_patch.dart
+++ b/sdk/lib/_internal/vm/lib/core_patch.dart
@@ -49,6 +49,8 @@
 
 import "dart:convert" show ascii, Encoding, json, latin1, utf8;
 
+import "dart:ffi" show Pointer, Struct;
+
 import "dart:isolate" show Isolate;
 
 import "dart:math" show Random;
diff --git a/sdk/lib/_internal/vm/lib/expando_patch.dart b/sdk/lib/_internal/vm/lib/expando_patch.dart
index 01cbe51..868e6238e 100644
--- a/sdk/lib/_internal/vm/lib/expando_patch.dart
+++ b/sdk/lib/_internal/vm/lib/expando_patch.dart
@@ -140,9 +140,12 @@
     if ((object == null) ||
         (object is bool) ||
         (object is num) ||
-        (object is String)) {
+        (object is String) ||
+        (object is Pointer) ||
+        (object is Struct)) {
+      // TODO(https://dartbug.com/38491): Reject Unions here as well.
       throw new ArgumentError.value(object,
-          "Expandos are not allowed on strings, numbers, booleans or null");
+          "Expandos are not allowed on strings, numbers, booleans, null, Pointers or Structs.");
     }
   }
 
diff --git a/sdk/lib/core/expando.dart b/sdk/lib/core/expando.dart
index d623579..c081df8 100644
--- a/sdk/lib/core/expando.dart
+++ b/sdk/lib/core/expando.dart
@@ -6,7 +6,8 @@
 
 /// An [Expando] allows adding new properties to objects.
 ///
-/// Does not work on numbers, strings, booleans or `null`.
+/// Does not work on numbers, strings, booleans, `null`, `dart:ffi` pointers
+/// or `dart:ffi` structs.
 ///
 /// An `Expando` does not hold on to the added property value after an object
 /// becomes inaccessible.
@@ -38,13 +39,15 @@
   /// object. If the object hasn't been expanded, the method returns
   /// `null`.
   ///
-  /// The object must not be a number, a string or a boolean.
+  /// The object must not be a number, a string, a boolean, `null`, a
+  /// `dart:ffi` pointer, or a `dart:ffi` struct.
   external T? operator [](Object object);
 
   /// Sets the value of this [Expando]'s property on the given
   /// object. Properties can effectively be removed again by setting
   /// their value to `null`.
   ///
-  /// The object must not be a number, a string or a boolean.
+  /// The object must not be a number, a string, a boolean, `null`, a
+  /// `dart:ffi` pointer, or a `dart:ffi` struct.
   external void operator []=(Object object, T? value);
 }
diff --git a/tests/corelib/expando_test.dart b/tests/corelib/expando_test.dart
index 02810bc..ccc074a 100644
--- a/tests/corelib/expando_test.dart
+++ b/tests/corelib/expando_test.dart
@@ -1,6 +1,9 @@
 // Copyright (c) 2012, 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.
+//
+// VMOptions=--enable-ffi=true
+// VMOptions=--enable-ffi=false
 
 import "package:expect/expect.dart";
 
diff --git a/tests/corelib_2/expando_test.dart b/tests/corelib_2/expando_test.dart
index e2a342b..95ae7db 100644
--- a/tests/corelib_2/expando_test.dart
+++ b/tests/corelib_2/expando_test.dart
@@ -1,6 +1,9 @@
 // Copyright (c) 2012, 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.
+//
+// VMOptions=--enable-ffi=true
+// VMOptions=--enable-ffi=false
 
 import "package:expect/expect.dart";
 
diff --git a/tests/ffi/expando_test.dart b/tests/ffi/expando_test.dart
new file mode 100644
index 0000000..473fc6f
--- /dev/null
+++ b/tests/ffi/expando_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, 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.
+//
+// Dart test program for testing dart:ffi primitive data pointers.
+//
+// SharedObjects=ffi_test_functions
+
+import 'dart:ffi';
+
+import "package:expect/expect.dart";
+
+void main() {
+  testPointer();
+  testStruct();
+}
+
+Expando<int> expando = Expando('myExpando');
+
+void testPointer() {
+  final pointer = Pointer<Int8>.fromAddress(0xdeadbeef);
+  Expect.throws(() {
+    expando[pointer];
+  });
+  Expect.throws(() {
+    expando[pointer] = 1234;
+  });
+}
+
+class MyStruct extends Struct {
+  external Pointer notEmpty;
+}
+
+void testStruct() {
+  final pointer = Pointer<MyStruct>.fromAddress(0xdeadbeef);
+  final struct = pointer.ref;
+  Expect.throws(() {
+    expando[struct];
+  });
+  Expect.throws(() {
+    expando[struct] = 1234;
+  });
+}
diff --git a/tests/ffi_2/expando_test.dart b/tests/ffi_2/expando_test.dart
new file mode 100644
index 0000000..ac14324
--- /dev/null
+++ b/tests/ffi_2/expando_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, 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.
+//
+// Dart test program for testing dart:ffi primitive data pointers.
+//
+// SharedObjects=ffi_test_functions
+
+import 'dart:ffi';
+
+import "package:expect/expect.dart";
+
+void main() {
+  testPointer();
+  testStruct();
+}
+
+Expando<int> expando = Expando('myExpando');
+
+void testPointer() {
+  final pointer = Pointer<Int8>.fromAddress(0xdeadbeef);
+  Expect.throws(() {
+    expando[pointer];
+  });
+  Expect.throws(() {
+    expando[pointer] = 1234;
+  });
+}
+
+class MyStruct extends Struct {
+  Pointer notEmpty;
+}
+
+void testStruct() {
+  final pointer = Pointer<MyStruct>.fromAddress(0xdeadbeef);
+  final struct = pointer.ref;
+  Expect.throws(() {
+    expando[struct];
+  });
+  Expect.throws(() {
+    expando[struct] = 1234;
+  });
+}
diff --git a/tools/VERSION b/tools/VERSION
index 9201ef4..e26d75d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 3
+PRERELEASE 4
 PRERELEASE_PATCH 0
\ No newline at end of file