[wasm-gc] Implement iso-recursive types

This CL replaces the equirecursive type system for wasm-gc with the
isorecursive hybrid type system presented here:
https://github.com/WebAssembly/gc/issues/257.

In broad strokes, this includes the following changes:
- In the module decoder, remove equirecursive types. Implement recursive
  type groups, subtype definitions, and function/struct/array
  definitions. Treat nominal modules as syntactic sugar of an
  isorecursive module, where all types belong in the same recursive
  group.
- Remove rtt.sub and all related infrastructure.
- Change subtyping to work with explicit supertypes only.
- Add ValidSubtypeDefinition in subtyping, to check that subtype
  declarations are valid during decoding.
- Remove the subtyping cache.
- Add support for functions to have specific signature index in
  WasmModuleBuilder and in test-gc.cc.
- Adapt tests.

Current restrictions:
- Recursive groups are not stored beyond decoding.
- Type canonicalization is not implemented. No tests relying on types
  being considered identical post-canonicalization.
- No cross-module subtyping is possible. Tests relying on cross-module
  subtyping have been commented out.

Bug: v8:7748
Change-Id: I69fd04ecc5611f6230c95d5c89d1c520163fffae
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3416239
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78816}
diff --git a/src/builtins/wasm.tq b/src/builtins/wasm.tq
index 5f46124..99e1591 100644
--- a/src/builtins/wasm.tq
+++ b/src/builtins/wasm.tq
@@ -34,7 +34,6 @@
     Context, WasmInstanceObject, Number, Number, BigInt): Smi;
 extern runtime WasmI64AtomicWait(
     Context, WasmInstanceObject, Number, BigInt, BigInt): Smi;
-extern runtime WasmAllocateRtt(Context, Smi, Map, Smi): Map;
 extern runtime WasmArrayCopy(
     Context, WasmArray, Smi, WasmArray, Smi, Smi): JSAny;
 extern runtime WasmArrayInitFromData(
@@ -54,10 +53,6 @@
 const kExternNonNullTableType: constexpr int31
     generates 'wasm::kWasmExternNonNullableRef.raw_bit_field()';
 
-const kRttSubCanonicalize: constexpr int31
-    generates 'WasmRttSubMode::kCanonicalize';
-const kRttSubFresh: constexpr int31 generates 'WasmRttSubMode::kFresh';
-
 extern macro WasmBuiltinsAssembler::LoadInstanceFromFrame(): WasmInstanceObject;
 
 // WasmInstanceObject has a field layout that Torque can't handle yet.
@@ -306,18 +301,6 @@
   return AllocateJSArray(ElementsKind::PACKED_ELEMENTS, map, size, size);
 }
 
-builtin WasmAllocateRtt(typeIndex: intptr, parent: Map): Map {
-  tail runtime::WasmAllocateRtt(
-      LoadContextFromFrame(), SmiTag(typeIndex), parent,
-      SmiConstant(kRttSubCanonicalize));
-}
-
-builtin WasmAllocateFreshRtt(typeIndex: intptr, parent: Map): Map {
-  tail runtime::WasmAllocateRtt(
-      LoadContextFromFrame(), SmiTag(typeIndex), parent,
-      SmiConstant(kRttSubFresh));
-}
-
 builtin WasmAllocateStructWithRtt(rtt: Map): HeapObject {
   const typeInfo: WasmTypeInfo = %RawDownCast<WasmTypeInfo>(
       rtt.constructor_or_back_pointer_or_native_context);
diff --git a/src/common/globals.h b/src/common/globals.h
index a71775b..64fcff2 100644
--- a/src/common/globals.h
+++ b/src/common/globals.h
@@ -1772,8 +1772,6 @@
   V(TrapArrayOutOfBounds)          \
   V(TrapArrayTooLarge)
 
-enum WasmRttSubMode { kCanonicalize, kFresh };
-
 enum KeyedAccessLoadMode {
   STANDARD_LOAD,
   LOAD_IGNORE_OUT_OF_BOUNDS,
diff --git a/src/compiler/loop-analysis.cc b/src/compiler/loop-analysis.cc
index 14acd92..2b9b5fa 100644
--- a/src/compiler/loop-analysis.cc
+++ b/src/compiler/loop-analysis.cc
@@ -629,8 +629,7 @@
             WasmCode::kWasmAllocateFixedArray, WasmCode::kWasmThrow,
             WasmCode::kWasmRethrow, WasmCode::kWasmRethrowExplicitContext,
             // Fast wasm-gc operations.
-            WasmCode::kWasmRefFunc, WasmCode::kWasmAllocateRtt,
-            WasmCode::kWasmAllocateFreshRtt};
+            WasmCode::kWasmRefFunc};
         if (std::count(unrollable_builtins,
                        unrollable_builtins + arraysize(unrollable_builtins),
                        info) == 0) {
diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index 1cd60b0..5e55cdf 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc
@@ -620,7 +620,7 @@
       Builtin callee = static_cast<Builtin>(matcher.ResolvedValue());
       // Note: Make sure to only add builtins which are guaranteed to return a
       // fresh object. E.g. kWasmAllocateFixedArray may return the canonical
-      // empty array, and kWasmAllocateRtt may return a cached rtt.
+      // empty array.
       return callee == Builtin::kWasmAllocateArray_Uninitialized ||
              callee == Builtin::kWasmAllocateArray_InitNull ||
              callee == Builtin::kWasmAllocateArray_InitZero ||
diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc
index d435dac..9109ae2 100644
--- a/src/compiler/wasm-compiler.cc
+++ b/src/compiler/wasm-compiler.cc
@@ -5655,15 +5655,6 @@
       wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(type_index));
 }
 
-Node* WasmGraphBuilder::RttSub(uint32_t type_index, Node* parent_rtt,
-                               WasmRttSubMode mode) {
-  Builtin target = mode == WasmRttSubMode::kCanonicalize
-                       ? Builtin::kWasmAllocateRtt
-                       : Builtin::kWasmAllocateFreshRtt;
-  return gasm_->CallBuiltin(target, Operator::kEliminatable,
-                            Int32Constant(type_index), parent_rtt);
-}
-
 WasmGraphBuilder::Callbacks WasmGraphBuilder::TestCallbacks(
     GraphAssemblerLabel<1>* label) {
   return {// succeed_if
diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h
index f9fb89f..dbfa99a 100644
--- a/src/compiler/wasm-compiler.h
+++ b/src/compiler/wasm-compiler.h
@@ -514,7 +514,6 @@
   Node* I31GetS(Node* input);
   Node* I31GetU(Node* input);
   Node* RttCanon(uint32_t type_index);
-  Node* RttSub(uint32_t type_index, Node* parent_rtt, WasmRttSubMode mode);
 
   Node* RefTest(Node* object, Node* rtt, ObjectReferenceKnowledge config);
   Node* RefCast(Node* object, Node* rtt, ObjectReferenceKnowledge config,
diff --git a/src/runtime/runtime-wasm.cc b/src/runtime/runtime-wasm.cc
index 0775d1a..e6a2331 100644
--- a/src/runtime/runtime-wasm.cc
+++ b/src/runtime/runtime-wasm.cc
@@ -639,19 +639,6 @@
   return ReadOnlyRoots(isolate).undefined_value();
 }
 
-RUNTIME_FUNCTION(Runtime_WasmAllocateRtt) {
-  ClearThreadInWasmScope flag_scope(isolate);
-  HandleScope scope(isolate);
-  DCHECK_EQ(3, args.length());
-  CONVERT_UINT32_ARG_CHECKED(type_index, 0);
-  CONVERT_ARG_HANDLE_CHECKED(Map, parent, 1);
-  CONVERT_SMI_ARG_CHECKED(raw_mode, 2);
-  Handle<WasmInstanceObject> instance(GetWasmInstanceOnStackTop(isolate),
-                                      isolate);
-  return *wasm::AllocateSubRtt(isolate, instance, type_index, parent,
-                               static_cast<WasmRttSubMode>(raw_mode));
-}
-
 namespace {
 inline void* ArrayElementAddress(Handle<WasmArray> array, uint32_t index,
                                  int element_size_bytes) {
diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h
index 37ecee1..7259b66d 100644
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -587,7 +587,6 @@
   F(WasmCompileWrapper, 2, 1)         \
   F(WasmTriggerTierUp, 1, 1)          \
   F(WasmDebugBreak, 0, 1)             \
-  F(WasmAllocateRtt, 3, 1)            \
   F(WasmArrayCopy, 5, 1)              \
   F(WasmArrayInitFromData, 5, 1)      \
   F(WasmAllocateContinuation, 1, 1)   \
diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc
index 6c192fa..dcf5a3a 100644
--- a/src/wasm/baseline/liftoff-compiler.cc
+++ b/src/wasm/baseline/liftoff-compiler.cc
@@ -5449,27 +5449,6 @@
     __ PushRegister(kRttWithDepth, rtt);
   }
 
-  void RttSub(FullDecoder* decoder, uint32_t type_index, const Value& parent,
-              Value* result, WasmRttSubMode mode) {
-    ValueKind parent_value_kind = parent.type.kind();
-    ValueKind rtt_value_kind = kRttWithDepth;
-    LiftoffRegister type_reg = __ GetUnusedRegister(kGpReg, {});
-    LiftoffAssembler::VarState parent_var =
-        __ cache_state()->stack_state.end()[-1];
-    __ LoadConstant(type_reg, WasmValue(type_index));
-    LiftoffAssembler::VarState type_var(kI32, type_reg, 0);
-    WasmCode::RuntimeStubId target = mode == WasmRttSubMode::kCanonicalize
-                                         ? WasmCode::kWasmAllocateRtt
-                                         : WasmCode::kWasmAllocateFreshRtt;
-    CallRuntimeStub(
-        target,
-        MakeSig::Returns(rtt_value_kind).Params(kI32, parent_value_kind),
-        {type_var, parent_var}, decoder->position());
-    // Drop the parent RTT.
-    __ cache_state()->stack_state.pop_back(1);
-    __ PushRegister(rtt_value_kind, LiftoffRegister(kReturnRegister0));
-  }
-
   enum NullSucceeds : bool {  // --
     kNullSucceeds = true,
     kNullFails = false
diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h
index 055885f..312e229 100644
--- a/src/wasm/function-body-decoder-impl.h
+++ b/src/wasm/function-body-decoder-impl.h
@@ -944,8 +944,6 @@
     const IndexImmediate<validate>& data_segment, const Value& offset,    \
     const Value& length, const Value& rtt, Value* result)                 \
   F(RttCanon, uint32_t type_index, Value* result)                         \
-  F(RttSub, uint32_t type_index, const Value& parent, Value* result,      \
-    WasmRttSubMode mode)                                                  \
   F(DoReturn, uint32_t drop_values)
 
 #define INTERFACE_NON_CONSTANT_FUNCTIONS(F) /*       force 80 columns       */ \
@@ -1900,8 +1898,6 @@
             return length + imm.length;
           }
           case kExprRttCanon:
-          case kExprRttSub:
-          case kExprRttFreshSub:
           case kExprRefTestStatic:
           case kExprRefCastStatic:
           case kExprBrOnCastStatic:
@@ -2068,8 +2064,6 @@
           case kExprI31GetU:
           case kExprArrayNewDefault:
           case kExprArrayLen:
-          case kExprRttSub:
-          case kExprRttFreshSub:
           case kExprRefTestStatic:
           case kExprRefCastStatic:
           case kExprBrOnCastStatic:
@@ -4499,39 +4493,6 @@
         Push(value);
         return opcode_length + imm.length;
       }
-      case kExprRttFreshSub:
-      case kExprRttSub: {
-        IndexImmediate<validate> imm(this, this->pc_ + opcode_length,
-                                     "type index");
-        if (!this->ValidateType(this->pc_ + opcode_length, imm)) return 0;
-        Value parent = Peek(0, 0);
-        if (parent.type.is_bottom()) {
-          DCHECK(!current_code_reachable_and_ok_);
-          // Just leave the unreachable/bottom value on the stack.
-        } else {
-          if (!VALIDATE(parent.type.is_rtt() &&
-                        IsHeapSubtypeOf(imm.index, parent.type.ref_index(),
-                                        this->module_))) {
-            PopTypeError(
-                0, parent,
-                "rtt for a supertype of type " + std::to_string(imm.index));
-            return 0;
-          }
-          Value value = parent.type.has_depth()
-                            ? CreateValue(ValueType::Rtt(
-                                  imm.index, parent.type.depth() + 1))
-                            : CreateValue(ValueType::Rtt(imm.index));
-
-          WasmRttSubMode mode = opcode == kExprRttSub
-                                    ? WasmRttSubMode::kCanonicalize
-                                    : WasmRttSubMode::kFresh;
-          CALL_INTERFACE_IF_OK_AND_REACHABLE(RttSub, imm.index, parent, &value,
-                                             mode);
-          Drop(parent);
-          Push(value);
-        }
-        return opcode_length + imm.length;
-      }
       case kExprRefTest:
       case kExprRefTestStatic: {
         NON_CONST_ONLY
diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc
index eec52e6..f60258a 100644
--- a/src/wasm/graph-builder-interface.cc
+++ b/src/wasm/graph-builder-interface.cc
@@ -1138,11 +1138,6 @@
     result->node = builder_->RttCanon(type_index);
   }
 
-  void RttSub(FullDecoder* decoder, uint32_t type_index, const Value& parent,
-              Value* result, WasmRttSubMode mode) {
-    result->node = builder_->RttSub(type_index, parent.node, mode);
-  }
-
   using StaticKnowledge = compiler::WasmGraphBuilder::ObjectReferenceKnowledge;
 
   StaticKnowledge ComputeStaticKnowledge(ValueType object_type,
diff --git a/src/wasm/init-expr-interface.cc b/src/wasm/init-expr-interface.cc
index 2de5496..ad9f243 100644
--- a/src/wasm/init-expr-interface.cc
+++ b/src/wasm/init-expr-interface.cc
@@ -195,20 +195,6 @@
       ValueType::Rtt(type_index, 0));
 }
 
-void InitExprInterface::RttSub(FullDecoder* decoder, uint32_t type_index,
-                               const Value& parent, Value* result,
-                               WasmRttSubMode mode) {
-  if (!generate_result()) return;
-  ValueType type = parent.type.has_depth()
-                       ? ValueType::Rtt(type_index, parent.type.depth() + 1)
-                       : ValueType::Rtt(type_index);
-  result->runtime_value =
-      WasmValue(Handle<Object>::cast(AllocateSubRtt(
-                    isolate_, instance_, type_index,
-                    Handle<Map>::cast(parent.runtime_value.to_ref()), mode)),
-                type);
-}
-
 void InitExprInterface::DoReturn(FullDecoder* decoder,
                                  uint32_t /*drop_values*/) {
   end_found_ = true;
diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc
index 1f47f7a..463e1f1 100644
--- a/src/wasm/module-decoder.cc
+++ b/src/wasm/module-decoder.cc
@@ -554,109 +554,183 @@
     }
   }
 
