Quellcode durchsuchen

Replacing lowering vectors with FixedSizeValueStore (#5636)

Changes the vectors on `Lower::FileContext` to be `FixedSizeValueStore`
where possible, which we have several at this point.

This changes `FixedSizeValueStore` to prefer inferring the size from a
`ValueStore<IdT>`, which should make adding incorrect sizes harder. Note
I wasn't sure that adding a `size()` to `TypeStore` that returned
`insts().size()` would be good because it doesn't directly work that
way; `ConstantValueStore` would've also required more work since it
doesn't have access to that right now.
Jon Ross-Perkins vor 10 Monaten
Ursprung
Commit
81ca949ab8

+ 40 - 5
toolchain/base/fixed_size_value_store.h

@@ -11,30 +11,62 @@
 #include "llvm/ADT/StringRef.h"
 #include "toolchain/base/mem_usage.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/base/value_store_chunk.h"
 
 namespace Carbon {
 
 // A value store with a predetermined size.
 template <typename IdT, typename ValueT>
-class FixedSizeValueStore : public MoveOnly<FixedSizeValueStore<IdT, ValueT>> {
+class FixedSizeValueStore {
  public:
+  using IdType = IdT;
   using ValueType = ValueStoreTypes<IdT, ValueT>::ValueType;
   using RefType = ValueStoreTypes<IdT, ValueT>::RefType;
   using ConstRefType = ValueStoreTypes<IdT, ValueT>::ConstRefType;
 
   // Makes a ValueStore of the specified size, but without initializing values.
   // Entries must be set before reading.
-  static auto MakeForOverwrite(size_t size) -> FixedSizeValueStore {
+  static auto MakeForOverwriteWithExplicitSize(size_t size)
+      -> FixedSizeValueStore {
     FixedSizeValueStore store;
     store.values_.resize_for_overwrite(size);
     return store;
   }
 
+  // Makes a ValueStore of the same size as a source `ValueStoreT`, but without
+  // initializing values. Entries must be set before reading.
+  template <typename ValueStoreT>
+    requires std::same_as<IdT, typename ValueStoreT::IdType>
+  static auto MakeForOverwrite(const ValueStoreT& size_source)
+      -> FixedSizeValueStore {
+    FixedSizeValueStore store;
+    store.values_.resize_for_overwrite(size_source.size());
+    return store;
+  }
+
   // Makes a ValueStore of the specified size, initialized to a default.
-  explicit FixedSizeValueStore(size_t size, ValueT default_value) {
-    values_.resize(size, default_value);
+  static auto MakeWithExplicitSize(size_t size, ValueT default_value)
+      -> FixedSizeValueStore {
+    FixedSizeValueStore store;
+    store.values_.resize(size, default_value);
+    return store;
+  }
+
+  // Makes a ValueStore of the same size as a source `ValueStoreT`. This is
+  // the safest constructor to use, since it ensures everything's initialized to
+  // a default, and verifies a matching `IdT` for the size.
+  template <typename ValueStoreT>
+    requires std::same_as<IdT, typename ValueStoreT::IdType>
+  explicit FixedSizeValueStore(const ValueStoreT& size_source,
+                               ValueT default_value) {
+    values_.resize(size_source.size(), default_value);
   }
 
+  // Move-only.
+  FixedSizeValueStore(FixedSizeValueStore&&) noexcept = default;
+  auto operator=(FixedSizeValueStore&&) noexcept
+      -> FixedSizeValueStore& = default;
+
   // Sets the value for an ID.
   auto Set(IdT id, ValueType value) -> void {
     CARBON_DCHECK(id.index >= 0, "{0}", id);
@@ -60,9 +92,12 @@ class FixedSizeValueStore : public MoveOnly<FixedSizeValueStore<IdT, ValueT>> {
   }
 
   auto size() const -> size_t { return values_.size(); }
+  auto values() -> auto {
+    return llvm::make_range(values_.begin(), values_.end());
+  }
 
  private:
-  // Allow default construction for `MakeForOverwrite`.
+  // Allow default construction for `Make` functions.
   FixedSizeValueStore() = default;
 
   // Storage for the `ValueT` objects, indexed by the id.

+ 1 - 0
toolchain/base/value_store.h

@@ -65,6 +65,7 @@ class ValueStore
                             typename IdT::ValueType>,
           Yaml::Printable<ValueStore<IdT>>, Internal::ValueStoreNotPrintable> {
  public:
+  using IdType = IdT;
   using ValueType = ValueStoreTypes<IdT>::ValueType;
   using RefType = ValueStoreTypes<IdT>::RefType;
   using ConstRefType = ValueStoreTypes<IdT>::ConstRefType;

+ 1 - 0
toolchain/lower/BUILD

@@ -50,6 +50,7 @@ cc_library(
         "//common:map",
         "//common:raw_string_ostream",
         "//common:vlog",
+        "//toolchain/base:fixed_size_value_store",
         "//toolchain/base:kind_switch",
         "//toolchain/parse:tree",
         "//toolchain/sem_ir:absolute_node_id",

+ 6 - 7
toolchain/lower/constant.cpp

@@ -18,7 +18,7 @@ namespace Carbon::Lower {
 class ConstantContext {
  public:
   explicit ConstantContext(FileContext& file_context,
-                           llvm::MutableArrayRef<llvm::Constant*> constants)
+                           const FileContext::LoweredConstantStore* constants)
       : file_context_(&file_context), constants_(constants) {}
 
   // Gets the lowered constant value for an instruction, which must have a
@@ -40,8 +40,7 @@ class ConstantContext {
       // This constant hasn't been lowered.
       return nullptr;
     }
-    CARBON_CHECK(inst_id.index >= 0);
-    return constants_[inst_id.index];
+    return constants_->Get(inst_id);
   }
 
   // Returns a constant for the case of a value that should never be used.
@@ -91,7 +90,7 @@ class ConstantContext {
 
  private:
   FileContext* file_context_;
-  llvm::MutableArrayRef<llvm::Constant*> constants_;
+  const FileContext::LoweredConstantStore* constants_;
   int32_t last_lowered_constant_index_ = -1;
 };
 
@@ -283,8 +282,8 @@ static auto MaybeEmitAsConstant(ConstantContext& context, InstT inst)
 }
 
 auto LowerConstants(FileContext& file_context,
-                    llvm::MutableArrayRef<llvm::Constant*> constants) -> void {
-  ConstantContext context(file_context, constants);
+                    FileContext::LoweredConstantStore& constants) -> void {
+  ConstantContext context(file_context, &constants);
   // Lower each constant in InstId order. This guarantees we lower the
   // dependencies of a constant before we lower the constant itself.
   for (auto [inst_id, const_id] :
@@ -317,7 +316,7 @@ auto LowerConstants(FileContext& file_context,
 #include "toolchain/sem_ir/inst_kind.def"
     }
 
-    constants[inst_id.index] = value;
+    constants.Set(inst_id, value);
     context.SetLastLoweredConstantIndex(inst_id.index);
   }
 }

+ 2 - 1
toolchain/lower/constant.h

@@ -6,6 +6,7 @@
 #define CARBON_TOOLCHAIN_LOWER_CONSTANT_H_
 
 #include "llvm/ADT/ArrayRef.h"
+#include "toolchain/base/fixed_size_value_store.h"
 #include "toolchain/lower/file_context.h"
 
 namespace Carbon::Lower {
@@ -15,7 +16,7 @@ namespace Carbon::Lower {
 // concrete constant instructions are populated with corresponding constant
 // values.
 auto LowerConstants(FileContext& file_context,
-                    llvm::MutableArrayRef<llvm::Constant*> constants) -> void;
+                    FileContext::LoweredConstantStore& constants) -> void;
 
 }  // namespace Carbon::Lower
 

+ 46 - 50
toolchain/lower/file_context.cpp

@@ -42,7 +42,17 @@ FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
     : context_(&context),
       sem_ir_(&sem_ir),
       inst_namer_(inst_namer),
-      vlog_stream_(vlog_stream) {
+      vlog_stream_(vlog_stream),
+      functions_(LoweredFunctionStore::MakeForOverwrite(sem_ir.functions())),
+      specific_functions_(sem_ir.specifics(), nullptr),
+      types_(LoweredTypeStore::MakeWithExplicitSize(sem_ir.insts().size(),
+                                                    nullptr)),
+      constants_(LoweredConstantStore::MakeWithExplicitSize(
+          sem_ir.insts().size(), nullptr)),
+      lowered_specifics_(sem_ir.generics(), {}),
+      lowered_specifics_type_fingerprint_(sem_ir.specifics(), {}),
+      lowered_specific_fingerprint_(sem_ir.specifics(), {}),
+      equivalent_specifics_(sem_ir.specifics(), SemIR::SpecificId::None) {
   // Initialization that relies on invariants of the class.
   cpp_code_generator_ = CreateCppCodeGenerator();
   CARBON_CHECK(!sem_ir.has_errors(),
@@ -59,32 +69,18 @@ auto FileContext::PrepareToLower() -> void {
   }
 
   // Lower all types that were required to be complete.
-  types_.resize(sem_ir_->insts().size());
   for (auto type_id : sem_ir_->types().complete_types()) {
     if (type_id.index >= 0) {
-      types_[type_id.index] = BuildType(sem_ir_->types().GetInstId(type_id));
+      types_.Set(type_id, BuildType(sem_ir_->types().GetInstId(type_id)));
     }
   }
 
   // Lower function declarations.
-  functions_.resize_for_overwrite(sem_ir_->functions().size());
   for (auto [id, _] : sem_ir_->functions().enumerate()) {
-    functions_[id.index] = BuildFunctionDecl(id);
+    functions_.Set(id, BuildFunctionDecl(id));
   }
 
-  // Specific functions are lowered when we emit a reference to them.
-  specific_functions_.resize(sem_ir_->specifics().size());
-  // Additional data stored for specifics, for when attempting to coalesce.
-  // Indexed by `GenericId`.
-  lowered_specifics_.resize(sem_ir_->generics().size());
-  // Indexed by `SpecificId`.
-  lowered_specifics_type_fingerprint_.resize(sem_ir_->specifics().size());
-  lowered_specific_fingerprint_.resize(sem_ir_->specifics().size());
-  equivalent_specifics_.resize(sem_ir_->specifics().size(),
-                               SemIR::SpecificId::None);
-
   // Lower constants.
-  constants_.resize(sem_ir_->insts().size());
   LowerConstants(*this, constants_);
 }
 
@@ -127,7 +123,7 @@ auto FileContext::LowerDefinitions() -> void {
   for (auto [id, fn_info] : sem_ir_->functions().enumerate()) {
     // If we created a declaration and the function definition is not imported,
     // build a definition.
-    if (functions_[id.index] && fn_info.definition_id.has_value() &&
+    if (functions_.Get(id) && fn_info.definition_id.has_value() &&
         !sem_ir().insts().GetImportSource(fn_info.definition_id).has_value()) {
       BuildFunctionDefinition(id);
     }
@@ -187,13 +183,13 @@ auto FileContext::ContainsPair(
 }
 
 auto FileContext::CoalesceEquivalentSpecifics() -> void {
-  for (auto& specifics : lowered_specifics_) {
+  for (auto& specifics : lowered_specifics_.values()) {
     // i cannot be unsigned due to the comparison with a negative number when
     // the specifics vector is empty.
     for (int i = 0; i < static_cast<int>(specifics.size()) - 1; ++i) {
       // This specific was already replaced, skip it.
-      if (equivalent_specifics_[specifics[i].index].has_value() &&
-          equivalent_specifics_[specifics[i].index] != specifics[i]) {
+      if (equivalent_specifics_.Get(specifics[i]).has_value() &&
+          equivalent_specifics_.Get(specifics[i]) != specifics[i]) {
         specifics[i] = specifics[specifics.size() - 1];
         specifics.pop_back();
         --i;
@@ -203,8 +199,8 @@ auto FileContext::CoalesceEquivalentSpecifics() -> void {
       // `lowered_specifics_type_fingerprint_` and `common_fingerprint`.
       for (int j = i + 1; j < static_cast<int>(specifics.size()); ++j) {
         // When the specific was already replaced, skip it.
-        if (equivalent_specifics_[specifics[j].index].has_value() &&
-            equivalent_specifics_[specifics[j].index] != specifics[j]) {
+        if (equivalent_specifics_.Get(specifics[j]).has_value() &&
+            equivalent_specifics_.Get(specifics[j]) != specifics[j]) {
           specifics[j] = specifics[specifics.size() - 1];
           specifics.pop_back();
           --j;
@@ -243,10 +239,9 @@ auto FileContext::CoalesceEquivalentSpecifics() -> void {
 
           // Delete function bodies for already replaced functions.
           for (auto specific_id : specifics_to_delete) {
-            specific_functions_[specific_id.index]->eraseFromParent();
-            specific_functions_[specific_id.index] =
-                specific_functions_[equivalent_specifics_[specific_id.index]
-                                        .index];
+            specific_functions_.Get(specific_id)->eraseFromParent();
+            specific_functions_.Get(specific_id) =
+                specific_functions_.Get(equivalent_specifics_.Get(specific_id));
           }
 
           // Removed the replaced specific from the list of emitted specifics.
@@ -272,10 +267,10 @@ auto FileContext::ProcessSpecificEquivalence(
                "Expected values in equivalence check");
 
   auto get_canon = [&](SemIR::SpecificId specific_id) {
-    return equivalent_specifics_[specific_id.index].has_value()
+    return equivalent_specifics_.Get(specific_id).has_value()
                ? std::make_pair(
-                     equivalent_specifics_[specific_id.index],
-                     (equivalent_specifics_[specific_id.index] != specific_id))
+                     equivalent_specifics_.Get(specific_id),
+                     (equivalent_specifics_.Get(specific_id) != specific_id))
                : std::make_pair(specific_id, false);
   };
   auto [canon_id1, replaced_before1] = get_canon(specific_id1);
@@ -295,10 +290,10 @@ auto FileContext::ProcessSpecificEquivalence(
   // Update equivalent_specifics_ for all. This is used as an indicator that
   // this specific_id may be the canonical one when reducing the equivalence
   // chains in `IsKnownEquivalence`.
-  equivalent_specifics_[specific_id1.index] = canon_id1;
-  equivalent_specifics_[specific_id2.index] = canon_id1;
-  specific_functions_[canon_id2.index]->replaceAllUsesWith(
-      specific_functions_[canon_id1.index]);
+  equivalent_specifics_.Set(specific_id1, canon_id1);
+  equivalent_specifics_.Set(specific_id2, canon_id1);
+  specific_functions_.Get(canon_id2)->replaceAllUsesWith(
+      specific_functions_.Get(canon_id1));
   if (!replaced_before2) {
     specifics_to_delete.push_back(canon_id2);
   }
@@ -306,39 +301,40 @@ auto FileContext::ProcessSpecificEquivalence(
 
 auto FileContext::IsKnownEquivalence(SemIR::SpecificId specific_id1,
                                      SemIR::SpecificId specific_id2) -> bool {
-  if (!equivalent_specifics_[specific_id1.index].has_value() ||
-      !equivalent_specifics_[specific_id2.index].has_value()) {
+  if (!equivalent_specifics_.Get(specific_id1).has_value() ||
+      !equivalent_specifics_.Get(specific_id2).has_value()) {
     return false;
   }
 
   auto update_equivalent_specific = [&](SemIR::SpecificId specific_id) {
     llvm::SmallVector<SemIR::SpecificId> stack;
     SemIR::SpecificId specific_to_update = specific_id;
-    while (equivalent_specifics_[equivalent_specifics_[specific_to_update.index]
-                                     .index] !=
-           equivalent_specifics_[specific_to_update.index]) {
+    while (equivalent_specifics_.Get(
+               equivalent_specifics_.Get(specific_to_update)) !=
+           equivalent_specifics_.Get(specific_to_update)) {
       stack.push_back(specific_to_update);
-      specific_to_update = equivalent_specifics_[specific_to_update.index];
+      specific_to_update = equivalent_specifics_.Get(specific_to_update);
     }
     for (auto specific : llvm::reverse(stack)) {
-      equivalent_specifics_[specific.index] =
-          equivalent_specifics_[equivalent_specifics_[specific.index].index];
+      equivalent_specifics_.Set(
+          specific,
+          equivalent_specifics_.Get(equivalent_specifics_.Get(specific)));
     }
   };
 
   update_equivalent_specific(specific_id1);
   update_equivalent_specific(specific_id2);
 
-  return equivalent_specifics_[specific_id1.index] ==
-         equivalent_specifics_[specific_id2.index];
+  return equivalent_specifics_.Get(specific_id1) ==
+         equivalent_specifics_.Get(specific_id2);
 }
 
 auto FileContext::AreFunctionTypesEquivalent(SemIR::SpecificId specific_id1,
                                              SemIR::SpecificId specific_id2)
     -> bool {
   CARBON_CHECK(specific_id1.has_value() && specific_id2.has_value());
-  return lowered_specifics_type_fingerprint_[specific_id1.index] ==
-         lowered_specifics_type_fingerprint_[specific_id2.index];
+  return lowered_specifics_type_fingerprint_.Get(specific_id1) ==
+         lowered_specifics_type_fingerprint_.Get(specific_id2);
 }
 
 auto FileContext::AreFunctionBodiesEquivalent(
@@ -352,8 +348,8 @@ auto FileContext::AreFunctionBodiesEquivalent(
     auto outer_pair = worklist.pop_back_val();
     auto [specific_id1, specific_id2] = outer_pair;
 
-    auto state1 = lowered_specific_fingerprint_[specific_id1.index];
-    auto state2 = lowered_specific_fingerprint_[specific_id2.index];
+    auto state1 = lowered_specific_fingerprint_.Get(specific_id1);
+    auto state2 = lowered_specific_fingerprint_.Get(specific_id2);
     if (state1.common_fingerprint != state2.common_fingerprint) {
       InsertPair(specific_id1, specific_id2, non_equivalent_specifics_);
       return false;
@@ -409,7 +405,7 @@ auto FileContext::CreateCppCodeGenerator()
 auto FileContext::GetConstant(SemIR::ConstantId const_id,
                               SemIR::InstId use_inst_id) -> llvm::Value* {
   auto const_inst_id = sem_ir().constant_values().GetInstId(const_id);
-  auto* const_value = constants_[const_inst_id.index];
+  auto* const_value = constants_.Get(const_inst_id);
 
   // For value expressions and initializing expressions, the value produced by
   // a constant instruction is a value representation of the constant. For
@@ -626,7 +622,7 @@ auto FileContext::HandleReferencedSpecificFunction(
   llvm_type->print(os);
   function_type_fingerprint.update(os.TakeStr());
   function_type_fingerprint.final(
-      lowered_specifics_type_fingerprint_[specific_id.index]);
+      lowered_specifics_type_fingerprint_.Get(specific_id));
 }
 
 auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,

+ 30 - 33
toolchain/lower/file_context.h

@@ -20,6 +20,9 @@ namespace Carbon::Lower {
 // Context and shared functionality for lowering within a SemIR file.
 class FileContext {
  public:
+  using LoweredConstantStore =
+      FixedSizeValueStore<SemIR::InstId, llvm::Constant*>;
+
   // Describes a specific function's body fingerprint.
   struct SpecificFunctionFingerprint {
     // Fingerprint with all specific-dependent instructions, except specific
@@ -72,9 +75,9 @@ class FileContext {
     CARBON_CHECK(type_id.has_value(), "Should not be called with `None`");
     CARBON_CHECK(type_id.is_concrete(), "Lowering symbolic type {0}: {1}",
                  type_id, sem_ir().types().GetAsInst(type_id));
-    CARBON_CHECK(types_[type_id.index], "Missing type {0}: {1}", type_id,
+    CARBON_CHECK(types_.Get(type_id), "Missing type {0}: {1}", type_id,
                  sem_ir().types().GetAsInst(type_id));
-    return types_[type_id.index];
+    return types_.Get(type_id);
   }
 
   // Returns location information for use with DebugInfo.
@@ -145,8 +148,8 @@ class FileContext {
   // Gets the location in which a callable's function is stored.
   auto GetFunctionAddr(SemIR::FunctionId function_id,
                        SemIR::SpecificId specific_id) -> llvm::Function** {
-    return specific_id.has_value() ? &specific_functions_[specific_id.index]
-                                   : &functions_[function_id.index];
+    return specific_id.has_value() ? &specific_functions_.Get(specific_id)
+                                   : &functions_.Get(function_id);
   }
 
   // Notes that a C++ function has been referenced for the first time, so we
@@ -195,7 +198,7 @@ class FileContext {
   // by one while lowering their definitions.
   auto AddLoweredSpecificForGeneric(SemIR::GenericId generic_id,
                                     SemIR::SpecificId specific_id) {
-    lowered_specifics_[generic_id.index].push_back(specific_id);
+    lowered_specifics_.Get(generic_id).push_back(specific_id);
   }
 
   // Initializes and returns a SpecificFunctionFingerprint* instance for a
@@ -206,7 +209,7 @@ class FileContext {
     if (!specific_id.has_value()) {
       return nullptr;
     }
-    return &lowered_specific_fingerprint_[specific_id.index];
+    return &lowered_specific_fingerprint_.Get(specific_id);
   }
 
   // Entry point for coalescing equivalent specifics. Two function definitions,
@@ -282,44 +285,38 @@ class FileContext {
 
   // Maps callables to lowered functions. SemIR treats callables as the
   // canonical form of a function, so lowering needs to do the same.
-  // Vector indexes correspond to `FunctionId` indexes. We resize this directly
-  // to the correct size.
-  llvm::SmallVector<llvm::Function*, 0> functions_;
+  using LoweredFunctionStore =
+      FixedSizeValueStore<SemIR::FunctionId, llvm::Function*>;
+  LoweredFunctionStore functions_;
 
-  // Maps specific callables to lowered functions. Vector indexes correspond to
-  // `SpecificId` indexes. We resize this directly to the correct size.
-  llvm::SmallVector<llvm::Function*, 0> specific_functions_;
+  // Maps specific callables to lowered functions.
+  FixedSizeValueStore<SemIR::SpecificId, llvm::Function*> specific_functions_;
 
-  // Provides lowered versions of types.
-  // Vector indexes correspond to `TypeId` indexes for non-symbolic types. We
-  // resize this directly to the (often large) correct size.
-  llvm::SmallVector<llvm::Type*, 0> types_;
+  // Provides lowered versions of types. Entries are non-symbolic types.
+  using LoweredTypeStore = FixedSizeValueStore<SemIR::TypeId, llvm::Type*>;
+  LoweredTypeStore types_;
 
-  // Maps constants to their lowered values.
-  // Vector indexes correspond to `InstId` indexes for constant instructions. We
-  // resize this directly to the (often large) correct size.
-  llvm::SmallVector<llvm::Constant*, 0> constants_;
+  // Maps constants to their lowered values. Indexes are the `InstId` for
+  // constant instructions.
+  LoweredConstantStore constants_;
 
   // Maps global variables to their lowered variant.
   Map<SemIR::InstId, llvm::GlobalVariable*> global_variables_;
 
   // For a generic function, keep track of the specifics for which LLVM
   // function declarations were created. Those can be retrieved then from
-  // `specific_functions_`. We resize this to the correct size. Vector indexes
-  // correspond to `GenericId` indexes.
-  llvm::SmallVector<llvm::SmallVector<SemIR::SpecificId>, 0> lowered_specifics_;
+  // `specific_functions_`.
+  FixedSizeValueStore<SemIR::GenericId, llvm::SmallVector<SemIR::SpecificId>>
+      lowered_specifics_;
 
   // For specifics that exist in lowered_specifics, a hash of their function
-  // type information: return and parameter types. We resize this to the
-  // correct size. Vector indexes correspond to `SpecificId` indexes.
+  // type information: return and parameter types.
   // TODO: Hashing all members of `FunctionTypeInfo` may not be necessary.
-  llvm::SmallVector<llvm::BLAKE3Result<32>, 0>
+  FixedSizeValueStore<SemIR::SpecificId, llvm::BLAKE3Result<32>>
       lowered_specifics_type_fingerprint_;
 
   // This is initialized and populated while lowering a specific.
-  // We resize this to the correct size. Vector indexes correspond to
-  // `SpecificId` indexes.
-  llvm::SmallVector<SpecificFunctionFingerprint, 0>
+  FixedSizeValueStore<SemIR::SpecificId, SpecificFunctionFingerprint>
       lowered_specific_fingerprint_;
 
   // Equivalent specifics that have been found. For each specific, this points
@@ -327,10 +324,10 @@ class FileContext {
   // define the canonical specific as the one with the lowest
   // `SpecificId.index`.
   //
-  // We resize this to the correct size and initialize to `SpecificId::None`,
-  // which defines that there is no other equivalent specific to this
-  // `SpecificId`. Vector indexes correspond to `SpecificId` indexes.
-  llvm::SmallVector<SemIR::SpecificId, 0> equivalent_specifics_;
+  // Entries are initialized to `SpecificId::None`, which defines that there is
+  // no other equivalent specific to this `SpecificId`.
+  FixedSizeValueStore<SemIR::SpecificId, SemIR::SpecificId>
+      equivalent_specifics_;
 
   // Non-equivalent specifics found.
   // TODO: Revisit this due to its quadratic space growth.

+ 2 - 1
toolchain/parse/tree_and_subtrees.cpp

@@ -16,7 +16,8 @@ TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens,
                                  const Tree& tree)
     : tokens_(&tokens),
       tree_(&tree),
-      subtree_sizes_(SubtreeSizeStore::MakeForOverwrite(tree_->size())) {
+      subtree_sizes_(
+          SubtreeSizeStore::MakeForOverwriteWithExplicitSize(tree_->size())) {
   // A stack of nodes which haven't yet been used as children.
   llvm::SmallVector<NodeId> size_stack;
   for (auto n : tree.postorder()) {

+ 3 - 3
toolchain/sem_ir/formatter.cpp

@@ -44,7 +44,7 @@ Formatter::Formatter(const File* sem_ir,
       use_dump_sem_ir_ranges_(use_dump_sem_ir_ranges),
       // Create a placeholder visible chunk and assign it to all instructions
       // that don't have a chunk of their own.
-      tentative_inst_chunks_(sem_ir_->insts().size(), AddChunkNoFlush(true)) {
+      tentative_inst_chunks_(sem_ir_->insts(), AddChunkNoFlush(true)) {
   if (use_dump_sem_ir_ranges_) {
     ComputeNodeParents();
   }
@@ -103,8 +103,8 @@ auto Formatter::Format() -> void {
 
 auto Formatter::ComputeNodeParents() -> void {
   CARBON_CHECK(!node_parents_);
-  node_parents_ =
-      NodeParentStore(sem_ir_->parse_tree().size(), Parse::NodeId::None);
+  node_parents_ = NodeParentStore::MakeWithExplicitSize(
+      sem_ir_->parse_tree().size(), Parse::NodeId::None);
   for (auto n : sem_ir_->parse_tree().postorder()) {
     for (auto child : get_tree_and_subtrees_().children(n)) {
       node_parents_->Set(child, n);

+ 2 - 0
toolchain/sem_ir/generic.h

@@ -104,6 +104,8 @@ struct Specific : Printable<Specific> {
 // their associated generic argument list.
 class SpecificStore : public Yaml::Printable<SpecificStore> {
  public:
+  using IdType = SpecificId;
+
   // Adds a new specific, or gets the existing specific for a specified generic
   // and argument list. Returns the ID of the specific. The argument IDs must be
   // for instructions in the constant block, and must be a canonical instruction

+ 2 - 0
toolchain/sem_ir/inst.h

@@ -389,6 +389,8 @@ struct LocIdAndInst {
 // Provides a ValueStore wrapper for an API specific to instructions.
 class InstStore {
  public:
+  using IdType = InstId;
+
   explicit InstStore(File* file) : file_(file) {}
 
   // Adds an instruction to the instruction list, returning an ID to reference