Skip to content

Commit

Permalink
Extend ConstantEvalResult to handle cases where the resulting
Browse files Browse the repository at this point in the history
instruction has a different phase from the arguments to the evaluation.

This allows us to move AsCompatible handling out to eval_inst.cpp.
  • Loading branch information
zygoloid committed Feb 27, 2025
1 parent 8a99c1e commit 2c03896
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 65 deletions.
62 changes: 30 additions & 32 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1528,33 +1528,33 @@ static auto MakeConstantForCall(EvalContext& eval_context, SemIRLoc loc,
return SemIR::ConstantId::NotConstant;
}

// TODO: This overload uses machinery that is local to this file. Find a way of
// expressing this that doesn't couple this logic to the evaluation machinery.
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));
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.
// 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 kinds = value_inst.ArgKinds();
GetConstantValueForArg(eval_context, kinds.first, value_inst.arg0(), &phase);
GetConstantValueForArg(eval_context, kinds.second, value_inst.arg1(), &phase);

auto phase = GetPhase(context.constant_values(),
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));
return 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));
// 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()));
}
return result.existing();
}

// Evaluates an instruction of a known type in an evaluation context. The
Expand Down Expand Up @@ -1600,14 +1600,12 @@ static auto TryEvalTypedInst(EvalContext& eval_context, SemIR::InstId inst_id,
ConstantKind == SemIR::InstConstantKind::WheneverPossible) {
return MakeConstantResult(eval_context.context(), inst, phase);
} 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);
}
}
}
Expand Down
65 changes: 37 additions & 28 deletions toolchain/check/eval_inst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ static auto PerformAggregateAccess(Context& context, SemIR::Inst inst)
context.constant_values().Get(elements[index]));
}

return ConstantEvalResult::New(inst);
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::New(
return ConstantEvalResult::NewSamePhase(
SemIR::TupleValue{.type_id = inst.type_id, .elements_id = inst.inits_id});
}

Expand All @@ -45,7 +45,7 @@ auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::ArrayType inst)
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);
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
Expand All @@ -66,14 +66,21 @@ auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::ArrayType inst)
{.type = int_bound->type_id, .value = bound_val});
return ConstantEvalResult::Error;
}
return ConstantEvalResult::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

// TODO: The AsCompatible overload should be here, but is in eval.cpp instead,
// because it uses eval.cpp's machinery.
//
// auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::AsCompatible inst)
// -> ConstantEvalResult;
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 {
Expand All @@ -99,12 +106,12 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ClassDecl inst)
// 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{
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::New(
return ConstantEvalResult::NewSamePhase(
SemIR::ClassType{.type_id = SemIR::TypeType::SingletonTypeId,
.class_id = inst.class_id,
.specific_id = SemIR::SpecificId::None});
Expand All @@ -114,7 +121,7 @@ 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::New(SemIR::StructValue{
return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
.type_id = inst.type_id, .elements_id = inst.elements_id});
}

Expand All @@ -126,7 +133,7 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ConstType inst)
context.types().GetConstantId(inst.inner_id));
}
// Otherwise, `const T` evaluates to itself.
return ConstantEvalResult::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::Converted inst)
Expand Down Expand Up @@ -156,7 +163,7 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
return ConstantEvalResult::Existing(
context.constant_values().Get(facet_value->type_inst_id));
}
return ConstantEvalResult::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
Expand All @@ -166,21 +173,22 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
return ConstantEvalResult::Existing(
context.constant_values().Get(facet_value->witness_inst_id));
}
return ConstantEvalResult::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::FloatType inst)
-> ConstantEvalResult {
return ValidateFloatType(context, loc, inst) ? ConstantEvalResult::New(inst)
: ConstantEvalResult::Error;
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::New(SemIR::StructValue{
return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
.type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
}

Expand Down Expand Up @@ -208,7 +216,7 @@ auto EvalConstantInst(Context& context, SemIRLoc loc,
context.sem_ir(), witness->specific_id, element));
}

return ConstantEvalResult::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
Expand All @@ -227,21 +235,22 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,

auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::IntType inst)
-> ConstantEvalResult {
return ValidateIntType(context, loc, inst) ? ConstantEvalResult::New(inst)
: ConstantEvalResult::Error;
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::New(SemIR::StructValue{
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::New(FacetTypeFromInterface(
return ConstantEvalResult::NewSamePhase(FacetTypeFromInterface(
context, inst.interface_id, SemIR::SpecificId::None));
}

Expand Down Expand Up @@ -270,14 +279,14 @@ auto EvalConstantInst(Context& context, SemIRLoc loc,
})) {
return ConstantEvalResult::Error;
}
return ConstantEvalResult::New(SemIR::CompleteTypeWitness{
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::New(inst);
return ConstantEvalResult::NewSamePhase(inst);
}

auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
Expand All @@ -303,7 +312,7 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,

auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
SemIR::StructInit inst) -> ConstantEvalResult {
return ConstantEvalResult::New(SemIR::StructValue{
return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
.type_id = inst.type_id, .elements_id = inst.elements_id});
}

Expand All @@ -320,7 +329,7 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,

auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/,
SemIR::TupleInit inst) -> ConstantEvalResult {
return ConstantEvalResult::New(SemIR::TupleValue{
return ConstantEvalResult::NewSamePhase(SemIR::TupleValue{
.type_id = inst.type_id, .elements_id = inst.elements_id});
}

Expand All @@ -333,7 +342,7 @@ auto EvalConstantInst(Context& context, SemIRLoc /*loc*/,
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::NewSamePhase(value);
}
return ConstantEvalResult::NotConstant;
}
Expand Down
27 changes: 22 additions & 5 deletions toolchain/check/eval_inst.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ class ConstantEvalResult {
// 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);
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.
Expand Down Expand Up @@ -55,17 +62,27 @@ class ConstantEvalResult {
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) {}
: result_id_(raw_id), same_phase_as_inst_(false) {}

explicit ConstantEvalResult(SemIR::Inst inst)
: result_id_(SemIR::ConstantId::None), new_inst_(inst) {}
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 =
Expand Down

0 comments on commit 2c03896

Please sign in to comment.