+  TypeDefinition consume_base_type_definition() {
+    DCHECK(enabled_features_.has_gc());
+    uint8_t kind = consume_u8("type kind");
+    switch (kind) {
+      case kWasmFunctionTypeCode: {
+        const FunctionSig* sig = consume_sig(module_->signature_zone.get());
+        return {sig, kNoSuperType};
+      }
+      case kWasmStructTypeCode: {
+        const StructType* type = consume_struct(module_->signature_zone.get());
+        return {type, kNoSuperType};
+      }
+      case kWasmArrayTypeCode: {
+        const ArrayType* type = consume_array(module_->signature_zone.get());
+        return {type, kNoSuperType};
+      }
+      case kWasmFunctionNominalCode:
+      case kWasmArrayNominalCode:
+      case kWasmStructNominalCode:
+        errorf(pc() - 1,
+               "mixing nominal and isorecursive types is not allowed");
+        return {};
+      default:
+        errorf(pc() - 1, "unknown type form: %d", kind);
+        return {};
+    }
+  }
+
+  TypeDefinition consume_nominal_type_definition() {
+    DCHECK(enabled_features_.has_gc());
+    size_t num_types = module_->types.size();
+    uint8_t kind = consume_u8("type kind");
+    switch (kind) {
+      case kWasmFunctionNominalCode: {
+        const FunctionSig* sig = consume_sig(module_->signature_zone.get());
+        uint32_t super_index = kNoSuperType;
+        HeapType super_type = consume_super_type();
+        if (super_type.is_index()) {
+          super_index = super_type.representation();
+        } else if (V8_UNLIKELY(super_type != HeapType::kFunc)) {
+          errorf(pc() - 1, "type %zu: invalid supertype %d", num_types,
+                 super_type.code());
+          return {};
+        }
+        return {sig, super_index};
+      }
+      case kWasmStructNominalCode: {
+        const StructType* type = consume_struct(module_->signature_zone.get());
+        uint32_t super_index = kNoSuperType;
+        HeapType super_type = consume_super_type();
+        if (super_type.is_index()) {
+          super_index = super_type.representation();
+        } else if (V8_UNLIKELY(super_type != HeapType::kData)) {
+          errorf(pc() - 1, "type %zu: invalid supertype %d", num_types,
+                 super_type.code());
+          return {};
+        }
+        return {type, super_index};
+      }
+      case kWasmArrayNominalCode: {
+        const ArrayType* type = consume_array(module_->signature_zone.get());
+        uint32_t super_index = kNoSuperType;
+        HeapType super_type = consume_super_type();
+        if (super_type.is_index()) {
+          super_index = super_type.representation();
+        } else if (V8_UNLIKELY(super_type != HeapType::kData)) {
+          errorf(pc() - 1, "type %zu: invalid supertype %d", num_types,
+                 super_type.code());
+          return {};
+        }
+        return {type, super_index};
+      }
+      case kWasmFunctionTypeCode:
+      case kWasmArrayTypeCode:
+      case kWasmStructTypeCode:
+      case kWasmSubtypeCode:
+      case kWasmRecursiveTypeGroupCode:
+        errorf(pc() - 1,
+               "mixing nominal and isorecursive types is not allowed");
+        return {};
+      default:
+        errorf(pc() - 1, "unknown type form: %d", kind);
+        return {};
+    }
+  }
+
+  TypeDefinition consume_subtype_definition() {
+    DCHECK(enabled_features_.has_gc());
+    uint8_t kind = read_u8<Decoder::kFullValidation>(pc(), "type kind");
+    if (kind == kWasmSubtypeCode) {
+      consume_bytes(1, "subtype definition");
+      constexpr uint32_t kMaximumSupertypes = 1;
+      uint32_t supertype_count =
+          consume_count("supertype count", kMaximumSupertypes);
+      uint32_t supertype =
+          supertype_count == 1 ? consume_u32v("supertype") : kNoSuperType;
+      if (V8_UNLIKELY(supertype >= module_->types.capacity())) {
+        errorf(pc(), "type %zu: invalid supertype %d", module_->types.size(),
+               supertype);
+        return {};
+      }
+      TypeDefinition type = consume_base_type_definition();
+      type.supertype = supertype;
+      return type;
+    } else {
+      return consume_base_type_definition();
+    }
+  }
+
   void DecodeTypeSection() {
     uint32_t types_count = consume_count("types count", kV8MaxWasmTypes);
-    module_->types.reserve(types_count);
-    for (uint32_t i = 0; ok() && i < types_count; ++i) {
-      TRACE("DecodeSignature[%d] module+%d\n", i,
-            static_cast<int>(pc_ - start_));
-      uint8_t kind = consume_u8("type kind");
-      switch (kind) {
-        case kWasmFunctionTypeCode:
-        case kWasmFunctionSubtypeCode: {
-          const FunctionSig* s = consume_sig(module_->signature_zone.get());
-          uint32_t super_index = kNoSuperType;
-          if (kind == kWasmFunctionSubtypeCode) {
-            if (!enabled_features_.has_gc()) {
-              errorf(pc(),
-                     "invalid function type definition, enable with "
-                     "--experimental-wasm-gc");
-              break;
-            }
-            HeapType super_type = consume_super_type();
-            if (super_type == HeapType::kFunc) {
-              super_index = kGenericSuperType;
-            } else if (super_type.is_index()) {
-              super_index = super_type.representation();
-            } else {
-              errorf(pc(), "type %d: invalid supertype %d", i,
-                     super_type.code());
-              break;
-            }
-          }
-          module_->add_signature(s, super_index);
-          break;
+
+    // Non wasm-gc type section decoding.
+    if (!enabled_features_.has_gc()) {
+      for (uint32_t i = 0; ok() && i < types_count; ++i) {
+        TRACE("DecodeSignature[%d] module+%d\n", i,
+              static_cast<int>(pc_ - start_));
+        expect_u8("signature definition", kWasmFunctionTypeCode);
+        const FunctionSig* sig = consume_sig(module_->signature_zone.get());
+        if (!ok()) break;
+        module_->add_signature(sig, kNoSuperType);
+      }
+      return;
+    }
+
+    if (types_count > 0) {
+      uint8_t first_type_opcode = this->read_u8<Decoder::kFullValidation>(pc());
+      if (first_type_opcode == kWasmFunctionNominalCode ||
+          first_type_opcode == kWasmStructNominalCode ||
+          first_type_opcode == kWasmArrayNominalCode) {
+        // wasm-gc nominal type section decoding.
+        // In a nominal module, all types belong in the same recursive group. We
+        // use the type vector's capacity to mark the end of the current
+        // recursive group.
+        module_->types.reserve(types_count);
+        for (uint32_t i = 0; ok() && i < types_count; ++i) {
+          TRACE("DecodeType[%d] module+%d\n", i,
+                static_cast<int>(pc_ - start_));
+          TypeDefinition type = consume_nominal_type_definition();
+          if (ok()) module_->add_type(type);
         }
-        case kWasmStructTypeCode:
-        case kWasmStructSubtypeCode: {
-          if (!enabled_features_.has_gc()) {
-            errorf(pc(),
-                   "invalid struct type definition, enable with "
-                   "--experimental-wasm-gc");
-            break;
-          }
-          const StructType* s = consume_struct(module_->signature_zone.get());
-          uint32_t super_index = kNoSuperType;
-          if (kind == kWasmStructSubtypeCode) {
-            HeapType super_type = consume_super_type();
-            if (super_type == HeapType::kData) {
-              super_index = kGenericSuperType;
-            } else if (super_type.is_index()) {
-              super_index = super_type.representation();
-            } else {
-              errorf(pc(), "type %d: invalid supertype %d", i,
-                     super_type.code());
-              break;
+      } else {
+        // wasm-gc isorecursive type section decoding.
+        for (uint32_t i = 0; ok() && i < types_count; ++i) {
+          TRACE("DecodeType[%d] module+%d\n", i,
+                static_cast<int>(pc_ - start_));
+          uint8_t kind = read_u8<Decoder::kFullValidation>(pc(), "type kind");
+          if (kind == kWasmRecursiveTypeGroupCode) {
+            consume_bytes(1, "rec. group definition");
+            uint32_t group_size =
+                consume_count("recursive group size", kV8MaxWasmTypes);
+            if (module_->types.size() + group_size > kV8MaxWasmTypes) {
+              errorf(pc(), "Type definition count exeeds maximum %zu",
+                     kV8MaxWasmTypes);
+              return;
             }
-          }
-          module_->add_struct_type(s, super_index);
-          // TODO(7748): Should we canonicalize struct types, like
-          // {signature_map} does for function signatures?
-          break;
-        }
-        case kWasmArrayTypeCode:
-        case kWasmArraySubtypeCode: {
-          if (!enabled_features_.has_gc()) {
-            errorf(pc(),
-                   "invalid array type definition, enable with "
-                   "--experimental-wasm-gc");
-            break;
-          }
-          const ArrayType* type = consume_array(module_->signature_zone.get());
-          uint32_t super_index = kNoSuperType;
-          if (kind == kWasmArraySubtypeCode) {
-            HeapType super_type = consume_super_type();
-            if (super_type == HeapType::kData) {
-              super_index = kGenericSuperType;
-            } else if (super_type.is_index()) {
-              super_index = super_type.representation();
-            } else {
-              errorf(pc(), "type %d: invalid supertype %d", i,
-                     super_type.code());
-              break;
+            // Reserve space for the current recursive group, so we are
+            // allowed to reference its elements.
+            module_->types.reserve(module_->types.size() + group_size);
+            for (uint32_t i = 0; i < group_size; i++) {
+              TypeDefinition type = consume_subtype_definition();
+              if (ok()) module_->add_type(type);
             }
+          } else {
+            TypeDefinition type = consume_subtype_definition();
+            if (ok()) module_->add_type(type);
           }
-          module_->add_array_type(type, super_index);
-          break;
         }
-        default:
-          errorf(pc(), "unknown type form: %d", kind);
-          break;
       }
     }
+
     // Check validity of explicitly defined supertypes.
     const WasmModule* module = module_.get();
     for (uint32_t i = 0; ok() && i < types_count; ++i) {
       uint32_t explicit_super = module_->supertype(i);
       if (explicit_super == kNoSuperType) continue;
-      if (explicit_super == kGenericSuperType) continue;
       DCHECK_LT(explicit_super, types_count);  // {consume_super_type} checks.
-      // Only types that have an explicit supertype themselves can be explicit
-      // supertypes of other types.
-      if (!module->has_supertype(explicit_super)) {
-        errorf("type %d has invalid explicit supertype %d", i, explicit_super);
-        continue;
-      }
       int depth = GetSubtypingDepth(module, i);
       if (depth > static_cast<int>(kV8MaxRttSubtypingDepth)) {
         errorf("type %d: subtyping depth is greater than allowed", i);
@@ -666,23 +740,10 @@
         errorf("type %d: cyclic inheritance", i);
         continue;
       }
-      switch (module_->types[i].kind) {
-        case TypeDefinition::kStruct:
-          if (!module->has_struct(explicit_super)) break;
-          if (!StructIsSubtypeOf(i, explicit_super, module, module)) break;
-          continue;
-        case TypeDefinition::kArray:
-          if (!module->has_array(explicit_super)) break;
-          if (!ArrayIsSubtypeOf(i, explicit_super, module, module)) break;
-          continue;
-        case TypeDefinition::kFunction:
-          if (!module->has_signature(explicit_super)) break;
-          if (!FunctionIsSubtypeOf(i, explicit_super, module, module)) break;
-          continue;
-        default:
-          UNREACHABLE();
+      if (!ValidSubtypeDefinition(i, explicit_super, module, module)) {
+        errorf("type %d has invalid explicit supertype %d", i, explicit_super);
+        continue;
       }
-      errorf("type %d has invalid explicit supertype %d", i, explicit_super);
     }
     module_->signature_map.Freeze();
   }
@@ -735,10 +796,9 @@
           const byte* type_position = pc();
           ValueType type = consume_reference_type();
           if (!WasmTable::IsValidTableType(type, module_.get())) {
-            error(
-                type_position,
-                "Currently, only externref and function references are allowed "
-                "as table types");
+            error(type_position,
+                  "Currently, only externref and function references are "
+                  "allowed as table types");
             break;
           }
           table->type = type;
@@ -1234,8 +1294,8 @@
       hint.top_tier =
           static_cast<WasmCompilationHintTier>(hint_byte >> 4 & 0x3);
 
-      // Ensure that the top tier never downgrades a compilation result.
-      // If baseline and top tier are the same compilation will be invoked only
+      // Ensure that the top tier never downgrades a compilation result. If
+      // baseline and top tier are the same compilation will be invoked only
       // once.
       if (hint.top_tier < hint.baseline_tier &&
           hint.top_tier != WasmCompilationHintTier::kDefault) {
@@ -1377,10 +1437,10 @@
 
   ModuleResult FinishDecoding(bool verify_functions = true) {
     if (ok() && CheckMismatchedCounts()) {
-      // We calculate the global offsets here, because there may not be a global
-      // section and code section that would have triggered the calculation
-      // before. Even without the globals section the calculation is needed
-      // because globals can also be defined in the import section.
+      // We calculate the global offsets here, because there may not be a
+      // global section and code section that would have triggered the
+      // calculation before. Even without the globals section the calculation
+      // is needed because globals can also be defined in the import section.
       CalculateGlobalOffsets(module_.get());
     }
 
@@ -1526,9 +1586,9 @@
     }
   }
 
-  // Calculate individual global offsets and total size of globals table.
-  // This function should be called after all globals have been defined, which
-  // is after the import section and the global section, but before the global
+  // Calculate individual global offsets and total size of globals table. This
+  // function should be called after all globals have been defined, which is
+  // after the import section and the global section, but before the global
   // offsets are accessed, e.g. by the function compilers. The moment when this
   // function should be called is not well-defined, as the global section may
   // not exist. Therefore this function is called multiple times.
@@ -1745,6 +1805,7 @@
     }
   }
 
+  // Consumes a byte, and emits an error if it does not equal {expected}.
   bool expect_u8(const char* name, uint8_t expected) {
     const byte* pos = pc();
     uint8_t value = consume_u8(name);
diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc
index 3379286..aab0cc9 100644
--- a/src/wasm/module-instantiate.cc
+++ b/src/wasm/module-instantiate.cc
@@ -223,7 +223,7 @@
   // map for that supertype is created first, so that the supertypes list
   // that's cached on every RTT can be set up correctly.
   uint32_t supertype = module->supertype(type_index);
-  if (supertype != kNoSuperType && supertype != kGenericSuperType) {
+  if (supertype != kNoSuperType) {
     // This recursion is safe, because kV8MaxRttSubtypingDepth limits the
     // number of recursive steps, so we won't overflow the stack.
     CreateMapForType(isolate, module, supertype, instance, maps);
@@ -246,73 +246,6 @@
   maps->set(type_index, *map);
 }
 
-namespace {
-
-// TODO(7748): Consider storing this array in Maps'
-// "transitions_or_prototype_info" slot.
-// Also consider being more memory-efficient, e.g. use inline storage for
-// single entries, and/or adapt the growth strategy.
-class RttSubtypes : public ArrayList {
- public:
-  static Handle<ArrayList> Insert(Isolate* isolate, Handle<ArrayList> array,
-                                  uint32_t type_index, Handle<Map> sub_rtt) {
-    Handle<Smi> key = handle(Smi::FromInt(type_index), isolate);
-    return Add(isolate, array, key, sub_rtt);
-  }
-
-  static Map SearchSubtype(Handle<ArrayList> array, uint32_t type_index) {
-    // Linear search for now.
-    // TODO(7748): Consider keeping the array sorted and using binary search
-    // here, if empirical data indicates that that would be worthwhile.
-    int count = array->Length();
-    for (int i = 0; i < count; i += 2) {
-      if (Smi::cast(array->Get(i)).value() == static_cast<int>(type_index)) {
-        return Map::cast(array->Get(i + 1));
-      }
-    }
-    return {};
-  }
-};
-
-}  // namespace
-
-Handle<Map> AllocateSubRtt(Isolate* isolate,
-                           Handle<WasmInstanceObject> instance, uint32_t type,
-                           Handle<Map> parent, WasmRttSubMode mode) {
-  DCHECK(parent->IsWasmStructMap() || parent->IsWasmArrayMap() ||
-         parent->IsWasmInternalFunctionMap());
-
-  const wasm::WasmModule* module = instance->module();
-  if (module->has_signature(type)) {
-    // Function references are implicitly allocated with their canonical rtt,
-    // and type checks against sub-rtts will always fail. Therefore, we simply
-    // create a fresh function map here.
-    return CreateFuncRefMap(isolate, module, Handle<Map>(), instance);
-  }
-  // If canonicalization is requested, check for an existing RTT first.
-  Handle<ArrayList> cache;
-  if (mode == WasmRttSubMode::kCanonicalize) {
-    cache = handle(parent->wasm_type_info().subtypes(), isolate);
-    Map maybe_cached = RttSubtypes::SearchSubtype(cache, type);
-    if (!maybe_cached.is_null()) return handle(maybe_cached, isolate);
-  }
-
-  // Allocate a fresh RTT otherwise.
-  Handle<Map> rtt;
-  if (module->has_struct(type)) {
-    rtt = wasm::CreateStructMap(isolate, module, type, parent, instance);
-  } else {
-    DCHECK(module->has_array(type));
-    rtt = wasm::CreateArrayMap(isolate, module, type, parent, instance);
-  }
-
-  if (mode == WasmRttSubMode::kCanonicalize) {
-    cache = RttSubtypes::Insert(isolate, cache, type, rtt);
-    parent->wasm_type_info().set_subtypes(*cache);
-  }
-  return rtt;
-}
-
 // A helper class to simplify instantiating a module from a module object.
 // It closes over the {Isolate}, the {ErrorThrower}, etc.
 class InstanceBuilder {
diff --git a/src/wasm/wasm-code-manager.h b/src/wasm/wasm-code-manager.h
index fec51c7..e9d3c0d 100644
--- a/src/wasm/wasm-code-manager.h
+++ b/src/wasm/wasm-code-manager.h
@@ -121,8 +121,6 @@
   V(WasmArrayCopy)                        \
   V(WasmArrayCopyWithChecks)              \
   V(WasmArrayInitFromData)                \
-  V(WasmAllocateRtt)                      \
-  V(WasmAllocateFreshRtt)                 \
   V(WasmAllocateStructWithRtt)            \
   V(WasmSubtypeCheck)                     \
   V(WasmOnStackReplace)                   \
diff --git a/src/wasm/wasm-constants.h b/src/wasm/wasm-constants.h
index 7c67a4c..0442f3d 100644
--- a/src/wasm/wasm-constants.h
+++ b/src/wasm/wasm-constants.h
@@ -49,13 +49,16 @@
   kDataRefCode = 0x67,
   kArrayRefCode = 0x66
 };
-// Binary encoding of other types.
+
+// Binary encoding of type definitions.
 constexpr uint8_t kWasmFunctionTypeCode = 0x60;
 constexpr uint8_t kWasmStructTypeCode = 0x5f;
 constexpr uint8_t kWasmArrayTypeCode = 0x5e;
-constexpr uint8_t kWasmFunctionSubtypeCode = 0x5d;
-constexpr uint8_t kWasmStructSubtypeCode = 0x5c;
-constexpr uint8_t kWasmArraySubtypeCode = 0x5b;
+constexpr uint8_t kWasmFunctionNominalCode = 0x5d;
+constexpr uint8_t kWasmStructNominalCode = 0x5c;
+constexpr uint8_t kWasmArrayNominalCode = 0x5b;
+constexpr uint8_t kWasmSubtypeCode = 0x50;
+constexpr uint8_t kWasmRecursiveTypeGroupCode = 0x49;
 
 // Binary encoding of import/export kinds.
 enum ImportExportKindCode : uint8_t {
diff --git a/src/wasm/wasm-init-expr.cc b/src/wasm/wasm-init-expr.cc
index db7e003..20cdf81 100644
--- a/src/wasm/wasm-init-expr.cc
+++ b/src/wasm/wasm-init-expr.cc
@@ -47,16 +47,6 @@
       return ValueType::Ref(immediate().index, kNonNullable);
     case kRttCanon:
       return ValueType::Rtt(immediate().heap_type, 0);
-    case kRttSub:
-    case kRttFreshSub: {
-      ValueType operand_type = (*operands())[0].type(module, enabled_features);
-      if (!operand_type.is_rtt()) return kWasmBottom;
-      if (operand_type.has_depth()) {
-        return ValueType::Rtt(immediate().heap_type, operand_type.depth() + 1);
-      } else {
-        return ValueType::Rtt(immediate().heap_type);
-      }
-    }
   }
 }
 
diff --git a/src/wasm/wasm-init-expr.h b/src/wasm/wasm-init-expr.h
index 6ae4e5f..44fadd4 100644
--- a/src/wasm/wasm-init-expr.h
+++ b/src/wasm/wasm-init-expr.h
@@ -43,8 +43,6 @@
     kArrayInit,
     kArrayInitStatic,
     kRttCanon,
-    kRttSub,
-    kRttFreshSub,
   };
 
   union Immediate {
@@ -149,25 +147,6 @@
     return expr;
   }
 
-  static WasmInitExpr RttSub(Zone* zone, uint32_t index,
-                             WasmInitExpr supertype) {
-    WasmInitExpr expr(
-        kRttSub, zone->New<ZoneVector<WasmInitExpr>>(
-                     std::initializer_list<WasmInitExpr>{supertype}, zone));
-    expr.immediate_.index = index;
-    return expr;
-  }
-
-  static WasmInitExpr RttFreshSub(Zone* zone, uint32_t index,
-                                  WasmInitExpr supertype) {
-    WasmInitExpr expr(
-        kRttFreshSub,
-        zone->New<ZoneVector<WasmInitExpr>>(
-            std::initializer_list<WasmInitExpr>{supertype}, zone));
-    expr.immediate_.index = index;
-    return expr;
-  }
-
   Immediate immediate() const { return immediate_; }
   Operator kind() const { return kind_; }
   const ZoneVector<WasmInitExpr>* operands() const { return operands_; }
@@ -211,10 +190,6 @@
           if (operands()[i] != other.operands()[i]) return false;
         }
         return true;
-      case kRttSub:
-      case kRttFreshSub:
-        return immediate().index == other.immediate().index &&
-               operands()[0] == other.operands()[0];
     }
   }
 
diff --git a/src/wasm/wasm-module-builder.cc b/src/wasm/wasm-module-builder.cc
index 33d6177..9d4495e 100644
--- a/src/wasm/wasm-module-builder.cc
+++ b/src/wasm/wasm-module-builder.cc
@@ -64,6 +64,13 @@
   signature_index_ = builder_->AddSignature(sig);
 }
 
+void WasmFunctionBuilder::SetSignature(uint32_t sig_index) {
+  DCHECK(!locals_.has_sig());
+  DCHECK_EQ(builder_->types_[sig_index].kind, TypeDefinition::kFunction);
+  signature_index_ = sig_index;
+  locals_.set_sig(builder_->types_[sig_index].function_sig);
+}
+
 uint32_t WasmFunctionBuilder::AddLocal(ValueType type) {
   DCHECK(locals_.has_sig());
   return locals_.AddLocals(1, type);
@@ -281,6 +288,12 @@
   return functions_.back();
 }
 
+WasmFunctionBuilder* WasmModuleBuilder::AddFunction(uint32_t sig_index) {
+  functions_.push_back(zone_->New<WasmFunctionBuilder>(this));
+  functions_.back()->SetSignature(sig_index);
+  return functions_.back();
+}
+
 void WasmModuleBuilder::AddDataSegment(const byte* data, uint32_t size,
                                        uint32_t dest) {
   data_segments_.push_back({ZoneVector<byte>(zone()), dest});
@@ -564,19 +577,6 @@
       buffer->write_u8(static_cast<uint8_t>(kExprRttCanon));
       buffer->write_i32v(static_cast<int32_t>(init.immediate().index));
       break;
-    case WasmInitExpr::kRttSub:
-    case WasmInitExpr::kRttFreshSub:
-      // The operand to rtt.sub must be emitted first.
-      WriteInitializerExpressionWithEnd(buffer, (*init.operands())[0],
-                                        kWasmBottom);
-      STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix);
-      STATIC_ASSERT((kExprRttFreshSub >> 8) == kGCPrefix);
-      buffer->write_u8(kGCPrefix);
-      buffer->write_u8(static_cast<uint8_t>(init.kind() == WasmInitExpr::kRttSub
-                                                ? kExprRttSub
-                                                : kExprRttFreshSub));
-      buffer->write_i32v(static_cast<int32_t>(init.immediate().index));
-      break;
   }
 }
 
