Explorar o código

Move the `EvalConstantInst` overloads out of `eval.cpp` into their own file. (#5040)

For now they're all in the same file; we might consider splitting them
further if that file gets too large.
Richard Smith hai 1 ano
pai
achega
f30fa2d3db

+ 2 - 0
toolchain/check/BUILD

@@ -22,6 +22,7 @@ cc_library(
         "decl_name_stack.cpp",
         "decl_name_stack.cpp",
         "deduce.cpp",
         "deduce.cpp",
         "eval.cpp",
         "eval.cpp",
+        "eval_inst.cpp",
         "facet_type.cpp",
         "facet_type.cpp",
         "function.cpp",
         "function.cpp",
         "generic.cpp",
         "generic.cpp",
@@ -57,6 +58,7 @@ cc_library(
         "deduce.h",
         "deduce.h",
         "diagnostic_helpers.h",
         "diagnostic_helpers.h",
         "eval.h",
         "eval.h",
+        "eval_inst.h",
         "facet_type.h",
         "facet_type.h",
         "function.h",
         "function.h",
         "generic.h",
         "generic.h",

+ 28 - 508
toolchain/check/eval.cpp

@@ -6,6 +6,7 @@
 
 
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/check/eval_inst.h"
 #include "toolchain/check/facet_type.h"
 #include "toolchain/check/facet_type.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/import_ref.h"
@@ -694,39 +695,6 @@ static auto PerformArrayIndex(EvalContext& eval_context, SemIR::ArrayIndex inst)
   return eval_context.GetConstantValue(elements[index_val.getZExtValue()]);
   return eval_context.GetConstantValue(elements[index_val.getZExtValue()]);
 }
 }
 
 
-// Enforces that an integer type has a valid bit width.
-static auto ValidateIntType(Context& context, SemIRLoc loc,
-                            SemIR::IntType result) -> bool {
-  auto bit_width =
-      context.insts().TryGetAs<SemIR::IntValue>(result.bit_width_id);
-  if (!bit_width) {
-    // Symbolic bit width.
-    return true;
-  }
-  const auto& bit_width_val = context.ints().Get(bit_width->int_id);
-  if (bit_width_val.isZero() ||
-      (context.types().IsSignedInt(bit_width->type_id) &&
-       bit_width_val.isNegative())) {
-    CARBON_DIAGNOSTIC(IntWidthNotPositive, Error,
-                      "integer type width of {0} is not positive", TypedInt);
-    context.emitter().Emit(
-        loc, IntWidthNotPositive,
-        {.type = bit_width->type_id, .value = bit_width_val});
-    return false;
-  }
-  if (bit_width_val.ugt(IntStore::MaxIntWidth)) {
-    CARBON_DIAGNOSTIC(IntWidthTooLarge, Error,
-                      "integer type width of {0} is greater than the "
-                      "maximum supported width of {1}",
-                      TypedInt, int);
-    context.emitter().Emit(loc, IntWidthTooLarge,
-                           {.type = bit_width->type_id, .value = bit_width_val},
-                           IntStore::MaxIntWidth);
-    return false;
-  }
-  return true;
-}
-
 // Forms a constant int type as an evaluation result. Requires that width_id is
 // Forms a constant int type as an evaluation result. Requires that width_id is
 // constant.
 // constant.
 static auto MakeIntTypeResult(Context& context, SemIRLoc loc,
 static auto MakeIntTypeResult(Context& context, SemIRLoc loc,
@@ -742,31 +710,6 @@ static auto MakeIntTypeResult(Context& context, SemIRLoc loc,
   return MakeConstantResult(context, result, phase);
   return MakeConstantResult(context, result, phase);
 }
 }
 
 
-// Enforces that the bit width is 64 for a float.
-static auto ValidateFloatBitWidth(Context& context, SemIRLoc loc,
-                                  SemIR::InstId inst_id) -> bool {
-  auto inst = context.insts().GetAs<SemIR::IntValue>(inst_id);
-  if (context.ints().Get(inst.int_id) == 64) {
-    return true;
-  }
-
-  CARBON_DIAGNOSTIC(CompileTimeFloatBitWidth, Error, "bit width must be 64");
-  context.emitter().Emit(loc, CompileTimeFloatBitWidth);
-  return false;
-}
-
-// Enforces that a float type has a valid bit width.
-static auto ValidateFloatType(Context& context, SemIRLoc loc,
-                              SemIR::FloatType result) -> bool {
-  auto bit_width =
-      context.insts().TryGetAs<SemIR::IntValue>(result.bit_width_id);
-  if (!bit_width) {
-    // Symbolic bit width.
-    return true;
-  }
-  return ValidateFloatBitWidth(context, loc, result.bit_width_id);
-}
-
 // Performs a conversion between integer types, truncating if the value doesn't
 // Performs a conversion between integer types, truncating if the value doesn't
 // fit in the destination type.
 // fit in the destination type.
 static auto PerformIntConvert(Context& context, SemIR::InstId arg_id,
 static auto PerformIntConvert(Context& context, SemIR::InstId arg_id,
@@ -1585,454 +1528,33 @@ static auto MakeConstantForCall(EvalContext& eval_context, SemIRLoc loc,
   return SemIR::ConstantId::NotConstant;
   return SemIR::ConstantId::NotConstant;
 }
 }
 
 
-// The result of constant evaluation of an instruction.
-class ConstantEvalResult {
- public:
-  // Produce a new constant as the result of an evaluation. The phase of the
-  // produced constant must be the same as the greatest phase of the operands in
-  // the evaluation. This will typically be the case if the evaluation uses all
-  // of its operands.
-  static auto New(SemIR::Inst inst) -> ConstantEvalResult {
-    return ConstantEvalResult(inst);
-  }
-
-  // Produce an existing constant as the result of an evaluation.
-  static constexpr auto Existing(SemIR::ConstantId existing_id)
-      -> ConstantEvalResult {
-    CARBON_CHECK(existing_id.is_constant());
-    return ConstantEvalResult(existing_id);
-  }
-
-  // Indicates that an error was produced by evaluation.
-  static const ConstantEvalResult Error;
-
-  // Indicates that we encountered an instruction whose evaluation is
-  // non-constant despite having constant operands. This should be rare;
-  // usually we want to produce an error in this case.
-  static const ConstantEvalResult NotConstant;
-
-  // Indicates that we encountered an instruction for which we've not
-  // implemented constant evaluation yet. Instruction is treated as not
-  // constant.
-  static const ConstantEvalResult TODO;
-
-  // Returns whether the result of evaluation is that we should produce a new
-  // constant described by `new_inst()` rather than an existing `ConstantId`
-  // described by `existing()`.
-  auto is_new() const -> bool { return !result_id_.has_value(); }
-
-  // Returns the existing constant that this the instruction evaluates to, or
-  // `None` if this is evaluation produces a new constant.
-  auto existing() const -> SemIR::ConstantId { return result_id_; }
-
-  // Returns the new constant instruction that is the result of evaluation.
-  auto new_inst() const -> SemIR::Inst {
-    CARBON_CHECK(is_new());
-    return new_inst_;
-  }
-
- private:
-  constexpr explicit ConstantEvalResult(SemIR::ConstantId raw_id)
-      : result_id_(raw_id) {}
-
-  explicit ConstantEvalResult(SemIR::Inst inst)
-      : result_id_(SemIR::ConstantId::None), new_inst_(inst) {}
-
-  SemIR::ConstantId result_id_;
-  union {
-    SemIR::Inst new_inst_;
-  };
-};
-
-constexpr ConstantEvalResult ConstantEvalResult::Error =
-    Existing(SemIR::ErrorInst::SingletonConstantId);
-
-constexpr ConstantEvalResult ConstantEvalResult::NotConstant =
-    ConstantEvalResult(SemIR::ConstantId::NotConstant);
-
-constexpr ConstantEvalResult ConstantEvalResult::TODO = NotConstant;
-
-// `EvalConstantInst` evaluates an instruction whose operands are all constant,
-// in a context unrelated to the enclosing evaluation. The function is given the
-// instruction after its operands, including its type, are replaced by their
-// evaluated value, and returns a `ConstantEvalResult` describing the result of
-// evaluating the instruction.
-//
-// An overload is provided for each type whose constant kind is one of the
-// following:
-//
-// - InstConstantKind::Indirect
-// - InstConstantKind::SymbolicOnly
-// - InstConstantKind::Conditional
-//
-// ... except for cases where the result of evaluation depends on the evaluation
-// context itself. Those cases are handled by explicit specialization of
-// `TryEvalTypedInst`.
-
-static auto EvalConstantInst(Context& context, SemIRLoc loc,
-                             SemIR::ArrayType inst) -> ConstantEvalResult {
-  auto bound_inst = context.insts().Get(inst.bound_id);
-  auto int_bound = bound_inst.TryAs<SemIR::IntValue>();
-  if (!int_bound) {
-    CARBON_CHECK(context.constant_values().Get(inst.bound_id).is_symbolic(),
-                 "Unexpected inst {0} for template constant int", bound_inst);
-    return ConstantEvalResult::New(inst);
-  }
-  // TODO: We should check that the size of the resulting array type
-  // fits in 64 bits, not just that the bound does. Should we use a
-  // 32-bit limit for 32-bit targets?
-  const auto& bound_val = context.ints().Get(int_bound->int_id);
-  if (context.types().IsSignedInt(int_bound->type_id) &&
-      bound_val.isNegative()) {
-    CARBON_DIAGNOSTIC(ArrayBoundNegative, Error,
-                      "array bound of {0} is negative", TypedInt);
-    context.emitter().Emit(loc, ArrayBoundNegative,
-                           {.type = int_bound->type_id, .value = bound_val});
-    return ConstantEvalResult::Error;
-  }
-  if (bound_val.getActiveBits() > 64) {
-    CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
-                      "array bound of {0} is too large", TypedInt);
-    context.emitter().Emit(loc, ArrayBoundTooLarge,
-                           {.type = int_bound->type_id, .value = bound_val});
-    return ConstantEvalResult::Error;
-  }
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc loc,
-                             SemIR::IntType inst) -> ConstantEvalResult {
-  return ValidateIntType(context, loc, inst) ? ConstantEvalResult::New(inst)
-                                             : ConstantEvalResult::Error;
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc loc,
-                             SemIR::FloatType inst) -> ConstantEvalResult {
-  return ValidateFloatType(context, loc, inst) ? ConstantEvalResult::New(inst)
-                                               : ConstantEvalResult::Error;
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::ArrayInit init) -> ConstantEvalResult {
-  // TODO: Add an `ArrayValue` to represent a constant array object
-  // representation instead of using a `TupleValue`.
-  return ConstantEvalResult::New(
-      SemIR::TupleValue{.type_id = init.type_id, .elements_id = init.inits_id});
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::ClassInit init) -> ConstantEvalResult {
-  // TODO: Add a `ClassValue` to represent a constant class object
-  // representation instead of using a `StructValue`.
-  return ConstantEvalResult::New(SemIR::StructValue{
-      .type_id = init.type_id, .elements_id = init.elements_id});
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::StructInit init) -> ConstantEvalResult {
-  return ConstantEvalResult::New(SemIR::StructValue{
-      .type_id = init.type_id, .elements_id = init.elements_id});
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::TupleInit init) -> ConstantEvalResult {
-  return ConstantEvalResult::New(SemIR::TupleValue{
-      .type_id = init.type_id, .elements_id = init.elements_id});
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::FunctionDecl inst) -> ConstantEvalResult {
-  return ConstantEvalResult::New(SemIR::StructValue{
-      .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ClassDecl inst) -> ConstantEvalResult {
-  // If the class has generic parameters, we don't produce a class type, but a
-  // callable whose return value is a class type.
-  if (context.classes().Get(inst.class_id).has_parameters()) {
-    return ConstantEvalResult::New(SemIR::StructValue{
-        .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
-  }
-
-  // A non-generic class declaration evaluates to the class type.
-  return ConstantEvalResult::New(
-      SemIR::ClassType{.type_id = SemIR::TypeType::SingletonTypeId,
-                       .class_id = inst.class_id,
-                       .specific_id = SemIR::SpecificId::None});
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::InterfaceDecl inst) -> ConstantEvalResult {
-  // If the interface has generic parameters, we don't produce an interface
-  // type, but a callable whose return value is an interface type.
-  if (context.interfaces().Get(inst.interface_id).has_parameters()) {
-    return ConstantEvalResult::New(SemIR::StructValue{
-        .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
-  }
-
-  // A non-generic interface declaration evaluates to a facet type.
-  return ConstantEvalResult::New(FacetTypeFromInterface(
-      context, inst.interface_id, SemIR::SpecificId::None));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::SpecificConstant inst)
-    -> ConstantEvalResult {
-  // Pull the constant value out of the specific.
-  return ConstantEvalResult::Existing(SemIR::GetConstantValueInSpecific(
-      context.sem_ir(), inst.specific_id, inst.inst_id));
-}
-
-// Performs an access into an aggregate, retrieving the specified element.
-static auto PerformAggregateAccess(Context& context, SemIR::Inst inst)
-    -> ConstantEvalResult {
-  auto access_inst = inst.As<SemIR::AnyAggregateAccess>();
-  if (auto aggregate = context.insts().TryGetAs<SemIR::AnyAggregateValue>(
-          access_inst.aggregate_id)) {
-    auto elements = context.inst_blocks().Get(aggregate->elements_id);
-    auto index = static_cast<size_t>(access_inst.index.index);
-    CARBON_CHECK(index < elements.size(), "Access out of bounds.");
-    // `Phase` is not used here. If this element is a concrete constant, then
-    // so is the result of indexing, even if the aggregate also contains a
-    // symbolic context.
-    return ConstantEvalResult::Existing(
-        context.constant_values().Get(elements[index]));
-  }
-
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ClassElementAccess inst)
-    -> ConstantEvalResult {
-  return PerformAggregateAccess(context, inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::StructAccess inst) -> ConstantEvalResult {
-  return PerformAggregateAccess(context, inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::TupleAccess inst) -> ConstantEvalResult {
-  return PerformAggregateAccess(context, inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc loc,
-                             SemIR::ImplWitnessAccess inst)
-    -> ConstantEvalResult {
-  // This is PerformAggregateAccess followed by GetConstantInSpecific.
-  if (auto witness =
-          context.insts().TryGetAs<SemIR::ImplWitness>(inst.witness_id)) {
-    auto elements = context.inst_blocks().Get(witness->elements_id);
-    auto index = static_cast<size_t>(inst.index.index);
-    CARBON_CHECK(index < elements.size(), "Access out of bounds.");
-    auto element = elements[index];
-    if (!element.has_value()) {
-      // TODO: Perhaps this should be a `{}` value with incomplete type?
-      CARBON_DIAGNOSTIC(ImplAccessMemberBeforeComplete, Error,
-                        "accessing member from impl before the end of "
-                        "its definition");
-      // TODO: Add note pointing to the impl declaration.
-      context.emitter().Emit(loc, ImplAccessMemberBeforeComplete);
-      return ConstantEvalResult::Error;
-    }
-
-    LoadImportRef(context, element);
-    return ConstantEvalResult::Existing(GetConstantValueInSpecific(
-        context.sem_ir(), witness->specific_id, element));
-  }
-
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::BindValue /*inst*/) -> ConstantEvalResult {
-  // TODO: Handle this once we've decided how to represent constant values of
-  // reference expressions.
-  return ConstantEvalResult::TODO;
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::Deref /*inst*/) -> ConstantEvalResult {
-  // TODO: Handle this.
-  return ConstantEvalResult::TODO;
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::Temporary /*inst*/) -> ConstantEvalResult {
-  // TODO: Handle this. Can we just return the value of `init_id`?
-  return ConstantEvalResult::TODO;
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::VtablePtr /*inst*/) -> ConstantEvalResult {
-  // TODO: Handle this.
-  return ConstantEvalResult::TODO;
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::AsCompatible inst) -> ConstantEvalResult {
-  // AsCompatible changes the type of the source instruction; its constant
-  // value, if there is one, needs to be modified to be of the same type.
-  auto value_id = context.constant_values().Get(inst.source_id);
-  CARBON_CHECK(value_id.is_constant());
+// Given an instruction, compute its phase based on its operands.
+static auto ComputeInstPhase(Context& context, SemIR::Inst inst) -> Phase {
+  EvalContext eval_context(context, SemIR::InstId::None);
 
 
-  auto value_inst =
-      context.insts().Get(context.constant_values().GetInstId(value_id));
   auto phase = GetPhase(context.constant_values(),
   auto phase = GetPhase(context.constant_values(),
-                        context.types().GetConstantId(inst.type_id));
-  value_inst.SetType(inst.type_id);
-
-  // Finish computing the new phase by incorporating the phases of the
-  // arguments.
-  EvalContext eval_context(context, SemIR::InstId::None);
-  auto kinds = value_inst.ArgKinds();
-  GetConstantValueForArg(eval_context, kinds.first, value_inst.arg0(), &phase);
-  GetConstantValueForArg(eval_context, kinds.second, value_inst.arg1(), &phase);
+                        context.types().GetConstantId(inst.type_id()));
+  auto kinds = inst.ArgKinds();
+  GetConstantValueForArg(eval_context, kinds.first, inst.arg0(), &phase);
+  GetConstantValueForArg(eval_context, kinds.second, inst.arg1(), &phase);
   CARBON_CHECK(IsConstant(phase));
   CARBON_CHECK(IsConstant(phase));
-
-  // We can't use `ConstantEvalResult::New` because it would use the wrong
-  // phase, so manually build a new constant.
-  return ConstantEvalResult::Existing(
-      MakeConstantResult(context, value_inst, phase));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::BindAlias inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.value_id));
+  return phase;
 }
 }
 
 
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ExportDecl inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.value_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::NameRef inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.value_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ValueParamPattern inst)
-    -> ConstantEvalResult {
-  // TODO: Treat this as a non-expression (here and in GetExprCategory)
-  // once generic deduction doesn't need patterns to have constant values.
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.subpattern_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::Converted inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.result_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::InitializeFrom inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.src_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::SpliceBlock inst) -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.result_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ValueOfInitializer inst)
-    -> ConstantEvalResult {
-  return ConstantEvalResult::Existing(
-      context.constant_values().Get(inst.init_id));
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::FacetAccessType inst)
-    -> ConstantEvalResult {
-  if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
-          inst.facet_value_inst_id)) {
-    return ConstantEvalResult::Existing(
-        context.constant_values().Get(facet_value->type_inst_id));
-  }
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::FacetAccessWitness inst)
-    -> ConstantEvalResult {
-  if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
-          inst.facet_value_inst_id)) {
-    return ConstantEvalResult::Existing(
-        context.constant_values().Get(facet_value->witness_inst_id));
-  }
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::UnaryOperatorNot inst)
-    -> ConstantEvalResult {
-  // `not true` -> `false`, `not false` -> `true`.
-  // All other uses of unary `not` are non-constant.
-  auto const_id = context.constant_values().Get(inst.operand_id);
-  if (const_id.is_concrete()) {
-    auto value = context.insts().GetAs<SemIR::BoolLiteral>(
-        context.constant_values().GetInstId(const_id));
-    value.value = SemIR::BoolValue::From(!value.value.ToBool());
-    return ConstantEvalResult::New(value);
-  }
-  return ConstantEvalResult::NotConstant;
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
-                             SemIR::ConstType inst) -> ConstantEvalResult {
-  // `const (const T)` evaluates to `const T`.
-  if (context.types().Is<SemIR::ConstType>(inst.inner_id)) {
-    return ConstantEvalResult::Existing(
-        context.types().GetConstantId(inst.inner_id));
-  }
-  // Otherwise, `const T` evaluates to itself.
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& context, SemIRLoc loc,
-                             SemIR::RequireCompleteType inst)
-    -> ConstantEvalResult {
-  auto witness_type_id =
-      GetSingletonType(context, SemIR::WitnessType::SingletonInstId);
-
-  // If the type is a concrete constant, require it to be complete now.
-  auto complete_type_id = inst.complete_type_id;
-  if (context.types().GetConstantId(complete_type_id).is_concrete()) {
-    if (!TryToCompleteType(context, complete_type_id, loc, [&] {
-          // TODO: It'd be nice to report the original type prior to
-          // evaluation here.
-          CARBON_DIAGNOSTIC(IncompleteTypeInMonomorphization, Error,
-                            "type {0} is incomplete", SemIR::TypeId);
-          return context.emitter().Build(loc, IncompleteTypeInMonomorphization,
-                                         complete_type_id);
-        })) {
-      return ConstantEvalResult::Error;
-    }
-    return ConstantEvalResult::New(SemIR::CompleteTypeWitness{
-        .type_id = witness_type_id,
-        .object_repr_id = context.types().GetObjectRepr(complete_type_id)});
+// Convert a ConstantEvalResult to a ConstantId. Factored out of
+// TryEvalTypedInst to avoid repeated instantiation of common code.
+static auto ConvertEvalResultToConstantId(Context& context,
+                                          ConstantEvalResult result,
+                                          Phase orig_phase)
+    -> SemIR::ConstantId {
+  if (result.is_new()) {
+    return MakeConstantResult(
+        context, result.new_inst(),
+        result.same_phase_as_inst()
+            ? orig_phase
+            : ComputeInstPhase(context, result.new_inst()));
   }
   }
-
-  // If it's not a concrete constant, require it to be complete once it
-  // becomes one.
-  return ConstantEvalResult::New(inst);
-}
-
-static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
-                             SemIR::ImportRefUnloaded inst)
-    -> ConstantEvalResult {
-  CARBON_FATAL("ImportRefUnloaded should be loaded before TryEvalInst: {0}",
-               inst);
+  return result.existing();
 }
 }
 
 
 // Evaluates an instruction of a known type in an evaluation context. The
 // Evaluates an instruction of a known type in an evaluation context. The
@@ -2078,14 +1600,12 @@ static auto TryEvalTypedInst(EvalContext& eval_context, SemIR::InstId inst_id,
                   ConstantKind == SemIR::InstConstantKind::WheneverPossible) {
                   ConstantKind == SemIR::InstConstantKind::WheneverPossible) {
       return MakeConstantResult(eval_context.context(), inst, phase);
       return MakeConstantResult(eval_context.context(), inst, phase);
     } else {
     } else {
-      ConstantEvalResult result = EvalConstantInst(
-          eval_context.context(), eval_context.GetDiagnosticLoc({inst_id}),
-          inst.As<InstT>());
-      if (result.is_new()) {
-        return MakeConstantResult(eval_context.context(), result.new_inst(),
-                                  phase);
-      }
-      return result.existing();
+      return ConvertEvalResultToConstantId(
+          eval_context.context(),
+          EvalConstantInst(eval_context.context(),
+                           eval_context.GetDiagnosticLoc({inst_id}),
+                           inst.As<InstT>()),
+          phase);
     }
     }
   }
   }
 }
 }

