Przeglądaj źródła

Stop using `ImplWitness[Table]` for a C++ synthesized witness. (#6451)

Add a `CppWitness` and use it instead of using `ImplWitness` with an
`ImplId` and `SpecificId` of `None`. This witness can be substantially
simpler because we never need a `SpecificId`.
Richard Smith 4 miesięcy temu
rodzic
commit
7fd62ff58d

+ 28 - 26
toolchain/check/cpp/impl_lookup.cpp

@@ -66,30 +66,30 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id,
     return SemIR::ErrorInst::InstId;
   }
 
-  // Prepare an empty witness table.
-  auto witness_table_id =
-      context.inst_blocks().AddUninitialized(assoc_entities.size());
-  auto witness_table = context.inst_blocks().GetMutable(witness_table_id);
-  for (auto& witness_value_id : witness_table) {
-    witness_value_id = SemIR::InstId::ImplWitnessTablePlaceholder;
-  }
-
-  // Build a witness. We use an `ImplWitness` with an `impl_id` of `None` to
-  // represent a synthesized witness.
-  // TODO: Stop using `ImplWitnessTable` here and add a distinct instruction
-  // that doesn't contain an `InstId` and supports deduplication.
-  auto witness_table_inst_id = AddInst<SemIR::ImplWitnessTable>(
-      context, loc_id,
-      {.elements_id = witness_table_id, .impl_id = SemIR::ImplId::None});
-  auto witness_id = AddInst<SemIR::ImplWitness>(
-      context, loc_id,
-      {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
-       .witness_table_id = witness_table_inst_id,
-       .specific_id = SemIR::SpecificId::None});
+  llvm::SmallVector<SemIR::InstId> entries;
+
+  // Build a witness with the current contents of the witness table. This will
+  // grow as we progress through the impl. In theory this will build O(n^2)
+  // table entries, but in practice n <= 2, so that's OK.
+  //
+  // This is necessary because later associated entities may refer to earlier
+  // associated entities in their signatures. In particular, an associated
+  // result type may be used as the return type of an associated function.
+  //
+  // TODO: Consider building one witness after all associated constants, and
+  // then a second after all associated functions, rather than building one at
+  // each step. For now this doesn't really matter since we don't have more than
+  // one of each anyway.
+  auto make_witness = [&] {
+    return context.constant_values().GetInstId(EvalOrAddInst<SemIR::CppWitness>(
+        context, loc_id,
+        {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
+         .elements_id = context.inst_blocks().Add(entries)}));
+  };
 
   // Fill in the witness table.
-  for (const auto& [assoc_entity_id, value_id, witness_value_id] :
-       llvm::zip_equal(assoc_entities, values, witness_table)) {
+  for (const auto& [assoc_entity_id, value_id] :
+       llvm::zip_equal(assoc_entities, values)) {
     LoadImportRef(context, assoc_entity_id);
     auto decl_id =
         context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific(
@@ -104,11 +104,13 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id,
         // TODO: If a thunk is needed, this will build a different value each
         // time it's called, so we won't properly deduplicate repeated
         // witnesses.
-        witness_value_id = CheckAssociatedFunctionImplementation(
+        // TODO: Skip calling make_witness if this function signature doesn't
+        // involve `Self`.
+        entries.push_back(CheckAssociatedFunctionImplementation(
             context,
             context.types().GetAs<SemIR::FunctionType>(struct_value.type_id),
-            value_id, self_type_id, witness_id,
-            /*defer_thunk_definition=*/false);
+            value_id, self_type_id, make_witness(),
+            /*defer_thunk_definition=*/false));
         break;
       }
       case SemIR::AssociatedConstantDecl::Kind: {
@@ -123,7 +125,7 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id,
     }
   }
 
-  return witness_id;
+  return make_witness();
 }
 
 static auto LookupCopyImpl(Context& context, SemIR::LocId loc_id,

+ 19 - 3
toolchain/check/eval_inst.cpp

@@ -316,6 +316,8 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
 
 auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
                       SemIR::ImplWitnessAccess inst) -> ConstantEvalResult {
+  CARBON_DIAGNOSTIC(ImplAccessMemberBeforeSet, Error,
+                    "accessing member from impl before it has a defined value");
   if (auto witness =
           context.insts().TryGetAs<SemIR::ImplWitness>(inst.witness_id)) {
     // This is PerformAggregateAccess followed by GetConstantValueInSpecific.
@@ -334,12 +336,26 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
             context.sem_ir(), witness->specific_id, element));
       }
     }
