Skip to content

Commit

Permalink
Track a list of dependent instructions created within a generic (#4092)
Browse files Browse the repository at this point in the history
When checking a declaration or definition of a generic, track a list of
created instructions that depend on the generic's parameters in some
way, along with information on how they depend on the parameters. This
will eventually be used to determine what information we need to compute
when creating instances of the generic, but for now we're just building
the list.

Information is tracked separately for the declaration region and the
definition region of the generic, because in general these may be first
provided in separate declarations, and they should be substituted into
at different times.
  • Loading branch information
zygoloid authored Jul 1, 2024
1 parent 1776635 commit fa11050
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 45 deletions.
16 changes: 16 additions & 0 deletions toolchain/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ cc_library(
"//common:vlog",
"//toolchain/base:index_base",
"//toolchain/base:kind_switch",
"//toolchain/check:generic_region_stack",
"//toolchain/check:scope_stack",
"//toolchain/diagnostics:diagnostic_emitter",
"//toolchain/lex:token_kind",
Expand Down Expand Up @@ -144,6 +145,21 @@ cc_library(
],
)

cc_library(
name = "generic_region_stack",
srcs = ["generic_region_stack.cpp"],
hdrs = ["generic_region_stack.h"],
deps = [
"//common:check",
"//common:ostream",
"//common:vlog",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:ids",
"//toolchain/sem_ir:inst",
"@llvm-project//llvm:Support",
],
)

cc_library(
name = "impl",
srcs = ["impl.cpp"],
Expand Down
70 changes: 41 additions & 29 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic_region_stack.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst_block_stack.h"
#include "toolchain/check/merge.h"
Expand Down Expand Up @@ -53,6 +54,10 @@ Context::Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter,
type_ids_for_type_constants_.insert(
{SemIR::ConstantId::ForTemplateConstant(SemIR::InstId::BuiltinTypeType),
SemIR::TypeId::TypeType});

// TODO: Remove this and add a `VerifyOnFinish` once we properly push and pop
// in the right places.
generic_region_stack().Push();
}

auto Context::TODO(SemIRLoc loc, std::string label) -> bool {
Expand All @@ -72,18 +77,45 @@ auto Context::VerifyOnFinish() -> void {
param_and_arg_refs_stack_.VerifyOnFinish();
}

auto Context::AddInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG() << "AddInst: " << loc_id_and_inst.inst << "\n";
// Finish producing an instruction. Set its constant value, and register it in
// any applicable instruction lists.
auto Context::FinishInst(SemIR::InstId inst_id, SemIR::Inst inst) -> void {
GenericRegionStack::DependencyKind dep_kind =
GenericRegionStack::DependencyKind::None;

auto const_id = TryEvalInst(*this, inst_id, loc_id_and_inst.inst);
// If the instruction has a symbolic constant type, track that we need to
// substitute into it.
if (types().GetConstantId(inst.type_id()).is_symbolic()) {
dep_kind |= GenericRegionStack::DependencyKind::SymbolicType;
}

// If the instruction has a constant value, compute it.
auto const_id = TryEvalInst(*this, inst_id, inst);
constant_values().Set(inst_id, const_id);
if (const_id.is_constant()) {
CARBON_VLOG() << "Constant: " << loc_id_and_inst.inst << " -> "
CARBON_VLOG() << "Constant: " << inst << " -> "
<< constant_values().GetInstId(const_id) << "\n";
constant_values().Set(inst_id, const_id);

// If the constant value is symbolic, track that we need to substitute into
// it.
if (const_id.is_symbolic()) {
dep_kind |= GenericRegionStack::DependencyKind::SymbolicConstant;
}
}

// Keep track of dependent instructions.
if (dep_kind != GenericRegionStack::DependencyKind::None) {
// TODO: Also check for template-dependent instructions.
generic_region_stack().AddDependentInst(
{.inst_id = inst_id, .kind = dep_kind});
}
}

auto Context::AddInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG() << "AddInst: " << loc_id_and_inst.inst << "\n";
FinishInst(inst_id, loc_id_and_inst.inst);
return inst_id;
}

Expand Down Expand Up @@ -118,36 +150,16 @@ auto Context::AddConstant(SemIR::Inst inst, bool is_symbolic)
auto Context::ReplaceLocIdAndInstBeforeConstantUse(
SemIR::InstId inst_id, SemIR::LocIdAndInst loc_id_and_inst) -> void {
sem_ir().insts().SetLocIdAndInst(inst_id, loc_id_and_inst);

CARBON_VLOG() << "ReplaceInst: " << inst_id << " -> " << loc_id_and_inst.inst
<< "\n";

// Redo evaluation. This is only safe to do if this instruction has not
// already been used as a constant, which is the caller's responsibility to
// ensure.
auto const_id = TryEvalInst(*this, inst_id, loc_id_and_inst.inst);
if (const_id.is_constant()) {
CARBON_VLOG() << "Constant: " << loc_id_and_inst.inst << " -> "
<< constant_values().GetInstId(const_id) << "\n";
}
constant_values().Set(inst_id, const_id);
FinishInst(inst_id, loc_id_and_inst.inst);
}

auto Context::ReplaceInstBeforeConstantUse(SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
sem_ir().insts().Set(inst_id, inst);

CARBON_VLOG() << "ReplaceInst: " << inst_id << " -> " << inst << "\n";

// Redo evaluation. This is only safe to do if this instruction has not
// already been used as a constant, which is the caller's responsibility to
// ensure.
auto const_id = TryEvalInst(*this, inst_id, inst);
if (const_id.is_constant()) {
CARBON_VLOG() << "Constant: " << inst << " -> "
<< constant_values().GetInstId(const_id) << "\n";
}
constant_values().Set(inst_id, const_id);
FinishInst(inst_id, inst);
}

auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def)
Expand Down
12 changes: 12 additions & 0 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "toolchain/check/decl_introducer_state.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/check/generic_region_stack.h"
#include "toolchain/check/inst_block_stack.h"
#include "toolchain/check/node_stack.h"
#include "toolchain/check/param_and_arg_refs_stack.h"
Expand Down Expand Up @@ -362,6 +363,10 @@ class Context {
return scope_stack().break_continue_stack();
}

