Преглед на файлове

Add well-known identifier caching (#6486)

I'm doing this because I figured it'd be an incremental improvement for
all the operator lookups that we do. Even to the extent that we've
discussed witness caching, I think it'll still apply. It does add one
more step to adding new interfaces (before, you'd just write the string,
now you add it to the def file and reference it).

I'll claim it makes GetClangOperatorKind a lot friendlier to read/edit,
nevermind removing the string comparisons. :)
Jon Ross-Perkins преди 4 месеца
родител
ревизия
c0b335b87f

+ 15 - 0
toolchain/check/BUILD

@@ -134,6 +134,7 @@ cc_library(
         "type_structure.h",
     ],
     deps = [
+        ":core_identifier",
         ":node_stack",
         "//common:array_stack",
         "//common:check",
@@ -217,6 +218,7 @@ cc_library(
     hdrs = ["check.h"],
     deps = [
         ":context",
+        ":core_identifier",
         ":diagnostic_emitter",
         ":dump",
         ":scope_stack",
@@ -322,6 +324,19 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "core_identifier",
+    srcs = ["core_identifier.cpp"],
+    hdrs = ["core_identifier.h"],
+    textual_hdrs = ["core_identifier.def"],
+    deps = [
+        "//common:enum_base",
+        "//toolchain/base:shared_value_stores",
+        "//toolchain/base:value_ids",
+        "//toolchain/sem_ir:typed_insts",
+    ],
+)
+
 cc_library(
     name = "diagnostic_emitter",
     srcs = ["diagnostic_emitter.cpp"],

+ 2 - 1
toolchain/check/context.cpp

@@ -38,7 +38,8 @@ Context::Context(DiagnosticEmitterBase* emitter,
       global_init_(this),
       region_stack_([this](SemIR::LocId loc_id, std::string label) {
         TODO(loc_id, label);
-      }) {
+      }),
+      core_identifiers_(&identifiers()) {
   // Prepare fields which relate to the number of IRs available for import.
   import_irs().Reserve(imported_ir_count);
   import_ir_constant_values_.reserve(imported_ir_count);

+ 6 - 0
toolchain/check/context.h

@@ -12,6 +12,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/base/canonical_value_store.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/cpp/context.h"
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/decl_name_stack.h"
@@ -279,6 +280,8 @@ class Context {
     return *std::exchange(return_type_inst_id_, std::nullopt);
   }
 
+  auto core_identifiers() -> CoreIdentifierCache& { return core_identifiers_; }
+
   // --------------------------------------------------------------------------
   // Directly expose SemIR::File data accessors for brevity in calls.
   // --------------------------------------------------------------------------
@@ -517,6 +520,9 @@ class Context {
 
   // Declared return type for the in-progress function declaration, if any.
   std::optional<SemIR::TypeInstId> return_type_inst_id_;
+
+  // See `CoreIdentifierCache` for details.
+  CoreIdentifierCache core_identifiers_;
 };
 
 }  // namespace Carbon::Check

+ 1 - 1
toolchain/check/control_flow.cpp

@@ -162,7 +162,7 @@ static auto AddCleanupBlock(Context& context) -> void {
     // cleanup blocks, so we'll want to avoid this in the future.
     BuildUnaryOperator(context,
                        context.insts().GetLocIdForDesugaring(destroy_id),
-                       {.interface_name = "Destroy"}, destroy_id);
+                       {.interface_name = CoreIdentifier::Destroy}, destroy_id);
   }
 }
 

+ 8 - 6
toolchain/check/convert.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/check/action.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/control_flow.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/impl_lookup.h"
@@ -1367,7 +1368,8 @@ static auto PerformCopy(Context& context, SemIR::InstId expr_id,
   }
 
   auto copy_id = BuildUnaryOperator(
-      context, SemIR::LocId(expr_id), {"Copy"}, expr_id, [&] {
+      context, SemIR::LocId(expr_id), {.interface_name = CoreIdentifier::Copy},
+      expr_id, [&] {
         if (!target.diagnose) {
           return context.emitter().BuildSuppressed();
         }
@@ -1407,14 +1409,14 @@ static auto ConvertValueForCppThunkRef(Context& context, SemIR::InstId expr_id)
 
 // Returns the Core interface name to use for a given kind of conversion.
 static auto GetConversionInterfaceName(ConversionTarget::Kind kind)
-    -> llvm::StringLiteral {
+    -> CoreIdentifier {
   switch (kind) {
     case ConversionTarget::ExplicitAs:
-      return "As";
+      return CoreIdentifier::As;
     case ConversionTarget::ExplicitUnsafeAs:
-      return "UnsafeAs";
+      return CoreIdentifier::UnsafeAs;
     default:
-      return "ImplicitAs";
+      return CoreIdentifier::ImplicitAs;
   }
 }
 
@@ -1559,7 +1561,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
     Operator op = {
         .interface_name = GetConversionInterfaceName(target.kind),
         .interface_args_ref = interface_args,
-        .op_name = "Convert",
+        .op_name = CoreIdentifier::Convert,
     };
     expr_id = BuildUnaryOperator(context, loc_id, op, expr_id, [&] {
       if (!target.diagnose) {

+ 14 - 0
toolchain/check/core_identifier.cpp

@@ -0,0 +1,14 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/core_identifier.h"
+
+namespace Carbon::Check {
+
+CARBON_DEFINE_ENUM_CLASS_NAMES(CoreIdentifier) {
+#define CARBON_CORE_IDENTIFIER(Name) CARBON_ENUM_CLASS_NAME_STRING(Name)
+#include "toolchain/check/core_identifier.def"
+};
+
+}  // namespace Carbon::Check

+ 81 - 0
toolchain/check/core_identifier.def

@@ -0,0 +1,81 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// This is an X-macro header. It does not use `#include` guards, and instead is
+// designed to be `#include`ed after the x-macro is defined in order for its
+// inclusion to expand to the desired output. Macro definitions are cleaned up
+// at the end of this file.
+//
+// This macro should be defined before including this header:
+// - CARBON_CORE_IDENTIFIER(Name)
+//   Invoked for each `Core` identifier.
+
+#ifndef CARBON_CORE_IDENTIFIER
+#error "Must define the x-macro to use this file."
+#define CARBON_CORE_IDENTIFIER(Name)
+#endif
+
+CARBON_CORE_IDENTIFIER(AddAssignWith)
+CARBON_CORE_IDENTIFIER(AddWith)
+CARBON_CORE_IDENTIFIER(As)
+CARBON_CORE_IDENTIFIER(AssignWith)
+CARBON_CORE_IDENTIFIER(At)
+CARBON_CORE_IDENTIFIER(BitAndAssignWith)
+CARBON_CORE_IDENTIFIER(BitAndWith)
+CARBON_CORE_IDENTIFIER(BitComplement)
+CARBON_CORE_IDENTIFIER(BitOrAssignWith)
+CARBON_CORE_IDENTIFIER(BitOrWith)
+CARBON_CORE_IDENTIFIER(BitXorAssignWith)
+CARBON_CORE_IDENTIFIER(BitXorWith)
+CARBON_CORE_IDENTIFIER(Bool)
+CARBON_CORE_IDENTIFIER(Char)
+CARBON_CORE_IDENTIFIER(Convert)
+CARBON_CORE_IDENTIFIER(Copy)
+CARBON_CORE_IDENTIFIER(CppCompat)
+CARBON_CORE_IDENTIFIER(Dec)
+CARBON_CORE_IDENTIFIER(Destroy)
+CARBON_CORE_IDENTIFIER(DivAssignWith)
+CARBON_CORE_IDENTIFIER(DivWith)
+CARBON_CORE_IDENTIFIER(EqWith)
+CARBON_CORE_IDENTIFIER(Equal)
+CARBON_CORE_IDENTIFIER(Float)
+CARBON_CORE_IDENTIFIER(Get)
+CARBON_CORE_IDENTIFIER(Greater)
+CARBON_CORE_IDENTIFIER(GreaterOrEquivalent)
+CARBON_CORE_IDENTIFIER(HasValue)
+CARBON_CORE_IDENTIFIER(ImplicitAs)
+CARBON_CORE_IDENTIFIER(Inc)
+CARBON_CORE_IDENTIFIER(IndexWith)
+CARBON_CORE_IDENTIFIER(Int)
+CARBON_CORE_IDENTIFIER(Iterate)
+CARBON_CORE_IDENTIFIER(LeftShiftAssignWith)
+CARBON_CORE_IDENTIFIER(LeftShiftWith)
+CARBON_CORE_IDENTIFIER(Less)
+CARBON_CORE_IDENTIFIER(LessOrEquivalent)
+CARBON_CORE_IDENTIFIER(Long32)
+CARBON_CORE_IDENTIFIER(LongLong64)
+CARBON_CORE_IDENTIFIER(ModAssignWith)
+CARBON_CORE_IDENTIFIER(ModWith)
+CARBON_CORE_IDENTIFIER(MulAssignWith)
+CARBON_CORE_IDENTIFIER(MulWith)
+CARBON_CORE_IDENTIFIER(Negate)
+CARBON_CORE_IDENTIFIER(NewCursor)
+CARBON_CORE_IDENTIFIER(Next)
+CARBON_CORE_IDENTIFIER(NotEqual)
+CARBON_CORE_IDENTIFIER(NullptrT)
+CARBON_CORE_IDENTIFIER(Op)
+CARBON_CORE_IDENTIFIER(Optional)
+CARBON_CORE_IDENTIFIER(OrderedWith)
+CARBON_CORE_IDENTIFIER(RightShiftAssignWith)
+CARBON_CORE_IDENTIFIER(RightShiftWith)
+CARBON_CORE_IDENTIFIER(String)
+CARBON_CORE_IDENTIFIER(SubAssignWith)
+CARBON_CORE_IDENTIFIER(SubWith)
+CARBON_CORE_IDENTIFIER(UInt)
+CARBON_CORE_IDENTIFIER(ULong32)
+CARBON_CORE_IDENTIFIER(ULongLong64)
+CARBON_CORE_IDENTIFIER(UnsafeAs)
+CARBON_CORE_IDENTIFIER(VoidBase)
+
+#undef CARBON_CORE_IDENTIFIER

+ 74 - 0
toolchain/check/core_identifier.h

@@ -0,0 +1,74 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_CHECK_CORE_IDENTIFIER_H_
+#define CARBON_TOOLCHAIN_CHECK_CORE_IDENTIFIER_H_
+
+#include "common/enum_base.h"
+#include "toolchain/base/shared_value_stores.h"
+#include "toolchain/base/value_ids.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::Check {
+
+CARBON_DEFINE_RAW_ENUM_CLASS(CoreIdentifier, uint8_t) {
+#define CARBON_CORE_IDENTIFIER(Name) CARBON_RAW_ENUM_ENUMERATOR(Name)
+#include "toolchain/check/core_identifier.def"
+};
+
+// An identifier in `Core` that's significant for the language (typically used
+// by desugaring) but not a builtin or keyword. Note this includes non-top-level
+// identifiers, such as both `AddWith` and `Op` in `Core.AddWith.Op`.
+class CoreIdentifier : public CARBON_ENUM_BASE(CoreIdentifier) {
+ public:
+#define CARBON_CORE_IDENTIFIER(Name) CARBON_ENUM_CONSTANT_DECL(Name)
+#include "toolchain/check/core_identifier.def"
+
+ private:
+  // Exposes `AsInt`.
+  friend class CoreIdentifierCache;
+};
+
+#define CARBON_CORE_IDENTIFIER(Name) \
+  CARBON_ENUM_CONSTANT_DEFINITION(CoreIdentifier, Name)
+#include "toolchain/check/core_identifier.def"
+
+// A cache of added `Core` identifiers. These are added to the identifier
+// store on first use.
+class CoreIdentifierCache {
+ public:
+  explicit CoreIdentifierCache(SharedValueStores::IdentifierStore* identifiers)
+      : identifiers_(identifiers) {}
+
+  // Returns the `NameId` for a `CoreIdentifier`.
+  auto AddNameId(CoreIdentifier identifier) -> SemIR::NameId {
+    auto& value = cache_[identifier.AsInt()];
+    if (!value.has_value()) {
+      value =
+          SemIR::NameId::ForIdentifier(identifiers_->Add(identifier.name()));
+    }
+    return value;
+  }
+
+ private:
+  // The number of cache entries.
+  static constexpr int CacheSize = 0
+#define CARBON_CORE_IDENTIFIER(Name) +1
+#include "toolchain/check/core_identifier.def"
+      ;
+
+  // A pointer for adding identifiers.
+  SharedValueStores::IdentifierStore* identifiers_;
+
+  // The cache of added identifiers. These are stored as a `NameId` because the
+  // `IdentifierId` isn't directly used.
+  SemIR::NameId cache_[CacheSize] = {
+#define CARBON_CORE_IDENTIFIER(Name) SemIR::NameId::None,
+#include "toolchain/check/core_identifier.def"
+  };
+};
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_CORE_IDENTIFIER_H_

+ 17 - 11
toolchain/check/cpp/import.cpp

@@ -32,6 +32,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/cpp/access.h"
 #include "toolchain/check/cpp/custom_type_mapping.h"
 #include "toolchain/check/cpp/generate_ast.h"
@@ -730,16 +731,17 @@ static auto MakeIntType(Context& context, IntId size_id, bool is_signed)
 }
 
 static auto MakeCppCompatType(Context& context, SemIR::LocId loc_id,
-                              llvm::StringRef name) -> TypeExpr {
-  return ExprAsType(context, loc_id,
-                    LookupNameInCore(context, loc_id, {"CppCompat", name}));
+                              CoreIdentifier name) -> TypeExpr {
+  return ExprAsType(
+      context, loc_id,
+      LookupNameInCore(context, loc_id, {CoreIdentifier::CppCompat, name}));
 }
 
 // Maps a C++ builtin integer type to a Carbon `Core.CppCompat` type.
 static auto MapBuiltinCppCompatIntegerType(Context& context,
                                            unsigned int cpp_width,
                                            unsigned int carbon_width,
-                                           llvm::StringRef cpp_compat_name)
+                                           CoreIdentifier cpp_compat_name)
     -> TypeExpr {
   if (cpp_width != carbon_width) {
     return TypeExpr::None;
@@ -775,23 +777,27 @@ static auto MapBuiltinIntegerType(Context& context, SemIR::LocId loc_id,
                       MakeCharTypeLiteral(context, Parse::NodeId::None));
   }
   if (clang::ASTContext::hasSameType(qual_type, ast_context.LongTy)) {
-    return MapBuiltinCppCompatIntegerType(context, width, 32, "Long32");
+    return MapBuiltinCppCompatIntegerType(context, width, 32,
+                                          CoreIdentifier::Long32);
   }
   if (clang::ASTContext::hasSameType(qual_type, ast_context.UnsignedLongTy)) {
-    return MapBuiltinCppCompatIntegerType(context, width, 32, "ULong32");
+    return MapBuiltinCppCompatIntegerType(context, width, 32,
+                                          CoreIdentifier::ULong32);
   }
   if (clang::ASTContext::hasSameType(qual_type, ast_context.LongLongTy)) {
-    return MapBuiltinCppCompatIntegerType(context, width, 64, "LongLong64");
+    return MapBuiltinCppCompatIntegerType(context, width, 64,
+                                          CoreIdentifier::LongLong64);
   }
   if (clang::ASTContext::hasSameType(qual_type,
                                      ast_context.UnsignedLongLongTy)) {
-    return MapBuiltinCppCompatIntegerType(context, width, 64, "ULongLong64");
+    return MapBuiltinCppCompatIntegerType(context, width, 64,
+                                          CoreIdentifier::ULongLong64);
   }
   return TypeExpr::None;
 }
 
 static auto MapNullptrType(Context& context, SemIR::LocId loc_id) -> TypeExpr {
-  return MakeCppCompatType(context, loc_id, "NullptrT");
+  return MakeCppCompatType(context, loc_id, CoreIdentifier::NullptrT);
 }
 
 // Maps a C++ builtin type to a Carbon type.
@@ -820,7 +826,7 @@ static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
     }
     // TODO: Handle floating-point types that map to named aliases.
   } else if (type.isVoidType()) {
-    return MakeCppCompatType(context, loc_id, "VoidBase");
+    return MakeCppCompatType(context, loc_id, CoreIdentifier::VoidBase);
   } else if (type.isNullPtrType()) {
     return MapNullptrType(context, loc_id);
   }
@@ -937,7 +943,7 @@ static auto ClangGetUnqualifiedTypePreserveNonNull(
 // `inner_type_inst_id`.
 static auto MakeOptionalType(Context& context, SemIR::LocId loc_id,
                              SemIR::InstId inner_type_inst_id) -> TypeExpr {
-  auto fn_inst_id = LookupNameInCore(context, loc_id, "Optional");
+  auto fn_inst_id = LookupNameInCore(context, loc_id, CoreIdentifier::Optional);
   auto call_id = PerformCall(context, loc_id, fn_inst_id, {inner_type_inst_id});
   return ExprAsType(context, loc_id, call_id);
 }

+ 135 - 128
toolchain/check/cpp/operators.cpp

@@ -6,6 +6,7 @@
 
 #include "clang/Sema/Overload.h"
 #include "clang/Sema/Sema.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/cpp/location.h"
 #include "toolchain/check/cpp/overload_resolution.h"
@@ -20,147 +21,153 @@ namespace Carbon::Check {
 
 // Maps Carbon operator interface and operator names to Clang operator kinds.
 static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
-                                 llvm::StringLiteral interface_name,
-                                 llvm::StringLiteral op_name)
+                                 CoreIdentifier interface_name,
+                                 CoreIdentifier op_name)
     -> std::optional<clang::OverloadedOperatorKind> {
-  // Unary operators.
-  if (interface_name == "Destroy" || interface_name == "As" ||
-      interface_name == "ImplicitAs" || interface_name == "Copy") {
-    // TODO: Support destructors and conversions.
-    return std::nullopt;
-  }
-
-  // Increment and Decrement.
-  if (interface_name == "Inc") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PlusPlus;
-  }
-  if (interface_name == "Dec") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_MinusMinus;
-  }
+  switch (interface_name) {
+      // Unary operators.
+    case CoreIdentifier::Destroy:
+    case CoreIdentifier::As:
+    case CoreIdentifier::ImplicitAs:
+    case CoreIdentifier::Copy: {
+      // TODO: Support destructors and conversions.
+      return std::nullopt;
+    }
 
-  // Arithmetic.
-  if (interface_name == "Negate") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Minus;
-  }
+    // Increment and decrement.
+    case CoreIdentifier::Inc: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_PlusPlus;
+    }
+    case CoreIdentifier::Dec: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_MinusMinus;
+    }
 
-  // Binary operators.
+    // Arithmetic.
+    case CoreIdentifier::Negate: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Minus;
+    }
 
-  // Arithmetic Operators.
-  if (interface_name == "AddWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Plus;
-  }
-  if (interface_name == "SubWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Minus;
-  }
-  if (interface_name == "MulWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Star;
-  }
-  if (interface_name == "DivWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Slash;
-  }
-  if (interface_name == "ModWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Percent;
-  }
+    // Binary operators.
 
-  // Bitwise Operators.
-  if (interface_name == "BitAndWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Amp;
-  }
-  if (interface_name == "BitOrWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Pipe;
-  }
-  if (interface_name == "BitXorWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Caret;
-  }
-  if (interface_name == "LeftShiftWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_LessLess;
-  }
-  if (interface_name == "RightShiftWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_GreaterGreater;
-  }
+    // Arithmetic operators.
+    case CoreIdentifier::AddWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Plus;
+    }
+    case CoreIdentifier::SubWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Minus;
+    }
+    case CoreIdentifier::MulWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Star;
+    }
+    case CoreIdentifier::DivWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Slash;
+    }
+    case CoreIdentifier::ModWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Percent;
+    }
 
-  // Compound Assignment Arithmetic Operators.
-  if (interface_name == "AddAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PlusEqual;
-  }
-  if (interface_name == "SubAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_MinusEqual;
-  }
-  if (interface_name == "MulAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_StarEqual;
-  }
-  if (interface_name == "DivAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_SlashEqual;
-  }
-  if (interface_name == "ModAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PercentEqual;
-  }
+    // Bitwise operators.
+    case CoreIdentifier::BitAndWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Amp;
+    }
+    case CoreIdentifier::BitOrWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Pipe;
+    }
+    case CoreIdentifier::BitXorWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_Caret;
+    }
+    case CoreIdentifier::LeftShiftWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_LessLess;
+    }
+    case CoreIdentifier::RightShiftWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_GreaterGreater;
+    }
 
-  // Compound Assignment Bitwise Operators.
-  if (interface_name == "BitAndAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_AmpEqual;
-  }
-  if (interface_name == "BitOrAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PipeEqual;
-  }
-  if (interface_name == "BitXorAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_CaretEqual;
-  }
-  if (interface_name == "LeftShiftAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_LessLessEqual;
-  }
-  if (interface_name == "RightShiftAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_GreaterGreaterEqual;
-  }
+    // Compound assignment arithmetic operators.
+    case CoreIdentifier::AddAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_PlusEqual;
+    }
+    case CoreIdentifier::SubAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_MinusEqual;
+    }
+    case CoreIdentifier::MulAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_StarEqual;
+    }
+    case CoreIdentifier::DivAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_SlashEqual;
+    }
+    case CoreIdentifier::ModAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_PercentEqual;
+    }
 
-  // Relational Operators.
-  if (interface_name == "EqWith") {
-    if (op_name == "Equal") {
-      return clang::OO_EqualEqual;
+    // Compound assignment bitwise operators.
+    case CoreIdentifier::BitAndAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_AmpEqual;
     }
-    CARBON_CHECK(op_name == "NotEqual");
-    return clang::OO_ExclaimEqual;
-  }
-  if (interface_name == "OrderedWith") {
-    if (op_name == "Less") {
-      return clang::OO_Less;
+    case CoreIdentifier::BitOrAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_PipeEqual;
     }
-    if (op_name == "Greater") {
-      return clang::OO_Greater;
+    case CoreIdentifier::BitXorAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_CaretEqual;
     }
-    if (op_name == "LessOrEquivalent") {
-      return clang::OO_LessEqual;
+    case CoreIdentifier::LeftShiftAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_LessLessEqual;
+    }
+    case CoreIdentifier::RightShiftAssignWith: {
+      CARBON_CHECK(op_name == CoreIdentifier::Op);
+      return clang::OO_GreaterGreaterEqual;
     }
-    CARBON_CHECK(op_name == "GreaterOrEquivalent");
-    return clang::OO_GreaterEqual;
-  }
 
