Files
carbon-lang/toolchain/check/inst.cpp
T
Dana Jansens a0416a1250 Don't finish the non-canonical instruction created in EvalOrAddInst (#6972)
EvalOrAddInst has to create a non-canonical instruction for evaluating a
few typed insts, such as LookupImplWitness which uses an InstId to
provide a location for diagnostics.

But the output of the function is a ConstantId. We do not have access to
the non-canonical InstId after the function returns. But if the constant
value was symbolic, it was being attached to the inst, and the inst
would be added to the eval block of the enclosing generic. This
needlessly added semir for a symbolic value.

The ConstantId returned by EvalOrAddInst can be used immediately, such
as to evaluate an ImplWitnessAccess. In that case, the final evaluated
result is all we need to keep in semir.

If the ConstantId needs to be replaced by specifics, it is only as part
of some other instruction, since ConstantIds themselves are not modified
by specifics, instructions are. In that case, the canonical instruction
in the constant value would have been added to some other (now symbolic)
instruction, which would be replaced by a specific.

This has no functional change, but it reduces runtime overhead and semir
output for LookupImplWitness and ImplWitnessAccess.
2026-03-27 19:29:34 +00:00

272 lines
11 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/inst.h"
#include "common/vlog.h"
#include "toolchain/check/context.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/import.h"
#include "toolchain/sem_ir/constant.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst_kind.h"
namespace Carbon::Check {
// Finish producing an instruction. Set its constant value, and register it in
// any applicable instruction lists.
static auto FinishInst(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
DependentInstKind dep_kind = DependentInstKind::None;
// If the instruction has a symbolic constant type, track that we need to
// substitute into it.
if (context.constant_values().DependsOnGenericParameter(
context.types().GetConstantId(inst.type_id()))) {
dep_kind.Add(DependentInstKind::SymbolicType);
}
// If the instruction has a constant value, compute it.
auto const_id = TryEvalInstUnsafe(context, inst_id, inst);
context.constant_values().Set(inst_id, const_id);
if (const_id.is_constant()) {
CARBON_VLOG_TO(context.vlog_stream(), "Constant: {0} -> {1}\n", inst,
context.constant_values().GetInstId(const_id));
// If the constant value is symbolic, track that we need to substitute into
// it.
if (context.constant_values().DependsOnGenericParameter(const_id)) {
dep_kind.Add(DependentInstKind::SymbolicConstant);
}
}
// Template-dependent instructions are handled separately by
// `AddDependentActionInst`.
CARBON_CHECK(
inst.kind().constant_kind() != SemIR::InstConstantKind::InstAction,
"Use AddDependentActionInst to add an action instruction");
// Keep track of dependent instructions.
if (!dep_kind.empty()) {
AttachDependentInstToCurrentGeneric(context,
{.inst_id = inst_id, .kind = dep_kind});
}
}
auto AddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
context.inst_block_stack().AddInstId(inst_id);
return inst_id;
}
auto AddInstInNoBlock(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddInst: {0}\n", loc_id_and_inst.inst);
FinishInst(context, inst_id, loc_id_and_inst.inst);
return inst_id;
}
auto AddDependentActionInst(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddDependentActionInst: {0}\n",
loc_id_and_inst.inst);
// Set the constant value of this instruction to point back to itself.
auto const_id = context.constant_values().AddSymbolicConstant(
{.inst_id = inst_id,
.generic_id = SemIR::GenericId::None,
.index = SemIR::GenericInstIndex::None,
.dependence = SemIR::ConstantDependence::Template});
context.constant_values().Set(inst_id, const_id);
// Register the instruction to be added to the eval block.
AttachDependentInstToCurrentGeneric(
context, {.inst_id = inst_id, .kind = DependentInstKind::Template});
return inst_id;
}
auto AddPatternInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto type_id = loc_id_and_inst.inst.type_id();
CARBON_CHECK(type_id == SemIR::ErrorInst::TypeId ||
context.types().Is<SemIR::PatternType>(type_id));
auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
context.pattern_block_stack().AddInstId(inst_id);
return inst_id;
}
auto GetOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
CARBON_CHECK(!loc_id_and_inst.inst.kind().has_cleanup());
auto handle_constant_id = [&](SemIR::ConstantId const_id) -> SemIR::InstId {
CARBON_CHECK(const_id.has_value());
// If we didn't produce a constant value for the instruction, we have to add
// the instruction.
if (!const_id.is_constant()) {
return SemIR::InstId::None;
}
if (const_id.is_symbolic()) {
// TODO: Only add this instruction to the eval block, and don't
// re-evaluate it.
return AddInst(context, loc_id_and_inst);
}
CARBON_VLOG_TO(context.vlog_stream(), "GetOrAddInst: constant: {0}\n",
loc_id_and_inst.inst);
return context.constant_values().GetInstId(const_id);
};
// If the instruction is from desugaring, produce its constant value instead
// if possible.
if (loc_id_and_inst.loc_id.is_desugared()) {
switch (loc_id_and_inst.inst.kind().constant_needs_inst_id()) {
case SemIR::InstConstantNeedsInstIdKind::No: {
// Evaluation doesn't need an InstId. Just do it.
auto const_id = TryEvalInstUnsafe(context, SemIR::InstId::None,
loc_id_and_inst.inst);
if (auto result_inst_id = handle_constant_id(const_id);
result_inst_id.has_value()) {
return result_inst_id;
}
break;
}
case SemIR::InstConstantNeedsInstIdKind::DuringEvaluation: {
// Evaluation temporarily needs an InstId. Add one for now.
auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
auto const_id = context.constant_values().Get(inst_id);
if (auto result_inst_id = handle_constant_id(const_id);
result_inst_id.has_value()) {
// TODO: We didn't end up needing the `inst_id` instruction. Consider
// removing it from `insts` if it's still the most recently added
// instruction.
CARBON_CHECK(result_inst_id != inst_id);
return result_inst_id;
}
context.inst_block_stack().AddInstId(inst_id);
return inst_id;
}
case SemIR::InstConstantNeedsInstIdKind::Permanent: {
// Evaluation needs a permanent InstId. Add the instruction.
break;
}
}
}
// TODO: For an implicit instruction, this reattempts evaluation.
return AddInst(context, loc_id_and_inst);
}
auto EvalOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::ConstantId {
CARBON_CHECK(!loc_id_and_inst.inst.kind().has_cleanup());
switch (loc_id_and_inst.inst.kind().constant_needs_inst_id()) {
case SemIR::InstConstantNeedsInstIdKind::No: {
// Evaluation doesn't need an InstId. Just do it.
return TryEvalInstUnsafe(context, SemIR::InstId::None,
loc_id_and_inst.inst);
}
case SemIR::InstConstantNeedsInstIdKind::DuringEvaluation: {
// Evaluation temporarily needs an InstId. Add one for now. We add the
// instruction outside of a block, and never call `FinishInst` for this
// non-canonical instruction. This means it never gets attached to the
// constant value, and is not added to any enclosing generic context's
// eval block.
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddInst: {0}\n",
loc_id_and_inst.inst);
// TODO: Consider removing `inst_id` from `insts` if it's still the most
// recently added instruction.
return TryEvalInstUnsafe(context, inst_id, loc_id_and_inst.inst);
}
case SemIR::InstConstantNeedsInstIdKind::Permanent: {
// Evaluation needs a permanent InstId. Add the instruction.
auto inst_id = AddInst(context, loc_id_and_inst);
return context.constant_values().Get(inst_id);
}
}
}
auto AddPlaceholderInstInNoBlock(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddPlaceholderInst: {0}\n",
loc_id_and_inst.inst);
context.constant_values().Set(inst_id, SemIR::ConstantId::None);
return inst_id;
}
auto AddPlaceholderImportedInstInNoBlock(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddPlaceholderInstInNoBlock(context, loc_id_and_inst);
context.imports().push_back(inst_id);
return inst_id;
}
auto AddPlaceholderInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddPlaceholderInstInNoBlock(context, loc_id_and_inst);
context.inst_block_stack().AddInstId(inst_id);
return inst_id;
}
auto ReplaceLocIdAndInstBeforeConstantUse(Context& context,
SemIR::InstId inst_id,
SemIR::LocIdAndInst loc_id_and_inst)
-> void {
context.sem_ir().insts().SetLocIdAndInst(inst_id, loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
loc_id_and_inst.inst);
FinishInst(context, inst_id, loc_id_and_inst.inst);
}
auto ReplaceInstBeforeConstantUse(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
context.sem_ir().insts().Set(inst_id, inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
inst);
FinishInst(context, inst_id, inst);
}
auto ReplaceInstPreservingConstantValue(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
// Check that the type didn't change: a change of type will change the
// constant value. Replace the type with the attached type.
auto old_type_id = context.insts().GetAttachedType(inst_id);
CARBON_CHECK(context.types().GetUnattachedType(old_type_id) == inst.type_id(),
"Given wrong type for replacement instruction");
inst.SetType(old_type_id);
// Update the instruction.
context.sem_ir().insts().Set(inst_id, inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
inst);
// Check the constant value didn't change.
auto old_const_id = context.constant_values().Get(inst_id);
auto new_const_id = TryEvalInstUnsafe(context, inst_id, inst);
CARBON_CHECK(old_const_id == new_const_id);
}
auto SetNamespaceNodeId(Context& context, SemIR::InstId inst_id,
Parse::NodeId node_id) -> void {
context.sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id));
}
} // namespace Carbon::Check