Просмотр исходного кода

Track a list of dependent instructions created within a generic (#4092)

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.
Richard Smith 1 год назад
Родитель
Сommit
fa11050961

+ 16 - 0
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"],

+ 41 - 29
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();
 }
 
-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;
 }
 
@@ -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)

+ 12 - 0
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<SemIR::ConstantValueStore, 0>& {
     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`

+ 32 - 9
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,

+ 2 - 3
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

+ 32 - 0
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<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 - 0
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<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_

+ 1 - 1
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(

+ 1 - 1
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.

+ 1 - 1
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);

+ 5 - 0
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

+ 1 - 1
toolchain/sem_ir/inst.h

@@ -238,7 +238,7 @@ class Inst : public Printable<Inst> {
   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) {