Przeglądaj źródła

Make IdTag typesafe (#6574)

The IdTag knows the type of the Id its tagging and the type of the Id
being used as the tag. This prevents mixing up tagged and untagged ids,
and avoids having to work with untyped integers.

Adds an Untagged marker struct that's used as the tag type in IdTag when
no tag is desired.

The complexity of ConstantIds and TypeIds became a bit visible: TypeIds
are concrete ConstantIds. And ConstantIds have two different tagging
schemes, one for concrete and one for symbolic ids. And ConstantIds are
actually re-cast InstIds with the same index. The LoweredTypeStore needs
to work with tagged TypeIds, but the tags actually come from an InstId
store in ConstantValueStore. Now this is expressed in the type system by
getting the tags for TypeIds from the ConstantValueStore.

ValueStores without an TagId type parameter are now visibly untagged.

IdTag is now only default constructible when it does not have a tag,
which means ValueStore is only default constructible when the TagId is
untagged. This forces tagged value stores to be constructed correctly
with a tag at compile time, and untagged ones to be constructed without.

FixedSizeValueStore has overloads for dealing with tagged and untagged
Ids, since it can't default-construct ValueStore for tagged ids, and no
longer requires passing in default-constructed tags when there is no tag
in the ids.
Dana Jansens 3 miesięcy temu
rodzic
commit
c64117d0e0
46 zmienionych plików z 440 dodań i 279 usunięć
  1. 2 0
      toolchain/base/BUILD
  2. 13 8
      toolchain/base/block_value_store.h
  3. 19 14
      toolchain/base/canonical_value_store.h
  4. 58 13
      toolchain/base/fixed_size_value_store.h
  5. 103 64
      toolchain/base/id_tag.h
  6. 6 3
      toolchain/base/relational_value_store.h
  7. 26 16
      toolchain/base/value_store.h
  8. 4 6
      toolchain/check/check_unit.cpp
  9. 7 5
      toolchain/check/check_unit.h
  10. 2 3
      toolchain/check/context.cpp
  11. 4 5
      toolchain/check/context.h
  12. 67 67
      toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon
  13. 4 4
      toolchain/driver/compile_subcommand.cpp
  14. 1 1
      toolchain/language_server/context.cpp
  15. 4 4
      toolchain/lower/file_context.cpp
  16. 17 7
      toolchain/lower/file_context.h
  17. 10 5
      toolchain/lower/specific_coalescer.h
  18. 1 1
      toolchain/sem_ir/associated_constant.h
  19. 1 1
      toolchain/sem_ir/clang_decl.h
  20. 1 1
      toolchain/sem_ir/class.h
  21. 29 5
      toolchain/sem_ir/constant.h
  22. 2 2
      toolchain/sem_ir/cpp_global_var.h
  23. 2 1
      toolchain/sem_ir/cpp_overload_set.h
  24. 2 1
      toolchain/sem_ir/entity_name.h
  25. 2 1
      toolchain/sem_ir/facet_type_info.h
  26. 4 4
      toolchain/sem_ir/file.cpp
  27. 5 4
      toolchain/sem_ir/file.h
  28. 1 1
      toolchain/sem_ir/formatter.cpp
  29. 1 1
      toolchain/sem_ir/formatter.h
  30. 1 1
      toolchain/sem_ir/function.h
  31. 2 3
      toolchain/sem_ir/generic.cpp
  32. 4 4
      toolchain/sem_ir/generic.h
  33. 5 0
      toolchain/sem_ir/ids.h
  34. 2 2
      toolchain/sem_ir/impl.h
  35. 1 1
      toolchain/sem_ir/import_ir.h
  36. 8 6
      toolchain/sem_ir/inst.h
  37. 4 3
      toolchain/sem_ir/inst_fingerprinter.cpp
  38. 4 2
      toolchain/sem_ir/inst_fingerprinter.h
  39. 1 1
      toolchain/sem_ir/interface.h
  40. 1 1
      toolchain/sem_ir/name_scope.cpp
  41. 1 1
      toolchain/sem_ir/name_scope.h
  42. 2 1
      toolchain/sem_ir/named_constraint.h
  43. 3 2
      toolchain/sem_ir/require_impls.h
  44. 1 1
      toolchain/sem_ir/specific_interface.h
  45. 1 1
      toolchain/sem_ir/struct_type_field.h
  46. 1 1
      toolchain/sem_ir/vtable.h

+ 2 - 0
toolchain/base/BUILD

@@ -12,6 +12,7 @@ cc_library(
     name = "block_value_store",
     hdrs = ["block_value_store.h"],
     deps = [
+        ":id_tag",
         ":mem_usage",
         ":value_store",
         ":yaml",
@@ -88,6 +89,7 @@ cc_library(
     hdrs = ["id_tag.h"],
     deps = [
         "//common:check",
+        "//common:ostream",
         "@llvm-project//llvm:Support",
     ],
 )

+ 13 - 8
toolchain/base/block_value_store.h

@@ -10,6 +10,7 @@
 #include "common/check.h"
 #include "common/set.h"
 #include "llvm/Support/Allocator.h"
+#include "toolchain/base/id_tag.h"
 #include "toolchain/base/mem_usage.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
@@ -22,17 +23,21 @@ namespace Carbon::SemIR {
 //
 // BlockValueStore is used as-is, but there are also children that expose the
 // protected members for type-specific functionality.
-template <typename IdT, typename ElementT>
-class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT, ElementT>> {
+template <typename IdT, typename ElementT, typename TagIdT = Untagged>
+class BlockValueStore
+    : public Yaml::Printable<BlockValueStore<IdT, ElementT, TagIdT>> {
  public:
   using IdType = IdT;
+  using IdTagType = IdTag<IdT, TagIdT>;
   using ElementType = ElementT;
   using RefType = llvm::MutableArrayRef<ElementT>;
   using ConstRefType = llvm::ArrayRef<ElementT>;
 
   explicit BlockValueStore(llvm::BumpPtrAllocator& allocator,
-                           IdTag tag = IdTag())
-      : allocator_(&allocator), values_(tag) {
+                           IdTagType::TagIdType tag_id,
+                           int32_t initial_reserved_ids = 0)
+    requires(!IdTagIsUntagged<IdTagType>)
+      : allocator_(&allocator), values_(tag_id, initial_reserved_ids) {
     auto empty = RefType();
     auto empty_val = canonical_blocks_.Insert(
         empty, [&] { return values_.Add(empty); }, KeyContext(this));
@@ -129,18 +134,18 @@ class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT, ElementT>> {
   }
 
   // Allow children to have more complex value handling.
-  auto values() -> ValueStore<IdT, RefType>& { return values_; }
+  auto values() -> ValueStore<IdT, RefType, TagIdT>& { return values_; }
 
  private:
   class KeyContext;
 
   llvm::BumpPtrAllocator* allocator_;
-  ValueStore<IdT, RefType> values_;
+  ValueStore<IdT, RefType, TagIdT> values_;
   Set<IdT, /*SmallSize=*/0, KeyContext> canonical_blocks_;
 };
 
-template <typename IdT, typename ElementT>
-class BlockValueStore<IdT, ElementT>::KeyContext
+template <typename IdT, typename ElementT, typename TagIdT>
+class BlockValueStore<IdT, ElementT, TagIdT>::KeyContext
     : public TranslatingKeyContext<KeyContext> {
  public:
   explicit KeyContext(const BlockValueStore* store) : store_(store) {}

+ 19 - 14
toolchain/base/canonical_value_store.h

@@ -22,10 +22,12 @@ namespace Carbon {
 // `KeyT` can optionally be different from `ValueT`, and if so is used for the
 // argument to `Lookup`. In this case, `ValueT` must provide a `GetAsKey` member
 // function that returns the corresponding key.
-template <typename IdT, typename KeyT, typename ValueT = KeyT>
+template <typename IdT, typename KeyT, typename TagIdT = Untagged,
+          typename ValueT = KeyT>
 class CanonicalValueStore {
  public:
   using IdType = IdT;
+  using IdTagType = IdTag<IdT, TagIdT>;
   using KeyType = std::remove_cvref_t<KeyT>;
   using ValueType = ValueStoreTypes<ValueT>::ValueType;
   using RefType = ValueStoreTypes<ValueT>::RefType;
@@ -56,7 +58,7 @@ class CanonicalValueStore {
   }
 
   auto values() const [[clang::lifetimebound]]
-  -> ValueStore<IdT, ValueType>::Range {
+  -> ValueStore<IdT, ValueType, TagIdT>::Range {
     return values_.values();
   }
   auto size() const -> size_t { return values_.size(); }
@@ -71,7 +73,7 @@ class CanonicalValueStore {
 
   auto GetRawIndex(IdT id) const -> int32_t { return values_.GetRawIndex(id); }
 
-  auto GetIdTag() const -> IdTag { return values_.GetIdTag(); }
+  auto GetIdTag() const -> IdTagType { return values_.GetIdTag(); }
 
  private:
   class KeyContext;
@@ -87,15 +89,15 @@ class CanonicalValueStore {
     return value.GetAsKey();
   }
 
-  ValueStore<IdT, ValueType> values_;
+  ValueStore<IdT, ValueType, TagIdT> values_;
   Set<IdT, /*SmallSize=*/0, KeyContext> set_;
 };
 
-template <typename IdT, typename KeyT, typename ValueT>
-class CanonicalValueStore<IdT, KeyT, ValueT>::KeyContext
+template <typename IdT, typename KeyT, typename TagIdT, typename ValueT>
+class CanonicalValueStore<IdT, KeyT, TagIdT, ValueT>::KeyContext
     : public TranslatingKeyContext<KeyContext> {
  public:
-  explicit KeyContext(const ValueStore<IdT, ValueType>* values)
+  explicit KeyContext(const ValueStore<IdT, ValueType, TagIdT>* values)
       : values_(values) {}
 
   // Note that it is safe to return a reference here as the underlying object's
@@ -106,25 +108,28 @@ class CanonicalValueStore<IdT, KeyT, ValueT>::KeyContext
   }
 
  private:
-  const ValueStore<IdT, ValueType>* values_;
+  const ValueStore<IdT, ValueType, TagIdT>* values_;
 };
 
-template <typename IdT, typename KeyT, typename ValueT>
-auto CanonicalValueStore<IdT, KeyT, ValueT>::Add(ValueType value) -> IdT {
+template <typename IdT, typename KeyT, typename TagIdT, typename ValueT>
+auto CanonicalValueStore<IdT, KeyT, TagIdT, ValueT>::Add(ValueType value)
+    -> IdT {
   auto make_key = [&] { return IdT(values_.Add(std::move(value))); };
   return set_.Insert(GetAsKey(value), make_key, KeyContext(&values_)).key();
 }
 
-template <typename IdT, typename KeyT, typename ValueT>
-auto CanonicalValueStore<IdT, KeyT, ValueT>::Lookup(KeyType key) const -> IdT {
+template <typename IdT, typename KeyT, typename TagIdT, typename ValueT>
+auto CanonicalValueStore<IdT, KeyT, TagIdT, ValueT>::Lookup(KeyType key) const
+    -> IdT {
   if (auto result = set_.Lookup(key, KeyContext(&values_))) {
     return result.key();
   }
   return IdT::None;
 }
 
-template <typename IdT, typename KeyT, typename ValueT>
-auto CanonicalValueStore<IdT, KeyT, ValueT>::Reserve(size_t size) -> void {
+template <typename IdT, typename KeyT, typename TagIdT, typename ValueT>
+auto CanonicalValueStore<IdT, KeyT, TagIdT, ValueT>::Reserve(size_t size)
+    -> void {
   // Compute the resulting new insert count using the size of values -- the
   // set doesn't have a fast to compute current size.
   if (size > values_.size()) {

+ 58 - 13
toolchain/base/fixed_size_value_store.h

@@ -19,18 +19,32 @@
 namespace Carbon {
 
 // A value store with a predetermined size.
-template <typename IdT, typename ValueT>
+template <typename IdT, typename ValueT, typename TagIdT = Untagged>
 class FixedSizeValueStore {
  public:
   using IdType = IdT;
+  using IdTagType = IdTag<IdT, TagIdT>;
   using ValueType = ValueStoreTypes<ValueT>::ValueType;
   using RefType = ValueStoreTypes<ValueT>::RefType;
   using ConstRefType = ValueStoreTypes<ValueT>::ConstRefType;
 
   // Makes a ValueStore of the specified size, but without initializing values.
   // Entries must be set before reading.
+  static auto MakeForOverwriteWithExplicitSize(size_t size,
+                                               IdTagType::TagIdType tag_id,
+                                               int32_t initial_reserved_ids = 0)
+      -> FixedSizeValueStore
+    requires(!IdTagIsUntagged<IdTagType>)
+  {
+    FixedSizeValueStore store(IdTagType(tag_id, initial_reserved_ids));
+    store.values_.resize_for_overwrite(size);
+    return store;
+  }
+
   static auto MakeForOverwriteWithExplicitSize(size_t size)
-      -> FixedSizeValueStore {
+      -> FixedSizeValueStore
+    requires(IdTagIsUntagged<IdTagType>)
+  {
     FixedSizeValueStore store;
     store.values_.resize_for_overwrite(size);
     return store;
@@ -39,7 +53,8 @@ class FixedSizeValueStore {
   // 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>
+    requires(std::same_as<IdT, typename ValueStoreT::IdType> &&
+             !IdTagIsUntagged<typename ValueStoreT::IdTagType>)
   static auto MakeForOverwrite(const ValueStoreT& size_source)
       -> FixedSizeValueStore {
     FixedSizeValueStore store(size_source.GetIdTag());
@@ -48,11 +63,21 @@ class FixedSizeValueStore {
   }
 
   // Makes a ValueStore of the specified size, initialized to a default.
-  static auto MakeWithExplicitSize(IdTag tag, size_t size,
+  static auto MakeWithExplicitSize(size_t size, IdTagType tag,
                                    ConstRefType default_value)
-      -> FixedSizeValueStore {
+      -> FixedSizeValueStore
+    requires(!IdTagIsUntagged<IdTagType>)
+  {
+    FixedSizeValueStore store(tag);
+    store.values_.resize(size, default_value);
+    return store;
+  }
+
+  static auto MakeWithExplicitSize(size_t size, ConstRefType default_value)
+      -> FixedSizeValueStore
+    requires(IdTagIsUntagged<IdTagType>)
+  {
     FixedSizeValueStore store;
-    store.tag_ = tag;
     store.values_.resize(size, default_value);
     return store;
   }
@@ -74,19 +99,29 @@ class FixedSizeValueStore {
   // 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>
+    requires(std::same_as<IdT, typename ValueStoreT::IdType> &&
+             !IdTagIsUntagged<IdTagType> && !IdTagIsUntagged<ValueStoreT>)
   explicit FixedSizeValueStore(const ValueStoreT& size_source,
                                ConstRefType default_value)
       : tag_(size_source.GetIdTag()) {
     values_.resize(size_source.size(), default_value);
   }
 
-  explicit FixedSizeValueStore(IdTag tag) : tag_(tag) {}
+  template <typename ValueStoreT>
+    requires(std::same_as<IdT, typename ValueStoreT::IdType> &&
+             IdTagIsUntagged<IdTagType> && IdTagIsUntagged<ValueStoreT>)
+  explicit FixedSizeValueStore(const ValueStoreT& size_source,
+                               ConstRefType default_value) {
+    values_.resize(size_source.size(), default_value);
+  }
+
+  explicit FixedSizeValueStore(IdTagType tag) : tag_(tag) {}
 
   // Makes a ValueStore using a mapped range of `source`. The `factory_fn`
   // receives each enumerated entry for construction of `ValueType`.
   template <typename ValueStoreT>
-    requires std::same_as<IdT, typename ValueStoreT::IdType>
+    requires(std::same_as<IdT, typename ValueStoreT::IdType> &&
+             !IdTagIsUntagged<IdTagType> && !IdTagIsUntagged<ValueStoreT>)
   explicit FixedSizeValueStore(
       const ValueStoreT& source,
       llvm::function_ref<
@@ -95,6 +130,16 @@ class FixedSizeValueStore {
       : values_(llvm::map_range(source.enumerate(), factory_fn)),
         tag_(GetIdTag(source)) {}
 
+  template <typename ValueStoreT>
+    requires(std::same_as<IdT, typename ValueStoreT::IdType> &&
+             IdTagIsUntagged<IdTagType> && IdTagIsUntagged<ValueStoreT>)
+  explicit FixedSizeValueStore(
+      const ValueStoreT& source,
+      llvm::function_ref<
+          auto(IdT, typename ValueStoreT::ConstRefType)->ValueType>
+          factory_fn)
+      : values_(llvm::map_range(source.enumerate(), factory_fn)) {}
+
   // Move-only.
   FixedSizeValueStore(FixedSizeValueStore&&) noexcept = default;
   auto operator=(FixedSizeValueStore&&) noexcept
@@ -103,21 +148,21 @@ class FixedSizeValueStore {
   // Sets the value for an ID.
   auto Set(IdT id, ValueType value) -> void {
     CARBON_DCHECK(id.index >= 0, "{0}", id);
-    auto index = tag_.Remove(id.index);
+    auto index = tag_.Remove(id);
     values_[index] = value;
   }
 
   // Returns a mutable value for an ID.
   auto Get(IdT id) -> RefType {
     CARBON_DCHECK(id.index >= 0, "{0}", id);
-    auto index = tag_.Remove(id.index);
+    auto index = tag_.Remove(id);
     return values_[index];
   }
 
   // Returns the value for an ID.
   auto Get(IdT id) const -> ConstRefType {
     CARBON_DCHECK(id.index >= 0, "{0}", id);
-    auto index = tag_.Remove(id.index);
+    auto index = tag_.Remove(id);
     return values_[index];
   }
 
@@ -146,7 +191,7 @@ class FixedSizeValueStore {
   // Storage for the `ValueT` objects, indexed by the id.
   llvm::SmallVector<ValueT, 0> values_;
 
-  IdTag tag_;
+  IdTagType tag_;
 };
 
 }  // namespace Carbon

+ 103 - 64
toolchain/base/id_tag.h

@@ -10,17 +10,52 @@
 #include <limits>
 
 #include "common/check.h"
+#include "common/ostream.h"
 #include "llvm/Support/MathExtras.h"
 
 namespace Carbon {
 
+// A sentinel type to construct an IdTag without tagging.
+struct Untagged : Printable<Untagged> {
+  auto Print(llvm::raw_ostream& out) const -> void { out << "<untagged>"; }
+};
+
+// A wrapper type used as the template argument to IdTag, in order to mark the
+// tag type as such.
+template <typename TagIdT>
+struct Tag {};
+
+template <typename T>
+struct GetTagIdType {
+  static_assert(false, "IdTag with TagT that is neither Untagged nor Tag");
+};
+template <>
+struct GetTagIdType<Untagged> {
+  using TagIdType = Untagged;
+};
+template <typename TagIdT>
+struct GetTagIdType<Tag<TagIdT>> {
+  using TagIdType = TagIdT;
+};
+
+// Tests if an `IdTag` type is untagged.
+template <typename IdTagT>
+concept IdTagIsUntagged = std::same_as<typename IdTagT::TagIdType, Untagged>;
+
 // A tagged Id. It is used to add a tag into the unused bits of the id, in order
-// to verify ids are used in the correct context. The tag should look like an
-// Id, which is a non-negative number.
+// to verify ids are used in the correct context. The tag type must be `Tag` or
+// `Untagged`.
+template <typename IdT, typename TagT>
 struct IdTag {
-  IdTag() = default;
+  using IdType = IdT;
+  using TagIdType = GetTagIdType<TagT>::TagIdType;
+
+  IdTag()
+    requires(IdTagIsUntagged<IdTag>)
+  = default;
 
-  explicit IdTag(int32_t tag, int32_t initial_reserved_ids)
+  IdTag(TagIdType tag, int32_t initial_reserved_ids)
+    requires(!IdTagIsUntagged<IdTag>)
       :  // Shift down by 1 to get out of the high bit to avoid using any
          // negative ids, since they have special uses. Shift down by another 1
          // to free up the second highest bit for a marker to indicate whether
@@ -28,43 +63,43 @@ struct IdTag {
          // index so it's not zero-based, to make it a bit less likely this
          // doesn't collide with anything else (though with the
          // second-highest-bit-tagging this might not be needed).
-        id_tag_(llvm::reverseBits((((tag + 1) << 1) | 1) << 1)),
-        initial_reserved_ids_(initial_reserved_ids) {
-    CARBON_CHECK(
-        tag != -1,
-        "IdTag should be default constructed if no tagging id is available.");
-  }
+        tag_(llvm::reverseBits((((tag.index + 1) << 1) | 1) << 1)),
+        initial_reserved_ids_(initial_reserved_ids) {}
 
-  auto Apply(int32_t index) const -> int32_t {
+  auto Apply(int32_t index) const -> IdT {
     CARBON_DCHECK(index >= 0, "{0}", index);
     if (index < initial_reserved_ids_) {
-      return index;
+      return IdT(index);
     }
-    // TODO: Assert that id_tag_ doesn't have the second highest bit set.
-    auto tagged_index = index ^ id_tag_;
+    // TODO: Assert that tag_ doesn't have the second highest bit set.
+    auto tagged_index = index ^ tag_;
     CARBON_DCHECK(tagged_index >= 0, "{0}", tagged_index);
-    return tagged_index;
+    return IdT(tagged_index);
   }
 
-  auto Remove(int32_t tagged_index) const -> int32_t {
-    CARBON_DCHECK(tagged_index >= 0, "{0}", tagged_index);
-    if (!HasTag(tagged_index)) {
-      CARBON_DCHECK(tagged_index < initial_reserved_ids_,
+  auto Remove(IdT id) const -> int32_t {
+    CARBON_DCHECK(id.index >= 0, "{0}", id);
+    if (!HasTag(id.index)) {
+      CARBON_DCHECK(id.index < initial_reserved_ids_,
                     "This untagged index is outside the initial reserved ids "
                     "and should have been tagged.");
-      return tagged_index;
+      return id.index;
     }
-    auto index = tagged_index ^ id_tag_;
-    CARBON_DCHECK(index >= initial_reserved_ids_,
+    auto untagged_index = id.index ^ tag_;
+    CARBON_DCHECK(untagged_index >= initial_reserved_ids_,
                   "When removing tagging bits, found an index that "
                   "shouldn't've been tagged in the first place.");
-    return index;
+    return untagged_index;
   }
 
   // Gets the value unique to this IdTag instance that is added to indices in
   // Apply, and removed in Remove.
-  auto GetContainerTag() const -> int32_t {
-    return (llvm::reverseBits(id_tag_) >> 2) - 1;
+  auto GetContainerTag() const -> TagIdType {
+    if constexpr (IdTagIsUntagged<IdTag>) {
+      return TagIdType();
+    } else {
+      return TagIdType((llvm::reverseBits(tag_) >> 2) - 1);
+    }
   }
 
   // Returns whether `tagged_index` has an IdTag applied to it, from this IdTag
@@ -73,56 +108,60 @@ struct IdTag {
     return (llvm::reverseBits(2) & tagged_index) != 0;
   }
 
-  template <class TagT>
   struct TagAndIndex {
-    int32_t tag;
+    TagIdType tag;
     int32_t index;
   };
 
-  template <typename TagT>
-  static auto DecomposeWithBestEffort(int32_t tagged_index)
-      -> TagAndIndex<TagT> {
-    if (tagged_index < 0) {
-      // TODO: This should return TagT::None, but we need a fallback TagT other
-      // than `int32_t`.
-      return {TagT{-1}, tagged_index};
-    }
-    if (!HasTag(tagged_index)) {
-      // TODO: This should return TagT::None, but we need a fallback TagT other
-      // than `int32_t`.
-      return {TagT{-1}, tagged_index};
-    }
-    int length = 0;
-    int location = 0;
-    for (int i = 0; i != 32; ++i) {
-      int current_run = 0;
-      int location_of_current_run = i;
-      while (i != 32 && (tagged_index & (1 << i)) == 0) {
-        ++current_run;
-        ++i;
+  static auto DecomposeWithBestEffort(IdT id) -> TagAndIndex {
+    if constexpr (IdTagIsUntagged<IdTag>) {
+      return {TagIdType(), id.index};
+    } else {
+      if (!id.has_value()) {
+        return {TagIdType::None, id.index};
       }
-      if (current_run != 0) {
-        --i;
+      if (!HasTag(id.index)) {
+        return {TagIdType::None, id.index};
       }
-      if (current_run > length) {
-        length = current_run;
-        location = location_of_current_run;
+      int length = 0;
+      int location = 0;
+      for (int i = 0; i != 32; ++i) {
+        int current_run = 0;
+        int location_of_current_run = i;
+        while (i != 32 && (id.index & (1 << i)) == 0) {
+          ++current_run;
+          ++i;
+        }
+        if (current_run != 0) {
+          --i;
+        }
+        if (current_run > length) {
+          length = current_run;
+          location = location_of_current_run;
+        }
       }
+      if (length < 8) {
+        return {TagIdType::None, id.index};
+      }
+      auto index_mask = llvm::maskTrailingOnes<uint32_t>(location);
+      auto tag = (llvm::reverseBits(id.index & ~index_mask) >> 2) - 1;
+      auto index = id.index & index_mask;
+      return {.tag = TagIdType(static_cast<int32_t>(tag)),
+              .index = static_cast<int32_t>(index)};
     }
-    if (length < 8) {
-      // TODO: This should return TagT::None, but we need a fallback TagT other
-      // than `int32_t`.
-      return {TagT{-1}, tagged_index};
-    }
-    auto index_mask = llvm::maskTrailingOnes<uint32_t>(location);
-    auto tag = (llvm::reverseBits(tagged_index & ~index_mask) >> 2) - 1;
-    auto index = tagged_index & index_mask;
-    return {.tag = TagT{static_cast<int32_t>(tag)},
-            .index = static_cast<int32_t>(index)};
+  }
+
+  // Converts an IdTag to be used for a different ID type. This is only valid
+  // when the id indices are interchangeable, as they will have the same tag and
+  // the same reserved ids.
+  template <typename OtherIdT>
+    requires(!IdTagIsUntagged<IdTag>)
+  auto ToEquivalentIdType() -> IdTag<OtherIdT, Tag<TagIdType>> {
+    return {GetContainerTag(), initial_reserved_ids_};
   }
 
  private:
-  int32_t id_tag_ = 0;
+  int32_t tag_ = 0;
   int32_t initial_reserved_ids_ = std::numeric_limits<int32_t>::max();
 };
 

+ 6 - 3
toolchain/base/relational_value_store.h

@@ -35,6 +35,8 @@ template <typename RelatedStoreT, typename IdT, typename ValueT>
 class RelationalValueStore {
  public:
   using RelatedIdType = RelatedStoreT::IdType;
+  using RelatedIdTagType = RelatedStoreT::IdTagType;
+  using RelatedTagIdType = RelatedIdTagType::TagIdType;
   using ValueType = ValueStoreTypes<ValueT>::ValueType;
   using ConstRefType = ValueStoreTypes<ValueT>::ConstRefType;
 
@@ -50,7 +52,7 @@ class RelationalValueStore {
     CARBON_CHECK(!opt.has_value(),
                  "Add with `related_id` that was already added to the store");
     opt.emplace(std::move(value));
-    return IdT(related_store_->GetIdTag().Apply(related_index));
+    return IdT(related_store_->GetIdTag().Apply(related_index).index);
   }
 
   // Returns the ID of a value in the store if the `related_id` was previously
@@ -64,7 +66,7 @@ class RelationalValueStore {
     if (!opt.has_value()) {
       return IdT::None;
     }
-    return IdT(related_store_->GetIdTag().Apply(related_index));
+    return IdT(related_store_->GetIdTag().Apply(related_index).index);
   }
 
   // Returns a value for an ID.
@@ -73,7 +75,8 @@ class RelationalValueStore {
   }
 
  private:
-  ValueStore<RelatedIdType, std::optional<ValueType>> values_;
+  ValueStore<RelatedIdType, std::optional<ValueType>, Tag<RelatedTagIdType>>
+      values_;
   const RelatedStoreT* related_store_;
 };
 

+ 26 - 16
toolchain/base/value_store.h

@@ -35,13 +35,14 @@ class ValueStoreNotPrintable {};
 
 // A simple wrapper for accumulating values, providing IDs to later retrieve the
 // value. This does not do deduplication.
-template <typename IdT, typename ValueT>
+template <typename IdT, typename ValueT, typename TagIdT = Untagged>
 class ValueStore
     : public std::conditional<std::is_base_of_v<Printable<ValueT>, ValueT>,
-                              Yaml::Printable<ValueStore<IdT, ValueT>>,
+                              Yaml::Printable<ValueStore<IdT, ValueT, TagIdT>>,
                               Internal::ValueStoreNotPrintable> {
  public:
   using IdType = IdT;
+  using IdTagType = IdTag<IdT, TagIdT>;
   using ValueType = ValueStoreTypes<ValueT>::ValueType;
   using RefType = ValueStoreTypes<ValueT>::RefType;
   using ConstRefType = ValueStoreTypes<ValueT>::ConstRefType;
@@ -75,11 +76,21 @@ class ValueStore
     FlattenedRangeType flattened_range_;
   };
 
-  ValueStore() = default;
-  explicit ValueStore(IdTag tag) : tag_(tag) {}
-  template <typename Id>
-  explicit ValueStore(Id id, int32_t initial_reserved_ids = 0)
-      : tag_(id.index, initial_reserved_ids) {}
+  // Default constructor, only valid when the IdTag's tag type is Untagged.
+  ValueStore()
+    requires(IdTagIsUntagged<IdTagType>)
+  = default;
+
+  // Construct a ValueStore sharing the IdTag from another ValueStore. Useful
+  // for when two ValueStores are sharing the same ID types.
+  explicit ValueStore(IdTagType tag)
+    requires(!IdTagIsUntagged<IdTagType>)
+      : tag_(tag) {}
+
+  // Construct a ValueStore with a given tag and set of untagged (reserved) ids.
+  explicit ValueStore(IdTagType::TagIdType id, int32_t initial_reserved_ids = 0)
+    requires(!IdTagIsUntagged<IdTagType>)
+      : tag_(id, initial_reserved_ids) {}
 
   // Stores the value and returns an ID to reference it.
   auto Add(ValueType value) -> IdType {
@@ -88,7 +99,7 @@ class ValueStore
     // tracking down issues easier.
     CARBON_DCHECK(size_ < std::numeric_limits<int32_t>::max(), "Id overflow");
 
-    IdType id(tag_.Apply(size_));
+    IdType id = tag_.Apply(size_);
     auto [chunk_index, pos] = RawIndexToChunkIndices(size_);
     ++size_;
 
@@ -123,7 +134,7 @@ class ValueStore
                       ConstRefType default_value [[clang::lifetimebound]]) const
       -> ConstRefType {
     CARBON_DCHECK(id.index >= 0, "{0}", id);
-    auto index = tag_.Remove(id.index);
+    auto index = tag_.Remove(id);
     if (index >= size_) {
       return default_value;
     }
@@ -192,7 +203,7 @@ class ValueStore
   // Makes an iterable range over references to all values in the ValueStore.
   auto values() [[clang::lifetimebound]] -> auto {
     return llvm::map_range(llvm::seq(size_), [&](int32_t i) -> RefType {
-      return Get(IdType(tag_.Apply(i)));
+      return Get(tag_.Apply(i));
     });
   }
   auto values() const [[clang::lifetimebound]] -> Range { return Range(*this); }
@@ -211,7 +222,7 @@ class ValueStore
     // `mapped_iterator` incorrectly infers the pointer type for `PointerProxy`.
     // NOLINTNEXTLINE(readability-const-return-type)
     auto index_to_id = [&](int32_t i) -> const std::pair<IdType, ConstRefType> {
-      IdType id(tag_.Apply(i));
+      IdType id = tag_.Apply(i);
       return std::pair<IdType, ConstRefType>(id, Get(id));
     };
     // Because indices into `ValueStore` are all sequential values from 0, we
@@ -219,10 +230,10 @@ class ValueStore
     return llvm::map_range(llvm::seq(size_), index_to_id);
   }
 
-  auto GetIdTag() const -> IdTag { return tag_; }
+  auto GetIdTag() const -> IdTagType { return tag_; }
   auto GetRawIndex(IdT id) const -> int32_t {
     CARBON_DCHECK(id.index >= 0, "{0}", index);
-    auto index = tag_.Remove(id.index);
+    auto index = tag_.Remove(id);
 #ifndef NDEBUG
     if (index >= size_) {
       // Attempt to decompose id.index to include extra detail in the check
@@ -230,8 +241,7 @@ class ValueStore
       //
       // TODO: Teach ValueStore the type of the tag id with a template, then we
       // can print it with proper formatting instead of just as an integer.
-      auto [id_tag, id_untagged_index] =
-          IdTag::DecomposeWithBestEffort<int32_t>(id.index);
+      auto [id_tag, id_untagged_index] = IdTagType::DecomposeWithBestEffort(id);
       CARBON_DCHECK(
           index < size_,
           "Untagged index was outside of container range. Tagged index {0}. "
@@ -390,7 +400,7 @@ class ValueStore
   // fits in an `int32_t`, which is checked in non-optimized builds in Add().
   int32_t size_ = 0;
 
-  IdTag tag_;
+  IdTagType tag_;
 
   // Storage for the `ValueType` objects, indexed by the id. We use a vector of
   // chunks of `ValueType` instead of just a vector of `ValueType` so that

+ 4 - 6
toolchain/check/check_unit.cpp

@@ -169,9 +169,8 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
 
 auto CheckUnit::CollectDirectImports(
     llvm::SmallVector<SemIR::ImportIR>& results,
-    FixedSizeValueStore<SemIR::CheckIRId, int>& ir_to_result_index,
-    SemIR::InstId import_decl_id, const PackageImports& imports, bool is_local)
-    -> void {
+    CheckIRIdToIntStore& ir_to_result_index, SemIR::InstId import_decl_id,
+    const PackageImports& imports, bool is_local) -> void {
   for (const auto& import : imports.imports) {
     const auto& direct_ir = *import.unit_info->unit->sem_ir;
     auto& index = ir_to_result_index.Get(direct_ir.check_ir_id());
@@ -198,9 +197,8 @@ auto CheckUnit::CollectTransitiveImports(SemIR::InstId import_decl_id,
   // Track whether an IR was imported in full, including `export import`. This
   // distinguishes from IRs that are indirectly added without all names being
   // exported to this IR.
-  auto ir_to_result_index =
-      FixedSizeValueStore<SemIR::CheckIRId, int>::MakeWithExplicitSize(
-          IdTag(), unit_and_imports_->unit->total_ir_count, -1);
+  auto ir_to_result_index = CheckIRIdToIntStore::MakeWithExplicitSize(
+      unit_and_imports_->unit->total_ir_count, -1);
 
   // First add direct imports. This means that if an entity is imported both
   // directly and indirectly, the import path will reflect the direct import.

+ 7 - 5
toolchain/check/check_unit.h

@@ -134,15 +134,17 @@ class CheckUnit {
   auto Run() -> void;
 
  private:
+  using CheckIRIdToIntStore = FixedSizeValueStore<SemIR::CheckIRId, int>;
+
   // Add imports to the root block.
   auto InitPackageScopeAndImports() -> void;
 
   // Collects direct imports, for CollectTransitiveImports.
-  auto CollectDirectImports(
-      llvm::SmallVector<SemIR::ImportIR>& results,
-      FixedSizeValueStore<SemIR::CheckIRId, int>& ir_to_result_index,
-      SemIR::InstId import_decl_id, const PackageImports& imports,
-      bool is_local) -> void;
+  auto CollectDirectImports(llvm::SmallVector<SemIR::ImportIR>& results,
+                            CheckIRIdToIntStore& ir_to_result_index,
+                            SemIR::InstId import_decl_id,
+                            const PackageImports& imports, bool is_local)
+      -> void;
 
   // Collects transitive imports, handling deduplication. These will be unified
   // between local_imports and api_imports.

+ 2 - 3
toolchain/check/context.cpp

@@ -32,9 +32,8 @@ Context::Context(DiagnosticEmitterBase* emitter,
       scope_stack_(sem_ir_),
       deferred_definition_worklist_(vlog_stream),
       vtable_stack_("vtable_stack_", *sem_ir, vlog_stream),
-      check_ir_map_(FixedSizeValueStore<SemIR::CheckIRId, SemIR::ImportIRId>::
-                        MakeWithExplicitSize(IdTag(), total_ir_count_,
-                                             SemIR::ImportIRId::None)),
+      check_ir_map_(CheckIRToImpportIRStore::MakeWithExplicitSize(
+          total_ir_count_, SemIR::ImportIRId::None)),
       global_init_(this),
       region_stack_([this](SemIR::LocId loc_id, std::string label) {
         TODO(loc_id, label);

+ 4 - 5
toolchain/check/context.h

@@ -158,10 +158,9 @@ class Context {
 
   auto exports() -> llvm::SmallVector<SemIR::InstId>& { return exports_; }
 
-  auto check_ir_map()
-      -> FixedSizeValueStore<SemIR::CheckIRId, SemIR::ImportIRId>& {
-    return check_ir_map_;
-  }
+  using CheckIRToImpportIRStore =
+      FixedSizeValueStore<SemIR::CheckIRId, SemIR::ImportIRId>;
+  auto check_ir_map() -> CheckIRToImpportIRStore& { return check_ir_map_; }
 
   auto import_ir_constant_values()
       -> llvm::SmallVector<SemIR::ConstantValueStore, 0>& {
@@ -445,7 +444,7 @@ class Context {
   llvm::SmallVector<SemIR::InstId> exports_;
 
   // Maps CheckIRId to ImportIRId.
-  FixedSizeValueStore<SemIR::CheckIRId, SemIR::ImportIRId> check_ir_map_;
+  CheckIRToImpportIRStore check_ir_map_;
 
   // Per-import constant values. These refer to the main IR and mainly serve as
   // a lookup table for quick access.

+ 67 - 67
toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon

@@ -378,18 +378,18 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:       value_repr:      {kind: pointer, type: type(inst6000002C)}
 // CHECK:STDOUT:     'type(inst6000003D)':
 // CHECK:STDOUT:       value_repr:      {kind: none, type: type(inst60000025)}
-// CHECK:STDOUT:     'type(symbolic_constant3)':
-// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constant3)}
-// CHECK:STDOUT:     'type(symbolic_constantF)':
-// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constantF)}
-// CHECK:STDOUT:     'type(symbolic_constant9)':
-// CHECK:STDOUT:       value_repr:      {kind: pointer, type: type(symbolic_constantF)}
+// CHECK:STDOUT:     'type(symbolic_constant60000003)':
+// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constant60000003)}
+// CHECK:STDOUT:     'type(symbolic_constant6000000F)':
+// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constant6000000F)}
+// CHECK:STDOUT:     'type(symbolic_constant60000009)':
+// CHECK:STDOUT:       value_repr:      {kind: pointer, type: type(symbolic_constant6000000F)}
 // CHECK:STDOUT:     'type(inst(WitnessType))':
 // CHECK:STDOUT:       value_repr:      {kind: copy, type: type(inst(WitnessType))}
-// CHECK:STDOUT:     'type(symbolic_constant4)':
-// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constant4)}
-// CHECK:STDOUT:     'type(symbolic_constantA)':
-// CHECK:STDOUT:       value_repr:      {kind: pointer, type: type(symbolic_constantF)}
+// CHECK:STDOUT:     'type(symbolic_constant60000004)':
+// CHECK:STDOUT:       value_repr:      {kind: copy, type: type(symbolic_constant60000004)}
+// CHECK:STDOUT:     'type(symbolic_constant6000000A)':
+// CHECK:STDOUT:       value_repr:      {kind: pointer, type: type(symbolic_constant6000000F)}
 // CHECK:STDOUT:     'type(inst(InstType))':
 // CHECK:STDOUT:       value_repr:      {kind: none, type: type(inst60000025)}
 // CHECK:STDOUT:     'type(inst6000004F)':
@@ -420,11 +420,11 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:     inst6000001B:    {kind: PointerType, arg0: inst6000001A, type: type(TypeType)}
 // CHECK:STDOUT:     inst6000001C:    {kind: PointerType, arg0: inst60000016, type: type(TypeType)}
 // CHECK:STDOUT:     inst6000001D:    {kind: PointerType, arg0: inst60000017, type: type(TypeType)}
-// CHECK:STDOUT:     inst6000001E:    {kind: ValueBinding, arg0: entity_name60000002, arg1: inst60000038, type: type(symbolic_constant4)}
+// CHECK:STDOUT:     inst6000001E:    {kind: ValueBinding, arg0: entity_name60000002, arg1: inst60000038, type: type(symbolic_constant60000004)}
 // CHECK:STDOUT:     inst6000001F:    {kind: PatternType, arg0: inst6000001C, type: type(TypeType)}
-// CHECK:STDOUT:     inst60000020:    {kind: ValueBindingPattern, arg0: entity_name60000002, type: type(symbolic_constant6)}
+// CHECK:STDOUT:     inst60000020:    {kind: ValueBindingPattern, arg0: entity_name60000002, type: type(symbolic_constant60000006)}
 // CHECK:STDOUT:     inst60000021:    {kind: PatternType, arg0: inst6000001D, type: type(TypeType)}
-// CHECK:STDOUT:     inst60000022:    {kind: ValueParamPattern, arg0: inst60000020, arg1: call_param0, type: type(symbolic_constant6)}
+// CHECK:STDOUT:     inst60000022:    {kind: ValueParamPattern, arg0: inst60000020, arg1: call_param0, type: type(symbolic_constant60000006)}
 // CHECK:STDOUT:     inst60000023:    {kind: NameRef, arg0: name1, arg1: inst60000015, type: type(TypeType)}
 // CHECK:STDOUT:     inst60000024:    {kind: PointerType, arg0: inst60000023, type: type(TypeType)}
 // CHECK:STDOUT:     inst60000025:    {kind: TupleType, arg0: inst_block_empty, type: type(TypeType)}
@@ -443,13 +443,13 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:     inst60000032:    {kind: InitForm, arg0: inst6000002E, arg1: call_param1, type: type(inst(FormType))}
 // CHECK:STDOUT:     inst60000033:    {kind: InitForm, arg0: inst60000030, arg1: call_param1, type: type(inst(FormType))}
 // CHECK:STDOUT:     inst60000034:    {kind: PatternType, arg0: inst6000002E, type: type(TypeType)}
-// CHECK:STDOUT:     inst60000035:    {kind: ReturnSlotPattern, arg0: inst6000002F, type: type(symbolic_constantE)}
+// CHECK:STDOUT:     inst60000035:    {kind: ReturnSlotPattern, arg0: inst6000002F, type: type(symbolic_constant6000000E)}
 // CHECK:STDOUT:     inst60000036:    {kind: PatternType, arg0: inst60000030, type: type(TypeType)}
-// CHECK:STDOUT:     inst60000037:    {kind: OutParamPattern, arg0: inst60000035, arg1: call_param1, type: type(symbolic_constantE)}
-// CHECK:STDOUT:     inst60000038:    {kind: ValueParam, arg0: call_param0, arg1: name2, type: type(symbolic_constant4)}
+// CHECK:STDOUT:     inst60000037:    {kind: OutParamPattern, arg0: inst60000035, arg1: call_param1, type: type(symbolic_constant6000000E)}
+// CHECK:STDOUT:     inst60000038:    {kind: ValueParam, arg0: call_param0, arg1: name2, type: type(symbolic_constant60000004)}
 // CHECK:STDOUT:     inst60000039:    {kind: SpliceBlock, arg0: inst_block60000006, arg1: inst6000001B, type: type(TypeType)}
-// CHECK:STDOUT:     inst6000003A:    {kind: OutParam, arg0: call_param1, arg1: name(ReturnSlot), type: type(symbolic_constantA)}
-// CHECK:STDOUT:     inst6000003B:    {kind: ReturnSlot, arg0: inst6000002E, arg1: inst6000003A, type: type(symbolic_constantA)}
+// CHECK:STDOUT:     inst6000003A:    {kind: OutParam, arg0: call_param1, arg1: name(ReturnSlot), type: type(symbolic_constant6000000A)}
+// CHECK:STDOUT:     inst6000003B:    {kind: ReturnSlot, arg0: inst6000002E, arg1: inst6000003A, type: type(symbolic_constant6000000A)}
 // CHECK:STDOUT:     inst6000003C:    {kind: FunctionDecl, arg0: function60000000, arg1: inst_block60000013, type: type(inst6000003D)}
 // CHECK:STDOUT:     inst6000003D:    {kind: FunctionType, arg0: function60000000, arg1: specific<none>, type: type(TypeType)}
 // CHECK:STDOUT:     inst6000003E:    {kind: StructValue, arg0: inst_block_empty, type: type(inst6000003D)}
@@ -461,11 +461,11 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:     inst60000044:    {kind: RequireCompleteType, arg0: inst6000001C, type: type(inst(WitnessType))}
 // CHECK:STDOUT:     inst60000045:    {kind: RequireCompleteType, arg0: inst6000001D, type: type(inst(WitnessType))}
 // CHECK:STDOUT:     inst60000046:    {kind: RequireCompleteType, arg0: inst6000002E, type: type(inst(WitnessType))}
-// CHECK:STDOUT:     inst60000047:    {kind: NameRef, arg0: name2, arg1: inst6000001E, type: type(symbolic_constant4)}
+// CHECK:STDOUT:     inst60000047:    {kind: NameRef, arg0: name2, arg1: inst6000001E, type: type(symbolic_constant60000004)}
 // CHECK:STDOUT:     inst60000048:    {kind: TupleLiteral, arg0: inst_block_empty, type: type(inst60000025)}
-// CHECK:STDOUT:     inst60000049:    {kind: TupleLiteral, arg0: inst_block60000019, type: type(symbolic_constantA)}
+// CHECK:STDOUT:     inst60000049:    {kind: TupleLiteral, arg0: inst_block60000019, type: type(symbolic_constant6000000A)}
 // CHECK:STDOUT:     inst6000004A:    {kind: RequireCompleteType, arg0: inst6000002E, type: type(inst(WitnessType))}
-// CHECK:STDOUT:     inst6000004B:    {kind: TupleAccess, arg0: inst6000003A, arg1: element0, type: type(symbolic_constant4)}
+// CHECK:STDOUT:     inst6000004B:    {kind: TupleAccess, arg0: inst6000003A, arg1: element0, type: type(symbolic_constant60000004)}
 // CHECK:STDOUT:     inst6000004C:    {kind: RequireCompleteType, arg0: inst6000001C, type: type(inst(WitnessType))}
 // CHECK:STDOUT:     inst6000004D:    {kind: ImportRefLoaded, arg0: import_ir_inst0, arg1: entity_name60000003, type: type(TypeType)}
 // CHECK:STDOUT:     inst6000004E:    {kind: InterfaceDecl, arg0: interface60000000, arg1: inst_block_empty, type: type(TypeType)}
@@ -757,60 +757,60 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:     inst6000016C:    {kind: SpecificImplFunction, arg0: inst60000168, arg1: specific60000019, type: type(inst(SpecificFunctionType))}
 // CHECK:STDOUT:     inst6000016D:    {kind: BoundMethod, arg0: inst60000047, arg1: inst6000016A, type: type(inst(BoundMethodType))}
 // CHECK:STDOUT:     inst6000016E:    {kind: RequireCompleteType, arg0: inst6000001C, type: type(inst(WitnessType))}
-// CHECK:STDOUT:     inst6000016F:    {kind: Call, arg0: inst6000016D, arg1: inst_block6000007F, type: type(symbolic_constant4)}
-// CHECK:STDOUT:     inst60000170:    {kind: InitializeFrom, arg0: inst6000016F, arg1: inst6000004B, type: type(symbolic_constant4)}
+// CHECK:STDOUT:     inst6000016F:    {kind: Call, arg0: inst6000016D, arg1: inst_block6000007F, type: type(symbolic_constant60000004)}
+// CHECK:STDOUT:     inst60000170:    {kind: InitializeFrom, arg0: inst6000016F, arg1: inst6000004B, type: type(symbolic_constant60000004)}
 // CHECK:STDOUT:     inst60000171:    {kind: TupleAccess, arg0: inst6000003A, arg1: element1, type: type(inst60000025)}
 // CHECK:STDOUT:     inst60000172:    {kind: TupleInit, arg0: inst_block_empty, arg1: inst60000171, type: type(inst60000025)}
 // CHECK:STDOUT:     inst60000173:    {kind: Converted, arg0: inst60000048, arg1: inst60000172, type: type(inst60000025)}
-// CHECK:STDOUT:     inst60000174:    {kind: TupleInit, arg0: inst_block60000080, arg1: inst6000003A, type: type(symbolic_constantA)}
-// CHECK:STDOUT:     inst60000175:    {kind: Converted, arg0: inst60000049, arg1: inst60000174, type: type(symbolic_constantA)}
+// CHECK:STDOUT:     inst60000174:    {kind: TupleInit, arg0: inst_block60000080, arg1: inst6000003A, type: type(symbolic_constant6000000A)}
+// CHECK:STDOUT:     inst60000175:    {kind: Converted, arg0: inst60000049, arg1: inst60000174, type: type(symbolic_constant6000000A)}
 // CHECK:STDOUT:     inst60000176:    {kind: ReturnExpr, arg0: inst60000175, arg1: inst6000003A}
 // CHECK:STDOUT:   constant_values:
 // CHECK:STDOUT:     values:
 // CHECK:STDOUT:       instF:           concrete_constant(instF)
 // CHECK:STDOUT:       inst60000011:    concrete_constant(inst60000011)
 // CHECK:STDOUT:       inst60000012:    concrete_constant(inst60000012)
-// CHECK:STDOUT:       inst60000013:    symbolic_constant0
-// CHECK:STDOUT:       inst60000014:    symbolic_constant0
-// CHECK:STDOUT:       inst60000015:    symbolic_constant2
-// CHECK:STDOUT:       inst60000016:    symbolic_constant1
-// CHECK:STDOUT:       inst60000017:    symbolic_constant2
+// CHECK:STDOUT:       inst60000013:    symbolic_constant60000000
+// CHECK:STDOUT:       inst60000014:    symbolic_constant60000000
+// CHECK:STDOUT:       inst60000015:    symbolic_constant60000002
+// CHECK:STDOUT:       inst60000016:    symbolic_constant60000001
+// CHECK:STDOUT:       inst60000017:    symbolic_constant60000002
 // CHECK:STDOUT:       inst60000018:    concrete_constant(inst60000018)
 // CHECK:STDOUT:       inst60000019:    concrete_constant(inst60000019)
-// CHECK:STDOUT:       inst6000001A:    symbolic_constant2
-// CHECK:STDOUT:       inst6000001B:    symbolic_constant4
-// CHECK:STDOUT:       inst6000001C:    symbolic_constant3
-// CHECK:STDOUT:       inst6000001D:    symbolic_constant4
-// CHECK:STDOUT:       inst6000001F:    symbolic_constant5
+// CHECK:STDOUT:       inst6000001A:    symbolic_constant60000002
+// CHECK:STDOUT:       inst6000001B:    symbolic_constant60000004
+// CHECK:STDOUT:       inst6000001C:    symbolic_constant60000003
+// CHECK:STDOUT:       inst6000001D:    symbolic_constant60000004
+// CHECK:STDOUT:       inst6000001F:    symbolic_constant60000005
 // CHECK:STDOUT:       inst60000020:    concrete_constant(inst60000020)
-// CHECK:STDOUT:       inst60000021:    symbolic_constant6
+// CHECK:STDOUT:       inst60000021:    symbolic_constant60000006
 // CHECK:STDOUT:       inst60000022:    concrete_constant(inst60000022)
-// CHECK:STDOUT:       inst60000023:    symbolic_constant2
-// CHECK:STDOUT:       inst60000024:    symbolic_constant4
+// CHECK:STDOUT:       inst60000023:    symbolic_constant60000002
+// CHECK:STDOUT:       inst60000024:    symbolic_constant60000004
 // CHECK:STDOUT:       inst60000025:    concrete_constant(inst60000025)
 // CHECK:STDOUT:       inst60000026:    concrete_constant(inst60000027)
 // CHECK:STDOUT:       inst60000027:    concrete_constant(inst60000027)
 // CHECK:STDOUT:       inst60000028:    concrete_constant(inst60000028)
-// CHECK:STDOUT:       inst60000029:    symbolic_constant8
-// CHECK:STDOUT:       inst6000002A:    symbolic_constant7
-// CHECK:STDOUT:       inst6000002B:    symbolic_constant8
+// CHECK:STDOUT:       inst60000029:    symbolic_constant60000008
+// CHECK:STDOUT:       inst6000002A:    symbolic_constant60000007
+// CHECK:STDOUT:       inst6000002B:    symbolic_constant60000008
 // CHECK:STDOUT:       inst6000002C:    concrete_constant(inst6000002C)
 // CHECK:STDOUT:       inst6000002D:    concrete_constant(inst60000025)
-// CHECK:STDOUT:       inst6000002E:    symbolic_constant9
-// CHECK:STDOUT:       inst6000002F:    symbolic_constantA
-// CHECK:STDOUT:       inst60000030:    symbolic_constantA
-// CHECK:STDOUT:       inst60000031:    symbolic_constantC
-// CHECK:STDOUT:       inst60000032:    symbolic_constantB
-// CHECK:STDOUT:       inst60000033:    symbolic_constantC
-// CHECK:STDOUT:       inst60000034:    symbolic_constantD
+// CHECK:STDOUT:       inst6000002E:    symbolic_constant60000009
+// CHECK:STDOUT:       inst6000002F:    symbolic_constant6000000A
+// CHECK:STDOUT:       inst60000030:    symbolic_constant6000000A
+// CHECK:STDOUT:       inst60000031:    symbolic_constant6000000C
+// CHECK:STDOUT:       inst60000032:    symbolic_constant6000000B
+// CHECK:STDOUT:       inst60000033:    symbolic_constant6000000C
+// CHECK:STDOUT:       inst60000034:    symbolic_constant6000000D
 // CHECK:STDOUT:       inst60000035:    concrete_constant(inst60000035)
-// CHECK:STDOUT:       inst60000036:    symbolic_constantE
+// CHECK:STDOUT:       inst60000036:    symbolic_constant6000000E
 // CHECK:STDOUT:       inst60000037:    concrete_constant(inst60000037)
-// CHECK:STDOUT:       inst60000039:    symbolic_constant4
+// CHECK:STDOUT:       inst60000039:    symbolic_constant60000004
 // CHECK:STDOUT:       inst6000003C:    concrete_constant(inst6000003E)
 // CHECK:STDOUT:       inst6000003D:    concrete_constant(inst6000003D)
 // CHECK:STDOUT:       inst6000003E:    concrete_constant(inst6000003E)
-// CHECK:STDOUT:       inst6000003F:    symbolic_constantF
+// CHECK:STDOUT:       inst6000003F:    symbolic_constant6000000F
 // CHECK:STDOUT:       inst60000040:    symbolic_constant60000011
 // CHECK:STDOUT:       inst60000041:    symbolic_constant60000010
 // CHECK:STDOUT:       inst60000042:    symbolic_constant60000011
@@ -1112,22 +1112,22 @@ fn Foo[T:! type](p: T*) -> (T*, ()) {
 // CHECK:STDOUT:       inst60000172:    concrete_constant(inst60000027)
 // CHECK:STDOUT:       inst60000173:    concrete_constant(inst60000027)
 // CHECK:STDOUT:     symbolic_constants:
-// CHECK:STDOUT:       symbolic_constant0: {inst: inst60000014, generic: generic<none>, index: generic_inst<none>, kind: self}
-// CHECK:STDOUT:       symbolic_constant1: {inst: inst60000016, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constant2: {inst: inst60000016, generic: generic60000000, index: generic_inst_in_decl0, kind: checked}
-// CHECK:STDOUT:       symbolic_constant3: {inst: inst6000001C, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constant4: {inst: inst6000001C, generic: generic60000000, index: generic_inst_in_decl1, kind: checked}
-// CHECK:STDOUT:       symbolic_constant5: {inst: inst6000001F, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constant6: {inst: inst6000001F, generic: generic60000000, index: generic_inst_in_decl2, kind: checked}
-// CHECK:STDOUT:       symbolic_constant7: {inst: inst6000002A, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constant8: {inst: inst6000002A, generic: generic60000000, index: generic_inst_in_decl3, kind: checked}
-// CHECK:STDOUT:       symbolic_constant9: {inst: inst6000002E, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constantA: {inst: inst6000002E, generic: generic60000000, index: generic_inst_in_decl4, kind: checked}
-// CHECK:STDOUT:       symbolic_constantB: {inst: inst60000032, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constantC: {inst: inst60000032, generic: generic60000000, index: generic_inst_in_decl5, kind: checked}
-// CHECK:STDOUT:       symbolic_constantD: {inst: inst60000034, generic: generic<none>, index: generic_inst<none>, kind: checked}
-// CHECK:STDOUT:       symbolic_constantE: {inst: inst60000034, generic: generic60000000, index: generic_inst_in_decl6, kind: checked}
-// CHECK:STDOUT:       symbolic_constantF: {inst: inst6000003F, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000000: {inst: inst60000014, generic: generic<none>, index: generic_inst<none>, kind: self}
+// CHECK:STDOUT:       symbolic_constant60000001: {inst: inst60000016, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000002: {inst: inst60000016, generic: generic60000000, index: generic_inst_in_decl0, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000003: {inst: inst6000001C, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000004: {inst: inst6000001C, generic: generic60000000, index: generic_inst_in_decl1, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000005: {inst: inst6000001F, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000006: {inst: inst6000001F, generic: generic60000000, index: generic_inst_in_decl2, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000007: {inst: inst6000002A, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000008: {inst: inst6000002A, generic: generic60000000, index: generic_inst_in_decl3, kind: checked}
+// CHECK:STDOUT:       symbolic_constant60000009: {inst: inst6000002E, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000A: {inst: inst6000002E, generic: generic60000000, index: generic_inst_in_decl4, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000B: {inst: inst60000032, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000C: {inst: inst60000032, generic: generic60000000, index: generic_inst_in_decl5, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000D: {inst: inst60000034, generic: generic<none>, index: generic_inst<none>, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000E: {inst: inst60000034, generic: generic60000000, index: generic_inst_in_decl6, kind: checked}
+// CHECK:STDOUT:       symbolic_constant6000000F: {inst: inst6000003F, generic: generic<none>, index: generic_inst<none>, kind: checked}
 // CHECK:STDOUT:       symbolic_constant60000010: {inst: inst60000041, generic: generic<none>, index: generic_inst<none>, kind: checked}
 // CHECK:STDOUT:       symbolic_constant60000011: {inst: inst60000041, generic: generic60000000, index: generic_inst_in_def0, kind: checked}
 // CHECK:STDOUT:       symbolic_constant60000012: {inst: inst60000044, generic: generic<none>, index: generic_inst<none>, kind: checked}

+ 4 - 4
toolchain/driver/compile_subcommand.cpp

@@ -616,8 +616,8 @@ class MultiUnitCache {
 
   auto include_in_dumps() -> const IncludeInDumpsStore& {
     if (!include_in_dumps_) {
-      include_in_dumps_.emplace(IncludeInDumpsStore::MakeWithExplicitSize(
-          IdTag(), units_.size(), false));
+      include_in_dumps_.emplace(
+          IncludeInDumpsStore::MakeWithExplicitSize(units_.size(), false));
       for (const auto& [i, unit] : llvm::enumerate(units_)) {
         // If this is first accessed after lexing is complete, we need to apply
         // per-file includes. Otherwise, this is based only on the exclude
@@ -637,8 +637,8 @@ class MultiUnitCache {
   auto tree_and_subtrees_getters() -> const TreeAndSubtreesGettersStore& {
     if (!tree_and_subtrees_getters_) {
       tree_and_subtrees_getters_.emplace(
-          TreeAndSubtreesGettersStore::MakeWithExplicitSize(
-              IdTag(), units_.size(), nullptr));
+          TreeAndSubtreesGettersStore::MakeWithExplicitSize(units_.size(),
+                                                            nullptr));
       for (const auto& [i, unit] : llvm::enumerate(units_)) {
         if (unit->has_source()) {
           tree_and_subtrees_getters_->Set(SemIR::CheckIRId(i),

+ 1 - 1
toolchain/language_server/context.cpp

@@ -166,7 +166,7 @@ auto Context::File::SetText(Context& context, std::optional<int64_t> version,
   Check::CheckParseTreesOptions check_options;
   check_options.vlog_stream = context.vlog_stream();
   auto getters =
-      Parse::GetTreeAndSubtreesStore::MakeWithExplicitSize(IdTag(), 1, getter);
+      Parse::GetTreeAndSubtreesStore::MakeWithExplicitSize(1, getter);
 
   auto clang_invocation =
       BuildClangInvocation(consumer, fs, context.installation(),

+ 4 - 4
toolchain/lower/file_context.cpp

@@ -51,11 +51,11 @@ FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
       vlog_stream_(vlog_stream),
       functions_(LoweredFunctionStore::MakeForOverwrite(sem_ir.functions())),
       specific_functions_(sem_ir.specifics(), nullptr),
-      types_(LoweredTypeStore::MakeWithExplicitSize(sem_ir.insts().GetIdTag(),
-                                                    sem_ir.insts().size(),
-                                                    {nullptr, nullptr})),
+      types_(LoweredTypeStore::MakeWithExplicitSize(
+          sem_ir.constant_values().ConcreteStoreSize(),
+          sem_ir.constant_values().GetTypeIdTag(), {nullptr, nullptr})),
       constants_(LoweredConstantStore::MakeWithExplicitSize(
-          sem_ir.insts().GetIdTag(), sem_ir.insts().size(), nullptr)),
+          sem_ir.insts().size(), sem_ir.insts().GetIdTag(), nullptr)),
       lowered_specifics_(sem_ir.generics(),
                          llvm::SmallVector<SemIR::SpecificId>()),
       coalescer_(vlog_stream_, sem_ir.specifics()),

+ 17 - 7
toolchain/lower/file_context.h

@@ -21,7 +21,8 @@ namespace Carbon::Lower {
 class FileContext {
  public:
   using LoweredConstantStore =
-      FixedSizeValueStore<SemIR::InstId, llvm::Constant*>;
+      FixedSizeValueStore<SemIR::InstId, llvm::Constant*,
+                          Tag<SemIR::CheckIRId>>;
 
   explicit FileContext(Context& context, const SemIR::File& sem_ir,
                        const SemIR::InstNamer* inst_namer,
@@ -249,14 +250,19 @@ class FileContext {
   // Maps callables to lowered functions. SemIR treats callables as the
   // canonical form of a function, so lowering needs to do the same.
   using LoweredFunctionStore =
-      FixedSizeValueStore<SemIR::FunctionId, llvm::Function*>;
+      FixedSizeValueStore<SemIR::FunctionId, llvm::Function*,
+                          Tag<SemIR::CheckIRId>>;
   LoweredFunctionStore functions_;
 
   // Maps specific callables to lowered functions.
-  FixedSizeValueStore<SemIR::SpecificId, llvm::Function*> specific_functions_;
+  FixedSizeValueStore<SemIR::SpecificId, llvm::Function*, Tag<SemIR::CheckIRId>>
+      specific_functions_;
 
   // Provides lowered versions of types. Entries are non-symbolic types.
-  using LoweredTypeStore = FixedSizeValueStore<SemIR::TypeId, LoweredTypes>;
+  //
+  // TypeIds internally are concrete ConstantIds.
+  using LoweredTypeStore =
+      FixedSizeValueStore<SemIR::TypeId, LoweredTypes, Tag<SemIR::CheckIRId>>;
   LoweredTypeStore types_;
 
   // Maps constants to their lowered values. Indexes are the `InstId` for
@@ -269,13 +275,17 @@ class FileContext {
   // For a generic function, keep track of the specifics for which LLVM
   // function declarations were created. Those can be retrieved then from
   // `specific_functions_`.
-  FixedSizeValueStore<SemIR::GenericId, llvm::SmallVector<SemIR::SpecificId>>
+  FixedSizeValueStore<SemIR::GenericId, llvm::SmallVector<SemIR::SpecificId>,
+                      Tag<SemIR::CheckIRId>>
       lowered_specifics_;
 
   SpecificCoalescer coalescer_;
 
-  FixedSizeValueStore<SemIR::VtableId, llvm::GlobalVariable*> vtables_;
-  FixedSizeValueStore<SemIR::SpecificId, llvm::GlobalVariable*>
+  FixedSizeValueStore<SemIR::VtableId, llvm::GlobalVariable*,
+                      Tag<SemIR::CheckIRId>>
+      vtables_;
+  FixedSizeValueStore<SemIR::SpecificId, llvm::GlobalVariable*,
+                      Tag<SemIR::CheckIRId>>
       specific_vtables_;
 };
 

+ 10 - 5
toolchain/lower/specific_coalescer.h

@@ -16,9 +16,11 @@ class SpecificCoalescer {
  public:
   using LoweredSpecificsStore =
       FixedSizeValueStore<SemIR::GenericId,
-                          llvm::SmallVector<SemIR::SpecificId>>;
+                          llvm::SmallVector<SemIR::SpecificId>,
+                          Tag<SemIR::CheckIRId>>;
   using LoweredLlvmFunctionStore =
-      FixedSizeValueStore<SemIR::SpecificId, llvm::Function*>;
+      FixedSizeValueStore<SemIR::SpecificId, llvm::Function*,
+                          Tag<SemIR::CheckIRId>>;
 
   // Describes a specific function's body fingerprint.
   struct SpecificFunctionFingerprint {
@@ -128,11 +130,13 @@ class SpecificCoalescer {
 
   // For specifics that exist in lowered_specifics, a hash of their function
   // type information.
-  FixedSizeValueStore<SemIR::SpecificId, llvm::BLAKE3Result<32>>
+  FixedSizeValueStore<SemIR::SpecificId, llvm::BLAKE3Result<32>,
+                      Tag<SemIR::CheckIRId>>
       lowered_specifics_type_fingerprint_;
 
   // This is initialized and populated while lowering a specific.
-  FixedSizeValueStore<SemIR::SpecificId, SpecificFunctionFingerprint>
+  FixedSizeValueStore<SemIR::SpecificId, SpecificFunctionFingerprint,
+                      Tag<SemIR::CheckIRId>>
       lowered_specific_fingerprint_;
 
   // Equivalent specifics that have been found. For each specific, this points
@@ -142,7 +146,8 @@ class SpecificCoalescer {
   //
   // Entries are initialized to `SpecificId::None`, which defines that there is
   // no other equivalent specific to this `SpecificId`.
-  FixedSizeValueStore<SemIR::SpecificId, SemIR::SpecificId>
+  FixedSizeValueStore<SemIR::SpecificId, SemIR::SpecificId,
+                      Tag<SemIR::CheckIRId>>
       equivalent_specifics_;
 
   // Non-equivalent specifics found.

+ 1 - 1
toolchain/sem_ir/associated_constant.h

@@ -50,7 +50,7 @@ struct AssociatedConstant : public Printable<AssociatedConstant> {
 };
 
 using AssociatedConstantStore =
-    ValueStore<AssociatedConstantId, AssociatedConstant>;
+    ValueStore<AssociatedConstantId, AssociatedConstant, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 1 - 1
toolchain/sem_ir/clang_decl.h

@@ -96,7 +96,7 @@ struct ClangDecl : public Printable<ClangDecl> {
 
 // Use the AST node pointer directly when doing `Lookup` to find an ID.
 using ClangDeclStore =
-    CanonicalValueStore<ClangDeclId, ClangDeclKey, ClangDecl>;
+    CanonicalValueStore<ClangDeclId, ClangDeclKey, Tag<CheckIRId>, ClangDecl>;
 
 }  // namespace Carbon::SemIR
 

+ 1 - 1
toolchain/sem_ir/class.h

@@ -116,7 +116,7 @@ struct Class : public EntityWithParamsBase,
   auto GetObjectRepr(const File& file, SpecificId specific_id) const -> TypeId;
 };
 
-using ClassStore = ValueStore<ClassId, Class>;
+using ClassStore = ValueStore<ClassId, Class, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 29 - 5
toolchain/sem_ir/constant.h

@@ -116,12 +116,15 @@ class ConstantValueStore {
   // Constructs an unusable ConstantValueStore, only good as a placeholder (eg:
   // in C++ interop, where there's no foreign SemIR to reference)
   explicit ConstantValueStore(UnusableType /* tag */)
-      : default_(ConstantId::None), insts_(nullptr) {}
+      : default_(ConstantId::None),
+        values_(CheckIRId::None),
+        symbolic_constants_(CheckIRId::None),
+        insts_(nullptr) {}
 
   explicit ConstantValueStore(ConstantId default_value, const InstStore* insts)
       : default_(default_value),
-        values_((CARBON_CHECK(insts), insts->GetIdTag())),
-        symbolic_constants_(insts->GetIdTag()),
+        values_(insts->GetIdTag()),
+        symbolic_constants_(insts->GetIdTag().GetContainerTag()),
         insts_(insts) {}
 
   // Returns the constant value of the requested instruction, which is default_
@@ -247,6 +250,26 @@ class ConstantValueStore {
     });
   }
 
+  // The tag used in ConstantIds for concrete constants.
+  using ConcreteIdTagType = IdTag<SemIR::ConstantId, Tag<SemIR::CheckIRId>>;
+  auto GetConcreteIdTag() const -> ConcreteIdTagType {
+    return values_.GetIdTag().ToEquivalentIdType<SemIR::ConstantId>();
+  }
+  // The tag used for TypeId, which are concrete constants internally.
+  using TypeIdTagType = IdTag<SemIR::TypeId, Tag<SemIR::CheckIRId>>;
+  auto GetTypeIdTag() const -> TypeIdTagType {
+    return values_.GetIdTag().ToEquivalentIdType<SemIR::TypeId>();
+  }
+  // The tag used in ConstantIds for symbolic constants.
+  using SymbolicIdTagType =
+      IdTag<ConstantId::SymbolicId, Tag<SemIR::CheckIRId>>;
+  auto GetSymbolicIdTag() const -> SymbolicIdTagType {
+    return symbolic_constants_.GetIdTag();
+  }
+
+  // The size of the value store for concrete constant values.
+  auto ConcreteStoreSize() const -> size_t { return values_.size(); }
+
  private:
   const ConstantId default_;
 
@@ -256,14 +279,15 @@ class ConstantValueStore {
   //
   // Set inline size to 0 because these will typically be too large for the
   // stack, while this does make File smaller.
-  ValueStore<InstId, ConstantId> values_;
+  ValueStore<InstId, ConstantId, Tag<CheckIRId>> values_;
 
   // A mapping from a symbolic constant ID index to information about the
   // symbolic constant. For a concrete constant, the only information that we
   // track is the instruction ID, which is stored directly within the
   // `ConstantId`. For a symbolic constant, we also track information about
   // where the constant was used, which is stored here.
-  ValueStore<ConstantId::SymbolicId, SymbolicConstant> symbolic_constants_;
+  ValueStore<ConstantId::SymbolicId, SymbolicConstant, Tag<CheckIRId>>
+      symbolic_constants_;
 
   const InstStore* insts_;
 };

+ 2 - 2
toolchain/sem_ir/cpp_global_var.h

@@ -49,8 +49,8 @@ struct CppGlobalVar : public Printable<CppGlobalVar> {
 };
 
 // Use the name of a C++ global variable when doing `Lookup` to find an ID.
-using CppGlobalVarStore =
-    CanonicalValueStore<CppGlobalVarId, CppGlobalVarKey, CppGlobalVar>;
+using CppGlobalVarStore = CanonicalValueStore<CppGlobalVarId, CppGlobalVarKey,
+                                              Tag<CheckIRId>, CppGlobalVar>;
 
 }  // namespace Carbon::SemIR
 

+ 2 - 1
toolchain/sem_ir/cpp_overload_set.h

@@ -39,7 +39,8 @@ struct CppOverloadSet : public Printable<CppOverloadSet> {
   }
 };
 
-using CppOverloadSetStore = ValueStore<CppOverloadSetId, CppOverloadSet>;
+using CppOverloadSetStore =
+    ValueStore<CppOverloadSetId, CppOverloadSet, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 2 - 1
toolchain/sem_ir/entity_name.h

@@ -60,7 +60,8 @@ struct EntityName : public Printable<EntityName> {
 
 // Value store for EntityName. In addition to the regular ValueStore
 // functionality, this can provide optional canonical IDs for EntityNames.
-struct EntityNameStore : public ValueStore<EntityNameId, EntityName> {
+struct EntityNameStore
+    : public ValueStore<EntityNameId, EntityName, Tag<CheckIRId>> {
  public:
   using ValueStore::ValueStore;
 

+ 2 - 1
toolchain/sem_ir/facet_type_info.h

@@ -113,7 +113,8 @@ constexpr FacetTypeInfo::RewriteConstraint
     FacetTypeInfo::RewriteConstraint::None = {.lhs_id = InstId::None,
                                               .rhs_id = InstId::None};
 
-using FacetTypeInfoStore = CanonicalValueStore<FacetTypeId, FacetTypeInfo>;
+using FacetTypeInfoStore =
+    CanonicalValueStore<FacetTypeId, FacetTypeInfo, Tag<CheckIRId>>;
 
 struct IdentifiedFacetType {
   using RequiredInterface = SpecificInterface;

+ 4 - 4
toolchain/sem_ir/file.cpp

@@ -43,7 +43,7 @@ File::File(const Parse::Tree* parse_tree, CheckIRId check_ir_id,
       named_constraints_(check_ir_id),
       require_impls_(check_ir_id),
       // 1 reserved id for `RequireImplsBlockId::Empty`.
-      require_impls_blocks_(allocator_, IdTag(check_ir_id.index, 1)),
+      require_impls_blocks_(allocator_, check_ir_id, 1),
       associated_constants_(check_ir_id),
       facet_types_(check_ir_id),
       identified_facet_types_(&facet_types_),
@@ -53,7 +53,7 @@ File::File(const Parse::Tree* parse_tree, CheckIRId check_ir_id,
       specifics_(check_ir_id),
       // The `2` prevents adding a tag for the global ids
       // `ImportIRId::{ApiForImpl,Cpp}`.
-      import_irs_(IdTag(check_ir_id.index, 2)),
+      import_irs_(check_ir_id, 2),
       clang_decls_(check_ir_id),
       // The `+1` prevents adding a tag to the global `NameSpace::PackageInstId`
       // instruction. It's not a "singleton" instruction, but it's a unique
@@ -64,9 +64,9 @@ File::File(const Parse::Tree* parse_tree, CheckIRId check_ir_id,
       inst_blocks_(allocator_, check_ir_id),
       constants_(this),
       // 1 reserved id for `StructTypeFieldsId::Empty`.
-      struct_type_fields_(allocator_, IdTag(check_ir_id.index, 1)),
+      struct_type_fields_(allocator_, check_ir_id, 1),
       // 1 reserved id for `CustomLayoutId::Empty`.
-      custom_layouts_(allocator_, IdTag(check_ir_id.index, 1)),
+      custom_layouts_(allocator_, check_ir_id, 1),
       expr_regions_(check_ir_id),
       clang_source_locs_(check_ir_id) {
   // `type`, `form`, and the error type are both complete & concrete types.

+ 5 - 4
toolchain/sem_ir/file.h

@@ -63,9 +63,10 @@ struct ExprRegion {
   InstId result_id;
 };
 
-using ExprRegionStore = ValueStore<ExprRegionId, ExprRegion>;
+using ExprRegionStore = ValueStore<ExprRegionId, ExprRegion, Tag<CheckIRId>>;
 
-using CustomLayoutStore = BlockValueStore<CustomLayoutId, uint64_t>;
+using CustomLayoutStore =
+    BlockValueStore<CustomLayoutId, uint64_t, Tag<CheckIRId>>;
 
 // The semantic IR for a single file.
 class File : public Printable<File> {
@@ -264,7 +265,7 @@ class File : public Printable<File> {
   auto expr_regions() const -> const ExprRegionStore& { return expr_regions_; }
 
   using ClangSourceLocStore =
-      ValueStore<ClangSourceLocId, clang::SourceLocation>;
+      ValueStore<ClangSourceLocId, clang::SourceLocation, Tag<CheckIRId>>;
   auto clang_source_locs() -> ClangSourceLocStore& {
     return clang_source_locs_;
   }
@@ -409,7 +410,7 @@ class File : public Printable<File> {
   StructTypeFieldsStore struct_type_fields_;
 
   // Storage for custom layouts.
-  CustomLayoutStore custom_layouts_ = CustomLayoutStore(allocator_);
+  CustomLayoutStore custom_layouts_;
 
   // Descriptions of types used in this file.
   TypeStore types_ = TypeStore(this);

+ 1 - 1
toolchain/sem_ir/formatter.cpp

@@ -120,7 +120,7 @@ auto Formatter::Format() -> void {
 auto Formatter::ComputeNodeParents() -> void {
   CARBON_CHECK(!node_parents_);
   node_parents_ = NodeParentStore::MakeWithExplicitSize(
-      IdTag(), sem_ir_->parse_tree().size(), Parse::NodeId::None);
+      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);

+ 1 - 1
toolchain/sem_ir/formatter.h

@@ -407,7 +407,7 @@ class Formatter {
 
   // Indexes of chunks of output that should be included when an instruction is
   // referenced, indexed by the instruction's index.
-  FixedSizeValueStore<InstId, size_t> tentative_inst_chunks_;
+  FixedSizeValueStore<InstId, size_t, Tag<CheckIRId>> tentative_inst_chunks_;
 
   // Maps nodes to their parents. Only set when dump ranges are in use, because
   // the parents aren't used otherwise.

+ 1 - 1
toolchain/sem_ir/function.h

@@ -213,7 +213,7 @@ struct Function : public EntityWithParamsBase,
   }
 };
 
-using FunctionStore = ValueStore<FunctionId, Function>;
+using FunctionStore = ValueStore<FunctionId, Function, Tag<CheckIRId>>;
 
 class File;
 

+ 2 - 3
toolchain/sem_ir/generic.cpp

@@ -19,8 +19,7 @@ class SpecificStore::KeyContext : public TranslatingKeyContext<KeyContext> {
     friend auto operator==(const Key&, const Key&) -> bool = default;
   };
 
-  explicit KeyContext(const ValueStore<SpecificId, Specific>* specifics)
-      : specifics_(specifics) {}
+  explicit KeyContext(const ValueStore* specifics) : specifics_(specifics) {}
 
   auto TranslateKey(SpecificId id) const -> Key {
     const auto& specific = specifics_->Get(id);
@@ -28,7 +27,7 @@ class SpecificStore::KeyContext : public TranslatingKeyContext<KeyContext> {
   }
 
  private:
-  const ValueStore<SpecificId, Specific>* specifics_;
+  const ValueStore* specifics_;
 };
 
 auto SpecificStore::GetOrAdd(GenericId generic_id, InstBlockId args_id)

+ 4 - 4
toolchain/sem_ir/generic.h

@@ -53,7 +53,7 @@ struct Generic : public Printable<Generic> {
 };
 
 // Provides storage for generics.
-class GenericStore : public ValueStore<GenericId, Generic> {
+class GenericStore : public ValueStore<GenericId, Generic, Tag<CheckIRId>> {
  public:
   using ValueStore::ValueStore;
 
@@ -109,6 +109,7 @@ struct Specific : Printable<Specific> {
 class SpecificStore : public Yaml::Printable<SpecificStore> {
  public:
   using IdType = SpecificId;
+  using ValueStore = ValueStore<SpecificId, Specific, Tag<CheckIRId>>;
 
   explicit SpecificStore(CheckIRId check_ir_id) : specifics_(check_ir_id) {}
 
@@ -144,8 +145,7 @@ class SpecificStore : public Yaml::Printable<SpecificStore> {
   auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
       -> void;
 
-  auto values() const [[clang::lifetimebound]]
-  -> ValueStore<SpecificId, Specific>::Range {
+  auto values() const [[clang::lifetimebound]] -> ValueStore::Range {
     return specifics_.values();
   }
   auto size() const -> size_t { return specifics_.size(); }
@@ -159,7 +159,7 @@ class SpecificStore : public Yaml::Printable<SpecificStore> {
   // Context for hashing keys.
   class KeyContext;
 
-  ValueStore<SpecificId, Specific> specifics_;
+  ValueStore specifics_;
   Carbon::Set<SpecificId, 0, KeyContext> lookup_table_;
 };
 

+ 5 - 0
toolchain/sem_ir/ids.h

@@ -173,6 +173,11 @@ class MetaInstId : public InstId {
 // constant instruction that defines the constant. Symbolic constants are an
 // index into a separate table of `SymbolicConstant`s maintained by the constant
 // value store.
+//
+// IdTags for ConstantIds are slightly complex, and you need to know if the
+// constant is concrete or symbolic to know its tag:
+// - Concrete ConstantIds use the tag of the store of InstIds.
+// - Symbolic ConstantIds use the tag of the store of internal SymbolicIds.
 struct ConstantId : public IdBase<ConstantId> {
   static constexpr llvm::StringLiteral Label = "constant";
 

+ 2 - 2
toolchain/sem_ir/impl.h

@@ -202,7 +202,7 @@ class ImplStore {
   }
 
   auto values() const [[clang::lifetimebound]]
-  -> ValueStore<ImplId, Impl>::Range {
+  -> ValueStore<ImplId, Impl, Tag<CheckIRId>>::Range {
     return values_.values();
   }
   auto size() const -> size_t { return values_.size(); }
@@ -212,7 +212,7 @@ class ImplStore {
 
  private:
   File& sem_ir_;
-  ValueStore<ImplId, Impl> values_;
+  ValueStore<ImplId, Impl, Tag<CheckIRId>> values_;
   Map<std::pair<InstId, SpecificInterface>, ImplOrLookupBucketId> lookup_;
   // Buckets with at least 2 entries, which will be rare; see LookupBucketRef.
   llvm::SmallVector<llvm::SmallVector<ImplId, 2>> lookup_buckets_;

+ 1 - 1
toolchain/sem_ir/import_ir.h

@@ -26,7 +26,7 @@ struct ImportIR : public Printable<ImportIR> {
 
 static_assert(sizeof(ImportIR) == 8 + sizeof(uintptr_t), "Unexpected size");
 
-using ImportIRStore = ValueStore<ImportIRId, ImportIR>;
+using ImportIRStore = ValueStore<ImportIRId, ImportIR, Tag<CheckIRId>>;
 
 // A reference to an instruction in an imported IR. Used for diagnostics with
 // LocId. For a `Cpp` import, points to a Clang source location.

+ 8 - 6
toolchain/sem_ir/inst.h

@@ -459,6 +459,7 @@ struct LocIdAndInst {
 class InstStore {
  public:
   using IdType = InstId;
+  using IdTagType = IdTag<IdType, Tag<CheckIRId>>;
 
   explicit InstStore(File* file, int32_t reserved_inst_ids);
 
@@ -658,7 +659,7 @@ class InstStore {
   }
 
   auto values() const [[clang::lifetimebound]]
-  -> ValueStore<InstId, Inst>::Range {
+  -> ValueStore<InstId, Inst, Tag<CheckIRId>>::Range {
     return values_.values();
   }
   auto size() const -> int { return values_.size(); }
@@ -670,7 +671,7 @@ class InstStore {
     return values_.GetRawIndex(id);
   }
 
-  auto GetIdTag() const -> IdTag { return values_.GetIdTag(); }
+  auto GetIdTag() const -> IdTagType { return values_.GetIdTag(); }
 
  private:
   // Given a symbolic type, get the corresponding unattached type.
@@ -687,19 +688,20 @@ class InstStore {
 
   File* file_;
   llvm::SmallVector<LocId> loc_ids_;
-  ValueStore<InstId, Inst> values_;
+  ValueStore<InstId, Inst, Tag<CheckIRId>> values_;
 };
 
 // Adapts BlockValueStore for instruction blocks.
-class InstBlockStore : public BlockValueStore<InstBlockId, InstId> {
+class InstBlockStore
+    : public BlockValueStore<InstBlockId, InstId, Tag<CheckIRId>> {
  public:
-  using BaseType = BlockValueStore<InstBlockId, InstId>;
+  using BaseType = BlockValueStore<InstBlockId, InstId, Tag<CheckIRId>>;
 
   explicit InstBlockStore(llvm::BumpPtrAllocator& allocator,
                           CheckIRId check_ir_id = CheckIRId::None)
       // 4 reserved ids for the
       // `InstBlockId::{Empty,Exports,Imports,GlobalInit}` global ids.
-      : BaseType(allocator, IdTag(check_ir_id.index, 4)) {
+      : BaseType(allocator, check_ir_id, 4) {
     auto exports_id = AddPlaceholder();
     CARBON_CHECK(exports_id == InstBlockId::Exports);
     auto imports_id = AddPlaceholder();

+ 4 - 3
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -25,7 +25,8 @@ namespace Carbon::SemIR {
 
 namespace {
 struct Worklist {
-  using FingerprintStore = FixedSizeValueStore<InstId, uint64_t>;
+  using FingerprintStore =
+      FixedSizeValueStore<InstId, uint64_t, Tag<CheckIRId>>;
   using FilesFingerprintStores =
       FixedSizeValueStore<CheckIRId, FingerprintStore>;
 
@@ -66,8 +67,8 @@ struct Worklist {
   auto SetFingerprint(const File* file, InstId inst_id, uint64_t fingerprint) {
     auto& store = fingerprints->Get(file->check_ir_id());
     if (store.size() == 0) {
-      store = FixedSizeValueStore<InstId, uint64_t>::MakeWithExplicitSize(
-          file->insts().GetIdTag(), file->insts().size(), 0);
+      store = FingerprintStore::MakeWithExplicitSize(
+          file->insts().size(), file->insts().GetIdTag(), 0);
     }
     store.Set(inst_id, fingerprint ? fingerprint : 1);
   }

+ 4 - 2
toolchain/sem_ir/inst_fingerprinter.h

@@ -18,7 +18,8 @@ class InstFingerprinter {
   explicit InstFingerprinter(int total_ir_count)
       : fingerprints_(FilesFingerprintStores::MakeWithExplicitSizeFrom(
             total_ir_count, [] {
-              return FingerprintStore::MakeForOverwriteWithExplicitSize(0);
+              return FingerprintStore::MakeForOverwriteWithExplicitSize(
+                  0, CheckIRId::None);
             })) {}
 
   // Gets or computes a fingerprint for the given instruction.
@@ -43,7 +44,8 @@ class InstFingerprinter {
   // the `GetOrCompute` overload for `InstBlockId`s, and may save some work if
   // the same canonical inst block is used by multiple instructions, for example
   // as a specific argument list.
-  using FingerprintStore = FixedSizeValueStore<InstId, uint64_t>;
+  using FingerprintStore =
+      FixedSizeValueStore<InstId, uint64_t, Tag<CheckIRId>>;
   using FilesFingerprintStores =
       FixedSizeValueStore<CheckIRId, FingerprintStore>;
   FilesFingerprintStores fingerprints_;

+ 1 - 1
toolchain/sem_ir/interface.h

@@ -52,7 +52,7 @@ struct Interface : public EntityWithParamsBase,
   }
 };
 
-using InterfaceStore = ValueStore<InterfaceId, Interface>;
+using InterfaceStore = ValueStore<InterfaceId, Interface, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 1 - 1
toolchain/sem_ir/name_scope.cpp

@@ -14,7 +14,7 @@ namespace Carbon::SemIR {
 NameScopeStore::NameScopeStore(const File* file)
     // 1 reserved untagged id because the Package NameScope is used across
     // Files.
-    : file_(file), values_(IdTag(file->check_ir_id().index, 1)) {}
+    : file_(file), values_(file->check_ir_id(), 1) {}
 
 auto NameScope::Print(llvm::raw_ostream& out) const -> void {
   out << "{inst: " << inst_id_ << ", parent_scope: " << parent_scope_id_

+ 1 - 1
toolchain/sem_ir/name_scope.h

@@ -366,7 +366,7 @@ class NameScopeStore {
 
  private:
   const File* file_;
-  ValueStore<NameScopeId, NameScope> values_;
+  ValueStore<NameScopeId, NameScope, Tag<CheckIRId>> values_;
 };
 
 }  // namespace Carbon::SemIR

+ 2 - 1
toolchain/sem_ir/named_constraint.h

@@ -49,7 +49,8 @@ struct NamedConstraint : public EntityWithParamsBase,
   }
 };
 
-using NamedConstraintStore = ValueStore<NamedConstraintId, NamedConstraint>;
+using NamedConstraintStore =
+    ValueStore<NamedConstraintId, NamedConstraint, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 3 - 2
toolchain/sem_ir/require_impls.h

@@ -43,10 +43,11 @@ struct RequireImpls : Printable<RequireImpls> {
   }
 };
 
-using RequireImplsStore = ValueStore<RequireImplsId, RequireImpls>;
+using RequireImplsStore =
+    ValueStore<RequireImplsId, RequireImpls, Tag<CheckIRId>>;
 
 using RequireImplsBlockStore =
-    BlockValueStore<RequireImplsBlockId, RequireImplsId>;
+    BlockValueStore<RequireImplsBlockId, RequireImplsId, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 1 - 1
toolchain/sem_ir/specific_interface.h

@@ -27,7 +27,7 @@ inline constexpr SpecificInterface SpecificInterface::None = {
     .interface_id = InterfaceId::None, .specific_id = SpecificId::None};
 
 using SpecificInterfaceStore =
-    CanonicalValueStore<SpecificInterfaceId, SpecificInterface>;
+    CanonicalValueStore<SpecificInterfaceId, SpecificInterface, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR
 

+ 1 - 1
toolchain/sem_ir/struct_type_field.h

@@ -25,7 +25,7 @@ struct StructTypeField : Printable<StructTypeField> {
 };
 
 using StructTypeFieldsStore =
-    BlockValueStore<StructTypeFieldsId, StructTypeField>;
+    BlockValueStore<StructTypeFieldsId, StructTypeField, Tag<CheckIRId>>;
 
 // See common/hashing.h. Supports canonicalization of fields.
 inline auto CarbonHashValue(const StructTypeField& value, uint64_t seed)

+ 1 - 1
toolchain/sem_ir/vtable.h

@@ -29,7 +29,7 @@ struct Vtable : public VtableFields, public Printable<Vtable> {
   }
 };
 
-using VtableStore = ValueStore<VtableId, Vtable>;
+using VtableStore = ValueStore<VtableId, Vtable, Tag<CheckIRId>>;
 
 }  // namespace Carbon::SemIR