auto generic_region_stack() -> GenericRegionStack& {
return generic_region_stack_;
}

auto import_ir_constant_values()
-> llvm::SmallVector<SemIR::ConstantValueStore, 0>& {
return import_ir_constant_values_;
Expand Down Expand Up @@ -436,6 +441,10 @@ class Context {
SemIR::TypeId type_id_;
};

// Finish producing an instruction. Set its constant value, and register it in
// any applicable instruction lists.
auto FinishInst(SemIR::InstId inst_id, SemIR::Inst inst) -> void;

// Tokens for getting data on literals.
const Lex::TokenizedBuffer* tokens_;

Expand Down Expand Up @@ -476,6 +485,9 @@ class Context {
// The stack of scopes we are currently within.
ScopeStack scope_stack_;

// The stack of generic regions we are currently within.
GenericRegionStack generic_region_stack_;

// Cache of reverse mapping from type constants to types.
//
// TODO: Instead of mapping to a dense `TypeId` space, we could make `TypeId`
Expand Down
41 changes: 32 additions & 9 deletions toolchain/check/generic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,59 @@

namespace Carbon::Check {

auto StartGenericDecl(Context& /*context*/) -> void {
// TODO: Start tracking the contents of this declaration.
auto StartGenericDecl(Context& context) -> void {
context.generic_region_stack().Push();
}

auto StartGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Start tracking the contents of this definition.
auto StartGenericDefinition(Context& context) -> void {
// Push a generic region even if we don't have a generic_id. We might still
// have locally-introduced generic parameters to track:
//
// fn F() {
// let T:! type = i32;
// var x: T;
// }
context.generic_region_stack().Push();
}

auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
-> SemIR::GenericId {
if (context.scope_stack().compile_time_binding_stack().empty()) {
CARBON_CHECK(context.generic_region_stack().PeekDependentInsts().empty())
<< "Have dependent instructions but no compile time bindings are in "
"scope.";
context.generic_region_stack().Pop();
return SemIR::GenericId::Invalid;
}

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

auto FinishGenericRedecl(Context& /*context*/, SemIR::InstId /*decl_id*/,
auto FinishGenericRedecl(Context& context, SemIR::InstId /*decl_id*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Compare contents of this declaration with the existing one on the
// generic.
context.generic_region_stack().Pop();
}

auto FinishGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Track contents of this generic definition.
auto FinishGenericDefinition(Context& context, SemIR::GenericId generic_id)
-> void {
if (!generic_id.is_valid()) {
// TODO: We can have symbolic constants in a context that had a non-generic
// declaration, for example if there's a local generic let binding in a
// function definition. Handle this case somehow -- perhaps by forming
// substituted constant values now.
context.generic_region_stack().Pop();
return;
}

// TODO: Track the list of dependent instructions in this region.
context.generic_region_stack().Pop();
}

auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
Expand Down
5 changes: 2 additions & 3 deletions toolchain/check/generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
namespace Carbon::Check {

// Start processing a declaration or definition that might be a generic entity.
auto StartGenericDecl(Context& /*context*/) -> void;
auto StartGenericDecl(Context& context) -> void;

// Start processing a declaration or definition that might be a generic entity.
auto StartGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void;
auto StartGenericDefinition(Context& context) -> void;

// Finish processing a potentially generic declaration and produce a
// corresponding generic object. Returns SemIR::GenericId::Invalid if this
Expand Down
32 changes: 32 additions & 0 deletions toolchain/check/generic_region_stack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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/generic_region_stack.h"

namespace Carbon::Check {

auto GenericRegionStack::Push() -> void {
regions_.push_back(
{.first_dependent_inst = static_cast<int32_t>(dependent_insts_.size())});
}

auto GenericRegionStack::Pop() -> void {
auto region = regions_.pop_back_val();
dependent_insts_.truncate(region.first_dependent_inst);
}

auto GenericRegionStack::AddDependentInst(DependentInst inst) -> void {
CARBON_CHECK(!regions_.empty())
<< "Formed a dependent instruction while not in a generic region.";
CARBON_CHECK(inst.kind != DependencyKind::None);
dependent_insts_.push_back(inst);
}

auto GenericRegionStack::PeekDependentInsts() -> llvm::ArrayRef<DependentInst> {
CARBON_CHECK(!regions_.empty());
return llvm::ArrayRef(dependent_insts_)
.slice(regions_.back().first_dependent_inst);
}

} // namespace Carbon::Check
81 changes: 81 additions & 0 deletions toolchain/check/generic_region_stack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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

#ifndef CARBON_TOOLCHAIN_CHECK_GENERIC_REGION_STACK_H_
#define CARBON_TOOLCHAIN_CHECK_GENERIC_REGION_STACK_H_

#include "llvm/ADT/BitmaskEnum.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {

LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();

// A stack of enclosing regions that might be declaring or defining a generic
// entity. In such a region, we track the generic constructs that are used, such
// as symbolic constants and types, and instructions that depend on a template
// parameter.
//
// TODO: For now we're just tracking symbolic constants.
//
// We split a generic into two regions -- declaration and definition -- because
// these are in general introduced separately, and substituted into separately.
// For example, for `class C(T:! type, N:! T) { var x: T; }`, a use such as
// `C(i32, 0)*` substitutes into just the declaration, whereas a use such as
// `var x: C(i32, 0) = {.x = 0};` also substitutes into the definition.
class GenericRegionStack {
public:
// Ways in which an instruction can depend on a generic parameter.
enum class DependencyKind : int8_t {
None = 0x0,
// The type of the instruction depends on a checked generic parameter.
SymbolicType = 0x1,
// The constant value of the instruction depends on a checked generic
// parameter.
SymbolicConstant = 0x2,
Template = 0x4,
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Template)
};

// An instruction that depends on a generic parameter in some way.
struct DependentInst {
SemIR::InstId inst_id;
DependencyKind kind;
};

// Pushes a region that might be declaring or defining a generic.
auto Push() -> void;

// Pops a generic region.
auto Pop() -> void;

// Adds an instruction to the list of instructions in the current region that
// in some way depend on a generic parameter.
auto AddDependentInst(DependentInst inst) -> void;

// Returns the list of dependent instructions in the current generic region.
auto PeekDependentInsts() -> llvm::ArrayRef<DependentInst>;

private:
// Information about an enclosing generic region that has been pushed onto the
// stack.
struct RegionInfo {
// The size of `dependent_insts_` at the start of this region. Equivalently,
// this is the first index within `dependent_insts_` that belongs to this
// region or a region nested within it.
int32_t first_dependent_inst;
};

// The current set of enclosing generic regions.
llvm::SmallVector<RegionInfo> regions_;

// List of symbolic constants used in any of the enclosing generic regions. We
// keep a single vector rather than one vector per region in order to minimize
// heap allocations.
llvm::SmallVector<DependentInst> dependent_insts_;
};

} // namespace Carbon::Check

#endif // CARBON_TOOLCHAIN_CHECK_GENERIC_REGION_STACK_H_
2 changes: 1 addition & 1 deletion toolchain/check/handle_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ auto HandleClassDefinitionStart(Context& context,

// Enter the class scope.
context.scope_stack().Push(class_decl_id, class_info.scope_id);
StartGenericDefinition(context, class_info.generic_id);
StartGenericDefinition(context);

// Introduce `Self`.
context.name_scopes().AddRequiredName(
Expand Down
2 changes: 1 addition & 1 deletion toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ static auto HandleFunctionDefinitionAfterSignature(
context.return_scope_stack().push_back({.decl_id = decl_id});
context.inst_block_stack().Push();
context.scope_stack().Push(decl_id);
StartGenericDefinition(context, function.generic_id);
StartGenericDefinition(context);
context.AddCurrentCodeBlockToFunction();

// Check the return type is complete.
Expand Down
Loading

0 comments on commit fa11050

Please sign in to comment.