[vm] Use a weak set to represent dependent code.

Fixes a scaling limitation where compiling N functions using the same guarded field or CHA guarded interface will result in O(N^2) comparisons when registering the dependencies.

Don't use Instructions' address as the hash, as this gets relocated between the snapshot writer and reader.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/51125
Change-Id: I9a13d57455e10865d9c5f7c12009d869a4ef0488
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279753
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/tests/vm/dart/appjit_cha_deopt_test_body.dart b/runtime/tests/vm/dart/appjit_cha_deopt_test_body.dart
index c59f720..f8b6727 100644
--- a/runtime/tests/vm/dart/appjit_cha_deopt_test_body.dart
+++ b/runtime/tests/vm/dart/appjit_cha_deopt_test_body.dart
@@ -5,39 +5,82 @@
 // Verify that app-jit snapshot contains dependencies between classes and CHA
 // optimized code.
 
-import 'package:expect/expect.dart';
+import "package:expect/expect.dart";
 
 class A {
-  void getMyName() => getMyNameImpl();
-
-  void getMyNameImpl() => "A";
+  String getMyName() => "A";
 }
 
 class B extends A {
-  void getMyNameImpl() => "B";
+  String getMyName() => "B";
 }
 
 final Function makeA = () => new A();
 final Function makeB = () => new B();
 
