Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build a list of dependent constants to recompute in each instance of a generic. #4110

Merged
merged 12 commits into from
Jul 8, 2024
7 changes: 6 additions & 1 deletion toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,12 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
return context.constant_values().Get(typed_inst.value_id);
}
case CARBON_KIND(SemIR::NameRef typed_inst): {
return context.constant_values().Get(typed_inst.value_id);
// Map from an instance-specific constant value to the canonical value.
// TODO: Remove this once we properly model instructions with
// instance-dependent constant values.
return GetConstantInInstance(
context, SemIR::GenericInstanceId::Invalid,
context.constant_values().Get(typed_inst.value_id));
}
case CARBON_KIND(SemIR::Converted typed_inst): {
return context.constant_values().Get(typed_inst.result_id);
Expand Down
3 changes: 2 additions & 1 deletion toolchain/check/function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ auto CheckFunctionTypeMatches(Context& context,
prev_return_type_id =
SubstType(context, prev_return_type_id, substitutions);
}
if (new_return_type_id != prev_return_type_id) {
if (!context.types().EqualAcrossDeclarations(new_return_type_id,
prev_return_type_id)) {
CARBON_DIAGNOSTIC(
FunctionRedeclReturnTypeDiffers, Error,
"Function redeclaration differs because return type is `{0}`.",
Expand Down
163 changes: 160 additions & 3 deletions toolchain/check/generic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#include "toolchain/check/generic.h"

#include "common/map.h"
#include "toolchain/check/generic_region_stack.h"
#include "toolchain/check/subst.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {
Expand All @@ -23,6 +26,124 @@ auto StartGenericDefinition(Context& context) -> void {
context.generic_region_stack().Push();
}

// Adds an instruction `generic_inst_id` to the eval block for a generic region,
// which is the current instruction block. The instruction `generic_inst_id` is
// expected to compute the value of the constant described by `const_inst_id` in
// each instance of the generic. Forms and returns a corresponding symbolic
// constant ID that refers to the substituted value of that instruction in each
// instance of the generic.
static auto AddGenericConstantToEvalBlock(
Context& context, SemIR::GenericId generic_id,
SemIR::GenericInstIndex::Region region, SemIR::InstId const_inst_id,
SemIR::InstId generic_inst_id) -> SemIR::ConstantId {
auto index = SemIR::GenericInstIndex(
region, context.inst_block_stack().PeekCurrentBlockContents().size());
context.inst_block_stack().AddInstId(generic_inst_id);
return context.constant_values().AddSymbolicConstant(
{.inst_id = const_inst_id, .generic_id = generic_id, .index = index});
}

// Adds instructions to compute the substituted version of `type_id` in each
// instance of a generic into the eval block for the generic, which is the
// current instruction block. Returns a symbolic type ID that refers to the
// substituted type in each instance of the generic.
static auto AddGenericTypeToEvalBlock(
Context& context, SemIR::GenericId generic_id,
SemIR::GenericInstIndex::Region region,
Map<SemIR::InstId, SemIR::InstId>& constants_in_generic,
SemIR::TypeId type_id) -> SemIR::TypeId {
// Check for instructions we've already substituted and return the known
// value.
auto subst_fn = [&](SemIR::InstId& inst_id) -> bool {
if (context.constant_values().Get(inst_id).is_template()) {
// This instruction is a template constant, so can't contain any
// bindings that need to be substituted.
return true;
}

// If this instruction is in the map, return the known result.
if (auto result = constants_in_generic.Lookup(inst_id)) {
inst_id = result.value();
CARBON_CHECK(inst_id.is_valid());
return true;
}
return false;
};

// Build a new instruction in the eval block corresponding to the given
// constant.
auto rebuild_fn = [&](SemIR::InstId orig_inst_id,
SemIR::Inst new_inst) -> SemIR::InstId {
// TODO: Add a function on `Context` to add the instruction without
// inserting it into the dependent instructions list or computing a constant
// value for it.
auto inst_id = context.sem_ir().insts().AddInNoBlock(
SemIR::LocIdAndInst::NoLoc(new_inst));
auto result = constants_in_generic.Insert(orig_inst_id, inst_id);
CARBON_CHECK(result.is_inserted())
<< "Substituted into an instruction that was already in the map.";
auto const_id = AddGenericConstantToEvalBlock(context, generic_id, region,
orig_inst_id, inst_id);
context.constant_values().Set(inst_id, const_id);
return inst_id;
};

// Substitute into the type's constant instruction and rebuild it in the eval
// block.
auto type_inst_id = SubstInst(context, context.types().GetInstId(type_id),
subst_fn, rebuild_fn);
return context.GetTypeIdForTypeInst(type_inst_id);
}

// Builds and returns a block of instructions whose constant values need to be
// evaluated in order to resolve a generic instance.
static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
SemIR::GenericInstIndex::Region region)
-> SemIR::InstBlockId {
context.inst_block_stack().Push();

Map<SemIR::InstId, SemIR::InstId> constants_in_generic;
// TODO: For the definition region, populate constants from the declaration.

// TODO: See if we can ensure that the generic region stack is unchanged
// throughout this work. If so, we can use a range-based loop here instead.
for (size_t i = 0;
i != context.generic_region_stack().PeekDependentInsts().size(); ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (size_t i = 0;
i != context.generic_region_stack().PeekDependentInsts().size(); ++i) {
for (auto i : llvm::seq(context.generic_region_stack().PeekDependentInsts().size())) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yeah, while the collection could get reallocated (eg, by import ref handling), the number of instructions at this level on the stack really shouldn't change. Made this change and also added a CHECK.

auto [inst_id, dep_kind] =
context.generic_region_stack().PeekDependentInsts()[i];

// If the type is symbolic, replace it with a type specific to this generic.
if ((dep_kind & GenericRegionStack::DependencyKind::SymbolicType) !=
GenericRegionStack::DependencyKind::None) {
auto inst = context.insts().Get(inst_id);
inst.SetType(AddGenericTypeToEvalBlock(
context, generic_id, region, constants_in_generic, inst.type_id()));
context.sem_ir().insts().Set(inst_id, inst);
}

// If the instruction has a symbolic constant value, then make a note that
// we'll need to evaluate this instruction in the generic instance. Update
// the constant value of the instruction to refer to the result of that
// eventual evaluation.
if ((dep_kind & GenericRegionStack::DependencyKind::SymbolicConstant) !=
GenericRegionStack::DependencyKind::None) {
auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);

// Create a new symbolic constant representing this instruction in this
// generic, if it doesn't already exist.
auto result = constants_in_generic.Insert(const_inst_id, inst_id);
auto const_id =
result.is_inserted()
? AddGenericConstantToEvalBlock(context, generic_id, region,
const_inst_id, inst_id)
: context.constant_values().Get(result.value());
context.constant_values().Set(inst_id, const_id);
}
}

return context.inst_block_stack().Pop();
}

auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
-> SemIR::GenericId {
auto all_bindings =
Expand All @@ -37,10 +158,15 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
}

auto bindings_id = context.inst_blocks().Add(all_bindings);
// TODO: Track the list of dependent instructions in this region.
context.generic_region_stack().Pop();
return context.generics().Add(
auto generic_id = context.generics().Add(
SemIR::Generic{.decl_id = decl_id, .bindings_id = bindings_id});

auto decl_block_id = MakeGenericEvalBlock(
context, generic_id, SemIR::GenericInstIndex::Region::Declaration);
context.generic_region_stack().Pop();

context.generics().Get(generic_id).decl_block_id = decl_block_id;
return generic_id;
}

auto FinishGenericRedecl(Context& context, SemIR::InstId /*decl_id*/,
Expand Down Expand Up @@ -98,4 +224,35 @@ auto MakeGenericSelfInstance(Context& context, SemIR::GenericId generic_id)
return MakeGenericInstance(context, generic_id, args_id);
}

auto GetConstantInInstance(Context& context,
SemIR::GenericInstanceId /*instance_id*/,
SemIR::ConstantId const_id) -> SemIR::ConstantId {
if (!const_id.is_symbolic()) {
// Type does not depend on a generic parameter.
return const_id;
}

const auto& symbolic =
context.constant_values().GetSymbolicConstant(const_id);
if (!symbolic.generic_id.is_valid()) {
// Constant is an abstract symbolic constant, not an instance-specific one.
return const_id;
}

// TODO: Look up the value in the generic instance. For now, return the
// canonical constant value.
return context.constant_values().Get(symbolic.inst_id);
}

auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
SemIR::TypeId type_id) -> SemIR::TypeId {
auto const_id = context.types().GetConstantId(type_id);
auto inst_const_id = GetConstantInInstance(context, instance_id, const_id);
if (inst_const_id == const_id) {
// Common case: not an instance constant.
return type_id;
}
return context.GetTypeIdForTypeConstant(inst_const_id);
}

} // namespace Carbon::Check
17 changes: 17 additions & 0 deletions toolchain/check/generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
auto MakeGenericSelfInstance(Context& context, SemIR::GenericId generic_id)
-> SemIR::GenericInstanceId;

// Gets the substituted value of a constant within a specified instance of a
// generic. Note that this does not perform substitution, and will return
// `Invalid` if the substituted constant value is not yet known.
//
// TODO: Move this to sem_ir so that lowering can use it.
auto GetConstantInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::ConstantId const_id) -> SemIR::ConstantId;

// Gets the substituted value of a type within a specified instance of a
// generic. Note that this does not perform substitution, and will return
// `Invalid` if the substituted type is not yet known.
//
// TODO: Move this to sem_ir so that lowering can use it.
auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
SemIR::TypeId type_id) -> SemIR::TypeId;

} // namespace Carbon::Check

#endif // CARBON_TOOLCHAIN_CHECK_GENERIC_H_
10 changes: 10 additions & 0 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,16 @@ static auto BuildFunctionDecl(Context& context,
}
function_decl.type_id = context.GetFunctionType(function_decl.function_id);

// TODO: Temporarily replace the return type with the canonical return type.
// This is a placeholder to avoid breaking tests before generic type
// substitution is ready.
if (return_storage_id.is_valid()) {
auto return_storage = context.insts().Get(return_storage_id);
return_storage.SetType(GetTypeInInstance(
context, SemIR::GenericInstanceId::Invalid, return_storage.type_id()));
context.sem_ir().insts().Set(return_storage_id, return_storage);
}

// Write the function ID into the FunctionDecl.
context.ReplaceInstBeforeConstantUse(decl_id, function_decl);

Expand Down
9 changes: 7 additions & 2 deletions toolchain/check/handle_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "toolchain/check/context.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/member_access.h"
#include "toolchain/check/name_component.h"
Expand Down Expand Up @@ -80,10 +81,14 @@ static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id)
static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
SemIR::NameId name_id) -> bool {
auto value_id = context.LookupUnqualifiedName(node_id, name_id);
// TODO: Lookup should produce this.
auto instance_id = SemIR::GenericInstanceId::Invalid;
auto value = context.insts().Get(value_id);
auto type_id = GetTypeInInstance(context, instance_id, value.type_id());
CARBON_CHECK(type_id.is_valid()) << "Missing type for " << value;

context.AddInstAndPush<SemIR::NameRef>(
node_id,
{.type_id = value.type_id(), .name_id = name_id, .value_id = value_id});
node_id, {.type_id = type_id, .name_id = name_id, .value_id = value_id});
return true;
}

Expand Down
5 changes: 3 additions & 2 deletions toolchain/check/merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ static auto CheckRedeclParam(Context& context,
auto new_param_ref = context.insts().Get(new_param_ref_id);
auto prev_param_ref = context.insts().Get(prev_param_ref_id);
if (new_param_ref.kind() != prev_param_ref.kind() ||
new_param_ref.type_id() !=
SubstType(context, prev_param_ref.type_id(), substitutions)) {
!context.types().EqualAcrossDeclarations(
new_param_ref.type_id(),
SubstType(context, prev_param_ref.type_id(), substitutions))) {
diagnose();
return false;
}
Expand Down
Loading
Loading