Przeglądaj źródła

Replace `FoldingSet` with `DenseMap` for instruction canonicalization. (#3979)

Switch from recursing into non-canonical instruction fields to
separately canonicalizing those fields. This means we now form canonical
`InstBlockId`s, `TypeBlockId`s, `IntId`s, `FloatId`s, and `BindNameId`s
at least in the cases when they're referenced by a constant instruction.

This reduces the overall runtime for @chandlerc's 10MLoC example by
27.5% on my machine.
Richard Smith 1 rok temu
rodzic
commit
5c8fa6ad5c
35 zmienionych plików z 479 dodań i 505 usunięć
  1. 10 0
      common/hashing.h
  2. 1 0
      common/hashing_test.cpp
  3. 1 0
      toolchain/base/BUILD
  4. 99 19
      toolchain/base/value_store.h
  5. 2 4
      toolchain/check/context.cpp
  6. 3 5
      toolchain/check/context.h
  7. 4 3
      toolchain/check/eval.cpp
  8. 2 2
      toolchain/check/subst.cpp
  9. 0 2
      toolchain/check/testdata/basics/no_prelude/multifile_raw_and_textual_ir.carbon
  10. 0 2
      toolchain/check/testdata/basics/no_prelude/multifile_raw_ir.carbon
  11. 5 20
      toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon
  12. 5 20
      toolchain/check/testdata/basics/no_prelude/raw_ir.carbon
  13. 6 8
      toolchain/check/testdata/builtins/float/div.carbon
  14. 12 14
      toolchain/check/testdata/builtins/float/eq.carbon
  15. 23 31
      toolchain/check/testdata/builtins/float/greater.carbon
  16. 26 34
      toolchain/check/testdata/builtins/float/greater_eq.carbon
  17. 26 34
      toolchain/check/testdata/builtins/float/less.carbon
  18. 23 31
      toolchain/check/testdata/builtins/float/less_eq.carbon
  19. 6 8
      toolchain/check/testdata/builtins/float/neq.carbon
  20. 2 5
      toolchain/driver/testdata/dump_shared_values.carbon
  21. 4 2
      toolchain/sem_ir/BUILD
  22. 76 0
      toolchain/sem_ir/bind_name.h
  23. 57 0
      toolchain/sem_ir/block_value_store.h
  24. 14 62
      toolchain/sem_ir/constant.cpp
  25. 6 17
      toolchain/sem_ir/constant.h
  26. 6 0
      toolchain/sem_ir/copy_on_write_block.h
  27. 8 25
      toolchain/sem_ir/file.h
  28. 1 1
      toolchain/sem_ir/formatter.cpp
  29. 7 0
      toolchain/sem_ir/ids.h
  30. 2 2
      toolchain/sem_ir/inst.cpp
  31. 41 7
      toolchain/sem_ir/inst.h
  32. 1 1
      toolchain/sem_ir/inst_namer.cpp
  33. 0 123
      toolchain/sem_ir/inst_profile.cpp
  34. 0 21
      toolchain/sem_ir/inst_profile.h
  35. 0 2
      toolchain/sem_ir/name.cpp

+ 10 - 0
common/hashing.h

@@ -279,6 +279,16 @@ class Hasher {
   // state along with the contents.
   auto HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void;
 
+  // Incorporate a dynamically sized sequence of bytes represented as an array
+  // of objects into the hasher's state.
+  template <typename T>
+    requires std::has_unique_object_representations_v<T>
+  auto HashSizedBytes(llvm::ArrayRef<T> data) -> void {
+    HashSizedBytes(llvm::ArrayRef<std::byte>(
+        reinterpret_cast<const std::byte*>(data.data()),
+        data.size() * sizeof(T)));
+  }
+
   // An out-of-line, throughput-optimized routine for incorporating a
   // dynamically sized sequence when the sequence size is guaranteed to be >32.
   // The size is always incorporated into the state.

+ 1 - 0
common/hashing_test.cpp

@@ -283,6 +283,7 @@ struct HashableType {
 
   int ignored = 0;
 
+  // See common/hashing.h.
   friend auto CarbonHashValue(const HashableType& value, uint64_t seed)
       -> HashCode {
     Hasher hasher(seed);

+ 1 - 0
toolchain/base/BUILD

@@ -38,6 +38,7 @@ cc_library(
         ":index_base",
         ":yaml",
         "//common:check",
+        "//common:hashing",
         "//common:ostream",
         "@llvm-project//llvm:Support",
     ],

+ 99 - 19
toolchain/base/value_store.h

@@ -8,6 +8,7 @@
 #include <type_traits>
 
 #include "common/check.h"
+#include "common/hashing.h"
 #include "common/ostream.h"
 #include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/APInt.h"
@@ -21,13 +22,13 @@
 
 namespace Carbon {
 
-// The value of a real literal.
+// The value of a real literal token.
 //
 // This is either a dyadic fraction (mantissa * 2^exponent) or a decadic
 // fraction (mantissa * 10^exponent).
 //
-// TODO: For SemIR, replace this with a Rational type, per the design:
-// docs/design/expressions/literals.md
+// These values are not canonicalized, because we don't expect them to repeat
+// and don't use them in SemIR values.
 class Real : public Printable<Real> {
  public:
   auto Print(llvm::raw_ostream& output_stream) const -> void {
@@ -48,9 +49,12 @@ class Real : public Printable<Real> {
   bool is_decimal;
 };
 
-// Corresponds to an integer value represented by an APInt.
+// Corresponds to an integer value represented by an APInt. This is used both
+// for integer literal tokens, which are unsigned and have an unspecified
+// bit-width, and integer values in SemIR, which have a signedness and bit-width
+// matching their type.
 struct IntId : public IdBase, public Printable<IntId> {
-  using ValueType = const llvm::APInt;
+  using ValueType = llvm::APInt;
   static const IntId Invalid;
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -60,9 +64,10 @@ struct IntId : public IdBase, public Printable<IntId> {
 };
 constexpr IntId IntId::Invalid(IntId::InvalidIndex);
 
-// Corresponds to a float value represented by an APFloat.
+// Corresponds to a float value represented by an APFloat. This is used for
+// floating-point values in SemIR.
 struct FloatId : public IdBase, public Printable<FloatId> {
-  using ValueType = const llvm::APFloat;
+  using ValueType = llvm::APFloat;
   static const FloatId Invalid;
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -72,9 +77,29 @@ struct FloatId : public IdBase, public Printable<FloatId> {
 };
 constexpr FloatId FloatId::Invalid(FloatId::InvalidIndex);
 
+// DenseMapInfo for llvm::APFloat, for use in the canonical float value store.
+// LLVM has an implementation of this but it strangely lives in the non-public
+// header lib/IR/LLVMContextImpl.h, so we use our own.
+// TODO: Remove this once our new hash table is available.
+struct APFloatDenseMapInfo {
+  static auto getEmptyKey() -> llvm::APFloat {
+    return llvm::APFloat(llvm::APFloat::Bogus(), 1);
+  }
+  static auto getTombstoneKey() -> llvm::APFloat {
+    return llvm::APFloat(llvm::APFloat::Bogus(), 2);
+  }
+  static auto getHashValue(const llvm::APFloat& val) -> unsigned {
+    return hash_value(val);
+  }
+  static auto isEqual(const llvm::APFloat& lhs, const llvm::APFloat& rhs)
+      -> bool {
+    return lhs.bitwiseIsEqual(rhs);
+  }
+};
+
 // Corresponds to a Real value.
 struct RealId : public IdBase, public Printable<RealId> {
-  using ValueType = const Real;
+  using ValueType = Real;
   static const RealId Invalid;
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -86,7 +111,7 @@ constexpr RealId RealId::Invalid(RealId::InvalidIndex);
 
 // Corresponds to a StringRef.
 struct StringId : public IdBase, public Printable<StringId> {
-  using ValueType = const std::string;
+  using ValueType = std::string;
   static const StringId Invalid;
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -191,10 +216,62 @@ class ValueStore
   llvm::SmallVector<std::decay_t<ValueType>, 0> values_;
 };
 
+// A wrapper for accumulating immutable values with deduplication, providing IDs
+// to later retrieve the value.
+//
+// IdT::ValueType must represent the type being indexed.
+template <typename IdT,
+          typename DenseMapInfoT = llvm::DenseMapInfo<typename IdT::ValueType>>
+class CanonicalValueStore {
+ public:
+  using ValueType = typename IdT::ValueType;
+
+  // Stores a canonical copy of the value and returns an ID to reference it.
+  auto Add(ValueType value) -> IdT {
+    auto [it, added] = map_.insert({value, IdT::Invalid});
+    if (added) {
+      it->second = values_.Add(value);
+    }
+    return it->second;
+  }
+
+  // Returns the value for an ID.
+  auto Get(IdT id) const -> const ValueType& { return values_.Get(id); }
+
+  // Reserves space.
+  auto Reserve(size_t size) -> void {
+    values_.Reserve(size);
+    map_.reserve(size);
+  }
+
+  // These are to support printable structures, and are not guaranteed.
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return values_.OutputYaml();
+  }
+
+  auto array_ref() const -> llvm::ArrayRef<ValueType> {
+    return values_.array_ref();
+  }
+  auto size() const -> size_t { return values_.size(); }
+
+ private:
+  // We store two copies of each value: one in the ValueStore for fast mapping
+  // from IdT to value, and another in the DenseMap for the reverse mapping.
+  //
+  // We could avoid this by storing the map instead as a DenseSet<IdT>, but
+  // there's no way to provide a DenseMapInfo that looks the IDs up in the
+  // vector.
+  //
+  // TODO: Switch to a better representation that avoids the duplication.
+  ValueStore<IdT> values_;
+  llvm::DenseMap<ValueType, IdT, DenseMapInfoT> map_;
+};
+
 // Storage for StringRefs. The caller is responsible for ensuring storage is
 // allocated.
 template <>
-class ValueStore<StringId> : public Yaml::Printable<ValueStore<StringId>> {
+class CanonicalValueStore<StringId>
+    : public Yaml::Printable<CanonicalValueStore<StringId>> {
  public:
   // Returns an ID to reference the value. May return an existing ID if the
   // string was previously added.
@@ -244,7 +321,8 @@ class ValueStore<StringId> : public Yaml::Printable<ValueStore<StringId>> {
 template <typename IdT>
 class StringStoreWrapper : public Printable<StringStoreWrapper<IdT>> {
  public:
-  explicit StringStoreWrapper(ValueStore<StringId>* values) : values_(values) {}
+  explicit StringStoreWrapper(CanonicalValueStore<StringId>* values)
+      : values_(values) {}
 
   auto Add(llvm::StringRef value) -> IdT {
     return IdT(values_->Add(value).index);
@@ -263,9 +341,11 @@ class StringStoreWrapper : public Printable<StringStoreWrapper<IdT>> {
   auto size() const -> size_t { return values_->size(); }
 
  private:
-  ValueStore<StringId>* values_;
+  CanonicalValueStore<StringId>* values_;
 };
 
+using FloatValueStore = CanonicalValueStore<FloatId, APFloatDenseMapInfo>;
+
 // Stores that will be used across compiler phases for a given compilation unit.
 // This is provided mainly so that they don't need to be passed separately.
 class SharedValueStores : public Yaml::Printable<SharedValueStores> {
@@ -283,12 +363,12 @@ class SharedValueStores : public Yaml::Printable<SharedValueStores> {
   auto identifiers() const -> const StringStoreWrapper<IdentifierId>& {
     return identifiers_;
   }
-  auto ints() -> ValueStore<IntId>& { return ints_; }
-  auto ints() const -> const ValueStore<IntId>& { return ints_; }
+  auto ints() -> CanonicalValueStore<IntId>& { return ints_; }
+  auto ints() const -> const CanonicalValueStore<IntId>& { return ints_; }
   auto reals() -> ValueStore<RealId>& { return reals_; }
   auto reals() const -> const ValueStore<RealId>& { return reals_; }
-  auto floats() -> ValueStore<FloatId>& { return floats_; }
-  auto floats() const -> const ValueStore<FloatId>& { return floats_; }
+  auto floats() -> FloatValueStore& { return floats_; }
+  auto floats() const -> const FloatValueStore& { return floats_; }
   auto string_literal_values() -> StringStoreWrapper<StringLiteralValueId>& {
     return string_literal_values_;
   }
@@ -313,11 +393,11 @@ class SharedValueStores : public Yaml::Printable<SharedValueStores> {
   }
 
  private:
-  ValueStore<IntId> ints_;
+  CanonicalValueStore<IntId> ints_;
   ValueStore<RealId> reals_;
-  ValueStore<FloatId> floats_;
+  FloatValueStore floats_;
 
-  ValueStore<StringId> strings_;
+  CanonicalValueStore<StringId> strings_;
   StringStoreWrapper<IdentifierId> identifiers_;
   StringStoreWrapper<StringLiteralValueId> string_literal_values_;
 };

+ 2 - 4
toolchain/check/context.cpp

@@ -1076,10 +1076,8 @@ auto Context::GetStructType(SemIR::InstBlockId refs_id) -> SemIR::TypeId {
 
 auto Context::GetTupleType(llvm::ArrayRef<SemIR::TypeId> type_ids)
     -> SemIR::TypeId {
-  // TODO: Deduplicate the type block here. Currently requesting the same tuple
-  // type more than once will create multiple type blocks, all but one of which
-  // is unused.
-  return GetTypeImpl<SemIR::TupleType>(*this, type_blocks().Add(type_ids));
+  return GetTypeImpl<SemIR::TupleType>(*this,
+                                       type_blocks().AddCanonical(type_ids));
 }
 
 auto Context::GetAssociatedEntityType(SemIR::InterfaceId interface_id,

+ 3 - 5
toolchain/check/context.h

@@ -341,15 +341,13 @@ class Context {
   auto identifiers() -> StringStoreWrapper<IdentifierId>& {
     return sem_ir().identifiers();
   }
-  auto ints() -> ValueStore<IntId>& { return sem_ir().ints(); }
+  auto ints() -> CanonicalValueStore<IntId>& { return sem_ir().ints(); }
   auto reals() -> ValueStore<RealId>& { return sem_ir().reals(); }
-  auto floats() -> ValueStore<FloatId>& { return sem_ir().floats(); }
+  auto floats() -> FloatValueStore& { return sem_ir().floats(); }
   auto string_literal_values() -> StringStoreWrapper<StringLiteralValueId>& {
     return sem_ir().string_literal_values();
   }
-  auto bind_names() -> ValueStore<SemIR::BindNameId>& {
-    return sem_ir().bind_names();
-  }
+  auto bind_names() -> SemIR::BindNameStore& { return sem_ir().bind_names(); }
   auto functions() -> ValueStore<SemIR::FunctionId>& {
     return sem_ir().functions();
   }

+ 4 - 3
toolchain/check/eval.cpp

@@ -141,9 +141,9 @@ static auto GetConstantValue(Context& context, SemIR::InstBlockId inst_block_id,
 
     const_insts.push_back(const_inst_id);
   }
-  // TODO: If the new block is identical to the original block, return the
-  // original ID.
-  return context.inst_blocks().Add(const_insts);
+  // TODO: If the new block is identical to the original block, and we know the
+  // old ID was canonical, return the original ID.
+  return context.inst_blocks().AddCanonical(const_insts);
 }
 
 // The constant value of a type block is that type block, but we still need to
@@ -1054,6 +1054,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case CARBON_KIND(SemIR::BindSymbolicName bind): {
       // The constant form of a symbolic binding is an idealized form of the
       // original, with no equivalent value.
+      bind.bind_name_id = context.bind_names().MakeCanonical(bind.bind_name_id);
       bind.value_id = SemIR::InstId::Invalid;
       return MakeConstantResult(context, bind, Phase::Symbolic);
     }

+ 2 - 2
toolchain/check/subst.cpp

@@ -108,7 +108,7 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind,
       for (auto i : llvm::reverse(llvm::seq(size))) {
         new_inst_block.Set(i, worklist.Pop());
       }
-      return new_inst_block.id().index;
+      return new_inst_block.GetCanonical().index;
     }
     case SemIR::IdKind::For<SemIR::TypeBlockId>: {
       auto old_type_block_id = static_cast<SemIR::TypeBlockId>(arg);
@@ -119,7 +119,7 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind,
         new_type_block.Set(size - i - 1,
                            context.GetTypeIdForTypeInst(worklist.Pop()));
       }
-      return new_type_block.id().index;
+      return new_type_block.GetCanonical().index;
     }
     default:
       return arg;

+ 0 - 2
toolchain/check/testdata/basics/no_prelude/multifile_raw_and_textual_ir.carbon

@@ -34,7 +34,6 @@ fn B() {}
 // CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: none, type: type2}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
 // CHECK:STDOUT:     'inst+1':          {kind: FunctionDecl, arg0: function0, arg1: empty, type: type1}
@@ -96,7 +95,6 @@ fn B() {}
 // CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: none, type: type2}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
 // CHECK:STDOUT:     'inst+1':          {kind: FunctionDecl, arg0: function0, arg1: empty, type: type1}

+ 0 - 2
toolchain/check/testdata/basics/no_prelude/multifile_raw_ir.carbon

@@ -34,7 +34,6 @@ fn B() {}
 // CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: none, type: type2}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
 // CHECK:STDOUT:     'inst+1':          {kind: FunctionDecl, arg0: function0, arg1: empty, type: type1}
@@ -75,7 +74,6 @@ fn B() {}
 // CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: none, type: type2}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
 // CHECK:STDOUT:     'inst+1':          {kind: FunctionDecl, arg0: function0, arg1: empty, type: type1}

+ 5 - 20
toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon

@@ -31,21 +31,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     type4:           {constant: template inst+17, value_rep: {kind: copy, type: type4}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
-// CHECK:STDOUT:     typeBlock2:      {}
-// CHECK:STDOUT:     typeBlock3:      {}
-// CHECK:STDOUT:     typeBlock4:
-// CHECK:STDOUT:       0:               type1
-// CHECK:STDOUT:       1:               type1
-// CHECK:STDOUT:     typeBlock5:      {}
-// CHECK:STDOUT:     typeBlock6:      {}
-// CHECK:STDOUT:     typeBlock7:
-// CHECK:STDOUT:       0:               type1
-// CHECK:STDOUT:       1:               type1
-// CHECK:STDOUT:     typeBlock8:      {}
-// CHECK:STDOUT:     typeBlock9:      {}
-// CHECK:STDOUT:     typeBlock10:     {}
-// CHECK:STDOUT:     typeBlock11:
+// CHECK:STDOUT:     typeBlock1:
 // CHECK:STDOUT:       0:               type1
 // CHECK:STDOUT:       1:               type1
 // CHECK:STDOUT:   insts:
@@ -57,7 +43,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     'inst+5':          {kind: BindName, arg0: bindName0, arg1: inst+4, type: type1}
 // CHECK:STDOUT:     'inst+6':          {kind: TupleLiteral, arg0: empty, type: type1}
 // CHECK:STDOUT:     'inst+7':          {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock4, type: typeTypeType}
+// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
 // CHECK:STDOUT:     'inst+9':          {kind: TupleLiteral, arg0: block4, type: type2}
 // CHECK:STDOUT:     'inst+10':         {kind: Converted, arg0: inst+6, arg1: inst+1, type: typeTypeType}
 // CHECK:STDOUT:     'inst+11':         {kind: Converted, arg0: inst+7, arg1: inst+1, type: typeTypeType}
@@ -78,7 +64,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     'inst+26':         {kind: TupleInit, arg0: empty, arg1: inst+25, type: type1}
 // CHECK:STDOUT:     'inst+27':         {kind: Converted, arg0: inst+19, arg1: inst+26, type: type1}
 // CHECK:STDOUT:     'inst+28':         {kind: TupleInit, arg0: block10, arg1: inst+13, type: type2}
-// CHECK:STDOUT:     'inst+29':         {kind: TupleValue, arg0: block12, type: type2}
+// CHECK:STDOUT:     'inst+29':         {kind: TupleValue, arg0: block11, type: type2}
 // CHECK:STDOUT:     'inst+30':         {kind: Converted, arg0: inst+20, arg1: inst+28, type: type2}
 // CHECK:STDOUT:     'inst+31':         {kind: ReturnExpr, arg0: inst+30, arg1: inst+13}
 // CHECK:STDOUT:   constant_values:
@@ -144,11 +130,10 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     block10:
 // CHECK:STDOUT:       0:               inst+24
 // CHECK:STDOUT:       1:               inst+27
-// CHECK:STDOUT:     block11:         {}
-// CHECK:STDOUT:     block12:
+// CHECK:STDOUT:     block11:
 // CHECK:STDOUT:       0:               inst+23
 // CHECK:STDOUT:       1:               inst+23
-// CHECK:STDOUT:     block13:
+// CHECK:STDOUT:     block12:
 // CHECK:STDOUT:       0:               inst+0
 // CHECK:STDOUT:       1:               inst+14
 // CHECK:STDOUT: ...

+ 5 - 20
toolchain/check/testdata/basics/no_prelude/raw_ir.carbon

@@ -31,21 +31,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     type4:           {constant: template inst+17, value_rep: {kind: copy, type: type4}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
-// CHECK:STDOUT:     typeBlock1:      {}
-// CHECK:STDOUT:     typeBlock2:      {}
-// CHECK:STDOUT:     typeBlock3:      {}
-// CHECK:STDOUT:     typeBlock4:
-// CHECK:STDOUT:       0:               type1
-// CHECK:STDOUT:       1:               type1
-// CHECK:STDOUT:     typeBlock5:      {}
-// CHECK:STDOUT:     typeBlock6:      {}
-// CHECK:STDOUT:     typeBlock7:
-// CHECK:STDOUT:       0:               type1
-// CHECK:STDOUT:       1:               type1
-// CHECK:STDOUT:     typeBlock8:      {}
-// CHECK:STDOUT:     typeBlock9:      {}
-// CHECK:STDOUT:     typeBlock10:     {}
-// CHECK:STDOUT:     typeBlock11:
+// CHECK:STDOUT:     typeBlock1:
 // CHECK:STDOUT:       0:               type1
 // CHECK:STDOUT:       1:               type1
 // CHECK:STDOUT:   insts:
@@ -57,7 +43,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     'inst+5':          {kind: BindName, arg0: bindName0, arg1: inst+4, type: type1}
 // CHECK:STDOUT:     'inst+6':          {kind: TupleLiteral, arg0: empty, type: type1}
 // CHECK:STDOUT:     'inst+7':          {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock4, type: typeTypeType}
+// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
 // CHECK:STDOUT:     'inst+9':          {kind: TupleLiteral, arg0: block4, type: type2}
 // CHECK:STDOUT:     'inst+10':         {kind: Converted, arg0: inst+6, arg1: inst+1, type: typeTypeType}
 // CHECK:STDOUT:     'inst+11':         {kind: Converted, arg0: inst+7, arg1: inst+1, type: typeTypeType}
@@ -78,7 +64,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     'inst+26':         {kind: TupleInit, arg0: empty, arg1: inst+25, type: type1}
 // CHECK:STDOUT:     'inst+27':         {kind: Converted, arg0: inst+19, arg1: inst+26, type: type1}
 // CHECK:STDOUT:     'inst+28':         {kind: TupleInit, arg0: block10, arg1: inst+13, type: type2}
-// CHECK:STDOUT:     'inst+29':         {kind: TupleValue, arg0: block12, type: type2}
+// CHECK:STDOUT:     'inst+29':         {kind: TupleValue, arg0: block11, type: type2}
 // CHECK:STDOUT:     'inst+30':         {kind: Converted, arg0: inst+20, arg1: inst+28, type: type2}
 // CHECK:STDOUT:     'inst+31':         {kind: ReturnExpr, arg0: inst+30, arg1: inst+13}
 // CHECK:STDOUT:   constant_values:
@@ -144,11 +130,10 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     block10:
 // CHECK:STDOUT:       0:               inst+24
 // CHECK:STDOUT:       1:               inst+27
-// CHECK:STDOUT:     block11:         {}
-// CHECK:STDOUT:     block12:
+// CHECK:STDOUT:     block11:
 // CHECK:STDOUT:       0:               inst+23
 // CHECK:STDOUT:       1:               inst+23
-// CHECK:STDOUT:     block13:
+// CHECK:STDOUT:     block12:
 // CHECK:STDOUT:       0:               inst+0
 // CHECK:STDOUT:       1:               inst+14
 // CHECK:STDOUT: ...

+ 6 - 8
toolchain/check/testdata/builtins/float/div.carbon

@@ -62,9 +62,7 @@ fn RuntimeCallBadReturnType(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.5: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.6: f64 = float_literal 0 [template]
 // CHECK:STDOUT:   %.7: f64 = float_literal +Inf [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.9: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.10: f64 = float_literal NaN [template]
+// CHECK:STDOUT:   %.8: f64 = float_literal NaN [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -99,11 +97,11 @@ fn RuntimeCallBadReturnType(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc9_27.2: f64 = converted %float.div.loc9, %.loc9_27.1 [template = constants.%.7]
 // CHECK:STDOUT:   %b.loc9: f64 = bind_name b, %.loc9_27.2
 // CHECK:STDOUT:   %Div.ref.loc10: Div = name_ref Div, %Div.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc10_18: f64 = float_literal 0 [template = constants.%.8]
-// CHECK:STDOUT:   %.loc10_23: f64 = float_literal 0 [template = constants.%.9]
-// CHECK:STDOUT:   %float.div.loc10: init f64 = call %Div.ref.loc10(%.loc10_18, %.loc10_23) [template = constants.%.10]
-// CHECK:STDOUT:   %.loc10_27.1: f64 = value_of_initializer %float.div.loc10 [template = constants.%.10]
-// CHECK:STDOUT:   %.loc10_27.2: f64 = converted %float.div.loc10, %.loc10_27.1 [template = constants.%.10]
+// CHECK:STDOUT:   %.loc10_18: f64 = float_literal 0 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc10_23: f64 = float_literal 0 [template = constants.%.6]
+// CHECK:STDOUT:   %float.div.loc10: init f64 = call %Div.ref.loc10(%.loc10_18, %.loc10_23) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc10_27.1: f64 = value_of_initializer %float.div.loc10 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc10_27.2: f64 = converted %float.div.loc10, %.loc10_27.1 [template = constants.%.8]
 // CHECK:STDOUT:   %c: f64 = bind_name c, %.loc10_27.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 12 - 14
toolchain/check/testdata/builtins/float/eq.carbon

@@ -42,11 +42,9 @@ fn WrongResult(a: f64, b: f64) -> f64 = "float.eq";
 // CHECK:STDOUT:   %struct.2: F = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = ptr_type {} [template]
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.5: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.6: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 2 [template]
-// CHECK:STDOUT:   %.9: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.5: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.6: f64 = float_literal 2 [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal false [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.3: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -104,10 +102,10 @@ fn WrongResult(a: f64, b: f64) -> f64 = "float.eq";
 // CHECK:STDOUT:   %true_.ref: True = name_ref true_, %true_
 // CHECK:STDOUT:   %Eq.ref.loc8: Eq = name_ref Eq, file.%Eq.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %.loc8_19: f64 = float_literal 1 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc8_24: f64 = float_literal 1 [template = constants.%.5]
-// CHECK:STDOUT:   %float.eq.loc8: init bool = call %Eq.ref.loc8(%.loc8_19, %.loc8_24) [template = constants.%.6]
-// CHECK:STDOUT:   %.loc8_13.1: bool = value_of_initializer %float.eq.loc8 [template = constants.%.6]
-// CHECK:STDOUT:   %.loc8_13.2: bool = converted %float.eq.loc8, %.loc8_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc8_24: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.eq.loc8: init bool = call %Eq.ref.loc8(%.loc8_19, %.loc8_24) [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_13.1: bool = value_of_initializer %float.eq.loc8 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_13.2: bool = converted %float.eq.loc8, %.loc8_13.1 [template = constants.%.5]
 // CHECK:STDOUT:   if %.loc8_13.2 br !if.expr.then.loc8 else br !if.expr.else.loc8
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc8:
@@ -122,11 +120,11 @@ fn WrongResult(a: f64, b: f64) -> f64 = "float.eq";
 // CHECK:STDOUT:   %.loc8_13.3: type = block_arg !if.expr.result.loc8 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Eq.ref.loc9: Eq = name_ref Eq, file.%Eq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc9_20: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc9_25: f64 = float_literal 2 [template = constants.%.8]
-// CHECK:STDOUT:   %float.eq.loc9: init bool = call %Eq.ref.loc9(%.loc9_20, %.loc9_25) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %float.eq.loc9 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc9_14.2: bool = converted %float.eq.loc9, %.loc9_14.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc9_20: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_25: f64 = float_literal 2 [template = constants.%.6]
+// CHECK:STDOUT:   %float.eq.loc9: init bool = call %Eq.ref.loc9(%.loc9_20, %.loc9_25) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %float.eq.loc9 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_14.2: bool = converted %float.eq.loc9, %.loc9_14.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc9_14.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc9:

+ 23 - 31
toolchain/check/testdata/builtins/float/greater.carbon

@@ -41,17 +41,9 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.5: f64 = float_literal 2 [template]
 // CHECK:STDOUT:   %.6: bool = bool_literal false [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.9: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.10: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.11: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.12: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.13: f64 = float_literal -1 [template]
-// CHECK:STDOUT:   %.14: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.15: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.16: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.17: f64 = float_literal -1 [template]
+// CHECK:STDOUT:   %.7: f64 = float_literal 0 [template]
+// CHECK:STDOUT:   %.8: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.9: f64 = float_literal -1 [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.4: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -135,8 +127,8 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc9_14.3: type = block_arg !if.expr.result.loc9 [template = constants.%False]
 // CHECK:STDOUT:   %false_.ref.loc10: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Greater.ref.loc10: Greater = name_ref Greater, file.%Greater.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc10_25: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc10_30: f64 = float_literal 1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc10_25: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_30: f64 = float_literal 1 [template = constants.%.4]
 // CHECK:STDOUT:   %float.greater.loc10: init bool = call %Greater.ref.loc10(%.loc10_25, %.loc10_30) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14.1: bool = value_of_initializer %float.greater.loc10 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_14.2: bool = converted %float.greater.loc10, %.loc10_14.1 [template = constants.%.6]
@@ -154,11 +146,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc10_14.3: type = block_arg !if.expr.result.loc10 [template = constants.%False]
 // CHECK:STDOUT:   %true_.ref.loc11: True = name_ref true_, %true_
 // CHECK:STDOUT:   %Greater.ref.loc11: Greater = name_ref Greater, file.%Greater.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc11_24: f64 = float_literal 1 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_29: f64 = float_literal 0 [template = constants.%.10]
-// CHECK:STDOUT:   %float.greater.loc11: init bool = call %Greater.ref.loc11(%.loc11_24, %.loc11_29) [template = constants.%.11]
-// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %float.greater.loc11 [template = constants.%.11]
-// CHECK:STDOUT:   %.loc11_13.2: bool = converted %float.greater.loc11, %.loc11_13.1 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc11_24: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_29: f64 = float_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %float.greater.loc11: init bool = call %Greater.ref.loc11(%.loc11_24, %.loc11_29) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %float.greater.loc11 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_13.2: bool = converted %float.greater.loc11, %.loc11_13.1 [template = constants.%.8]
 // CHECK:STDOUT:   if %.loc11_13.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc11:
@@ -174,11 +166,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %false_.ref.loc12: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Greater.ref.loc12: Greater = name_ref Greater, file.%Greater.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %Negate.ref.loc12: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc12_32: f64 = float_literal 1 [template = constants.%.12]
-// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_32) [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_38: f64 = float_literal 0 [template = constants.%.14]
-// CHECK:STDOUT:   %.loc12_24.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_24.2: f64 = converted %float.negate.loc12, %.loc12_24.1 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc12_32: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_32) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_38: f64 = float_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_24.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_24.2: f64 = converted %float.negate.loc12, %.loc12_24.1 [template = constants.%.9]
 // CHECK:STDOUT:   %float.greater.loc12: init bool = call %Greater.ref.loc12(%.loc12_24.2, %.loc12_38) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_14.1: bool = value_of_initializer %float.greater.loc12 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_14.2: bool = converted %float.greater.loc12, %.loc12_14.1 [template = constants.%.6]
@@ -196,15 +188,15 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc12_14.3: type = block_arg !if.expr.result.loc12 [template = constants.%False]
 // CHECK:STDOUT:   %true_.ref.loc13: True = name_ref true_, %true_
 // CHECK:STDOUT:   %Greater.ref.loc13: Greater = name_ref Greater, file.%Greater.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc13_24: f64 = float_literal 0 [template = constants.%.15]
+// CHECK:STDOUT:   %.loc13_24: f64 = float_literal 0 [template = constants.%.7]
 // CHECK:STDOUT:   %Negate.ref.loc13: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc13_36: f64 = float_literal 1 [template = constants.%.16]
-// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_36) [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_23.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_23.2: f64 = converted %float.negate.loc13, %.loc13_23.1 [template = constants.%.17]
-// CHECK:STDOUT:   %float.greater.loc13: init bool = call %Greater.ref.loc13(%.loc13_24, %.loc13_23.2) [template = constants.%.11]
-// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %float.greater.loc13 [template = constants.%.11]
-// CHECK:STDOUT:   %.loc13_13.2: bool = converted %float.greater.loc13, %.loc13_13.1 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc13_36: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_36) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.2: f64 = converted %float.negate.loc13, %.loc13_23.1 [template = constants.%.9]
+// CHECK:STDOUT:   %float.greater.loc13: init bool = call %Greater.ref.loc13(%.loc13_24, %.loc13_23.2) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %float.greater.loc13 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_13.2: bool = converted %float.greater.loc13, %.loc13_13.1 [template = constants.%.8]
 // CHECK:STDOUT:   if %.loc13_13.2 br !if.expr.then.loc13 else br !if.expr.else.loc13
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc13:

+ 26 - 34
toolchain/check/testdata/builtins/float/greater_eq.carbon

@@ -41,17 +41,9 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.5: f64 = float_literal 2 [template]
 // CHECK:STDOUT:   %.6: bool = bool_literal false [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.9: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.10: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.11: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.12: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.13: f64 = float_literal -1 [template]
-// CHECK:STDOUT:   %.14: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.15: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.16: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.17: f64 = float_literal -1 [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.8: f64 = float_literal 0 [template]
+// CHECK:STDOUT:   %.9: f64 = float_literal -1 [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.4: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -135,11 +127,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc9_14.3: type = block_arg !if.expr.result.loc9 [template = constants.%False]
 // CHECK:STDOUT:   %true_.ref.loc10: True = name_ref true_, %true_
 // CHECK:STDOUT:   %GreaterEq.ref.loc10: GreaterEq = name_ref GreaterEq, file.%GreaterEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc10_26: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc10_31: f64 = float_literal 1 [template = constants.%.8]
-// CHECK:STDOUT:   %float.greater_eq.loc10: init bool = call %GreaterEq.ref.loc10(%.loc10_26, %.loc10_31) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc10_13.1: bool = value_of_initializer %float.greater_eq.loc10 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc10_13.2: bool = converted %float.greater_eq.loc10, %.loc10_13.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc10_26: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_31: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.greater_eq.loc10: init bool = call %GreaterEq.ref.loc10(%.loc10_26, %.loc10_31) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_13.1: bool = value_of_initializer %float.greater_eq.loc10 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_13.2: bool = converted %float.greater_eq.loc10, %.loc10_13.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc10_13.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc10:
@@ -154,11 +146,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc10_13.3: type = block_arg !if.expr.result.loc10 [template = constants.%True]
 // CHECK:STDOUT:   %true_.ref.loc11: True = name_ref true_, %true_
 // CHECK:STDOUT:   %GreaterEq.ref.loc11: GreaterEq = name_ref GreaterEq, file.%GreaterEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc11_26: f64 = float_literal 1 [template = constants.%.10]
-// CHECK:STDOUT:   %.loc11_31: f64 = float_literal 0 [template = constants.%.11]
-// CHECK:STDOUT:   %float.greater_eq.loc11: init bool = call %GreaterEq.ref.loc11(%.loc11_26, %.loc11_31) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %float.greater_eq.loc11 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_13.2: bool = converted %float.greater_eq.loc11, %.loc11_13.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc11_26: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_31: f64 = float_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %float.greater_eq.loc11: init bool = call %GreaterEq.ref.loc11(%.loc11_26, %.loc11_31) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %float.greater_eq.loc11 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_13.2: bool = converted %float.greater_eq.loc11, %.loc11_13.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc11_13.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc11:
@@ -174,11 +166,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %false_.ref.loc12: False = name_ref false_, %false_
 // CHECK:STDOUT:   %GreaterEq.ref.loc12: GreaterEq = name_ref GreaterEq, file.%GreaterEq.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %Negate.ref.loc12: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc12_34: f64 = float_literal 1 [template = constants.%.12]
-// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_34) [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_40: f64 = float_literal 0 [template = constants.%.14]
-// CHECK:STDOUT:   %.loc12_26.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_26.2: f64 = converted %float.negate.loc12, %.loc12_26.1 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc12_34: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_34) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_40: f64 = float_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc12_26.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_26.2: f64 = converted %float.negate.loc12, %.loc12_26.1 [template = constants.%.9]
 // CHECK:STDOUT:   %float.greater_eq.loc12: init bool = call %GreaterEq.ref.loc12(%.loc12_26.2, %.loc12_40) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_14.1: bool = value_of_initializer %float.greater_eq.loc12 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_14.2: bool = converted %float.greater_eq.loc12, %.loc12_14.1 [template = constants.%.6]
@@ -196,15 +188,15 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc12_14.3: type = block_arg !if.expr.result.loc12 [template = constants.%False]
 // CHECK:STDOUT:   %true_.ref.loc13: True = name_ref true_, %true_
 // CHECK:STDOUT:   %GreaterEq.ref.loc13: GreaterEq = name_ref GreaterEq, file.%GreaterEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc13_26: f64 = float_literal 0 [template = constants.%.15]
+// CHECK:STDOUT:   %.loc13_26: f64 = float_literal 0 [template = constants.%.8]
 // CHECK:STDOUT:   %Negate.ref.loc13: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc13_38: f64 = float_literal 1 [template = constants.%.16]
-// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_38) [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_25.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_25.2: f64 = converted %float.negate.loc13, %.loc13_25.1 [template = constants.%.17]
-// CHECK:STDOUT:   %float.greater_eq.loc13: init bool = call %GreaterEq.ref.loc13(%.loc13_26, %.loc13_25.2) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %float.greater_eq.loc13 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc13_13.2: bool = converted %float.greater_eq.loc13, %.loc13_13.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_38: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_38) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_25.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_25.2: f64 = converted %float.negate.loc13, %.loc13_25.1 [template = constants.%.9]
+// CHECK:STDOUT:   %float.greater_eq.loc13: init bool = call %GreaterEq.ref.loc13(%.loc13_26, %.loc13_25.2) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %float.greater_eq.loc13 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_13.2: bool = converted %float.greater_eq.loc13, %.loc13_13.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc13_13.2 br !if.expr.then.loc13 else br !if.expr.else.loc13
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc13:

+ 26 - 34
toolchain/check/testdata/builtins/float/less.carbon

@@ -41,17 +41,9 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.5: f64 = float_literal 2 [template]
 // CHECK:STDOUT:   %.6: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.9: bool = bool_literal false [template]
-// CHECK:STDOUT:   %.10: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.11: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.12: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.13: f64 = float_literal -1 [template]
-// CHECK:STDOUT:   %.14: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.15: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.16: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.17: f64 = float_literal -1 [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.8: f64 = float_literal 0 [template]
+// CHECK:STDOUT:   %.9: f64 = float_literal -1 [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.4: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -135,11 +127,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc9_13.3: type = block_arg !if.expr.result.loc9 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref.loc10: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Less.ref.loc10: Less = name_ref Less, file.%Less.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc10_22: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc10_27: f64 = float_literal 1 [template = constants.%.8]
-// CHECK:STDOUT:   %float.less.loc10: init bool = call %Less.ref.loc10(%.loc10_22, %.loc10_27) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc10_14.1: bool = value_of_initializer %float.less.loc10 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc10_14.2: bool = converted %float.less.loc10, %.loc10_14.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc10_22: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_27: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.less.loc10: init bool = call %Less.ref.loc10(%.loc10_22, %.loc10_27) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_14.1: bool = value_of_initializer %float.less.loc10 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_14.2: bool = converted %float.less.loc10, %.loc10_14.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc10_14.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc10:
@@ -154,11 +146,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc10_14.3: type = block_arg !if.expr.result.loc10 [template = constants.%False]
 // CHECK:STDOUT:   %false_.ref.loc11: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Less.ref.loc11: Less = name_ref Less, file.%Less.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc11_22: f64 = float_literal 1 [template = constants.%.10]
-// CHECK:STDOUT:   %.loc11_27: f64 = float_literal 0 [template = constants.%.11]
-// CHECK:STDOUT:   %float.less.loc11: init bool = call %Less.ref.loc11(%.loc11_22, %.loc11_27) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %float.less.loc11 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_14.2: bool = converted %float.less.loc11, %.loc11_14.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc11_22: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_27: f64 = float_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %float.less.loc11: init bool = call %Less.ref.loc11(%.loc11_22, %.loc11_27) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %float.less.loc11 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_14.2: bool = converted %float.less.loc11, %.loc11_14.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc11_14.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc11:
@@ -174,11 +166,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %true_.ref.loc12: True = name_ref true_, %true_
 // CHECK:STDOUT:   %Less.ref.loc12: Less = name_ref Less, file.%Less.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %Negate.ref.loc12: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc12_28: f64 = float_literal 1 [template = constants.%.12]
-// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_28) [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_34: f64 = float_literal 0 [template = constants.%.14]
-// CHECK:STDOUT:   %.loc12_20.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_20.2: f64 = converted %float.negate.loc12, %.loc12_20.1 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc12_28: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_28) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_34: f64 = float_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc12_20.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_20.2: f64 = converted %float.negate.loc12, %.loc12_20.1 [template = constants.%.9]
 // CHECK:STDOUT:   %float.less.loc12: init bool = call %Less.ref.loc12(%.loc12_20.2, %.loc12_34) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_13.1: bool = value_of_initializer %float.less.loc12 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_13.2: bool = converted %float.less.loc12, %.loc12_13.1 [template = constants.%.6]
@@ -196,15 +188,15 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc12_13.3: type = block_arg !if.expr.result.loc12 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref.loc13: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Less.ref.loc13: Less = name_ref Less, file.%Less.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc13_22: f64 = float_literal 0 [template = constants.%.15]
+// CHECK:STDOUT:   %.loc13_22: f64 = float_literal 0 [template = constants.%.8]
 // CHECK:STDOUT:   %Negate.ref.loc13: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc13_34: f64 = float_literal 1 [template = constants.%.16]
-// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_34) [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_21.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_21.2: f64 = converted %float.negate.loc13, %.loc13_21.1 [template = constants.%.17]
-// CHECK:STDOUT:   %float.less.loc13: init bool = call %Less.ref.loc13(%.loc13_22, %.loc13_21.2) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %float.less.loc13 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc13_14.2: bool = converted %float.less.loc13, %.loc13_14.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_34: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_34) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_21.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_21.2: f64 = converted %float.negate.loc13, %.loc13_21.1 [template = constants.%.9]
+// CHECK:STDOUT:   %float.less.loc13: init bool = call %Less.ref.loc13(%.loc13_22, %.loc13_21.2) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %float.less.loc13 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_14.2: bool = converted %float.less.loc13, %.loc13_14.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc13_14.2 br !if.expr.then.loc13 else br !if.expr.else.loc13
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc13:

+ 23 - 31
toolchain/check/testdata/builtins/float/less_eq.carbon

@@ -41,17 +41,9 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.5: f64 = float_literal 2 [template]
 // CHECK:STDOUT:   %.6: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.9: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.10: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.11: bool = bool_literal false [template]
-// CHECK:STDOUT:   %.12: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.13: f64 = float_literal -1 [template]
-// CHECK:STDOUT:   %.14: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.15: f64 = float_literal 0 [template]
-// CHECK:STDOUT:   %.16: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.17: f64 = float_literal -1 [template]
+// CHECK:STDOUT:   %.7: f64 = float_literal 0 [template]
+// CHECK:STDOUT:   %.8: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.9: f64 = float_literal -1 [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.4: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -135,8 +127,8 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc9_13.3: type = block_arg !if.expr.result.loc9 [template = constants.%True]
 // CHECK:STDOUT:   %true_.ref.loc10: True = name_ref true_, %true_
 // CHECK:STDOUT:   %LessEq.ref.loc10: LessEq = name_ref LessEq, file.%LessEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc10_23: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc10_28: f64 = float_literal 1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc10_23: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_28: f64 = float_literal 1 [template = constants.%.4]
 // CHECK:STDOUT:   %float.less_eq.loc10: init bool = call %LessEq.ref.loc10(%.loc10_23, %.loc10_28) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_13.1: bool = value_of_initializer %float.less_eq.loc10 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc10_13.2: bool = converted %float.less_eq.loc10, %.loc10_13.1 [template = constants.%.6]
@@ -154,11 +146,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc10_13.3: type = block_arg !if.expr.result.loc10 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref.loc11: False = name_ref false_, %false_
 // CHECK:STDOUT:   %LessEq.ref.loc11: LessEq = name_ref LessEq, file.%LessEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc11_24: f64 = float_literal 1 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc11_29: f64 = float_literal 0 [template = constants.%.10]
-// CHECK:STDOUT:   %float.less_eq.loc11: init bool = call %LessEq.ref.loc11(%.loc11_24, %.loc11_29) [template = constants.%.11]
-// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %float.less_eq.loc11 [template = constants.%.11]
-// CHECK:STDOUT:   %.loc11_14.2: bool = converted %float.less_eq.loc11, %.loc11_14.1 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc11_24: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_29: f64 = float_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %float.less_eq.loc11: init bool = call %LessEq.ref.loc11(%.loc11_24, %.loc11_29) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %float.less_eq.loc11 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_14.2: bool = converted %float.less_eq.loc11, %.loc11_14.1 [template = constants.%.8]
 // CHECK:STDOUT:   if %.loc11_14.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc11:
@@ -174,11 +166,11 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %true_.ref.loc12: True = name_ref true_, %true_
 // CHECK:STDOUT:   %LessEq.ref.loc12: LessEq = name_ref LessEq, file.%LessEq.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %Negate.ref.loc12: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc12_30: f64 = float_literal 1 [template = constants.%.12]
-// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_30) [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_36: f64 = float_literal 0 [template = constants.%.14]
-// CHECK:STDOUT:   %.loc12_22.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.13]
-// CHECK:STDOUT:   %.loc12_22.2: f64 = converted %float.negate.loc12, %.loc12_22.1 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc12_30: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc12: init f64 = call %Negate.ref.loc12(%.loc12_30) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_36: f64 = float_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_22.1: f64 = value_of_initializer %float.negate.loc12 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_22.2: f64 = converted %float.negate.loc12, %.loc12_22.1 [template = constants.%.9]
 // CHECK:STDOUT:   %float.less_eq.loc12: init bool = call %LessEq.ref.loc12(%.loc12_22.2, %.loc12_36) [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_13.1: bool = value_of_initializer %float.less_eq.loc12 [template = constants.%.6]
 // CHECK:STDOUT:   %.loc12_13.2: bool = converted %float.less_eq.loc12, %.loc12_13.1 [template = constants.%.6]
@@ -196,15 +188,15 @@ fn RuntimeCall(a: f64, b: f64) -> bool {
 // CHECK:STDOUT:   %.loc12_13.3: type = block_arg !if.expr.result.loc12 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref.loc13: False = name_ref false_, %false_
 // CHECK:STDOUT:   %LessEq.ref.loc13: LessEq = name_ref LessEq, file.%LessEq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc13_24: f64 = float_literal 0 [template = constants.%.15]
+// CHECK:STDOUT:   %.loc13_24: f64 = float_literal 0 [template = constants.%.7]
 // CHECK:STDOUT:   %Negate.ref.loc13: Negate = name_ref Negate, file.%Negate.decl [template = constants.%struct.2]
-// CHECK:STDOUT:   %.loc13_36: f64 = float_literal 1 [template = constants.%.16]
-// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_36) [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_23.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.17]
-// CHECK:STDOUT:   %.loc13_23.2: f64 = converted %float.negate.loc13, %.loc13_23.1 [template = constants.%.17]
-// CHECK:STDOUT:   %float.less_eq.loc13: init bool = call %LessEq.ref.loc13(%.loc13_24, %.loc13_23.2) [template = constants.%.11]
-// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %float.less_eq.loc13 [template = constants.%.11]
-// CHECK:STDOUT:   %.loc13_14.2: bool = converted %float.less_eq.loc13, %.loc13_14.1 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc13_36: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.negate.loc13: init f64 = call %Negate.ref.loc13(%.loc13_36) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.1: f64 = value_of_initializer %float.negate.loc13 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.2: f64 = converted %float.negate.loc13, %.loc13_23.1 [template = constants.%.9]
+// CHECK:STDOUT:   %float.less_eq.loc13: init bool = call %LessEq.ref.loc13(%.loc13_24, %.loc13_23.2) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %float.less_eq.loc13 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_14.2: bool = converted %float.less_eq.loc13, %.loc13_14.1 [template = constants.%.8]
 // CHECK:STDOUT:   if %.loc13_14.2 br !if.expr.then.loc13 else br !if.expr.else.loc13
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc13:

+ 6 - 8
toolchain/check/testdata/builtins/float/neq.carbon

@@ -44,9 +44,7 @@ fn WrongResult(a: f64, b: f64) -> f64 = "float.neq";
 // CHECK:STDOUT:   %.4: f64 = float_literal 1 [template]
 // CHECK:STDOUT:   %.5: f64 = float_literal 2 [template]
 // CHECK:STDOUT:   %.6: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.7: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.8: f64 = float_literal 1 [template]
-// CHECK:STDOUT:   %.9: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal false [template]
 // CHECK:STDOUT:   %RuntimeCall: type = fn_type @RuntimeCall [template]
 // CHECK:STDOUT:   %struct.3: RuntimeCall = struct_value () [template]
 // CHECK:STDOUT: }
@@ -122,11 +120,11 @@ fn WrongResult(a: f64, b: f64) -> f64 = "float.neq";
 // CHECK:STDOUT:   %.loc8_13.3: type = block_arg !if.expr.result.loc8 [template = constants.%True]
 // CHECK:STDOUT:   %false_.ref: False = name_ref false_, %false_
 // CHECK:STDOUT:   %Neq.ref.loc9: Neq = name_ref Neq, file.%Neq.decl [template = constants.%struct.1]
-// CHECK:STDOUT:   %.loc9_21: f64 = float_literal 1 [template = constants.%.7]
-// CHECK:STDOUT:   %.loc9_26: f64 = float_literal 1 [template = constants.%.8]
-// CHECK:STDOUT:   %float.neq.loc9: init bool = call %Neq.ref.loc9(%.loc9_21, %.loc9_26) [template = constants.%.9]
-// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %float.neq.loc9 [template = constants.%.9]
-// CHECK:STDOUT:   %.loc9_14.2: bool = converted %float.neq.loc9, %.loc9_14.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc9_21: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_26: f64 = float_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %float.neq.loc9: init bool = call %Neq.ref.loc9(%.loc9_21, %.loc9_26) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %float.neq.loc9 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_14.2: bool = converted %float.neq.loc9, %.loc9_14.1 [template = constants.%.7]
 // CHECK:STDOUT:   if %.loc9_14.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then.loc9:

+ 2 - 5
toolchain/driver/testdata/dump_shared_values.carbon

@@ -21,11 +21,8 @@ var str2: String = "ab'\"c";
 // CHECK:STDOUT:   ints:
 // CHECK:STDOUT:     int0:            32
 // CHECK:STDOUT:     int1:            1
-// CHECK:STDOUT:     int2:            32
-// CHECK:STDOUT:     int3:            8
-// CHECK:STDOUT:     int4:            64
-// CHECK:STDOUT:     int5:            64
-// CHECK:STDOUT:     int6:            64
+// CHECK:STDOUT:     int2:            8
+// CHECK:STDOUT:     int3:            64
 // CHECK:STDOUT:   reals:
 // CHECK:STDOUT:     real0:           10*10^-1
 // CHECK:STDOUT:     real1:           8*10^7

+ 4 - 2
toolchain/sem_ir/BUILD

@@ -18,6 +18,7 @@ cc_library(
     name = "block_value_store",
     hdrs = ["block_value_store.h"],
     deps = [
+        "//common:hashing",
         "//toolchain/base:value_store",
         "//toolchain/base:yaml",
         "@llvm-project//llvm:Support",
@@ -68,6 +69,7 @@ cc_library(
         ":ids",
         ":inst_kind",
         "//common:check",
+        "//common:hashing",
         "//common:ostream",
         "//common:struct_reflection",
         "//toolchain/base:index_base",
@@ -82,11 +84,10 @@ cc_library(
         "constant.cpp",
         "file.cpp",
         "function.cpp",
-        "inst_profile.cpp",
-        "inst_profile.h",
         "name.cpp",
     ],
     hdrs = [
+        "bind_name.h",
         "builtin_function_kind.h",
         "class.h",
         "constant.h",
@@ -113,6 +114,7 @@ cc_library(
         "//common:check",
         "//common:enum_base",
         "//common:error",
+        "//common:hashing",
         "//toolchain/base:kind_switch",
         "//toolchain/base:value_store",
         "//toolchain/base:yaml",

+ 76 - 0
toolchain/sem_ir/bind_name.h

@@ -0,0 +1,76 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_BIND_NAME_H_
+#define CARBON_TOOLCHAIN_SEM_IR_BIND_NAME_H_
+
+#include "common/hashing.h"
+#include "toolchain/base/value_store.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+struct BindNameInfo : public Printable<BindNameInfo> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{name: " << name_id << ", enclosing_scope: " << enclosing_scope_id
+        << ", index: " << bind_index << "}";
+  }
+
+  // The name.
+  NameId name_id;
+  // The enclosing scope.
+  NameScopeId enclosing_scope_id;
+  // The index for a compile-time binding. Invalid for a runtime binding.
+  CompileTimeBindIndex bind_index;
+};
+
+// Hashing for BindNameInfo.
+inline auto CarbonHashValue(const BindNameInfo& value, uint64_t seed)
+    -> HashCode {
+  Hasher hasher(seed);
+  hasher.Hash(value);
+  return static_cast<HashCode>(hasher);
+}
+
+// DenseMapInfo for BindNameInfo.
+struct BindNameInfoDenseMapInfo {
+  static auto getEmptyKey() -> BindNameInfo {
+    return BindNameInfo{.name_id = NameId::Invalid,
+                        .enclosing_scope_id = NameScopeId::Invalid,
+                        .bind_index = CompileTimeBindIndex(
+                            CompileTimeBindIndex::InvalidIndex - 1)};
+  }
+  static auto getTombstoneKey() -> BindNameInfo {
+    return BindNameInfo{.name_id = NameId::Invalid,
+                        .enclosing_scope_id = NameScopeId::Invalid,
+                        .bind_index = CompileTimeBindIndex(
+                            CompileTimeBindIndex::InvalidIndex - 2)};
+  }
+  static auto getHashValue(const BindNameInfo& val) -> unsigned {
+    return static_cast<uint64_t>(HashValue(val));
+  }
+  static auto isEqual(const BindNameInfo& lhs, const BindNameInfo& rhs)
+      -> bool {
+    return std::memcmp(&lhs, &rhs, sizeof(BindNameInfo)) == 0;
+  }
+};
+
+// Value store for BindNameInfo. In addition to the regular ValueStore
+// functionality, this can provide optional canonical IDs for BindNameInfos.
+struct BindNameStore : public ValueStore<BindNameId> {
+ public:
+  // Convert an ID to a canonical ID. All calls to this with equivalent
+  // `BindNameInfo`s will return the same `BindNameId`.
+  auto MakeCanonical(BindNameId id) -> BindNameId {
+    return canonical_ids_.insert({Get(id), id}).first->second;
+  }
+
+ private:
+  llvm::DenseMap<BindNameInfo, BindNameId, BindNameInfoDenseMapInfo>
+      canonical_ids_;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_BIND_NAME_H_

+ 57 - 0
toolchain/sem_ir/block_value_store.h

@@ -7,6 +7,7 @@
 
 #include <type_traits>
 
+#include "common/hashing.h"
 #include "llvm/ADT/DenseMap.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
@@ -47,6 +48,25 @@ class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT>> {
     return values_.Get(id);
   }
 
+  // Adds a block or finds an existing canonical block with the given content,
+  // and returns an ID to reference it.
+  auto AddCanonical(llvm::ArrayRef<ElementType> content) -> IdT {
+    auto [it, added] = canonical_blocks_.insert({{content}, IdT::Invalid});
+    if (added) {
+      auto id = Add(content);
+      it->first.data = Get(id);
+      it->second = id;
+    }
+    return it->second;
+  }
+
+  // Promotes an existing block ID to a canonical block ID, or returns an
+  // existing canonical block ID if the block was already added. The specified
+  // block must not be modified after this point.
+  auto MakeCanonical(IdT id) -> IdT {
+    return canonical_blocks_.insert({{Get(id)}, id}).first->second;
+  }
+
   auto OutputYaml() const -> Yaml::OutputMapping {
     return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
       for (auto block_index : llvm::seq(values_.size())) {
@@ -82,6 +102,41 @@ class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT>> {
   }
 
  private:
+  // A canonical block, for which we allocate a deduplicated ID.
+  struct CanonicalBlock {
+    // This is mutable so we can repoint it at the allocated data if insertion
+    // succeeds.
+    mutable llvm::ArrayRef<ElementType> data;
+
+    // See common/hashing.h.
+    friend auto CarbonHashValue(CanonicalBlock block, uint64_t seed)
+        -> HashCode {
+      Hasher hasher(seed);
+      hasher.HashSizedBytes(block.data);
+      return static_cast<HashCode>(hasher);
+    }
+  };
+
+  struct CanonicalBlockDenseMapInfo {
+    // Blocks whose data() points to the start of `SpecialData` are used to
+    // represent the special "empty" and "tombstone" states.
+    static constexpr ElementType SpecialData[1] = {ElementType::Invalid};
+    static auto getEmptyKey() -> CanonicalBlock {
+      return CanonicalBlock{
+          llvm::ArrayRef(SpecialData, static_cast<size_t>(0))};
+    }
+    static auto getTombstoneKey() -> CanonicalBlock {
+      return CanonicalBlock{llvm::ArrayRef(SpecialData, 1)};
+    }
+    static auto getHashValue(CanonicalBlock val) -> unsigned {
+      return static_cast<uint64_t>(HashValue(val));
+    }
+    static auto isEqual(CanonicalBlock lhs, CanonicalBlock rhs) -> bool {
+      return lhs.data == rhs.data && (lhs.data.data() == SpecialData) ==
+                                         (rhs.data.data() == SpecialData);
+    }
+  };
+
   // Allocates an uninitialized array using our slab allocator.
   auto AllocateUninitialized(std::size_t size)
       -> llvm::MutableArrayRef<ElementType> {
@@ -103,6 +158,8 @@ class BlockValueStore : public Yaml::Printable<BlockValueStore<IdT>> {
 
   llvm::BumpPtrAllocator* allocator_;
   ValueStore<IdT> values_;
+  llvm::DenseMap<CanonicalBlock, IdT, CanonicalBlockDenseMapInfo>
+      canonical_blocks_;
 };
 
 }  // namespace Carbon::SemIR

+ 14 - 62
toolchain/sem_ir/constant.cpp

@@ -4,73 +4,25 @@
 
 #include "toolchain/sem_ir/constant.h"
 
-#include "toolchain/sem_ir/inst_profile.h"
+#include "toolchain/sem_ir/file.h"
 
 namespace Carbon::SemIR {
 
 auto ConstantStore::GetOrAdd(Inst inst, bool is_symbolic) -> ConstantId {
-  // Check that we're allowed to form this kind of constant.
-  switch (inst.kind().constant_kind()) {
-    case InstConstantKind::Never:
-      CARBON_FATAL() << "Should not form a constant from instruction " << inst;
-      break;
-    case InstConstantKind::SymbolicOnly:
-      CARBON_CHECK(is_symbolic)
-          << "Should only form a symbolic constant from instruction " << inst;
-      break;
-    case InstConstantKind::Conditional:
-      break;
-    case InstConstantKind::Always:
-      CARBON_CHECK(!is_symbolic)
-          << "Should only form a template constant from instruction " << inst;
-      break;
+  auto [it, added] = map_.insert({inst, ConstantId::Invalid});
+  if (added) {
+    auto inst_id = sem_ir_.insts().AddInNoBlock(LocIdAndInst::NoLoc(inst));
+    auto const_id = is_symbolic
+                        ? SemIR::ConstantId::ForSymbolicConstant(inst_id)
+                        : SemIR::ConstantId::ForTemplateConstant(inst_id);
+    it->second = const_id;
+    sem_ir_.constant_values().Set(inst_id, const_id);
+    constants_.push_back(inst_id);
+  } else {
+    CARBON_CHECK(it->second != ConstantId::Invalid);
+    CARBON_CHECK(it->second.is_symbolic() == is_symbolic);
   }
-
-  // Compute the instruction's profile.
-  ConstantNode node = {.inst = inst, .constant_id = ConstantId::NotConstant};
-  llvm::FoldingSetNodeID id;
-  node.Profile(id, constants_.getContext());
-
-  // Check if we have already created this constant.
-  void* insert_pos;
-  if (ConstantNode* found = constants_.FindNodeOrInsertPos(id, insert_pos)) {
-    CARBON_CHECK(found->constant_id.is_constant())
-        << "Found non-constant in constant store for " << inst;
-    CARBON_CHECK(found->constant_id.is_symbolic() == is_symbolic)
-        << "Mismatch in phase for constant " << inst;
-    return found->constant_id;
-  }
-
-  // Create the new inst and insert the new node.
-  auto inst_id =
-      constants_.getContext()->insts().AddInNoBlock(LocIdAndInst::NoLoc(inst));
-  auto constant_id = is_symbolic
-                         ? SemIR::ConstantId::ForSymbolicConstant(inst_id)
-                         : SemIR::ConstantId::ForTemplateConstant(inst_id);
-  node.constant_id = constant_id;
-  constants_.InsertNode(new (*allocator_) ConstantNode(node), insert_pos);
-
-  // The constant value of any constant instruction is that instruction itself.
-  constants_.getContext()->constant_values().Set(inst_id, constant_id);
-  return constant_id;
-}
-
-auto ConstantStore::GetAsVector() const -> llvm::SmallVector<InstId, 0> {
-  llvm::SmallVector<InstId, 0> result;
-  result.reserve(constants_.size());
-  for (const ConstantNode& node : constants_) {
-    result.push_back(node.constant_id.inst_id());
-  }
-  // For stability, put the results into index order. This happens to also be
-  // insertion order.
-  std::sort(result.begin(), result.end(),
-            [](InstId a, InstId b) { return a.index < b.index; });
-  return result;
-}
-
-auto ConstantStore::ConstantNode::Profile(llvm::FoldingSetNodeID& id,
-                                          File* sem_ir) -> void {
-  ProfileConstant(id, *sem_ir, inst);
+  return it->second;
 }
 
 }  // namespace Carbon::SemIR

+ 6 - 17
toolchain/sem_ir/constant.h

@@ -57,8 +57,8 @@ class ConstantValueStore {
 // Provides storage for instructions representing deduplicated global constants.
 class ConstantStore {
  public:
-  explicit ConstantStore(File& sem_ir, llvm::BumpPtrAllocator& allocator)
-      : allocator_(&allocator), constants_(&sem_ir) {}
+  explicit ConstantStore(File& sem_ir, llvm::BumpPtrAllocator& /*allocator*/)
+      : sem_ir_(sem_ir) {}
 
   // Adds a new constant instruction, or gets the existing constant with this
   // value. Returns the ID of the constant.
@@ -69,25 +69,14 @@ class ConstantStore {
 
   // Returns a copy of the constant IDs as a vector, in an arbitrary but
   // stable order. This should not be used anywhere performance-sensitive.
-  auto GetAsVector() const -> llvm::SmallVector<InstId, 0>;
+  auto array_ref() const -> llvm::ArrayRef<InstId> { return constants_; }
 
   auto size() const -> int { return constants_.size(); }
 
  private:
-  // TODO: We store two copies of each constant instruction: one in insts() and
-  // one here. We could avoid one of those copies and store just an InstId here,
-  // at the cost of some more indirection when recomputing profiles during
-  // lookup. Once we have a representative data set, we should measure the
-  // impact on compile time from that change.
-  struct ConstantNode : llvm::FoldingSetNode {
-    Inst inst;
-    ConstantId constant_id;
-
-    auto Profile(llvm::FoldingSetNodeID& id, File* sem_ir) -> void;
-  };
-
-  llvm::BumpPtrAllocator* allocator_;
-  llvm::ContextualFoldingSet<ConstantNode, File*> constants_;
+  File& sem_ir_;
+  llvm::DenseMap<Inst, ConstantId> map_;
+  llvm::SmallVector<InstId, 0> constants_;
 };
 
 }  // namespace Carbon::SemIR

+ 6 - 0
toolchain/sem_ir/copy_on_write_block.h

@@ -43,6 +43,12 @@ class CopyOnWriteBlock {
   // called once all modifications have been performed.
   auto id() const -> BlockIdType { return id_; }
 
+  // Gets a canonical block ID containing the resulting elements. This assumes
+  // the original block ID, if specified, was also canonical.
+  auto GetCanonical() const -> BlockIdType {
+    return id_ == source_id_ ? id_ : (file_.*ValueStore)().MakeCanonical(id_);
+  }
+
   // Sets the element at index `i` within the block. Lazily allocates a new
   // block when the value changes for the first time.
   auto Set(int i, typename BlockIdType::ElementType value) -> void {

+ 8 - 25
toolchain/sem_ir/file.h

@@ -12,6 +12,7 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
+#include "toolchain/sem_ir/bind_name.h"
 #include "toolchain/sem_ir/class.h"
 #include "toolchain/sem_ir/constant.h"
 #include "toolchain/sem_ir/function.h"
@@ -27,22 +28,6 @@
 
 namespace Carbon::SemIR {
 
-struct BindNameInfo : public Printable<BindNameInfo> {
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "{name: " << name_id << ", enclosing_scope: " << enclosing_scope_id
-        << ", index: " << bind_index << "}";
-  }
-
-  // The name.
-  NameId name_id;
-  // The enclosing scope.
-  NameScopeId enclosing_scope_id;
-  // The index for a compile-time binding. Invalid for a runtime binding.
-  CompileTimeBindIndex bind_index;
-};
-
-class File;
-
 // Provides semantic analysis on a Parse::Tree.
 class File : public Printable<File> {
  public:
@@ -108,16 +93,16 @@ class File : public Printable<File> {
   auto identifiers() const -> const StringStoreWrapper<IdentifierId>& {
     return value_stores_->identifiers();
   }
-  auto ints() -> ValueStore<IntId>& { return value_stores_->ints(); }
-  auto ints() const -> const ValueStore<IntId>& {
+  auto ints() -> CanonicalValueStore<IntId>& { return value_stores_->ints(); }
+  auto ints() const -> const CanonicalValueStore<IntId>& {
     return value_stores_->ints();
   }
   auto reals() -> ValueStore<RealId>& { return value_stores_->reals(); }
   auto reals() const -> const ValueStore<RealId>& {
     return value_stores_->reals();
   }
-  auto floats() -> ValueStore<FloatId>& { return value_stores_->floats(); }
-  auto floats() const -> const ValueStore<FloatId>& {
+  auto floats() -> FloatValueStore& { return value_stores_->floats(); }
+  auto floats() const -> const FloatValueStore& {
     return value_stores_->floats();
   }
   auto string_literal_values() -> StringStoreWrapper<StringLiteralValueId>& {
@@ -128,10 +113,8 @@ class File : public Printable<File> {
     return value_stores_->string_literal_values();
   }
 
-  auto bind_names() -> ValueStore<BindNameId>& { return bind_names_; }
-  auto bind_names() const -> const ValueStore<BindNameId>& {
-    return bind_names_;
-  }
+  auto bind_names() -> BindNameStore& { return bind_names_; }
+  auto bind_names() const -> const BindNameStore& { return bind_names_; }
   auto functions() -> ValueStore<FunctionId>& { return functions_; }
   auto functions() const -> const ValueStore<FunctionId>& { return functions_; }
   auto classes() -> ValueStore<ClassId>& { return classes_; }
@@ -208,7 +191,7 @@ class File : public Printable<File> {
   std::string filename_;
 
   // Storage for bind names.
-  ValueStore<BindNameId> bind_names_;
+  BindNameStore bind_names_;
 
   // Storage for callable objects.
   ValueStore<FunctionId> functions_;

+ 1 - 1
toolchain/sem_ir/formatter.cpp

@@ -127,7 +127,7 @@ class Formatter {
     llvm::SaveAndRestore constants_scope(scope_, InstNamer::ScopeId::Constants);
     out_ << inst_namer_.GetScopeName(InstNamer::ScopeId::Constants) << " ";
     OpenBrace();
-    FormatCodeBlock(sem_ir_.constants().GetAsVector());
+    FormatCodeBlock(sem_ir_.constants().array_ref());
     CloseBrace();
     out_ << "\n\n";
   }

+ 7 - 0
toolchain/sem_ir/ids.h

@@ -203,6 +203,8 @@ struct CompileTimeBindIndex : public IndexBase,
 
 constexpr CompileTimeBindIndex CompileTimeBindIndex::Invalid =
     CompileTimeBindIndex(InvalidIndex);
+// Note that InvalidIndex - 1 and InvalidIndex - 2 are used by
+// DenseMapInfo<BindNameInfo>.
 
 // The ID of a function.
 struct FunctionId : public IdBase, public Printable<FunctionId> {
@@ -538,6 +540,9 @@ struct TypeBlockId : public IdBase, public Printable<TypeBlockId> {
   using ElementType = TypeId;
   using ValueType = llvm::MutableArrayRef<ElementType>;
 
+  // An explicitly invalid ID.
+  static const TypeBlockId Invalid;
+
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "typeBlock";
@@ -545,6 +550,8 @@ struct TypeBlockId : public IdBase, public Printable<TypeBlockId> {
   }
 };
 
+constexpr TypeBlockId TypeBlockId::Invalid = TypeBlockId(InvalidIndex);
+
 // An index for element access, for structs, tuples, and classes.
 struct ElementIndex : public IndexBase, public Printable<ElementIndex> {
   using IndexBase::IndexBase;

+ 2 - 2
toolchain/sem_ir/inst.cpp

@@ -7,7 +7,7 @@
 namespace Carbon::SemIR {
 
 auto Inst::Print(llvm::raw_ostream& out) const -> void {
-  out << "{kind: " << kind_;
+  out << "{kind: " << kind();
 
   auto print_args = [&](auto info) {
     using Info = decltype(info);
@@ -19,7 +19,7 @@ auto Inst::Print(llvm::raw_ostream& out) const -> void {
     }
   };
 
-  switch (kind_) {
+  switch (kind()) {
 #define CARBON_SEM_IR_INST_KIND(Name)               \
   case Name::Kind:                                  \
     print_args(Internal::InstLikeTypeInfo<Name>()); \

+ 41 - 7
toolchain/sem_ir/inst.h

@@ -9,6 +9,7 @@
 #include <cstdint>
 
 #include "common/check.h"
+#include "common/hashing.h"
 #include "common/ostream.h"
 #include "common/struct_reflection.h"
 #include "toolchain/base/index_base.h"
@@ -129,15 +130,17 @@ class Inst : public Printable<Inst> {
   // NOLINTNEXTLINE(google-explicit-constructor)
   Inst(TypedInst typed_inst)
       // kind_ is always overwritten below.
-      : kind_(InstKind::Make({})),
+      : kind_(),
         type_id_(TypeId::Invalid),
         arg0_(InstId::InvalidIndex),
         arg1_(InstId::InvalidIndex) {
     if constexpr (Internal::HasKindMemberAsField<TypedInst>) {
-      kind_ = typed_inst.kind;
+      kind_ = typed_inst.kind.AsInt();
     } else {
-      kind_ = TypedInst::Kind;
+      kind_ = TypedInst::Kind.AsInt();
     }
+    CARBON_CHECK(kind_ >= 0)
+        << "Negative kind values are reserved for DenseMapInfo.";
     if constexpr (Internal::HasTypeIdMember<TypedInst>) {
       type_id_ = typed_inst.type_id;
     }
@@ -205,7 +208,9 @@ class Inst : public Printable<Inst> {
     }
   }
 
-  auto kind() const -> InstKind { return kind_; }
+  auto kind() const -> InstKind {
+    return InstKind::Make(static_cast<InstKind::RawEnumType>(kind_));
+  }
 
   // Gets the type of the value produced by evaluating this instruction.
   auto type_id() const -> TypeId { return type_id_; }
@@ -220,7 +225,9 @@ class Inst : public Printable<Inst> {
   }
 
   // Gets the kinds of IDs used for arg0 and arg1 of this instruction.
-  auto ArgKinds() const -> std::pair<IdKind, IdKind> { return ArgKinds(kind_); }
+  auto ArgKinds() const -> std::pair<IdKind, IdKind> {
+    return ArgKinds(kind());
+  }
 
   // Gets the first argument of the instruction. InvalidIndex if there is no
   // such argument.
@@ -240,12 +247,15 @@ class Inst : public Printable<Inst> {
 
  private:
   friend class InstTestHelper;
+  friend struct llvm::DenseMapInfo<Carbon::SemIR::Inst>;
 
   // Table mapping instruction kinds to their argument kinds.
   static const std::pair<IdKind, IdKind> ArgKindTable[];
 
-  // Raw constructor, used for testing.
+  // Raw constructor, used for testing and by DenseMapInfo.
   explicit Inst(InstKind kind, TypeId type_id, int32_t arg0, int32_t arg1)
+      : Inst(kind.AsInt(), type_id, arg0, arg1) {}
+  explicit Inst(int32_t kind, TypeId type_id, int32_t arg0, int32_t arg1)
       : kind_(kind), type_id_(type_id), arg0_(arg0), arg1_(arg1) {}
 
   // Convert a field to its raw representation, used as `arg0_` / `arg1_`.
@@ -264,7 +274,7 @@ class Inst : public Printable<Inst> {
     return BuiltinKind::FromInt(raw);
   }
 
-  InstKind kind_;
+  int32_t kind_;
   TypeId type_id_;
 
   // Use `As` to access arg0 and arg1.
@@ -445,6 +455,30 @@ class InstBlockStore : public BlockValueStore<InstBlockId> {
   }
 };
 
+// See common/hashing.h.
+inline auto CarbonHashValue(const Inst& value, uint64_t seed) -> HashCode {
+  Hasher hasher(seed);
+  hasher.Hash(value);
+  return static_cast<HashCode>(hasher);
+}
+
 }  // namespace Carbon::SemIR
 
+template <>
+struct llvm::DenseMapInfo<Carbon::SemIR::Inst> {
+  using Inst = Carbon::SemIR::Inst;
+  static auto getEmptyKey() -> Inst {
+    return Inst(-1, Carbon::SemIR::TypeId::Invalid, 0, 0);
+  }
+  static auto getTombstoneKey() -> Inst {
+    return Inst(-2, Carbon::SemIR::TypeId::Invalid, 0, 0);
+  }
+  static auto getHashValue(const Inst& val) -> unsigned {
+    return static_cast<uint64_t>(Carbon::HashValue(val));
+  }
+  static auto isEqual(const Inst& lhs, const Inst& rhs) -> bool {
+    return std::memcmp(&lhs, &rhs, sizeof(Inst)) == 0;
+  }
+};
+
 #endif  // CARBON_TOOLCHAIN_SEM_IR_INST_H_

+ 1 - 1
toolchain/sem_ir/inst_namer.cpp

@@ -27,7 +27,7 @@ InstNamer::InstNamer(const Lex::TokenizedBuffer& tokenized_buffer,
   scopes.resize(static_cast<size_t>(GetScopeFor(NumberOfScopesTag())));
 
   // Build the constants scope.
-  CollectNamesInBlock(ScopeId::Constants, sem_ir.constants().GetAsVector());
+  CollectNamesInBlock(ScopeId::Constants, sem_ir.constants().array_ref());
 
   // Build the file scope.
   CollectNamesInBlock(ScopeId::File, sem_ir.top_inst_block_id());

+ 0 - 123
toolchain/sem_ir/inst_profile.cpp

@@ -1,123 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "toolchain/sem_ir/inst_profile.h"
-
-#include "toolchain/sem_ir/file.h"
-#include "toolchain/sem_ir/ids.h"
-#include "toolchain/sem_ir/inst.h"
-
-namespace Carbon::SemIR {
-
-// A function to profile an argument of an instruction.
-using ProfileArgFunction = auto(llvm::FoldingSetNodeID&, const File& sem_ir,
-                                int32_t arg) -> void;
-
-// Profiling for unused arguments.
-static auto NullProfileArgFunction(llvm::FoldingSetNodeID& /*id*/,
-                                   const File& /*sem_ir*/, int32_t arg)
-    -> void {
-  CARBON_CHECK(arg == IdBase::InvalidIndex)
-      << "Unexpected value for unused argument.";
-}
-
-// Profiling for ID arguments that should participate in the instruction's
-// value.
-static auto DefaultProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                      const File& /*sem_ir*/, int32_t arg)
-    -> void {
-  id.AddInteger(arg);
-}
-
-// Profiling for block ID arguments for which the content of the block should be
-// included.
-static auto InstBlockProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                        const File& sem_ir, int32_t arg)
-    -> void {
-  auto inst_block_id = InstBlockId(arg);
-  if (!inst_block_id.is_valid()) {
-    id.AddInteger(-1);
-    return;
-  }
-
-  auto inst_block = sem_ir.inst_blocks().Get(inst_block_id);
-  id.AddInteger(inst_block.size());
-  for (auto inst_id : inst_block) {
-    id.AddInteger(inst_id.index);
-  }
-}
-
-// Profiling for type block ID arguments for which the content of the block
-// should be included.
-static auto TypeBlockProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                        const File& sem_ir, int32_t arg)
-    -> void {
-  auto type_block_id = TypeBlockId(arg);
-  if (!type_block_id.is_valid()) {
-    id.AddInteger(-1);
-    return;
-  }
-
-  auto type_block = sem_ir.type_blocks().Get(type_block_id);
-  id.AddInteger(type_block.size());
-  for (auto type_id : type_block) {
-    id.AddInteger(type_id.index);
-  }
-}
-
-// Profiling for integer IDs.
-static auto IntProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                  const File& sem_ir, int32_t arg) -> void {
-  sem_ir.ints().Get(IntId(arg)).Profile(id);
-}
-
-// Profiling for real number IDs.
-static auto RealProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                   const File& sem_ir, int32_t arg) -> void {
-  const auto& real = sem_ir.reals().Get(RealId(arg));
-  // TODO: Profile the value rather than the syntactic form.
-  real.mantissa.Profile(id);
-  real.exponent.Profile(id);
-  id.AddBoolean(real.is_decimal);
-}
-
-// Profiling for BindNameInfo.
-static auto BindNameIdProfileArgFunction(llvm::FoldingSetNodeID& id,
-                                         const File& sem_ir, int32_t arg)
-    -> void {
-  const auto& [name_id, enclosing_scope_id, bind_index] =
-      sem_ir.bind_names().Get(BindNameId(arg));
-  id.AddInteger(name_id.index);
-  id.AddInteger(enclosing_scope_id.index);
-  id.AddInteger(bind_index.index);
-}
-
-// Profiles the given instruction argument, which is of the specified kind.
-static auto ProfileArg(llvm::FoldingSetNodeID& id, const File& sem_ir,
-                       IdKind arg_kind, int32_t arg) -> void {
-  static constexpr std::array<ProfileArgFunction*, IdKind::NumValues>
-      ProfileFunctions = [] {
-        std::array<ProfileArgFunction*, IdKind::NumValues> array;
-        array.fill(DefaultProfileArgFunction);
-        array[IdKind::None.ToIndex()] = NullProfileArgFunction;
-        array[IdKind::For<InstBlockId>.ToIndex()] = InstBlockProfileArgFunction;
-        array[IdKind::For<TypeBlockId>.ToIndex()] = TypeBlockProfileArgFunction;
-        array[IdKind::For<IntId>.ToIndex()] = IntProfileArgFunction;
-        array[IdKind::For<RealId>.ToIndex()] = RealProfileArgFunction;
-        array[IdKind::For<BindNameId>.ToIndex()] = BindNameIdProfileArgFunction;
-        return array;
-      }();
-  ProfileFunctions[arg_kind.ToIndex()](id, sem_ir, arg);
-}
-
-auto ProfileConstant(llvm::FoldingSetNodeID& id, const File& sem_ir, Inst inst)
-    -> void {
-  inst.kind().Profile(id);
-  id.AddInteger(inst.type_id().index);
-  auto arg_kinds = inst.ArgKinds();
-  ProfileArg(id, sem_ir, arg_kinds.first, inst.arg0());
-  ProfileArg(id, sem_ir, arg_kinds.second, inst.arg1());
-}
-
-}  // namespace Carbon::SemIR

+ 0 - 21
toolchain/sem_ir/inst_profile.h

@@ -1,21 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef CARBON_TOOLCHAIN_SEM_IR_INST_PROFILE_H_
-#define CARBON_TOOLCHAIN_SEM_IR_INST_PROFILE_H_
-
-#include "llvm/ADT/FoldingSet.h"
-#include "toolchain/sem_ir/file.h"
-#include "toolchain/sem_ir/inst.h"
-
-namespace Carbon::SemIR {
-
-// Computes a profile of the given constant instruction, for canonicalization /
-// deduplication.
-auto ProfileConstant(llvm::FoldingSetNodeID& id, const File& sem_ir, Inst inst)
-    -> void;
-
-}  // namespace Carbon::SemIR
-
-#endif  // CARBON_TOOLCHAIN_SEM_IR_INST_PROFILE_H_

+ 0 - 2
toolchain/sem_ir/name.cpp

@@ -5,8 +5,6 @@
 #include "toolchain/sem_ir/name.h"
 
 #include "llvm/ADT/StringSwitch.h"
-#include "toolchain/sem_ir/file.h"
-#include "toolchain/sem_ir/inst_profile.h"
 
 namespace Carbon::SemIR {