-void optimizeGetMyName(dynamic obj) {
-  for (var i = 0; i < 100; i++) {
-    obj.getMyName();
-  }
-  Expect.equals("A", obj.getMyName());
+@pragma("vm:never-inline")
+dependentCode1(bool isTraining) {
+  dependentCode2(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
 }
 
-void main(List<String> args) {
+@pragma("vm:never-inline")
+dependentCode2(bool isTraining) {
+  dependentCode3(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode3(bool isTraining) {
+  dependentCode4(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode4(bool isTraining) {
+  dependentCode5(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode5(bool isTraining) {
+  dependentCode6(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode6(bool isTraining) {
+  dependentCode7(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode7(bool isTraining) {
+  dependentCode8(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode8(bool isTraining) {
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+main(List<String> args) {
   final isTraining = args.contains("--train");
-  final dynamic obj = (isTraining ? makeA : makeB)();
+  for (var i = 0; i < 200; i++) {
+    dependentCode1(isTraining);
+  }
   if (isTraining) {
-    for (var i = 0; i < 10; i++) {
-      optimizeGetMyName(obj);
-    }
-    Expect.equals('A', obj.getMyName());
-    print('OK(Trained)');
+    print("OK(Trained)");
   } else {
-    Expect.equals('B', obj.getMyName());
-    print('OK(Run)');
+    print("OK(Run)");
   }
 }
diff --git a/runtime/tests/vm/dart/appjit_field_guard_deopt_test.dart b/runtime/tests/vm/dart/appjit_field_guard_deopt_test.dart
new file mode 100644
index 0000000..538a483
--- /dev/null
+++ b/runtime/tests/vm/dart/appjit_field_guard_deopt_test.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, 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.
+
+// OtherResources=appjit_field_guard_deopt_test_body.dart
+// VMOptions=--optimization-counter-threshold=100 --deterministic
+
+// Verify that app-jit snapshot contains dependencies between fields and
+// field-guard optimized code.
+
+import 'dart:async';
+import 'dart:io' show Platform;
+
+import 'snapshot_test_helper.dart';
+
+Future<void> main() => runAppJitTest(
+    Platform.script.resolve('appjit_field_guard_deopt_test_body.dart'));
diff --git a/runtime/tests/vm/dart/appjit_field_guard_deopt_test_body.dart b/runtime/tests/vm/dart/appjit_field_guard_deopt_test_body.dart
new file mode 100644
index 0000000..c109481
--- /dev/null
+++ b/runtime/tests/vm/dart/appjit_field_guard_deopt_test_body.dart
@@ -0,0 +1,125 @@
+// Copyright (c) 2023, 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.
+
+// Verify that app-jit snapshot contains dependencies between fields and
+// field-guard optimized code.
+
+import "package:expect/expect.dart";
+
+class _A {
+  dynamic field;
+  _A(this.field);
+}
+
+@pragma("vm:never-inline")
+dependentCode1(_A a, bool isInt, tail) {
+  dependentCode2(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode2(_A a, bool isInt, tail) {
+  dependentCode3(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode3(_A a, bool isInt, tail) {
+  dependentCode4(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode4(_A a, bool isInt, tail) {
+  dependentCode5(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode5(_A a, bool isInt, tail) {
+  dependentCode6(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode6(_A a, bool isInt, tail) {
+  dependentCode7(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode7(_A a, bool isInt, tail) {
+  dependentCode8(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode8(_A a, bool isInt, tail) {
+  tail();
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+main(List<String> args) {
+  final isTraining = args.contains("--train");
+  if (isTraining) {
+    var a = new _A(0);
+    for (var i = 0; i < 200; i++) {
+      dependentCode1(a, true, () {});
+    }
+    Expect.equals(a.field, 200 * 8);
+    print("OK(Trained)");
+  } else {
+    var a = new _A(0);
+    var b;
+    dependentCode1(a, true, () {
+      b = new _A(0.0);
+    });
+    Expect.equals(a.field, 8);
+    for (var i = 0; i < 200; i++) {
+      dependentCode1(b, false, () {});
+    }
+    Expect.equals(b.field, 200 * 8);
+    print("OK(Run)");
+  }
+}
diff --git a/runtime/tests/vm/dart_2/appjit_cha_deopt_test_body.dart b/runtime/tests/vm/dart_2/appjit_cha_deopt_test_body.dart
index 917f652..0c54348 100644
--- a/runtime/tests/vm/dart_2/appjit_cha_deopt_test_body.dart
+++ b/runtime/tests/vm/dart_2/appjit_cha_deopt_test_body.dart
@@ -7,39 +7,82 @@
 // Verify that app-jit snapshot contains dependencies between classes and CHA
 // optimized code.
 
-import 'package:expect/expect.dart';
+import "package:expect/expect.dart";
 
 class A {
-  void getMyName() => getMyNameImpl();
-
-  void getMyNameImpl() => "A";
+  String getMyName() => "A";
 }
 
 class B extends A {
-  void getMyNameImpl() => "B";
+  String getMyName() => "B";
 }
 
 final Function makeA = () => new A();
 final Function makeB = () => new B();
 
-void optimizeGetMyName(dynamic obj) {
-  for (var i = 0; i < 100; i++) {
-    obj.getMyName();
-  }
-  Expect.equals("A", obj.getMyName());
+@pragma("vm:never-inline")
+dependentCode1(bool isTraining) {
+  dependentCode2(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
 }
 
-void main(List<String> args) {
+@pragma("vm:never-inline")
+dependentCode2(bool isTraining) {
+  dependentCode3(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode3(bool isTraining) {
+  dependentCode4(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode4(bool isTraining) {
+  dependentCode5(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode5(bool isTraining) {
+  dependentCode6(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode6(bool isTraining) {
+  dependentCode7(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode7(bool isTraining) {
+  dependentCode8(isTraining);
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+@pragma("vm:never-inline")
+dependentCode8(bool isTraining) {
+  A obj = (isTraining ? makeA : makeB)();
+  Expect.equals(isTraining ? "A" : "B", obj.getMyName());
+}
+
+main(List<String> args) {
   final isTraining = args.contains("--train");
-  final dynamic obj = (isTraining ? makeA : makeB)();
+  for (var i = 0; i < 200; i++) {
+    dependentCode1(isTraining);
+  }
   if (isTraining) {
-    for (var i = 0; i < 10; i++) {
-      optimizeGetMyName(obj);
-    }
-    Expect.equals('A', obj.getMyName());
-    print('OK(Trained)');
+    print("OK(Trained)");
   } else {
-    Expect.equals('B', obj.getMyName());
-    print('OK(Run)');
+    print("OK(Run)");
   }
 }
diff --git a/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test.dart b/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test.dart
new file mode 100644
index 0000000..2f28d12
--- /dev/null
+++ b/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2023, 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 = 2.9
+
+// OtherResources=appjit_field_guard_deopt_test_body.dart
+// VMOptions=--optimization-counter-threshold=100 --deterministic
+
+// Verify that app-jit snapshot contains dependencies between fields and
+// field-guard optimized code.
+
+import 'dart:async';
+import 'dart:io' show Platform;
+
+import 'snapshot_test_helper.dart';
+
+Future<void> main() => runAppJitTest(
+    Platform.script.resolve('appjit_field_guard_deopt_test_body.dart'));
diff --git a/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test_body.dart b/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test_body.dart
new file mode 100644
index 0000000..0eab436
--- /dev/null
+++ b/runtime/tests/vm/dart_2/appjit_field_guard_deopt_test_body.dart
@@ -0,0 +1,127 @@
+// Copyright (c) 2023, 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 = 2.9
+
+// Verify that app-jit snapshot contains dependencies between fields and
+// field-guard optimized code.
+
+import "package:expect/expect.dart";
+
+class _A {
+  dynamic field;
+  _A(this.field);
+}
+
+@pragma("vm:never-inline")
+dependentCode1(_A a, bool isInt, tail) {
+  dependentCode2(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode2(_A a, bool isInt, tail) {
+  dependentCode3(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode3(_A a, bool isInt, tail) {
+  dependentCode4(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode4(_A a, bool isInt, tail) {
+  dependentCode5(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode5(_A a, bool isInt, tail) {
+  dependentCode6(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode6(_A a, bool isInt, tail) {
+  dependentCode7(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode7(_A a, bool isInt, tail) {
+  dependentCode8(a, isInt, tail);
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+@pragma("vm:never-inline")
+dependentCode8(_A a, bool isInt, tail) {
+  tail();
+  a.field++;
+  if (isInt) {
+    Expect.type<int>(a.field);
+  } else {
+    Expect.type<double>(a.field);
+  }
+}
+
+main(List<String> args) {
+  final isTraining = args.contains("--train");
+  if (isTraining) {
+    var a = new _A(0);
+    for (var i = 0; i < 200; i++) {
+      dependentCode1(a, true, () {});
+    }
+    Expect.equals(a.field, 200 * 8);
+    print("OK(Trained)");
+  } else {
+    var a = new _A(0);
+    var b;
+    dependentCode1(a, true, () {
+      b = new _A(0.0);
+    });
+    Expect.equals(a.field, 8);
+    for (var i = 0; i < 200; i++) {
+      dependentCode1(b, false, () {});
+    }
+    Expect.equals(b.field, 200 * 8);
+    print("OK(Run)");
+  }
+}
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index 0bb1ded..7ac7fd3 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -1534,7 +1534,8 @@
       ASSERT(d_->kind() != Snapshot::kFullAOT);
       field->untag()->guarded_list_length_ = static_cast<SmiPtr>(d.ReadRef());
       if (kind == Snapshot::kFullJIT) {
-        field->untag()->dependent_code_ = static_cast<ArrayPtr>(d.ReadRef());
+        field->untag()->dependent_code_ =
+            static_cast<WeakArrayPtr>(d.ReadRef());
       }
       field->untag()->token_pos_ = d.ReadTokenPosition();
       field->untag()->end_token_pos_ = d.ReadTokenPosition();
diff --git a/runtime/vm/compiler/aot/precompiler_tracer.h b/runtime/vm/compiler/aot/precompiler_tracer.h
index 324e82c..99385cb 100644
--- a/runtime/vm/compiler/aot/precompiler_tracer.h
+++ b/runtime/vm/compiler/aot/precompiler_tracer.h
@@ -99,9 +99,9 @@
       if (obj.IsFunction()) {
         return Function::Cast(obj).Hash();
       } else if (obj.IsClass()) {
-        return String::HashRawSymbol(Class::Cast(obj).Name());
+        return Class::Cast(obj).Hash();
       } else if (obj.IsField()) {
-        return String::HashRawSymbol(Field::Cast(obj).name());
+        return Field::Cast(obj).Hash();
       }
       return obj.GetClassId();
     }
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 0cba356..2315c90 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -113,14 +113,13 @@
     return Instance::Cast(obj).CanonicalizeHash();
   }
   if (obj.IsCode()) {
-    // Instructions don't move during compaction.
-    return Code::Cast(obj).PayloadStart();
+    return Code::Cast(obj).Hash();
   }
   if (obj.IsFunction()) {
     return Function::Cast(obj).Hash();
   }
   if (obj.IsField()) {
-    return dart::String::HashRawSymbol(Field::Cast(obj).name());
+    return Field::Cast(obj).Hash();
   }
   if (obj.IsICData()) {
     return ICData::Cast(obj).Hash();
diff --git a/runtime/vm/heap/weak_code.cc b/runtime/vm/heap/weak_code.cc
index 5e59703..0989d73 100644
--- a/runtime/vm/heap/weak_code.cc
+++ b/runtime/vm/heap/weak_code.cc
@@ -7,6 +7,7 @@
 #include "platform/assert.h"
 
 #include "vm/code_patcher.h"
+#include "vm/hash_table.h"
 #include "vm/object.h"
 #include "vm/runtime_entry.h"
 #include "vm/stack_frame.h"
@@ -14,65 +15,44 @@
 
 namespace dart {
 
+class CodeTraits {
+ public:
+  static const char* Name() { return "CodeTraits"; }
+  static bool ReportStats() { return false; }
+  static bool IsMatch(const Object& a, const Object& b) {
+    return a.ptr() == b.ptr();
+  }
+  static uword Hash(const Object& key) { return Code::Cast(key).Hash(); }
+};
+
+typedef UnorderedHashSet<CodeTraits, WeakArrayStorageTraits> WeakCodeSet;
+
 bool WeakCodeReferences::HasCodes() const {
   return !array_.IsNull() && (array_.Length() > 0);
 }
 
 void WeakCodeReferences::Register(const Code& value) {
-  if (!array_.IsNull()) {
-    // Try to find and reuse cleared WeakProperty to avoid allocating new one.
-    WeakProperty& weak_property = WeakProperty::Handle();
-    for (intptr_t i = 0; i < array_.Length(); i++) {
-      weak_property ^= array_.At(i);
-      if (weak_property.key() == Code::null()) {
-        // Empty property found. Reuse it.
-        weak_property.set_key(value);
-        return;
-      }
-    }
-  }
-
-  const WeakProperty& weak_property =
-      WeakProperty::Handle(WeakProperty::New(Heap::kOld));
-  weak_property.set_key(value);
-
-  intptr_t length = array_.IsNull() ? 0 : array_.Length();
-  const Array& new_array =
-      Array::Handle(Array::Grow(array_, length + 1, Heap::kOld));
-  new_array.SetAt(length, weak_property);
-  UpdateArrayTo(new_array);
-}
-
-bool WeakCodeReferences::IsOptimizedCode(const Array& dependent_code,
-                                         const Code& code) {
-  if (!code.is_optimized()) {
-    return false;
-  }
-  WeakProperty& weak_property = WeakProperty::Handle();
-  for (intptr_t i = 0; i < dependent_code.Length(); i++) {
-    weak_property ^= dependent_code.At(i);
-    if (code.ptr() == weak_property.key()) {
-      return true;
-    }
-  }
-  return false;
+  WeakCodeSet set(array_.IsNull() ? HashTables::New<WeakCodeSet>(4, Heap::kOld)
+                                  : array_.ptr());
+  set.Insert(value);
+  UpdateArrayTo(set.Release());
 }
 
 void WeakCodeReferences::DisableCode(bool are_mutators_stopped) {
-  Thread* thread = Thread::Current();
-  const Array& code_objects = Array::Handle(thread->zone(), array_.ptr());
 #if defined(DART_PRECOMPILED_RUNTIME)
-  ASSERT(code_objects.IsNull());
+  ASSERT(array_.IsNull());
   return;
 #else
   // Ensure mutators see empty code_objects only after code was deoptimized.
   DEBUG_ASSERT(
       IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
 
-  if (code_objects.IsNull()) {
+  if (array_.IsNull()) {
     return;
   }
 
+  WeakCodeSet set(array_.ptr());
+
   auto isolate_group = IsolateGroup::Current();
   auto disable_code_fun = [&]() {
     Code& code = Code::Handle();
@@ -84,7 +64,8 @@
           StackFrame* frame = iterator.NextFrame();
           while (frame != nullptr) {
             code = frame->LookupDartCode();
-            if (IsOptimizedCode(code_objects, code)) {
+
+            if (set.ContainsKey(code)) {
               ReportDeoptimization(code);
               DeoptimizeAt(mutator_thread, code, frame);
             }
@@ -94,12 +75,11 @@
         /*at_safepoint=*/true);
 
     // Switch functions that use dependent code to unoptimized code.
-    WeakProperty& weak_property = WeakProperty::Handle();
     Object& owner = Object::Handle();
     Function& function = Function::Handle();
-    for (intptr_t i = 0; i < code_objects.Length(); i++) {
-      weak_property ^= code_objects.At(i);
-      code ^= weak_property.key();
+    WeakCodeSet::Iterator it(&set);
+    while (it.MoveNext()) {
+      code ^= set.GetKey(it.Current());
       if (code.IsNull()) {
         // Code was garbage collected already.
         continue;
@@ -138,7 +118,7 @@
       }
     }
 
-    UpdateArrayTo(Object::null_array());
+    UpdateArrayTo(WeakArray::Handle());
   };
 
   // Deoptimize stacks and disable code (with mutators stopped if they are not
@@ -149,6 +129,8 @@
     isolate_group->RunWithStoppedMutators(disable_code_fun);
   }
 
+  set.Release();
+
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 }
 
diff --git a/runtime/vm/heap/weak_code.h b/runtime/vm/heap/weak_code.h
index e87ac5c..c042d84 100644
--- a/runtime/vm/heap/weak_code.h
+++ b/runtime/vm/heap/weak_code.h
@@ -10,30 +10,28 @@
 
 namespace dart {
 
-class Array;
+class WeakArray;
 class Code;
 
 // Helper class to handle an array of code weak properties. Implements
 // registration and disabling of stored code objects.
 class WeakCodeReferences : public ValueObject {
  public:
-  explicit WeakCodeReferences(const Array& value) : array_(value) {}
+  explicit WeakCodeReferences(const WeakArray& value) : array_(value) {}
   virtual ~WeakCodeReferences() {}
 
   void Register(const Code& value);
 
-  virtual void UpdateArrayTo(const Array& array) = 0;
+  virtual void UpdateArrayTo(const WeakArray& array) = 0;
   virtual void ReportDeoptimization(const Code& code) = 0;
   virtual void ReportSwitchingCode(const Code& code) = 0;
 
-  static bool IsOptimizedCode(const Array& dependent_code, const Code& code);
-
   void DisableCode(bool are_mutators_stopped);
 
   bool HasCodes() const;
 
  private:
-  const Array& array_;  // Array of Code objects.
+  const WeakArray& array_;  // Array of Code objects.
   DISALLOW_COPY_AND_ASSIGN(WeakCodeReferences);
 };
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 4e9cceb..5e9ca6a 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -4273,9 +4273,10 @@
 class CHACodeArray : public WeakCodeReferences {
  public:
   explicit CHACodeArray(const Class& cls)
-      : WeakCodeReferences(Array::Handle(cls.dependent_code())), cls_(cls) {}
+      : WeakCodeReferences(WeakArray::Handle(cls.dependent_code())),
+        cls_(cls) {}
 
-  virtual void UpdateArrayTo(const Array& value) {
+  virtual void UpdateArrayTo(const WeakArray& value) {
     // TODO(fschneider): Fails for classes in the VM isolate.
     cls_.set_dependent_code(value);
   }
@@ -4333,13 +4334,13 @@
   DisableCHAOptimizedCode(Class::Handle());
 }
 
-ArrayPtr Class::dependent_code() const {
+WeakArrayPtr Class::dependent_code() const {
   DEBUG_ASSERT(
       IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
   return untag()->dependent_code();
 }
 
-void Class::set_dependent_code(const Array& array) const {
+void Class::set_dependent_code(const WeakArray& array) const {
   DEBUG_ASSERT(
       IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
   untag()->set_dependent_code(array.ptr());
@@ -5377,6 +5378,10 @@
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
+uint32_t Class::Hash() const {
+  return String::HashRawSymbol(Name());
+}
+
 int32_t Class::SourceFingerprint() const {
 #if !defined(DART_PRECOMPILED_RUNTIME)
   return kernel::KernelSourceFingerprintHelper::CalculateClassFingerprint(
@@ -11300,6 +11305,10 @@
   return PatchClass::Cast(obj).script();
 }
 
+uint32_t Field::Hash() const {
+  return String::HashRawSymbol(name());
+}
+
 ExternalTypedDataPtr Field::KernelData() const {
   const Object& obj = Object::Handle(this->untag()->owner());
   // During background JIT compilation field objects are copied
@@ -11623,13 +11632,13 @@
   return AccessorClosure(true);
 }
 
-ArrayPtr Field::dependent_code() const {
+WeakArrayPtr Field::dependent_code() const {
   DEBUG_ASSERT(
       IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
   return untag()->dependent_code();
 }
 
-void Field::set_dependent_code(const Array& array) const {
+void Field::set_dependent_code(const WeakArray& array) const {
   ASSERT(IsOriginal());
   DEBUG_ASSERT(
       IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
@@ -11639,10 +11648,10 @@
 class FieldDependentArray : public WeakCodeReferences {
  public:
   explicit FieldDependentArray(const Field& field)
-      : WeakCodeReferences(Array::Handle(field.dependent_code())),
+      : WeakCodeReferences(WeakArray::Handle(field.dependent_code())),
         field_(field) {}
 
-  virtual void UpdateArrayTo(const Array& value) {
+  virtual void UpdateArrayTo(const WeakArray& value) {
     field_.set_dependent_code(value);
   }
 
@@ -18185,6 +18194,24 @@
                          kScrubbedName, NameDisambiguation::kYes)));
 }
 
+uint32_t Code::Hash() const {
+  // PayloadStart() is a tempting hash as Instructions are not moved by the
+  // compactor, but Instructions are effectively moved between the process
+  // creating an AppJIT/AOT snapshot and the process loading the snapshot.
+  const Object& obj =
+      Object::Handle(WeakSerializationReference::UnwrapIfTarget(owner()));
+  if (obj.IsClass()) {
+    return Class::Cast(obj).Hash();
+  } else if (obj.IsAbstractType()) {
+    return AbstractType::Cast(obj).Hash();
+  } else if (obj.IsFunction()) {
+    return Function::Cast(obj).Hash();
+  } else {
+    // E.g., VM stub.
+    return 42;
+  }
+}
+
 const char* Code::Name() const {
   Zone* zone = Thread::Current()->zone();
   if (IsStubCode()) {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 471b395..83cd2a0 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1203,6 +1203,7 @@
   void set_end_token_pos(TokenPosition value) const;
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
+  uint32_t Hash() const;
   int32_t SourceFingerprint() const;
 
   // Return the Type with type parameters declared by this class filled in with
@@ -1799,8 +1800,8 @@
   // Return the list of code objects that were compiled using CHA of this class.
   // These code objects will be invalidated if new subclasses of this class
   // are finalized.
-  ArrayPtr dependent_code() const;
-  void set_dependent_code(const Array& array) const;
+  WeakArrayPtr dependent_code() const;
+  void set_dependent_code(const WeakArray& array) const;
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
   bool TraceAllocation(IsolateGroup* isolate_group) const;
@@ -4236,6 +4237,8 @@
   ScriptPtr Script() const;
   ObjectPtr RawOwner() const;
 
+  uint32_t Hash() const;
+
   AbstractTypePtr type() const { return untag()->type(); }
   // Used by class finalizer, otherwise initialized in constructor.
   void SetFieldType(const AbstractType& value) const;
@@ -4464,8 +4467,8 @@
   // assumptions about guarded class id and nullability of this field.
   // These code objects must be deoptimized when field's properties change.
   // Code objects are held weakly via an indirection through WeakProperty.
-  ArrayPtr dependent_code() const;
-  void set_dependent_code(const Array& array) const;
+  WeakArrayPtr dependent_code() const;
+  void set_dependent_code(const WeakArray& array) const;
 
   // Add the given code object to the list of dependent ones.
   void RegisterDependentCode(const Code& code) const;
@@ -6907,6 +6910,7 @@
                         UntaggedPcDescriptors::Kind kind) const;
   intptr_t GetDeoptIdForOsr(uword pc) const;
 
+  uint32_t Hash() const;
   const char* Name() const;
   const char* QualifiedName(const NameFormattingParams& params) const;
 
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 267b369..46b6ec0 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1009,7 +1009,7 @@
   // Stub code for allocation of instances.
   COMPRESSED_POINTER_FIELD(CodePtr, allocation_stub)
   // CHA optimized codes.
-  COMPRESSED_POINTER_FIELD(ArrayPtr, dependent_code)
+  COMPRESSED_POINTER_FIELD(WeakArrayPtr, dependent_code)
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 #if defined(DART_PRECOMPILED_RUNTIME)
@@ -1447,7 +1447,7 @@
   // - for static fields: index into field_table.
   COMPRESSED_POINTER_FIELD(SmiPtr, host_offset_or_field_id)
   COMPRESSED_POINTER_FIELD(SmiPtr, guarded_list_length)
-  COMPRESSED_POINTER_FIELD(ArrayPtr, dependent_code)
+  COMPRESSED_POINTER_FIELD(WeakArrayPtr, dependent_code)
   CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) {
     switch (kind) {
       case Snapshot::kFull: