Remove ArgKinds to encourage safer coding patterns (#5212)

#5171 ran into an issue where the wrong kind was associated with an arg
(`auto arg1 = RefineOperand(context, loc_id, arg0_kind,
action.arg1());`). This PR is trying to reduce risk of similar errors by
replaced `ArgKinds()` with instead an `ArgAndKind` structure and
corresponding accessors.

A couple things I considered and discarded were:

- Adding `CARBON_KIND_SWITCH` support (in this PR -- see #5216).
- The particular way that `ForCase` works would need to change, and I
was hesitant to do that here.
- But this is why I did add `As` to `ArgAndKind`, because it had me
thinking in that direction.
- Trying to make wrapper functions like `MutateArgs(callback_fn);`. This
kind of approach gets a little messy due to some of the conditional
passes, and in particular the reverse-iteration done for `PopOperand` in
subst.cpp
- Making something like `args_and_kinds() -> std::array<ArgAndKind, 2>`.
There's one spot where iteration is already set up as a loop, but for
others it felt a little convoluted with less gain than
`MutateArgs`-style things.

I'm not sure if there's a better way to set up the table generators, I
might keep tinkering with those for ideas.
This commit is contained in:
Jon Ross-Perkins
2025-03-28 23:24:36 +00:00
committed by GitHub
parent 2973cafd60
commit 4cb61ae4e1
8 changed files with 204 additions and 161 deletions
+44 -31
View File
@@ -640,36 +640,52 @@ static constexpr bool HasGetConstantValueOverload = requires {
Accept<auto (*)(EvalContext&, IdT, Phase*)->IdT>(GetConstantValue);
};
using ArgHandlerFnT = auto(EvalContext& context, int32_t arg, Phase* phase)
-> int32_t;
// Returns a lookup table to get constants by Id::Kind. Requires a null IdKind
// as a parameter in order to get the type pack.
template <typename... Types>
static constexpr auto MakeArgHandlerTable(
SemIR::TypeEnum<Types...>* /*id_kind*/)
-> std::array<ArgHandlerFnT*, SemIR::IdKind::NumValues> {
std::array<ArgHandlerFnT*, SemIR::IdKind::NumValues> table = {};
((table[SemIR::IdKind::template For<Types>.ToIndex()] =
[](EvalContext& eval_context, int32_t arg, Phase* phase) -> int32_t {
auto id = SemIR::Inst::FromRaw<Types>(arg);
if constexpr (HasGetConstantValueOverload<Types>) {
// If we have a custom `GetConstantValue` overload, call it.
return SemIR::Inst::ToRaw(GetConstantValue(eval_context, id, phase));
} else {
// Otherwise, we assume the value is already constant.
return arg;
}
}),
...);
table[SemIR::IdKind::Invalid.ToIndex()] = [](EvalContext& /*context*/,
int32_t /*arg*/,
Phase* /*phase*/) -> int32_t {
CARBON_FATAL("Instruction has argument with invalid IdKind");
};
table[SemIR::IdKind::None.ToIndex()] =
[](EvalContext& /*context*/, int32_t arg, Phase* /*phase*/) -> int32_t {
return arg;
};
return table;
}
// Given the stored value `arg` of an instruction field and its corresponding
// kind `kind`, returns the constant value to use for that field, if it has a
// constant phase. `*phase` is updated to include the new constant value. If
// the resulting phase is not constant, the returned value is not useful and
// will typically be `NoneIndex`.
template <typename... Type>
static auto GetConstantValueForArg(EvalContext& eval_context,
SemIR::TypeEnum<Type...> kind, int32_t arg,
SemIR::Inst::ArgAndKind arg_and_kind,
Phase* phase) -> int32_t {
using Handler = auto(EvalContext&, int32_t arg, Phase * phase)->int32_t;
static constexpr Handler* Handlers[] = {
[](EvalContext& eval_context, int32_t arg, Phase* phase) -> int32_t {
auto id = SemIR::Inst::FromRaw<Type>(arg);
if constexpr (HasGetConstantValueOverload<Type>) {
// If we have a custom `GetConstantValue` overload, call it.
return SemIR::Inst::ToRaw(GetConstantValue(eval_context, id, phase));
} else {
// Otherwise, we assume the value is already constant.
return arg;
}
}...,
[](EvalContext&, int32_t, Phase*) -> int32_t {
// Handler for IdKind::Invalid is next.
CARBON_FATAL("Instruction has argument with invalid IdKind");
},
[](EvalContext&, int32_t arg, Phase*) -> int32_t {
// Handler for IdKind::None is last.
return arg;
}};
return Handlers[kind.ToIndex()](eval_context, arg, phase);
static constexpr auto Table =
MakeArgHandlerTable(static_cast<SemIR::IdKind*>(nullptr));
return Table[arg_and_kind.kind.ToIndex()](eval_context, arg_and_kind.value,
phase);
}
// Given an instruction, replaces its type and operands with their constant
@@ -680,22 +696,20 @@ static auto ReplaceAllFieldsWithConstantValues(EvalContext& eval_context,
SemIR::Inst* inst, Phase* phase)
-> bool {
auto type_id = SemIR::TypeId(
GetConstantValueForArg(eval_context, SemIR::IdKind::For<SemIR::TypeId>,
inst->type_id().index, phase));
GetConstantValueForArg(eval_context, inst->type_id_and_kind(), phase));
inst->SetType(type_id);
if (!IsConstant(*phase)) {
return false;
}
auto kinds = inst->ArgKinds();
auto arg0 =
GetConstantValueForArg(eval_context, kinds.first, inst->arg0(), phase);
GetConstantValueForArg(eval_context, inst->arg0_and_kind(), phase);
if (!IsConstant(*phase)) {
return false;
}
auto arg1 =
GetConstantValueForArg(eval_context, kinds.second, inst->arg1(), phase);
GetConstantValueForArg(eval_context, inst->arg1_and_kind(), phase);
if (!IsConstant(*phase)) {
return false;
}
@@ -1601,9 +1615,8 @@ static auto ComputeInstPhase(Context& context, SemIR::Inst inst) -> 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);
GetConstantValueForArg(eval_context, inst.arg0_and_kind(), &phase);
GetConstantValueForArg(eval_context, inst.arg1_and_kind(), &phase);
CARBON_CHECK(IsConstant(phase));
return phase;
}