Skip to content

Commit

Permalink
Build a list of dependent constants to recompute in each instance of …
Browse files Browse the repository at this point in the history
…a generic. (#4110)

For each generic, build a list of instructions describing the
computations we need to do when resolving an instance of the generic:
this is a list of the instance-specific constants and types that the
generic uses. Another way of viewing this list is as a block of Carbon
SemIR code that is evaluated in order to form an instance of the generic
-- this is referenced in the code as the "eval block" for the generic.

For each instruction in the generic whose type or value is a symbolic
constant, replace that type or constant value with a symbolic reference
that says "to find the actual type or value, look at index N in the list
of values for the generic instance".

For an instruction with a symbolic constant value, we can just add that
instruction to our list. For an instruction with a symbolic constant
type, however, we may not have a corresponding instruction computing the
type within the generic and may need to build a new instruction, but
will reuse one where possible. In the case where we build a new
instruction, we use the existing substitution code to build the type
within the eval block.

For now, this transformation is only done in the declaration region of
the generic, not in the definition region. Also, we map back from the
symbolic references to the underlying constant value in a few places
where we will eventually need to do a lookup into a generic instance, in
order to avoid regressing the tests.
  • Loading branch information
zygoloid authored Jul 8, 2024
1 parent 809920d commit 7322a1e
Show file tree
Hide file tree
Showing 58 changed files with 848 additions and 432 deletions.
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().AreEqualAcrossDeclarations(new_return_type_id,
prev_return_type_id)) {
CARBON_DIAGNOSTIC(
FunctionRedeclReturnTypeDiffers, Error,
"Function redeclaration differs because return type is `{0}`.",
Expand Down
200 changes: 197 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,161 @@ 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});
}

namespace {
// Substitution callbacks to rebuild a generic type in the eval block for a
// generic region.
class RebuildGenericTypeInEvalBlockCallbacks : public SubstInstCallbacks {
public:
RebuildGenericTypeInEvalBlockCallbacks(
Context& context, SemIR::GenericId generic_id,
SemIR::GenericInstIndex::Region region,
Map<SemIR::InstId, SemIR::InstId>& constants_in_generic)
: context_(context),
generic_id_(generic_id),
region_(region),
constants_in_generic_(constants_in_generic) {}

// Check for instructions for which we already have a mapping into the eval
// block, and substitute them for the instructions in the eval block. Note
// that this will at least include mappings for the `BindSymbolicName`
// instructions that introduce our parameters.
auto Subst(SemIR::InstId& inst_id) const -> bool override {
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(SemIR::InstId orig_inst_id, SemIR::Inst new_inst) const
-> SemIR::InstId override {
// 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;
}

private:
Context& context_;
SemIR::GenericId generic_id_;
SemIR::GenericInstIndex::Region region_;
Map<SemIR::InstId, SemIR::InstId>& constants_in_generic_;
};
} // namespace

// 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 {
// 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),
RebuildGenericTypeInEvalBlockCallbacks(
context, generic_id, region, constants_in_generic));
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: Add `BindSymbolicName` instructions for enclosing generics to the
// map.

// The work done in this loop might invalidate iterators into the generic
// region stack, but shouldn't add new dependent instructions to the current
// region.
auto num_dependent_insts =
context.generic_region_stack().PeekDependentInsts().size();
for (auto i : llvm::seq(num_dependent_insts)) {
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);
}
}

CARBON_CHECK(num_dependent_insts ==
context.generic_region_stack().PeekDependentInsts().size())
<< "Building eval block added new dependent insts, for example "
<< context.insts().Get(context.generic_region_stack()
.PeekDependentInsts()[num_dependent_insts]
.inst_id);

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

auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
-> SemIR::GenericId {
auto all_bindings =
Expand All @@ -37,10 +195,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 +261,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
2 changes: 1 addition & 1 deletion toolchain/check/member_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ static auto LookupInterfaceWitness(Context& context,
// considering impls that are for the same interface we're querying. We can
// also skip impls that mention any types that aren't part of our impl query.
for (const auto& impl : context.impls().array_ref()) {
if (!context.constant_values().EqualAcrossDeclarations(
if (!context.constant_values().AreEqualAcrossDeclarations(
context.types().GetConstantId(impl.self_id), type_const_id)) {
continue;
}
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().AreEqualAcrossDeclarations(
new_param_ref.type_id(),
SubstType(context, prev_param_ref.type_id(), substitutions))) {
diagnose();
return false;
}
Expand Down
Loading

0 comments on commit 7322a1e

Please sign in to comment.