-  context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
-                                     interface_name));
-  return std::nullopt;
+    // Relational operators.
+    case CoreIdentifier::EqWith: {
+      if (op_name == CoreIdentifier::Equal) {
+        return clang::OO_EqualEqual;
+      }
+      CARBON_CHECK(op_name == CoreIdentifier::NotEqual);
+      return clang::OO_ExclaimEqual;
+    }
+    case CoreIdentifier::OrderedWith: {
+      switch (op_name) {
+        case CoreIdentifier::Less:
+          return clang::OO_Less;
+        case CoreIdentifier::Greater:
+          return clang::OO_Greater;
+        case CoreIdentifier::LessOrEquivalent:
+          return clang::OO_LessEqual;
+        case CoreIdentifier::GreaterOrEquivalent:
+          return clang::OO_GreaterEqual;
+        default:
+          CARBON_FATAL("Unexpected OrderedWith op `{0}`", op_name);
+      }
+    }
+
+    default:
+      context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
+                                         interface_name));
+      return std::nullopt;
+  }
 }
 
 auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,

+ 2 - 2
toolchain/check/handle_index.cpp

@@ -35,9 +35,9 @@ static auto PerformIndexWith(Context& context, Parse::NodeId node_id,
                              SemIR::InstId index_inst_id) -> SemIR::InstId {
   SemIR::InstId args[] = {
       context.types().GetInstId(context.insts().Get(index_inst_id).type_id())};
-  Operator op{.interface_name = "IndexWith",
+  Operator op{.interface_name = CoreIdentifier::IndexWith,
               .interface_args_ref = args,
-              .op_name = "At"};
+              .op_name = CoreIdentifier::At};
   return BuildBinaryOperator(context, node_id, op, operand_inst_id,
                              index_inst_id);
 }

