Przeglądaj źródła

Remove labels from the builtin inst kind macro (#4558)

This is stamping out the per-instruction structs, similar to what we do
elsewhere. `BuiltinInstKind::label` then finishes shifting to
`InstKind::ir_name`.
Jon Ross-Perkins 1 rok temu
rodzic
commit
be5db6e1cd

+ 4 - 3
toolchain/lower/mangler.cpp

@@ -56,8 +56,9 @@ auto Mangler::MangleInverseQualifiedNameScope(llvm::raw_ostream& os,
         names_to_render.push_back(
             {.name_scope_id = interface.scope_id, .prefix = ':'});
 
-        auto self_inst_id = constant_values().GetConstantInstId(impl.self_id);
-        CARBON_KIND_SWITCH(insts().Get(self_inst_id)) {
+        auto self_inst =
+            insts().Get(constant_values().GetConstantInstId(impl.self_id));
+        CARBON_KIND_SWITCH(self_inst) {
           case CARBON_KIND(SemIR::ClassType class_type): {
             auto next_name_scope_id =
                 sem_ir().classes().Get(class_type.class_id).scope_id;
@@ -76,7 +77,7 @@ auto Mangler::MangleInverseQualifiedNameScope(llvm::raw_ostream& os,
           case SemIR::TypeType::Kind:
           case SemIR::VtableType::Kind:
           case SemIR::WitnessType::Kind: {
-            os << self_inst_id.builtin_inst_kind().label();
+            os << self_inst.kind().ir_name();
             break;
           }
           case CARBON_KIND(SemIR::IntType int_type): {

+ 1 - 9
toolchain/sem_ir/builtin_inst_kind.cpp

@@ -7,17 +7,9 @@
 namespace Carbon::SemIR {
 
 CARBON_DEFINE_ENUM_CLASS_NAMES(BuiltinInstKind) = {
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) \
   CARBON_ENUM_CLASS_NAME_STRING(Name)
 #include "toolchain/sem_ir/inst_kind.def"
 };
 
-auto BuiltinInstKind::label() -> llvm::StringRef {
-  static constexpr llvm::StringLiteral Labels[] = {
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, Label) Label,
-#include "toolchain/sem_ir/inst_kind.def"
-  };
-  return Labels[AsInt()];
-}
-
 }  // namespace Carbon::SemIR

+ 4 - 8
toolchain/sem_ir/builtin_inst_kind.h

@@ -12,22 +12,18 @@
 namespace Carbon::SemIR {
 
 CARBON_DEFINE_RAW_ENUM_CLASS(BuiltinInstKind, uint8_t) {
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
-  CARBON_RAW_ENUM_ENUMERATOR(Name)
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) CARBON_RAW_ENUM_ENUMERATOR(Name)
 #include "toolchain/sem_ir/inst_kind.def"
 };
 
 class BuiltinInstKind : public CARBON_ENUM_BASE(BuiltinInstKind) {
  public:
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
-  CARBON_ENUM_CONSTANT_DECL(Name)
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) CARBON_ENUM_CONSTANT_DECL(Name)
 #include "toolchain/sem_ir/inst_kind.def"
 
-  auto label() -> llvm::StringRef;
-
   // The count of enum values excluding Invalid.
   static constexpr uint8_t ValidCount = 0
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) +1
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) +1
 #include "toolchain/sem_ir/inst_kind.def"
       ;
 
@@ -36,7 +32,7 @@ class BuiltinInstKind : public CARBON_ENUM_BASE(BuiltinInstKind) {
   using EnumBase::FromInt;
 };
 
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) \
   CARBON_ENUM_CONSTANT_DEFINITION(BuiltinInstKind, Name)
 #include "toolchain/sem_ir/inst_kind.def"
 

+ 1 - 1
toolchain/sem_ir/file.cpp

@@ -43,7 +43,7 @@ File::File(CheckIRId check_ir_id, IdentifierId package_id,
 // Error uses a self-referential type so that it's not accidentally treated as
 // a normal type. Every other builtin is a type, including the
 // self-referential TypeType.
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...)                    \
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name)                         \
   insts_.AddInNoBlock(LocIdAndInst::NoLoc<Name>(                      \
       {.type_id = BuiltinInstKind::Name == BuiltinInstKind::ErrorInst \
                       ? TypeId::Error                                 \

+ 0 - 2
toolchain/sem_ir/formatter.cpp

@@ -1021,8 +1021,6 @@ class FormatterImpl {
 
   auto FormatArg(BoolValue v) -> void { out_ << v; }
 
-  auto FormatArg(BuiltinInstKind kind) -> void { out_ << kind.label(); }
-
   auto FormatArg(EntityNameId id) -> void {
     const auto& info = sem_ir_.entity_names().Get(id);
     FormatName(info.name_id);

+ 3 - 4
toolchain/sem_ir/ids.h

@@ -40,8 +40,7 @@ struct InstId : public IdBase, public Printable<InstId> {
   static const InstId Invalid;
 
 // Builtin inst IDs.
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
-  static const InstId Builtin##Name;
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) static const InstId Builtin##Name;
 #include "toolchain/sem_ir/inst_kind.def"
 
   // The namespace for a `package` expression.
@@ -83,8 +82,8 @@ struct InstId : public IdBase, public Printable<InstId> {
 
 constexpr InstId InstId::Invalid = InstId(InvalidIndex);
 
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) \
-  constexpr InstId InstId::Builtin##Name =         \
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) \
+  constexpr InstId InstId::Builtin##Name =    \
       InstId::ForBuiltin(BuiltinInstKind::Name);
 #include "toolchain/sem_ir/inst_kind.def"
 

+ 16 - 55
toolchain/sem_ir/inst_kind.def

@@ -13,9 +13,9 @@
 //
 // Temporarily, we have CARBON_SEM_IR_BUILTIN_INST_KIND too:
 //
-// - CARBON_SEM_IR_BUILTIN_INST_KIND(Name, Label)
-//   Defines a non-Invalid builtin type. The label is used for stringifying
-//   types. Falls back to CARBON_SEM_IR_INST_KIND if not defined.
+// - CARBON_SEM_IR_BUILTIN_INST_KIND(Name)
+//   Defines a non-Invalid builtin type. Falls back to CARBON_SEM_IR_INST_KIND
+//   if not defined.
 //
 // TODO: Merge builtin instructions into the standard CARBON_SEM_IR_INST_KIND,
 // tracking the "builtin" annotation separately. This approach is used for
@@ -25,7 +25,7 @@
 // CARBON_SEM_IR_INST_KIND. However, if it's provided, make
 // CARBON_SEM_IR_INST_KIND optional. Per the above TODO, this is temporary.
 #ifndef CARBON_SEM_IR_BUILTIN_INST_KIND
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, ...) CARBON_SEM_IR_INST_KIND(Name)
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) CARBON_SEM_IR_INST_KIND(Name)
 #else
 #ifndef CARBON_SEM_IR_INST_KIND
 #define CARBON_SEM_IR_INST_KIND(Name)
@@ -36,57 +36,18 @@
 #error "Must define the x-macro to use this file."
 #endif
 
-// Tracks expressions which are valid as types.
-// This has a deliberately self-referential type.
-CARBON_SEM_IR_BUILTIN_INST_KIND(TypeType, "type")
-
-// Used when a semantic error has been detected, and a SemIR InstId is still
-// required. For example, when there is a type checking issue, this will be used
-// in the type_id. It's typically used as a cue that semantic checking doesn't
-// need to issue further diagnostics.
-CARBON_SEM_IR_BUILTIN_INST_KIND(ErrorInst, "<error>")
-
-// Used for the type of patterns that do not match a fixed type.
-CARBON_SEM_IR_BUILTIN_INST_KIND(AutoType, "auto")
-
-// -----------------------------------------------------------------------------
-// TODO: Below CARBON_SEM_IR_BUILTIN_INST_KIND types are all placeholders. While
-// the above may last, the below are expected to need to change in order to
-// better reflect Carbon's design. Keeping distinct placeholders can help find
-// usages for later fixes.
-// -----------------------------------------------------------------------------
-
-// The type of bool literals and branch conditions, bool.
-CARBON_SEM_IR_BUILTIN_INST_KIND(BoolType, "bool")
-
-// An arbitrary-precision integer type, which is used as the type of integer
-// literals and as the parameter type of `Core.Int` and `Core.Float`. This type
-// only provides compile-time operations, and is represented as an empty type at
-// runtime.
-CARBON_SEM_IR_BUILTIN_INST_KIND(IntLiteralType, "Core.IntLiteral")
-
-// The legacy float type. This is currently used for real literals, and is
-// treated as f64. It's separate from `FloatType`, and should change to mirror
-// integers, likely replacing this with a `FloatLiteralType`.
-CARBON_SEM_IR_BUILTIN_INST_KIND(LegacyFloatType, "f64")
-
-// The type of string values and String literals.
-CARBON_SEM_IR_BUILTIN_INST_KIND(StringType, "String")
-
-// The type of bound method values.
-CARBON_SEM_IR_BUILTIN_INST_KIND(BoundMethodType, "<bound method>")
-
-// The type of specific functions.
-CARBON_SEM_IR_BUILTIN_INST_KIND(SpecificFunctionType, "<specific function>")
-
-// The type of namespace and imported package names.
-CARBON_SEM_IR_BUILTIN_INST_KIND(NamespaceType, "<namespace>")
-
-// The type of witnesses.
-CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType, "<witness>")
-
-// The type of virtual function tables
-CARBON_SEM_IR_BUILTIN_INST_KIND(VtableType, "<vtable>")
+CARBON_SEM_IR_BUILTIN_INST_KIND(TypeType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(ErrorInst)
+CARBON_SEM_IR_BUILTIN_INST_KIND(AutoType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(BoolType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(IntLiteralType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(LegacyFloatType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(StringType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(BoundMethodType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(SpecificFunctionType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(NamespaceType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(VtableType)
 
 // For each instruction kind declared here there is a matching definition in
 // `typed_insts.h`.

+ 1 - 1
toolchain/sem_ir/inst_namer.cpp

@@ -146,7 +146,7 @@ auto InstNamer::GetNameFor(ScopeId scope_id, InstId inst_id) const
 
   // Check for a builtin.
   if (inst_id.is_builtin()) {
-    return inst_id.builtin_inst_kind().label().str();
+    return sem_ir_.insts().Get(inst_id).kind().ir_name().str();
   }
 
   if (inst_id == InstId::PackageNamespace) {

+ 16 - 18
toolchain/sem_ir/stringify_type.cpp

@@ -76,12 +76,6 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       continue;
     }
 
-    // Builtins have designated labels.
-    if (step.inst_id.is_builtin()) {
-      out << step.inst_id.builtin_inst_kind().label();
-      continue;
-    }
-
     const auto& sem_ir = step.sem_ir;
     // Helper for instructions with the current sem_ir.
     auto push_inst_id = [&](InstId inst_id) {
@@ -120,6 +114,22 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
 
     auto untyped_inst = sem_ir.insts().Get(step.inst_id);
     CARBON_KIND_SWITCH(untyped_inst) {
+      case SemIR::AutoType::Kind:
+      case SemIR::BoolType::Kind:
+      case SemIR::BoundMethodType::Kind:
+      case SemIR::ErrorInst::Kind:
+      case SemIR::IntLiteralType::Kind:
+      case SemIR::LegacyFloatType::Kind:
+      case SemIR::NamespaceType::Kind:
+      case SemIR::SpecificFunctionType::Kind:
+      case SemIR::StringType::Kind:
+      case SemIR::TypeType::Kind:
+      case SemIR::VtableType::Kind:
+      case SemIR::WitnessType::Kind: {
+        // Builtin instructions use their IR name as a label.
+        out << untyped_inst.kind().ir_name();
+        break;
+      }
       case CARBON_KIND(ArrayType inst): {
         if (step.index == 0) {
           out << "[";
@@ -346,16 +356,13 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       case Assign::Kind:
       case AssociatedConstantDecl::Kind:
       case AssociatedEntity::Kind:
-      case AutoType::Kind:
       case BaseDecl::Kind:
       case BindName::Kind:
       case BindValue::Kind:
       case BindingPattern::Kind:
       case BlockArg::Kind:
       case BoolLiteral::Kind:
-      case BoolType::Kind:
       case BoundMethod::Kind:
-      case BoundMethodType::Kind:
       case Branch::Kind:
       case BranchIf::Kind:
       case BranchWithArg::Kind:
@@ -366,7 +373,6 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       case CompleteTypeWitness::Kind:
       case Converted::Kind:
       case Deref::Kind:
-      case ErrorInst::Kind:
       case FieldDecl::Kind:
       case FloatLiteral::Kind:
       case FunctionDecl::Kind:
@@ -375,14 +381,11 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       case ImportRefLoaded::Kind:
       case ImportRefUnloaded::Kind:
       case InitializeFrom::Kind:
-      case IntLiteralType::Kind:
       case IntValue::Kind:
       case InterfaceDecl::Kind:
       case InterfaceWitness::Kind:
       case InterfaceWitnessAccess::Kind:
-      case LegacyFloatType::Kind:
       case Namespace::Kind:
-      case NamespaceType::Kind:
       case OutParam::Kind:
       case OutParamPattern::Kind:
       case RequirementEquivalent::Kind:
@@ -394,10 +397,8 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       case ReturnSlotPattern::Kind:
       case SpecificConstant::Kind:
       case SpecificFunction::Kind:
-      case SpecificFunctionType::Kind:
       case SpliceBlock::Kind:
       case StringLiteral::Kind:
-      case StringType::Kind:
       case StructAccess::Kind:
       case StructInit::Kind:
       case StructLiteral::Kind:
@@ -409,15 +410,12 @@ auto StringifyTypeExpr(const SemIR::File& outer_sem_ir, InstId outer_inst_id)
       case TupleInit::Kind:
       case TupleLiteral::Kind:
       case TupleValue::Kind:
-      case TypeType::Kind:
       case UnaryOperatorNot::Kind:
       case ValueAsRef::Kind:
       case ValueOfInitializer::Kind:
       case ValueParam::Kind:
       case ValueParamPattern::Kind:
       case VarStorage::Kind:
-      case VtableType::Kind:
-      case WitnessType::Kind:
         // We don't know how to print this instruction, but it might have a
         // constant value that we can print.
         auto const_inst_id =

+ 170 - 12
toolchain/sem_ir/typed_insts.h

@@ -45,19 +45,32 @@
 
 namespace Carbon::SemIR {
 
-// A builtin instruction, corresponding to instructions like
-// InstId::BuiltinTypeType.
+// Used for the type of patterns that do not match a fixed type.
 //
-// Builtins don't have a parse node associated with them.
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name, Label)                          \
-  struct Name {                                                               \
-    static constexpr auto Kind = InstKind::Name.Define<Parse::InvalidNodeId>( \
-        {.ir_name = Label,                                                    \
-         .is_type = InstIsType::Always,                                       \
-         .constant_kind = InstConstantKind::Always});                         \
-    TypeId type_id;                                                           \
-  };
-#include "toolchain/sem_ir/inst_kind.def"
+// TODO: Annotate as a builtin.
+struct AutoType {
+  static constexpr auto Kind = InstKind::AutoType.Define<Parse::InvalidNodeId>(
+      {.ir_name = "auto",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
+// The type of bool literals and branch conditions, bool.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+//
+// TODO: Annotate as a builtin.
+struct BoolType {
+  static constexpr auto Kind = InstKind::BoolType.Define<Parse::InvalidNodeId>(
+      {.ir_name = "bool",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
 
 // An adapted type declaration in a class, of the form `adapt T;`.
 struct AdaptDecl {
@@ -370,6 +383,20 @@ struct BoundMethod {
   InstId function_id;
 };
 
+// The type of bound method values.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct BoundMethodType {
+  static constexpr auto Kind =
+      InstKind::BoundMethodType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "<bound method>",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // Common representation for all kinds of `Branch*` node.
 struct AnyBranch {
   static constexpr InstKind Kinds[] = {InstKind::Branch, InstKind::BranchIf,
@@ -535,6 +562,21 @@ struct Deref {
   InstId pointer_id;
 };
 
+// Used when a semantic error has been detected, and a SemIR InstId is still
+// required. For example, when there is a type checking issue, this will be used
+// in the type_id. It's typically used as a cue that semantic checking doesn't
+// need to issue further diagnostics.
+//
+// TODO: Annotate as a builtin.
+struct ErrorInst {
+  static constexpr auto Kind = InstKind::ErrorInst.Define<Parse::InvalidNodeId>(
+      {.ir_name = "<error>",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // An `export bind_name` declaration.
 struct ExportDecl {
   static constexpr auto Kind =
@@ -624,6 +666,22 @@ struct FloatType {
   InstId bit_width_id;
 };
 
+// The legacy float type. This is currently used for real literals, and is
+// treated as f64. It's separate from `FloatType`, and should change to mirror
+// integers, likely replacing this with a `FloatLiteralType`.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct LegacyFloatType {
+  static constexpr auto Kind =
+      InstKind::LegacyFloatType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "f64",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // A function declaration.
 struct FunctionDecl {
   static constexpr auto Kind =
@@ -801,6 +859,23 @@ struct IntValue {
   IntId int_id;
 };
 
+// An arbitrary-precision integer type, which is used as the type of integer
+// literals and as the parameter type of `Core.Int` and `Core.Float`. This type
+// only provides compile-time operations, and is represented as an empty type at
+// runtime.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct IntLiteralType {
+  static constexpr auto Kind =
+      InstKind::IntLiteralType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "Core.IntLiteral",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // An integer type.
 struct IntType {
   static constexpr auto Kind = InstKind::IntType.Define<Parse::InvalidNodeId>(
@@ -840,6 +915,20 @@ struct Namespace {
   AbsoluteInstId import_id;
 };
 
+// The type of namespace and imported package names.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct NamespaceType {
+  static constexpr auto Kind =
+      InstKind::NamespaceType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "<namespace>",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // A parameter for a function or other parameterized block, as exposed in the
 // SemIR calling convention. The sub-kinds differ only in their expression
 // category.
@@ -1057,6 +1146,20 @@ struct SpecificFunction {
   SpecificId specific_id;
 };
 
+// The type of specific functions.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct SpecificFunctionType {
+  static constexpr auto Kind =
+      InstKind::SpecificFunctionType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "<specific function>",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // Splices a block into the location where this appears. This may be an
 // expression, producing a result with a given type. For example, when
 // constructing from aggregates we may figure out which conversions are required
@@ -1081,6 +1184,20 @@ struct StringLiteral {
   StringLiteralValueId string_literal_id;
 };
 
+// The type of string values and String literals.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct StringType {
+  static constexpr auto Kind =
+      InstKind::StringType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "String",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // Access to a struct type, with the index into the struct_id representation.
 struct StructAccess {
   // TODO: Make Parse::NodeId more specific.
@@ -1208,6 +1325,19 @@ struct TupleValue {
   InstBlockId elements_id;
 };
 
+// Tracks expressions which are valid as types. This has a deliberately
+// self-referential type.
+//
+// TODO: Annotate as a builtin.
+struct TypeType {
+  static constexpr auto Kind = InstKind::TypeType.Define<Parse::InvalidNodeId>(
+      {.ir_name = "type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // The `not` operator, such as `not operand`.
 struct UnaryOperatorNot {
   // TODO: Make Parse::NodeId more specific.
@@ -1270,6 +1400,20 @@ struct VarStorage {
   NameId name_id;
 };
 
+// The type of virtual function tables.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct VtableType {
+  static constexpr auto Kind =
+      InstKind::VtableType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "<vtable>",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // An `expr where requirements` expression.
 struct WhereExpr {
   static constexpr auto Kind = InstKind::WhereExpr.Define<Parse::WhereExprId>(
@@ -1284,6 +1428,20 @@ struct WhereExpr {
   InstBlockId requirements_id;
 };
 
+// The type of witnesses.
+//
+// Although this is a builtin, it may still evolve to a more standard type and
+// be removed.
+struct WitnessType {
+  static constexpr auto Kind =
+      InstKind::WitnessType.Define<Parse::InvalidNodeId>(
+          {.ir_name = "<witness>",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // These concepts are an implementation detail of the library, not public API.
 namespace Internal {