Переглянути джерело

Refactor InstKind to move metadata from macros to the type. (#4119)

This adds `DefinitionInfo` for `Define`-based configuration so that
parameters are optional. It also makes it easier to provide the
equivalent functions on both `Definition` and `Define`.

A common pattern used here is to change from a `switch` with in-line
`case`s to instead have `case`s that call an overloaded function. What's
happening here is that the instruction type is used to select an
overload, and if an overload is not defined, a compiler error would
result. Meanwhile, clusters of overloads are being defined using
`requires`-based templating, so that equivalent implementations are not
copied. This addresses a limitation of a vanilla `switch` approach where
it's hard to have redundant cases using conditional logic, while also
getting compiler errors when adding new `InstKind` entries, which had
been a significant part of why we used macros previously.

This starts hitting some odd clang-format edge cases causing
`CARBON_KIND_SWITCH(inst){` (missing space), which I haven't seen
before. Adding `CARBON_KIND_SWITCH` to .clang-format works around it.
Jon Ross-Perkins 1 рік тому
батько
коміт
469f1c8e64

+ 5 - 1
.clang-format

@@ -15,5 +15,9 @@ PointerAlignment: Left
 # We abuse control macros for formatting other kinds of macros.
 SpaceBeforeParens: ControlStatementsExceptControlMacros
 IfMacros:
-  ['CARBON_DEFINE_RAW_ENUM_CLASS', 'CARBON_DEFINE_RAW_ENUM_CLASS_NO_NAMES']
+  [
+    'CARBON_DEFINE_RAW_ENUM_CLASS',
+    'CARBON_DEFINE_RAW_ENUM_CLASS_NO_NAMES',
+    'CARBON_KIND_SWITCH',
+  ]
 StatementMacros: ['ABSTRACT']

+ 84 - 71
toolchain/check/context.cpp

@@ -856,7 +856,7 @@ class TypeCompleter {
     return value_rep;
   }
 
-  auto BuildBuiltinValueRepr(SemIR::TypeId type_id,
+  auto BuildValueReprForInst(SemIR::TypeId type_id,
                              SemIR::BuiltinInst builtin) const
       -> SemIR::ValueRepr {
     switch (builtin.builtin_inst_kind) {
@@ -901,8 +901,8 @@ class TypeCompleter {
     return MakePointerValueRepr(elementwise_rep, aggregate_kind);
   }
 
-  auto BuildStructTypeValueRepr(SemIR::TypeId type_id,
-                                SemIR::StructType struct_type) const
+  auto BuildValueReprForInst(SemIR::TypeId type_id,
+                             SemIR::StructType struct_type) const
       -> SemIR::ValueRepr {
     // TODO: Share more code with tuples.
     auto fields = context_.inst_blocks().Get(struct_type.fields_id);
@@ -935,8 +935,8 @@ class TypeCompleter {
                                        same_as_object_rep);
   }
 
-  auto BuildTupleTypeValueRepr(SemIR::TypeId type_id,
-                               SemIR::TupleType tuple_type) const
+  auto BuildValueReprForInst(SemIR::TypeId type_id,
+                             SemIR::TupleType tuple_type) const
       -> SemIR::ValueRepr {
     // TODO: Share more code with structs.
     auto elements = context_.type_blocks().Get(tuple_type.elements_id);
@@ -964,78 +964,91 @@ class TypeCompleter {
                                        same_as_object_rep);
   }
 
-  // Builds and returns the value representation for the given type. All nested
-  // types, as found by AddNestedIncompleteTypes, are known to be complete.
-  auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const
+  auto BuildValueReprForInst(SemIR::TypeId type_id,
+                             SemIR::ArrayType /*inst*/) const
       -> SemIR::ValueRepr {
-    CARBON_KIND_SWITCH(inst) {
-#define CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS(...)
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-#define CARBON_SEM_IR_INST_KIND(Name) case SemIR::Name::Kind:
-#include "toolchain/sem_ir/inst_kind.def"
-      CARBON_FATAL() << "Type refers to non-type inst " << inst;
+    // For arrays, it's convenient to always use a pointer representation,
+    // even when the array has zero or one element, in order to support
+    // indexing.
+    return MakePointerValueRepr(type_id, SemIR::ValueRepr::ObjectAggregate);
+  }
 
-      case SemIR::ArrayType::Kind: {
-        // For arrays, it's convenient to always use a pointer representation,
-        // even when the array has zero or one element, in order to support
-        // indexing.
-        return MakePointerValueRepr(type_id, SemIR::ValueRepr::ObjectAggregate);
-      }
+  auto BuildValueReprForInst(SemIR::TypeId /*type_id*/,
+                             SemIR::ClassType inst) const -> SemIR::ValueRepr {
+    auto& class_info = context_.classes().Get(inst.class_id);
+    // The value representation of an adapter is the value representation of
+    // its adapted type.
+    if (class_info.adapt_id.is_valid()) {
+      return GetNestedValueRepr(class_info.object_repr_id);
+    }
+    // Otherwise, the value representation for a class is a pointer to the
+    // object representation.
+    // TODO: Support customized value representations for classes.
+    // TODO: Pick a better value representation when possible.
+    return MakePointerValueRepr(class_info.object_repr_id,
+                                SemIR::ValueRepr::ObjectAggregate);
+  }
 
-      case CARBON_KIND(SemIR::StructType struct_type): {
-        return BuildStructTypeValueRepr(type_id, struct_type);
-      }
-      case CARBON_KIND(SemIR::TupleType tuple_type): {
-        return BuildTupleTypeValueRepr(type_id, tuple_type);
-      }
-      case CARBON_KIND(SemIR::ClassType class_type): {
-        auto& class_info = context_.classes().Get(class_type.class_id);
-        // The value representation of an adapter is the value representation of
-        // its adapted type.
-        if (class_info.adapt_id.is_valid()) {
-          return GetNestedValueRepr(class_info.object_repr_id);
-        }
-        // Otherwise, the value representation for a class is a pointer to the
-        // object representation.
-        // TODO: Support customized value representations for classes.
-        // TODO: Pick a better value representation when possible.
-        return MakePointerValueRepr(class_info.object_repr_id,
-                                    SemIR::ValueRepr::ObjectAggregate);
-      }
-      case SemIR::AssociatedEntityType::Kind:
-      case SemIR::FunctionType::Kind:
-      case SemIR::GenericClassType::Kind:
-      case SemIR::GenericInterfaceType::Kind:
-      case SemIR::InterfaceType::Kind:
-      case SemIR::UnboundElementType::Kind: {
-        // These types have no runtime operations, so we use an empty value
-        // representation.
-        //
-        // TODO: There is information we could model here:
-        // - For an interface, we could use a witness.
-        // - For an associated entity, we could use an index into the witness.
-        // - For an unbound element, we could use an index or offset.
-        return MakeEmptyValueRepr();
-      }
-      case CARBON_KIND(SemIR::BuiltinInst builtin): {
-        return BuildBuiltinValueRepr(type_id, builtin);
-      }
+  template <typename InstT>
+    requires(InstT::Kind.template IsAnyOf<
+             SemIR::AssociatedEntityType, SemIR::FunctionType,
+             SemIR::GenericClassType, SemIR::GenericInterfaceType,
+             SemIR::InterfaceType, SemIR::UnboundElementType>())
+  auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const
+      -> SemIR::ValueRepr {
+    // These types have no runtime operations, so we use an empty value
+    // representation.
+    //
+    // TODO: There is information we could model here:
+    // - For an interface, we could use a witness.
+    // - For an associated entity, we could use an index into the witness.
+    // - For an unbound element, we could use an index or offset.
+    return MakeEmptyValueRepr();
+  }
 
-      case SemIR::BindSymbolicName::Kind:
-      case SemIR::InterfaceWitnessAccess::Kind:
-        // For symbolic types, we arbitrarily pick a copy representation.
-        return MakeCopyValueRepr(type_id);
+  template <typename InstT>
+    requires(InstT::Kind.template IsAnyOf<SemIR::BindSymbolicName,
+                                          SemIR::InterfaceWitnessAccess>())
+  auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const
+      -> SemIR::ValueRepr {
+    // For symbolic types, we arbitrarily pick a copy representation.
+    return MakeCopyValueRepr(type_id);
+  }
 
-      case SemIR::FloatType::Kind:
-      case SemIR::IntType::Kind:
-      case SemIR::PointerType::Kind:
-        return MakeCopyValueRepr(type_id);
+  template <typename InstT>
+    requires(InstT::Kind.template IsAnyOf<SemIR::FloatType, SemIR::IntType,
+                                          SemIR::PointerType>())
+  auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const
+      -> SemIR::ValueRepr {
+    return MakeCopyValueRepr(type_id);
+  }
 
-      case CARBON_KIND(SemIR::ConstType const_type): {
-        // The value representation of `const T` is the same as that of `T`.
-        // Objects are not modifiable through their value representations.
-        return GetNestedValueRepr(const_type.inner_id);
-      }
+  auto BuildValueReprForInst(SemIR::TypeId /*type_id*/,
+                             SemIR::ConstType inst) const -> SemIR::ValueRepr {
+    // The value representation of `const T` is the same as that of `T`.
+    // Objects are not modifiable through their value representations.
+    return GetNestedValueRepr(inst.inner_id);
+  }
+
+  template <typename InstT>
+    requires(InstT::Kind.is_type() == SemIR::InstIsType::Never)
+  auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT inst) const
+      -> SemIR::ValueRepr {
+    CARBON_FATAL() << "Type refers to non-type inst " << inst;
+  }
+
+  // Builds and returns the value representation for the given type. All nested
+  // types, as found by AddNestedIncompleteTypes, are known to be complete.
+  auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const
+      -> SemIR::ValueRepr {
+    // Use overload resolution to select the implementation, producing compile
+    // errors when BuildTypeForInst isn't defined for a given instruction.
+    CARBON_KIND_SWITCH(inst) {
+#define CARBON_SEM_IR_INST_KIND(Name)                  \
+  case CARBON_KIND(SemIR::Name typed_inst): {          \
+    return BuildValueReprForInst(type_id, typed_inst); \
+  }
+#include "toolchain/sem_ir/inst_kind.def"
     }
   }
 

+ 51 - 60
toolchain/lower/constant.cpp

@@ -82,24 +82,6 @@ class ConstantContext {
   int32_t last_lowered_constant_index_ = -1;
 };
 
-// For each instruction kind that can produce a constant, there is a function
-// below to convert it to an `llvm::Constant*`:
-//
-// auto Emit<InstKind>AsConstant(ConstantContext& context,
-//                               SemIR::<InstKind> inst) -> llvm::Constant*;
-
-// For constants that are always of type `type`, produce the trivial runtime
-// representation of type `type`.
-#define CARBON_SEM_IR_INST_KIND_TYPE_NEVER(...)
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY(...)
-#define CARBON_SEM_IR_INST_KIND(Name)                                      \
-  static auto Emit##Name##AsConstant(                                      \
-      ConstantContext& context, SemIR::Name /*inst*/) -> llvm::Constant* { \
-    return context.GetTypeAsValue();                                       \
-  }
-#include "toolchain/sem_ir/inst_kind.def"
-
 // Emits an aggregate constant of LLVM type `Type` whose elements are the
 // contents of `refs_id`.
 template <typename ConstantType, typename Type>
@@ -116,16 +98,37 @@ static auto EmitAggregateConstant(ConstantContext& context,
   return ConstantType::get(llvm_type, elements);
 }
 
-static auto EmitStructValueAsConstant(ConstantContext& context,
-                                      SemIR::StructValue inst)
+// For each instruction InstT, there is a function below to convert it to an
+// `llvm::Constant*`:
+//
+// auto EmitAsConstant(ConstantContext& context, SemIR::InstT inst)
+//     -> llvm::Constant*;
+
+template <typename InstT>
+  requires(InstT::Kind.constant_kind() == SemIR::InstConstantKind::Never ||
+           InstT::Kind.constant_kind() == SemIR::InstConstantKind::SymbolicOnly)
+static auto EmitAsConstant(ConstantContext& /*context*/, InstT inst)
+    -> llvm::Constant* {
+  CARBON_FATAL() << "Unexpected constant instruction kind " << inst;
+}
+
+// For constants that are always of type `type`, produce the trivial runtime
+// representation of type `type`.
+template <typename InstT>
+  requires(InstT::Kind.is_type() == SemIR::InstIsType::Always)
+static auto EmitAsConstant(ConstantContext& context, InstT /*inst*/)
+    -> llvm::Constant* {
+  return context.GetTypeAsValue();
+}
+
+static auto EmitAsConstant(ConstantContext& context, SemIR::StructValue inst)
     -> llvm::Constant* {
   return EmitAggregateConstant<llvm::ConstantStruct>(
       context, inst.elements_id,
       cast<llvm::StructType>(context.GetType(inst.type_id)));
 }
 
-static auto EmitTupleValueAsConstant(ConstantContext& context,
-                                     SemIR::TupleValue inst)
+static auto EmitAsConstant(ConstantContext& context, SemIR::TupleValue inst)
     -> llvm::Constant* {
   // TODO: Add an ArrayValue instruction and stop using TupleValues to represent
   // array constants.
@@ -140,80 +143,72 @@ static auto EmitTupleValueAsConstant(ConstantContext& context,
       cast<llvm::StructType>(context.GetType(inst.type_id)));
 }
 
-static auto EmitAddrOfAsConstant(ConstantContext& /*context*/,
-                                 SemIR::AddrOf /*inst*/) -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& /*context*/, SemIR::AddrOf /*inst*/)
+    -> llvm::Constant* {
   // TODO: Constant lvalue support. For now we have no constant lvalues, so we
   // should never form a constant AddrOf.
   CARBON_FATAL() << "AddrOf constants not supported yet";
 }
 
-static auto EmitAssociatedEntityAsConstant(ConstantContext& context,
-                                           SemIR::AssociatedEntity inst)
-    -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& context,
+                           SemIR::AssociatedEntity inst) -> llvm::Constant* {
   return context.GetUnusedConstant(inst.type_id);
 }
 
-static auto EmitBaseDeclAsConstant(ConstantContext& context,
-                                   SemIR::BaseDecl inst) -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& context, SemIR::BaseDecl inst)
+    -> llvm::Constant* {
   return context.GetUnusedConstant(inst.type_id);
 }
 