-    CARBON_DIAGNOSTIC(
-        ImplAccessMemberBeforeSet, Error,
-        "accessing member from impl before it has a defined value");
+    // If we get here, this impl witness table entry has not been populated yet,
+    // because the impl was referenced within its own definition.
     // TODO: Add note pointing to the impl declaration.
     context.emitter().Emit(inst_id, ImplAccessMemberBeforeSet);
     return ConstantEvalResult::Error;
+  } else if (auto cpp_witness =
+                 context.insts().TryGetAs<SemIR::CppWitness>(inst.witness_id)) {
+    auto elements = context.inst_blocks().Get(cpp_witness->elements_id);
+    auto index = static_cast<size_t>(inst.index.index);
+    // `elements` can be shorter than the number of associated entities while
+    // we're building the synthetic witness.
+    if (index < elements.size()) {
+      return ConstantEvalResult::Existing(
+          context.constant_values().Get(elements[index]));
+    }
+    // If we get here, this synthesized witness table entry has not been
+    // populated yet.
+    // TODO: Is this reachable? We have no test coverage for this diagnostic.
+    context.emitter().Emit(inst_id, ImplAccessMemberBeforeSet);
+    return ConstantEvalResult::Error;
   } else if (auto witness = context.insts().TryGetAs<SemIR::LookupImplWitness>(
                  inst.witness_id)) {
     // If the witness is symbolic but has a self type that is a FacetType, it

+ 31 - 35
toolchain/check/testdata/interop/cpp/impls/copy.carbon

@@ -200,11 +200,11 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT:   %ptr.e47: type = ptr_type %Copyable [concrete]
 // CHECK:STDOUT:   %Copyable__carbon_thunk.type: type = fn_type @Copyable__carbon_thunk [concrete]
 // CHECK:STDOUT:   %Copyable__carbon_thunk: %Copyable__carbon_thunk.type = struct_value () [concrete]
-// CHECK:STDOUT:   %impl_witness.65e: <witness> = impl_witness @CopyCopyable.%impl_witness_table [concrete]
-// CHECK:STDOUT:   %Copy.facet.26f: %Copy.type = facet_value %Copyable, (%impl_witness.65e) [concrete]
 // CHECK:STDOUT:   %Copyable.Op.type: type = fn_type @Copyable.Op [concrete]
 // CHECK:STDOUT:   %Copyable.Op: %Copyable.Op.type = struct_value () [concrete]
-// CHECK:STDOUT:   %.667: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.26f [concrete]
+// CHECK:STDOUT:   %cpp_witness.524: <witness> = cpp_witness (%Copyable.Op) [concrete]
+// CHECK:STDOUT:   %Copy.facet.2cd: %Copy.type = facet_value %Copyable, (%cpp_witness.524) [concrete]
+// CHECK:STDOUT:   %.dc1: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.2cd [concrete]
 // CHECK:STDOUT:   %ExplicitCopy: type = class_type @ExplicitCopy [concrete]
 // CHECK:STDOUT:   %ExplicitCopy.ExplicitCopy.type: type = fn_type @ExplicitCopy.ExplicitCopy [concrete]
 // CHECK:STDOUT:   %ExplicitCopy.ExplicitCopy: %ExplicitCopy.ExplicitCopy.type = struct_value () [concrete]
@@ -213,11 +213,11 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT:   %ptr.84c: type = ptr_type %ExplicitCopy [concrete]
 // CHECK:STDOUT:   %ExplicitCopy__carbon_thunk.type: type = fn_type @ExplicitCopy__carbon_thunk [concrete]
 // CHECK:STDOUT:   %ExplicitCopy__carbon_thunk: %ExplicitCopy__carbon_thunk.type = struct_value () [concrete]
-// CHECK:STDOUT:   %impl_witness.215: <witness> = impl_witness @CopyExplicitCopy.%impl_witness_table [concrete]
-// CHECK:STDOUT:   %Copy.facet.cfa: %Copy.type = facet_value %ExplicitCopy, (%impl_witness.215) [concrete]
 // CHECK:STDOUT:   %ExplicitCopy.Op.type: type = fn_type @ExplicitCopy.Op [concrete]
 // CHECK:STDOUT:   %ExplicitCopy.Op: %ExplicitCopy.Op.type = struct_value () [concrete]
-// CHECK:STDOUT:   %.a87: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.cfa [concrete]
+// CHECK:STDOUT:   %cpp_witness.b38: <witness> = cpp_witness (%ExplicitCopy.Op) [concrete]
+// CHECK:STDOUT:   %Copy.facet.27f: %Copy.type = facet_value %ExplicitCopy, (%cpp_witness.b38) [concrete]
+// CHECK:STDOUT:   %.82a: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.27f [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -246,10 +246,8 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT: fn @CopyCopyable(%c.param: %Copyable) -> %return.param: %Copyable {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: %Copyable = name_ref c, %c
-// CHECK:STDOUT:   %impl_witness_table = impl_witness_table (%Copyable.Op.decl), invalid [concrete]
-// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness %impl_witness_table [concrete = constants.%impl_witness.65e]
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %impl.elem0: %.667 = impl_witness_access constants.%impl_witness.65e, element0 [concrete = constants.%Copyable.Op]
+// CHECK:STDOUT:   %impl.elem0: %.dc1 = impl_witness_access constants.%cpp_witness.524, element0 [concrete = constants.%Copyable.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %c.ref, %impl.elem0
 // CHECK:STDOUT:   %.loc8_10.1: ref %Copyable = temporary_storage
 // CHECK:STDOUT:   %Op.ref: %Copyable.Copyable.type = name_ref Op, imports.%Copyable.Copyable.decl [concrete = constants.%Copyable.Copyable]
@@ -267,10 +265,8 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT: fn @CopyExplicitCopy(%c.param: %ExplicitCopy) -> %return.param: %ExplicitCopy {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: %ExplicitCopy = name_ref c, %c
-// CHECK:STDOUT:   %impl_witness_table = impl_witness_table (%ExplicitCopy.Op.decl), invalid [concrete]
-// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness %impl_witness_table [concrete = constants.%impl_witness.215]
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %impl.elem0: %.a87 = impl_witness_access constants.%impl_witness.215, element0 [concrete = constants.%ExplicitCopy.Op]
+// CHECK:STDOUT:   %impl.elem0: %.82a = impl_witness_access constants.%cpp_witness.b38, element0 [concrete = constants.%ExplicitCopy.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %c.ref, %impl.elem0
 // CHECK:STDOUT:   %.loc14_10.1: ref %ExplicitCopy = temporary_storage
 // CHECK:STDOUT:   %Op.ref: %ExplicitCopy.ExplicitCopy.type = name_ref Op, imports.%ExplicitCopy.ExplicitCopy.decl [concrete = constants.%ExplicitCopy.ExplicitCopy]
@@ -296,17 +292,19 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT:   %ptr.as.Copy.impl.Op.type.75b: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%T.d9f) [symbolic]
 // CHECK:STDOUT:   %ptr.as.Copy.impl.Op.692: %ptr.as.Copy.impl.Op.type.75b = struct_value () [symbolic]
 // CHECK:STDOUT:   %Copyable: type = class_type @Copyable [concrete]
-// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness @DoCopy.%impl_witness_table [concrete]
-// CHECK:STDOUT:   %Copy.facet.26f: %Copy.type.705 = facet_value %Copyable, (%impl_witness) [concrete]
-// CHECK:STDOUT:   %Copy.specific_fn: <specific function> = specific_function %Copy, @Copy.loc6(%Copy.facet.26f) [concrete]
-// CHECK:STDOUT:   %Wrap.248: type = class_type @Wrap, @Wrap(%Copy.facet.26f) [concrete]
-// CHECK:STDOUT:   %ptr.510: type = ptr_type %Wrap.248 [concrete]
-// CHECK:STDOUT:   %Copy.impl_witness.cab: <witness> = impl_witness imports.%Copy.impl_witness_table.67d, @ptr.as.Copy.impl(%Wrap.248) [concrete]
-// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.type.b1b: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%Wrap.248) [concrete]
-// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.ea7: %ptr.as.Copy.impl.Op.type.b1b = struct_value () [concrete]
-// CHECK:STDOUT:   %Copy.facet.5be: %Copy.type.705 = facet_value %ptr.510, (%Copy.impl_witness.cab) [concrete]
-// CHECK:STDOUT:   %.e13: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.5be [concrete]
-// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %ptr.as.Copy.impl.Op.ea7, @ptr.as.Copy.impl.Op(%Wrap.248) [concrete]
+// CHECK:STDOUT:   %Copyable.Op.type: type = fn_type @Copyable.Op [concrete]
+// CHECK:STDOUT:   %Copyable.Op: %Copyable.Op.type = struct_value () [concrete]
+// CHECK:STDOUT:   %cpp_witness.524: <witness> = cpp_witness (%Copyable.Op) [concrete]
+// CHECK:STDOUT:   %Copy.facet.2cd: %Copy.type.705 = facet_value %Copyable, (%cpp_witness.524) [concrete]
+// CHECK:STDOUT:   %Copy.specific_fn: <specific function> = specific_function %Copy, @Copy.loc6(%Copy.facet.2cd) [concrete]
+// CHECK:STDOUT:   %Wrap.380: type = class_type @Wrap, @Wrap(%Copy.facet.2cd) [concrete]
+// CHECK:STDOUT:   %ptr.ca9: type = ptr_type %Wrap.380 [concrete]
+// CHECK:STDOUT:   %Copy.impl_witness.c72: <witness> = impl_witness imports.%Copy.impl_witness_table.67d, @ptr.as.Copy.impl(%Wrap.380) [concrete]
+// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.type.4f5: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%Wrap.380) [concrete]
+// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.c1c: %ptr.as.Copy.impl.Op.type.4f5 = struct_value () [concrete]
+// CHECK:STDOUT:   %Copy.facet.fd9: %Copy.type.705 = facet_value %ptr.ca9, (%Copy.impl_witness.c72) [concrete]
+// CHECK:STDOUT:   %.127: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.fd9 [concrete]
+// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %ptr.as.Copy.impl.Op.c1c, @ptr.as.Copy.impl.Op(%Wrap.380) [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -318,27 +316,25 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Copy.ref: %Copy.type.6f0 = name_ref Copy, file.%Copy.decl [concrete = constants.%Copy]
 // CHECK:STDOUT:   %c.ref: %Copyable = name_ref c, %c
-// CHECK:STDOUT:   %impl_witness_table = impl_witness_table (%Copyable.Op.decl), invalid [concrete]
-// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness %impl_witness_table [concrete = constants.%impl_witness]
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %Copy.facet.loc12_16.1: %Copy.type.705 = facet_value constants.%Copyable, (constants.%impl_witness) [concrete = constants.%Copy.facet.26f]
-// CHECK:STDOUT:   %.loc12_16.1: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.1 [concrete = constants.%Copy.facet.26f]
-// CHECK:STDOUT:   %Copy.facet.loc12_16.2: %Copy.type.705 = facet_value constants.%Copyable, (constants.%impl_witness) [concrete = constants.%Copy.facet.26f]
-// CHECK:STDOUT:   %.loc12_16.2: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.2 [concrete = constants.%Copy.facet.26f]
-// CHECK:STDOUT:   %Copy.specific_fn: <specific function> = specific_function %Copy.ref, @Copy.loc6(constants.%Copy.facet.26f) [concrete = constants.%Copy.specific_fn]
+// CHECK:STDOUT:   %Copy.facet.loc12_16.1: %Copy.type.705 = facet_value constants.%Copyable, (constants.%cpp_witness.524) [concrete = constants.%Copy.facet.2cd]
+// CHECK:STDOUT:   %.loc12_16.1: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.1 [concrete = constants.%Copy.facet.2cd]
+// CHECK:STDOUT:   %Copy.facet.loc12_16.2: %Copy.type.705 = facet_value constants.%Copyable, (constants.%cpp_witness.524) [concrete = constants.%Copy.facet.2cd]
+// CHECK:STDOUT:   %.loc12_16.2: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.2 [concrete = constants.%Copy.facet.2cd]
+// CHECK:STDOUT:   %Copy.specific_fn: <specific function> = specific_function %Copy.ref, @Copy.loc6(constants.%Copy.facet.2cd) [concrete = constants.%Copy.specific_fn]
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Copy.call: init %Copyable = call %Copy.specific_fn(%c.ref) to %.loc10_28
 // CHECK:STDOUT:   return %Copy.call to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @EqualWitnesses(%p.param: %ptr.510) -> %ptr.510 {
+// CHECK:STDOUT: fn @EqualWitnesses(%p.param: %ptr.ca9) -> %ptr.ca9 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %p.ref: %ptr.510 = name_ref p, %p
-// CHECK:STDOUT:   %impl.elem0: %.e13 = impl_witness_access constants.%Copy.impl_witness.cab, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ea7]
+// CHECK:STDOUT:   %p.ref: %ptr.ca9 = name_ref p, %p
+// CHECK:STDOUT:   %impl.elem0: %.127 = impl_witness_access constants.%Copy.impl_witness.c72, element0 [concrete = constants.%ptr.as.Copy.impl.Op.c1c]
 // CHECK:STDOUT:   %bound_method.loc20_10.1: <bound method> = bound_method %p.ref, %impl.elem0
-// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%Wrap.248) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
+// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%Wrap.380) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc20_10.2: <bound method> = bound_method %p.ref, %specific_fn
-// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.call: init %ptr.510 = call %bound_method.loc20_10.2(%p.ref)
+// CHECK:STDOUT:   %ptr.as.Copy.impl.Op.call: init %ptr.ca9 = call %bound_method.loc20_10.2(%p.ref)
 // CHECK:STDOUT:   return %ptr.as.Copy.impl.Op.call to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -55,6 +55,7 @@ CARBON_SEM_IR_INST_KIND(ConvertToValueAction)
 CARBON_SEM_IR_INST_KIND(Converted)
 CARBON_SEM_IR_INST_KIND(CppOverloadSetType)
 CARBON_SEM_IR_INST_KIND(CppOverloadSetValue)
+CARBON_SEM_IR_INST_KIND(CppWitness)
 CARBON_SEM_IR_INST_KIND(CustomLayoutType)
 CARBON_SEM_IR_INST_KIND(Deref)
 CARBON_SEM_IR_INST_KIND(ErrorInst)

+ 4 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -907,6 +907,10 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddInstName("const");
       return;
     }
