Files
carbon-lang/toolchain/check/action.cpp
T
Richard Smith 5a07a14fe9 Support for lowering templates (#7727)
Add basic support for lowering templates: we can now lower `SpliceInst`
in the case where the generic and specific are from the same file (and
we don't support importing templates from other files yet in general).
In order for this to work, lowering needs to be able to query the
expression category, and to handle instructions that appear to be
(template) constants in the generic but turn out to be non-constant in
the specific, so support for that is added.

Switch `type_of_inst` from being added as an action inst to being added
as a normal inst, since it's not an action and the old approach led to a
crash in lowering.
2026-09-08 18:39:57 +00:00

369 lines
15 KiB
C++

// 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/action.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/generic_region_stack.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/type.h"
#include "toolchain/sem_ir/constant.h"
#include "toolchain/sem_ir/copy_on_write_block.h"
#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/id_kind.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
auto PerformAction(Context& context, SemIR::SpecificId specific_id,
SemIR::LocId loc_id, SemIR::RefineInstAction action)
-> SemIR::InstId {
return AddInst<SemIR::SpecificInst>(
context, loc_id,
{.type_id = GetTypeOfInstInSpecific(context.sem_ir(), specific_id,
action.inst_id),
.inst_id = action.inst_id,
.specific_id = specific_id});
}
static auto OperandDependence(Context& context, SemIR::ConstantId const_id)
-> SemIR::ConstantDependence {
// A type operand makes the instruction dependent if it is a
// template-dependent constant.
if (!const_id.is_symbolic()) {
return SemIR::ConstantDependence::None;
}
return context.constant_values().GetSymbolicConstant(const_id).dependence;
}
auto OperandDependence(Context& context, SemIR::TypeId type_id)
-> SemIR::ConstantDependence {
// A type operand makes the instruction dependent if it is a
// template-dependent type.
return OperandDependence(context, context.types().GetConstantId(type_id));
}
auto OperandDependence(Context& context, SemIR::InstId inst_id)
-> SemIR::ConstantDependence {
// An instruction operand makes the instruction dependent if its type or
// constant value is dependent.
return std::max(
OperandDependence(context, context.insts().Get(inst_id).type_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
// constant value is dependent. TypeInstId has type `TypeType` which is
// concrete, so we only need to look at the constant value.
return OperandDependence(context, context.constant_values().Get(inst_id));
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT> &&
SameAsOneOf<IdT, SemIR::IdAndKind::NoneType, SemIR::AbsoluteInstId,
SemIR::CallParamIndex, SemIR::NameId,
SemIR::ElementIndex, SemIR::ClangDeclId,
SemIR::BoolValue>
static auto OperandDependence(Context& /*context*/, IdT /*id*/)
-> SemIR::ConstantDependence {
return SemIR::ConstantDependence::None;
}
template <typename BundleT>
static auto OperandDependence(Context& context,
SemIR::BundleId<BundleT> bundle_id)
-> SemIR::ConstantDependence {
return std::apply(
[&](auto... ids) {
return std::max({OperandDependence(context, ids)...});
},
context.bundles().GetAsTuple(bundle_id));
}
static auto OperandDependence(Context& context,
SemIR::InstBlockId inst_block_id)
-> SemIR::ConstantDependence {
auto result = SemIR::ConstantDependence::None;
for (auto arg_id : context.inst_blocks().Get(inst_block_id)) {
result = std::max(result, OperandDependence(context, arg_id));
}
return result;
}
static auto OperandDependence(Context& context,
SemIR::MetaInstBlockId inst_block_id)
-> SemIR::ConstantDependence {
return OperandDependence(context, SemIR::InstBlockId{inst_block_id});
}
static auto OperandDependence(Context& context, SemIR::SpecificId specific_id)
-> SemIR::ConstantDependence {
auto specific = context.specifics().Get(specific_id);
return OperandDependence(context, specific.args_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);
}
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,
SemIR::SpecificId specific_id) -> bool {
if (auto refine_action = action_inst.TryAs<SemIR::RefineInstAction>()) {
// `RefineInstAction` is performable once the instruction's type and
// constant value are not template-dependent.
return OperandDependence(
context, GetTypeOfInstInSpecific(context.sem_ir(), specific_id,
refine_action->inst_id)) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, SemIR::GetConstantValueInSpecific(
context.sem_ir(), specific_id,
refine_action->inst_id)) <
SemIR::ConstantDependence::Template;
}
// A form-parameterized action is performable if we can see at least the top
// level of its form's structure (i.e. it is not an action or a splice).
if (auto form_parameterized_action =
action_inst.TryAs<SemIR::AnyFormParamAction>()) {
auto form_const_id =
context.constant_values().Get(form_parameterized_action->form_id);
auto form_id = context.constant_values().GetInstIdIfValid(form_const_id);
if (!form_id.has_value()) {
// This is an error which will be diagnosed elsewhere, so we should just
// get out of its way.
return true;
}
auto form_inst = context.insts().Get(form_id);
switch (form_inst.kind()) {
case SemIR::InitForm::Kind:
case SemIR::RefForm::Kind:
case SemIR::ValueForm::Kind:
case SemIR::ErrorInst::Kind:
return true;
default:
return false;
}
}
return OperandDependence(context, action_inst.type_id()) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, action_inst.arg0_and_kind()) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, action_inst.arg1_and_kind()) <
SemIR::ConstantDependence::Template;
}
static auto AddDependentActionSpliceImpl(Context& context,
SemIR::LocIdAndInst action,
SemIR::TypeInstId result_type_inst_id)
-> SemIR::InstId {
auto inst_id = AddDependentActionInst(context, action);
if (!result_type_inst_id.has_value()) {
result_type_inst_id =
AddTypeInst(context, action.loc_id,
SemIR::TypeOfInst{.type_id = SemIR::TypeType::TypeId,
.inst_id = inst_id});
}
return AddInst(
context, action.loc_id,
SemIR::SpliceInst{.type_id = context.types().GetTypeIdForTypeInstId(
result_type_inst_id),
.inst_id = inst_id});
}
// Refine one operand of an action. Given an argument from a template, this
// produces an argument that has the template-dependent parts replaced with
// their concrete values, so that the action doesn't need to know which specific
// it is operating on.
//
// This is the default case, for ID kinds that can't be refined.
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto RefineTypedOperand(Context& /*context*/, SemIR::LocId /*loc_id*/,
IdT id) -> IdT {
return id;
}
static auto RefineTypedOperand(Context& context, SemIR::LocId loc_id,
SemIR::MetaInstId inst_id) -> SemIR::MetaInstId {
auto inst = context.insts().Get(inst_id);
if (inst.Is<SemIR::SpliceInst>()) {
// The argument will evaluate to the spliced instruction, which is already
// refined.
return inst_id;
}
// If the constant value of the instruction is template-dependent and
// unattached, replace it with a corresponding attached constant value.
auto const_id = context.constant_values().GetAttached(inst_id);
if (const_id.is_symbolic() &&
!context.constant_values().IsAttached(const_id)) {
return GetOrAddInstWithSpecificConstantValue(context, inst_id);
}
// If the type or constant value of the action argument is dependent, refine
// to an instruction with the type and value from the specific.
if (OperandDependence(context, inst.type_id()) ==
SemIR::ConstantDependence::Template ||
OperandDependence(context, const_id) ==
SemIR::ConstantDependence::Template) {
auto type_inst_id = context.types().GetTypeInstId(inst.type_id());
inst_id = AddDependentActionSpliceImpl(
context,
SemIR::LocIdAndInst(
loc_id,
SemIR::RefineInstAction{.type_id = GetSingletonType(
context, SemIR::InstType::TypeInstId),
.inst_id = inst_id}),
type_inst_id);
}
return inst_id;
}
template <typename DerivedInstIdT>
requires SemIR::Internal::IsIdKindType<DerivedInstIdT> &&
std::derived_from<DerivedInstIdT, SemIR::InstId>
static auto RefineTypedOperand(Context& context, SemIR::LocId /*loc_id*/,
DerivedInstIdT inst_id) -> DerivedInstIdT {
// Refine an instruction that refers to a value within the current generic to
// refer to the corresponding value within the specific. This is analogous to
// the work we do to rebuild generic constants in the eval block, but is done
// as refinement rather than rebuilding since action instructions *only* live
// in the eval block.
auto result = GetOrAddInstWithSpecificConstantValue(context, inst_id);
if constexpr (requires { DerivedInstIdT(result); }) {
return DerivedInstIdT(result);
} else {
return DerivedInstIdT::UnsafeMake(result);
}
}
template <typename DerivedInstBlockIdT>
requires SemIR::Internal::IsIdKindType<DerivedInstBlockIdT> &&
std::derived_from<DerivedInstBlockIdT, SemIR::InstBlockId>
static auto RefineTypedOperand(Context& context, SemIR::LocId loc_id,
DerivedInstBlockIdT inst_block_id)
-> DerivedInstBlockIdT {
auto block = context.inst_blocks().Get(inst_block_id);
llvm::SmallVector<SemIR::InstId> new_block;
new_block.reserve(block.size());
bool any_changed = false;
for (auto inst_id : block) {
new_block.push_back(RefineTypedOperand(
context, loc_id, typename DerivedInstBlockIdT::InstIdT(inst_id)));
any_changed |= new_block.back() != inst_id;
}
if (!any_changed) {
return inst_block_id;
}
return DerivedInstBlockIdT(context.inst_blocks().AddCanonical(new_block));
}
template <typename BundleT>
static auto RefineTypedOperand(Context& context, SemIR::LocId loc_id,
SemIR::BundleId<BundleT> bundle_id)
-> SemIR::BundleId<BundleT> {
auto bundle_tuple = context.bundles().GetAsTuple(bundle_id);
BundleT refined_bundle = std::apply(
[&](auto... bundle_fields) {
// This can't actually recurse, because bundles can't contain bundle
// IDs.
return BundleT{RefineTypedOperand(context, loc_id, bundle_fields)...};
},
bundle_tuple);
return context.bundles().AddCanonical(refined_bundle);
}
// Dynamically dispatched wrapper for RefineTypedOperand.
static auto RefineOperand(Context& context, SemIR::LocId loc_id,
SemIR::IdAndKind arg) -> int32_t {
return arg.Dispatch<int32_t>([&](auto id) {
return SemIR::ToRaw(RefineTypedOperand(context, loc_id, id));
});
}
// Refine the operands of an action, ensuring that they will refer to concrete
// instructions that don't have template-dependent types.
static auto RefineOperands(Context& context, SemIR::LocId loc_id,
SemIR::Inst action) -> SemIR::Inst {
auto arg0 = RefineOperand(context, loc_id, action.arg0_and_kind());
auto arg1 = RefineOperand(context, loc_id, action.arg1_and_kind());
action.SetArgs(arg0, arg1);
return action;
}
auto AddDependentActionSplice(Context& context, SemIR::LocIdAndInst action,
SemIR::TypeInstId result_type_inst_id)
-> SemIR::InstId {
action.inst = RefineOperands(context, action.loc_id, action.inst);
return AddDependentActionSpliceImpl(context, action, result_type_inst_id);
}
auto Internal::BeginPerformDelayedAction(Context& context) -> void {
// Push an `InstBlock` to hold any instructions created by the action.
// Note that we assume that actions don't need to create multiple blocks. If
// this changes, we should push a region too.
context.inst_block_stack().Push();
context.pattern_block_stack().Push();
}
auto Internal::EndPerformDelayedAction(Context& context,
SemIR::InstId result_id)
-> SemIR::InstId {
// If the only created instruction is the result, then we can use it directly.
auto contents = context.inst_block_stack().PeekCurrentBlockContents();
auto pattern_contents =
context.pattern_block_stack().PeekCurrentBlockContents();
if ((contents == llvm::ArrayRef(result_id) && pattern_contents.empty()) ||
(pattern_contents == llvm::ArrayRef(result_id) && contents.empty())) {
context.inst_block_stack().PopAndDiscard();
context.pattern_block_stack().PopAndDiscard();
return result_id;
}
// Otherwise, create a splice_block to represent the sequence of instructions
// created by the action.
auto block_id = SemIR::InstBlockId::None;
if (pattern_contents.empty()) {
block_id = context.inst_block_stack().Pop();
context.pattern_block_stack().PopAndDiscard();
} else {
// TODO: pattern insts can depend on non-pattern insts, so we'll probably
// eventually need to support actions that produce both.
CARBON_CHECK(contents.empty());
block_id = context.pattern_block_stack().Pop();
context.inst_block_stack().PopAndDiscard();
}
auto result = context.insts().GetWithLocId(result_id);
return AddInstInNoBlock(context, result.loc_id,
SemIR::SpliceBlock{.type_id = result.inst.type_id(),
.block_id = block_id,
.result_id = result_id});
}
} // namespace Carbon::Check