+ 1 - 1
toolchain/check/handle_literal.cpp

@@ -77,7 +77,7 @@ auto HandleParseNode(Context& context, Parse::StringLiteralId node_id) -> bool {
 
 auto HandleParseNode(Context& context, Parse::BoolTypeLiteralId node_id)
     -> bool {
-  auto fn_inst_id = LookupNameInCore(context, node_id, "Bool");
+  auto fn_inst_id = LookupNameInCore(context, node_id, CoreIdentifier::Bool);
   auto type_inst_id = PerformCall(context, node_id, fn_inst_id, {});
   context.node_stack().Push(node_id, type_inst_id);
   return true;

+ 16 - 12
toolchain/check/handle_loop_statement.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/full_pattern_stack.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/inst.h"
@@ -135,10 +136,9 @@ auto HandleParseNode(Context& context, Parse::ForInId node_id) -> bool {
 // For a value or reference of type `Optional(T)`, call the given accessor.
 static auto CallOptionalAccessor(Context& context, Parse::NodeId node_id,
                                  SemIR::InstId optional_id,
-                                 llvm::StringLiteral accessor_name)
+                                 CoreIdentifier accessor_name)
     -> SemIR::InstId {
-  auto accessor_name_id =
-      SemIR::NameId::ForIdentifier(context.identifiers().Add(accessor_name));
+  auto accessor_name_id = context.core_identifiers().AddNameId(accessor_name);
   auto accessor_id =
       PerformMemberAccess(context, node_id, optional_id, accessor_name_id);
   return PerformCall(context, node_id, accessor_id, {});
@@ -160,9 +160,11 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
   // Create the cursor variable.
   // TODO: Produce a custom diagnostic if the range operand can't be used as a
   // range.
-  auto cursor_id = BuildUnaryOperator(
-      context, node_id, {.interface_name = "Iterate", .op_name = "NewCursor"},
-      range_id);
+  auto cursor_id =
+      BuildUnaryOperator(context, node_id,
+                         {.interface_name = CoreIdentifier::Iterate,
+                          .op_name = CoreIdentifier::NewCursor},
+                         range_id);
   auto cursor_type_id = context.insts().Get(cursor_id).type_id();
   auto cursor_var_id = AddInstWithCleanup<SemIR::VarStorage>(
       context, node_id,
@@ -180,9 +182,11 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
       context, node_id,
       {.type_id = GetPointerType(context, cursor_type_inst_id),
        .lvalue_id = cursor_var_id});
-  auto element_id = BuildBinaryOperator(
-      context, node_id, {.interface_name = "Iterate", .op_name = "Next"},
-      range_id, cursor_addr_id);
+  auto element_id =
+      BuildBinaryOperator(context, node_id,
+                          {.interface_name = CoreIdentifier::Iterate,
+                           .op_name = CoreIdentifier::Next},
+                          range_id, cursor_addr_id);
   // We need to convert away from an initializing expression in order to call
   // `HasValue` and then separately pattern-match against the element.
   // TODO: Instead, form a `.Some(pattern_id)` pattern and pattern-match against
@@ -190,8 +194,8 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
   element_id = ConvertToValueOrRefExpr(context, element_id);
 
   // Branch to the loop body if the optional element has a value.
-  auto cond_value_id =
-      CallOptionalAccessor(context, node_id, element_id, "HasValue");
+  auto cond_value_id = CallOptionalAccessor(context, node_id, element_id,
+                                            CoreIdentifier::HasValue);
   BranchAndStartLoopBody(context, node_id, loop_header_id, cond_value_id);
 
   // The loop pattern's initializer is now complete, and any bindings in it
@@ -204,7 +208,7 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
 
   // Initialize the pattern from `<element>.Get()`.
   auto element_value_id =
-      CallOptionalAccessor(context, node_id, element_id, "Get");
+      CallOptionalAccessor(context, node_id, element_id, CoreIdentifier::Get);
   LocalPatternMatch(context, pattern_id, element_value_id);
   return true;
 }

+ 55 - 39
toolchain/check/handle_operator.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/operator.h"
@@ -18,16 +19,19 @@ namespace Carbon::Check {
 
 // Common logic for unary operator handlers.
 static auto HandleUnaryOperator(Context& context, Parse::AnyExprId expr_node_id,
-                                Operator op) -> bool {
+                                CoreIdentifier interface_name) -> bool {
   auto operand_id = context.node_stack().PopExpr();
-  auto result_id = BuildUnaryOperator(context, expr_node_id, op, operand_id);
+  auto result_id = BuildUnaryOperator(
+      context, expr_node_id, {.interface_name = interface_name}, operand_id);
   context.node_stack().Push(expr_node_id, result_id);
   return true;
 }
 
 // Common logic for binary operator handlers.
 static auto HandleBinaryOperator(Context& context,
-                                 Parse::AnyExprId expr_node_id, Operator op)
+                                 Parse::AnyExprId expr_node_id,
+                                 CoreIdentifier interface_name,
+                                 CoreIdentifier op_name = CoreIdentifier::Op)
     -> bool {
   auto rhs_id = context.node_stack().PopExpr();
   auto lhs_id = context.node_stack().PopExpr();
@@ -36,9 +40,11 @@ static auto HandleBinaryOperator(Context& context,
   // this function for it.
   SemIR::InstId args[] = {
       context.types().GetInstId(context.insts().Get(rhs_id).type_id())};
-  op.interface_args_ref = args;
-  auto result_id =
-      BuildBinaryOperator(context, expr_node_id, op, lhs_id, rhs_id);
+  auto result_id = BuildBinaryOperator(context, expr_node_id,
+                                       {.interface_name = interface_name,
+                                        .interface_args_ref = args,
+                                        .op_name = op_name},
+                                       lhs_id, rhs_id);
   context.node_stack().Push(expr_node_id, result_id);
   return true;
 }
@@ -46,12 +52,13 @@ static auto HandleBinaryOperator(Context& context,
 auto HandleParseNode(Context& context, Parse::InfixOperatorAmpId node_id)
     -> bool {
   // TODO: Facet type intersection may need to be handled directly.
-  return HandleBinaryOperator(context, node_id, {"BitAndWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::BitAndWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorAmpEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"BitAndAssignWith"});
+  return HandleBinaryOperator(context, node_id,
+                              CoreIdentifier::BitAndAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::UnsafeModifierId node_id)
@@ -78,12 +85,13 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorAsId node_id)
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorCaretId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"BitXorWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::BitXorWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorCaretEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"BitXorAssignWith"});
+  return HandleBinaryOperator(context, node_id,
+                              CoreIdentifier::BitXorAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
@@ -91,7 +99,8 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
   // TODO: Switch to using assignment interface for most assignment. Some cases
   // may need to be handled directly.
   //
-  //   return HandleBinaryOperator(context, node_id, {"AssignWith"});
+  //   return HandleBinaryOperator(context, node_id,
+  //                               CoreIdentifier::AssignWith);
 
   auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
   auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();
@@ -117,45 +126,50 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorEqualEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"EqWith", {}, "Equal"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::EqWith,
+                              CoreIdentifier::Equal);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorExclaimEqualId node_id) -> bool {
-  return HandleBinaryOperator(context, node_id, {"EqWith", {}, "NotEqual"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::EqWith,
+                              CoreIdentifier::NotEqual);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorGreaterId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"OrderedWith", {}, "Greater"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::OrderedWith,
+                              CoreIdentifier::Greater);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorGreaterEqualId node_id) -> bool {
-  return HandleBinaryOperator(context, node_id,
-                              {"OrderedWith", {}, "GreaterOrEquivalent"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::OrderedWith,
+                              CoreIdentifier::GreaterOrEquivalent);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorGreaterGreaterId node_id) -> bool {
-  return HandleBinaryOperator(context, node_id, {"RightShiftWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::RightShiftWith);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorGreaterGreaterEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"RightShiftAssignWith"});
+  return HandleBinaryOperator(context, node_id,
+                              CoreIdentifier::RightShiftAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorLessId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"OrderedWith", {}, "Less"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::OrderedWith,
+                              CoreIdentifier::Less);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorLessEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id,
-                              {"OrderedWith", {}, "LessOrEquivalent"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::OrderedWith,
+                              CoreIdentifier::LessOrEquivalent);
 }
 
 auto HandleParseNode(Context& context,
@@ -165,72 +179,74 @@ auto HandleParseNode(Context& context,
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorLessLessId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"LeftShiftWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::LeftShiftWith);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorLessLessEqualId node_id) -> bool {
-  return HandleBinaryOperator(context, node_id, {"LeftShiftAssignWith"});
+  return HandleBinaryOperator(context, node_id,
+                              CoreIdentifier::LeftShiftAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorMinusId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"SubWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::SubWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorMinusEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"SubAssignWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::SubAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorPercentId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"ModWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::ModWith);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::InfixOperatorPercentEqualId node_id) -> bool {
-  return HandleBinaryOperator(context, node_id, {"ModAssignWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::ModAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorPipeId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"BitOrWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::BitOrWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorPipeEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"BitOrAssignWith"});
+  return HandleBinaryOperator(context, node_id,
+                              CoreIdentifier::BitOrAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorPlusId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"AddWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::AddWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorPlusEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"AddAssignWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::AddAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorSlashId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"DivWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::DivWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorSlashEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"DivAssignWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::DivAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorStarId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"MulWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::MulWith);
 }
 
 auto HandleParseNode(Context& context, Parse::InfixOperatorStarEqualId node_id)
     -> bool {
-  return HandleBinaryOperator(context, node_id, {"MulAssignWith"});
+  return HandleBinaryOperator(context, node_id, CoreIdentifier::MulAssignWith);
 }
 
 auto HandleParseNode(Context& context, Parse::PostfixOperatorStarId node_id)
@@ -271,7 +287,7 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorAmpId node_id)
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorCaretId node_id)
     -> bool {
-  return HandleUnaryOperator(context, node_id, {"BitComplement"});
+  return HandleUnaryOperator(context, node_id, CoreIdentifier::BitComplement);
 }
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorConstId node_id)
@@ -296,12 +312,12 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorConstId node_id)
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorMinusId node_id)
     -> bool {
-  return HandleUnaryOperator(context, node_id, {"Negate"});
+  return HandleUnaryOperator(context, node_id, CoreIdentifier::Negate);
 }
 
 auto HandleParseNode(Context& context,
                      Parse::PrefixOperatorMinusMinusId node_id) -> bool {
-  return HandleUnaryOperator(context, node_id, {"Dec"});
+  return HandleUnaryOperator(context, node_id, CoreIdentifier::Dec);
 }
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorNotId node_id)
@@ -338,7 +354,7 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorPartialId node_id)
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorPlusPlusId node_id)
     -> bool {
-  return HandleUnaryOperator(context, node_id, {"Inc"});
+  return HandleUnaryOperator(context, node_id, CoreIdentifier::Inc);
 }
 
 auto HandleParseNode(Context& context, Parse::PrefixOperatorStarId node_id)

+ 7 - 5
toolchain/check/literal.cpp

@@ -27,15 +27,17 @@ auto MakeIntLiteral(Context& context, Parse::NodeId node_id, IntId int_id)
 
 auto MakeCharTypeLiteral(Context& context, Parse::NodeId node_id)
     -> SemIR::InstId {
-  return LookupNameInCore(context, node_id, "Char");
+  return LookupNameInCore(context, node_id, CoreIdentifier::Char);
 }
 
 auto MakeIntTypeLiteral(Context& context, Parse::NodeId node_id,
                         SemIR::IntKind int_kind, IntId size_id)
     -> SemIR::InstId {
   auto width_id = MakeIntLiteral(context, node_id, size_id);
-  auto fn_inst_id = LookupNameInCore(
-      context, node_id, int_kind == SemIR::IntKind::Signed ? "Int" : "UInt");
+  auto fn_inst_id = LookupNameInCore(context, node_id,
+                                     int_kind == SemIR::IntKind::Signed
+                                         ? CoreIdentifier::Int
+                                         : CoreIdentifier::UInt);
   return PerformCall(context, node_id, fn_inst_id, {width_id});
 }
 
@@ -48,7 +50,7 @@ auto MakeIntType(Context& context, Parse::NodeId node_id,
 auto MakeFloatTypeLiteral(Context& context, Parse::NodeId node_id,
                           IntId size_id) -> SemIR::InstId {
   auto width_id = MakeIntLiteral(context, node_id, size_id);
-  auto fn_inst_id = LookupNameInCore(context, node_id, "Float");
+  auto fn_inst_id = LookupNameInCore(context, node_id, CoreIdentifier::Float);
   return PerformCall(context, node_id, fn_inst_id, {width_id});
 }
 
@@ -169,7 +171,7 @@ auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
 
 auto MakeStringTypeLiteral(Context& context, SemIR::LocId loc_id)
     -> SemIR::InstId {
-  return LookupNameInCore(context, loc_id, "String");
+  return LookupNameInCore(context, loc_id, CoreIdentifier::String);
 }
 
 auto MakeStringType(Context& context, SemIR::LocId loc_id) -> TypeExpr {

+ 27 - 14
toolchain/check/name_lookup.cpp

@@ -6,6 +6,7 @@
 
 #include <optional>
 
+#include "common/raw_string_ostream.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/import.h"
@@ -492,12 +493,23 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
   return result;
 }
 
+// Returns a `Core.<qualifiers>` name for diagnostics.
+static auto GetCoreQualifiedName(llvm::ArrayRef<CoreIdentifier> qualifiers)
+    -> std::string {
+  RawStringOstream str;
+  str << "Core";
+  for (auto qualifier : qualifiers) {
+    str << "." << qualifier;
+  }
+  return str.TakeStr();
+}
+
 // Returns the scope of the Core package, or `None` if it's not found.
 //
 // TODO: Consider tracking the Core package in SemIR so we don't need to use
 // name lookup to find it.
 static auto GetCorePackage(Context& context, SemIR::LocId loc_id,
-                           llvm::ArrayRef<llvm::StringRef> names)
+                           llvm::ArrayRef<CoreIdentifier> qualifiers)
     -> SemIR::NameScopeId {
   auto packaging = context.parse_tree().packaging_decl();
   if (packaging && packaging->names.package_id == PackageNameId::Core) {
@@ -520,25 +532,26 @@ static auto GetCorePackage(Context& context, SemIR::LocId loc_id,
 
   CARBON_DIAGNOSTIC(
       CoreNotFound, Error,
-      "`Core.{0}` implicitly referenced here, but package `Core` not found",
+      "`{0}` implicitly referenced here, but package `Core` not found",
       std::string);
-  context.emitter().Emit(loc_id, CoreNotFound, llvm::join(names, "."));
+  context.emitter().Emit(loc_id, CoreNotFound,
+                         GetCoreQualifiedName(qualifiers));
   return SemIR::NameScopeId::None;
 }
 
 auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
-                      llvm::ArrayRef<llvm::StringRef> names) -> SemIR::InstId {
-  CARBON_CHECK(!names.empty());
+                      llvm::ArrayRef<CoreIdentifier> qualifiers)
+    -> SemIR::InstId {
+  CARBON_CHECK(!qualifiers.empty());
 
-  auto core_package_id = GetCorePackage(context, loc_id, names);
+  auto core_package_id = GetCorePackage(context, loc_id, qualifiers);
   if (!core_package_id.has_value()) {
     return SemIR::ErrorInst::InstId;
   }
 
   auto inst_id = SemIR::InstId::None;
-  for (auto name : names) {
-    auto name_id =
-        SemIR::NameId::ForIdentifier(context.identifiers().Add(name));
+  for (auto qualifier : qualifiers) {
+    auto name_id = context.core_identifiers().AddNameId(qualifier);
 
     auto scope_id = SemIR::NameScopeId::None;
     if (inst_id.has_value()) {
@@ -556,11 +569,11 @@ auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
                                      context.name_scopes().Get(scope_id))
             : SemIR::ScopeLookupResult::MakeNotFound();
     if (!scope_result.is_found()) {
-      CARBON_DIAGNOSTIC(
-          CoreNameNotFound, Error,
-          "name `Core.{0}` implicitly referenced here, but not found",
-          std::string);
-      context.emitter().Emit(loc_id, CoreNameNotFound, llvm::join(names, "."));
+      CARBON_DIAGNOSTIC(CoreNameNotFound, Error,
+                        "name `{0}` implicitly referenced here, but not found",
+                        std::string);
+      context.emitter().Emit(loc_id, CoreNameNotFound,
+                             GetCoreQualifiedName(qualifiers));
       return SemIR::ErrorInst::InstId;
     }
 

+ 7 - 5
toolchain/check/name_lookup.h

@@ -8,6 +8,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -94,16 +95,17 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
                          std::optional<AccessInfo> access_info = std::nullopt)
     -> LookupResult;
 
-// Returns the `InstId` corresponding to a qualified name in the core package,
+// Returns the `InstId` corresponding to a qualified name in the `Core` package,
 // or BuiltinErrorInst if not found.
 auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
-                      llvm::ArrayRef<llvm::StringRef> names) -> SemIR::InstId;
+                      llvm::ArrayRef<CoreIdentifier> qualifiers)
+    -> SemIR::InstId;
 
-// Returns the `InstId` corresponding to a name in the core package, or
+// Returns the `InstId` corresponding to a name in the `Core`, or
 // BuiltinErrorInst if not found.
 inline auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
-                             llvm::StringRef name) -> SemIR::InstId {
-  return LookupNameInCore(context, loc_id, llvm::ArrayRef{name});
+                             CoreIdentifier identifier) -> SemIR::InstId {
+  return LookupNameInCore(context, loc_id, llvm::ArrayRef{identifier});
 }
 
 // Checks whether a name is accessible in the given access context. Produces a

+ 1 - 2
toolchain/check/operator.cpp

@@ -35,8 +35,7 @@ static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id,
   }
 
   // Look up the interface member.
-  auto op_name_id =
-      SemIR::NameId::ForIdentifier(context.identifiers().Add(op.op_name));
+  auto op_name_id = context.core_identifiers().AddNameId(op.op_name);
   return PerformMemberAccess(context, implicit_loc_id, interface_id,
                              op_name_id);
 }

+ 3 - 2
toolchain/check/operator.h

@@ -6,15 +6,16 @@
 #define CARBON_TOOLCHAIN_CHECK_OPERATOR_H_
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/core_identifier.h"
 #include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
 
 struct Operator {
-  llvm::StringLiteral interface_name;
+  CoreIdentifier interface_name;
   llvm::ArrayRef<SemIR::InstId> interface_args_ref = {};
-  llvm::StringLiteral op_name = "Op";
+  CoreIdentifier op_name = CoreIdentifier::Op;
 };
 
 // Checks and builds SemIR for a unary operator expression. For example,