Browse Source

chore: cherry-pick 3fdedec45691 from v8

Keeley Hammond 4 months ago
parent
commit
0c73dad61f
2 changed files with 911 additions and 0 deletions
  1. 1 0
      patches/v8/.patches
  2. 910 0
      patches/v8/cherry-pick-3fdedec45691.patch

+ 1 - 0
patches/v8/.patches

@@ -1,3 +1,4 @@
 chore_allow_customizing_microtask_policy_per_context.patch
 deps_add_v8_object_setinternalfieldfornodecore.patch
 fix_disable_scope_reuse_associated_dchecks.patch
+cherry-pick-3fdedec45691.patch

+ 910 - 0
patches/v8/cherry-pick-3fdedec45691.patch

@@ -0,0 +1,910 @@
+From 3fdedec45691a3ab005d62c3295436507e8d277a Mon Sep 17 00:00:00 2001
+From: Clemens Backes <[email protected]>
+Date: Tue, 19 Nov 2024 18:17:33 +0100
+Subject: [PATCH] Merged: [wasm] Remove relative type indexes from canonical types
+
+Those relative types were leaking from the type canonicalizer, which
+leads to type confusion in callers.
+
+This CL fully removes the concept of relative type indexes (and thus
+removes the `CanonicalRelativeField` bit from the bitfield in
+`ValueTypeBase`). During canonicalization we pass the start and end of
+the recursion group into hashing and equality checking, and use this to
+compute relative indexes within the recursion group on demand. The
+stored version will always have absolute indexes though.
+
[email protected]
+
+Bug: 379009132
+(cherry picked from commit 20d9a7f760c018183c836283017a321638b66810)
+
+Change-Id: I9bee6b37b9da36684f8c5b2866725eac79c896ad
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6049645
+Commit-Queue: Clemens Backes <[email protected]>
+Reviewed-by: Jakob Kummerow <[email protected]>
+Cr-Commit-Position: refs/branch-heads/13.2@{#22}
+Cr-Branched-From: 24068c59cedad9ee976ddc05431f5f497b1ebd71-refs/heads/13.2.152@{#1}
+Cr-Branched-From: 6054ba94db0969220be4f94dc1677fc4696bdc4f-refs/heads/main@{#97085}
+---
+
+diff --git a/src/base/bounds.h b/src/base/bounds.h
+index 85f7bba..1646e81 100644
+--- a/src/base/bounds.h
++++ b/src/base/bounds.h
+@@ -14,9 +14,11 @@
+ // Checks if value is in range [lower_limit, higher_limit] using a single
+ // branch.
+ template <typename T, typename U>
++  requires((std::is_integral_v<T> || std::is_enum_v<T>) &&
++           (std::is_integral_v<U> || std::is_enum_v<U>)) &&
++          (sizeof(U) <= sizeof(T))
+ inline constexpr bool IsInRange(T value, U lower_limit, U higher_limit) {
+   DCHECK_LE(lower_limit, higher_limit);
+-  static_assert(sizeof(U) <= sizeof(T));
+   using unsigned_T = typename std::make_unsigned<T>::type;
+   // Use static_cast to support enum classes.
+   return static_cast<unsigned_T>(static_cast<unsigned_T>(value) -
+@@ -27,10 +29,12 @@
+ 
+ // Like IsInRange but for the half-open range [lower_limit, higher_limit).
+ template <typename T, typename U>
++  requires((std::is_integral_v<T> || std::is_enum_v<T>) &&
++           (std::is_integral_v<U> || std::is_enum_v<U>)) &&
++          (sizeof(U) <= sizeof(T))
+ inline constexpr bool IsInHalfOpenRange(T value, U lower_limit,
+                                         U higher_limit) {
+   DCHECK_LE(lower_limit, higher_limit);
+-  static_assert(sizeof(U) <= sizeof(T));
+   using unsigned_T = typename std::make_unsigned<T>::type;
+   // Use static_cast to support enum classes.
+   return static_cast<unsigned_T>(static_cast<unsigned_T>(value) -
+diff --git a/src/wasm/canonical-types.cc b/src/wasm/canonical-types.cc
+index 3443018..2ecb78b 100644
+--- a/src/wasm/canonical-types.cc
++++ b/src/wasm/canonical-types.cc
+@@ -43,11 +43,17 @@
+   // Multiple threads could try to register recursive groups concurrently.
+   // TODO(manoskouk): Investigate if we can fine-grain the synchronization.
+   base::MutexGuard mutex_guard(&mutex_);
++  // Compute the first canonical index in the recgroup in the case that it does
++  // not already exist.
++  CanonicalTypeIndex first_new_canonical_index{
++      static_cast<uint32_t>(canonical_supertypes_.size())};
++
+   DCHECK_GE(module->types.size(), start_index + size);
+-  CanonicalGroup group{&zone_, size};
++  CanonicalGroup group{&zone_, size, first_new_canonical_index};
+   for (uint32_t i = 0; i < size; i++) {
+-    group.types[i] = CanonicalizeTypeDef(module, module->types[start_index + i],
+-                                         start_index);
++    group.types[i] = CanonicalizeTypeDef(
++        module, ModuleTypeIndex{start_index + i}, ModuleTypeIndex{start_index},
++        first_new_canonical_index);
+   }
+   if (CanonicalTypeIndex canonical_index = FindCanonicalGroup(group);
+       canonical_index.valid()) {
+@@ -62,22 +68,13 @@
+     // allocated in {CanonicalizeTypeDef{).
+     return;
+   }
+-  // Identical group not found. Add new canonical representatives for the new
+-  // types.
+-  uint32_t first_canonical_index =
+-      static_cast<uint32_t>(canonical_supertypes_.size());
+-  canonical_supertypes_.resize(first_canonical_index + size);
++  canonical_supertypes_.resize(first_new_canonical_index.index + size);
+   CheckMaxCanonicalIndex();
+   for (uint32_t i = 0; i < size; i++) {
+     CanonicalType& canonical_type = group.types[i];
+-    // Compute the canonical index of the supertype: If it is relative, we
+-    // need to add {first_canonical_index}.
+-    canonical_supertypes_[first_canonical_index + i] =
+-        canonical_type.is_relative_supertype
+-            ? CanonicalTypeIndex{canonical_type.supertype.index +
+-                                 first_canonical_index}
+-            : canonical_type.supertype;
+-    CanonicalTypeIndex canonical_id{first_canonical_index + i};
++    canonical_supertypes_[first_new_canonical_index.index + i] =
++        canonical_type.supertype;
++    CanonicalTypeIndex canonical_id{first_new_canonical_index.index + i};
+     module->isorecursive_canonical_type_ids[start_index + i] = canonical_id;
+     if (canonical_type.kind == CanonicalType::kFunction) {
+       const CanonicalSig* sig = canonical_type.function_sig;
+@@ -85,15 +82,13 @@
+     }
+   }
+   // Check that this canonical ID is not used yet.
+-  DCHECK(std::none_of(canonical_singleton_groups_.begin(),
+-                      canonical_singleton_groups_.end(), [=](auto& entry) {
+-                        return entry.second.index == first_canonical_index;
+-                      }));
+-  DCHECK(std::none_of(canonical_groups_.begin(), canonical_groups_.end(),
+-                      [=](auto& entry) {
+-                        return entry.second.index == first_canonical_index;
+-                      }));
+-  canonical_groups_.emplace(group, CanonicalTypeIndex{first_canonical_index});
++  DCHECK(std::none_of(
++      canonical_singleton_groups_.begin(), canonical_singleton_groups_.end(),
++      [=](auto& entry) { return entry.index == first_new_canonical_index; }));
++  DCHECK(std::none_of(
++      canonical_groups_.begin(), canonical_groups_.end(),
++      [=](auto& entry) { return entry.start == first_new_canonical_index; }));
++  canonical_groups_.emplace(group);
+ }
+ 
+ void TypeCanonicalizer::AddRecursiveSingletonGroup(WasmModule* module) {
+@@ -105,8 +100,11 @@
+                                                    uint32_t start_index) {
+   base::MutexGuard guard(&mutex_);
+   DCHECK_GT(module->types.size(), start_index);
+-  CanonicalTypeIndex canonical_index = AddRecursiveGroup(
+-      CanonicalizeTypeDef(module, module->types[start_index], start_index));
++  CanonicalTypeIndex first_new_canonical_index{
++      static_cast<uint32_t>(canonical_supertypes_.size())};
++  CanonicalTypeIndex canonical_index = AddRecursiveGroup(CanonicalizeTypeDef(
++      module, ModuleTypeIndex{start_index}, ModuleTypeIndex{start_index},
++      first_new_canonical_index));
+   module->isorecursive_canonical_type_ids[start_index] = canonical_index;
+ }
+ 
+@@ -118,7 +116,6 @@
+ #endif
+   const bool kFinal = true;
+   const bool kNotShared = false;
+-  const bool kNonRelativeSupertype = false;
+   // Because of the checks above, we can treat the type_def as canonical.
+   // TODO(366180605): It would be nice to not have to rely on a cast here.
+   // Is there a way to avoid it? In the meantime, these asserts provide at
+@@ -127,13 +124,14 @@
+   static_assert(CanonicalValueType::Primitive(kI32).raw_bit_field() ==
+                 ValueType::Primitive(kI32).raw_bit_field());
+   CanonicalType canonical{reinterpret_cast<const CanonicalSig*>(sig),
+-                          CanonicalTypeIndex{kNoSuperType}, kFinal, kNotShared,
+-                          kNonRelativeSupertype};
++                          CanonicalTypeIndex{kNoSuperType}, kFinal, kNotShared};
+   base::MutexGuard guard(&mutex_);
+   // Fast path lookup before canonicalizing (== copying into the
+   // TypeCanonicalizer's zone) the function signature.
+-  CanonicalTypeIndex index =
+-      FindCanonicalGroup(CanonicalSingletonGroup{canonical});
++  CanonicalTypeIndex hypothetical_new_canonical_index{
++      static_cast<uint32_t>(canonical_supertypes_.size())};
++  CanonicalTypeIndex index = FindCanonicalGroup(
++      CanonicalSingletonGroup{canonical, hypothetical_new_canonical_index});
+   if (index.valid()) return index;
+   // Copy into this class's zone, then call the generic {AddRecursiveGroup}.
+   CanonicalSig::Builder builder(&zone_, sig->return_count(),
+@@ -145,12 +143,16 @@
+     builder.AddParam(CanonicalValueType{param});
+   }
+   canonical.function_sig = builder.Get();
+-  return AddRecursiveGroup(canonical);
++  CanonicalTypeIndex canonical_index = AddRecursiveGroup(canonical);
++  DCHECK_EQ(canonical_index, hypothetical_new_canonical_index);
++  return canonical_index;
+ }
+ 
+ CanonicalTypeIndex TypeCanonicalizer::AddRecursiveGroup(CanonicalType type) {
+   mutex_.AssertHeld();  // The caller must hold the mutex.
+-  CanonicalSingletonGroup group{type};
++  CanonicalTypeIndex new_canonical_index{
++      static_cast<uint32_t>(canonical_supertypes_.size())};
++  CanonicalSingletonGroup group{type, new_canonical_index};
+   if (CanonicalTypeIndex index = FindCanonicalGroup(group); index.valid()) {
+     //  Make sure this signature can be looked up later.
+     DCHECK_IMPLIES(type.kind == CanonicalType::kFunction,
+@@ -158,26 +160,21 @@
+     return index;
+   }
+   static_assert(kMaxCanonicalTypes <= kMaxUInt32);
+-  CanonicalTypeIndex index{static_cast<uint32_t>(canonical_supertypes_.size())};
+   // Check that this canonical ID is not used yet.
+-  DCHECK(std::none_of(canonical_singleton_groups_.begin(),
+-                      canonical_singleton_groups_.end(),
+-                      [=](auto& entry) { return entry.second == index; }));
+-  DCHECK(std::none_of(canonical_groups_.begin(), canonical_groups_.end(),
+-                      [=](auto& entry) { return entry.second == index; }));
+-  canonical_singleton_groups_.emplace(group, index);
+-  // Compute the canonical index of the supertype: If it is relative, we
+-  // need to add {canonical_index}.
+-  canonical_supertypes_.push_back(
+-      type.is_relative_supertype
+-          ? CanonicalTypeIndex{type.supertype.index + index.index}
+-          : type.supertype);
++  DCHECK(std::none_of(
++      canonical_singleton_groups_.begin(), canonical_singleton_groups_.end(),
++      [=](auto& entry) { return entry.index == new_canonical_index; }));
++  DCHECK(std::none_of(
++      canonical_groups_.begin(), canonical_groups_.end(),
++      [=](auto& entry) { return entry.start == new_canonical_index; }));
++  canonical_singleton_groups_.emplace(group);
++  canonical_supertypes_.push_back(type.supertype);
+   if (type.kind == CanonicalType::kFunction) {
+     const CanonicalSig* sig = type.function_sig;
+-    CHECK(canonical_function_sigs_.emplace(index, sig).second);
++    CHECK(canonical_function_sigs_.emplace(new_canonical_index, sig).second);
+   }
+   CheckMaxCanonicalIndex();
+-  return index;
++  return new_canonical_index;
+ }
+ 
+ const CanonicalSig* TypeCanonicalizer::LookupFunctionSignature(
+@@ -194,34 +191,22 @@
+                                  {kPredefinedArrayI16Index, {kWasmI16}}};
+   for (auto [index, element_type] : kPredefinedArrayTypes) {
+     DCHECK_EQ(index.index, canonical_singleton_groups_.size());
+-    CanonicalSingletonGroup group;
+     static constexpr bool kMutable = true;
+     // TODO(jkummerow): Decide whether this should be final or nonfinal.
+     static constexpr bool kFinal = true;
+     static constexpr bool kShared = false;  // TODO(14616): Fix this.
+-    static constexpr bool kNonRelativeSupertype = false;
+     CanonicalArrayType* type =
+         zone_.New<CanonicalArrayType>(element_type, kMutable);
+-    group.type = CanonicalType(type, CanonicalTypeIndex{kNoSuperType}, kFinal,
+-                               kShared, kNonRelativeSupertype);
+-    canonical_singleton_groups_.emplace(group, index);
++    CanonicalSingletonGroup group{
++        .type = CanonicalType(type, CanonicalTypeIndex{kNoSuperType}, kFinal,
++                              kShared),
++        .index = index};
++    canonical_singleton_groups_.emplace(group);
+     canonical_supertypes_.emplace_back(CanonicalTypeIndex{kNoSuperType});
+     DCHECK_LE(canonical_supertypes_.size(), kMaxCanonicalTypes);
+   }
+ }
+ 
+-CanonicalValueType TypeCanonicalizer::CanonicalizeValueType(
+-    const WasmModule* module, ValueType type,
+-    uint32_t recursive_group_start) const {
+-  if (!type.has_index()) return CanonicalValueType{type};
+-  static_assert(kMaxCanonicalTypes <= (1u << ValueType::kHeapTypeBits));
+-  return type.ref_index().index >= recursive_group_start
+-             ? CanonicalValueType::WithRelativeIndex(
+-                   type.kind(), type.ref_index().index - recursive_group_start)
+-             : CanonicalValueType::FromIndex(
+-                   type.kind(), module->canonical_type_id(type.ref_index()));
+-}
+-
+ bool TypeCanonicalizer::IsCanonicalSubtype(CanonicalTypeIndex sub_index,
+                                            CanonicalTypeIndex super_index) {
+   // Fast path without synchronization:
+@@ -259,62 +244,75 @@
+ }
+ 
+ TypeCanonicalizer::CanonicalType TypeCanonicalizer::CanonicalizeTypeDef(
+-    const WasmModule* module, TypeDefinition type,
+-    uint32_t recursive_group_start) {
++    const WasmModule* module, ModuleTypeIndex module_type_idx,
++    ModuleTypeIndex recgroup_start,
++    CanonicalTypeIndex canonical_recgroup_start) {
+   mutex_.AssertHeld();  // The caller must hold the mutex.
+-  CanonicalTypeIndex supertype{kNoSuperType};
+-  bool is_relative_supertype = false;
+-  if (type.supertype.index < recursive_group_start) {
+-    supertype = module->canonical_type_id(type.supertype);
+-  } else if (type.supertype.valid()) {
+-    supertype =
+-        CanonicalTypeIndex{type.supertype.index - recursive_group_start};
+-    is_relative_supertype = true;
+-  }
++
++  auto CanonicalizeTypeIndex = [=](ModuleTypeIndex type_index) {
++    DCHECK(type_index.valid());
++    return type_index < recgroup_start
++               // This references a type from an earlier recgroup; use the
++               // already-canonicalized type index.
++               ? module->canonical_type_id(type_index)
++               // For types within the same recgroup, generate indexes assuming
++               // that this is a new canonical recgroup.
++               : CanonicalTypeIndex{canonical_recgroup_start.index +
++                                    (type_index.index - recgroup_start.index)};
++  };
++
++  auto CanonicalizeValueType = [=](ValueType type) {
++    if (!type.has_index()) return CanonicalValueType{type};
++    static_assert(kMaxCanonicalTypes <= (1u << ValueType::kHeapTypeBits));
++    return CanonicalValueType::FromIndex(
++        type.kind(), CanonicalizeTypeIndex(type.ref_index()));
++  };
++
++  TypeDefinition type = module->type(module_type_idx);
++  CanonicalTypeIndex supertype = type.supertype.valid()
++                                     ? CanonicalizeTypeIndex(type.supertype)
++                                     : CanonicalTypeIndex::Invalid();
+   switch (type.kind) {
+     case TypeDefinition::kFunction: {
+       const FunctionSig* original_sig = type.function_sig;
+       CanonicalSig::Builder builder(&zone_, original_sig->return_count(),
+                                     original_sig->parameter_count());
+       for (ValueType ret : original_sig->returns()) {
+-        builder.AddReturn(
+-            CanonicalizeValueType(module, ret, recursive_group_start));
++        builder.AddReturn(CanonicalizeValueType(ret));
+       }
+       for (ValueType param : original_sig->parameters()) {
+-        builder.AddParam(
+-            CanonicalizeValueType(module, param, recursive_group_start));
++        builder.AddParam(CanonicalizeValueType(param));
+       }
+       return CanonicalType(builder.Get(), supertype, type.is_final,
+-                           type.is_shared, is_relative_supertype);
++                           type.is_shared);
+     }
+     case TypeDefinition::kStruct: {
+       const StructType* original_type = type.struct_type;
+       CanonicalStructType::Builder builder(&zone_,
+                                            original_type->field_count());
+       for (uint32_t i = 0; i < original_type->field_count(); i++) {
+-        builder.AddField(CanonicalizeValueType(module, original_type->field(i),
+-                                               recursive_group_start),
++        builder.AddField(CanonicalizeValueType(original_type->field(i)),
+                          original_type->mutability(i),
+                          original_type->field_offset(i));
+       }
+       builder.set_total_fields_size(original_type->total_fields_size());
+       return CanonicalType(
+           builder.Build(CanonicalStructType::Builder::kUseProvidedOffsets),
+-          supertype, type.is_final, type.is_shared, is_relative_supertype);
++          supertype, type.is_final, type.is_shared);
+     }
+     case TypeDefinition::kArray: {
+-      CanonicalValueType element_type = CanonicalizeValueType(
+-          module, type.array_type->element_type(), recursive_group_start);
++      CanonicalValueType element_type =
++          CanonicalizeValueType(type.array_type->element_type());
+       CanonicalArrayType* array_type = zone_.New<CanonicalArrayType>(
+           element_type, type.array_type->mutability());
+-      return CanonicalType(array_type, supertype, type.is_final, type.is_shared,
+-                           is_relative_supertype);
++      return CanonicalType(array_type, supertype, type.is_final,
++                           type.is_shared);
+     }
+   }
+ }
+ 
+ // Returns the index of the canonical representative of the first type in this
+-// group, or -1 if an identical group does not exist.
++// group if it exists, and `CanonicalTypeIndex::Invalid()` otherwise.
+ CanonicalTypeIndex TypeCanonicalizer::FindCanonicalGroup(
+     const CanonicalGroup& group) const {
+   // Groups of size 0 do not make sense here; groups of size 1 should use
+@@ -322,7 +320,7 @@
+   DCHECK_LT(1, group.types.size());
+   auto it = canonical_groups_.find(group);
+   return it == canonical_groups_.end() ? CanonicalTypeIndex::Invalid()
+-                                       : it->second;
++                                       : it->start;
+ }
+ 
+ // Returns the canonical index of the given group if it already exists.
+@@ -330,10 +328,8 @@
+     const CanonicalSingletonGroup& group) const {
+   auto it = canonical_singleton_groups_.find(group);
+   static_assert(kMaxCanonicalTypes <= kMaxInt);
+-  if (it == canonical_singleton_groups_.end()) {
+-    return CanonicalTypeIndex::Invalid();
+-  }
+-  return it->second;
++  return it == canonical_singleton_groups_.end() ? CanonicalTypeIndex::Invalid()
++                                                 : it->index;
+ }
+ 
+ size_t TypeCanonicalizer::EstimateCurrentMemoryConsumption() const {
+diff --git a/src/wasm/canonical-types.h b/src/wasm/canonical-types.h
+index 64cfc5c..42cb526 100644
+--- a/src/wasm/canonical-types.h
++++ b/src/wasm/canonical-types.h
+@@ -11,6 +11,7 @@
+ 
+ #include <unordered_map>
+ 
++#include "src/base/bounds.h"
+ #include "src/base/functional.h"
+ #include "src/wasm/value-type.h"
+ #include "src/wasm/wasm-module.h"
+@@ -144,106 +145,226 @@
+     bool is_final = false;
+     bool is_shared = false;
+     uint8_t subtyping_depth = 0;
+-    bool is_relative_supertype;
+ 
+     constexpr CanonicalType(const CanonicalSig* sig,
+                             CanonicalTypeIndex supertype, bool is_final,
+-                            bool is_shared, bool is_relative_supertype)
++                            bool is_shared)
+         : function_sig(sig),
+           supertype(supertype),
+           kind(kFunction),
+           is_final(is_final),
+-          is_shared(is_shared),
+-          is_relative_supertype(is_relative_supertype) {}
++          is_shared(is_shared) {}
+ 
+     constexpr CanonicalType(const CanonicalStructType* type,
+                             CanonicalTypeIndex supertype, bool is_final,
+-                            bool is_shared, bool is_relative_supertype)
++                            bool is_shared)
+         : struct_type(type),
+           supertype(supertype),
+           kind(kStruct),
+           is_final(is_final),
+-          is_shared(is_shared),
+-          is_relative_supertype(is_relative_supertype) {}
++          is_shared(is_shared) {}
+ 
+     constexpr CanonicalType(const CanonicalArrayType* type,
+                             CanonicalTypeIndex supertype, bool is_final,
+-                            bool is_shared, bool is_relative_supertype)
++                            bool is_shared)
+         : array_type(type),
+           supertype(supertype),
+           kind(kArray),
+           is_final(is_final),
+-          is_shared(is_shared),
+-          is_relative_supertype(is_relative_supertype) {}
++          is_shared(is_shared) {}
+ 
+     constexpr CanonicalType() = default;
++  };
+ 
+-    bool operator==(const CanonicalType& other) const {
+-      if (supertype != other.supertype) return false;
+-      if (kind != other.kind) return false;
+-      if (is_final != other.is_final) return false;
+-      if (is_shared != other.is_shared) return false;
+-      if (is_relative_supertype != other.is_relative_supertype) return false;
+-      if (kind == kFunction) return *function_sig == *other.function_sig;
+-      if (kind == kStruct) return *struct_type == *other.struct_type;
+-      DCHECK_EQ(kArray, kind);
+-      return *array_type == *other.array_type;
++  // Define the range of a recursion group; for use in {CanonicalHashing} and
++  // {CanonicalEquality}.
++  struct RecursionGroupRange {
++    const CanonicalTypeIndex start;
++    const CanonicalTypeIndex end;
++
++    bool Contains(CanonicalTypeIndex index) const {
++      return base::IsInRange(index.index, start.index, end.index);
+     }
+ 
+-    bool operator!=(const CanonicalType& other) const {
+-      return !operator==(other);
++    CanonicalTypeIndex RelativeIndex(CanonicalTypeIndex index) const {
++      return Contains(index)
++                 // Make the value_type relative within the recursion group.
++                 ? CanonicalTypeIndex{index.index - start.index}
++                 : index;
++    }
++
++    CanonicalValueType RelativeType(CanonicalValueType type) const {
++      return type.has_index()
++                 ? CanonicalValueType::FromIndex(
++                       type.kind(), RelativeIndex(type.ref_index()))
++                 : type;
++    }
++  };
++
++  // Support for hashing of recursion groups, where type indexes have to be
++  // hashed relative to the recursion group.
++  struct CanonicalHashing {
++    base::Hasher hasher;
++    const RecursionGroupRange recgroup;
++
++    explicit CanonicalHashing(RecursionGroupRange recgroup)
++        : recgroup{recgroup} {}
++
++    void Add(CanonicalType type) {
++      CanonicalTypeIndex relative_supertype =
++          recgroup.RelativeIndex(type.supertype);
++      uint32_t metadata =
++          (relative_supertype.index << 1) | (type.is_final ? 1 : 0);
++      hasher.Add(metadata);
++      switch (type.kind) {
++        case CanonicalType::kFunction:
++          Add(*type.function_sig);
++          break;
++        case CanonicalType::kStruct:
++          Add(*type.struct_type);
++          break;
++        case CanonicalType::kArray:
++          Add(*type.array_type);
++          break;
++      }
++    }
++
++    void Add(CanonicalValueType value_type) {
++      hasher.Add(recgroup.RelativeType(value_type));
++    }
++
++    void Add(const CanonicalSig& sig) {
++      hasher.Add(sig.parameter_count());
++      for (CanonicalValueType type : sig.all()) Add(type);
++    }
++
++    void Add(const CanonicalStructType& struct_type) {
++      hasher.AddRange(struct_type.mutabilities());
++      for (const ValueTypeBase& field : struct_type.fields()) {
++        Add(CanonicalValueType{field});
++      }
++    }
++
++    void Add(const CanonicalArrayType& array_type) {
++      hasher.Add(array_type.mutability());
++      Add(array_type.element_type());
++    }
++
++    size_t hash() const { return hasher.hash(); }
++  };
++
++  // Support for equality checking of recursion groups, where type indexes have
++  // to be compared relative to their respective recursion group.
++  struct CanonicalEquality {
++    // Recursion group bounds for LHS and RHS.
++    const RecursionGroupRange recgroup1;
++    const RecursionGroupRange recgroup2;
++
++    CanonicalEquality(RecursionGroupRange recgroup1,
++                      RecursionGroupRange recgroup2)
++        : recgroup1{recgroup1}, recgroup2{recgroup2} {}
++
++    bool EqualType(const CanonicalType& type1,
++                   const CanonicalType& type2) const {
++      if (recgroup1.RelativeIndex(type1.supertype) !=
++          recgroup2.RelativeIndex(type2.supertype)) {
++        return false;
++      }
++      if (type1.is_final != type2.is_final) return false;
++      if (type1.is_shared != type2.is_shared) return false;
++      switch (type1.kind) {
++        case CanonicalType::kFunction:
++          return type2.kind == CanonicalType::kFunction &&
++                 EqualSig(*type1.function_sig, *type2.function_sig);
++        case CanonicalType::kStruct:
++          return type2.kind == CanonicalType::kStruct &&
++                 EqualStructType(*type1.struct_type, *type2.struct_type);
++        case CanonicalType::kArray:
++          return type2.kind == CanonicalType::kArray &&
++                 EqualArrayType(*type1.array_type, *type2.array_type);
++      }
++    }
++
++    bool EqualTypes(base::Vector<const CanonicalType> types1,
++                    base::Vector<const CanonicalType> types2) const {
++      return std::equal(types1.begin(), types1.end(), types2.begin(),
++                        types2.end(),
++                        std::bind_front(&CanonicalEquality::EqualType, this));
++    }
++
++    bool EqualValueType(CanonicalValueType type1,
++                        CanonicalValueType type2) const {
++      return recgroup1.RelativeType(type1) == recgroup2.RelativeType(type2);
++    }
++
++    bool EqualSig(const CanonicalSig& sig1, const CanonicalSig& sig2) const {
++      if (sig1.parameter_count() != sig2.parameter_count()) return false;
++      return std::equal(
++          sig1.all().begin(), sig1.all().end(), sig2.all().begin(),
++          sig2.all().end(),
++          std::bind_front(&CanonicalEquality::EqualValueType, this));
++    }
++
++    bool EqualStructType(const CanonicalStructType& type1,
++                         const CanonicalStructType& type2) const {
++      return std::equal(
++          type1.fields().begin(), type1.fields().end(), type2.fields().begin(),
++          type2.fields().end(),
++          std::bind_front(&CanonicalEquality::EqualValueType, this));
++    }
++
++    bool EqualArrayType(const CanonicalArrayType& type1,
++                        const CanonicalArrayType& type2) const {
++      return type1.mutability() == type2.mutability() &&
++             EqualValueType(type1.element_type(), type2.element_type());
++    }
++  };
++
++  struct CanonicalGroup {
++    CanonicalGroup(Zone* zone, size_t size, CanonicalTypeIndex start)
++        : types(zone->AllocateVector<CanonicalType>(size)), start(start) {
++      // size >= 2; otherwise a `CanonicalSingletonGroup` should have been used.
++      DCHECK_LE(2, size);
++    }
++
++    bool operator==(const CanonicalGroup& other) const {
++      CanonicalTypeIndex end{start.index +
++                             static_cast<uint32_t>(types.size() - 1)};
++      CanonicalTypeIndex other_end{
++          other.start.index + static_cast<uint32_t>(other.types.size() - 1)};
++      CanonicalEquality equality{{start, end}, {other.start, other_end}};
++      return equality.EqualTypes(types, other.types);
+     }
+ 
+     size_t hash_value() const {
+-      uint32_t metadata = (supertype.index << 2) | (is_final ? 2 : 0) |
+-                          (is_relative_supertype ? 1 : 0);
+-      base::Hasher hasher;
+-      hasher.Add(metadata);
+-      if (kind == kFunction) {
+-        hasher.Add(*function_sig);
+-      } else if (kind == kStruct) {
+-        hasher.Add(*struct_type);
+-      } else {
+-        DCHECK_EQ(kArray, kind);
+-        hasher.Add(*array_type);
++      CanonicalTypeIndex end{start.index + static_cast<uint32_t>(types.size()) -
++                             1};
++      CanonicalHashing hasher{{start, end}};
++      for (CanonicalType t : types) {
++        hasher.Add(t);
+       }
+       return hasher.hash();
+     }
+-  };
+-  struct CanonicalGroup {
+-    CanonicalGroup(Zone* zone, size_t size)
+-        : types(zone->AllocateVector<CanonicalType>(size)) {}
+-
+-    bool operator==(const CanonicalGroup& other) const {
+-      return types == other.types;
+-    }
+-
+-    bool operator!=(const CanonicalGroup& other) const {
+-      return types != other.types;
+-    }
+-
+-    size_t hash_value() const {
+-      return base::Hasher{}.AddRange(types.begin(), types.end()).hash();
+-    }
+ 
+     // The storage of this vector is the TypeCanonicalizer's zone_.
+-    base::Vector<CanonicalType> types;
++    const base::Vector<CanonicalType> types;
++    const CanonicalTypeIndex start;
+   };
+ 
+   struct CanonicalSingletonGroup {
+-    struct hash {
+-      size_t operator()(const CanonicalSingletonGroup& group) const {
+-        return group.hash_value();
+-      }
+-    };
+-
+     bool operator==(const CanonicalSingletonGroup& other) const {
+-      return type == other.type;
++      CanonicalEquality equality{{index, index}, {other.index, other.index}};
++      return equality.EqualType(type, other.type);
+     }
+ 
+-    size_t hash_value() const { return type.hash_value(); }
++    size_t hash_value() const {
++      CanonicalHashing hasher{{index, index}};
++      hasher.Add(type);
++      return hasher.hash();
++    }
+ 
+     CanonicalType type;
++    CanonicalTypeIndex index;
+   };
+ 
+   void AddPredefinedArrayTypes();
+@@ -251,30 +372,25 @@
+   CanonicalTypeIndex FindCanonicalGroup(const CanonicalGroup&) const;
+   CanonicalTypeIndex FindCanonicalGroup(const CanonicalSingletonGroup&) const;
+ 
+-  // Canonicalize all types present in {type} (including supertype) according to
+-  // {CanonicalizeValueType}.
+-  CanonicalType CanonicalizeTypeDef(const WasmModule* module,
+-                                    TypeDefinition type,
+-                                    uint32_t recursive_group_start);
+-
+-  // An indexed type gets mapped to a {CanonicalValueType::WithRelativeIndex}
+-  // if its index points inside the new canonical group; otherwise, the index
+-  // gets mapped to its canonical representative.
+-  CanonicalValueType CanonicalizeValueType(
+-      const WasmModule* module, ValueType type,
+-      uint32_t recursive_group_start) const;
++  // Canonicalize the module-specific type at `module_type_idx` within the
++  // recursion group starting at `recursion_group_start`, using
++  // `canonical_recgroup_start` as the start offset of types within the
++  // recursion group.
++  CanonicalType CanonicalizeTypeDef(
++      const WasmModule* module, ModuleTypeIndex module_type_idx,
++      ModuleTypeIndex recgroup_start,
++      CanonicalTypeIndex canonical_recgroup_start);
+ 
+   CanonicalTypeIndex AddRecursiveGroup(CanonicalType type);
+ 
+   void CheckMaxCanonicalIndex() const;
+ 
+   std::vector<CanonicalTypeIndex> canonical_supertypes_;
+-  // Maps groups of size >=2 to the canonical id of the first type.
+-  std::unordered_map<CanonicalGroup, CanonicalTypeIndex,
+-                     base::hash<CanonicalGroup>>
++  // Set of all known canonical recgroups of size >=2.
++  std::unordered_set<CanonicalGroup, base::hash<CanonicalGroup>>
+       canonical_groups_;
+-  // Maps group of size 1 to the canonical id of the type.
+-  std::unordered_map<CanonicalSingletonGroup, CanonicalTypeIndex,
++  // Set of all known canonical recgroups of size 1.
++  std::unordered_set<CanonicalSingletonGroup,
+                      base::hash<CanonicalSingletonGroup>>
+       canonical_singleton_groups_;
+   // Maps canonical indices back to the function signature.
+diff --git a/src/wasm/std-object-sizes.h b/src/wasm/std-object-sizes.h
+index 6b16ef2..e1b11d3 100644
+--- a/src/wasm/std-object-sizes.h
++++ b/src/wasm/std-object-sizes.h
+@@ -45,8 +45,8 @@
+   return raw * 4 / 3;
+ }
+ 
+-template <typename T>
+-inline size_t ContentSize(std::unordered_set<T> set) {
++template <typename T, typename Hash>
++inline size_t ContentSize(const std::unordered_set<T, Hash>& set) {
+   // Very rough lower bound approximation: two internal pointers per entry.
+   size_t raw = set.size() * (sizeof(T) + 2 * sizeof(void*));
+   // In the spirit of computing lower bounds of definitely-used memory,
+diff --git a/src/wasm/struct-types.h b/src/wasm/struct-types.h
+index 1e2b02b..ae85c0d 100644
+--- a/src/wasm/struct-types.h
++++ b/src/wasm/struct-types.h
+@@ -266,8 +266,9 @@
+ 
+ // Support base::hash<StructTypeBase>.
+ inline size_t hash_value(const StructTypeBase& type) {
++  // Note: If you update this you probably also want to update
++  // `CanonicalHashing::Add(CanonicalStructType)`.
+   return base::Hasher{}
+-      .Add(type.field_count())
+       .AddRange(type.fields())
+       .AddRange(type.mutabilities())
+       .hash();
+@@ -324,6 +325,8 @@
+   return base::Hasher::Combine(type.element_type(), type.mutability());
+ }
+ inline size_t hash_value(const CanonicalArrayType& type) {
++  // Note: If you update this you probably also want to update
++  // `CanonicalHashing::Add(CanonicalArrayType)`.
+   return base::Hasher::Combine(type.element_type(), type.mutability());
+ }
+ 
+diff --git a/src/wasm/value-type.h b/src/wasm/value-type.h
+index 906da41..e823698 100644
+--- a/src/wasm/value-type.h
++++ b/src/wasm/value-type.h
+@@ -63,13 +63,12 @@
+ struct ModuleTypeIndex : public TypeIndex {
+   inline static constexpr ModuleTypeIndex Invalid();
+   // Can't use "=default" because the base class doesn't have operator<=>.
+-  bool operator==(const ModuleTypeIndex& other) const {
+-    return index == other.index;
+-  }
+-  auto operator<=>(const ModuleTypeIndex& other) const {
++  bool operator==(ModuleTypeIndex other) const { return index == other.index; }
++  auto operator<=>(ModuleTypeIndex other) const {
+     return index <=> other.index;
+   }
+ };
++ASSERT_TRIVIALLY_COPYABLE(ModuleTypeIndex);
+ 
+ constexpr ModuleTypeIndex ModuleTypeIndex::Invalid() {
+   return ModuleTypeIndex{ModuleTypeIndex::kInvalid};
+@@ -78,13 +77,14 @@
+ struct CanonicalTypeIndex : public TypeIndex {
+   inline static constexpr CanonicalTypeIndex Invalid();
+ 
+-  bool operator==(const CanonicalTypeIndex& other) const {
++  bool operator==(CanonicalTypeIndex other) const {
+     return index == other.index;
+   }
+-  auto operator<=>(const CanonicalTypeIndex& other) const {
++  auto operator<=>(CanonicalTypeIndex other) const {
+     return index <=> other.index;
+   }
+ };
++ASSERT_TRIVIALLY_COPYABLE(CanonicalTypeIndex);
+ 
+ constexpr CanonicalTypeIndex CanonicalTypeIndex::Invalid() {
+   return CanonicalTypeIndex{CanonicalTypeIndex::kInvalid};
+@@ -610,8 +610,6 @@
+ // A ValueType is encoded by two components: a ValueKind and a heap
+ // representation (for reference types/rtts). Those are encoded into 32 bits
+ // using base::BitField.
+-// ValueType encoding includes an additional bit marking the index of a type as
+-// relative. This should only be used during type canonicalization.
+ // {ValueTypeBase} shouldn't be used directly; code should be using one of
+ // the subclasses. To enforce this, the public interface is limited to
+ // type index agnostic getters.
+@@ -849,7 +847,7 @@
+   /**************************** Static constants ******************************/
+   static constexpr int kKindBits = 5;
+   static constexpr int kHeapTypeBits = 20;
+-  static constexpr int kLastUsedBit = 25;
++  static constexpr int kLastUsedBit = 24;
+ 
+   static const intptr_t kBitFieldOffset;
+ 
+@@ -908,17 +906,12 @@
+ 
+   using KindField = base::BitField<ValueKind, 0, kKindBits>;
+   using HeapTypeField = KindField::Next<uint32_t, kHeapTypeBits>;
+-  // Marks a type as a canonical type which uses an index relative to its
+-  // recursive group start. Used only during type canonicalization.
+-  using CanonicalRelativeField = HeapTypeField::Next<bool, 1>;
+ 
+   static_assert(kV8MaxWasmTypes < (1u << kHeapTypeBits),
+                 "Type indices fit in kHeapTypeBits");
+   // This is implemented defensively against field order changes.
+-  static_assert(kLastUsedBit ==
+-                    std::max(KindField::kLastUsedBit,
+-                             std::max(HeapTypeField::kLastUsedBit,
+-                                      CanonicalRelativeField::kLastUsedBit)),
++  static_assert(kLastUsedBit == std::max(KindField::kLastUsedBit,
++                                         HeapTypeField::kLastUsedBit),
+                 "kLastUsedBit is consistent");
+ 
+   constexpr explicit ValueTypeBase(uint32_t bit_field)
+@@ -1058,13 +1051,6 @@
+         KindField::encode(kind) | HeapTypeField::encode(index.index))};
+   }
+ 
+-  static constexpr CanonicalValueType WithRelativeIndex(ValueKind kind,
+-                                                        uint32_t index) {
+-    return CanonicalValueType{
+-        ValueTypeBase(KindField::encode(kind) | HeapTypeField::encode(index) |
+-                      CanonicalRelativeField::encode(true))};
+-  }
+-
+   static constexpr CanonicalValueType FromRawBitField(uint32_t bit_field) {
+     return CanonicalValueType{ValueTypeBase::FromRawBitField(bit_field)};
+   }
+@@ -1079,10 +1065,6 @@
+   constexpr CanonicalTypeIndex ref_index() const {
+     return CanonicalTypeIndex{ValueTypeBase::ref_index()};
+   }
+-
+-  constexpr bool is_canonical_relative() const {
+-    return has_index() && CanonicalRelativeField::decode(bit_field_);
+-  }
+ };
+ ASSERT_TRIVIALLY_COPYABLE(CanonicalValueType);
+ 
+diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc
+index b80b809..fbe6735 100644
+--- a/src/wasm/wasm-objects.cc
++++ b/src/wasm/wasm-objects.cc
+@@ -2689,7 +2689,7 @@
+ 
+ bool WasmExportedFunctionData::MatchesSignature(
+     wasm::CanonicalTypeIndex other_canonical_type_index) {
+-  return wasm::GetWasmEngine()->type_canonicalizer()->IsCanonicalSubtype(
++  return wasm::GetTypeCanonicalizer()->IsCanonicalSubtype(
+       sig_index(), other_canonical_type_index);
+ }
+ 
+diff --git a/test/unittests/wasm/subtyping-unittest.cc b/test/unittests/wasm/subtyping-unittest.cc
+index 4d02be7..33a8649 100644
+--- a/test/unittests/wasm/subtyping-unittest.cc
++++ b/test/unittests/wasm/subtyping-unittest.cc
+@@ -74,9 +74,15 @@
+ 
+   // Set up two identical modules.
+   for (WasmModule* module : {module1, module2}) {
+-    /*  0 */ DefineStruct(module, {mut(ref(2)), immut(refNull(2))});
+-    /*  1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))}, Idx{0});
+-    /*  2 */ DefineArray(module, immut(ref(0)));
++    // Three mutually recursive types.
++    /*  0 */ DefineStruct(module, {mut(ref(2)), immut(refNull(2))},
++                          kNoSuperType, false, false, false);
++    /*  1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))}, Idx{0}, false,
++                          false, false);
++    /*  2 */ DefineArray(module, immut(ref(0)), kNoSuperType, false, false,
++                         false);
++    GetTypeCanonicalizer()->AddRecursiveGroup(module, 3);
++
+     /*  3 */ DefineArray(module, immut(ref(1)), Idx{2});
+     /*  4 */ DefineStruct(module, {mut(ref(2)), immut(ref(3)), immut(kWasmF64)},
+                           Idx{1});