-static auto EmitBoolLiteralAsConstant(ConstantContext& context,
-                                      SemIR::BoolLiteral inst)
+static auto EmitAsConstant(ConstantContext& context, SemIR::BoolLiteral inst)
     -> llvm::Constant* {
   return llvm::ConstantInt::get(llvm::Type::getInt1Ty(context.llvm_context()),
                                 inst.value.index);
 }
 
-static auto EmitBoundMethodAsConstant(ConstantContext& context,
-                                      SemIR::BoundMethod inst)
+static auto EmitAsConstant(ConstantContext& context, SemIR::BoundMethod inst)
     -> llvm::Constant* {
   // Propagate just the function; the object is separately provided to the
   // enclosing call as an implicit argument.
   return context.GetConstant(inst.function_id);
 }
 
-static auto EmitFieldDeclAsConstant(ConstantContext& context,
-                                    SemIR::FieldDecl inst) -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& context, SemIR::FieldDecl inst)
+    -> llvm::Constant* {
   return context.GetUnusedConstant(inst.type_id);
 }
 
-static auto EmitFloatLiteralAsConstant(ConstantContext& context,
-                                       SemIR::FloatLiteral inst)
+static auto EmitAsConstant(ConstantContext& context, SemIR::FloatLiteral inst)
     -> llvm::Constant* {
   const llvm::APFloat& value = context.sem_ir().floats().Get(inst.float_id);
   return llvm::ConstantFP::get(context.GetType(inst.type_id), value);
 }
 
-static auto EmitInterfaceWitnessAsConstant(ConstantContext& context,
-                                           SemIR::InterfaceWitness inst)
-    -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& context,
+                           SemIR::InterfaceWitness inst) -> llvm::Constant* {
   // TODO: For dynamic dispatch, we might want to lower witness tables as
   // constants.
   return context.GetUnusedConstant(inst.type_id);
 }
 
-static auto EmitIntLiteralAsConstant(ConstantContext& context,
-                                     SemIR::IntLiteral inst)
+static auto EmitAsConstant(ConstantContext& context, SemIR::IntLiteral inst)
     -> llvm::Constant* {
   return llvm::ConstantInt::get(context.GetType(inst.type_id),
                                 context.sem_ir().ints().Get(inst.int_id));
 }
 
-static auto EmitNamespaceAsConstant(ConstantContext& context,
-                                    SemIR::Namespace inst) -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& context, SemIR::Namespace inst)
+    -> llvm::Constant* {
   return context.GetUnusedConstant(inst.type_id);
 }
 
-static auto EmitStringLiteralAsConstant(ConstantContext& /*context*/,
-                                        SemIR::StringLiteral inst)
-    -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& /*context*/,
+                           SemIR::StringLiteral inst) -> llvm::Constant* {
   CARBON_FATAL() << "TODO: Add support: " << inst;
 }
 
-static auto EmitStructTypeFieldAsConstant(ConstantContext& /*context*/,
-                                          SemIR::StructTypeField /*inst*/)
-    -> llvm::Constant* {
+static auto EmitAsConstant(ConstantContext& /*context*/,
+                           SemIR::StructTypeField /*inst*/) -> llvm::Constant* {
   // A StructTypeField isn't a value, so this constant value won't ever be used.
   // It also doesn't even have a type, so we can't use GetUnusedConstant.
   return nullptr;
@@ -240,21 +235,17 @@ auto LowerConstants(FileContext& file_context,
     auto inst = file_context.sem_ir().insts().Get(inst_id);
     llvm::Constant* value = nullptr;
     CARBON_KIND_SWITCH(inst) {
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER(...)
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY(...)
-#define CARBON_SEM_IR_INST_KIND(Name)                    \
-  case CARBON_KIND(SemIR::Name const_inst):              \
-    value = Emit##Name##AsConstant(context, const_inst); \
-    break;
+#define CARBON_SEM_IR_INST_KIND(Name)            \
+  case CARBON_KIND(SemIR::Name const_inst): {    \
+    value = EmitAsConstant(context, const_inst); \
+    break;                                       \
+  }
 #include "toolchain/sem_ir/inst_kind.def"
-
-      default:
-        CARBON_FATAL() << "Unexpected constant instruction kind " << inst;
     }
 
     constants[inst_id.index] = value;
     context.SetLastLoweredConstantIndex(inst_id.index);
   }
-}
+}  // namespace Carbon::Lower
 
 }  // namespace Carbon::Lower

+ 133 - 97
toolchain/lower/file_context.cpp

@@ -309,107 +309,143 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id)
   }
 }
 
-auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
-  CARBON_KIND_SWITCH(sem_ir_->insts().Get(inst_id)) {
-    case CARBON_KIND(SemIR::ArrayType inst): {
-      return llvm::ArrayType::get(GetType(inst.element_type_id),
-                                  sem_ir_->GetArrayBoundValue(inst.bound_id));
-    }
-    case CARBON_KIND(SemIR::BuiltinInst inst): {
-      switch (inst.builtin_inst_kind) {
-        case SemIR::BuiltinInstKind::Invalid:
-        case SemIR::BuiltinInstKind::Error:
-          CARBON_FATAL() << "Unexpected builtin type in lowering.";
-        case SemIR::BuiltinInstKind::TypeType:
-          return GetTypeType();
-        case SemIR::BuiltinInstKind::FloatType:
-          return llvm::Type::getDoubleTy(*llvm_context_);
-        case SemIR::BuiltinInstKind::IntType:
-          return llvm::Type::getInt32Ty(*llvm_context_);
-        case SemIR::BuiltinInstKind::BoolType:
-          // TODO: We may want to have different representations for `bool`
-          // storage
-          // (`i8`) versus for `bool` values (`i1`).
-          return llvm::Type::getInt1Ty(*llvm_context_);
-        case SemIR::BuiltinInstKind::StringType:
-          // TODO: Decide how we want to represent `StringType`.
-          return llvm::PointerType::get(*llvm_context_, 0);
-        case SemIR::BuiltinInstKind::BoundMethodType:
-        case SemIR::BuiltinInstKind::NamespaceType:
-        case SemIR::BuiltinInstKind::WitnessType:
-          // Return an empty struct as a placeholder.
-          return llvm::StructType::get(*llvm_context_);
-      }
-    }
-    case CARBON_KIND(SemIR::ClassType inst): {
-      auto object_repr_id =
-          sem_ir_->classes().Get(inst.class_id).object_repr_id;
-      return GetType(object_repr_id);
-    }
-    case CARBON_KIND(SemIR::ConstType inst): {
-      return GetType(inst.inner_id);
-    }
-    case SemIR::FloatType::Kind: {
-      // TODO: Handle different sizes.
-      return llvm::Type::getDoubleTy(*llvm_context_);
-    }
-    case CARBON_KIND(SemIR::IntType inst): {
-      auto width =
-          sem_ir_->insts().TryGetAs<SemIR::IntLiteral>(inst.bit_width_id);
-      CARBON_CHECK(width) << "Can't lower int type with symbolic width";
-      return llvm::IntegerType::get(
-          *llvm_context_, sem_ir_->ints().Get(width->int_id).getZExtValue());
-    }
-    case SemIR::PointerType::Kind: {
-      return llvm::PointerType::get(*llvm_context_, /*AddressSpace=*/0);
-    }
-    case CARBON_KIND(SemIR::StructType inst): {
-      auto fields = sem_ir_->inst_blocks().Get(inst.fields_id);
-      llvm::SmallVector<llvm::Type*> subtypes;
-      subtypes.reserve(fields.size());
-      for (auto field_id : fields) {
-        auto field = sem_ir_->insts().GetAs<SemIR::StructTypeField>(field_id);
-        subtypes.push_back(GetType(field.field_type_id));
-      }
-      return llvm::StructType::get(*llvm_context_, subtypes);
-    }
-    case CARBON_KIND(SemIR::TupleType inst): {
-      // TODO: Investigate special-casing handling of empty tuples so that they
-      // can be collectively replaced with LLVM's void, particularly around
-      // function returns. LLVM doesn't allow declaring variables with a void
-      // type, so that may require significant special casing.
-      auto elements = sem_ir_->type_blocks().Get(inst.elements_id);
-      llvm::SmallVector<llvm::Type*> subtypes;
-      subtypes.reserve(elements.size());
-      for (auto element_id : elements) {
-        subtypes.push_back(GetType(element_id));
-      }
-      return llvm::StructType::get(*llvm_context_, subtypes);
-    }
-    case SemIR::AssociatedEntityType::Kind:
-    case SemIR::InterfaceType::Kind:
-    case SemIR::FunctionType::Kind:
-    case SemIR::GenericClassType::Kind:
-    case SemIR::GenericInterfaceType::Kind:
-    case SemIR::UnboundElementType::Kind: {
+static auto BuildTypeForInst(FileContext& context, SemIR::ArrayType inst)
+    -> llvm::Type* {
+  return llvm::ArrayType::get(
+      context.GetType(inst.element_type_id),
+      context.sem_ir().GetArrayBoundValue(inst.bound_id));
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::BuiltinInst inst)
+    -> llvm::Type* {
+  switch (inst.builtin_inst_kind) {
+    case SemIR::BuiltinInstKind::Invalid:
+    case SemIR::BuiltinInstKind::Error:
+      CARBON_FATAL() << "Unexpected builtin type in lowering.";
+    case SemIR::BuiltinInstKind::TypeType:
+      return context.GetTypeType();
+    case SemIR::BuiltinInstKind::FloatType:
+      return llvm::Type::getDoubleTy(context.llvm_context());
+    case SemIR::BuiltinInstKind::IntType:
+      return llvm::Type::getInt32Ty(context.llvm_context());
+    case SemIR::BuiltinInstKind::BoolType:
+      // TODO: We may want to have different representations for `bool`
+      // storage
+      // (`i8`) versus for `bool` values (`i1`).
+      return llvm::Type::getInt1Ty(context.llvm_context());
+    case SemIR::BuiltinInstKind::StringType:
+      // TODO: Decide how we want to represent `StringType`.
+      return llvm::PointerType::get(context.llvm_context(), 0);
+    case SemIR::BuiltinInstKind::BoundMethodType:
+    case SemIR::BuiltinInstKind::NamespaceType:
+    case SemIR::BuiltinInstKind::WitnessType:
       // Return an empty struct as a placeholder.
-      // TODO: Should we model an interface as a witness table, or an associated
-      // entity as an index?
-      return llvm::StructType::get(*llvm_context_);
-    }
+      return llvm::StructType::get(context.llvm_context());
+  }
+}
 
-    // Treat non-monomorphized symbolic types as opaque.
-    case SemIR::BindSymbolicName::Kind:
-    case SemIR::InterfaceWitnessAccess::Kind: {
-      return llvm::StructType::get(*llvm_context_);
-    }
+// BuildTypeForInst is used to construct types for FileContext::BuildType below.
+// Implementations return the LLVM type for the instruction. This first overload
+// is the fallback handler for non-type instructions.
+template <typename InstT>
+  requires(InstT::Kind.is_type() == SemIR::InstIsType::Never)
+static auto BuildTypeForInst(FileContext& /*context*/, InstT inst)
+    -> llvm::Type* {
+  CARBON_FATAL() << "Cannot use inst as type: " << inst;
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::ClassType inst)
+    -> llvm::Type* {
+  auto object_repr_id =
+      context.sem_ir().classes().Get(inst.class_id).object_repr_id;
+  return context.GetType(object_repr_id);
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::ConstType inst)
+    -> llvm::Type* {
+  return context.GetType(inst.inner_id);
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::FloatType /*inst*/)
+    -> llvm::Type* {
+  // TODO: Handle different sizes.
+  return llvm::Type::getDoubleTy(context.llvm_context());
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::IntType inst)
+    -> llvm::Type* {
+  auto width =
+      context.sem_ir().insts().TryGetAs<SemIR::IntLiteral>(inst.bit_width_id);
+  CARBON_CHECK(width) << "Can't lower int type with symbolic width";
+  return llvm::IntegerType::get(
+      context.llvm_context(),
+      context.sem_ir().ints().Get(width->int_id).getZExtValue());
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::PointerType /*inst*/)
+    -> llvm::Type* {
+  return llvm::PointerType::get(context.llvm_context(), /*AddressSpace=*/0);
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::StructType inst)
+    -> llvm::Type* {
+  auto fields = context.sem_ir().inst_blocks().Get(inst.fields_id);
+  llvm::SmallVector<llvm::Type*> subtypes;
+  subtypes.reserve(fields.size());
+  for (auto field_id : fields) {
+    auto field =
+        context.sem_ir().insts().GetAs<SemIR::StructTypeField>(field_id);
+    subtypes.push_back(context.GetType(field.field_type_id));
+  }
+  return llvm::StructType::get(context.llvm_context(), subtypes);
+}
+
+static auto BuildTypeForInst(FileContext& context, SemIR::TupleType inst)
+    -> llvm::Type* {
+  // TODO: Investigate special-casing handling of empty tuples so that they
+  // can be collectively replaced with LLVM's void, particularly around
+  // function returns. LLVM doesn't allow declaring variables with a void
+  // type, so that may require significant special casing.
+  auto elements = context.sem_ir().type_blocks().Get(inst.elements_id);
+  llvm::SmallVector<llvm::Type*> subtypes;
+  subtypes.reserve(elements.size());
+  for (auto element_id : elements) {
+    subtypes.push_back(context.GetType(element_id));
+  }
+  return llvm::StructType::get(context.llvm_context(), subtypes);
+}
+
+template <typename InstT>
+  requires(InstT::Kind.template IsAnyOf<
+           SemIR::AssociatedEntityType, SemIR::FunctionType,
+           SemIR::GenericClassType, SemIR::GenericInterfaceType,
+           SemIR::InterfaceType, SemIR::UnboundElementType>())
+static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
+    -> llvm::Type* {
+  // Return an empty struct as a placeholder.
+  // TODO: Should we model an interface as a witness table, or an associated
+  // entity as an index?
+  return llvm::StructType::get(context.llvm_context());
+}
 
-#define CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS(...)
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-#define CARBON_SEM_IR_INST_KIND(Name) case SemIR::Name::Kind:
+// Treat non-monomorphized symbolic types as opaque.
+template <typename InstT>
+  requires(InstT::Kind.template IsAnyOf<SemIR::BindSymbolicName,
+                                        SemIR::InterfaceWitnessAccess>())
+static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
+    -> llvm::Type* {
+  return llvm::StructType::get(context.llvm_context());
+}
+
+auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
+  // Use overload resolution to select the implementation, producing compile
+  // errors when BuildTypeForInst isn't defined for a given instruction.
+  CARBON_KIND_SWITCH(sem_ir_->insts().Get(inst_id)) {
+#define CARBON_SEM_IR_INST_KIND(Name)     \
+  case CARBON_KIND(SemIR::Name inst): {   \
+    return BuildTypeForInst(*this, inst); \
+  }
 #include "toolchain/sem_ir/inst_kind.def"
-      CARBON_FATAL() << "Cannot use inst as type: " << inst_id << " "
-                     << sem_ir_->insts().Get(inst_id);
   }
 }
 

+ 9 - 9
toolchain/lower/file_context.h