+    case CppWitness::Kind: {
+      AddInstName("cpp_witness");
+      return;
+    }
     case CARBON_KIND(FacetAccessType inst): {
       auto name_id = SemIR::NameId::None;
       if (auto name =

+ 20 - 3
toolchain/sem_ir/typed_insts.h

@@ -754,6 +754,22 @@ struct CppOverloadSetValue {
   CppOverloadSetId overload_set_id;
 };
 
+// A witness synthesized for a C++ construct such as a constructor, conversion
+// function, or overloaded operator.
+struct CppWitness {
+  static constexpr auto Kind = InstKind::CppWitness.Define<Parse::NodeId>(
+      {.ir_name = "cpp_witness",
+       .constant_kind = InstConstantKind::Always,
+       // TODO: For dynamic dispatch, we might want to lower witness tables as
+       // constants.
+       .is_lowered = false});
+
+  // Always the type of the builtin `WitnessType` singleton instruction.
+  TypeId type_id;
+  // The witness table of instructions.
+  InstBlockId elements_id;
+};
+
 // The type of the name of a generic class. The corresponding value is an empty
 // `StructValue`.
 struct GenericClassType {
@@ -2066,9 +2082,10 @@ struct WhereExpr {
   InstBlockId requirements_id;
 };
 
-// The type of `ImplWitness` and `LookupImplWitness` instructions. The latter
-// will evaluate at some point during specific computation into the former, and
-// their types should not change in the process.
+// The type of `ImplWitness`, `CppWitness`, and `LookupImplWitness`
+// instructions. The latter will evaluate at some point during specific
+// computation into one of the former two, and their types should not change in
+// the process.
 //
 // Also the type of `RequireCompleteType` instructions.
 //