+ 373 - 0
toolchain/check/eval_inst.cpp

@@ -0,0 +1,373 @@
+// 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/check/eval_inst.h"
+
+#include "toolchain/check/facet_type.h"
+#include "toolchain/check/import_ref.h"
+#include "toolchain/check/type.h"
+#include "toolchain/check/type_completion.h"
+
+namespace Carbon::Check {
+
+// Performs an access into an aggregate, retrieving the specified element.
+static auto PerformAggregateAccess(Context& context, SemIR::Inst inst)
+    -> ConstantEvalResult {
+  auto access_inst = inst.As<SemIR::AnyAggregateAccess>();
+  if (auto aggregate = context.insts().TryGetAs<SemIR::AnyAggregateValue>(
+          access_inst.aggregate_id)) {
+    auto elements = context.inst_blocks().Get(aggregate->elements_id);
+    auto index = static_cast<size_t>(access_inst.index.index);
+    CARBON_CHECK(index < elements.size(), "Access out of bounds.");
+    // `Phase` is not used here. If this element is a concrete constant, then
+    // so is the result of indexing, even if the aggregate also contains a
+    // symbolic context.
+    return ConstantEvalResult::Existing(
+        context.constant_values().Get(elements[index]));
+  }
+
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::ArrayInit inst) -> ConstantEvalResult {
+  // TODO: Add an `ArrayValue` to represent a constant array object
+  // representation instead of using a `TupleValue`.
+  return ConstantEvalResult::NewSamePhase(
+      SemIR::TupleValue{.type_id = inst.type_id, .elements_id = inst.inits_id});
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::ArrayType inst)
+    -> ConstantEvalResult {
+  auto bound_inst = context.insts().Get(inst.bound_id);
+  auto int_bound = bound_inst.TryAs<SemIR::IntValue>();
+  if (!int_bound) {
+    CARBON_CHECK(context.constant_values().Get(inst.bound_id).is_symbolic(),
+                 "Unexpected inst {0} for template constant int", bound_inst);
+    return ConstantEvalResult::NewSamePhase(inst);
+  }
+  // TODO: We should check that the size of the resulting array type
+  // fits in 64 bits, not just that the bound does. Should we use a
+  // 32-bit limit for 32-bit targets?
+  const auto& bound_val = context.ints().Get(int_bound->int_id);
+  if (context.types().IsSignedInt(int_bound->type_id) &&
+      bound_val.isNegative()) {
+    CARBON_DIAGNOSTIC(ArrayBoundNegative, Error,
+                      "array bound of {0} is negative", TypedInt);
+    context.emitter().Emit(loc, ArrayBoundNegative,
+                           {.type = int_bound->type_id, .value = bound_val});
+    return ConstantEvalResult::Error;
+  }
+  if (bound_val.getActiveBits() > 64) {
+    CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
+                      "array bound of {0} is too large", TypedInt);
+    context.emitter().Emit(loc, ArrayBoundTooLarge,
+                           {.type = int_bound->type_id, .value = bound_val});
+    return ConstantEvalResult::Error;
+  }
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::AsCompatible inst) -> ConstantEvalResult {
+  // AsCompatible changes the type of the source instruction; its constant
+  // value, if there is one, needs to be modified to be of the same type.
+  auto value_id = context.constant_values().Get(inst.source_id);
+  CARBON_CHECK(value_id.is_constant());
+
+  auto value_inst =
+      context.insts().Get(context.constant_values().GetInstId(value_id));
+  value_inst.SetType(inst.type_id);
+  return ConstantEvalResult::NewAnyPhase(value_inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::BindAlias inst)
+    -> ConstantEvalResult {
+  // An alias evaluates to the value it's bound to.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.value_id));
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::BindValue /*inst*/) -> ConstantEvalResult {
+  // TODO: Handle this once we've decided how to represent constant values of
+  // reference expressions.
+  return ConstantEvalResult::TODO;
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::ClassElementAccess inst) -> ConstantEvalResult {
+  return PerformAggregateAccess(context, inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ClassDecl inst)
+    -> ConstantEvalResult {
+  // If the class has generic parameters, we don't produce a class type, but a
+  // callable whose return value is a class type.
+  if (context.classes().Get(inst.class_id).has_parameters()) {
+    return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+        .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
+  }
+
+  // A non-generic class declaration evaluates to the class type.
+  return ConstantEvalResult::NewSamePhase(
+      SemIR::ClassType{.type_id = SemIR::TypeType::SingletonTypeId,
+                       .class_id = inst.class_id,
+                       .specific_id = SemIR::SpecificId::None});
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::ClassInit inst) -> ConstantEvalResult {
+  // TODO: Add a `ClassValue` to represent a constant class object
+  // representation instead of using a `StructValue`.
+  return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+      .type_id = inst.type_id, .elements_id = inst.elements_id});
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ConstType inst)
+    -> ConstantEvalResult {
+  // `const (const T)` evaluates to `const T`.
+  if (context.types().Is<SemIR::ConstType>(inst.inner_id)) {
+    return ConstantEvalResult::Existing(
+        context.types().GetConstantId(inst.inner_id));
+  }
+  // Otherwise, `const T` evaluates to itself.
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::Converted inst)
+    -> ConstantEvalResult {
+  // A conversion evaluates to the result of the conversion.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.result_id));
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::Deref /*inst*/) -> ConstantEvalResult {
+  // TODO: Handle this.
+  return ConstantEvalResult::TODO;
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::ExportDecl inst) -> ConstantEvalResult {
+  // An export instruction evaluates to the exported declaration.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.value_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::FacetAccessType inst) -> ConstantEvalResult {
+  if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
+          inst.facet_value_inst_id)) {
+    return ConstantEvalResult::Existing(
+        context.constant_values().Get(facet_value->type_inst_id));
+  }
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::FacetAccessWitness inst) -> ConstantEvalResult {
+  if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
+          inst.facet_value_inst_id)) {
+    return ConstantEvalResult::Existing(
+        context.constant_values().Get(facet_value->witness_inst_id));
+  }
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::FloatType inst)
+    -> ConstantEvalResult {
+  return ValidateFloatType(context, loc, inst)
+             ? ConstantEvalResult::NewSamePhase(inst)
+             : ConstantEvalResult::Error;
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::FunctionDecl inst) -> ConstantEvalResult {
+  // A function declaration evaluates to a function object, which is an empty
+  // object of function type.
+  // TODO: Eventually we may need to handle captures here.
+  return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+      .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc loc,
+                      SemIR::ImplWitnessAccess inst) -> ConstantEvalResult {
+  // This is PerformAggregateAccess followed by GetConstantInSpecific.
+  if (auto witness =
+          context.insts().TryGetAs<SemIR::ImplWitness>(inst.witness_id)) {
+    auto elements = context.inst_blocks().Get(witness->elements_id);
+    auto index = static_cast<size_t>(inst.index.index);
+    CARBON_CHECK(index < elements.size(), "Access out of bounds.");
+    auto element = elements[index];
+    if (!element.has_value()) {
+      // TODO: Perhaps this should be a `{}` value with incomplete type?
+      CARBON_DIAGNOSTIC(ImplAccessMemberBeforeComplete, Error,
+                        "accessing member from impl before the end of "
+                        "its definition");
+      // TODO: Add note pointing to the impl declaration.
+      context.emitter().Emit(loc, ImplAccessMemberBeforeComplete);
+      return ConstantEvalResult::Error;
+    }
+
+    LoadImportRef(context, element);
+    return ConstantEvalResult::Existing(GetConstantValueInSpecific(
+        context.sem_ir(), witness->specific_id, element));
+  }
+
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::ImportRefUnloaded inst) -> ConstantEvalResult {
+  CARBON_FATAL("ImportRefUnloaded should be loaded before TryEvalInst: {0}",
+               inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::InitializeFrom inst) -> ConstantEvalResult {
+  // Initialization is not performed in-place during constant evaluation, so
+  // just return the value of the initializer.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.src_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::IntType inst)
+    -> ConstantEvalResult {
+  return ValidateIntType(context, loc, inst)
+             ? ConstantEvalResult::NewSamePhase(inst)
+             : ConstantEvalResult::Error;
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::InterfaceDecl inst) -> ConstantEvalResult {
+  // If the interface has generic parameters, we don't produce an interface
+  // type, but a callable whose return value is an interface type.
+  if (context.interfaces().Get(inst.interface_id).has_parameters()) {
+    return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+        .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
+  }
+
+  // A non-generic interface declaration evaluates to a facet type.
+  return ConstantEvalResult::NewSamePhase(FacetTypeFromInterface(
+      context, inst.interface_id, SemIR::SpecificId::None));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::NameRef inst)
+    -> ConstantEvalResult {
+  // A name reference evaluates to the value the name resolves to.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.value_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc loc,
+                      SemIR::RequireCompleteType inst) -> ConstantEvalResult {
+  auto witness_type_id =
+      GetSingletonType(context, SemIR::WitnessType::SingletonInstId);
+
+  // If the type is a concrete constant, require it to be complete now.
+  auto complete_type_id = inst.complete_type_id;
+  if (context.types().GetConstantId(complete_type_id).is_concrete()) {
+    if (!TryToCompleteType(context, complete_type_id, loc, [&] {
+          // TODO: It'd be nice to report the original type prior to
+          // evaluation here.
+          CARBON_DIAGNOSTIC(IncompleteTypeInMonomorphization, Error,
+                            "type {0} is incomplete", SemIR::TypeId);
+          return context.emitter().Build(loc, IncompleteTypeInMonomorphization,
+                                         complete_type_id);
+        })) {
+      return ConstantEvalResult::Error;
+    }
+    return ConstantEvalResult::NewSamePhase(SemIR::CompleteTypeWitness{
+        .type_id = witness_type_id,
+        .object_repr_id = context.types().GetObjectRepr(complete_type_id)});
+  }
+
+  // If it's not a concrete constant, require it to be complete once it
+  // becomes one.
+  return ConstantEvalResult::NewSamePhase(inst);
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::SpecificConstant inst) -> ConstantEvalResult {
+  // Pull the constant value out of the specific.
+  return ConstantEvalResult::Existing(SemIR::GetConstantValueInSpecific(
+      context.sem_ir(), inst.specific_id, inst.inst_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::SpliceBlock inst) -> ConstantEvalResult {
+  // SpliceBlock evaluates to the result value that is (typically) within the
+  // block. This can be constant even if the block contains other non-constant
+  // instructions.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.result_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::StructAccess inst) -> ConstantEvalResult {
+  return PerformAggregateAccess(context, inst);
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::StructInit inst) -> ConstantEvalResult {
+  return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+      .type_id = inst.type_id, .elements_id = inst.elements_id});
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::Temporary /*inst*/) -> ConstantEvalResult {
+  // TODO: Handle this. Can we just return the value of `init_id`?
+  return ConstantEvalResult::TODO;
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::TupleAccess inst) -> ConstantEvalResult {
+  return PerformAggregateAccess(context, inst);
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::TupleInit inst) -> ConstantEvalResult {
+  return ConstantEvalResult::NewSamePhase(SemIR::TupleValue{
+      .type_id = inst.type_id, .elements_id = inst.elements_id});
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::UnaryOperatorNot inst) -> ConstantEvalResult {
+  // `not true` -> `false`, `not false` -> `true`.
+  // All other uses of unary `not` are non-constant.
+  auto const_id = context.constant_values().Get(inst.operand_id);
+  if (const_id.is_concrete()) {
+    auto value = context.insts().GetAs<SemIR::BoolLiteral>(
+        context.constant_values().GetInstId(const_id));
+    value.value = SemIR::BoolValue::From(!value.value.ToBool());
+    return ConstantEvalResult::NewSamePhase(value);
+  }
+  return ConstantEvalResult::NotConstant;
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::ValueOfInitializer inst) -> ConstantEvalResult {
+  // Values of value expressions and initializing expressions are represented in
+  // the same way during constant evaluation, so just return the value of the
+  // operand.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.init_id));
+}
+
+auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
+                      SemIR::ValueParamPattern inst) -> ConstantEvalResult {
+  // TODO: Treat this as a non-expression (here and in GetExprCategory)
+  // once generic deduction doesn't need patterns to have constant values.
+  return ConstantEvalResult::Existing(
+      context.constant_values().Get(inst.subpattern_id));
+}
+
+auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
+                      SemIR::VtablePtr /*inst*/) -> ConstantEvalResult {
+  // TODO: Handle this.
+  return ConstantEvalResult::TODO;
+}
+
+}  // namespace Carbon::Check