@@ -597,13 +597,17 @@
     size_t start = EmitSection(kTypeSectionCode, buffer);
     buffer->write_size(types_.size());
 
+    // TODO(7748): Add support for recursive groups.
     for (const TypeDefinition& type : types_) {
-      bool has_super = type.supertype != kNoSuperType;
+      if (type.supertype != kNoSuperType) {
+        buffer->write_u8(kWasmSubtypeCode);
+        buffer->write_u8(1);  // The supertype count is always 1.
+        buffer->write_u32v(type.supertype);
+      }
       switch (type.kind) {
         case TypeDefinition::kFunction: {
           const FunctionSig* sig = type.function_sig;
-          buffer->write_u8(has_super ? kWasmFunctionSubtypeCode
-                                     : kWasmFunctionTypeCode);
+          buffer->write_u8(kWasmFunctionTypeCode);
           buffer->write_size(sig->parameter_count());
           for (auto param : sig->parameters()) {
             WriteValueType(buffer, param);
@@ -612,40 +616,23 @@
           for (auto ret : sig->returns()) {
             WriteValueType(buffer, ret);
           }
-          if (type.supertype == kGenericSuperType) {
-            buffer->write_u8(kFuncRefCode);
-          } else if (has_super) {
-            buffer->write_i32v(type.supertype);
-          }
           break;
         }
         case TypeDefinition::kStruct: {
           const StructType* struct_type = type.struct_type;
-          buffer->write_u8(has_super ? kWasmStructSubtypeCode
-                                     : kWasmStructTypeCode);
+          buffer->write_u8(kWasmStructTypeCode);
           buffer->write_size(struct_type->field_count());
           for (uint32_t i = 0; i < struct_type->field_count(); i++) {
             WriteValueType(buffer, struct_type->field(i));
             buffer->write_u8(struct_type->mutability(i) ? 1 : 0);
           }
-          if (type.supertype == kGenericSuperType) {
-            buffer->write_u8(kDataRefCode);
-          } else if (has_super) {
-            buffer->write_i32v(type.supertype);
-          }
           break;
         }
         case TypeDefinition::kArray: {
           const ArrayType* array_type = type.array_type;
-          buffer->write_u8(has_super ? kWasmArraySubtypeCode
-                                     : kWasmArrayTypeCode);
+          buffer->write_u8(kWasmArrayTypeCode);
           WriteValueType(buffer, array_type->element_type());
           buffer->write_u8(array_type->mutability() ? 1 : 0);
-          if (type.supertype == kGenericSuperType) {
-            buffer->write_u8(kDataRefCode);
-          } else if (has_super) {
-            buffer->write_i32v(type.supertype);
-          }
           break;
         }
       }
diff --git a/src/wasm/wasm-module-builder.h b/src/wasm/wasm-module-builder.h
index 80bbcaf..dc8ccf5 100644
--- a/src/wasm/wasm-module-builder.h
+++ b/src/wasm/wasm-module-builder.h
@@ -167,6 +167,7 @@
  public:
   // Building methods.
   void SetSignature(const FunctionSig* sig);
+  void SetSignature(uint32_t sig_index);
   uint32_t AddLocal(ValueType type);
   void EmitByte(byte b);
   void EmitI32V(int32_t val);
@@ -312,6 +313,7 @@
   uint32_t AddImport(base::Vector<const char> name, FunctionSig* sig,
                      base::Vector<const char> module = {});
   WasmFunctionBuilder* AddFunction(const FunctionSig* sig = nullptr);
+  WasmFunctionBuilder* AddFunction(uint32_t sig_index);
   uint32_t AddGlobal(ValueType type, bool mutability = true,
                      WasmInitExpr init = WasmInitExpr());
   uint32_t AddGlobalImport(base::Vector<const char> name, ValueType type,
diff --git a/src/wasm/wasm-module.cc b/src/wasm/wasm-module.cc
index 762e954..ea9a891 100644
--- a/src/wasm/wasm-module.cc
+++ b/src/wasm/wasm-module.cc
@@ -119,11 +119,8 @@
 int GetSubtypingDepth(const WasmModule* module, uint32_t type_index) {
   uint32_t starting_point = type_index;
   int depth = 0;
-  while ((type_index = module->supertype(type_index)) != kGenericSuperType) {
+  while ((type_index = module->supertype(type_index)) != kNoSuperType) {
     if (type_index == starting_point) return -1;  // Cycle detected.
-    // This is disallowed and will be rejected by validation, but might occur
-    // when this function is called.
-    if (type_index == kNoSuperType) break;
     depth++;
     if (depth > static_cast<int>(kV8MaxRttSubtypingDepth)) break;
   }
@@ -224,8 +221,6 @@
 WasmModule::WasmModule(std::unique_ptr<Zone> signature_zone)
     : signature_zone(std::move(signature_zone)) {}
 
-WasmModule::~WasmModule() { DeleteCachedTypeJudgementsForModule(this); }
-
 bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) {
   // TODO(wasm): Once wasm has its own CSP policy, we should introduce a
   // separate callback that includes information about the module about to be
diff --git a/src/wasm/wasm-module.h b/src/wasm/wasm-module.h
index 69c067e..d14040d 100644
--- a/src/wasm/wasm-module.h
+++ b/src/wasm/wasm-module.h
@@ -344,6 +344,9 @@
   std::unique_ptr<AsmJsOffsets> decoded_offsets_;
 };
 
+// Used as the supertype for a type at the top of the type hierarchy.
+constexpr uint32_t kNoSuperType = std::numeric_limits<uint32_t>::max();
+
 struct TypeDefinition {
   enum Kind { kFunction, kStruct, kArray };
 
@@ -353,6 +356,8 @@
       : struct_type(type), supertype(supertype), kind(kStruct) {}
   TypeDefinition(const ArrayType* type, uint32_t supertype)
       : array_type(type), supertype(supertype), kind(kArray) {}
+  TypeDefinition()
+      : function_sig(nullptr), supertype(kNoSuperType), kind(kFunction) {}
 
   union {
     const FunctionSig* function_sig;
@@ -386,11 +391,6 @@
 
 struct WasmTable;
 
-// End of a chain of explicit supertypes.
-constexpr uint32_t kGenericSuperType = std::numeric_limits<uint32_t>::max() - 1;
-// Used for types that have no explicit supertype.
-constexpr uint32_t kNoSuperType = std::numeric_limits<uint32_t>::max();
-
 // Static representation of a module.
 struct V8_EXPORT_PRIVATE WasmModule {
   std::unique_ptr<Zone> signature_zone;
@@ -418,11 +418,20 @@
   WireBytesRef code = {0, 0};
   WireBytesRef name = {0, 0};
 
+  void add_type(TypeDefinition type) {
+    types.push_back(type);
+    uint32_t canonical_id = type.kind == TypeDefinition::kFunction
+                                ? signature_map.FindOrInsert(*type.function_sig)
+                                : 0;
+    canonicalized_type_ids.push_back(canonical_id);
+  }
+
   bool has_type(uint32_t index) const { return index < types.size(); }
 
   void add_signature(const FunctionSig* sig, uint32_t supertype) {
     types.push_back(TypeDefinition(sig, supertype));
-    uint32_t canonical_id = sig ? signature_map.FindOrInsert(*sig) : 0;
+    DCHECK_NOT_NULL(sig);
+    uint32_t canonical_id = signature_map.FindOrInsert(*sig);
     canonicalized_type_ids.push_back(canonical_id);
   }
   bool has_signature(uint32_t index) const {
@@ -498,7 +507,6 @@
 
   explicit WasmModule(std::unique_ptr<Zone> signature_zone = nullptr);
   WasmModule(const WasmModule&) = delete;
-  ~WasmModule();
   WasmModule& operator=(const WasmModule&) = delete;
 };
 
diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h
index 2f780ec..1cea466 100644
--- a/src/wasm/wasm-objects.h
+++ b/src/wasm/wasm-objects.h
@@ -1042,9 +1042,6 @@
 Handle<Map> CreateArrayMap(Isolate* isolate, const WasmModule* module,
                            int array_index, MaybeHandle<Map> rtt_parent,
                            Handle<WasmInstanceObject> instance);
-Handle<Map> AllocateSubRtt(Isolate* isolate,
-                           Handle<WasmInstanceObject> instance, uint32_t type,
-                           Handle<Map> parent, WasmRttSubMode mode);
 
 bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
                        Handle<Object> value, ValueType expected,
diff --git a/src/wasm/wasm-opcodes-inl.h b/src/wasm/wasm-opcodes-inl.h
index 5ee594c..8e191e5 100644
--- a/src/wasm/wasm-opcodes-inl.h
+++ b/src/wasm/wasm-opcodes-inl.h
@@ -419,8 +419,6 @@
     CASE_OP(I31GetS, "i31.get_s")
     CASE_OP(I31GetU, "i31.get_u")
     CASE_OP(RttCanon, "rtt.canon")
-    CASE_OP(RttSub, "rtt.sub")
-    CASE_OP(RttFreshSub, "rtt.fresh_sub")
     CASE_OP(RefTest, "ref.test")
     CASE_OP(RefTestStatic, "ref.test_static")
     CASE_OP(RefCast, "ref.cast")
diff --git a/src/wasm/wasm-opcodes.h b/src/wasm/wasm-opcodes.h
index 03c382d..59ee645 100644
--- a/src/wasm/wasm-opcodes.h
+++ b/src/wasm/wasm-opcodes.h
@@ -686,13 +686,11 @@
   V(ArrayInitStatic, 0xfb1a, _)                                      \
   V(ArrayNew, 0xfb1b, _)                                             \
   V(ArrayNewDefault, 0xfb1c, _)                                      \
-  V(ArrayInitFromDataStatic, 0xfb1d, _) /* n/s - V8 experimental */  \
+  V(ArrayInitFromDataStatic, 0xfb1d, _) /* n/st - V8 experimental */ \
   V(I31New, 0xfb20, _)                                               \
   V(I31GetS, 0xfb21, _)                                              \
   V(I31GetU, 0xfb22, _)                                              \
   V(RttCanon, 0xfb30, _)                                             \
-  V(RttSub, 0xfb31, _)                                               \
-  V(RttFreshSub, 0xfb32, _) /* not standardized - V8 experimental */ \
   V(RefTest, 0xfb40, _)                                              \
   V(RefCast, 0xfb41, _)                                              \
   V(BrOnCast, 0xfb42, _)                                             \
diff --git a/src/wasm/wasm-subtyping.cc b/src/wasm/wasm-subtyping.cc
index 7a0454e5..6f02324 100644
--- a/src/wasm/wasm-subtyping.cc
+++ b/src/wasm/wasm-subtyping.cc
@@ -14,220 +14,21 @@
 
 namespace {
 
-using CacheKey =
-    std::tuple<uint32_t, uint32_t, const WasmModule*, const WasmModule*>;
-
-struct CacheKeyHasher {
-  size_t operator()(CacheKey key) const {
-    static constexpr size_t large_prime = 14887;
-    return std::get<0>(key) + (std::get<1>(key) * large_prime) +
-           (reinterpret_cast<size_t>(std::get<2>(key)) * large_prime *
-            large_prime) +
-           (reinterpret_cast<size_t>(std::get<3>(key)) * large_prime *
-            large_prime * large_prime);
-  }
-};
-
-class TypeJudgementCache {
- public:
-  TypeJudgementCache()
-      : zone_(new AccountingAllocator(), "type judgement zone"),
-        subtyping_cache_(&zone_),
-        type_equivalence_cache_(&zone_) {}
-
-  static TypeJudgementCache* instance() {
-    static base::LazyInstance<TypeJudgementCache>::type instance_ =
-        LAZY_INSTANCE_INITIALIZER;
-    return instance_.Pointer();
-  }
-
-  base::RecursiveMutex* type_cache_mutex() { return &type_cache_mutex_; }
-  bool is_cached_subtype(uint32_t subtype, uint32_t supertype,
-                         const WasmModule* sub_module,
-                         const WasmModule* super_module) const {
-    return subtyping_cache_.count(std::make_tuple(
-               subtype, supertype, sub_module, super_module)) == 1;
-  }
-  void cache_subtype(uint32_t subtype, uint32_t supertype,
-                     const WasmModule* sub_module,
-                     const WasmModule* super_module) {
-    subtyping_cache_.emplace(subtype, supertype, sub_module, super_module);
-  }
-  void uncache_subtype(uint32_t subtype, uint32_t supertype,
-                       const WasmModule* sub_module,
-                       const WasmModule* super_module) {
-    subtyping_cache_.erase(
-        std::make_tuple(subtype, supertype, sub_module, super_module));
-  }
-  bool is_cached_equivalent_type(uint32_t type1, uint32_t type2,
-                                 const WasmModule* module1,
-                                 const WasmModule* module2) const {
-    if (type1 > type2) std::swap(type1, type2);
-    if (reinterpret_cast<uintptr_t>(module1) >
-        reinterpret_cast<uintptr_t>(module2)) {
-      std::swap(module1, module2);
-    }
-    return type_equivalence_cache_.count(
-               std::make_tuple(type1, type2, module1, module2)) == 1;
-  }
-  void cache_type_equivalence(uint32_t type1, uint32_t type2,
-                              const WasmModule* module1,
-                              const WasmModule* module2) {
-    if (type1 > type2) std::swap(type1, type2);
-    if (reinterpret_cast<uintptr_t>(module1) >
-        reinterpret_cast<uintptr_t>(module2)) {
-      std::swap(module1, module2);
-    }
-    type_equivalence_cache_.emplace(type1, type2, module1, module2);
-  }
-  void uncache_type_equivalence(uint32_t type1, uint32_t type2,
-                                const WasmModule* module1,
-                                const WasmModule* module2) {
-    if (type1 > type2) std::swap(type1, type2);
-    if (reinterpret_cast<uintptr_t>(module1) >
-        reinterpret_cast<uintptr_t>(module2)) {
-      std::swap(module1, module2);
-    }
-    type_equivalence_cache_.erase(
-        std::make_tuple(type1, type2, module1, module2));
-  }
-  void delete_module(const WasmModule* module) {
-    for (auto iterator = type_equivalence_cache_.begin();
-         iterator != type_equivalence_cache_.end();) {
-      if (std::get<2>(*iterator) == module ||
-          std::get<3>(*iterator) == module) {
-        iterator = type_equivalence_cache_.erase(iterator);
-      } else {
-        iterator++;
-      }
-    }
-    for (auto iterator = subtyping_cache_.begin();
-         iterator != subtyping_cache_.end();) {
-      if (std::get<2>(*iterator) == module ||
-          std::get<3>(*iterator) == module) {
-        iterator = subtyping_cache_.erase(iterator);
-      } else {
-        iterator++;
-      }
-    }
-  }
-
- private:
-  Zone zone_;
-  ZoneUnorderedSet<CacheKey, CacheKeyHasher>
-      // Cache for discovered subtyping pairs.
-      subtyping_cache_,
-      // Cache for discovered equivalent type pairs.
-      // Indexes and modules are stored in increasing order.
-      type_equivalence_cache_;
-  // The above two caches are used from background compile jobs, so they
-  // must be protected from concurrent modifications:
-  base::RecursiveMutex type_cache_mutex_;
-};
-
-bool ArrayEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
-                            const WasmModule* module1,
-                            const WasmModule* module2) {
-  const ArrayType* sub_array = module1->types[type_index_1].array_type;
-  const ArrayType* super_array = module2->types[type_index_2].array_type;
-  if (sub_array->mutability() != super_array->mutability()) return false;
-
-  // Temporarily cache type equivalence for the recursive call.
-  TypeJudgementCache::instance()->cache_type_equivalence(
-      type_index_1, type_index_2, module1, module2);
-  if (EquivalentTypes(sub_array->element_type(), super_array->element_type(),
-                      module1, module2)) {
-    return true;
-  } else {
-    TypeJudgementCache::instance()->uncache_type_equivalence(
-        type_index_1, type_index_2, module1, module2);
-    // TODO(7748): Consider caching negative results as well.
-    return false;
-  }
-}
-
-bool StructEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
-                             const WasmModule* module1,
-                             const WasmModule* module2) {
-  const StructType* sub_struct = module1->types[type_index_1].struct_type;
-  const StructType* super_struct = module2->types[type_index_2].struct_type;
-
-  if (sub_struct->field_count() != super_struct->field_count()) {
-    return false;
-  }
-
-  // Temporarily cache type equivalence for the recursive call.
-  TypeJudgementCache::instance()->cache_type_equivalence(
-      type_index_1, type_index_2, module1, module2);
-  for (uint32_t i = 0; i < sub_struct->field_count(); i++) {
-    if (sub_struct->mutability(i) != super_struct->mutability(i) ||
-        !EquivalentTypes(sub_struct->field(i), super_struct->field(i), module1,
-                         module2)) {
-      TypeJudgementCache::instance()->uncache_type_equivalence(
-          type_index_1, type_index_2, module1, module2);
-      return false;
-    }
-  }
-  return true;
-}
-
-bool FunctionEquivalentIndices(uint32_t type_index_1, uint32_t type_index_2,
-                               const WasmModule* module1,
-                               const WasmModule* module2) {
-  const FunctionSig* sig1 = module1->types[type_index_1].function_sig;
-  const FunctionSig* sig2 = module2->types[type_index_2].function_sig;
-
-  if (sig1->parameter_count() != sig2->parameter_count() ||
-      sig1->return_count() != sig2->return_count()) {
-    return false;
-  }
-
-  auto iter1 = sig1->all();
-  auto iter2 = sig2->all();
-
-  // Temporarily cache type equivalence for the recursive call.
-  TypeJudgementCache::instance()->cache_type_equivalence(
-      type_index_1, type_index_2, module1, module2);
-  for (int i = 0; i < iter1.size(); i++) {
-    if (!EquivalentTypes(iter1[i], iter2[i], module1, module2)) {
-      TypeJudgementCache::instance()->uncache_type_equivalence(
-          type_index_1, type_index_2, module1, module2);
-      return false;
-    }
-  }
-  return true;
-}
-
 V8_INLINE bool EquivalentIndices(uint32_t index1, uint32_t index2,
                                  const WasmModule* module1,
                                  const WasmModule* module2) {
   DCHECK(index1 != index2 || module1 != module2);
-  TypeDefinition::Kind kind1 = module1->types[index1].kind;
-
-  if (kind1 != module2->types[index2].kind) return false;
-
-  base::RecursiveMutexGuard type_cache_access(
-      TypeJudgementCache::instance()->type_cache_mutex());
-  if (TypeJudgementCache::instance()->is_cached_equivalent_type(
-          index1, index2, module1, module2)) {
-    return true;
-  }
-
-  if (kind1 == TypeDefinition::kStruct) {
-    return StructEquivalentIndices(index1, index2, module1, module2);
-  } else if (kind1 == TypeDefinition::kArray) {
-    return ArrayEquivalentIndices(index1, index2, module1, module2);
-  } else {
-    DCHECK_EQ(kind1, TypeDefinition::kFunction);
-    return FunctionEquivalentIndices(index1, index2, module1, module2);
-  }
+  // TODO(7748): Canonicalize types.
+  return false;
 }
 
-}  // namespace
+bool ValidStructSubtypeDefinition(uint32_t subtype_index,
+                                  uint32_t supertype_index,
+                                  const WasmModule* sub_module,
+                                  const WasmModule* super_module) {
+  // TODO(7748): Figure out the cross-module story.
+  if (sub_module != super_module) return false;
 
-bool StructIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                       const WasmModule* sub_module,
-                       const WasmModule* super_module) {
   const StructType* sub_struct = sub_module->types[subtype_index].struct_type;
   const StructType* super_struct =
       super_module->types[supertype_index].struct_type;
@@ -236,10 +37,6 @@
     return false;
   }
 
-  if (!sub_module->has_supertype(subtype_index)) {
-    TypeJudgementCache::instance()->cache_subtype(
-        subtype_index, supertype_index, sub_module, super_module);
-  }
   for (uint32_t i = 0; i < super_struct->field_count(); i++) {
     bool sub_mut = sub_struct->mutability(i);
     bool super_mut = super_struct->mutability(i);
@@ -249,48 +46,41 @@
                           sub_module, super_module)) ||
         (!sub_mut && !IsSubtypeOf(sub_struct->field(i), super_struct->field(i),
                                   sub_module, super_module))) {
-      TypeJudgementCache::instance()->uncache_subtype(
-          subtype_index, supertype_index, sub_module, super_module);
       return false;
     }
   }
   return true;
 }
 
-bool ArrayIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                      const WasmModule* sub_module,
-                      const WasmModule* super_module) {
+bool ValidArraySubtypeDefinition(uint32_t subtype_index,
+                                 uint32_t supertype_index,
+                                 const WasmModule* sub_module,
+                                 const WasmModule* super_module) {
+  // TODO(7748): Figure out the cross-module story.
+  if (sub_module != super_module) return false;
+
   const ArrayType* sub_array = sub_module->types[subtype_index].array_type;
   const ArrayType* super_array =
       super_module->types[supertype_index].array_type;
   bool sub_mut = sub_array->mutability();
   bool super_mut = super_array->mutability();
-  if (!sub_module->has_supertype(subtype_index)) {
-    TypeJudgementCache::instance()->cache_subtype(
-        subtype_index, supertype_index, sub_module, super_module);
-  }
-  if (sub_mut != super_mut ||
-      (sub_mut &&
-       !EquivalentTypes(sub_array->element_type(), super_array->element_type(),
-                        sub_module, super_module)) ||
-      (!sub_mut &&
-       !IsSubtypeOf(sub_array->element_type(), super_array->element_type(),
-                    sub_module, super_module))) {
-    TypeJudgementCache::instance()->uncache_subtype(
-        subtype_index, supertype_index, sub_module, super_module);
-    return false;
-  } else {
-    return true;
-  }
+
+  return (sub_mut && super_mut &&
+          EquivalentTypes(sub_array->element_type(),
+                          super_array->element_type(), sub_module,
+                          super_module)) ||
+         (!sub_mut && !super_mut &&
+          IsSubtypeOf(sub_array->element_type(), super_array->element_type(),
+                      sub_module, super_module));
 }
 
-bool FunctionIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                         const WasmModule* sub_module,
-                         const WasmModule* super_module) {
-  if (!FLAG_experimental_wasm_gc) {
-    return FunctionEquivalentIndices(subtype_index, supertype_index, sub_module,
-                                     super_module);
-  }
+bool ValidFunctionSubtypeDefinition(uint32_t subtype_index,
+                                    uint32_t supertype_index,
+                                    const WasmModule* sub_module,
+                                    const WasmModule* super_module) {
+  // TODO(7748): Figure out the cross-module story.
+  if (sub_module != super_module) return false;
+
   const FunctionSig* sub_func = sub_module->types[subtype_index].function_sig;
   const FunctionSig* super_func =
       super_module->types[supertype_index].function_sig;
@@ -300,17 +90,10 @@
     return false;
   }
 
-  if (!sub_module->has_supertype(subtype_index)) {
-    TypeJudgementCache::instance()->cache_subtype(
-        subtype_index, supertype_index, sub_module, super_module);
-  }
-
   for (uint32_t i = 0; i < sub_func->parameter_count(); i++) {
     // Contravariance for params.
     if (!IsSubtypeOf(super_func->parameters()[i], sub_func->parameters()[i],
                      super_module, sub_module)) {
-      TypeJudgementCache::instance()->uncache_subtype(
-          subtype_index, supertype_index, sub_module, super_module);
       return false;
     }
   }
@@ -318,8 +101,6 @@
     // Covariance for returns.
     if (!IsSubtypeOf(sub_func->returns()[i], super_func->returns()[i],
                      sub_module, super_module)) {
-      TypeJudgementCache::instance()->uncache_subtype(
-          subtype_index, supertype_index, sub_module, super_module);
       return false;
     }
   }