@@ -49,6 +49,15 @@ class FileContext {
   // Returns a global value for the given instruction.
   auto GetGlobal(SemIR::InstId inst_id) -> llvm::Value*;
 
+  // Returns the empty LLVM struct type used to represent the type `type`.
+  auto GetTypeType() -> llvm::StructType* {
+    if (!type_type_) {
+      // `type` is lowered to an empty LLVM StructType.
+      type_type_ = llvm::StructType::create(*llvm_context_, {}, "type");
+    }
+    return type_type_;
+  }
+
   auto llvm_context() -> llvm::LLVMContext& { return *llvm_context_; }
   auto llvm_module() -> llvm::Module& { return *llvm_module_; }
   auto sem_ir() -> const SemIR::File& { return *sem_ir_; }
@@ -67,15 +76,6 @@ class FileContext {
   // the caller.
   auto BuildType(SemIR::InstId inst_id) -> llvm::Type*;
 
-  // Returns the empty LLVM struct type used to represent the type `type`.
-  auto GetTypeType() -> llvm::StructType* {
-    if (!type_type_) {
-      // `type` is lowered to an empty LLVM StructType.
-      type_type_ = llvm::StructType::create(*llvm_context_, {}, "type");
-    }
-    return type_type_;
-  }
-
   // State for building the LLVM IR.
   llvm::LLVMContext* llvm_context_;
   std::unique_ptr<llvm::Module> llvm_module_;

+ 29 - 14
toolchain/lower/function_context.cpp

@@ -60,6 +60,17 @@ static auto FatalErrorIfEncountered(InstT inst) -> void {
       << inst;
 }
 
+// For instructions that are always of type `type`, produce the trivial runtime
+// representation of type `type`.
+static auto SetTrivialType(FunctionContext& context, SemIR::InstId inst_id)
+    -> void {
+  context.SetLocal(inst_id, context.GetTypeAsValue());
+}
+
+// TODO: Consider renaming Handle##Name, instead relying on typed_inst overload
+// resolution. That would allow putting the nonexistent handler implementations
+// in `requires`-style overloads.
+// NOLINTNEXTLINE(readability-function-size): The define confuses lint.
 auto FunctionContext::LowerInst(SemIR::InstId inst_id) -> void {
   // Skip over constants. `FileContext::GetGlobal` lowers them as needed.
   if (sem_ir().constant_values().Get(inst_id).is_constant()) {
@@ -69,25 +80,29 @@ auto FunctionContext::LowerInst(SemIR::InstId inst_id) -> void {
   auto inst = sem_ir().insts().Get(inst_id);
   CARBON_VLOG() << "Lowering " << inst_id << ": " << inst << "\n";
   builder_.getInserter().SetCurrentInstId(inst_id);
+
   CARBON_KIND_SWITCH(inst) {
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(Name)
-#define CARBON_SEM_IR_INST_KIND(Name)               \
-  case CARBON_KIND(SemIR::Name typed_inst): {       \
-    if constexpr (SemIR::Name::Kind.is_lowered()) { \
-      Handle##Name(*this, inst_id, typed_inst);     \
-    } else {                                        \
-      FatalErrorIfEncountered(typed_inst);          \
-    }                                               \
-    break;                                          \
+#define CARBON_SEM_IR_INST_KIND(Name)                                      \
+  case CARBON_KIND(SemIR::Name typed_inst): {                              \
+    if constexpr (!SemIR::Name::Kind.is_lowered()) {                       \
+      FatalErrorIfEncountered(typed_inst);                                 \
+    } else if constexpr (SemIR::Name::Kind.constant_kind() ==              \
+                         SemIR::InstConstantKind::Always) {                \
+      CARBON_FATAL() << "Missing constant value for constant instruction " \
+                     << inst;                                              \
+    } else if constexpr (SemIR::Name::Kind.is_type() ==                    \
+                         SemIR::InstIsType::Always) {                      \
+      SetTrivialType(*this, inst_id);                                      \
+    } else {                                                               \
+      Handle##Name(*this, inst_id, typed_inst);                            \
+    }                                                                      \
+    break;                                                                 \
   }
 #include "toolchain/sem_ir/inst_kind.def"
-
-    default:
-      CARBON_FATAL() << "Missing constant value for constant instruction "
-                     << inst;
   }
+
   builder_.getInserter().SetCurrentInstId(SemIR::InstId::Invalid);
-}  // namespace Carbon::Lower
+}
 
 auto FunctionContext::GetBlockArg(SemIR::InstBlockId block_id,
                                   SemIR::TypeId type_id) -> llvm::PHINode* {

+ 2 - 3
toolchain/lower/function_context.h

@@ -159,9 +159,8 @@ class FunctionContext {
   Map<SemIR::InstId, llvm::Value*> locals_;
 };
 
-// Declare handlers for each SemIR::File instruction that is not always
-// constant.
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(Name)
+// Declare handlers for each SemIR::File instruction. Note that these aren't all
+// defined.
 #define CARBON_SEM_IR_INST_KIND(Name)                                \
   auto Handle##Name(FunctionContext& context, SemIR::InstId inst_id, \
                     SemIR::Name inst) -> void;

+ 5 - 0
toolchain/lower/handle.cpp

@@ -160,6 +160,11 @@ auto HandleDeref(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetValue(inst.pointer_id));
 }
 
+auto HandleFacetTypeAccess(FunctionContext& context, SemIR::InstId inst_id,
+                           SemIR::FacetTypeAccess /*inst*/) -> void {
+  context.SetLocal(inst_id, context.GetTypeAsValue());
+}
+
 auto HandleInitializeFrom(FunctionContext& context, SemIR::InstId /*inst_id*/,
                           SemIR::InitializeFrom inst) -> void {
   auto storage_type_id = context.sem_ir().insts().Get(inst.dest_id).type_id();

+ 0 - 27
toolchain/lower/handle_type.cpp

@@ -1,27 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "toolchain/lower/function_context.h"
-#include "toolchain/sem_ir/typed_insts.h"
-
-namespace Carbon::Lower {
-
-// For instructions that are always of type `type`, produce the trivial runtime
-// representation of type `type`.
-#define CARBON_SEM_IR_INST_KIND_TYPE_NEVER(...)
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(...)
-#define CARBON_SEM_IR_INST_KIND(Name)                                \
-  auto Handle##Name(FunctionContext& context, SemIR::InstId inst_id, \
-                    SemIR::Name /*inst*/) -> void {                  \
-    context.SetLocal(inst_id, context.GetTypeAsValue());             \
-  }
-#include "toolchain/sem_ir/inst_kind.def"
-
-auto HandleFacetTypeAccess(FunctionContext& context, SemIR::InstId inst_id,
-                           SemIR::FacetTypeAccess /*inst*/) -> void {
-  context.SetLocal(inst_id, context.GetTypeAsValue());
-}
-
-}  // namespace Carbon::Lower

+ 8 - 27
toolchain/sem_ir/file.cpp

@@ -176,34 +176,15 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
 // precedence of that type's syntax. Higher numbers correspond to higher
 // precedence.
 static auto GetTypePrecedence(InstKind kind) -> int {
-  switch (kind) {
-    case ArrayType::Kind:
-    case AssociatedEntityType::Kind:
-    case BindSymbolicName::Kind:
-    case BuiltinInst::Kind:
-    case ClassType::Kind:
-    case FloatType::Kind:
-    case FunctionType::Kind:
-    case GenericClassType::Kind:
-    case GenericInterfaceType::Kind:
-    case InterfaceType::Kind:
-    case InterfaceWitnessAccess::Kind:
-    case IntType::Kind:
-    case StructType::Kind:
-    case TupleType::Kind:
-    case UnboundElementType::Kind:
-      return 0;
-    case ConstType::Kind:
-      return -1;
-    case PointerType::Kind:
-      return -2;
-
-#define CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS(...)
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-#define CARBON_SEM_IR_INST_KIND(Name) case SemIR::Name::Kind:
-#include "toolchain/sem_ir/inst_kind.def"
-      CARBON_FATAL() << "GetTypePrecedence for non-type inst kind " << kind;
+  CARBON_CHECK(kind.is_type() != InstIsType::Never)
+      << "Only called for kinds which can define a type.";
+  if (kind == ConstType::Kind) {
+    return -1;
+  }
+  if (kind == PointerType::Kind) {
+    return -2;
   }
+  return 0;
 }
 
 // Implements File::StringifyTypeExpr. Static to prevent accidental use of

+ 4 - 26
toolchain/sem_ir/inst_kind.cpp

@@ -13,12 +13,12 @@ CARBON_DEFINE_ENUM_CLASS_NAMES(InstKind) = {
 #include "toolchain/sem_ir/inst_kind.def"
 };
 
-auto InstKind::ir_name() const -> llvm::StringLiteral {
-  static constexpr const llvm::StringLiteral Table[] = {
-#define CARBON_SEM_IR_INST_KIND(Name) SemIR::Name::Kind.ir_name(),
+auto InstKind::definition_info(InstKind inst_kind) -> const DefinitionInfo& {
+  static constexpr InstKind::DefinitionInfo DefinitionInfos[] = {
+#define CARBON_SEM_IR_INST_KIND(Name) SemIR::Name::Kind.info_,
 #include "toolchain/sem_ir/inst_kind.def"
   };
-  return Table[AsInt()];
+  return DefinitionInfos[inst_kind.AsInt()];
 }
 
 auto InstKind::value_kind() const -> InstValueKind {
@@ -31,26 +31,4 @@ auto InstKind::value_kind() const -> InstValueKind {
   return Table[AsInt()];
 }
 
-auto InstKind::constant_kind() const -> InstConstantKind {
-  static constexpr InstConstantKind Table[] = {
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER(...) InstConstantKind::Never,
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY(...) \
-  InstConstantKind::SymbolicOnly,
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_CONDITIONAL(...) \
-  InstConstantKind::Conditional,
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(...) InstConstantKind::Always,
-#define CARBON_SEM_IR_INST_KIND(Name)
-#include "toolchain/sem_ir/inst_kind.def"
-  };
-  return Table[AsInt()];
-}
-
-auto InstKind::terminator_kind() const -> TerminatorKind {
-  static constexpr const TerminatorKind Table[] = {
-#define CARBON_SEM_IR_INST_KIND(Name) SemIR::Name::Kind.terminator_kind(),
-#include "toolchain/sem_ir/inst_kind.def"
-  };
-  return Table[AsInt()];
-}
-
 }  // namespace Carbon::SemIR

+ 78 - 148
toolchain/sem_ir/inst_kind.def

@@ -10,160 +10,90 @@
 // This macro should be defined before including this header:
 // - CARBON_SEM_IR_INST_KIND(Name)
 //   Invoked for each kind of semantic instruction.
-//
-// The invocation of the above macro will be wrapped in one macro from each of
-// the following sets, which by default expand to their argument:
-//
-// Whether the instruction can define a type:
-// - CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS(...)
-//   Invoked for each instruction that is always of type `type`, and might
-//   define a type constant.
-// - CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...)
-//   Invoked for each instruction that is sometimes of type `type`, and might
-//   define a type constant.
-// - CARBON_SEM_IR_INST_KIND_TYPE_NEVER(...)
-//   Invoked for each instruction that can never define a type constant. Note
-//   that such instructions can still have type `type`, but are not the
-//   canonical definition of any type.
-//
-// Whether the instruction can define a constant, see `InstConstantKind`:
-// - CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER(...)
-//   Invoked when `constant_kind()` is `InstConstantKind::Never`.
-// - CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY(...)
-//   Invoked when `constant_kind()` is `InstConstantKind::SymbolicOnly`.
-// - CARBON_SEM_IR_INST_KIND_CONSTANT_CONDITIONAL(...)
-//   Invoked when `constant_kind()` is `InstConstantKind::Conditional`.
-// - CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(...)
-//   Invoked when `constant_kind()` is `InstConstantKind::Always`.
-//
-// Defining these is optional.
 
 #ifndef CARBON_SEM_IR_INST_KIND
 #error "Must define the x-macro to use this file."
 #endif
 
-#ifndef CARBON_SEM_IR_INST_KIND_TYPE_NEVER
-#define CARBON_SEM_IR_INST_KIND_TYPE_NEVER(...) __VA_ARGS__
-#endif
-#ifndef CARBON_SEM_IR_INST_KIND_TYPE_MAYBE
-#define CARBON_SEM_IR_INST_KIND_TYPE_MAYBE(...) __VA_ARGS__
-#endif
-#ifndef CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS
-#define CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS(...) __VA_ARGS__
-#endif
-
-#ifndef CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER(...) __VA_ARGS__
-#endif
-#ifndef CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY(...) __VA_ARGS__
-#endif
-#ifndef CARBON_SEM_IR_INST_KIND_CONSTANT_CONDITIONAL
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_CONDITIONAL(...) __VA_ARGS__
-#endif
-#ifndef CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS
-#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(...) __VA_ARGS__
-#endif
-
-#define CARBON_SEM_IR_INST_KIND_IMPL(Name, IsType, IsConstant) \
-  CARBON_SEM_IR_INST_KIND_##IsType(                            \
-      CARBON_SEM_IR_INST_KIND_##IsConstant(CARBON_SEM_IR_INST_KIND(Name)))
-
 // For each instruction kind declared here there is a matching definition in
 // `typed_insts.h`.
-CARBON_SEM_IR_INST_KIND_IMPL(AdaptDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(AddrOf, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(AddrPattern, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ArrayIndex, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ArrayInit, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ArrayType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(AsCompatible, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(Assign, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(AssociatedConstantDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(AssociatedEntity, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(AssociatedEntityType, TYPE_ALWAYS,
-                             CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(BaseDecl, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(BindAlias, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ExportDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BindName, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BindSymbolicName, TYPE_MAYBE,
-                             CONSTANT_SYMBOLIC_ONLY)
-CARBON_SEM_IR_INST_KIND_IMPL(BindValue, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BlockArg, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BoolLiteral, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(BoundMethod, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(Branch, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BranchIf, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BranchWithArg, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(BuiltinInst, TYPE_ALWAYS, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(Call, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ClassDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ClassElementAccess, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ClassInit, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ClassType, TYPE_ALWAYS, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(ConstType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(Converted, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(Deref, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(FacetTypeAccess, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(FieldDecl, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(FloatLiteral, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(FloatType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(FunctionDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(FunctionType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(GenericClassType, TYPE_ALWAYS,
-                             CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(GenericInterfaceType, TYPE_ALWAYS,
-                             CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(ImplDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ImportDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ImportRefUnloaded, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ImportRefLoaded, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(InitializeFrom, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(InterfaceDecl, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(InterfaceType, TYPE_ALWAYS, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(InterfaceWitness, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(InterfaceWitnessAccess, TYPE_MAYBE,
-                             CONSTANT_SYMBOLIC_ONLY)
-CARBON_SEM_IR_INST_KIND_IMPL(IntLiteral, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(IntType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(NameRef, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(Namespace, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(Param, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(PointerType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(ReturnExpr, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(Return, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(SpliceBlock, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(StringLiteral, TYPE_NEVER, CONSTANT_ALWAYS)
-CARBON_SEM_IR_INST_KIND_IMPL(StructAccess, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(StructInit, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(StructLiteral, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(StructType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(StructTypeField, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(StructValue, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(TemporaryStorage, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(Temporary, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleAccess, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleIndex, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleInit, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleLiteral, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(TupleValue, TYPE_NEVER, CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(UnaryOperatorNot, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(UnboundElementType, TYPE_ALWAYS,
-                             CONSTANT_CONDITIONAL)
-CARBON_SEM_IR_INST_KIND_IMPL(ValueAsRef, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(ValueOfInitializer, TYPE_NEVER, CONSTANT_NEVER)
-CARBON_SEM_IR_INST_KIND_IMPL(VarStorage, TYPE_NEVER, CONSTANT_NEVER)
-
-#undef CARBON_SEM_IR_INST_KIND_TYPE_ALWAYS
-#undef CARBON_SEM_IR_INST_KIND_TYPE_MAYBE
-#undef CARBON_SEM_IR_INST_KIND_TYPE_NEVER
-
-#undef CARBON_SEM_IR_INST_KIND_CONSTANT_NEVER
-#undef CARBON_SEM_IR_INST_KIND_CONSTANT_SYMBOLIC_ONLY
-#undef CARBON_SEM_IR_INST_KIND_CONSTANT_CONDITIONAL
-#undef CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS
+CARBON_SEM_IR_INST_KIND(AdaptDecl)
+CARBON_SEM_IR_INST_KIND(AddrOf)
+CARBON_SEM_IR_INST_KIND(AddrPattern)
+CARBON_SEM_IR_INST_KIND(ArrayIndex)
+CARBON_SEM_IR_INST_KIND(ArrayInit)
+CARBON_SEM_IR_INST_KIND(ArrayType)
+CARBON_SEM_IR_INST_KIND(AsCompatible)
+CARBON_SEM_IR_INST_KIND(Assign)
+CARBON_SEM_IR_INST_KIND(AssociatedConstantDecl)
+CARBON_SEM_IR_INST_KIND(AssociatedEntity)
+CARBON_SEM_IR_INST_KIND(AssociatedEntityType)
+CARBON_SEM_IR_INST_KIND(BaseDecl)
+CARBON_SEM_IR_INST_KIND(BindAlias)
+CARBON_SEM_IR_INST_KIND(ExportDecl)
+CARBON_SEM_IR_INST_KIND(BindName)
+CARBON_SEM_IR_INST_KIND(BindSymbolicName)
+CARBON_SEM_IR_INST_KIND(BindValue)
+CARBON_SEM_IR_INST_KIND(BlockArg)
+CARBON_SEM_IR_INST_KIND(BoolLiteral)
+CARBON_SEM_IR_INST_KIND(BoundMethod)
+CARBON_SEM_IR_INST_KIND(Branch)
+CARBON_SEM_IR_INST_KIND(BranchIf)
+CARBON_SEM_IR_INST_KIND(BranchWithArg)
+CARBON_SEM_IR_INST_KIND(BuiltinInst)
+CARBON_SEM_IR_INST_KIND(Call)
+CARBON_SEM_IR_INST_KIND(ClassDecl)
+CARBON_SEM_IR_INST_KIND(ClassElementAccess)
+CARBON_SEM_IR_INST_KIND(ClassInit)
+CARBON_SEM_IR_INST_KIND(ClassType)
+CARBON_SEM_IR_INST_KIND(ConstType)
+CARBON_SEM_IR_INST_KIND(Converted)
+CARBON_SEM_IR_INST_KIND(Deref)
+CARBON_SEM_IR_INST_KIND(FacetTypeAccess)
+CARBON_SEM_IR_INST_KIND(FieldDecl)
+CARBON_SEM_IR_INST_KIND(FloatLiteral)
+CARBON_SEM_IR_INST_KIND(FloatType)
+CARBON_SEM_IR_INST_KIND(FunctionDecl)
+CARBON_SEM_IR_INST_KIND(FunctionType)
+CARBON_SEM_IR_INST_KIND(GenericClassType)
+CARBON_SEM_IR_INST_KIND(GenericInterfaceType)
+CARBON_SEM_IR_INST_KIND(ImplDecl)
+CARBON_SEM_IR_INST_KIND(ImportDecl)
+CARBON_SEM_IR_INST_KIND(ImportRefUnloaded)
+CARBON_SEM_IR_INST_KIND(ImportRefLoaded)
+CARBON_SEM_IR_INST_KIND(InitializeFrom)
+CARBON_SEM_IR_INST_KIND(InterfaceDecl)
+CARBON_SEM_IR_INST_KIND(InterfaceType)
+CARBON_SEM_IR_INST_KIND(InterfaceWitness)
+CARBON_SEM_IR_INST_KIND(InterfaceWitnessAccess)
+CARBON_SEM_IR_INST_KIND(IntLiteral)
+CARBON_SEM_IR_INST_KIND(IntType)
+CARBON_SEM_IR_INST_KIND(NameRef)
+CARBON_SEM_IR_INST_KIND(Namespace)
+CARBON_SEM_IR_INST_KIND(Param)
+CARBON_SEM_IR_INST_KIND(PointerType)
+CARBON_SEM_IR_INST_KIND(ReturnExpr)
+CARBON_SEM_IR_INST_KIND(Return)
+CARBON_SEM_IR_INST_KIND(SpliceBlock)
+CARBON_SEM_IR_INST_KIND(StringLiteral)
+CARBON_SEM_IR_INST_KIND(StructAccess)
+CARBON_SEM_IR_INST_KIND(StructInit)
+CARBON_SEM_IR_INST_KIND(StructLiteral)
+CARBON_SEM_IR_INST_KIND(StructType)
+CARBON_SEM_IR_INST_KIND(StructTypeField)
+CARBON_SEM_IR_INST_KIND(StructValue)
+CARBON_SEM_IR_INST_KIND(TemporaryStorage)
+CARBON_SEM_IR_INST_KIND(Temporary)
+CARBON_SEM_IR_INST_KIND(TupleAccess)
+CARBON_SEM_IR_INST_KIND(TupleIndex)
+CARBON_SEM_IR_INST_KIND(TupleInit)
+CARBON_SEM_IR_INST_KIND(TupleLiteral)
+CARBON_SEM_IR_INST_KIND(TupleType)
+CARBON_SEM_IR_INST_KIND(TupleValue)
+CARBON_SEM_IR_INST_KIND(UnaryOperatorNot)
+CARBON_SEM_IR_INST_KIND(UnboundElementType)
+CARBON_SEM_IR_INST_KIND(ValueAsRef)
+CARBON_SEM_IR_INST_KIND(ValueOfInitializer)
+CARBON_SEM_IR_INST_KIND(VarStorage)
 
-#undef CARBON_SEM_IR_INST_KIND_IMPL
 #undef CARBON_SEM_IR_INST_KIND

+ 67 - 43
toolchain/sem_ir/inst_kind.h

@@ -13,6 +13,17 @@
 
 namespace Carbon::SemIR {
 
+// Whether an instruction defines a type.
+enum class InstIsType : int8_t {
+  // Always of type `type`, and might define a type constant.
+  Always,
+  // Sometimes of type `type`, and might define a type constant.
+  Maybe,
+  // Never defines a type constant. Note that such instructions can still have
+  // type `type`, but are not the canonical definition of any type.
+  Never,
+};
+
 // Whether an instruction produces or represents a value, and if so, what kind
 // of value.
 enum class InstValueKind : int8_t {
@@ -75,42 +86,62 @@ class InstKind : public CARBON_ENUM_BASE(InstKind) {
   template <typename TypedNodeId>
   class Definition;
 
+  // Information about a definition. See associated accessors below for
+  // comments.
+  struct DefinitionInfo {
+    llvm::StringLiteral ir_name;
+    InstIsType is_type = InstIsType::Never;
+    InstConstantKind constant_kind = InstConstantKind::Never;
+    TerminatorKind terminator_kind = TerminatorKind::NotTerminator;
+    bool is_lowered = true;
+  };
+
   // Provides a definition for this instruction kind. Should only be called
   // once, to construct the kind as part of defining it in `typed_insts.h`.
   template <typename TypedNodeId>
-  constexpr auto Define(
-      llvm::StringLiteral ir_name,
-      TerminatorKind terminator_kind = TerminatorKind::NotTerminator) const
-      -> Definition<TypedNodeId>;
-
-  // As above, but for instructions which are not lowered; `Define(ir_name)`
-  // should be used instead of specifying `is_lowered = true`.
-  template <typename TypedNodeId>
-  constexpr auto Define(llvm::StringLiteral ir_name, bool is_lowered) const
-      -> Definition<TypedNodeId>;
+  constexpr auto Define(DefinitionInfo info) const -> Definition<TypedNodeId>;
 
   using EnumBase::AsInt;
   using EnumBase::Make;
 
+  // Returns true if the kind matches any of the provided instructions' kinds.
+  template <typename... InstT>
+  constexpr auto IsAnyOf() const -> bool {
+    return ((*this == InstT::Kind) || ...);
+  }
+
   // Returns the name to use for this instruction kind in Semantics IR.
-  auto ir_name() const -> llvm::StringLiteral;
+  auto ir_name() const -> llvm::StringLiteral {
+    return definition_info(*this).ir_name;
+  }
+
+  // Returns whether this instruction kind defines a type.
+  auto is_type() const -> InstIsType { return definition_info(*this).is_type; }
 
-  // Returns whether this kind of instruction is expected to produce a value.
+  // Returns whether this instruction kind is expected to produce a value.
   auto value_kind() const -> InstValueKind;
 
-  // Returns whether this kind of instruction is able to define a constant.
-  auto constant_kind() const -> InstConstantKind;
+  // Returns this instruction kind's category of allowed constants.
+  auto constant_kind() const -> InstConstantKind {
+    return definition_info(*this).constant_kind;
+  }
 
   // Returns whether this instruction kind is a code block terminator, such as
   // an unconditional branch instruction, or part of the termination sequence,
   // such as a conditional branch instruction. The termination sequence of a
   // code block appears after all other instructions, and ends with a
   // terminator instruction.
-  auto terminator_kind() const -> TerminatorKind;
+  auto terminator_kind() const -> TerminatorKind {
+    return definition_info(*this).terminator_kind;
+  }
 
   // Compute a fingerprint for this instruction kind, allowing its use as part
   // of the key in a `FoldingSet`.
   void Profile(llvm::FoldingSetNodeID& id) { id.AddInteger(AsInt()); }
+
+ private:
+  // Returns the DefinitionInfo for the kind.
+  static auto definition_info(InstKind kind) -> const DefinitionInfo&;
 };
 
 #define CARBON_SEM_IR_INST_KIND(Name) \
@@ -122,9 +153,9 @@ static_assert(sizeof(InstKind) == 1, "Kind objects include padding!");
 
 // A definition of an instruction kind. This is an InstKind value, plus
 // ancillary data such as the name to use for the node kind in LLVM IR. These
-// are not copyable, and only one instance of this type is expected to exist per
-// instruction kind, specifically `TypedInst::Kind`. Use `InstKind` instead as a
-// thin wrapper around an instruction kind index.
+// are not copyable, and only one instance of this type is expected to exist
+// per instruction kind, specifically `TypedInst::Kind`. Use `InstKind`
+// instead as a thin wrapper around an instruction kind index.
 template <typename TypedNodeIdArg>
 class InstKind::Definition : public InstKind {
  public:
@@ -135,47 +166,40 @@ class InstKind::Definition : public InstKind {
   auto operator=(const Definition&) -> Definition& = delete;
 
   // Returns the name to use for this instruction kind in Semantics IR.
-  constexpr auto ir_name() const -> llvm::StringLiteral { return ir_name_; }
+  constexpr auto ir_name() const -> llvm::StringLiteral {
+    return info_.ir_name;
+  }
+
+  // Returns whether this instruction kind defines a type.
+  constexpr auto is_type() const -> InstIsType { return info_.is_type; }
+
+  // Returns this instruction kind's category of allowed constants.
+  constexpr auto constant_kind() const -> InstConstantKind {
+    return info_.constant_kind;
+  }
 
   // Returns whether this instruction kind is a code block terminator. See
   // InstKind::terminator_kind().
   constexpr auto terminator_kind() const -> TerminatorKind {
-    return terminator_kind_;
+    return info_.terminator_kind;
   }
 
   // Returns true if the instruction is lowered.
-  constexpr auto is_lowered() const -> bool { return is_lowered_; }
+  constexpr auto is_lowered() const -> bool { return info_.is_lowered; }
 
  private:
   friend class InstKind;
 
-  constexpr Definition(InstKind kind, llvm::StringLiteral ir_name,
-                       TerminatorKind terminator_kind, bool is_lowered)
-      : InstKind(kind),
-        ir_name_(ir_name),
-        terminator_kind_(terminator_kind),
-        is_lowered_(is_lowered) {}
+  constexpr Definition(InstKind kind, InstKind::DefinitionInfo info)
+      : InstKind(kind), info_(info) {}
 
-  llvm::StringLiteral ir_name_;
-  TerminatorKind terminator_kind_;
-  bool is_lowered_;
+  InstKind::DefinitionInfo info_;
 };
 
 template <typename TypedNodeId>
-constexpr auto InstKind::Define(llvm::StringLiteral ir_name,
-                                TerminatorKind terminator_kind) const
-    -> Definition<TypedNodeId> {
-  return Definition<TypedNodeId>(*this, ir_name, terminator_kind,
-                                 /*is_lowered=*/true);
-}
-
-template <typename TypedNodeId>
-constexpr auto InstKind::Define(llvm::StringLiteral ir_name,
-                                bool is_lowered) const
+constexpr auto InstKind::Define(DefinitionInfo info) const
     -> Definition<TypedNodeId> {
-  CARBON_CHECK(!is_lowered) << "Use Define(ir_name) instead";
-  return Definition<TypedNodeId>(*this, ir_name, TerminatorKind::NotTerminator,
-                                 /*is_lowered=*/is_lowered);
+  return Definition<TypedNodeId>(*this, info);
 }
 
 }  // namespace Carbon::SemIR

+ 161 - 105
toolchain/sem_ir/typed_insts.h

@@ -47,7 +47,7 @@ namespace Carbon::SemIR {
 // An adapted type declaration in a class, of the form `adapt T;`.
 struct AdaptDecl {
   static constexpr auto Kind = InstKind::AdaptDecl.Define<Parse::AdaptDeclId>(
-      "adapt_decl", /*is_lowered=*/false);
+      {.ir_name = "adapt_decl", .is_lowered = false});
 
   // No type_id; this is not a value.
   TypeId adapted_type_id;
@@ -56,8 +56,8 @@ struct AdaptDecl {
 // The `&` address-of operator, as in `&lvalue`.
 struct AddrOf {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::AddrOf.Define<Parse::NodeId>("addr_of");
+  static constexpr auto Kind = InstKind::AddrOf.Define<Parse::NodeId>(
+      {.ir_name = "addr_of", .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InstId lvalue_id;
@@ -67,7 +67,7 @@ struct AddrOf {
 // generally be one of `AnyBindName`.
 struct AddrPattern {
   static constexpr auto Kind =
-      InstKind::AddrPattern.Define<Parse::AddrId>("addr_pattern");
+      InstKind::AddrPattern.Define<Parse::AddrId>({.ir_name = "addr_pattern"});
 
   TypeId type_id;
   // The `self` binding.
@@ -78,7 +78,7 @@ struct AddrPattern {
 struct ArrayIndex {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::ArrayIndex.Define<Parse::NodeId>("array_index");
+      InstKind::ArrayIndex.Define<Parse::NodeId>({.ir_name = "array_index"});
 
   TypeId type_id;
   InstId array_id;
@@ -138,7 +138,7 @@ struct AnyAggregateValue {
 struct ArrayInit {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::ArrayInit.Define<Parse::NodeId>("array_init");
+      InstKind::ArrayInit.Define<Parse::NodeId>({.ir_name = "array_init"});
 
   TypeId type_id;
   InstBlockId inits_id;
@@ -147,8 +147,10 @@ struct ArrayInit {
 
 // An array of `element_type_id` values, sized to `bound_id`.
 struct ArrayType {
-  static constexpr auto Kind =
-      InstKind::ArrayType.Define<Parse::ArrayExprId>("array_type");
+  static constexpr auto Kind = InstKind::ArrayType.Define<Parse::ArrayExprId>(
+      {.ir_name = "array_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InstId bound_id;
@@ -157,8 +159,8 @@ struct ArrayType {
 
 // Perform a no-op conversion to a compatible type.
 struct AsCompatible {
-  static constexpr auto Kind =
-      InstKind::AsCompatible.Define<Parse::NodeId>("as_compatible");
+  static constexpr auto Kind = InstKind::AsCompatible.Define<Parse::NodeId>(
+      {.ir_name = "as_compatible"});
 
   TypeId type_id;
   InstId source_id;
@@ -170,7 +172,7 @@ struct AsCompatible {
 struct Assign {
   static constexpr auto Kind = InstKind::Assign.Define<
       Parse::NodeIdOneOf<Parse::InfixOperatorEqualId, Parse::VariableDeclId>>(
-      "assign");
+      {.ir_name = "assign"});
 
   // Assignments are statements, and so have no type.
   InstId lhs_id;
@@ -181,7 +183,7 @@ struct Assign {
 struct AssociatedConstantDecl {
   static constexpr auto Kind =
       InstKind::AssociatedConstantDecl.Define<Parse::NodeId>(
-          "assoc_const_decl", /*is_lowered=*/false);
+          {.ir_name = "assoc_const_decl", .is_lowered = false});
 
   TypeId type_id;
   NameId name_id;
@@ -192,8 +194,8 @@ struct AssociatedConstantDecl {
 // This represents the entity before impl lookup is performed, and identifies
 // the slot within a witness where the constant value will be found.
 struct AssociatedEntity {
-  static constexpr auto Kind =
-      InstKind::AssociatedEntity.Define<Parse::NodeId>("assoc_entity");
+  static constexpr auto Kind = InstKind::AssociatedEntity.Define<Parse::NodeId>(
+      {.ir_name = "assoc_entity", .constant_kind = InstConstantKind::Always});
 
   // The type of the associated entity. This is an AssociatedEntityType.
   TypeId type_id;
@@ -206,7 +208,9 @@ struct AssociatedEntity {
 struct AssociatedEntityType {
   static constexpr auto Kind =
       InstKind::AssociatedEntityType.Define<Parse::InvalidNodeId>(
-          "assoc_entity_type");
+          {.ir_name = "assoc_entity_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InterfaceId interface_id;
@@ -217,8 +221,8 @@ struct AssociatedEntityType {
 // element of the derived class, and the type of the `BaseDecl` instruction is
 // an `UnboundElementType`.
 struct BaseDecl {
-  static constexpr auto Kind =
-      InstKind::BaseDecl.Define<Parse::BaseDeclId>("base_decl");
+  static constexpr auto Kind = InstKind::BaseDecl.Define<Parse::BaseDeclId>(
+      {.ir_name = "base_decl", .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   TypeId base_type_id;
@@ -253,7 +257,7 @@ struct AnyBindNameOrExportDecl {
 // Binds a name as an alias.
 struct BindAlias {
   static constexpr auto Kind =
-      InstKind::BindAlias.Define<Parse::NodeId>("bind_alias");
+      InstKind::BindAlias.Define<Parse::NodeId>({.ir_name = "bind_alias"});
 
   TypeId type_id;
   BindNameId bind_name_id;
@@ -264,7 +268,7 @@ struct BindAlias {
 struct BindName {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::BindName.Define<Parse::NodeId>("bind_name");
+      InstKind::BindName.Define<Parse::NodeId>({.ir_name = "bind_name"});
 
   TypeId type_id;
   BindNameId bind_name_id;
@@ -275,8 +279,10 @@ struct BindName {
 
 // Binds a symbolic name, such as `x` in `let x:! i32 = 7;`.
 struct BindSymbolicName {
-  static constexpr auto Kind =
-      InstKind::BindSymbolicName.Define<Parse::NodeId>("bind_symbolic_name");
+  static constexpr auto Kind = InstKind::BindSymbolicName.Define<Parse::NodeId>(
+      {.ir_name = "bind_symbolic_name",
+       .is_type = InstIsType::Maybe,
+       .constant_kind = InstConstantKind::SymbolicOnly});
 
   TypeId type_id;
   BindNameId bind_name_id;
@@ -288,7 +294,7 @@ struct BindSymbolicName {
 struct BindValue {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::BindValue.Define<Parse::NodeId>("bind_value");
+      InstKind::BindValue.Define<Parse::NodeId>({.ir_name = "bind_value"});
 
   TypeId type_id;
   InstId value_id;
@@ -298,7 +304,7 @@ struct BindValue {
 struct BlockArg {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::BlockArg.Define<Parse::NodeId>("block_arg");
+      InstKind::BlockArg.Define<Parse::NodeId>({.ir_name = "block_arg"});
 
   TypeId type_id;
   InstBlockId block_id;
@@ -306,8 +312,8 @@ struct BlockArg {
 
 // A literal bool value, `true` or `false`.
 struct BoolLiteral {
-  static constexpr auto Kind =
-      InstKind::BoolLiteral.Define<Parse::NodeId>("bool_literal");
+  static constexpr auto Kind = InstKind::BoolLiteral.Define<Parse::NodeId>(
+      {.ir_name = "bool_literal", .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   BoolValue value;
@@ -316,8 +322,9 @@ struct BoolLiteral {
 // A bound method, that combines a function with the value to use for its
 // `self` parameter, such as `object.MethodName`.
 struct BoundMethod {
-  static constexpr auto Kind =
-      InstKind::BoundMethod.Define<Parse::NodeId>("bound_method");
+  static constexpr auto Kind = InstKind::BoundMethod.Define<Parse::NodeId>(
+      {.ir_name = "bound_method",
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   // The object argument in the bound method, which will be used to initialize
@@ -342,8 +349,8 @@ struct AnyBranch {
 // Control flow to branch to the target block.
 struct Branch {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::Branch.Define<Parse::NodeId>("br", TerminatorKind::Terminator);
+  static constexpr auto Kind = InstKind::Branch.Define<Parse::NodeId>(
+      {.ir_name = "br", .terminator_kind = TerminatorKind::Terminator});
 
   // Branches don't produce a value, so have no type.
   InstBlockId target_id;
@@ -353,7 +360,7 @@ struct Branch {
 struct BranchIf {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind = InstKind::BranchIf.Define<Parse::NodeId>(
-      "br", TerminatorKind::TerminatorSequence);
+      {.ir_name = "br", .terminator_kind = TerminatorKind::TerminatorSequence});
 
   // Branches don't produce a value, so have no type.
   InstBlockId target_id;
@@ -365,7 +372,7 @@ struct BranchIf {
 struct BranchWithArg {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind = InstKind::BranchWithArg.Define<Parse::NodeId>(
-      "br", TerminatorKind::Terminator);
+      {.ir_name = "br", .terminator_kind = TerminatorKind::Terminator});
 
   // Branches don't produce a value, so have no type.
   InstBlockId target_id;
@@ -375,9 +382,12 @@ struct BranchWithArg {
 // A builtin instruction, corresponding to instructions like
 // InstId::BuiltinTypeType.
 struct BuiltinInst {
-  // Builtin instructions don't have a parse node associated with them.
+  // Builtins don't have a parse node associated with them.
   static constexpr auto Kind =
-      InstKind::BuiltinInst.Define<Parse::InvalidNodeId>("builtin");
+      InstKind::BuiltinInst.Define<Parse::InvalidNodeId>(
+          {.ir_name = "builtin",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   BuiltinInstKind builtin_inst_kind;
@@ -389,7 +399,8 @@ struct Call {
   // For a syntactic call, the parse node will be a CallExprStartId. However,
   // calls can arise from other syntaxes, such as operators and implicit
   // conversions.
-  static constexpr auto Kind = InstKind::Call.Define<Parse::NodeId>("call");
+  static constexpr auto Kind =
+      InstKind::Call.Define<Parse::NodeId>({.ir_name = "call"});
 
   TypeId type_id;
   InstId callee_id;
@@ -403,7 +414,8 @@ struct Call {
 // A class declaration.
 struct ClassDecl {
   static constexpr auto Kind =
-      InstKind::ClassDecl.Define<Parse::AnyClassDeclId>("class_decl");
+      InstKind::ClassDecl.Define<Parse::AnyClassDeclId>(
+          {.ir_name = "class_decl"});
 
   TypeId type_id;
   // TODO: For a generic class declaration, the name of the class declaration
@@ -420,7 +432,7 @@ struct ClassElementAccess {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
       InstKind::ClassElementAccess.Define<Parse::NodeId>(
-          "class_element_access");
+          {.ir_name = "class_element_access"});
 
   TypeId type_id;
   InstId base_id;
@@ -431,7 +443,7 @@ struct ClassElementAccess {
 struct ClassInit {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::ClassInit.Define<Parse::NodeId>("class_init");
+      InstKind::ClassInit.Define<Parse::NodeId>({.ir_name = "class_init"});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -440,8 +452,10 @@ struct ClassInit {
 
 // The type for a class, either non-generic or parameterized generic instance.
 struct ClassType {
-  static constexpr auto Kind =
-      InstKind::ClassType.Define<Parse::NodeId>("class_type");
+  static constexpr auto Kind = InstKind::ClassType.Define<Parse::NodeId>(
+      {.ir_name = "class_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   ClassId class_id;
@@ -451,7 +465,10 @@ struct ClassType {
 // Indicates `const` on a type, such as `var x: const i32`.
 struct ConstType {
   static constexpr auto Kind =
-      InstKind::ConstType.Define<Parse::PrefixOperatorConstId>("const_type");
+      InstKind::ConstType.Define<Parse::PrefixOperatorConstId>(
+          {.ir_name = "const_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   TypeId inner_id;
@@ -461,7 +478,7 @@ struct ConstType {
 // result.
 struct Converted {
   static constexpr auto Kind =
-      InstKind::Converted.Define<Parse::NodeId>("converted");
+      InstKind::Converted.Define<Parse::NodeId>({.ir_name = "converted"});
 
   TypeId type_id;
   InstId original_id;
@@ -471,7 +488,8 @@ struct Converted {
 // The `*` dereference operator, as in `*pointer`.
 struct Deref {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind = InstKind::Deref.Define<Parse::NodeId>("deref");
+  static constexpr auto Kind =
+      InstKind::Deref.Define<Parse::NodeId>({.ir_name = "deref"});
 
   TypeId type_id;
   InstId pointer_id;
@@ -480,7 +498,7 @@ struct Deref {
 // An `export bind_name` declaration.
 struct ExportDecl {
   static constexpr auto Kind =
-      InstKind::ExportDecl.Define<Parse::NodeId>("export");
+      InstKind::ExportDecl.Define<Parse::NodeId>({.ir_name = "export"});
 
   TypeId type_id;
   BindNameId bind_name_id;
@@ -491,8 +509,8 @@ struct ExportDecl {
 // Represents accessing the `type` field in a facet value, which is notionally a
 // pair of a type and a witness.
 struct FacetTypeAccess {
-  static constexpr auto Kind =
-      InstKind::FacetTypeAccess.Define<Parse::NodeId>("facet_type_access");
+  static constexpr auto Kind = InstKind::FacetTypeAccess.Define<Parse::NodeId>(
+      {.ir_name = "facet_type_access"});
 
   TypeId type_id;
   InstId facet_id;
@@ -502,7 +520,8 @@ struct FacetTypeAccess {
 // `FieldDecl` instruction is an `UnboundElementType`.
 struct FieldDecl {
   static constexpr auto Kind =
-      InstKind::FieldDecl.Define<Parse::BindingPatternId>("field_decl");
+      InstKind::FieldDecl.Define<Parse::BindingPatternId>(
+          {.ir_name = "field_decl", .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   NameId name_id;
@@ -512,7 +531,9 @@ struct FieldDecl {
 // A literal floating point value.
 struct FloatLiteral {
   static constexpr auto Kind =
-      InstKind::FloatLiteral.Define<Parse::RealLiteralId>("float_literal");
+      InstKind::FloatLiteral.Define<Parse::RealLiteralId>(
+          {.ir_name = "float_literal",
+           .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   FloatId float_id;
@@ -520,8 +541,10 @@ struct FloatLiteral {
 
 // A floating point type.
 struct FloatType {
-  static constexpr auto Kind =
-      InstKind::FloatType.Define<Parse::NodeId>("float_type");
+  static constexpr auto Kind = InstKind::FloatType.Define<Parse::NodeId>(
+      {.ir_name = "float_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   // TODO: Consider adding a more compact way of representing either a small
@@ -533,7 +556,7 @@ struct FloatType {
 struct FunctionDecl {
   static constexpr auto Kind =
       InstKind::FunctionDecl.Define<Parse::AnyFunctionDeclId>(
-          "fn_decl", /*is_lowered=*/false);
+          {.ir_name = "fn_decl", .is_lowered = false});
 
   TypeId type_id;
   FunctionId function_id;
@@ -545,7 +568,10 @@ struct FunctionDecl {
 // The type of a function.
 struct FunctionType {
   static constexpr auto Kind =
-      InstKind::FunctionType.Define<Parse::AnyFunctionDeclId>("fn_type");
+      InstKind::FunctionType.Define<Parse::AnyFunctionDeclId>(
+          {.ir_name = "fn_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   FunctionId function_id;
@@ -557,7 +583,9 @@ struct GenericClassType {
   // This is only ever created as a constant, so doesn't have a location.
   static constexpr auto Kind =
       InstKind::GenericClassType.Define<Parse::InvalidNodeId>(
-          "generic_class_type");
+          {.ir_name = "generic_class_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   ClassId class_id;
@@ -569,7 +597,9 @@ struct GenericInterfaceType {
   // This is only ever created as a constant, so doesn't have a location.
   static constexpr auto Kind =
       InstKind::GenericInterfaceType.Define<Parse::InvalidNodeId>(
-          "generic_interface_type");
+          {.ir_name = "generic_interface_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InterfaceId interface_id;
@@ -578,7 +608,7 @@ struct GenericInterfaceType {
 // An `impl` declaration.
 struct ImplDecl {
   static constexpr auto Kind = InstKind::ImplDecl.Define<Parse::AnyImplDeclId>(
-      "impl_decl", /*is_lowered=*/false);
+      {.ir_name = "impl_decl", .is_lowered = false});
 
   // No type: an impl declaration is not a value.
   ImplId impl_id;
@@ -591,7 +621,7 @@ struct ImplDecl {
 // correspondence with actual `import`s isn't guaranteed.
 struct ImportDecl {
   static constexpr auto Kind = InstKind::ImportDecl.Define<Parse::ImportDeclId>(
-      "import", /*is_lowered=*/false);
+      {.ir_name = "import", .is_lowered = false});
 
   NameId package_id;
 };
@@ -613,7 +643,7 @@ struct ImportRefUnloaded {
   // No parse node: any parse node logic must use the referenced IR.
   static constexpr auto Kind =
       InstKind::ImportRefUnloaded.Define<Parse::InvalidNodeId>(
-          "import_ref", /*is_lowered=*/false);
+          {.ir_name = "import_ref", .is_lowered = false});
 
   ImportIRInstId import_ir_inst_id;
   BindNameId bind_name_id;
@@ -624,7 +654,7 @@ struct ImportRefLoaded {
   // No parse node: any parse node logic must use the referenced IR.
   static constexpr auto Kind =
       InstKind::ImportRefLoaded.Define<Parse::InvalidNodeId>(
-          "import_ref", /*is_lowered=*/false);
+          {.ir_name = "import_ref", .is_lowered = false});
 
   TypeId type_id;
   ImportIRInstId import_ir_inst_id;
@@ -636,8 +666,8 @@ struct ImportRefLoaded {
 // whose initialization is not in-place.
 struct InitializeFrom {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::InitializeFrom.Define<Parse::NodeId>("initialize_from");
+  static constexpr auto Kind = InstKind::InitializeFrom.Define<Parse::NodeId>(
+      {.ir_name = "initialize_from"});
 
   TypeId type_id;
   InstId src_id;
@@ -648,7 +678,7 @@ struct InitializeFrom {
 struct InterfaceDecl {
   static constexpr auto Kind =
       InstKind::InterfaceDecl.Define<Parse::AnyInterfaceDeclId>(
-          "interface_decl", /*is_lowered=*/false);
+          {.ir_name = "interface_decl", .is_lowered = false});
 
   TypeId type_id;
   // TODO: For a generic interface declaration, the name of the interface
@@ -662,8 +692,10 @@ struct InterfaceDecl {
 // The type for an interface, either non-generic or parameterized generic
 // instance.
 struct InterfaceType {
-  static constexpr auto Kind =
-      InstKind::InterfaceType.Define<Parse::NodeId>("interface_type");
+  static constexpr auto Kind = InstKind::InterfaceType.Define<Parse::NodeId>(
+      {.ir_name = "interface_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   InterfaceId interface_id;
@@ -674,7 +706,9 @@ struct InterfaceType {
 struct InterfaceWitness {
   static constexpr auto Kind =
       InstKind::InterfaceWitness.Define<Parse::InvalidNodeId>(
-          "interface_witness", /*is_lowered=*/false);
+          {.ir_name = "interface_witness",
+           .constant_kind = InstConstantKind::Conditional,
+           .is_lowered = false});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -684,7 +718,10 @@ struct InterfaceWitness {
 struct InterfaceWitnessAccess {
   static constexpr auto Kind =
       InstKind::InterfaceWitnessAccess.Define<Parse::InvalidNodeId>(
-          "interface_witness_access", /*is_lowered=*/false);
+          {.ir_name = "interface_witness_access",
+           .is_type = InstIsType::Maybe,
+           .constant_kind = InstConstantKind::SymbolicOnly,
+           .is_lowered = false});
 
   TypeId type_id;
   InstId witness_id;
@@ -694,8 +731,8 @@ struct InterfaceWitnessAccess {
 // A literal integer value.
 struct IntLiteral {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::IntLiteral.Define<Parse::NodeId>("int_literal");
+  static constexpr auto Kind = InstKind::IntLiteral.Define<Parse::NodeId>(
+      {.ir_name = "int_literal", .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   IntId int_id;
@@ -703,8 +740,10 @@ struct IntLiteral {
 
 // An integer type.
 struct IntType {
-  static constexpr auto Kind =
-      InstKind::IntType.Define<Parse::NodeId>("int_type");
+  static constexpr auto Kind = InstKind::IntType.Define<Parse::NodeId>(
+      {.ir_name = "int_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   IntKind int_kind;
@@ -718,7 +757,7 @@ struct IntType {
 struct NameRef {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::NameRef.Define<Parse::NodeId>("name_ref");
+      InstKind::NameRef.Define<Parse::NodeId>({.ir_name = "name_ref"});
 
   TypeId type_id;
   NameId name_id;
@@ -728,7 +767,8 @@ struct NameRef {
 // A namespace declaration.
 struct Namespace {
   static constexpr auto Kind =
-      InstKind::Namespace.Define<Parse::AnyNamespaceId>("namespace");
+      InstKind::Namespace.Define<Parse::AnyNamespaceId>(
+          {.ir_name = "namespace", .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   NameScopeId name_scope_id;
@@ -740,7 +780,8 @@ struct Namespace {
 // A parameter for a function or other parameterized block.
 struct Param {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind = InstKind::Param.Define<Parse::NodeId>("param");
+  static constexpr auto Kind =
+      InstKind::Param.Define<Parse::NodeId>({.ir_name = "param"});
 
   TypeId type_id;
   NameId name_id;
@@ -750,8 +791,10 @@ struct Param {
 // `x: i32*`, where `pointee_id` is `i32` and `type_id` is `type`.
 struct PointerType {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::PointerType.Define<Parse::NodeId>("ptr_type");
+  static constexpr auto Kind = InstKind::PointerType.Define<Parse::NodeId>(
+      {.ir_name = "ptr_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   TypeId pointee_id;
@@ -761,7 +804,7 @@ struct Return {
   static constexpr auto Kind =
       InstKind::Return.Define<Parse::NodeIdOneOf<Parse::FunctionDefinitionId,
                                                  Parse::ReturnStatementId>>(
-          "return", TerminatorKind::Terminator);
+          {.ir_name = "return", .terminator_kind = TerminatorKind::Terminator});
 
   // This is a statement, so has no type.
 };
@@ -770,7 +813,7 @@ struct Return {
 struct ReturnExpr {
   static constexpr auto Kind =
       InstKind::ReturnExpr.Define<Parse::ReturnStatementId>(
-          "return", TerminatorKind::Terminator);
+          {.ir_name = "return", .terminator_kind = TerminatorKind::Terminator});
 
   // This is a statement, so has no type.
   InstId expr_id;
@@ -785,7 +828,7 @@ struct ReturnExpr {
 struct SpliceBlock {
   // TODO: Can we make Parse::NodeId more specific?
   static constexpr auto Kind =
-      InstKind::SpliceBlock.Define<Parse::NodeId>("splice_block");
+      InstKind::SpliceBlock.Define<Parse::NodeId>({.ir_name = "splice_block"});
 
   TypeId type_id;
   InstBlockId block_id;
@@ -795,7 +838,9 @@ struct SpliceBlock {
 // A literal string value.
 struct StringLiteral {
   static constexpr auto Kind =
-      InstKind::StringLiteral.Define<Parse::StringLiteralId>("string_literal");
+      InstKind::StringLiteral.Define<Parse::StringLiteralId>(
+          {.ir_name = "string_literal",
+           .constant_kind = InstConstantKind::Always});
 
   TypeId type_id;
   StringLiteralValueId string_literal_id;
@@ -804,8 +849,8 @@ struct StringLiteral {
 // Access to a struct type, with the index into the struct_id representation.
 struct StructAccess {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::StructAccess.Define<Parse::NodeId>("struct_access");
+  static constexpr auto Kind = InstKind::StructAccess.Define<Parse::NodeId>(
+      {.ir_name = "struct_access"});
 
   TypeId type_id;
   InstId struct_id;
@@ -816,7 +861,7 @@ struct StructAccess {
 struct StructInit {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::StructInit.Define<Parse::NodeId>("struct_init");
+      InstKind::StructInit.Define<Parse::NodeId>({.ir_name = "struct_init"});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -826,7 +871,8 @@ struct StructInit {
 // A literal struct value, such as `{.a = 1, .b = 2}`.
 struct StructLiteral {
   static constexpr auto Kind =
-      InstKind::StructLiteral.Define<Parse::StructLiteralId>("struct_literal");
+      InstKind::StructLiteral.Define<Parse::StructLiteralId>(
+          {.ir_name = "struct_literal"});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -836,8 +882,10 @@ struct StructLiteral {
 struct StructType {
   // TODO: Make this more specific. It can be one of: ClassDefinitionId,
   // StructLiteralId, StructTypeLiteralId
-  static constexpr auto Kind =
-      InstKind::StructType.Define<Parse::NodeId>("struct_type");
+  static constexpr auto Kind = InstKind::StructType.Define<Parse::NodeId>(
+      {.ir_name = "struct_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InstBlockId fields_id;
@@ -850,8 +898,9 @@ struct StructType {
 // instruction has no type.
 struct StructTypeField {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::StructTypeField.Define<Parse::NodeId>("struct_type_field");
+  static constexpr auto Kind = InstKind::StructTypeField.Define<Parse::NodeId>(
+      {.ir_name = "struct_type_field",
+       .constant_kind = InstConstantKind::Conditional});
 
   NameId name_id;
   TypeId field_type_id;
@@ -860,8 +909,9 @@ struct StructTypeField {
 // A struct value.
 struct StructValue {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::StructValue.Define<Parse::NodeId>("struct_value");
+  static constexpr auto Kind = InstKind::StructValue.Define<Parse::NodeId>(
+      {.ir_name = "struct_value",
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -871,7 +921,7 @@ struct StructValue {
 struct Temporary {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::Temporary.Define<Parse::NodeId>("temporary");
+      InstKind::Temporary.Define<Parse::NodeId>({.ir_name = "temporary"});
 
   TypeId type_id;
   InstId storage_id;
@@ -881,8 +931,8 @@ struct Temporary {
 // Storage for a temporary value.
 struct TemporaryStorage {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::TemporaryStorage.Define<Parse::NodeId>("temporary_storage");
+  static constexpr auto Kind = InstKind::TemporaryStorage.Define<Parse::NodeId>(
+      {.ir_name = "temporary_storage"});
 
   TypeId type_id;
 };
@@ -894,7 +944,7 @@ struct TemporaryStorage {
 struct TupleAccess {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::TupleAccess.Define<Parse::NodeId>("tuple_access");
+      InstKind::TupleAccess.Define<Parse::NodeId>({.ir_name = "tuple_access"});
 
   TypeId type_id;
   InstId tuple_id;
@@ -903,8 +953,8 @@ struct TupleAccess {
 
 // Access to a tuple member by index, such as `tuple[index]`.
 struct TupleIndex {
-  static constexpr auto Kind =
-      InstKind::TupleIndex.Define<Parse::IndexExprId>("tuple_index");
+  static constexpr auto Kind = InstKind::TupleIndex.Define<Parse::IndexExprId>(
+      {.ir_name = "tuple_index"});
 
   TypeId type_id;
   InstId tuple_id;
@@ -915,7 +965,7 @@ struct TupleIndex {
 struct TupleInit {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::TupleInit.Define<Parse::NodeId>("tuple_init");
+      InstKind::TupleInit.Define<Parse::NodeId>({.ir_name = "tuple_init"});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -925,7 +975,8 @@ struct TupleInit {
 // A literal tuple value.
 struct TupleLiteral {
   static constexpr auto Kind =
-      InstKind::TupleLiteral.Define<Parse::TupleLiteralId>("tuple_literal");
+      InstKind::TupleLiteral.Define<Parse::TupleLiteralId>(
+          {.ir_name = "tuple_literal"});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -934,8 +985,10 @@ struct TupleLiteral {
 // The type of a tuple.
 struct TupleType {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::TupleType.Define<Parse::NodeId>("tuple_type");
+  static constexpr auto Kind = InstKind::TupleType.Define<Parse::NodeId>(
+      {.ir_name = "tuple_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   TypeBlockId elements_id;
@@ -944,8 +997,9 @@ struct TupleType {
 // A tuple value.
 struct TupleValue {
   // TODO: Make Parse::NodeId more specific.
-  static constexpr auto Kind =
-      InstKind::TupleValue.Define<Parse::NodeId>("tuple_value");
+  static constexpr auto Kind = InstKind::TupleValue.Define<Parse::NodeId>(
+      {.ir_name = "tuple_value",
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   InstBlockId elements_id;
@@ -955,7 +1009,7 @@ struct TupleValue {
 struct UnaryOperatorNot {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::UnaryOperatorNot.Define<Parse::NodeId>("not");
+      InstKind::UnaryOperatorNot.Define<Parse::NodeId>({.ir_name = "not"});
 
   TypeId type_id;
   InstId operand_id;
@@ -967,7 +1021,9 @@ struct UnaryOperatorNot {
 struct UnboundElementType {
   static constexpr auto Kind = InstKind::UnboundElementType.Define<
       Parse::NodeIdOneOf<Parse::BaseDeclId, Parse::BindingPatternId>>(
-      "unbound_element_type");
+      {.ir_name = "unbound_element_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
 
   TypeId type_id;
   // The class that a value of this type is an element of.
@@ -981,8 +1037,8 @@ struct UnboundElementType {
 // example, when indexing a value expression of array type, this is used to
 // form a reference to the array object.
 struct ValueAsRef {
-  static constexpr auto Kind =
-      InstKind::ValueAsRef.Define<Parse::IndexExprId>("value_as_ref");
+  static constexpr auto Kind = InstKind::ValueAsRef.Define<Parse::IndexExprId>(
+      {.ir_name = "value_as_ref"});
 
   TypeId type_id;
   InstId value_id;
@@ -995,7 +1051,7 @@ struct ValueOfInitializer {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
       InstKind::ValueOfInitializer.Define<Parse::NodeId>(
-          "value_of_initializer");
+          {.ir_name = "value_of_initializer"});
 
   TypeId type_id;
   InstId init_id;
@@ -1005,7 +1061,7 @@ struct ValueOfInitializer {
 struct VarStorage {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =
-      InstKind::VarStorage.Define<Parse::NodeId>("var");
+      InstKind::VarStorage.Define<Parse::NodeId>({.ir_name = "var"});
 
   TypeId type_id;
   NameId name_id;