mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-02 17:52:58 +01:00
Add support for deferring initialization as a template action, and performing the deferred initialization during template instantiation. This is substantially more complex than other conversion actions, for two primary reasons: * The initializer in the generic may have storage arguments as inputs. We model an initializing expression as having a "slot" where initialization writes the location that should be initialized by that initializing expression, and that needs to be an output of the initialization action. * Initialization from a tuple or struct literal needs to recurse into that literal, and the literal will have been spelled in the generic, meaning we don't have an `InstId` that can be used to name the specific version of the initializer as input for nested conversions. These issues are addressed by introducing two new features to the action machinery: In addition to `InstAction`, we now have `MultiInstAction`, which is an action that produces a tuple of instruction values instead of a single instruction value. Initialization actions produce one instruction for the final result, which is spliced at the point of initialization, plus one instruction for each storage argument, which are spliced into the storage argument slots in the original generic. During initialization, if we find one of those splices in the storage argument of an initializing expression, we return the new storage argument back to the initialization action to be included in the specific, instead of overwriting the storage argument in the generic. Actions whose `PerforrmAction` takes a `SpecificId` as input no longer perform automatic refinement of their operands to specific instructions. Instead, the action is given control over when and where it performs that refinement. In `InitializeAction`, we use this freedom to form a `SpecificInst` for the initializer in the primary output block, and form a `SpecificInst` for the target in the target block. When detecting whether we are initializing from a tuple or struct literal, we step over the `SpecificInst` and track its `SpecificId`, and if necessary create a new `SpecificInst` wrapping the sub-initializer when we recurse into the nested element conversion. Assisted-by: Claude and Gemini via Antigravity
303 lines
12 KiB
C++
303 lines
12 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/expr_info.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);
|
|
if (SemIR::GetExprCategory(context.sem_ir(), inst_id) ==
|
|
SemIR::ExprCategory::Pattern) {
|
|
auto type_id = loc_id_and_inst.inst.type_id();
|
|
CARBON_CHECK(type_id == SemIR::ErrorInst::TypeId ||
|
|
context.types().Is<SemIR::PatternType>(type_id),
|
|
"Unexpected kind for type {0}",
|
|
context.types().GetAsInst(type_id));
|
|
context.pattern_block_stack().AddInstId(inst_id);
|
|
} else {
|
|
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 AddTemplateConstantInstToEvalBlock(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(),
|
|
"AddTemplateConstantInstToEvalBlock: {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 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));
|
|
}
|
|
|
|
auto WrapInstForSpecific(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId inst_id, SemIR::SpecificId specific_id)
|
|
-> WrapInstForSpecificResult {
|
|
auto type_id =
|
|
SemIR::GetTypeOfInstInSpecific(context.sem_ir(), specific_id, inst_id);
|
|
CARBON_CHECK(type_id.has_value(), "Missing type for {0}",
|
|
context.insts().Get(inst_id));
|
|
|
|
// If `inst_id` has a constant value that depends on its specific, store the
|
|
// specific too.
|
|
if (specific_id.has_value() &&
|
|
context.constant_values().Get(inst_id).is_symbolic()) {
|
|
inst_id = AddInst<SemIR::SpecificConstant>(
|
|
context, loc_id,
|
|
{.type_id = type_id, .inst_id = inst_id, .specific_id = specific_id});
|
|
}
|
|
return {.inst_id = inst_id, .type_id = type_id};
|
|
}
|
|
|
|
auto BuildNameRef(Context& context, SemIR::LocId loc_id, SemIR::NameId name_id,
|
|
SemIR::InstId inst_id, SemIR::SpecificId specific_id)
|
|
-> SemIR::InstId {
|
|
auto [new_inst_id, type_id] =
|
|
WrapInstForSpecific(context, loc_id, inst_id, specific_id);
|
|
return AddInst<SemIR::NameRef>(
|
|
context, loc_id,
|
|
{.type_id = type_id, .name_id = name_id, .value_id = new_inst_id});
|
|
}
|
|
|
|
} // namespace Carbon::Check
|