@@ -327,6 +108,27 @@
   return true;
 }
 
+}  // namespace
+
+bool ValidSubtypeDefinition(uint32_t subtype_index, uint32_t supertype_index,
+                            const WasmModule* sub_module,
+                            const WasmModule* super_module) {
+  TypeDefinition::Kind sub_kind = sub_module->types[subtype_index].kind;
+  TypeDefinition::Kind super_kind = super_module->types[supertype_index].kind;
+  if (sub_kind != super_kind) return false;
+  switch (sub_kind) {
+    case TypeDefinition::kFunction:
+      return ValidFunctionSubtypeDefinition(subtype_index, supertype_index,
+                                            sub_module, super_module);
+    case TypeDefinition::kStruct:
+      return ValidStructSubtypeDefinition(subtype_index, supertype_index,
+                                          sub_module, super_module);
+    case TypeDefinition::kArray:
+      return ValidArraySubtypeDefinition(subtype_index, supertype_index,
+                                         sub_module, super_module);
+  }
+}
+
 V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
     ValueType subtype, ValueType supertype, const WasmModule* sub_module,
     const WasmModule* super_module) {
@@ -426,49 +228,15 @@
   // equality; here we catch (ref $x) being a subtype of (ref null $x).
   if (sub_module == super_module && sub_index == super_index) return true;
 
-  TypeDefinition::Kind sub_kind = sub_module->types[sub_index].kind;
+  // TODO(7748): Figure out cross-module story.
+  if (sub_module != super_module) return false;
 
-  if (sub_kind != super_module->types[super_index].kind) return false;
-
-  // Types with explicit supertypes just check those.
-  if (sub_module->has_supertype(sub_index)) {
-    // TODO(7748): Figure out cross-module story.
-    if (sub_module != super_module) return false;
-
-    uint32_t explicit_super = sub_module->supertype(sub_index);
-    while (true) {
-      if (explicit_super == super_index) return true;
-      // Reached the end of the explicitly defined inheritance chain.
-      if (explicit_super == kGenericSuperType) return false;
-      // Types without explicit supertype can't occur here, they would have
-      // failed validation.
-      DCHECK_NE(explicit_super, kNoSuperType);
-      explicit_super = sub_module->supertype(explicit_super);
-    }
-  } else {
-    // A structural type (without explicit supertype) is never a subtype of
-    // a nominal type (with explicit supertype).
-    if (super_module->has_supertype(super_index)) return false;
-  }
-
-  // Accessing the caches for subtyping and equivalence from multiple background
-  // threads is protected by a lock.
-  base::RecursiveMutexGuard type_cache_access(
-      TypeJudgementCache::instance()->type_cache_mutex());
-  if (TypeJudgementCache::instance()->is_cached_subtype(
-          sub_index, super_index, sub_module, super_module)) {
-    return true;
-  }
-
-  switch (sub_kind) {
-    case TypeDefinition::kStruct:
-      return StructIsSubtypeOf(sub_index, super_index, sub_module,
-                               super_module);
-    case TypeDefinition::kArray:
-      return ArrayIsSubtypeOf(sub_index, super_index, sub_module, super_module);
-    case TypeDefinition::kFunction:
-      return FunctionIsSubtypeOf(sub_index, super_index, sub_module,
-                                 super_module);
+  uint32_t explicit_super = sub_module->supertype(sub_index);
+  while (true) {
+    if (explicit_super == super_index) return true;
+    // Reached the end of the explicitly defined inheritance chain.
+    if (explicit_super == kNoSuperType) return false;
+    explicit_super = sub_module->supertype(explicit_super);
   }
 }
 
@@ -492,14 +260,6 @@
                            module2);
 }
 
-void DeleteCachedTypeJudgementsForModule(const WasmModule* module) {
-  // Accessing the caches for subtyping and equivalence from multiple background
-  // threads is protected by a lock.
-  base::RecursiveMutexGuard type_cache_access(
-      TypeJudgementCache::instance()->type_cache_mutex());
-  TypeJudgementCache::instance()->delete_module(module);
-}
-
 }  // namespace wasm
 }  // namespace internal
 }  // namespace v8
diff --git a/src/wasm/wasm-subtyping.h b/src/wasm/wasm-subtyping.h
index 96141b2..f4b4670 100644
--- a/src/wasm/wasm-subtyping.h
+++ b/src/wasm/wasm-subtyping.h
@@ -23,18 +23,12 @@
 
 // Checks if type1, defined in module1, is equivalent with type2, defined in
 // module2.
-// Type equivalence (~) is described by the following rules (structural
-// equivalence):
+// Type equivalence (~) is described by the following rules:
 // - Two numeric types are equivalent iff they are equal.
 // - T(ht1) ~ T(ht2) iff ht1 ~ ht2 for T in {ref, optref, rtt}.
-// - rtt(d1, ht1) ~ rtt(d2, ht2) iff (d1 = d2 and ht1 ~ ht2).
 // Equivalence of heap types ht1 ~ ht2 is defined as follows:
-// - Two generic heap types are equivalent iff they are equal.
-// - Two structs are equivalent iff they contain the same number of fields and
-//   these are pairwise equivalent.
-// - Two functions are equivalent iff they contain the same number of parameters
-//   and returns and these are pairwise equivalent.
-// - Two arrays are equivalent iff their element types are equivalent.
+// - Two heap types are equivalent iff they are equal.
+// - TODO(7748): Implement iso-recursive canonicalization.
 V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
                                  const WasmModule* module1,
                                  const WasmModule* module2);
@@ -46,8 +40,7 @@
 // - numeric types are subtype-related iff they are equal.
 // - optref(ht1) <: optref(ht2) iff ht1 <: ht2.
 // - ref(ht1) <: ref/optref(ht2) iff ht1 <: ht2.
-// - rtt1(d, ht1) <: rtt2(ht2) iff ht1 ~ ht2.
-// - rtt1 <: rtt2 iff rtt1 ~ rtt2, otherwise
+// - rtt1 <: rtt2 iff rtt1 ~ rtt2.
 // For heap types, the following subtyping rules hold:
 // - The abstract heap types form the following type hierarchy:
 //           any
@@ -60,14 +53,8 @@
 // - All functions are subtypes of func.
 // - All structs are subtypes of data.
 // - All arrays are subtypes of array.
-// - Struct subtyping: Subtype must have at least as many fields as supertype,
-//   covariance for immutable fields, equivalence for mutable fields.
-// - Array subtyping: subtyping of respective element types for immutable
-//   arrays, equivalence of element types for mutable arrays.
-// - Function subtyping depends on the enabled wasm features: if
-//   --experimental-wasm-gc is enabled, then subtyping is computed
-//   contravariantly for parameter types and covariantly for return types.
-//   Otherwise, the subtyping relation is the equivalence relation.
+// - An indexed heap type h1 is a subtype of indexed heap type h2 if h2 is
+//   transitively an explicit supertype of h1.
 V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
                            const WasmModule* sub_module,
                            const WasmModule* super_module) {
@@ -97,24 +84,20 @@
                      ValueType::Ref(supertype_index, kNonNullable), module);
 }
 
-// Call this function in {module}'s destructor to avoid spurious cache hits in
-// case another WasmModule gets allocated in the same address later.
-void DeleteCachedTypeJudgementsForModule(const WasmModule* module);
-
-// Checks whether {subtype_index} is a legal subtype of {supertype_index}.
-// These are the same checks that {IsSubtypeOf} uses for comparing types without
-// explicitly given supertypes; for validating such explicit supertypes they
-// can be called directly.
-bool StructIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                       const WasmModule* sub_module,
-                       const WasmModule* super_module);
-bool ArrayIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                      const WasmModule* sub_module,
-                      const WasmModule* super_module);
-bool FunctionIsSubtypeOf(uint32_t subtype_index, uint32_t supertype_index,
-                         const WasmModule* sub_module,
-                         const WasmModule* super_module);
-
+// Checks whether {subtype_index} is valid as a declared subtype of
+// {supertype_index}.
+// - Both type must be of the same kind (function, struct, or array).
+// - Structs: Subtype must have at least as many fields as supertype,
+//   covariance for respective immutable fields, equivalence for respective
+//   mutable fields.
+// - Arrays: subtyping of respective element types for immutable arrays,
+//   equivalence of element types for mutable arrays.
+// - Functions: equal number of parameter and return types. Contravariance for
+//   respective parameter types, covariance for respective return types.
+V8_EXPORT_PRIVATE bool ValidSubtypeDefinition(uint32_t subtype_index,
+                                              uint32_t supertype_index,
+                                              const WasmModule* sub_module,
+                                              const WasmModule* super_module);
 }  // namespace wasm
 }  // namespace internal
 }  // namespace v8
diff --git a/test/cctest/wasm/test-gc.cc b/test/cctest/wasm/test-gc.cc
index f7ca97e..5a94bb0 100644
--- a/test/cctest/wasm/test-gc.cc
+++ b/test/cctest/wasm/test-gc.cc
@@ -54,12 +54,13 @@
 
   byte DefineFunction(FunctionSig* sig, std::initializer_list<ValueType> locals,
                       std::initializer_list<byte> code) {
-    WasmFunctionBuilder* fun = builder_.AddFunction(sig);
-    for (ValueType local : locals) {
-      fun->AddLocal(local);
-    }
-    fun->EmitCode(code.begin(), static_cast<uint32_t>(code.size()));
-    return fun->func_index();
+    return DefineFunctionImpl(builder_.AddFunction(sig), locals, code);
+  }
+
+  byte DefineFunction(uint32_t sig_index,
+                      std::initializer_list<ValueType> locals,
+                      std::initializer_list<byte> code) {
+    return DefineFunctionImpl(builder_.AddFunction(sig_index), locals, code);
   }
 
   void DefineExportedFunction(const char* name, FunctionSig* sig,
@@ -94,7 +95,9 @@
                                  supertype);
   }
 
-  byte DefineSignature(FunctionSig* sig) { return builder_.AddSignature(sig); }
+  byte DefineSignature(FunctionSig* sig, uint32_t supertype = kNoSuperType) {
+    return builder_.AddSignature(sig, supertype);
+  }
 
   byte DefineTable(ValueType type, uint32_t min_size, uint32_t max_size) {
     return builder_.AddTable(type, min_size, max_size);
@@ -180,6 +183,16 @@
   const FlagScope<bool> flag_liftoff_only;
   const FlagScope<bool> flag_tierup;
 
+  byte DefineFunctionImpl(WasmFunctionBuilder* fun,
+                          std::initializer_list<ValueType> locals,
+                          std::initializer_list<byte> code) {
+    for (ValueType local : locals) {
+      fun->AddLocal(local);
+    }
+    fun->EmitCode(code.begin(), static_cast<uint32_t>(code.size()));
+    return fun->func_index();
+  }
+
   void CheckResultImpl(uint32_t function_index, const FunctionSig* sig,
                        CWasmArgumentsPacker* packer, int32_t expected) {
     CallFunctionImpl(function_index, sig, packer);
@@ -457,35 +470,27 @@
   WasmGCTester tester(execution_tier);
 
   const byte supertype_index = tester.DefineStruct({F(kWasmI32, true)});
-  const byte subtype1_index =
-      tester.DefineStruct({F(kWasmI32, true), F(kWasmF32, false)});
-  const byte subtype2_index =
-      tester.DefineStruct({F(kWasmI32, true), F(kWasmI64, false)});
+  const byte subtype1_index = tester.DefineStruct(
+      {F(kWasmI32, true), F(kWasmF32, false)}, supertype_index);
+  const byte subtype2_index = tester.DefineStruct(
+      {F(kWasmI32, true), F(kWasmI64, false)}, supertype_index);
 
   const byte kTestSuccessful = tester.DefineFunction(
       tester.sigs.i_v(), {ValueType::Ref(supertype_index, kNullable)},
       {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-                             subtype1_index,
-                             WASM_RTT_SUB(subtype1_index,
-                                          WASM_RTT_CANON(supertype_index)))),
+                             subtype1_index, WASM_RTT_CANON(subtype1_index))),
        WASM_STRUCT_GET(
            subtype1_index, 0,
-           WASM_REF_CAST(
-               WASM_LOCAL_GET(0),
-               WASM_RTT_SUB(subtype1_index, WASM_RTT_CANON(supertype_index)))),
+           WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_RTT_CANON(subtype1_index))),
        WASM_END});
 
   const byte kTestFailed = tester.DefineFunction(
       tester.sigs.i_v(), {ValueType::Ref(supertype_index, kNullable)},
       {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-                             subtype1_index,
-                             WASM_RTT_SUB(subtype1_index,
-                                          WASM_RTT_CANON(supertype_index)))),
+                             subtype1_index, WASM_RTT_CANON(subtype1_index))),
        WASM_STRUCT_GET(
            subtype2_index, 0,
-           WASM_REF_CAST(
-               WASM_LOCAL_GET(0),
-               WASM_RTT_SUB(subtype2_index, WASM_RTT_CANON(supertype_index)))),
+           WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_RTT_CANON(subtype2_index))),
        WASM_END});
 
   tester.CompileModule();
