Ver código fonte

Add framework for singleton instructions. (#4582)

Adds a singleton framework, and converts `File` and `InstId::Print` to
demonstrate functionality. Moves various functions from `ids.h` to
`ids.cpp` because `Inst::Print` needs the file if `singleton_insts.h` is
split out, so the small bit of cleanup feels consistent.

I added `Inst::MakeSingleton` because getting the type of the
instruction to make from an `InstKind` felt too hard. Singleton
instructions follow a basic structure, so I'm just putting that
instruction structure into `Inst`. Previously we required macros to do
this, and I'm trying to remove macro dependencies.

I'm trying to remove builtin/singleton-related functionality from
`InstId` in order to get a clearer boundary for the functionality. The
other builtin functions should be removed as part of the bigger
migration, but I'm trying to carefully scope changes to verify agreement
on the singleton approach in use first.

This provides `InstT::SingletonInstId` because that'll often be written
as `SemIR::TypeType::SingletonInstId`. The alternative of something like
`SemIR::SingletonInstId<SemIR::TypeType>` is just a little more verbose
due to the repeated `SemIR`, and it's more consistent with
`TypeType::Kind`.

An alternative I considered was consolidating singleton information to
`InstKind`. This felt challenging because of the
`InstId::BuiltinTypeType` and similar values. Maintaining those would
turn into something like `InstKind::IsSingleton()` and
`InstId::Singleton<TypeType>`, which didn't feel like as good a split.
`InstId::Print` remains aware of singletons, but I'm hoping to remove
other builtin-related calls from `InstId`.

Another thing I considered was adding `.is_singleton = true` to
`InstKind::Definition`. I don't think we could rely on that to get
`InstId::Singleton<TypeType>` set up as `constexpr`, though. At that
point, I think it'd mainly be _just_ a comment-like annotation, maybe
validated with `CHECK` but not having any effect on its own. So I
decided not to do that, just adding comments instead.

Sidenotes:

- "singleton" naming was discussed [on
#toolchain](https://discord.com/channels/655572317891461132/655578254970716160/1308870233729269781).
- The TODO in file.h about possibly excluding other things than
singletons seems moot. That's for raw IR, and we're much more focused on
textual IR these days.

---------

Co-authored-by: David Blaikie <dblaikie@gmail.com>
Co-authored-by: Dana Jansens <danakj@orodu.net>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Co-authored-by: Boaz Brickner <brickner@google.com>
Co-authored-by: Geoff Romer <gromer@google.com>
Jon Ross-Perkins 1 ano atrás
pai
commit
f45cbc6028

+ 10 - 10
toolchain/check/testdata/basics/builtin_insts.carbon

@@ -33,31 +33,31 @@
 // CHECK:STDOUT:     type_block0:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     instTypeType:    {kind: TypeType, type: typeTypeType}
-// CHECK:STDOUT:     instErrorInst:   {kind: ErrorInst, type: typeError}
 // CHECK:STDOUT:     instAutoType:    {kind: AutoType, type: typeTypeType}
 // CHECK:STDOUT:     instBoolType:    {kind: BoolType, type: typeTypeType}
+// CHECK:STDOUT:     instBoundMethodType: {kind: BoundMethodType, type: typeTypeType}
+// CHECK:STDOUT:     instErrorInst:   {kind: ErrorInst, type: typeError}
 // CHECK:STDOUT:     instIntLiteralType: {kind: IntLiteralType, type: typeTypeType}
 // CHECK:STDOUT:     instLegacyFloatType: {kind: LegacyFloatType, type: typeTypeType}
-// CHECK:STDOUT:     instStringType:  {kind: StringType, type: typeTypeType}
-// CHECK:STDOUT:     instBoundMethodType: {kind: BoundMethodType, type: typeTypeType}
-// CHECK:STDOUT:     instSpecificFunctionType: {kind: SpecificFunctionType, type: typeTypeType}
 // CHECK:STDOUT:     instNamespaceType: {kind: NamespaceType, type: typeTypeType}
-// CHECK:STDOUT:     instWitnessType: {kind: WitnessType, type: typeTypeType}
+// CHECK:STDOUT:     instSpecificFunctionType: {kind: SpecificFunctionType, type: typeTypeType}
+// CHECK:STDOUT:     instStringType:  {kind: StringType, type: typeTypeType}
 // CHECK:STDOUT:     instVtableType:  {kind: VtableType, type: typeTypeType}
+// CHECK:STDOUT:     instWitnessType: {kind: WitnessType, type: typeTypeType}
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type(instNamespaceType)}
 // CHECK:STDOUT:   constant_values:
 // CHECK:STDOUT:     instTypeType:    templateConstant(instTypeType)
-// CHECK:STDOUT:     instErrorInst:   templateConstant(instErrorInst)
 // CHECK:STDOUT:     instAutoType:    templateConstant(instAutoType)
 // CHECK:STDOUT:     instBoolType:    templateConstant(instBoolType)
+// CHECK:STDOUT:     instBoundMethodType: templateConstant(instBoundMethodType)
+// CHECK:STDOUT:     instErrorInst:   templateConstant(instErrorInst)
 // CHECK:STDOUT:     instIntLiteralType: templateConstant(instIntLiteralType)
 // CHECK:STDOUT:     instLegacyFloatType: templateConstant(instLegacyFloatType)
-// CHECK:STDOUT:     instStringType:  templateConstant(instStringType)
-// CHECK:STDOUT:     instBoundMethodType: templateConstant(instBoundMethodType)
-// CHECK:STDOUT:     instSpecificFunctionType: templateConstant(instSpecificFunctionType)
 // CHECK:STDOUT:     instNamespaceType: templateConstant(instNamespaceType)
-// CHECK:STDOUT:     instWitnessType: templateConstant(instWitnessType)
+// CHECK:STDOUT:     instSpecificFunctionType: templateConstant(instSpecificFunctionType)
+// CHECK:STDOUT:     instStringType:  templateConstant(instStringType)
 // CHECK:STDOUT:     instVtableType:  templateConstant(instVtableType)
+// CHECK:STDOUT:     instWitnessType: templateConstant(instWitnessType)
 // CHECK:STDOUT:     'inst+0':          templateConstant(inst+0)
 // CHECK:STDOUT:   symbolic_constants: {}
 // CHECK:STDOUT:   inst_blocks:

+ 2 - 0
toolchain/sem_ir/BUILD

@@ -22,6 +22,7 @@ cc_library(
     name = "typed_insts",
     srcs = [
         "builtin_inst_kind.cpp",
+        "ids.cpp",
         "inst_kind.cpp",
     ],
     hdrs = [
@@ -29,6 +30,7 @@ cc_library(
         "id_kind.h",
         "ids.h",
         "inst_kind.h",
+        "singleton_insts.h",
         "typed_insts.h",
     ],
     textual_hdrs = ["inst_kind.def"],

+ 11 - 23
toolchain/sem_ir/file.cpp

@@ -11,7 +11,6 @@
 #include "toolchain/base/shared_value_stores.h"
 #include "toolchain/base/yaml.h"
 #include "toolchain/parse/node_ids.h"
-#include "toolchain/sem_ir/builtin_inst_kind.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst_kind.h"
@@ -42,23 +41,12 @@ File::File(CheckIRId check_ir_id,
   types_.SetValueRepr(TypeId::Error,
                       {.kind = ValueRepr::Copy, .type_id = TypeId::Error});
 
-  insts_.Reserve(BuiltinInstKind::ValidCount);
-// Error uses a self-referential type so that it's not accidentally treated as
-// a normal type. Every other builtin is a type, including the
-// self-referential TypeType.
-#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name)                         \
-  insts_.AddInNoBlock(LocIdAndInst::NoLoc<Name>(                      \
-      {.type_id = BuiltinInstKind::Name == BuiltinInstKind::ErrorInst \
-                      ? TypeId::Error                                 \
-                      : TypeId::TypeType}));
-#include "toolchain/sem_ir/inst_kind.def"
-  CARBON_CHECK(insts_.size() == BuiltinInstKind::ValidCount,
-               "Builtins should produce {0} insts, actual: {1}",
-               BuiltinInstKind::ValidCount, insts_.size());
-  for (auto i : llvm::seq(BuiltinInstKind::ValidCount)) {
-    auto builtin_id = SemIR::InstId(i);
-    constant_values_.Set(builtin_id,
-                         SemIR::ConstantId::ForTemplateConstant(builtin_id));
+  insts_.Reserve(SingletonInstKinds.size());
+  for (auto kind : SingletonInstKinds) {
+    auto inst_id =
+        insts_.AddInNoBlock(LocIdAndInst::NoLoc(Inst::MakeSingleton(kind)));
+    constant_values_.Set(inst_id,
+                         SemIR::ConstantId::ForTemplateConstant(inst_id));
   }
 }
 
@@ -99,9 +87,9 @@ auto File::Verify() const -> ErrorOr<Success> {
   return Success();
 }
 
-auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
-  return Yaml::OutputMapping([this,
-                              include_builtins](Yaml::OutputMapping::Map map) {
+auto File::OutputYaml(bool include_singletons) const -> Yaml::OutputMapping {
+  return Yaml::OutputMapping([this, include_singletons](
+                                 Yaml::OutputMapping::Map map) {
     map.Add("filename", filename_);
     map.Add(
         "sem_ir", Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
@@ -118,7 +106,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
           map.Add("type_blocks", type_blocks_.OutputYaml());
           map.Add(
               "insts", Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
-                int start = include_builtins ? 0 : BuiltinInstKind::ValidCount;
+                int start = include_singletons ? 0 : SingletonInstKinds.size();
                 for (int i : llvm::seq(start, insts_.size())) {
                   auto id = InstId(i);
                   map.Add(PrintToString(id),
@@ -128,7 +116,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
           map.Add("constant_values",
                   Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
                     int start =
-                        include_builtins ? 0 : BuiltinInstKind::ValidCount;
+                        include_singletons ? 0 : SingletonInstKinds.size();
                     for (int i : llvm::seq(start, insts_.size())) {
                       auto id = InstId(i);
                       auto value = constant_values_.Get(id);

+ 8 - 10
toolchain/sem_ir/file.h

@@ -28,6 +28,7 @@
 #include "toolchain/sem_ir/interface.h"
 #include "toolchain/sem_ir/name.h"
 #include "toolchain/sem_ir/name_scope.h"
+#include "toolchain/sem_ir/singleton_insts.h"
 #include "toolchain/sem_ir/struct_type_field.h"
 #include "toolchain/sem_ir/type.h"
 #include "toolchain/sem_ir/type_info.h"
@@ -48,16 +49,13 @@ class File : public Printable<File> {
   // Verifies that invariants of the semantics IR hold.
   auto Verify() const -> ErrorOr<Success>;
 
-  // Prints the full IR. Allow omitting builtins so that unrelated changes are
-  // less likely to alter test golden files.
-  // TODO: In the future, the things to print may change, for example by adding
-  // preludes. We may then want the ability to omit other things similar to
-  // builtins.
-  auto Print(llvm::raw_ostream& out, bool include_builtins = false) const
+  // Prints the full IR. Allow omitting singletons so that changes to the list
+  // of singletons won't churn golden test file content.
+  auto Print(llvm::raw_ostream& out, bool include_singletons = false) const
       -> void {
-    Yaml::Print(out, OutputYaml(include_builtins));
+    Yaml::Print(out, OutputYaml(include_singletons));
   }
-  auto OutputYaml(bool include_builtins) const -> Yaml::OutputMapping;
+  auto OutputYaml(bool include_singletons) const -> Yaml::OutputMapping;
 
   // Collects memory usage of members.
   auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
@@ -241,8 +239,8 @@ class File : public Printable<File> {
   // the data is provided by allocator_.
   BlockValueStore<TypeBlockId> type_blocks_;
 
-  // All instructions. The first entries will always be BuiltinInsts, at
-  // indices matching BuiltinInstKind ordering.
+  // All instructions. The first entries will always be the singleton
+  // instructions.
   InstStore insts_;
 
   // Storage for name scopes.

+ 174 - 0
toolchain/sem_ir/ids.cpp

@@ -0,0 +1,174 @@
+// 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/ids.h"
+
+#include "toolchain/sem_ir/singleton_insts.h"
+
+namespace Carbon::SemIR {
+
+auto InstId::Print(llvm::raw_ostream& out) const -> void {
+  out << "inst";
+  if (!is_valid()) {
+    IdBase::Print(out);
+  } else if (is_builtin()) {
+    out << builtin_inst_kind();
+  } else {
+    // Use the `+` as a small reminder that this is a delta, rather than an
+    // absolute index.
+    out << "+" << index - SingletonInstKinds.size();
+  }
+}
+
+auto ConstantId::Print(llvm::raw_ostream& out, bool disambiguate) const
+    -> void {
+  if (!is_valid()) {
+    IdBase::Print(out);
+  } else if (is_template()) {
+    if (disambiguate) {
+      out << "templateConstant(";
+    }
+    out << template_inst_id();
+    if (disambiguate) {
+      out << ")";
+    }
+  } else if (is_symbolic()) {
+    out << "symbolicConstant" << symbolic_index();
+  } else {
+    out << "runtime";
+  }
+}
+
+auto RuntimeParamIndex::Print(llvm::raw_ostream& out) const -> void {
+  out << "runtime_param";
+  if (*this == Unknown) {
+    out << "<unknown>";
+  } else {
+    IndexBase::Print(out);
+  }
+}
+
+auto GenericInstIndex::Print(llvm::raw_ostream& out) const -> void {
+  out << "genericInst";
+  if (is_valid()) {
+    out << (region() == Declaration ? "InDecl" : "InDef") << index();
+  } else {
+    out << "<invalid>";
+  }
+}
+
+auto BoolValue::Print(llvm::raw_ostream& out) const -> void {
+  if (*this == False) {
+    out << "false";
+  } else if (*this == True) {
+    out << "true";
+  } else {
+    CARBON_FATAL("Invalid bool value {0}", index);
+  }
+}
+
+auto IntKind::Print(llvm::raw_ostream& out) const -> void {
+  if (*this == Unsigned) {
+    out << "unsigned";
+  } else if (*this == Signed) {
+    out << "signed";
+  } else {
+    CARBON_FATAL("Invalid int kind value {0}", index);
+  }
+}
+
+auto NameId::ForIdentifier(IdentifierId id) -> NameId {
+  if (id.index >= 0) {
+    return NameId(id.index);
+  } else if (!id.is_valid()) {
+    return NameId::Invalid;
+  } else {
+    CARBON_FATAL("Unexpected identifier ID {0}", id);
+  }
+}
+
+auto NameId::Print(llvm::raw_ostream& out) const -> void {
+  out << "name";
+  if (*this == SelfValue) {
+    out << "SelfValue";
+  } else if (*this == SelfType) {
+    out << "SelfType";
+  } else if (*this == PeriodSelf) {
+    out << "PeriodSelf";
+  } else if (*this == ReturnSlot) {
+    out << "ReturnSlot";
+  } else if (*this == PackageNamespace) {
+    out << "PackageNamespace";
+  } else if (*this == Base) {
+    out << "Base";
+  } else {
+    CARBON_CHECK(!is_valid() || index >= 0, "Unknown index {0}", index);
+    IdBase::Print(out);
+  }
+}
+
+auto InstBlockId::Print(llvm::raw_ostream& out) const -> void {
+  if (*this == Unreachable) {
+    out << "unreachable";
+  } else if (*this == Empty) {
+    out << "empty";
+  } else if (*this == Exports) {
+    out << "exports";
+  } else if (*this == ImportRefs) {
+    out << "import_refs";
+  } else if (*this == GlobalInit) {
+    out << "global_init";
+  } else {
+    out << "block";
+    IdBase::Print(out);
+  }
+}
+
+auto TypeId::Print(llvm::raw_ostream& out) const -> void {
+  out << "type";
+  if (*this == TypeType) {
+    out << "TypeType";
+  } else if (*this == AutoType) {
+    out << "AutoType";
+  } else if (*this == Error) {
+    out << "Error";
+  } else {
+    out << "(";
+    AsConstantId().Print(out, /*disambiguate=*/false);
+    out << ")";
+  }
+}
+
+auto LibraryNameId::ForStringLiteralValueId(StringLiteralValueId id)
+    -> LibraryNameId {
+  CARBON_CHECK(id.index >= InvalidIndex, "Unexpected library name ID {0}", id);
+  if (id == StringLiteralValueId::Invalid) {
+    // Prior to SemIR, we use invalid to indicate `default`.
+    return LibraryNameId::Default;
+  } else {
+    return LibraryNameId(id.index);
+  }
+}
+
+auto LibraryNameId::Print(llvm::raw_ostream& out) const -> void {
+  out << "libraryName";
+  if (*this == Default) {
+    out << "Default";
+  } else if (*this == Error) {
+    out << "<error>";
+  } else {
+    IdBase::Print(out);
+  }
+}
+
+auto LocId::Print(llvm::raw_ostream& out) const -> void {
+  out << "loc_";
+  if (is_node_id() || !is_valid()) {
+    out << node_id();
+  } else {
+    out << import_ir_inst_id();
+  }
+}
+
+}  // namespace Carbon::SemIR

+ 13 - 150
toolchain/sem_ir/ids.h

@@ -66,18 +66,7 @@ struct InstId : public IdBase, public Printable<InstId> {
     return BuiltinInstKind::FromInt(index);
   }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "inst";
-    if (!is_valid()) {
-      IdBase::Print(out);
-    } else if (is_builtin()) {
-      out << builtin_inst_kind();
-    } else {
-      // Use the `+` as a small reminder that this is a delta, rather than an
-      // absolute index.
-      out << "+" << index - BuiltinInstKind::ValidCount;
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr InstId InstId::Invalid = InstId(InvalidIndex);
@@ -165,23 +154,7 @@ struct ConstantId : public IdBase, public Printable<ConstantId> {
   // template constants should be wrapped with "templateConstant(...)" so that
   // they aren't printed the same as an InstId. This can be set to false if
   // there is no risk of ambiguity.
-  auto Print(llvm::raw_ostream& out, bool disambiguate = true) const -> void {
-    if (!is_valid()) {
-      IdBase::Print(out);
-    } else if (is_template()) {
-      if (disambiguate) {
-        out << "templateConstant(";
-      }
-      out << template_inst_id();
-      if (disambiguate) {
-        out << ")";
-      }
-    } else if (is_symbolic()) {
-      out << "symbolicConstant" << symbolic_index();
-    } else {
-      out << "runtime";
-    }
-  }
+  auto Print(llvm::raw_ostream& out, bool disambiguate = true) const -> void;
 
  private:
   friend class ConstantValueStore;
@@ -266,14 +239,7 @@ struct RuntimeParamIndex : public IndexBase,
 
   using IndexBase::IndexBase;
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "runtime_param";
-    if (*this == Unknown) {
-      out << "<unknown>";
-    } else {
-      IndexBase::Print(out);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr RuntimeParamIndex RuntimeParamIndex::Invalid =
@@ -441,14 +407,7 @@ struct GenericInstIndex : public IndexBase, public Printable<GenericInstIndex> {
     return IndexBase::index >= 0 ? Declaration : Definition;
   }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "genericInst";
-    if (is_valid()) {
-      out << (region() == Declaration ? "InDecl" : "InDef") << index();
-    } else {
-      out << "<invalid>";
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 
  private:
   static constexpr auto MakeInvalid() -> GenericInstIndex {
@@ -501,15 +460,7 @@ struct BoolValue : public IdBase, public Printable<BoolValue> {
   }
 
   using IdBase::IdBase;
-  auto Print(llvm::raw_ostream& out) const -> void {
-    if (*this == False) {
-      out << "false";
-    } else if (*this == True) {
-      out << "true";
-    } else {
-      CARBON_FATAL("Invalid bool value {0}", index);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr BoolValue BoolValue::False = BoolValue(0);
@@ -528,15 +479,7 @@ struct IntKind : public IdBase, public Printable<IntKind> {
   // Returns whether this type is signed.
   constexpr auto is_signed() -> bool { return *this == Signed; }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    if (*this == Unsigned) {
-      out << "unsigned";
-    } else if (*this == Signed) {
-      out << "signed";
-    } else {
-      CARBON_FATAL("Invalid int kind value {0}", index);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr IntKind IntKind::Unsigned = IntKind(0);
@@ -577,15 +520,7 @@ struct NameId : public IdBase, public Printable<NameId> {
   static const int NonIndexValueCount;
 
   // Returns the NameId corresponding to a particular IdentifierId.
-  static auto ForIdentifier(IdentifierId id) -> NameId {
-    if (id.index >= 0) {
-      return NameId(id.index);
-    } else if (!id.is_valid()) {
-      return NameId::Invalid;
-    } else {
-      CARBON_FATAL("Unexpected identifier ID {0}", id);
-    }
-  }
+  static auto ForIdentifier(IdentifierId id) -> NameId;
 
   using IdBase::IdBase;
 
@@ -595,25 +530,7 @@ struct NameId : public IdBase, public Printable<NameId> {
     return index >= 0 ? IdentifierId(index) : IdentifierId::Invalid;
   }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "name";
-    if (*this == SelfValue) {
-      out << "SelfValue";
-    } else if (*this == SelfType) {
-      out << "SelfType";
-    } else if (*this == PeriodSelf) {
-      out << "PeriodSelf";
-    } else if (*this == ReturnSlot) {
-      out << "ReturnSlot";
-    } else if (*this == PackageNamespace) {
-      out << "PackageNamespace";
-    } else if (*this == Base) {
-      out << "Base";
-    } else {
-      CARBON_CHECK(!is_valid() || index >= 0, "Unknown index {0}", index);
-      IdBase::Print(out);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr NameId NameId::Invalid = NameId(InvalidIndex);
@@ -676,22 +593,7 @@ struct InstBlockId : public IdBase, public Printable<InstBlockId> {
   static const InstBlockId Unreachable;
 
   using IdBase::IdBase;
-  auto Print(llvm::raw_ostream& out) const -> void {
-    if (*this == Unreachable) {
-      out << "unreachable";
-    } else if (*this == Empty) {
-      out << "empty";
-    } else if (*this == Exports) {
-      out << "exports";
-    } else if (*this == ImportRefs) {
-      out << "import_refs";
-    } else if (*this == GlobalInit) {
-      out << "global_init";
-    } else {
-      out << "block";
-      IdBase::Print(out);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr InstBlockId InstBlockId::Empty = InstBlockId(0);
@@ -756,20 +658,7 @@ struct TypeId : public IdBase, public Printable<TypeId> {
   // Returns the constant ID that defines the type.
   auto AsConstantId() const -> ConstantId { return ConstantId(index); }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "type";
-    if (*this == TypeType) {
-      out << "TypeType";
-    } else if (*this == AutoType) {
-      out << "AutoType";
-    } else if (*this == Error) {
-      out << "Error";
-    } else {
-      out << "(";
-      AsConstantId().Print(out, /*disambiguate=*/false);
-      out << ")";
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr TypeId TypeId::TypeType = TypeId::ForTypeConstant(
@@ -829,17 +718,7 @@ struct LibraryNameId : public IdBase, public Printable<NameId> {
   static const LibraryNameId Error;
 
   // Returns the LibraryNameId for a library name as a string literal.
-  static auto ForStringLiteralValueId(StringLiteralValueId id)
-      -> LibraryNameId {
-    CARBON_CHECK(id.index >= InvalidIndex, "Unexpected library name ID {0}",
-                 id);
-    if (id == StringLiteralValueId::Invalid) {
-      // Prior to SemIR, we use invalid to indicate `default`.
-      return LibraryNameId::Default;
-    } else {
-      return LibraryNameId(id.index);
-    }
-  }
+  static auto ForStringLiteralValueId(StringLiteralValueId id) -> LibraryNameId;
 
   using IdBase::IdBase;
 
@@ -849,16 +728,7 @@ struct LibraryNameId : public IdBase, public Printable<NameId> {
     return StringLiteralValueId(index);
   }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "libraryName";
-    if (*this == Default) {
-      out << "Default";
-    } else if (*this == Error) {
-      out << "<error>";
-    } else {
-      IdBase::Print(out);
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr LibraryNameId LibraryNameId::Invalid = LibraryNameId(InvalidIndex);
@@ -945,14 +815,7 @@ struct LocId : public IdBase, public Printable<LocId> {
     return ImportIRInstId(InvalidIndex + ImportIRInstId::InvalidIndex - index);
   }
 
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "loc_";
-    if (is_node_id() || !is_valid()) {
-      out << node_id();
-    } else {
-      out << import_ir_inst_id();
-    }
-  }
+  auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr LocId LocId::Invalid = LocId(Parse::NodeId::Invalid);

+ 13 - 0
toolchain/sem_ir/inst.h

@@ -19,6 +19,7 @@
 #include "toolchain/sem_ir/builtin_inst_kind.h"
 #include "toolchain/sem_ir/id_kind.h"
 #include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/singleton_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::SemIR {
@@ -127,6 +128,18 @@ concept InstLikeType = requires { sizeof(InstLikeTypeInfo<T>); };
 //   data where the instruction's kind is not known.
 class Inst : public Printable<Inst> {
  public:
+  // Makes an instruction for a singleton. This exists to support simple
+  // construction of all singletons by File.
+  static auto MakeSingleton(InstKind kind) -> Inst {
+    CARBON_CHECK(IsSingletonInstKind(kind));
+    // Error uses a self-referential type so that it's not accidentally treated
+    // as a normal type. Every other builtin is a type, including the
+    // self-referential TypeType.
+    auto type_id =
+        kind == InstKind::ErrorInst ? TypeId::Error : TypeId::TypeType;
+    return Inst(kind, type_id, InstId::InvalidIndex, InstId::InvalidIndex);
+  }
+
   template <typename TypedInst>
     requires Internal::InstLikeType<TypedInst>
   // NOLINTNEXTLINE(google-explicit-constructor)

+ 5 - 5
toolchain/sem_ir/inst_kind.def

@@ -37,17 +37,17 @@
 #endif
 
 CARBON_SEM_IR_BUILTIN_INST_KIND(TypeType)
-CARBON_SEM_IR_BUILTIN_INST_KIND(ErrorInst)
 CARBON_SEM_IR_BUILTIN_INST_KIND(AutoType)
 CARBON_SEM_IR_BUILTIN_INST_KIND(BoolType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(BoundMethodType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(ErrorInst)
 CARBON_SEM_IR_BUILTIN_INST_KIND(IntLiteralType)
 CARBON_SEM_IR_BUILTIN_INST_KIND(LegacyFloatType)
-CARBON_SEM_IR_BUILTIN_INST_KIND(StringType)
-CARBON_SEM_IR_BUILTIN_INST_KIND(BoundMethodType)
-CARBON_SEM_IR_BUILTIN_INST_KIND(SpecificFunctionType)
 CARBON_SEM_IR_BUILTIN_INST_KIND(NamespaceType)
-CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(SpecificFunctionType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(StringType)
 CARBON_SEM_IR_BUILTIN_INST_KIND(VtableType)
+CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType)
 
 // For each instruction kind declared here there is a matching definition in
 // `typed_insts.h`.

+ 1 - 0
toolchain/sem_ir/inst_kind.h

@@ -104,6 +104,7 @@ class InstKind : public CARBON_ENUM_BASE(InstKind) {
 
   using EnumBase::AsInt;
   using EnumBase::FromInt;
+  using EnumBase::Make;
 
   // Returns true if the kind matches any of the provided instructions' kinds.
   template <typename... InstT>

+ 76 - 0
toolchain/sem_ir/singleton_insts.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_SINGLETON_INSTS_H_
+#define CARBON_TOOLCHAIN_SEM_IR_SINGLETON_INSTS_H_
+
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst_kind.h"
+
+namespace Carbon::SemIR {
+
+// The canonical list of singleton kinds. The order of `TypeType` is
+// significant because other singletons use it as a type.
+static constexpr std::array SingletonInstKinds = {
+    InstKind::TypeType,
+    InstKind::AutoType,
+    InstKind::BoolType,
+    InstKind::BoundMethodType,
+    InstKind::ErrorInst,
+    InstKind::IntLiteralType,
+    InstKind::LegacyFloatType,
+    InstKind::NamespaceType,
+    InstKind::SpecificFunctionType,
+    InstKind::StringType,
+    InstKind::VtableType,
+    InstKind::WitnessType,
+};
+
+// Returns true if the InstKind is a singleton.
+inline constexpr auto IsSingletonInstKind(InstKind kind) -> bool;
+
+// Provides the InstId for singleton instructions. These are exposed as
+// `InstT::SingletonInstId` in `typed_insts.h`.
+template <InstKind::RawEnumType Kind>
+  requires(IsSingletonInstKind(InstKind::Make(Kind)))
+inline constexpr auto MakeSingletonInstId() -> InstId;
+
+// Only implementation details are below.
+
+namespace Internal {
+
+// Returns the index for a singleton instruction, or -1 if it's not a singleton.
+inline constexpr auto GetSingletonInstIndex(InstKind kind) -> int32_t {
+  for (int32_t i = 0; i < static_cast<int32_t>(SingletonInstKinds.size());
+       ++i) {
+    if (SingletonInstKinds[i] == kind) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+}  // namespace Internal
+
+inline constexpr auto IsSingletonInstKind(InstKind kind) -> bool {
+  return Internal::GetSingletonInstIndex(kind) >= 0;
+}
+
+template <InstKind::RawEnumType Kind>
+  requires(IsSingletonInstKind(InstKind::Make(Kind)))
+inline constexpr auto MakeSingletonInstId() -> InstId {
+  auto index = Internal::GetSingletonInstIndex(InstKind::Make(Kind));
+  return InstId(index);
+}
+
+// TODO: This verifies values match while working on removing
+// `CARBON_SEM_IR_BUILTIN_INST_KIND`.
+#define CARBON_SEM_IR_BUILTIN_INST_KIND(Name) \
+  static_assert(InstId::Builtin##Name ==      \
+                MakeSingletonInstId<InstKind::RawEnumType::Name>());
+#include "toolchain/sem_ir/inst_kind.def"
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_SINGLETON_INSTS_H_

+ 32 - 35
toolchain/sem_ir/typed_insts.h

@@ -10,6 +10,7 @@
 #include "toolchain/sem_ir/builtin_inst_kind.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/singleton_insts.h"
 
 // Representations for specific kinds of instructions.
 //
@@ -17,6 +18,7 @@
 //
 // - Either a `Kind` constant, or a `Kinds` constant and an `InstKind kind;`
 //   member. These are described below.
+// - Optionally, a `SingletonInstId` if it is a singleton instruction.
 // - Optionally, a `TypeId type_id;` member, for instructions that produce a
 //   value. This includes instructions that produce an abstract value, such as a
 //   `Namespace`, for which a placeholder type should be used.
@@ -49,28 +51,25 @@
 namespace Carbon::SemIR {
 
 // Used for the type of patterns that do not match a fixed type.
-//
-// TODO: Annotate as a builtin.
 struct AutoType {
   static constexpr auto Kind = InstKind::AutoType.Define<Parse::InvalidNodeId>(
       {.ir_name = "auto",
        .is_type = InstIsType::Always,
        .constant_kind = InstConstantKind::Always});
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
 
 // The type of bool literals and branch conditions, bool.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
-//
-// TODO: Annotate as a builtin.
 struct BoolType {
   static constexpr auto Kind = InstKind::BoolType.Define<Parse::InvalidNodeId>(
       {.ir_name = "bool",
        .is_type = InstIsType::Always,
        .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -401,15 +400,15 @@ struct BoundMethod {
 };
 
 // The type of bound method values.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct BoundMethodType {
   static constexpr auto Kind =
       InstKind::BoundMethodType.Define<Parse::InvalidNodeId>(
           {.ir_name = "<bound method>",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -584,13 +583,12 @@ struct Deref {
 // required. For example, when there is a type checking issue, this will be used
 // in the type_id. It's typically used as a cue that semantic checking doesn't
 // need to issue further diagnostics.
-//
-// TODO: Annotate as a builtin.
 struct ErrorInst {
   static constexpr auto Kind = InstKind::ErrorInst.Define<Parse::InvalidNodeId>(
       {.ir_name = "<error>",
        .is_type = InstIsType::Always,
        .constant_kind = InstConstantKind::Always});
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -703,15 +701,15 @@ struct FloatType {
 // The legacy float type. This is currently used for real literals, and is
 // treated as f64. It's separate from `FloatType`, and should change to mirror
 // integers, likely replacing this with a `FloatLiteralType`.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct LegacyFloatType {
   static constexpr auto Kind =
       InstKind::LegacyFloatType.Define<Parse::InvalidNodeId>(
           {.ir_name = "f64",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -899,15 +897,15 @@ struct IntValue {
 // literals and as the parameter type of `Core.Int` and `Core.Float`. This type
 // only provides compile-time operations, and is represented as an empty type at
 // runtime.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct IntLiteralType {
   static constexpr auto Kind =
       InstKind::IntLiteralType.Define<Parse::InvalidNodeId>(
           {.ir_name = "Core.IntLiteral",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -953,15 +951,15 @@ struct Namespace {
 };
 
 // The type of namespace and imported package names.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct NamespaceType {
   static constexpr auto Kind =
       InstKind::NamespaceType.Define<Parse::InvalidNodeId>(
           {.ir_name = "<namespace>",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -1185,15 +1183,15 @@ struct SpecificFunction {
 };
 
 // The type of specific functions.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct SpecificFunctionType {
   static constexpr auto Kind =
       InstKind::SpecificFunctionType.Define<Parse::InvalidNodeId>(
           {.ir_name = "<specific function>",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -1223,15 +1221,15 @@ struct StringLiteral {
 };
 
 // The type of string values and String literals.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct StringType {
   static constexpr auto Kind =
       InstKind::StringType.Define<Parse::InvalidNodeId>(
           {.ir_name = "String",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -1370,13 +1368,12 @@ struct TupleValue {
 
 // Tracks expressions which are valid as types. This has a deliberately
 // self-referential type.
-//
-// TODO: Annotate as a builtin.
 struct TypeType {
   static constexpr auto Kind = InstKind::TypeType.Define<Parse::InvalidNodeId>(
       {.ir_name = "type",
        .is_type = InstIsType::Always,
        .constant_kind = InstConstantKind::Always});
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -1444,15 +1441,15 @@ struct VarStorage {
 };
 
 // The type of virtual function tables.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct VtableType {
   static constexpr auto Kind =
       InstKind::VtableType.Define<Parse::InvalidNodeId>(
           {.ir_name = "<vtable>",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };
@@ -1472,15 +1469,15 @@ struct WhereExpr {
 };
 
 // The type of witnesses.
-//
-// Although this is a builtin, it may still evolve to a more standard type and
-// be removed.
 struct WitnessType {
   static constexpr auto Kind =
       InstKind::WitnessType.Define<Parse::InvalidNodeId>(
           {.ir_name = "<witness>",
            .is_type = InstIsType::Always,
            .constant_kind = InstConstantKind::Always});
+  // This is a singleton instruction. However, it may still evolve into a more
+  // standard type and be removed.
+  static constexpr auto SingletonInstId = MakeSingletonInstId<Kind>();
 
   TypeId type_id;
 };