diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index f7c6d813d282..0167ebd13233 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -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", @@ -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"], diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 27bc38751d7c..188eb20c8906 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -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" @@ -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 { @@ -72,18 +77,45 @@ auto Context::VerifyOnFinish() -> void { param_and_arg_refs_stack_.VerifyOnFinish(); } +// 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; + + // 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: " << inst << " -> " + << constant_values().GetInstId(const_id) << "\n"; + + // 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"; - - 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); return inst_id; } @@ -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) diff --git a/toolchain/check/context.h b/toolchain/check/context.h index e8fffa9139ff..429a3beb211a 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -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" @@ -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& { return import_ir_constant_values_; @@ -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_; @@ -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` diff --git a/toolchain/check/generic.cpp b/toolchain/check/generic.cpp index 4676c160ea40..74e3d368f26c 100644 --- a/toolchain/check/generic.cpp +++ b/toolchain/check/generic.cpp @@ -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, diff --git a/toolchain/check/generic.h b/toolchain/check/generic.h index 86a45836e481..d7c11161b51d 100644 --- a/toolchain/check/generic.h +++ b/toolchain/check/generic.h @@ -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 diff --git a/toolchain/check/generic_region_stack.cpp b/toolchain/check/generic_region_stack.cpp new file mode 100644 index 000000000000..05aa055f70f8 --- /dev/null +++ b/toolchain/check/generic_region_stack.cpp @@ -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(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 { + CARBON_CHECK(!regions_.empty()); + return llvm::ArrayRef(dependent_insts_) + .slice(regions_.back().first_dependent_inst); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/generic_region_stack.h b/toolchain/check/generic_region_stack.h new file mode 100644 index 000000000000..832be4854f05 --- /dev/null +++ b/toolchain/check/generic_region_stack.h @@ -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; + + 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 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 dependent_insts_; +}; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_GENERIC_REGION_STACK_H_ diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 6bfd5a630311..e1fd1615e1e9 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -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( diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 2fd8462c4d26..634c71b68513 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -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. diff --git a/toolchain/check/handle_interface.cpp b/toolchain/check/handle_interface.cpp index e35fb55056ff..f4615af66845 100644 --- a/toolchain/check/handle_interface.cpp +++ b/toolchain/check/handle_interface.cpp @@ -142,7 +142,7 @@ auto HandleInterfaceDefinitionStart(Context& context, // Enter the interface scope. context.scope_stack().Push(interface_decl_id, interface_info.scope_id); - StartGenericDefinition(context, interface_info.generic_id); + StartGenericDefinition(context); context.inst_block_stack().Push(); context.node_stack().Push(node_id, interface_id); diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 303272434c38..8293cd2bffd9 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -1587,8 +1587,13 @@ auto LoadImportRef(Context& context, SemIR::InstId inst_id) -> void { // Resolve will assign the constant. auto load_ir_inst = indirect_insts.pop_back_val(); ImportRefResolver resolver(context, load_ir_inst.ir_id); + // The resolver calls into Context to create instructions. Don't register + // those instructions as part of the enclosing generic scope if they're + // dependent on a generic parameter. + context.generic_region_stack().Push(); auto type_id = resolver.ResolveType(load_type_id); auto constant_id = resolver.Resolve(load_ir_inst.inst_id); + context.generic_region_stack().Pop(); // Replace the ImportRefUnloaded instruction with ImportRefLoaded. This // doesn't use ReplaceInstBeforeConstantUse because it would trigger diff --git a/toolchain/sem_ir/inst.h b/toolchain/sem_ir/inst.h index edd867a8d5eb..d2a9bacfba13 100644 --- a/toolchain/sem_ir/inst.h +++ b/toolchain/sem_ir/inst.h @@ -238,7 +238,7 @@ class Inst : public Printable { auto arg1() const -> int32_t { return arg1_; } // Sets the type of this instruction. - auto SetType(TypeId type_id) { type_id_ = type_id; } + auto SetType(TypeId type_id) -> void { type_id_ = type_id; } // Sets the arguments of this instruction. auto SetArgs(int32_t arg0, int32_t arg1) {