@@ -496,8 +501,7 @@
 WASM_COMPILED_EXEC_TEST(RefCastStatic) {
   WasmGCTester tester(execution_tier);
 
-  const byte supertype_index =
-      tester.DefineStruct({F(kWasmI32, true)}, kGenericSuperType);
+  const byte supertype_index = tester.DefineStruct({F(kWasmI32, true)});
   const byte subtype1_index = tester.DefineStruct(
       {F(kWasmI32, true), F(kWasmF32, false)}, supertype_index);
   const byte subtype2_index = tester.DefineStruct(
@@ -526,8 +530,7 @@
   FlagScope<bool> scope(&FLAG_experimental_wasm_assume_ref_cast_succeeds, true);
   WasmGCTester tester(execution_tier);
 
-  const byte supertype_index =
-      tester.DefineStruct({F(kWasmI32, true)}, kGenericSuperType);
+  const byte supertype_index = tester.DefineStruct({F(kWasmI32, true)});
   const byte subtype1_index = tester.DefineStruct(
       {F(kWasmI32, true), F(kWasmF32, false)}, supertype_index);
   const byte subtype2_index = tester.DefineStruct(
@@ -1301,6 +1304,7 @@
   tester.CheckResult(kZeroLength, 0);  // Does not throw.
 }
 
+/* TODO(7748): This test requires for recursive groups.
 WASM_COMPILED_EXEC_TEST(NewDefault) {
   WasmGCTester tester(execution_tier);
   const byte struct_type = tester.DefineStruct(
@@ -1334,13 +1338,14 @@
   tester.CheckResult(allocate_struct, 0);
   tester.CheckResult(allocate_array, 0);
 }
+*/
 
 WASM_COMPILED_EXEC_TEST(BasicRtt) {
   WasmGCTester tester(execution_tier);
 
   const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
-  const byte subtype_index =
-      tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)});
+  const byte subtype_index = tester.DefineStruct(
+      {F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}, type_index);
 
   ValueType kRttTypes[] = {ValueType::Rtt(type_index, 0)};
   FunctionSig sig_t_v(1, 0, kRttTypes);
@@ -1354,8 +1359,7 @@
   const byte kRttCanon = tester.DefineFunction(
       &sig_t_v, {}, {WASM_RTT_CANON(type_index), kExprEnd});
   const byte kRttSub = tester.DefineFunction(
-      &sig_t2_v, {},
-      {WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)), kExprEnd});
+      &sig_t2_v, {}, {WASM_RTT_CANON(subtype_index), kExprEnd});
   const byte kStructWithRtt = tester.DefineFunction(
       &sig_q_v, {},
       {WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
@@ -1415,40 +1419,12 @@
   tester.CheckResult(kRefCast, 43);
 }
 
-WASM_COMPILED_EXEC_TEST(RttFreshSub) {
-  WasmGCTester tester(execution_tier);
-  const byte kType = tester.DefineStruct({F(wasm::kWasmI32, true)});
-  HeapType::Representation type_repr =
-      static_cast<HeapType::Representation>(kType);
-
-  const byte kRtt = tester.AddGlobal(
-      ValueType::Rtt(kType, 1), false,
-      WasmInitExpr::RttFreshSub(tester.zone(), type_repr,
-                                WasmInitExpr::RttCanon(type_repr)));
-
-  // A struct allocated with a fresh RTT does not match other fresh RTTs
-  // created for the same type.
-  const byte kRefTest = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(kType)},
-      {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_WITH_RTT(
-                             kType, WASM_I32V(11),
-                             WASM_RTT_FRESH_SUB(kType, WASM_RTT_CANON(kType)))),
-       WASM_I32_ADD(
-           WASM_REF_TEST(WASM_LOCAL_GET(0),
-                         WASM_RTT_FRESH_SUB(kType, WASM_RTT_CANON(kType))),
-           WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_GLOBAL_GET(kRtt))),
-       kExprEnd});
-
-  tester.CompileModule();
-  tester.CheckResult(kRefTest, 0);
-}
-
 WASM_COMPILED_EXEC_TEST(RefTrivialCasts) {
   // TODO(7748): Add tests for branch_on_*.
   WasmGCTester tester(execution_tier);
   byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
-  byte subtype_index =
-      tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmS128, false)});
+  byte subtype_index = tester.DefineStruct(
+      {F(wasm::kWasmI32, true), F(wasm::kWasmS128, false)}, type_index);
   ValueType sig_types[] = {kWasmS128, kWasmI32, kWasmF64};
   FunctionSig sig(1, 2, sig_types);
   byte sig_index = tester.DefineSignature(&sig);
@@ -1460,14 +1436,6 @@
   // Upcasts should not be optimized away for structural types.
   const byte kRefTestUpcast = tester.DefineFunction(
       tester.sigs.i_v(), {},
-      {WASM_REF_TEST(
-           WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-               subtype_index,
-               WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
-           WASM_RTT_CANON(type_index)),
-       kExprEnd});
-  const byte kRefTestUpcastFail = tester.DefineFunction(
-      tester.sigs.i_v(), {},
       {WASM_REF_TEST(WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
                          subtype_index, WASM_RTT_CANON(subtype_index)),
                      WASM_RTT_CANON(type_index)),
@@ -1478,11 +1446,9 @@
        kExprEnd});
   const byte kRefTestUnrelated = tester.DefineFunction(
       tester.sigs.i_v(), {},
-      {WASM_REF_TEST(
-           WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-               subtype_index,
-               WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
-           WASM_RTT_CANON(sig_index)),
+      {WASM_REF_TEST(WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
+                         subtype_index, WASM_RTT_CANON(subtype_index)),
+                     WASM_RTT_CANON(sig_index)),
        kExprEnd});
   const byte kRefTestUnrelatedNull = tester.DefineFunction(
       tester.sigs.i_v(), {},
@@ -1502,11 +1468,10 @@
        kExprEnd});
   const byte kRefCastUpcast = tester.DefineFunction(
       tester.sigs.i_v(), {},
-      {WASM_REF_IS_NULL(WASM_REF_CAST(
-           WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-               subtype_index,
-               WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
-           WASM_RTT_CANON(type_index))),
+      {WASM_REF_IS_NULL(
+           WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
+                             subtype_index, WASM_RTT_CANON(subtype_index)),
+                         WASM_RTT_CANON(type_index))),
        kExprEnd});
   const byte kRefCastUpcastNull = tester.DefineFunction(
       tester.sigs.i_v(), {},
@@ -1515,11 +1480,10 @@
        kExprEnd});
   const byte kRefCastUnrelated = tester.DefineFunction(
       tester.sigs.i_v(), {},
-      {WASM_REF_IS_NULL(WASM_REF_CAST(
-           WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
-               subtype_index,
-               WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
-           WASM_RTT_CANON(sig_index))),
+      {WASM_REF_IS_NULL(
+           WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
+                             subtype_index, WASM_RTT_CANON(subtype_index)),
+                         WASM_RTT_CANON(sig_index))),
        kExprEnd});
   const byte kRefCastUnrelatedNull = tester.DefineFunction(
       tester.sigs.i_v(), {},
@@ -1538,7 +1502,6 @@
 
   tester.CheckResult(kRefTestNull, 0);
   tester.CheckResult(kRefTestUpcast, 1);
-  tester.CheckResult(kRefTestUpcastFail, 0);
   tester.CheckResult(kRefTestUpcastNull, 0);
   tester.CheckResult(kRefTestUnrelated, 0);
   tester.CheckResult(kRefTestUnrelatedNull, 0);
@@ -1555,8 +1518,7 @@
 WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
   // TODO(7748): Add tests for branch_on_*.
   WasmGCTester tester(execution_tier);
-  byte type_index =
-      tester.DefineStruct({F(wasm::kWasmI32, true)}, kGenericSuperType);
+  byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
   byte subtype_index = tester.DefineStruct(
       {F(wasm::kWasmI32, true), F(wasm::kWasmS128, false)}, type_index);
   ValueType sig_types[] = {kWasmS128, kWasmI32, kWasmF64};
@@ -1641,23 +1603,16 @@
   WasmGCTester tester(execution_tier);
 
   const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
-  const byte subtype_index =
-      tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)});
-  const byte empty_struct_index = tester.DefineStruct({});
+  const byte subtype_index = tester.DefineStruct(
+      {F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}, type_index);
 
   ValueType kRttTypeNoDepth = ValueType::Rtt(type_index);
   FunctionSig sig_t1_v_nd(1, 0, &kRttTypeNoDepth);
   ValueType kRttSubtypeNoDepth = ValueType::Rtt(subtype_index);
   FunctionSig sig_t2_v_nd(1, 0, &kRttSubtypeNoDepth);
 
-  const byte kRttTypeCanon = tester.DefineFunction(
-      &sig_t1_v_nd, {}, {WASM_RTT_CANON(type_index), kExprEnd});
   const byte kRttSubtypeCanon = tester.DefineFunction(
       &sig_t2_v_nd, {}, {WASM_RTT_CANON(subtype_index), kExprEnd});
-  const byte kRttSubtypeSub = tester.DefineFunction(
-      &sig_t2_v_nd, {},
-      {WASM_RTT_SUB(subtype_index, WASM_CALL_FUNCTION0(kRttTypeCanon)),
-       kExprEnd});
 
   const byte kTestCanon = tester.DefineFunction(
       tester.sigs.i_v(), {optref(type_index)},
@@ -1667,56 +1622,9 @@
        WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeCanon)),
        kExprEnd});
 
-  const byte kTestSub = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(type_index)},
-      {WASM_LOCAL_SET(
-           0, WASM_STRUCT_NEW_WITH_RTT(
-                  subtype_index, WASM_I32V(11), WASM_I32V(42),
-                  WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)))),
-       WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeSub)),
-       kExprEnd});
-
-  const byte kTestSubVsEmpty = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(type_index)},
-      {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_WITH_RTT(
-                             subtype_index, WASM_I32V(11), WASM_I32V(42),
-                             WASM_RTT_SUB(subtype_index,
-                                          WASM_RTT_CANON(empty_struct_index)))),
-       WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeSub)),
-       kExprEnd});
-
-  const byte kTestSubVsCanon = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(type_index)},
-      {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_WITH_RTT(
-                             subtype_index, WASM_I32V(11), WASM_I32V(42),
-                             WASM_RTT_CANON(subtype_index))),
-       WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeSub)),
-       kExprEnd});
-
-  const byte kTestCanonVsSub = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(type_index)},
-      {WASM_LOCAL_SET(
-           0, WASM_STRUCT_NEW_WITH_RTT(
-                  subtype_index, WASM_I32V(11), WASM_I32V(42),
-                  WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)))),
-       WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeCanon)),
-       kExprEnd});
-
-  const byte kTestSuperVsSub = tester.DefineFunction(
-      tester.sigs.i_v(), {optref(type_index)},
-      {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
-                                                  WASM_RTT_CANON(type_index))),
-       WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION0(kRttSubtypeCanon)),
-       kExprEnd});
-
   tester.CompileModule();
 
   tester.CheckResult(kTestCanon, 1);
-  tester.CheckResult(kTestSub, 1);
-  tester.CheckResult(kTestSubVsEmpty, 0);
-  tester.CheckResult(kTestSubVsCanon, 0);
-  tester.CheckResult(kTestCanonVsSub, 0);
-  tester.CheckResult(kTestSuperVsSub, 0);
 }
 
 WASM_COMPILED_EXEC_TEST(ArrayNewMap) {
@@ -1787,19 +1695,12 @@
       {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
        WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_RTT_CANON(sig_index)), kExprEnd});
 
-  const byte test_fail_1 = tester.DefineFunction(
+  const byte test_fail = tester.DefineFunction(
       tester.sigs.i_v(), {kWasmFuncRef},
       {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
        WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_RTT_CANON(other_sig_index)),
        kExprEnd});
 
-  const byte test_fail_2 = tester.DefineFunction(
-      tester.sigs.i_v(), {kWasmFuncRef},
-      {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
-       WASM_REF_TEST(WASM_LOCAL_GET(0),
-                     WASM_RTT_SUB(sig_index, WASM_RTT_CANON(sig_index))),
-       kExprEnd});
-
   tester.CompileModule();
 
   Handle<Object> result_canon =
@@ -1825,8 +1726,7 @@
            cast_function_reference->code().raw_instruction_start());
 
   tester.CheckResult(test, 1);
-  tester.CheckResult(test_fail_1, 0);
-  tester.CheckResult(test_fail_2, 0);
+  tester.CheckResult(test_fail, 0);
 }
 
 WASM_COMPILED_EXEC_TEST(CallRef) {
@@ -2119,8 +2019,8 @@
 WASM_COMPILED_EXEC_TEST(CastsBenchmark) {
   WasmGCTester tester(execution_tier);
   const byte SuperType = tester.DefineStruct({F(wasm::kWasmI32, true)});
-  const byte SubType =
-      tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)});
+  const byte SubType = tester.DefineStruct(
+      {F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}, SuperType);
 
   ValueType kDataRefNull = ValueType::Ref(HeapType::kData, kNullable);
   const byte ListType = tester.DefineArray(kDataRefNull, true);
@@ -2132,11 +2032,8 @@
   const byte RttSuper = tester.AddGlobal(
       ValueType::Rtt(SuperType, 0), false,
       WasmInitExpr::RttCanon(static_cast<HeapType::Representation>(SuperType)));
-  const byte RttSub = tester.AddGlobal(
-      ValueType::Rtt(SubType, 1), false,
-      WasmInitExpr::RttSub(tester.zone(),
-                           static_cast<HeapType::Representation>(SubType),
-                           WasmInitExpr::GlobalGet(RttSuper)));
+  const byte RttSub = tester.AddGlobal(ValueType::Rtt(SubType, 1), false,
+                                       WasmInitExpr::RttCanon(SubType));
   const byte RttList = tester.AddGlobal(
       ValueType::Rtt(ListType, 0), false,
       WasmInitExpr::RttCanon(static_cast<HeapType::Representation>(ListType)));
@@ -2234,25 +2131,25 @@
   WasmGCTester tester(execution_tier);
 
   byte super_struct = tester.DefineStruct({F(kWasmI32, false)});
-  byte sub_struct =
-      tester.DefineStruct({F(kWasmI32, false), F(kWasmI32, true)});
+  byte sub_struct = tester.DefineStruct({F(kWasmI32, false), F(kWasmI32, true)},
+                                        super_struct);
   FunctionSig* super_sig =
       FunctionSig::Build(tester.zone(), {kWasmI32}, {optref(sub_struct)});
   byte super_sig_index = tester.DefineSignature(super_sig);
   FunctionSig* sub_sig =
       FunctionSig::Build(tester.zone(), {kWasmI32}, {optref(super_struct)});
-  byte sub_sig_index = tester.DefineSignature(sub_sig);
+  byte sub_sig_index = tester.DefineSignature(sub_sig, super_sig_index);
 
   tester.DefineTable(optref(super_sig_index), 10, 10);
 
   byte super_func = tester.DefineFunction(
-      super_sig, {},
+      super_sig_index, {},
       {WASM_I32_ADD(WASM_STRUCT_GET(sub_struct, 0, WASM_LOCAL_GET(0)),
                     WASM_STRUCT_GET(sub_struct, 1, WASM_LOCAL_GET(0))),
        WASM_END});
 
   byte sub_func = tester.DefineFunction(
-      sub_sig, {},
+      sub_sig_index, {},
       {WASM_STRUCT_GET(super_struct, 0, WASM_LOCAL_GET(0)), WASM_END});
 
   byte setup_func = tester.DefineFunction(
diff --git a/test/common/wasm/wasm-macro-gen.h b/test/common/wasm/wasm-macro-gen.h
index fd61c5a..0cdaa3a 100644
--- a/test/common/wasm/wasm-macro-gen.h
+++ b/test/common/wasm/wasm-macro-gen.h
@@ -589,10 +589,6 @@
 #define WASM_RTT(typeidx) kRttCode, U32V_1(typeidx)
 #define WASM_RTT_CANON(typeidx) \
   WASM_GC_OP(kExprRttCanon), static_cast<byte>(typeidx)
-#define WASM_RTT_SUB(typeidx, supertype) \
-  supertype, WASM_GC_OP(kExprRttSub), static_cast<byte>(typeidx)
-#define WASM_RTT_FRESH_SUB(typeidx, supertype) \
-  supertype, WASM_GC_OP(kExprRttFreshSub), static_cast<byte>(typeidx)
 
 #define WASM_I31_NEW(val) val, WASM_GC_OP(kExprI31New)
 #define WASM_I31_GET_S(val) val, WASM_GC_OP(kExprI31GetS)
diff --git a/test/fuzzer/wasm-fuzzer-common.cc b/test/fuzzer/wasm-fuzzer-common.cc
index d2e22ea..a89950b 100644
--- a/test/fuzzer/wasm-fuzzer-common.cc
+++ b/test/fuzzer/wasm-fuzzer-common.cc
@@ -421,12 +421,6 @@
     result->init_expr = WasmInitExpr::RttCanon(type_index);
   }
 