+ 123 - 0
toolchain/check/eval_inst.h

@@ -0,0 +1,123 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_
+#define CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_
+
+#include "toolchain/check/eval.h"
+
+namespace Carbon::Check {
+
+// The result of constant evaluation of an instruction.
+class ConstantEvalResult {
+ public:
+  // Produce a new constant as the result of an evaluation. The phase of the
+  // produced constant must be the same as the greatest phase of the operands in
+  // the evaluation. This will typically be the case if the evaluation uses all
+  // of its operands.
+  static auto NewSamePhase(SemIR::Inst inst) -> ConstantEvalResult {
+    return ConstantEvalResult(inst, /*same_phase_as_inst=*/true);
+  }
+
+  // Produce a new constant as the result of an evaluation. The constant may
+  // have any phase. Use `NewSamePhase` instead where possible, as it avoids a
+  // phase recomputation.
+  static auto NewAnyPhase(SemIR::Inst inst) -> ConstantEvalResult {
+    return ConstantEvalResult(inst, /*same_phase_as_inst=*/false);
+  }
+
+  // Produce an existing constant as the result of an evaluation.
+  static constexpr auto Existing(SemIR::ConstantId existing_id)
+      -> ConstantEvalResult {
+    CARBON_CHECK(existing_id.is_constant());
+    return ConstantEvalResult(existing_id);
+  }
+
+  // Indicates that an error was produced by evaluation.
+  static const ConstantEvalResult Error;
+
+  // Indicates that we encountered an instruction whose evaluation is
+  // non-constant despite having constant operands. This should be rare;
+  // usually we want to produce an error in this case.
+  static const ConstantEvalResult NotConstant;
+
+  // Indicates that we encountered an instruction for which we've not
+  // implemented constant evaluation yet. Instruction is treated as not
+  // constant.
+  static const ConstantEvalResult TODO;
+
+  // Returns whether the result of evaluation is that we should produce a new
+  // constant described by `new_inst()` rather than an existing `ConstantId`
+  // described by `existing()`.
+  auto is_new() const -> bool { return !result_id_.has_value(); }
+
+  // Returns the existing constant that this the instruction evaluates to, or
+  // `None` if this is evaluation produces a new constant.
+  auto existing() const -> SemIR::ConstantId { return result_id_; }
+
+  // Returns the new constant instruction that is the result of evaluation.
+  auto new_inst() const -> SemIR::Inst {
+    CARBON_CHECK(is_new());
+    return new_inst_;
+  }
+
+  // Whether the new constant instruction is known to have the same phase as the
+  // evaluated instruction. Requires `is_new()`.
+  auto same_phase_as_inst() const -> bool {
+    CARBON_CHECK(is_new());
+    return same_phase_as_inst_;
+  }
+
+ private:
+  constexpr explicit ConstantEvalResult(SemIR::ConstantId raw_id)
+      : result_id_(raw_id), same_phase_as_inst_(false) {}
+
+  explicit ConstantEvalResult(SemIR::Inst inst, bool same_phase_as_inst)
+      : result_id_(SemIR::ConstantId::None),
+        new_inst_(inst),
+        same_phase_as_inst_(same_phase_as_inst) {}
+
+  SemIR::ConstantId result_id_;
+  union {
+    SemIR::Inst new_inst_;
+  };
+  bool same_phase_as_inst_;
+};
+
+constexpr ConstantEvalResult ConstantEvalResult::Error =
+    Existing(SemIR::ErrorInst::SingletonConstantId);
+
+constexpr ConstantEvalResult ConstantEvalResult::NotConstant =
+    ConstantEvalResult(SemIR::ConstantId::NotConstant);
+
+constexpr ConstantEvalResult ConstantEvalResult::TODO = NotConstant;
+
+// `EvalConstantInst` evaluates an instruction whose operands are all constant,
+// in a context unrelated to the enclosing evaluation. The function is given the
+// instruction after its operands, including its type, are replaced by their
+// evaluated value, and returns a `ConstantEvalResult` describing the result of
+// evaluating the instruction.
+//
+// An overload is defined for each type whose constant kind is one of the
+// following:
+//
+// - InstConstantKind::Indirect
+// - InstConstantKind::SymbolicOnly
+// - InstConstantKind::Conditional
+//
+// ... except for cases where the result of evaluation depends on the evaluation
+// context itself. Those cases are handled by explicit specialization of
+// `TryEvalTypedInst` in `eval.cpp` instead.
+//
+// Overloads are *declared* for all types, because there isn't a good way to
+// declare only the overloads we want here without duplicating the list of
+// types. Missing overloads will be diagnosed when linking.
+#define CARBON_SEM_IR_INST_KIND(Kind)                                     \
+  auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::Kind inst) \
+      -> ConstantEvalResult;
+#include "toolchain/sem_ir/inst_kind.def"
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_

