mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 17:00:25 +01:00
Two somewhat related fixes. The first is call-specific for now (because it's the first action to take a `MetaInstId`), and the second is general across all actions, but it seems like calls are the easiest place to hit it. 1) Add support for refining inst blocks as action operands. Refine all the insts in the block, using the appropriate InstId-derived type. 2) When an action operand is a `MetaInstId` referring to an unattached constant, form a corresponding attached constant. This comes up when forming (for example) an implicit `AddWith(%T)` call, where the `%T` operand is an unattached constant. This causes us to form correct specifics in more cases, where previously we formed specifics that contained values that were still template-dependent. This unfortunately causes some existing template tests to produce more errors, but those errors reflect cases where we were previously silently doing the wrong thing.
370 lines
14 KiB
C++
370 lines
14 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/id_kind.h"
|
|
#include "toolchain/sem_ir/inst.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
auto PerformAction(Context& context, SemIR::LocId loc_id,
|
|
SemIR::RefineTypeAction action) -> SemIR::InstId {
|
|
return AddInst<SemIR::AsCompatible>(
|
|
context, loc_id,
|
|
{.type_id =
|
|
context.types().GetTypeIdForTypeInstId(action.inst_type_inst_id),
|
|
.source_id = action.inst_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) -> bool {
|
|
if (auto action = action_inst.TryAs<SemIR::CallCppTemplateAction>()) {
|
|
auto args = context.inst_blocks().Get(action->args_id);
|
|
for (auto arg : args) {
|
|
auto const_id = context.constant_values().Get(arg);
|
|
if (const_id.is_symbolic()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (auto refine_action = action_inst.TryAs<SemIR::RefineTypeAction>()) {
|
|
// `RefineTypeAction` can be performed whenever the type is not template-
|
|
// dependent, even if we don't know the instruction yet.
|
|
return OperandDependence(context, refine_action->inst_type_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 = AddDependentActionTypeInst(
|
|
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.
|
|
if (auto const_id = context.constant_values().GetAttached(inst_id);
|
|
const_id.is_symbolic() &&
|
|
!context.constant_values().IsAttached(const_id)) {
|
|
return GetOrAddInstWithSpecificConstantValue(context, inst_id);
|
|
}
|
|
|
|
// If the type of the action argument is dependent, refine to an instruction
|
|
// with a concrete type.
|
|
if (OperandDependence(context, inst.type_id()) ==
|
|
SemIR::ConstantDependence::Template) {
|
|
auto type_inst_id = context.types().GetTypeInstId(inst.type_id());
|
|
inst_id = AddDependentActionSpliceImpl(
|
|
context,
|
|
SemIR::LocIdAndInst(
|
|
loc_id,
|
|
SemIR::RefineTypeAction{.type_id = GetSingletonType(
|
|
context, SemIR::InstType::TypeInstId),
|
|
.inst_id = inst_id,
|
|
.inst_type_inst_id = type_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
|