-  void RttSub(FullDecoder* decoder, uint32_t type_index, const Value& parent,
-              Value* result, WasmRttSubMode mode) {
-    result->init_expr =
-        WasmInitExpr::RttSub(zone_, type_index, parent.init_expr);
-  }
-
   void DoReturn(FullDecoder* decoder, uint32_t /*drop_values*/) {
     // End decoding on "end".
     decoder->set_end(decoder->pc() + 1);
@@ -504,14 +498,6 @@
     case WasmInitExpr::kRttCanon:
       os << "RttCanon(" << expr.immediate().index;
       break;
-    case WasmInitExpr::kRttSub:
-      os << "RttSub(" << expr.immediate().index << ", ";
-      AppendInitExpr(os, (*expr.operands())[0]);
-      break;
-    case WasmInitExpr::kRttFreshSub:
-      os << "RttFreshSub(" << expr.immediate().index << ", ";
-      AppendInitExpr(os, (*expr.operands())[0]);
-      break;
   }
 
   if (append_operands) {
diff --git a/test/inspector/debugger/wasm-gc-breakpoints-expected.txt b/test/inspector/debugger/wasm-gc-breakpoints-expected.txt
index df10788..0106c69 100644
--- a/test/inspector/debugger/wasm-gc-breakpoints-expected.txt
+++ b/test/inspector/debugger/wasm-gc-breakpoints-expected.txt
@@ -3,12 +3,12 @@
 Running test: test
 Instantiating.
 Waiting for wasm script (ignoring first non-wasm script).
-Setting breakpoint at offset 107 on script wasm://wasm/151aafd6
+Setting breakpoint at offset 109 on script wasm://wasm/856247ba
 Calling main()
 Paused:
-Script wasm://wasm/151aafd6 byte offset 107: Wasm opcode 0x21 (kExprLocalSet)
+Script wasm://wasm/856247ba byte offset 109: Wasm opcode 0x21 (kExprLocalSet)
 Scope:
-at $main (0:107):
+at $main (0:109):
  - scope (wasm-expression-stack):
    0: Array ((ref $ArrC))
      object details:
diff --git a/test/inspector/debugger/wasm-gc-breakpoints.js b/test/inspector/debugger/wasm-gc-breakpoints.js
index 4306e20..1d20a1a 100644
--- a/test/inspector/debugger/wasm-gc-breakpoints.js
+++ b/test/inspector/debugger/wasm-gc-breakpoints.js
@@ -14,8 +14,10 @@
   0x00, 0x61, 0x73, 0x6d, 1, 0, 0, 0,  // wasm magic
 
   0x01,  // type section
-  0x16,  // section length
-  0x04,  // number of types
+  0x18,  // section length
+  0x01,  // number of type section entries
+  0x49,  // recursive type group
+  0x04,  // number of types in the recursive group
   // type 0: struct $StrA (field ($byte i8) ($word i16) ($pointer (ref $StrB)))
   0x5f,  // struct
   0x03,  // field count
@@ -219,7 +221,7 @@
     // Ignore javascript and full module wasm script, get scripts for functions.
     const [, {params: wasm_script}] =
         await Protocol.Debugger.onceScriptParsed(2);
-    let offset = 107;  // "local.set $varC" at the end.
+    let offset = 109;  // "local.set $varC" at the end.
     await setBreakpoint(offset, wasm_script.scriptId, wasm_script.url);
     InspectorTest.log('Calling main()');
     await WasmInspectorTest.evalWithUrl('instance.exports.main()', 'runWasm');
diff --git a/test/mjsunit/wasm/call-ref.js b/test/mjsunit/wasm/call-ref.js
index 812a764..2a38bba 100644
--- a/test/mjsunit/wasm/call-ref.js
+++ b/test/mjsunit/wasm/call-ref.js
@@ -96,10 +96,11 @@
 
   print("--imported function from another module--");
   assertEquals(57, instance.exports.test_wasm_import());
+  /* TODO(7748): Implement cross-module type canonicalization.
   print("--not imported function defined in another module--");
   assertEquals(19, instance.exports.main(
     exporting_instance.exports.addition, 12, 7));
-
+*/
   print("--imported WebAssembly.Function--")
   assertEquals(21, instance.exports.test_js_api_import());
   print("--not imported WebAssembly.Function--")
diff --git a/test/mjsunit/wasm/gc-nominal.js b/test/mjsunit/wasm/gc-nominal.js
index 957381f..49221c3 100644
--- a/test/mjsunit/wasm/gc-nominal.js
+++ b/test/mjsunit/wasm/gc-nominal.js
@@ -9,12 +9,13 @@
 (function TestNominalTypesBasic() {
   print(arguments.callee.name);
   var builder = new WasmModuleBuilder();
-  let struct1 = builder.addStructSubtype([makeField(kWasmI32, true)]);
-  let struct2 = builder.addStructSubtype(
+  builder.setNominal();
+  let struct1 = builder.addStruct([makeField(kWasmI32, true)]);
+  let struct2 = builder.addStruct(
       [makeField(kWasmI32, true), makeField(kWasmI32, true)], struct1);
 
-  let array1 = builder.addArraySubtype(kWasmI32, true);
-  let array2 = builder.addArraySubtype(kWasmI32, true, array1);
+  let array1 = builder.addArray(kWasmI32, true);
+  let array2 = builder.addArray(kWasmI32, true, array1);
 
   builder.addFunction("main", kSig_v_v)
       .addLocals(wasmOptRefType(struct1), 1)
@@ -29,14 +30,13 @@
         kGCPrefix, kExprStructNewDefault, struct2, kExprLocalSet, 0,
         // Check that we can create an array with explicit RTT...
         kExprI32Const, 10,  // length
-        kGCPrefix, kExprRttCanon, array2, kGCPrefix,
-        kExprArrayNewDefaultWithRtt, array2,
+        kGCPrefix, kExprRttCanon, array2,
+        kGCPrefix, kExprArrayNewDefaultWithRtt, array2,
         // ...and upcast it.
         kExprLocalSet, 1,
         // Check that we can create an array with implicit RTT.
         kExprI32Const, 10,  // length
-        kGCPrefix, kExprArrayNewDefault, array2, kExprLocalSet, 1
-      ])
+        kGCPrefix, kExprArrayNewDefault, array2, kExprLocalSet, 1])
       .exportFunc();
 
   // This test is only interested in type checking.
