Restructure ArgAndKind as a type-safe generic ID. (#7172)

The key changes here are:
- Relocating and renaming it to align with `IdKind` (and relocating
`ToRaw` and `FromRaw` to follow it).
- Adding a `Dispatch` method that provides a generic overload-based API
for expressing per-ID-kind dispatch, and rewriting existing code to use
it.

Note in particular that using overloads instead of switch cases makes it
possible to generically handle all specializations of a templated ID
type, e.g. `SomeIdType<T>` for all `T`. We have no such templated ID
types yet, but I'm introducing one in a follow-up PR that needs this
capability.
This commit is contained in:
Geoff Romer
2026-05-07 00:13:52 +00:00
committed by GitHub
parent db24042fe5
commit daebbf32fa
10 changed files with 236 additions and 218 deletions
+26 -22
View File
@@ -90,6 +90,13 @@ auto OperandDependence(Context& context, SemIR::InstId inst_id)
OperandDependence(context, context.constant_values().Get(inst_id)));
}
static auto OperandDependence(Context& context, SemIR::MetaInstId inst_id)
-> SemIR::ConstantDependence {
// A meta-instruction operand makes the instruction dependent if its type or
// constant value is dependent.
return OperandDependence(context, SemIR::InstId{inst_id});
}
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
-> SemIR::ConstantDependence {
// An instruction operand makes the instruction dependent if its type or
@@ -98,30 +105,27 @@ auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
return OperandDependence(context, context.constant_values().Get(inst_id));
}
static auto OperandDependence(Context& context, SemIR::Inst::ArgAndKind arg)
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT> &&
SameAsOneOf<IdT, SemIR::IdAndKind::NoneType, SemIR::AbsoluteInstId,
SemIR::CallParamIndex, SemIR::NameId>
static auto OperandDependence(Context& /*context*/, IdT /*id*/)
-> SemIR::ConstantDependence {
CARBON_KIND_SWITCH(arg) {
case CARBON_KIND(SemIR::InstId inst_id): {
return OperandDependence(context, inst_id);
}
return SemIR::ConstantDependence::None;
}
case CARBON_KIND(SemIR::MetaInstId inst_id): {
return OperandDependence(context, inst_id);
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto OperandDependence(Context& /*context*/, IdT /*id*/)
-> SemIR::ConstantDependence {
// TODO: Properly handle different argument kinds.
CARBON_FATAL("Unexpected argument kind for action: {}", IdT::Label);
}
case CARBON_KIND(SemIR::TypeInstId inst_id): {
return OperandDependence(context, inst_id);
}
case SemIR::IdKind::None:
case SemIR::IdKind::For<SemIR::AbsoluteInstId>:
case SemIR::IdKind::For<SemIR::NameId>:
return SemIR::ConstantDependence::None;
default:
// TODO: Properly handle different argument kinds.
CARBON_FATAL("Unexpected argument kind for action");
}
static auto OperandDependence(Context& context, SemIR::IdAndKind arg)
-> SemIR::ConstantDependence {
return arg.Dispatch<SemIR::ConstantDependence>(
[&](auto id) { return OperandDependence(context, id); });
}
auto ActionIsPerformable(Context& context, SemIR::Inst action_inst) -> bool {
@@ -208,7 +212,7 @@ static auto AddDependentActionSpliceImpl(Context& context,
// their concrete values, so that the action doesn't need to know which specific
// it is operating on.
static auto RefineOperand(Context& context, SemIR::LocId loc_id,
SemIR::Inst::ArgAndKind arg) -> int32_t {
SemIR::IdAndKind arg) -> int32_t {
if (auto inst_id = arg.TryAs<SemIR::MetaInstId>()) {
auto inst = context.insts().Get(*inst_id);
if (inst.Is<SemIR::SpliceInst>()) {
+1 -1
View File
@@ -110,7 +110,7 @@ class DeductionWorklist {
}
// Adds a (param, arg) pair for an instruction argument, given its kind.
auto AddInstArg(SemIR::Inst::ArgAndKind param, int32_t arg) -> void {
auto AddInstArg(SemIR::IdAndKind param, int32_t arg) -> void {
CARBON_KIND_SWITCH(param) {
case SemIR::IdKind::None:
case SemIR::IdKind::For<SemIR::ClassId>:
+76 -96
View File
@@ -877,43 +877,25 @@ 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 the arg handler for an `IdKind`.
template <typename... Types>
static auto GetArgHandlerFn(TypeEnum<Types...> id_kind) -> ArgHandlerFnT* {
static constexpr std::array<ArgHandlerFnT*, SemIR::IdKind::NumValues> Table =
{
[](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;
}
}...,
// Invalid and None handling (ordering-sensitive).
[](auto...) -> int32_t { CARBON_FATAL("Unexpected invalid IdKind"); },
[](EvalContext& /*context*/, int32_t arg,
Phase* /*phase*/) -> int32_t { return arg; },
};
return Table[id_kind.ToIndex()];
}
// 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`.
static auto GetConstantValueForArg(EvalContext& eval_context,
SemIR::Inst::ArgAndKind arg_and_kind,
Phase* phase) -> int32_t {
return GetArgHandlerFn(arg_and_kind.kind())(eval_context,
arg_and_kind.value(), phase);
SemIR::IdAndKind arg_and_kind, Phase* phase)
-> int32_t {
return arg_and_kind.Dispatch<int32_t>([&]<typename IdT>(IdT id) -> int32_t {
if constexpr (HasGetConstantValueOverload<IdT>) {
// If we have a custom `GetConstantValue` overload, call it.
return SemIR::ToRaw(GetConstantValue(eval_context, id, phase));
} else if constexpr (std::same_as<IdT, SemIR::IdAndKind::InvalidType>) {
CARBON_FATAL("Unexpected invalid IdKind");
} else {
// Otherwise, we assume the value is already constant.
return SemIR::ToRaw(id);
}
});
}
// Given an instruction, replaces its operands with their constant values from
@@ -985,76 +967,74 @@ static auto ResolveSpecificDeclForSpecificId(EvalContext& eval_context,
specific_id);
}
static auto ResolveSpecificDeclForArg(EvalContext& eval_context,
SemIR::FacetTypeId facet_type_id)
-> void {
const auto& info = eval_context.context().facet_types().Get(facet_type_id);
for (const auto& interface : info.extend_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& interface : info.self_impls_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& constraint : info.extend_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, constraint.specific_id);
}
for (const auto& constraint : info.self_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, constraint.specific_id);
}
for (const auto& type_impls : info.type_impls_interfaces) {
ResolveSpecificDeclForSpecificId(eval_context,
type_impls.specific_interface.specific_id);
}
for (const auto& type_impls : info.type_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_named_constraint.specific_id);
}
}
static auto ResolveSpecificDeclForArg(EvalContext& eval_context,
SemIR::SpecificId specific_id) -> void {
ResolveSpecificDeclForSpecificId(eval_context, specific_id);
}
static auto ResolveSpecificDeclForArg(
EvalContext& eval_context, SemIR::SpecificInterfaceId specific_interface_id)
-> void {
ResolveSpecificDeclForSpecificId(eval_context,
eval_context.specific_interfaces()
.Get(specific_interface_id)
.specific_id);
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT> &&
SameAsOneOf<IdT, SemIR::IdAndKind::NoneType, SemIR::DestInstId,
SemIR::EntityNameId, SemIR::InstBlockId, SemIR::InstId,
SemIR::MetaInstId, SemIR::StructTypeFieldsId,
SemIR::TypeInstId>
static auto ResolveSpecificDeclForArg(EvalContext& /*eval_context*/, IdT /*id*/)
-> void {
// These id types have a GetConstantValue() overload but that overload
// does not canonicalize any SpecificId in the value type.
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto ResolveSpecificDeclForArg(EvalContext& /*eval_context*/, IdT /*id*/)
-> void {
if constexpr (HasGetConstantValueOverload<IdT>) {
CARBON_FATAL("Missing case for {0} which has a GetConstantValue() overload",
IdT::Label);
}
}
// Resolves the specific declarations for a specific id in any field of the
// `inst` instruction.
static auto ResolveSpecificDeclForInst(EvalContext& eval_context,
const SemIR::Inst& inst) -> void {
for (auto arg_and_kind : {inst.arg0_and_kind(), inst.arg1_and_kind()}) {
// This switch must handle any field type that has a GetConstantValue()
// overload which canonicalizes a specific (and thus potentially forms a new
// specific) as part of forming its constant value.
CARBON_KIND_SWITCH(arg_and_kind) {
case CARBON_KIND(SemIR::FacetTypeId facet_type_id): {
const auto& info =
eval_context.context().facet_types().Get(facet_type_id);
for (const auto& interface : info.extend_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& interface : info.self_impls_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& constraint : info.extend_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context,
constraint.specific_id);
}
for (const auto& constraint : info.self_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context,
constraint.specific_id);
}
for (const auto& type_impls : info.type_impls_interfaces) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_interface.specific_id);
}
for (const auto& type_impls : info.type_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_named_constraint.specific_id);
}
break;
}
case CARBON_KIND(SemIR::SpecificId specific_id): {
ResolveSpecificDeclForSpecificId(eval_context, specific_id);
break;
}
case CARBON_KIND(SemIR::SpecificInterfaceId specific_interface_id): {
ResolveSpecificDeclForSpecificId(eval_context,
eval_context.specific_interfaces()
.Get(specific_interface_id)
.specific_id);
break;
}
// These id types have a GetConstantValue() overload but that overload
// does not canonicalize any SpecificId in the value type.
case SemIR::IdKind::For<SemIR::DestInstId>:
case SemIR::IdKind::For<SemIR::EntityNameId>:
case SemIR::IdKind::For<SemIR::InstBlockId>:
case SemIR::IdKind::For<SemIR::InstId>:
case SemIR::IdKind::For<SemIR::MetaInstId>:
case SemIR::IdKind::For<SemIR::StructTypeFieldsId>:
case SemIR::IdKind::For<SemIR::TypeInstId>:
break;
case SemIR::IdKind::None:
// No arg.
break;
default:
CARBON_CHECK(
!KindHasGetConstantValueOverload(arg_and_kind.kind()),
"Missing case for {0} which has a GetConstantValue() overload",
arg_and_kind.kind());
break;
}
arg_and_kind.Dispatch<void>(
[&](auto id) { ResolveSpecificDeclForArg(eval_context, id); });
}
}
+2 -2
View File
@@ -87,7 +87,7 @@ class Worklist {
// Pushes the specified operand onto the worklist.
static auto PushOperand(Context& context, Worklist& worklist,
SemIR::Inst::ArgAndKind arg) -> void {
SemIR::IdAndKind arg) -> void {
auto push_block = [&](SemIR::InstBlockId block_id) {
for (auto inst_id :
context.inst_blocks().Get(SemIR::InstBlockId(block_id))) {
@@ -182,7 +182,7 @@ static auto ExpandOperands(Context& context, Worklist& worklist,
// Pops the specified operand from the worklist and returns it.
static auto PopOperand(Context& context, Worklist& worklist,
SemIR::Inst::ArgAndKind arg) -> int32_t {
SemIR::IdAndKind arg) -> int32_t {
auto pop_block_id = [&](SemIR::InstBlockId old_inst_block_id) {
auto size = context.inst_blocks().Get(old_inst_block_id).size();
SemIR::CopyOnWriteInstBlock new_inst_block(&context.sem_ir(),
+1 -1
View File
@@ -15,7 +15,7 @@
namespace Carbon::SemIR {
// Returns the InstId represented by an instruction operand.
static auto AsAnyInstId(Inst::ArgAndKind arg) -> InstId {
static auto AsAnyInstId(IdAndKind arg) -> InstId {
if (auto inst_id = arg.TryAs<SemIR::InstId>()) {
return *inst_id;
}
+10 -2
View File
@@ -1052,8 +1052,16 @@ auto Formatter::FormatNameAndForm(InstId inst_id, Inst inst) -> void {
}
}
auto Formatter::FormatInstArgAndKind(Inst::ArgAndKind arg_and_kind) -> void {
GetFormatArgFn(arg_and_kind.kind())(*this, arg_and_kind.value());
auto Formatter::FormatInstArgAndKind(IdAndKind arg_and_kind) -> void {
arg_and_kind.Dispatch<void>([this]<typename IdT>(IdT arg) {
if constexpr (requires { FormatArg(arg); }) {
FormatArg(arg);
} else if constexpr (std::is_same_v<IdT, IdAndKind::NoneType>) {
// Do nothing
} else {
CARBON_FATAL("Missing FormatArg for {0}", typeid(IdT).name());
}
});
}
auto Formatter::FormatInstRhs(Inst inst) -> void {
+2 -27
View File
@@ -279,15 +279,8 @@ class Formatter {
auto FormatArg(StringLiteralValueId id) -> void;
auto FormatArg(ConstantId id) -> void { FormatConstant(id); }
// A `FormatArg` wrapper for `FormatInstArgAndKind`.
using FormatArgFnT = auto(Formatter& formatter, int32_t arg) -> void;
// Returns the `FormatArgFnT` for the given `IdKind`.
template <typename... Types>
static auto GetFormatArgFn(TypeEnum<Types...> id_kind) -> FormatArgFnT*;
// Calls `FormatArg` from an `ArgAndKind`.
auto FormatInstArgAndKind(Inst::ArgAndKind arg_and_kind) -> void;
// Calls `FormatArg` from an `IdAndKind`.
auto FormatInstArgAndKind(IdAndKind arg_and_kind) -> void;
auto FormatReturnSlotArg(InstId dest_id) -> void;
@@ -410,24 +403,6 @@ auto Formatter::FormatEntityStart(llvm::StringRef entity_kind,
FormatEntityStart(entity_kind, entity.generic_id, entity_id);
}
template <typename... Types>
auto Formatter::GetFormatArgFn(TypeEnum<Types...> id_kind) -> FormatArgFnT* {
static constexpr std::array<FormatArgFnT*, IdKind::NumValues> Table = {
[](Formatter& formatter, int32_t arg) -> void {
auto typed_arg = Inst::FromRaw<Types>(arg);
if constexpr (requires { formatter.FormatArg(typed_arg); }) {
formatter.FormatArg(typed_arg);
} else {
CARBON_FATAL("Missing FormatArg for {0}", typeid(Types).name());
}
}...,
// Invalid and None handling (ordering-sensitive).
[](auto...) -> void { CARBON_FATAL("Unexpected invalid IdKind"); },
[](auto...) -> void {},
};
return Table[id_kind.ToIndex()];
}
} // namespace Carbon::SemIR
#endif // CARBON_TOOLCHAIN_SEM_IR_FORMATTER_H_
+108 -2
View File
@@ -15,8 +15,8 @@ namespace Carbon::SemIR {
//
// As instruction operands, the types listed here can appear as fields of typed
// instructions (`toolchain/sem_ir/typed_insts.h`) and must implement the
// `FromRaw` and `ToRaw` protocol in `Inst`. In most cases this is done by
// inheriting from `IdBase` or `IndexBase`.
// `FromRaw` and `ToRaw` protocol. In most cases this is done by inheriting from
// `IdBase` or `IndexBase`.
//
// clang-format off: We want one per line.
using IdKind = TypeEnum<
@@ -70,6 +70,112 @@ using IdKind = TypeEnum<
VtableId>;
// clang-format on
// Convert a field to its raw representation.
static constexpr auto ToRaw(AnyIdBase base) -> int32_t { return base.index; }
// Convert a field from its raw representation.
template <typename T>
requires IdKind::Contains<T>
static constexpr auto FromRaw(int32_t raw) -> T {
return T(raw);
}
// Specialization for IntId.
static constexpr auto ToRaw(IntId id) -> int32_t { return id.AsRaw(); }
template <>
constexpr auto FromRaw<IntId>(int32_t raw) -> IntId {
return IntId::MakeRaw(raw);
}
// A type-safe wrapper around any of the ID types in IdKind.
class IdAndKind {
public:
explicit IdAndKind(IdKind kind, int32_t value) : kind_(kind), value_(value) {}
// Converts to `IdT`, validating the `kind` matches.
template <typename IdT>
auto As() const -> IdT {
CARBON_DCHECK(kind_ == IdKind::For<IdT>);
return IdT(value_);
}
// Converts to `IdT`, returning nullopt if the kind is incorrect.
template <typename IdT>
auto TryAs() const -> std::optional<IdT> {
if (kind_ != IdKind::For<IdT>) {
return std::nullopt;
}
return IdT(value_);
}
auto kind() const -> IdKind { return kind_; }
auto value() const -> int32_t { return value_; }
// Sentinel type that represents TypeEnum::Invalid in a Dispatch overload set.
// TODO: Consider moving these to TypeEnum.
struct InvalidType : public Printable<InvalidType> {
static constexpr llvm::StringLiteral Label = "invalid";
void Print(llvm::raw_ostream& out) const { out << Label; }
};
// Sentinel type that represents TypeEnum::None in a Dispatch overload set.
struct NoneType : public Printable<NoneType> {
static constexpr llvm::StringLiteral Label = "none";
void Print(llvm::raw_ostream& out) const { out << Label; }
};
// Converts `*this` to the type corresponding to `kind()`, passes it to `f`,
// and returns the result. If `kind()` is `Invalid` or `None`, `f` is called
// with an `InvalidType` or `NoneType` argument. `f`'s return value must
// be convertible to `R`.
template <typename R, typename F>
auto Dispatch(F&& f) const -> R {
return GetDispatchFn<R, F>(kind_)(std::forward<F>(f), value_);
}
private:
template <typename R, typename F>
using DispatchFnT = auto(F&& f, int32_t id) -> R;
template <typename R, typename F, typename... Ids>
static auto GetDispatchFn(TypeEnum<Ids...> id_kind) -> DispatchFnT<R, F>* {
static constexpr std::array<DispatchFnT<R, F>*, TypeEnum<Ids...>::NumValues>
Table = {
[](F&& f, int32_t id) -> R {
return std::forward<F>(f)(SemIR::FromRaw<Ids>(id));
}...,
[](F&& f, int32_t /*id*/) -> R {
return std::forward<F>(f)(InvalidType{});
},
[](F&& f, int32_t /*id*/) -> R {
return std::forward<F>(f)(NoneType{});
},
};
return Table[id_kind.ToIndex()];
}
IdKind kind_;
int32_t value_;
};
namespace Internal {
template <typename T>
concept IsIdKindType =
IdKind::Contains<std::remove_cvref_t<T>> ||
SameAsOneOf<T, IdAndKind::NoneType, IdAndKind::InvalidType>;
}
// Specialization for None.
static constexpr auto ToRaw(IdAndKind::NoneType /*none*/) -> int32_t {
return AnyIdBase::NoneIndex;
}
template <typename T>
requires std::is_same_v<T, IdAndKind::NoneType>
constexpr auto FromRaw(int32_t raw) -> IdAndKind::NoneType {
CARBON_CHECK(raw == AnyIdBase::NoneIndex);
return {};
}
} // namespace Carbon::SemIR
#endif // CARBON_TOOLCHAIN_SEM_IR_ID_KIND_H_
+4 -49
View File
@@ -187,36 +187,6 @@ concept InstLikeType = requires { sizeof(InstLikeTypeInfo<T>); };
// data where the instruction's kind is not known.
class Inst : public Printable<Inst> {
public:
// Associates an argument (arg0 or arg1) with its IdKind.
class ArgAndKind {
public:
explicit ArgAndKind(IdKind kind, int32_t value)
: kind_(kind), value_(value) {}
// Converts to `IdT`, validating the `kind` matches.
template <typename IdT>
auto As() const -> IdT {
CARBON_DCHECK(kind_ == IdKind::For<IdT>);
return IdT(value_);
}
// Converts to `IdT`, returning nullopt if the kind is incorrect.
template <typename IdT>
auto TryAs() const -> std::optional<IdT> {
if (kind_ != IdKind::For<IdT>) {
return std::nullopt;
}
return IdT(value_);
}
auto kind() const -> IdKind { return kind_; }
auto value() const -> int32_t { return value_; }
private:
IdKind kind_;
int32_t value_;
};
// Makes an instruction for a singleton. This exists to support simple
// construction of all singletons by File.
static auto MakeSingleton(InstKind kind) -> Inst {
@@ -330,11 +300,11 @@ class Inst : public Printable<Inst> {
auto arg1() const -> int32_t { return arg1_; }
// Returns arguments with their IdKind.
auto arg0_and_kind() const -> ArgAndKind {
return ArgAndKind(ArgKindTable[kind_].first, arg0_);
auto arg0_and_kind() const -> IdAndKind {
return IdAndKind(ArgKindTable[kind_].first, arg0_);
}
auto arg1_and_kind() const -> ArgAndKind {
return ArgAndKind(ArgKindTable[kind_].second, arg1_);
auto arg1_and_kind() const -> IdAndKind {
return IdAndKind(ArgKindTable[kind_].second, arg1_);
}
// Sets the type of this instruction.
@@ -346,21 +316,6 @@ class Inst : public Printable<Inst> {
arg1_ = arg1;
}
// Convert a field to its raw representation, used as `arg0_` / `arg1_`.
static constexpr auto ToRaw(AnyIdBase base) -> int32_t { return base.index; }
static constexpr auto ToRaw(IntId id) -> int32_t { return id.AsRaw(); }
// Convert a field from its raw representation.
template <typename T>
requires IdKind::Contains<T>
static constexpr auto FromRaw(int32_t raw) -> T {
return T(raw);
}
template <>
constexpr auto FromRaw<IntId>(int32_t raw) -> IntId {
return IntId::MakeRaw(raw);
}
auto Print(llvm::raw_ostream& out) const -> void;
friend auto operator==(Inst lhs, Inst rhs) -> bool {
+6 -16
View File
@@ -406,25 +406,15 @@ struct Worklist {
CARBON_FATAL("Unexpected instruction operand kind {0}", typeid(T).name());
}
using AddFnT = auto(Worklist& worklist, int32_t arg) -> void;
// Returns the arg handler for an `IdKind`.
template <typename... Types>
static auto GetAddFn(TypeEnum<Types...> id_kind) -> AddFnT* {
static constexpr std::array<AddFnT*, IdKind::NumValues> Table = {
[](Worklist& worklist, int32_t arg) {
worklist.Add(Inst::FromRaw<Types>(arg));
}...,
// Invalid and None handling (ordering-sensitive).
[](auto...) { CARBON_FATAL("Unexpected invalid IdKind"); },
[](auto...) {},
};
return Table[id_kind.ToIndex()];
auto Add(IdAndKind::InvalidType /*invalid*/) -> void {
CARBON_FATAL("Unexpected invalid IdKind");
}
auto Add(IdAndKind::NoneType /*none*/) -> void {}
// Add an instruction argument to the contents of the current instruction.
auto AddWithKind(Inst::ArgAndKind arg) -> void {
GetAddFn(arg.kind())(*this, arg.value());
auto AddWithKind(IdAndKind arg) -> void {
arg.Dispatch<void>([this](auto id) { Add(id); });
}
// Ensure all the instructions on the todo list have fingerprints. To avoid a