+ 58 - 0
toolchain/check/type.cpp

@@ -10,6 +10,64 @@
 
 
 namespace Carbon::Check {
 namespace Carbon::Check {
 
 
+// Enforces that an integer type has a valid bit width.
+auto ValidateIntType(Context& context, SemIRLoc loc, SemIR::IntType result)
+    -> bool {
+  auto bit_width =
+      context.insts().TryGetAs<SemIR::IntValue>(result.bit_width_id);
+  if (!bit_width) {
+    // Symbolic bit width.
+    return true;
+  }
+  const auto& bit_width_val = context.ints().Get(bit_width->int_id);
+  if (bit_width_val.isZero() ||
+      (context.types().IsSignedInt(bit_width->type_id) &&
+       bit_width_val.isNegative())) {
+    CARBON_DIAGNOSTIC(IntWidthNotPositive, Error,
+                      "integer type width of {0} is not positive", TypedInt);
+    context.emitter().Emit(
+        loc, IntWidthNotPositive,
+        {.type = bit_width->type_id, .value = bit_width_val});
+    return false;
+  }
+  if (bit_width_val.ugt(IntStore::MaxIntWidth)) {
+    CARBON_DIAGNOSTIC(IntWidthTooLarge, Error,
+                      "integer type width of {0} is greater than the "
+                      "maximum supported width of {1}",
+                      TypedInt, int);
+    context.emitter().Emit(loc, IntWidthTooLarge,
+                           {.type = bit_width->type_id, .value = bit_width_val},
+                           IntStore::MaxIntWidth);
+    return false;
+  }
+  return true;
+}
+
+// Enforces that the bit width is 64 for a float.
+auto ValidateFloatBitWidth(Context& context, SemIRLoc loc,
+                           SemIR::InstId inst_id) -> bool {
+  auto inst = context.insts().GetAs<SemIR::IntValue>(inst_id);
+  if (context.ints().Get(inst.int_id) == 64) {
+    return true;
+  }
+
+  CARBON_DIAGNOSTIC(CompileTimeFloatBitWidth, Error, "bit width must be 64");
+  context.emitter().Emit(loc, CompileTimeFloatBitWidth);
+  return false;
+}
+
+// Enforces that a float type has a valid bit width.
+auto ValidateFloatType(Context& context, SemIRLoc loc, SemIR::FloatType result)
+    -> bool {
+  auto bit_width =
+      context.insts().TryGetAs<SemIR::IntValue>(result.bit_width_id);
+  if (!bit_width) {
+    // Symbolic bit width.
+    return true;
+  }
+  return ValidateFloatBitWidth(context, loc, result.bit_width_id);
+}
+
 // Gets or forms a type_id for a type, given the instruction kind and arguments.
 // Gets or forms a type_id for a type, given the instruction kind and arguments.
 template <typename InstT, typename... EachArgT>
 template <typename InstT, typename... EachArgT>
 static auto GetTypeImpl(Context& context, EachArgT... each_arg)
 static auto GetTypeImpl(Context& context, EachArgT... each_arg)

+ 12 - 0
toolchain/check/type.h

@@ -11,6 +11,18 @@
 
 
 namespace Carbon::Check {
 namespace Carbon::Check {
 
 
+// Enforces that an integer type has a valid bit width.
+auto ValidateIntType(Context& context, SemIRLoc loc, SemIR::IntType result)
+    -> bool;
+
+// Enforces that the bit width is 64 for a float.
+auto ValidateFloatBitWidth(Context& context, SemIRLoc loc,
+                           SemIR::InstId inst_id) -> bool;
+
+// Enforces that a float type has a valid bit width.
+auto ValidateFloatType(Context& context, SemIRLoc loc, SemIR::FloatType result)
+    -> bool;
+
 // Gets the type to use for an unbound associated entity declared in this
 // Gets the type to use for an unbound associated entity declared in this
 // interface. For example, this is the type of `I.T` after
 // interface. For example, this is the type of `I.T` after
 // `interface I { let T:! type; }`. The name of the interface is used for
 // `interface I { let T:! type; }`. The name of the interface is used for