@@ -46,10 +46,9 @@
 (function TestSubtypingDepthTooLarge() {
   print(arguments.callee.name);
   let builder = new WasmModuleBuilder();
-  builder.addStructSubtype([]);
-  for (let i = 0; i < 32; i++) {
-      builder.addStructSubtype([], i);
-  }
+  builder.setNominal();
+  builder.addStruct([]);
+  for (let i = 0; i < 32; i++) builder.addStruct([], i);
   assertThrows(
       () => builder.instantiate(), WebAssembly.CompileError,
       /subtyping depth is greater than allowed/);
@@ -58,7 +57,8 @@
 (function TestArrayInitFromDataStatic() {
   print(arguments.callee.name);
   let builder = new WasmModuleBuilder();
-  let array_type_index = builder.addArraySubtype(kWasmI16, true);
+  builder.setNominal();
+  let array_type_index = builder.addArray(kWasmI16, true);
 
   let dummy_byte = 0xff;
   let element_0 = 1000;
diff --git a/test/mjsunit/wasm/gc-optimizations.js b/test/mjsunit/wasm/gc-optimizations.js
index 9fcf9cf..145977c 100644
--- a/test/mjsunit/wasm/gc-optimizations.js
+++ b/test/mjsunit/wasm/gc-optimizations.js
@@ -376,9 +376,10 @@
 (function AllocationFolding() {
   print(arguments.callee.name);
   var builder = new WasmModuleBuilder();
+  builder.setNominal();
 
-  let struct_index = builder.addStructSubtype([makeField(kWasmI32, true)]);
-  let struct_2 = builder.addStructSubtype([
+  let struct_index = builder.addStruct([makeField(kWasmI32, true)]);
+  let struct_2 = builder.addStruct([
     makeField(wasmRefType(struct_index), false),
     makeField(wasmRefType(struct_index), false)
   ]);
diff --git a/test/mjsunit/wasm/imported-function-types.js b/test/mjsunit/wasm/imported-function-types.js
index 8956440..756296c 100644
--- a/test/mjsunit/wasm/imported-function-types.js
+++ b/test/mjsunit/wasm/imported-function-types.js
@@ -35,8 +35,9 @@
   return builder.instantiate({other: {func: imported_function}});
 };
 
+// TODO(7748): Implement cross-module subtyping.
 // Same form/different index should be fine.
-importing_module(exporting_module.exports.func2);
+// importing_module(exporting_module.exports.func2);
 // Same index/different form should throw.
 assertThrows(
     () => importing_module(exporting_module.exports.func1),
diff --git a/test/mjsunit/wasm/load-immutable.js b/test/mjsunit/wasm/load-immutable.js
index b8d60c8..567175d 100644
--- a/test/mjsunit/wasm/load-immutable.js
+++ b/test/mjsunit/wasm/load-immutable.js
@@ -77,7 +77,8 @@
 
 (function ImmutableLoadThroughEffect() {
   var builder = new WasmModuleBuilder();
-  var struct = builder.addStructSubtype([
+  builder.setNominal();
+  var struct = builder.addStruct([
     makeField(kWasmI32, false), makeField(kWasmI32, true)]);
 
   let effect = builder.addImport('m', 'f', kSig_v_v);
diff --git a/test/mjsunit/wasm/reference-globals.js b/test/mjsunit/wasm/reference-globals.js
index 6ab071f..361708d 100644
--- a/test/mjsunit/wasm/reference-globals.js
+++ b/test/mjsunit/wasm/reference-globals.js
@@ -6,6 +6,7 @@
 
 d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
 
+/* TODO(7748): Implement cross-module subtyping.
 (function TestReferenceGlobals() {
   print(arguments.callee.name);
 
@@ -105,6 +106,7 @@
   // The correct function reference has been passed.
   assertEquals(66, instance.exports.test_import(42, 24));
 })();
+*/
 
 (function TestStructInitExpr() {
   print(arguments.callee.name);
diff --git a/test/mjsunit/wasm/reference-tables.js b/test/mjsunit/wasm/reference-tables.js
index d73a241..5494bbf 100644
--- a/test/mjsunit/wasm/reference-tables.js
+++ b/test/mjsunit/wasm/reference-tables.js
@@ -5,7 +5,7 @@
 // Flags: --experimental-wasm-typed-funcref
 
 d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
-
+/* TODO(7748): Implement cross-module subtyping.
 (function TestTables() {
   print(arguments.callee.name);
   var exporting_instance = (function() {
@@ -102,6 +102,7 @@
       TypeError,
       /Argument 1 must be null or a WebAssembly function of type compatible to/);
 })();
+*/
 
 (function TestNonNullableTables() {
   print(arguments.callee.name);
@@ -109,11 +110,11 @@
 
   var binary_type = builder.addType(kSig_i_ii);
 
-  var addition = builder.addFunction('addition', kSig_i_ii).addBody([
+  var addition = builder.addFunction('addition', binary_type).addBody([
     kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add
   ]);
   var subtraction =
-      builder.addFunction('subtraction', kSig_i_ii)
+      builder.addFunction('subtraction', binary_type)
           .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub])
           .exportFunc();
 
diff --git a/test/mjsunit/wasm/speculative-inlining.js b/test/mjsunit/wasm/speculative-inlining.js
index e783be5..0572e74 100644
--- a/test/mjsunit/wasm/speculative-inlining.js
+++ b/test/mjsunit/wasm/speculative-inlining.js
@@ -44,17 +44,19 @@
   print(arguments.callee.name);
   let builder = new WasmModuleBuilder();
 
+  let sig_index = builder.addType(kSig_i_i);
+
   // h(x) = x - 1
-  let callee0 = builder.addFunction("callee0", kSig_i_i)
+  let callee0 = builder.addFunction("callee0", sig_index)
     .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
 
   // f(x) = x - 2
-  let callee1 = builder.addFunction("callee1", kSig_i_i)
+  let callee1 = builder.addFunction("callee1", sig_index)
     .addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);
 
-  let global0 = builder.addGlobal(wasmRefType(1), false,
+  let global0 = builder.addGlobal(wasmRefType(sig_index), false,
                                   WasmInitExpr.RefFunc(callee0.index));
-  let global1 = builder.addGlobal(wasmRefType(1), false,
+  let global1 = builder.addGlobal(wasmRefType(sig_index), false,
                                   WasmInitExpr.RefFunc(callee1.index));
 
   // g(x, y) = if (y) { h(5) + x } else { f(7) + x }
@@ -114,17 +116,19 @@
   print(arguments.callee.name);
   let builder = new WasmModuleBuilder();
 
+  let sig_index = builder.addType(kSig_i_i);
+
   // h(x) = x - 1
-  let callee0 = builder.addFunction("callee0", kSig_i_i)
+  let callee0 = builder.addFunction("callee0", sig_index)
     .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
 
   // f(x) = x - 2
-  let callee1 = builder.addFunction("callee1", kSig_i_i)
+  let callee1 = builder.addFunction("callee1", sig_index)
     .addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);
 
-  let global0 = builder.addGlobal(wasmRefType(1), false,
+  let global0 = builder.addGlobal(wasmRefType(sig_index), false,
                                  WasmInitExpr.RefFunc(callee0.index));
-  let global1 = builder.addGlobal(wasmRefType(1), false,
+  let global1 = builder.addGlobal(wasmRefType(sig_index), false,
                                  WasmInitExpr.RefFunc(callee1.index));
 
   // g(x, y) = if (y) { h(x) } else { f(x) }
@@ -151,6 +155,7 @@
   assertEquals(8, instance.exports.main(10, 0));
 })();
 
+/* TODO(7748): Implement cross-module subtyping.
 (function CallRefImportedFunction() {
   print(arguments.callee.name);
 
@@ -191,6 +196,7 @@
   // The function f1 defined in another module should not be inlined.
   assertEquals(1, instance2.exports.main(0, instance1.exports.f1));
 })();
+*/
 
 // Check that we handle WasmJSFunctions properly and do not inline them, both
 // in the monomorphic and polymorphic case.
diff --git a/test/mjsunit/wasm/wasm-module-builder.js b/test/mjsunit/wasm/wasm-module-builder.js
index e644794..b6666d5 100644
--- a/test/mjsunit/wasm/wasm-module-builder.js
+++ b/test/mjsunit/wasm/wasm-module-builder.js
@@ -77,9 +77,13 @@
 let kWasmFunctionTypeForm = 0x60;
 let kWasmStructTypeForm = 0x5f;
 let kWasmArrayTypeForm = 0x5e;
-let kWasmFunctionSubtypeForm = 0x5d;
-let kWasmStructSubtypeForm = 0x5c;
-let kWasmArraySubtypeForm = 0x5b;
+let kWasmFunctionNominalForm = 0x5d;
+let kWasmStructNominalForm = 0x5c;
+let kWasmArrayNominalForm = 0x5b;
+let kWasmSubtypeForm = 0x50;
+let kWasmRecursiveTypeGroupForm = 0x49;
+
+let kNoSuperType = 0xFFFFFFFF;
 
 let kLimitsNoMaximum = 0x00;
 let kLimitsWithMaximum = 0x01;
@@ -1299,38 +1303,25 @@
 }
 
 class WasmStruct {
-  constructor(fields) {
+  constructor(fields, supertype_idx) {
     if (!Array.isArray(fields)) {
       throw new Error('struct fields must be an array');
     }
     this.fields = fields;
     this.type_form = kWasmStructTypeForm;
-  }
-}
-
-class WasmStructSubtype extends WasmStruct {
-  constructor(fields, supertype_idx) {
-    super(fields);
     this.supertype = supertype_idx;
-    this.type_form = kWasmStructSubtypeForm;
   }
 }
 
 class WasmArray {
-  constructor(type, mutability) {
+  constructor(type, mutability, supertype_idx) {
     this.type = type;
     this.mutability = mutability;
     this.type_form = kWasmArrayTypeForm;
+    this.supertype = supertype_idx;
   }
 }
 
-class WasmArraySubtype extends WasmArray {
-  constructor(type, mutability, supertype_idx) {
-    super(type, mutability);
-    this.supertype = supertype_idx;
-    this.type_form = kWasmArraySubtypeForm;
-  }
-}
 class WasmElemSegment {
   constructor(table, offset, type, elements, is_decl) {
     this.table = table;
@@ -1383,6 +1374,7 @@
     this.num_imported_globals = 0;
     this.num_imported_tables = 0;
     this.num_imported_tags = 0;
+    this.nominal = false;  // Controls only how gc-modules are printed.
     this.early_data_count_section = false;
     return this;
   }
@@ -1442,6 +1434,9 @@
     this.explicit.push(this.createCustomSection(name, bytes));
   }
 
+  // TODO(7748): Support recursive groups.
+
+  // TODO(7748): Support function supertypes.
   addType(type) {
     this.types.push(type);
     var pl = type.params.length;   // should have params
@@ -1449,24 +1444,13 @@
     return this.types.length - 1;
   }
 
-  addStruct(fields) {
-    this.types.push(new WasmStruct(fields));
+  addStruct(fields, supertype_idx = kNoSuperType) {
+    this.types.push(new WasmStruct(fields, supertype_idx));
     return this.types.length - 1;
   }
 
-  kGenericSuperType = 0xFFFFFFFE;
-  addStructSubtype(fields, supertype_idx = this.kGenericSuperType) {
-    this.types.push(new WasmStructSubtype(fields, supertype_idx));
-    return this.types.length - 1;
-  }
-
-  addArray(type, mutability) {
-    this.types.push(new WasmArray(type, mutability));
-    return this.types.length - 1;
-  }
-
-  addArraySubtype(type, mutability, supertype_idx = this.kGenericSuperType) {
-    this.types.push(new WasmArraySubtype(type, mutability, supertype_idx));
+  addArray(type, mutability, supertype_idx = kNoSuperType) {
+    this.types.push(new WasmArray(type, mutability, supertype_idx));
     return this.types.length - 1;
   }
 
@@ -1679,6 +1663,10 @@
     return this;
   }
 
+  setNominal() {
+    this.nominal = true;
+  }
+
   setName(name) {
     this.name = name;
     return this;
@@ -1698,32 +1686,51 @@
         section.emit_u32v(wasm.types.length);
         for (let type of wasm.types) {
           if (type instanceof WasmStruct) {
-            section.emit_u8(type.type_form);
+            if (!this.nominal && type.supertype != kNoSuperType) {
+              section.emit_u8(kWasmSubtypeForm);
+              section.emit_u8(1);  // supertype count
+              section.emit_u32v(type.supertype);
+            }
+            section.emit_u8(this.nominal ? kWasmStructNominalForm
+                                         : kWasmStructTypeForm);
             section.emit_u32v(type.fields.length);
             for (let field of type.fields) {
               section.emit_type(field.type);
               section.emit_u8(field.mutability ? 1 : 0);
             }
-            if (type instanceof WasmStructSubtype) {
-              if (type.supertype === this.kGenericSuperType) {
+            if (this.nominal) {
+              if (type.supertype === kNoSuperType) {
                 section.emit_u8(kDataRefCode);
               } else {
                 section.emit_heap_type(type.supertype);
               }
             }
           } else if (type instanceof WasmArray) {
-            section.emit_u8(type.type_form);
+            if (!this.nominal && type.supertype != kNoSuperType) {
+              section.emit_u8(kWasmSubtypeForm);
+              section.emit_u8(1);  // supertype count
+              section.emit_u32v(type.supertype);
+            }
+            section.emit_u8(this.nominal ? kWasmArrayNominalForm
+                                         : kWasmArrayTypeForm);
             section.emit_type(type.type);
             section.emit_u8(type.mutability ? 1 : 0);
-            if (type instanceof WasmArraySubtype) {
-              if (type.supertype === this.kGenericSuperType) {
+            if (this.nominal) {
+              if (type.supertype === kNoSuperType) {
                 section.emit_u8(kDataRefCode);
               } else {
                 section.emit_heap_type(type.supertype);
               }
             }
           } else {
-            section.emit_u8(kWasmFunctionTypeForm);
+            /* TODO(7748): Support function supertypes.
+            if (!this.nominal && type.supertype != kNoSuperType) {
+              section.emit_u8(kWasmSubtypeForm);
+              section.emit_u8(1);  // supertype count
+              section.emit_u32v(type.supertype);
+            } */
+            section.emit_u8(this.nominal ? kWasmFunctionNominalForm
+                                         : kWasmFunctionTypeForm);
             section.emit_u32v(type.params.length);
             for (let param of type.params) {
               section.emit_type(param);
@@ -1732,6 +1739,15 @@
             for (let result of type.results) {
               section.emit_type(result);
             }
+            if (this.nominal) {
+              /* TODO(7748): Support function supertypes.
+              if (type.supertype === kNoSuperType) {
+                section.emit_u8(kFuncRefCode);
+              } else {
+                section.emit_heap_type(type.supertype);
+              }*/
+              section.emit_u8(kFuncRefCode);
+            }
           }
         }
       });
diff --git a/test/unittests/wasm/function-body-decoder-unittest.cc b/test/unittests/wasm/function-body-decoder-unittest.cc
index 0848cf5..ad272ca 100644
--- a/test/unittests/wasm/function-body-decoder-unittest.cc
+++ b/test/unittests/wasm/function-body-decoder-unittest.cc
@@ -86,24 +86,19 @@
     CHECK_LE(mod.globals.size(), kMaxByteSizedLeb128);
     return static_cast<byte>(mod.globals.size() - 1);
   }
-  byte AddSignature(const FunctionSig* sig) {
-    mod.add_signature(sig, kNoSuperType);
+  byte AddSignature(const FunctionSig* sig, uint32_t supertype = kNoSuperType) {
+    mod.add_signature(sig, supertype);
     CHECK_LE(mod.types.size(), kMaxByteSizedLeb128);
     return static_cast<byte>(mod.types.size() - 1);
   }
   byte AddFunction(const FunctionSig* sig, bool declared = true) {
     byte sig_index = AddSignature(sig);
-    mod.functions.push_back(
-        {sig,                                          // sig
-         static_cast<uint32_t>(mod.functions.size()),  // func_index
-         sig_index,                                    // sig_index
-         {0, 0},                                       // code
-         0,                                            // feedback slots
-         false,                                        // import
-         false,                                        // export
-         declared});                                   // declared
-    CHECK_LE(mod.functions.size(), kMaxByteSizedLeb128);
-    return static_cast<byte>(mod.functions.size() - 1);
+    return AddFunctionImpl(sig, sig_index, declared);
+  }
+  byte AddFunction(uint32_t sig_index, bool declared = true) {
+    DCHECK(mod.has_signature(sig_index));
+    return AddFunctionImpl(mod.types[sig_index].function_sig, sig_index,
+                           declared);
   }
   byte AddImport(const FunctionSig* sig) {
     byte result = AddFunction(sig);
@@ -181,6 +176,21 @@
   WasmModule* module() { return &mod; }
 
  private:
+  byte AddFunctionImpl(const FunctionSig* sig, uint32_t sig_index,
+                       bool declared) {
+    mod.functions.push_back(
+        {sig,                                          // sig
+         static_cast<uint32_t>(mod.functions.size()),  // func_index
+         sig_index,                                    // sig_index
+         {0, 0},                                       // code
+         0,                                            // feedback slots
+         false,                                        // import
+         false,                                        // export
+         declared});                                   // declared
+    CHECK_LE(mod.functions.size(), kMaxByteSizedLeb128);
+    return static_cast<byte>(mod.functions.size() - 1);
+  }
+
   AccountingAllocator allocator;
   WasmModule mod;
 };
@@ -1164,13 +1174,6 @@
                                WASM_GC_OP(kExprRefCast), struct_index,
                                struct_index, kExprDrop});
 
-  ExpectValidates(sigs.v_v(),
-                  {WASM_UNREACHABLE, WASM_GC_OP(kExprRttSub), array_index,
-                   WASM_GC_OP(kExprRttSub), array_index, kExprDrop});
-  ExpectValidates(sigs.v_v(),
-                  {WASM_UNREACHABLE, WASM_GC_OP(kExprRttFreshSub), array_index,
-                   WASM_GC_OP(kExprRttFreshSub), array_index, kExprDrop});
-
   ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, kExprBrOnNull, 0, WASM_DROP});
 
   ExpectValidates(&sig_v_s, {WASM_UNREACHABLE, WASM_LOCAL_GET(0), kExprBrOnNull,
@@ -1958,21 +1961,23 @@
   EXPERIMENTAL_FLAG_SCOPE(gc);
 
   byte empty_struct = builder.AddStruct({});
-  byte super_struct = builder.AddStruct({F(kWasmI32, false)});
-  byte sub_struct = builder.AddStruct({F(kWasmI32, false), F(kWasmF64, false)});
+  byte super_struct = builder.AddStruct({F(kWasmI32, false)}, empty_struct);
+  byte sub_struct =
+      builder.AddStruct({F(kWasmI32, false), F(kWasmF64, false)}, super_struct);
 
-  byte table_type = builder.AddSignature(
-      FunctionSig::Build(zone(), {ValueType::Ref(super_struct, kNullable)},
-                         {ValueType::Ref(sub_struct, kNullable)}));
   byte table_supertype = builder.AddSignature(
       FunctionSig::Build(zone(), {ValueType::Ref(empty_struct, kNullable)},
                          {ValueType::Ref(sub_struct, kNullable)}));
+  byte table_type = builder.AddSignature(
+      FunctionSig::Build(zone(), {ValueType::Ref(super_struct, kNullable)},
+                         {ValueType::Ref(sub_struct, kNullable)}),
+      table_supertype);
   auto function_sig =
       FunctionSig::Build(zone(), {ValueType::Ref(sub_struct, kNullable)},
                          {ValueType::Ref(super_struct, kNullable)});
-  byte function_type = builder.AddSignature(function_sig);
+  byte function_type = builder.AddSignature(function_sig, table_type);
 
-  byte function = builder.AddFunction(function_sig);
+  byte function = builder.AddFunction(function_type);
 
   byte table = builder.InitializeTable(ValueType::Ref(table_type, kNullable));
 
@@ -2677,10 +2682,11 @@
   WASM_FEATURE_SCOPE(typed_funcref);
   WASM_FEATURE_SCOPE(gc);
 
-  byte supertype1 = builder.AddStruct({F(kWasmI8, true), F(kWasmI16, false)});
-  byte supertype2 = builder.AddStruct({F(kWasmI8, true)});
+  byte supertype1 = builder.AddStruct({F(kWasmI8, true)});
+  byte supertype2 =
+      builder.AddStruct({F(kWasmI8, true), F(kWasmI16, false)}, supertype1);
   byte subtype = builder.AddStruct(
-      {F(kWasmI8, true), F(kWasmI16, false), F(kWasmI32, true)});
+      {F(kWasmI8, true), F(kWasmI16, false), F(kWasmI32, true)}, supertype2);
   ExpectValidates(
       sigs.v_v(),
       {WASM_BLOCK_R(wasm::ValueType::Ref(supertype1, kNonNullable),
@@ -3640,7 +3646,7 @@
   WASM_FEATURE_SCOPE(typed_funcref);
   WASM_FEATURE_SCOPE(gc);
   byte structural_type = builder.AddStruct({F(kWasmI32, true)});
-  byte nominal_type = builder.AddStruct({F(kWasmI32, true)}, kGenericSuperType);
+  byte nominal_type = builder.AddStruct({F(kWasmI32, true)});
   AddLocals(optref(structural_type), 1);
   AddLocals(optref(nominal_type), 1);
   // Try to assign a nominally-typed value to a structurally-typed local.
@@ -4330,94 +4336,6 @@
   }
 }
 
-TEST_F(FunctionBodyDecoderTest, RttSub) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  uint8_t array_type_index = builder.AddArray(kWasmI8, true);
-  uint8_t super_struct_type_index = builder.AddStruct({F(kWasmI16, true)});
-  uint8_t sub_struct_type_index =
-      builder.AddStruct({F(kWasmI16, true), F(kWasmI32, false)});
-
-  // Trivial type error.
-  ExpectFailure(
-      sigs.v_v(), {WASM_RTT_SUB(array_type_index, WASM_I32V(42)), kExprDrop},
-      kAppendEnd, "rtt.sub[0] expected rtt for a supertype of type 0");
-  ExpectFailure(
-      sigs.v_v(),
-      {WASM_RTT_FRESH_SUB(array_type_index, WASM_I32V(42)), kExprDrop},
-      kAppendEnd, "rtt.fresh_sub[0] expected rtt for a supertype of type 0");
-
-  {
-    ValueType type = ValueType::Rtt(array_type_index, 1);
-    FunctionSig sig(1, 0, &type);
-    // Can build an rtt.sub with self type for an array type.
-    ExpectValidates(&sig, {WASM_RTT_SUB(array_type_index,
-                                        WASM_RTT_CANON(array_type_index))});
-    ExpectValidates(&sig,
-                    {WASM_RTT_FRESH_SUB(array_type_index,
-                                        WASM_RTT_CANON(array_type_index))});
-    // Fails when argument to rtt.sub is not a supertype.
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_SUB(super_struct_type_index,
-                                WASM_RTT_CANON(array_type_index)),
-                   kExprDrop},
-                  kAppendEnd,
-                  "rtt.sub[0] expected rtt for a supertype of type 1");
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_FRESH_SUB(super_struct_type_index,
-                                      WASM_RTT_CANON(array_type_index)),
-                   kExprDrop},
-                  kAppendEnd,
-                  "rtt.fresh_sub[0] expected rtt for a supertype of type 1");
-  }
-
-  {
-    ValueType type = ValueType::Rtt(super_struct_type_index, 1);
-    FunctionSig sig(1, 0, &type);
-    // Can build an rtt.sub with self type for a struct type.
-    ExpectValidates(&sig,
-                    {WASM_RTT_SUB(super_struct_type_index,
-                                  WASM_RTT_CANON(super_struct_type_index))});
-    ExpectValidates(
-        &sig, {WASM_RTT_FRESH_SUB(super_struct_type_index,
-                                  WASM_RTT_CANON(super_struct_type_index))});
-    // Fails when argument to rtt.sub is not a supertype.
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_SUB(super_struct_type_index,
-                                WASM_RTT_CANON(array_type_index))},
-                  kAppendEnd,
-                  "rtt.sub[0] expected rtt for a supertype of type 1");
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_FRESH_SUB(super_struct_type_index,
-                                      WASM_RTT_CANON(array_type_index))},
-                  kAppendEnd,
-                  "rtt.fresh_sub[0] expected rtt for a supertype of type 1");
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_SUB(super_struct_type_index,
-                                WASM_RTT_CANON(sub_struct_type_index))},
-                  kAppendEnd,
-                  "rtt.sub[0] expected rtt for a supertype of type 1");
-    ExpectFailure(sigs.v_v(),
-                  {WASM_RTT_FRESH_SUB(super_struct_type_index,
-                                      WASM_RTT_CANON(sub_struct_type_index))},
-                  kAppendEnd,
-                  "rtt.fresh_sub[0] expected rtt for a supertype of type 1");
-  }
-
-  {
-    // Can build an rtt from a stuct supertype.
-    ValueType type = ValueType::Rtt(sub_struct_type_index, 1);
-    FunctionSig sig(1, 0, &type);
-    ExpectValidates(&sig,
-                    {WASM_RTT_SUB(sub_struct_type_index,
-                                  WASM_RTT_CANON(super_struct_type_index))});
-    ExpectValidates(
-        &sig, {WASM_RTT_FRESH_SUB(sub_struct_type_index,
-                                  WASM_RTT_CANON(super_struct_type_index))});
-  }
-}
-
 TEST_F(FunctionBodyDecoderTest, RefTestCast) {
   WASM_FEATURE_SCOPE(typed_funcref);
   WASM_FEATURE_SCOPE(gc);
@@ -4516,7 +4434,8 @@
   WASM_FEATURE_SCOPE(gc);
 
   byte super_struct = builder.AddStruct({F(kWasmI16, true)});
-  byte sub_struct = builder.AddStruct({F(kWasmI16, true), F(kWasmI32, false)});
+  byte sub_struct =
+      builder.AddStruct({F(kWasmI16, true), F(kWasmI32, false)}, super_struct);
 
   ValueType supertype = ValueType::Ref(super_struct, kNullable);
   ValueType subtype = ValueType::Ref(sub_struct, kNullable);
diff --git a/test/unittests/wasm/module-decoder-unittest.cc b/test/unittests/wasm/module-decoder-unittest.cc
index 2a4a09c..19c9ea6 100644
--- a/test/unittests/wasm/module-decoder-unittest.cc
+++ b/test/unittests/wasm/module-decoder-unittest.cc
@@ -567,15 +567,15 @@
     WASM_FEATURE_SCOPE(gc);
     static const byte referencing_undefined_global_nested[] = {
         SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI32Code, true)),
-        SECTION(Global, ENTRY_COUNT(2),            // --
-                WASM_RTT_WITH_DEPTH(1, 0),         // type
-                0,                                 // mutable
-                WASM_RTT_SUB(0,                    // init value
-                             WASM_GLOBAL_GET(1)),  // --
-                kExprEnd,                          // --
-                WASM_RTT_WITH_DEPTH(0, 0),         // type
-                0,                                 // mutable
-                WASM_RTT_CANON(0), kExprEnd)       // init value
+        SECTION(Global, ENTRY_COUNT(2),                      // --
+                kRefCode, 0,                                 // type
+                0,                                           // mutable
+                WASM_ARRAY_NEW_DEFAULT(0,                    // init value
+                                       WASM_GLOBAL_GET(1)),  // --
+                kExprEnd,                                    // --
+                kI32Code,                                    // type
+                0,                                           // mutable
+                WASM_I32V(10), kExprEnd)                     // init value
     };
     EXPECT_FAILURE_WITH_MSG(referencing_undefined_global_nested,
                             "Invalid global index: 1");
@@ -809,229 +809,6 @@
       "type error in init. expression[0] (expected (rtt 1 0), got (rtt 0 0))");
 }
 
-TEST_F(WasmModuleVerifyTest, GlobalRttSubOfCanon) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_SUB(1, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfCanon) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttSubOfSubOfCanon) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(2, 1), 1,
-              WASM_RTT_SUB(1, WASM_RTT_SUB(1, WASM_RTT_CANON(0))), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfSubOfCanon) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(2, 1), 1,
-              WASM_RTT_FRESH_SUB(1, WASM_RTT_SUB(1, WASM_RTT_CANON(0))),
-              kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfFreshSubOfCanon) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(2, 1), 1,
-              WASM_RTT_FRESH_SUB(1, WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0))),
-              kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobal) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Import,                     // section header
-              ENTRY_COUNT(1),             // number of imports
-              ADD_COUNT('m'),             // module name
-              ADD_COUNT('f'),             // global name
-              kExternalGlobal,            // import kind
-              WASM_RTT_WITH_DEPTH(0, 0),  // type
-              0),                         // mutability
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_SUB(1, WASM_GLOBAL_GET(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobal) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(2), STRUCT_FIELD(kI32Code, true),
-                              STRUCT_FIELD(kI32Code, true))),
-      SECTION(Import,                     // section header
-              ENTRY_COUNT(1),             // number of imports
-              ADD_COUNT('m'),             // module name
-              ADD_COUNT('f'),             // global name
-              kExternalGlobal,            // import kind
-              WASM_RTT_WITH_DEPTH(0, 0),  // type
-              0),                         // mutability
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_FRESH_SUB(1, WASM_GLOBAL_GET(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_OK(result);
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobalTypeError) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(1),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
-      SECTION(Import,           // section header
-              ENTRY_COUNT(1),   // number of imports
-              ADD_COUNT('m'),   // module name
-              ADD_COUNT('f'),   // global name
-              kExternalGlobal,  // import kind
-              kI32Code,         // type
-              0),               // mutability
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 0), 1,
-              WASM_RTT_SUB(0, WASM_GLOBAL_GET(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(result,
-                "rtt.sub[0] expected rtt for a supertype of type 0, found "
-                "global.get of type i32");
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobalTypeError) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(1),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
-      SECTION(Import,           // section header
-              ENTRY_COUNT(1),   // number of imports
-              ADD_COUNT('m'),   // module name
-              ADD_COUNT('f'),   // global name
-              kExternalGlobal,  // import kind
-              kI32Code,         // type
-              0),               // mutability
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 0), 1,
-              WASM_RTT_FRESH_SUB(0, WASM_GLOBAL_GET(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(result,
-                "rtt.fresh_sub[0] expected rtt for a supertype of type 0, "
-                "found global.get of type i32");
-}
-
-#if !V8_OS_FUCHSIA
-TEST_F(WasmModuleVerifyTest, GlobalRttSubIllegalParent) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kF32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_SUB(1, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(result,
-                "rtt.sub[0] expected rtt for a supertype of type 1, found "
-                "rtt.canon of type (rtt 0 0)");
-}
-
-TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubIllegalParent) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(2),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kF32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1,
-              WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(result,
-                "rtt.fresh_sub[0] expected rtt for a supertype of type 1, "
-                "found rtt.canon of type (rtt 0 0)");
-}
-#endif  // !V8_OS_FUCHSIA
-
-TEST_F(WasmModuleVerifyTest, RttSubGlobalTypeError) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(1),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(0, 0), 1,
-              WASM_RTT_SUB(0, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(
-      result,
-      "type error in init. expression[0] (expected (rtt 0 0), got (rtt 1 0))");
-}
-
-TEST_F(WasmModuleVerifyTest, RttFreshSubGlobalTypeError) {
-  WASM_FEATURE_SCOPE(typed_funcref);
-  WASM_FEATURE_SCOPE(gc);
-
-  static const byte data[] = {
-      SECTION(Type, ENTRY_COUNT(1),
-              WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(0, 0), 1,
-              WASM_RTT_FRESH_SUB(0, WASM_RTT_CANON(0)), kExprEnd)};
-  ModuleResult result = DecodeModule(data, data + sizeof(data));
-  EXPECT_NOT_OK(
-      result,
-      "type error in init. expression[0] (expected (rtt 0 0), got (rtt 1 0))");
-}
-
 TEST_F(WasmModuleVerifyTest, StructNewInitExpr) {
   WASM_FEATURE_SCOPE(typed_funcref);
   WASM_FEATURE_SCOPE(gc);
@@ -1047,12 +824,12 @@
   static const byte global_args[] = {
       SECTION(Type, ENTRY_COUNT(1),  // --
               WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
-      SECTION(Global, ENTRY_COUNT(3),                        // --
-              kI32Code, 0,                                   // type, mutability
-              WASM_INIT_EXPR_I32V_1(10),                     // --
-              kRttWithDepthCode, 1, 0, 0,                    // type, mutability
-              WASM_RTT_SUB(0, WASM_RTT_CANON(0)), kExprEnd,  // --
-              kRefCode, 0, 0,                                // type, mutability
+      SECTION(Global, ENTRY_COUNT(3),       // --
+              kI32Code, 0,                  // type, mutability
+              WASM_INIT_EXPR_I32V_1(10),    // --
+              kRttWithDepthCode, 0, 0, 0,   // type, mutability
+              WASM_RTT_CANON(0), kExprEnd,  // --
+              kRefCode, 0, 0,               // type, mutability
               WASM_INIT_EXPR_STRUCT_NEW(0, WASM_GLOBAL_GET(0),
                                         WASM_GLOBAL_GET(1)))};
   EXPECT_VERIFIES(global_args);
@@ -1230,18 +1007,18 @@
   // Inheritance: t1 <: t2 <: t0
   static const byte all_good[] = {
       SECTION(Type, ENTRY_COUNT(3),    // --
-              kWasmStructSubtypeCode,  // type #0
+              kWasmStructNominalCode,  // type #0
               1,                       // field count
               kI32Code, 1,             // mut i32
               kDataRefCode,            // root of type hierarchy
 
-              kWasmStructSubtypeCode,  // type #1
+              kWasmStructNominalCode,  // type #1
               2,                       // field count
               kI32Code, 1,             // mut i32 (inherited)
               kI64Code, 1,             // mut i32 (added)
               2,                       // supertype
 
-              kWasmStructSubtypeCode,  // type #2
+              kWasmStructNominalCode,  // type #2
               1,                       // field count
               kI32Code, 1,             // mut i32 (inherited)
               0)};                     // supertype
@@ -1249,26 +1026,26 @@
   ModuleResult result = DecodeModule(all_good, all_good + sizeof(all_good));
   EXPECT_OK(result);
   WasmModule* module = result.value().get();
-  EXPECT_EQ(kGenericSuperType, module->supertype(0));
+  EXPECT_EQ(kNoSuperType, module->supertype(0));
   EXPECT_EQ(2u, module->supertype(1));
   EXPECT_EQ(0u, module->supertype(2));
 
   static const byte self_or_mutual_ref[] = {
       SECTION(Type, ENTRY_COUNT(4),       // --
-              kWasmStructSubtypeCode, 0,  // empty struct
+              kWasmStructNominalCode, 0,  // empty struct
               kDataRefCode,               // root of hierarchy
 
-              kWasmStructSubtypeCode,  // type1
+              kWasmStructNominalCode,  // type1
               1,                       // field count
               kOptRefCode, 1, 1,       // mut optref type1
               0,                       // supertype
 
-              kWasmStructSubtypeCode,  // type 2
+              kWasmStructNominalCode,  // type 2
               1,                       // field count
               kOptRefCode, 3, 1,       // mut optref type3
               0,                       // supertype
 
-              kWasmStructSubtypeCode,  // type 3
+              kWasmStructNominalCode,  // type 3
               1,                       // field count
               kOptRefCode, 2, 1,       // mut optref type2
               0)};                     // supertype
@@ -1277,17 +1054,17 @@
   static const byte mutual_ref_with_subtyping[] = {
       SECTION(Type,
               ENTRY_COUNT(3),          // --
-              kWasmStructSubtypeCode,  //
+              kWasmStructNominalCode,  //
               1,                       // field count
               kOptRefCode, 0, 0,       // ref type0
               kDataRefCode,            // root of hierarchy
 
-              kWasmStructSubtypeCode,  // --
+              kWasmStructNominalCode,  // --
               1,                       // field count
               kOptRefCode, 2, 0,       // ref type2
               0,                       // supertype
 
-              kWasmStructSubtypeCode,  // --
+              kWasmStructNominalCode,  // --
               1,                       // field count
               kOptRefCode, 1, 0,       // ref type1
               0)};                     // supertype
@@ -1295,28 +1072,31 @@
 
   static const byte inheritance_cycle[] = {
       SECTION(Type, ENTRY_COUNT(2),            // --
-              kWasmStructSubtypeCode, 0, 1,    // no fields, supertype 1
-              kWasmStructSubtypeCode, 0, 0)};  // no fields, supertype 0
+              kWasmStructNominalCode, 0, 1,    // no fields, supertype 1
+              kWasmStructNominalCode, 0, 0)};  // no fields, supertype 0
   EXPECT_FAILURE_WITH_MSG(inheritance_cycle, "cyclic inheritance");
 
   static const byte invalid_field[] = {
       SECTION(Type, ENTRY_COUNT(2),                         // --
               kWasmStructTypeCode, U32V_1(1), kI32Code, 1,  // t0: [i32]
-              kWasmStructSubtypeCode, U32V_1(2),            // t1:
+              kWasmStructNominalCode, U32V_1(2),            // t1:
               kI64Code, 1,               // i64 (invalid inheritance)
               kI32Code, 1, U32V_1(0))};  // i32 (added), supertype 0
-  EXPECT_FAILURE_WITH_MSG(invalid_field, "invalid explicit supertype");
+  EXPECT_FAILURE_WITH_MSG(
+      invalid_field, "mixing nominal and isorecursive types is not allowed");
 
   static const byte structural_supertype[] = {
       SECTION(Type, ENTRY_COUNT(2),       // --
               kWasmStructTypeCode, 0,     // empty struct
-              kWasmStructSubtypeCode, 0,  // also empty
+              kWasmStructNominalCode, 0,  // also empty
               0)};                        // supertype is structural type
-  EXPECT_FAILURE_WITH_MSG(structural_supertype, "invalid explicit supertype");
+  EXPECT_FAILURE_WITH_MSG(
+      structural_supertype,
+      "mixing nominal and isorecursive types is not allowed");
 
   static const byte supertype_oob[] = {
       SECTION(Type, ENTRY_COUNT(1),  // --
-              kWasmStructSubtypeCode,
+              kWasmStructNominalCode,
               0,     // empty struct
               13)};  // supertype with invalid index
   EXPECT_FAILURE_WITH_MSG(supertype_oob, "Type index 13 is out of bounds");
@@ -1329,14 +1109,14 @@
 
   static const byte all_good[] = {
       SECTION(Type, ENTRY_COUNT(2),      // --
-              kWasmFunctionSubtypeCode,  // type #0
+              kWasmFunctionNominalCode,  // type #0
               1,                         // params count
               kRefCode, 0,               // ref #0
               1,                         // results count
               kOptRefCode, 0,            // optref #0
               kFuncRefCode,              // root of type hierarchy
 
-              kWasmFunctionSubtypeCode,  // type #1
+              kWasmFunctionNominalCode,  // type #1
               1,                         // params count
               kOptRefCode, 0,            // refined (contravariant)
               1,                         // results count
@@ -1346,7 +1126,7 @@
   ModuleResult result = DecodeModule(all_good, all_good + sizeof(all_good));
   EXPECT_OK(result);
   WasmModule* module = result.value().get();
-  EXPECT_EQ(kGenericSuperType, module->supertype(0));
+  EXPECT_EQ(kNoSuperType, module->supertype(0));
   EXPECT_EQ(0u, module->supertype(1));
 }
 
@@ -3547,6 +3327,7 @@
   EXPECT_NOT_OK(result, "data segments count 0 mismatch (1 expected)");
 }
 
+/* TODO(7748): Add support for rec. groups.
 TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
   WASM_FEATURE_SCOPE(gc);
   WASM_FEATURE_SCOPE(typed_funcref);
@@ -3561,7 +3342,7 @@
       WASM_ARRAY_DEF(WASM_OPT_REF(0), true))};
   ModuleResult result = DecodeModule(data, data + sizeof(data));
   EXPECT_OK(result);
-}
+}*/
 
 TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) {
   WASM_FEATURE_SCOPE(typed_funcref);
diff --git a/test/unittests/wasm/subtyping-unittest.cc b/test/unittests/wasm/subtyping-unittest.cc
index 0e8f67f..ec9e9c0 100644
--- a/test/unittests/wasm/subtyping-unittest.cc
+++ b/test/unittests/wasm/subtyping-unittest.cc
@@ -24,27 +24,30 @@
 FieldInit mut(ValueType type) { return FieldInit(type, true); }
 FieldInit immut(ValueType type) { return FieldInit(type, false); }
 
-void DefineStruct(WasmModule* module, std::initializer_list<FieldInit> fields) {
+void DefineStruct(WasmModule* module, std::initializer_list<FieldInit> fields,
+                  uint32_t supertype = kNoSuperType) {
   StructType::Builder builder(module->signature_zone.get(),
                               static_cast<uint32_t>(fields.size()));
   for (FieldInit field : fields) {
     builder.AddField(field.first, field.second);
   }
-  return module->add_struct_type(builder.Build(), kNoSuperType);
+  return module->add_struct_type(builder.Build(), supertype);
 }
 
-void DefineArray(WasmModule* module, FieldInit element_type) {
+void DefineArray(WasmModule* module, FieldInit element_type,
+                 uint32_t supertype = kNoSuperType) {
   module->add_array_type(module->signature_zone->New<ArrayType>(
                              element_type.first, element_type.second),
-                         kNoSuperType);
+                         supertype);
 }
 
 void DefineSignature(WasmModule* module,
                      std::initializer_list<ValueType> params,
-                     std::initializer_list<ValueType> returns) {
+                     std::initializer_list<ValueType> returns,
+                     uint32_t supertype = kNoSuperType) {
   module->add_signature(
       FunctionSig::Build(module->signature_zone.get(), returns, params),
-      kNoSuperType);
+      supertype);
 }
 
 TEST_F(WasmSubtypingTest, Subtyping) {
@@ -58,22 +61,22 @@
   // Set up two identical modules.
   for (WasmModule* module : {module1, module2}) {
     /*  0 */ DefineStruct(module, {mut(ref(2)), immut(optRef(2))});
-    /*  1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))});
+    /*  1 */ DefineStruct(module, {mut(ref(2)), immut(ref(2))}, 0);
     /*  2 */ DefineArray(module, immut(ref(0)));
-    /*  3 */ DefineArray(module, immut(ref(1)));
-    /*  4 */ DefineStruct(module,
-                          {mut(ref(2)), immut(ref(3)), immut(kWasmF64)});
+    /*  3 */ DefineArray(module, immut(ref(1)), 2);
+    /*  4 */ DefineStruct(module, {mut(ref(2)), immut(ref(3)), immut(kWasmF64)},
+                          1);
     /*  5 */ DefineStruct(module, {mut(optRef(2)), immut(ref(2))});
     /*  6 */ DefineArray(module, mut(kWasmI32));
     /*  7 */ DefineArray(module, immut(kWasmI32));
     /*  8 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
-    /*  9 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))});
+    /*  9 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(8))}, 8);
     /* 10 */ DefineSignature(module, {}, {});
     /* 11 */ DefineSignature(module, {kWasmI32}, {kWasmI32});
     /* 12 */ DefineSignature(module, {kWasmI32, kWasmI32}, {kWasmI32});
     /* 13 */ DefineSignature(module, {ref(1)}, {kWasmI32});
-    /* 14 */ DefineSignature(module, {ref(0)}, {kWasmI32});
-    /* 15 */ DefineSignature(module, {ref(0)}, {ref(4)});
+    /* 14 */ DefineSignature(module, {ref(0)}, {kWasmI32}, 13);
+    /* 15 */ DefineSignature(module, {ref(0)}, {ref(4)}, 16);
     /* 16 */ DefineSignature(module, {ref(0)}, {ref(0)});
   }
 
@@ -86,13 +89,22 @@
 
 #define SUBTYPE(type1, type2) \
   EXPECT_TRUE(IsSubtypeOf(type1, type2, module1, module))
-#define NOT_SUBTYPE(type1, type2) \
-  EXPECT_FALSE(IsSubtypeOf(type1, type2, module1, module))
 #define SUBTYPE_IFF(type1, type2, condition) \
   EXPECT_EQ(IsSubtypeOf(type1, type2, module1, module), condition)
+#define NOT_SUBTYPE(type1, type2) \
+  EXPECT_FALSE(IsSubtypeOf(type1, type2, module1, module))
+// Use only with indexed types.
+#define VALID_SUBTYPE(type1, type2)                                        \
+  EXPECT_TRUE(ValidSubtypeDefinition(type1.ref_index(), type2.ref_index(), \
+                                     module1, module));                    \
+  EXPECT_TRUE(IsSubtypeOf(type1, type2, module1, module));
+#define NOT_VALID_SUBTYPE(type1, type2)                                     \
+  EXPECT_FALSE(ValidSubtypeDefinition(type1.ref_index(), type2.ref_index(), \
+                                      module1, module));
 
   // Type judgements across modules should work the same as within one module.
-  for (WasmModule* module : {module1, module2}) {
+  // TODO(7748): add module2 once we have a cross-module story.
+  for (WasmModule* module : {module1 /* , module2 */}) {
     // Value types are unrelated, except if they are equal.
     for (ValueType subtype : numeric_types) {
       for (ValueType supertype : numeric_types) {
@@ -144,30 +156,30 @@
     }
 
     // Unrelated refs are unrelated.
-    NOT_SUBTYPE(ref(0), ref(2));
-    NOT_SUBTYPE(optRef(3), optRef(1));
+    NOT_VALID_SUBTYPE(ref(0), ref(2));
+    NOT_VALID_SUBTYPE(optRef(3), optRef(1));
     // ref is a subtype of optref for the same struct/array.
-    SUBTYPE(ref(0), optRef(0));
-    SUBTYPE(ref(2), optRef(2));
+    VALID_SUBTYPE(ref(0), optRef(0));
+    VALID_SUBTYPE(ref(2), optRef(2));
     // optref is not a subtype of ref for the same struct/array.
     NOT_SUBTYPE(optRef(0), ref(0));
     NOT_SUBTYPE(optRef(2), ref(2));
     // ref is a subtype of optref if the same is true for the underlying
     // structs/arrays.
-    SUBTYPE(ref(3), optRef(2));
+    VALID_SUBTYPE(ref(3), optRef(2));
     // Prefix subtyping for structs.
-    SUBTYPE(optRef(4), optRef(0));
+    VALID_SUBTYPE(optRef(4), optRef(0));
     // Mutable fields are invariant.
-    NOT_SUBTYPE(ref(0), ref(5));
+    NOT_VALID_SUBTYPE(ref(0), ref(5));
     // Immutable fields are covariant.
-    SUBTYPE(ref(1), ref(0));
+    VALID_SUBTYPE(ref(1), ref(0));
     // Prefix subtyping + immutable field covariance for structs.
-    SUBTYPE(optRef(4), optRef(1));
+    VALID_SUBTYPE(optRef(4), optRef(1));
     // No subtyping between mutable/immutable fields.
-    NOT_SUBTYPE(ref(7), ref(6));
-    NOT_SUBTYPE(ref(6), ref(7));
+    NOT_VALID_SUBTYPE(ref(7), ref(6));
+    NOT_VALID_SUBTYPE(ref(6), ref(7));
     // Recursive types.
-    SUBTYPE(ref(9), ref(8));
+    VALID_SUBTYPE(ref(9), ref(8));
 
     // Identical rtts are subtypes of each other.
     SUBTYPE(ValueType::Rtt(5, 3), ValueType::Rtt(5, 3));
@@ -180,8 +192,9 @@
     NOT_SUBTYPE(ValueType::Rtt(5, 1), ValueType::Rtt(5, 3));
     NOT_SUBTYPE(ValueType::Rtt(5, 8), ValueType::Rtt(5, 3));
     // Rtts of identical types are subtype-related.
-    SUBTYPE(ValueType::Rtt(8, 1), ValueType::Rtt(9, 1));
-    SUBTYPE(ValueType::Rtt(8), ValueType::Rtt(9));
+    // TODO(7748): Implement type canonicalization.
+    // SUBTYPE(ValueType::Rtt(8, 1), ValueType::Rtt(9, 1));
+    // SUBTYPE(ValueType::Rtt(8), ValueType::Rtt(9));
     // Rtts of subtypes are not related.
     NOT_SUBTYPE(ValueType::Rtt(1, 1), ValueType::Rtt(0, 1));
     NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(0));
@@ -190,36 +203,18 @@
       SUBTYPE(ValueType::Rtt(1, depth), ValueType::Rtt(1));
     }
 
-    // Function subtyping depends on the selected wasm features.
-    // Without wasm-gc:
-
+    // Function subtyping;
     // Unrelated function types are unrelated.
-    NOT_SUBTYPE(ref(10), ref(11));
+    NOT_VALID_SUBTYPE(ref(10), ref(11));
     // Function type with different parameter counts are unrelated.
-    NOT_SUBTYPE(ref(12), ref(11));
-    // Parameter contravariance does not hold.
-    NOT_SUBTYPE(ref(14), ref(13));
-    // Return type covariance does not hold.
-    NOT_SUBTYPE(ref(15), ref(16));
-    // Only identical types are subtype-related.
-    SUBTYPE(ref(10), ref(10));
-    SUBTYPE(ref(11), ref(11));
-
-    {
-      // With wasm-gc:
-      EXPERIMENTAL_FLAG_SCOPE(gc);
-      // Unrelated function types are unrelated.
-      NOT_SUBTYPE(ref(10), ref(11));
-      // Function type with different parameter counts are unrelated.
-      NOT_SUBTYPE(ref(12), ref(11));
-      // Parameter contravariance holds.
-      SUBTYPE(ref(14), ref(13));
-      // Return type covariance holds.
-      SUBTYPE(ref(15), ref(16));
-      // Identical types are subtype-related.
-      SUBTYPE(ref(10), ref(10));
-      SUBTYPE(ref(11), ref(11));
-    }
+    NOT_VALID_SUBTYPE(ref(12), ref(11));
+    // Parameter contravariance holds.
+    VALID_SUBTYPE(ref(14), ref(13));
+    // Return type covariance holds.
+    VALID_SUBTYPE(ref(15), ref(16));
+    // Identical types are subtype-related.
+    VALID_SUBTYPE(ref(10), ref(10));
+    VALID_SUBTYPE(ref(11), ref(11));
   }
 #undef SUBTYPE
 #undef NOT_SUBTYPE