[clang] [ObjC] Emit number, array, and dictionary literals as constants (PR #185130)

Oliver Hunt via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 6 16:55:11 PST 2026


================
@@ -0,0 +1,201 @@
+//===-- CodeGen/CGObjCMacConstantLiteralUtil.h - ----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This should be used for things that effect the ABI of
+// Obj-C constant initializer literals (`-fobjc-constant-literals`) to allow
+// future changes without breaking the ABI promises.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_CODEGEN_CGOBJCMACCONSTANTLITERALUTIL_H
+#define LLVM_CLANG_LIB_CODEGEN_CGOBJCMACCONSTANTLITERALUTIL_H
+
+#include "CGObjCRuntime.h"
+#include "clang/AST/ExprObjC.h"
+#include "clang/AST/Type.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include <numeric>
+
+namespace clang {
+namespace CodeGen {
+namespace CGObjCMacConstantLiteralUtil {
+
+class NSConstantNumberMapInfo {
+
+  enum class MapInfoType {
+    Empty,
+    Tombstone,
+    Int,
+    Float,
+  };
+
+  MapInfoType InfoType;
+  QualType QType;
+  llvm::APSInt Int;
+  llvm::APFloat Float;
+
+  /// Default constructor that can create Empty or Tombstone info entries
+  explicit NSConstantNumberMapInfo(MapInfoType I = MapInfoType::Empty)
+      : InfoType(I), QType(QualType()), Int(), Float(0.0) {}
+
+  bool isEmptyOrTombstone() const {
+    return InfoType == MapInfoType::Empty || InfoType == MapInfoType::Tombstone;
+  }
+
+public:
+  NSConstantNumberMapInfo(const QualType &QT, const llvm::APSInt &V)
+      : InfoType(MapInfoType::Int), QType(QT), Int(V), Float(0.0) {}
+  NSConstantNumberMapInfo(const QualType &QT, const llvm::APFloat &V)
+      : InfoType(MapInfoType::Float), QType(QT), Int(), Float(V) {}
+
+  unsigned getHashValue() const {
+    assert(!isEmptyOrTombstone() && "Cannot hash empty or tombstone map info!");
+
+    unsigned QTypeHash = llvm::DenseMapInfo<QualType>::getHashValue(
+        llvm::DenseMapInfo<QualType>::getTombstoneKey());
+
+    if (InfoType == MapInfoType::Int)
+      return llvm::detail::combineHashValue((unsigned)Int.getZExtValue(),
+                                            QTypeHash);
+
+    assert(InfoType == MapInfoType::Float);
+    return llvm::detail::combineHashValue(
+        (unsigned)Float.bitcastToAPInt().getZExtValue(), QTypeHash);
+  }
+
+  static inline NSConstantNumberMapInfo getEmptyKey() {
+    return NSConstantNumberMapInfo();
+  }
+
+  static inline NSConstantNumberMapInfo getTombstoneKey() {
+    return NSConstantNumberMapInfo(MapInfoType::Tombstone);
+  }
+
+  bool operator==(const NSConstantNumberMapInfo &RHS) const {
+    if (InfoType != RHS.InfoType || QType != RHS.QType)
+      return false;
+
+    // Handle the empty and tombstone equality
+    if (isEmptyOrTombstone())
+      return true;
+
+    if (InfoType == MapInfoType::Int)
+      return llvm::APSInt::isSameValue(Int, RHS.Int);
+
+    assert(InfoType == MapInfoType::Float);
+
+    // handle -0, NaN, and infinities correctly
+    return Float.bitwiseIsEqual(RHS.Float);
+  }
+};
+
+using std::iota;
+
+class NSDictionaryBuilder {
+  SmallVector<llvm::Constant *, 16> Keys;
+  SmallVector<llvm::Constant *, 16> Objects;
+  uint64_t Opts;
+
+public:
+  enum class Options : uint64_t { Sorted = 1 };
+
+  NSDictionaryBuilder(const ObjCDictionaryLiteral *E,
+                      const ArrayRef<llvm::Constant *> &KYS,
+                      const ArrayRef<llvm::Constant *> &OBS,
+                      const Options O = Options::Sorted) {
+    assert((KYS.size() == OBS.size()) &&
+           "NSConstantDictionary requires key / value pairs!"
+           "keys and objects not of equal size!");
+
+    Opts = static_cast<uint64_t>(O);
+    uint64_t const NumElements = KYS.size();
+
+    // Reserve the capacity for the sorted keys & values
+    Keys.reserve(NumElements);
+    Objects.reserve(NumElements);
+
+    // Setup the element indicies 0 ..< NumElements
+    SmallVector<size_t, 16> ElementIndicies;
+    ElementIndicies.reserve(NumElements);
+    for (size_t i = 0; i < NumElements; i++) {
+      ElementIndicies.push_back(i);
+    }
+    std::iota(ElementIndicies.begin(), ElementIndicies.end(), 0);
+
+    // Now perform the sorts and shift the indicies as needed
+    std::stable_sort(
+        ElementIndicies.begin(), ElementIndicies.end(),
+        [E, O](size_t LI, size_t RI) {
+          Expr *const LK = E->getKeyValueElement(LI).Key->IgnoreImpCasts();
+          Expr *const RK = E->getKeyValueElement(RI).Key->IgnoreImpCasts();
+
+          if (!isa<ObjCStringLiteral>(LK) || !isa<ObjCStringLiteral>(RK))
+            llvm_unreachable("Non-constant literals should not be sorted to "
+                             "maintain existing behavior");
+
+          // NOTE: Using the `StringLiteral->getString()` since it checks that
+          //       `chars` are 1 byte
+          StringRef LKS = cast<ObjCStringLiteral>(LK)->getString()->getString();
+          StringRef RKS = cast<ObjCStringLiteral>(RK)->getString()->getString();
+
+          // Do an alpha sort to aid in with de-dupe at link time
+          // `O(log n)` worst case lookup at runtime supported by `Foundation`
+          if (O == Options::Sorted)
+            return LKS < RKS;
+          llvm_unreachable("Unexpected `NSDictionaryBuilder::Options given");
+        });
+
+    // Finally use the sorted indicies to insert into `Keys` and `Objects`
+    for (auto &Idx : ElementIndicies) {
+      Keys.push_back(KYS[Idx]);
+      Objects.push_back(OBS[Idx]);
+    }
+  }
+
+  SmallVector<llvm::Constant *, 16> &getKeys() { return Keys; }
+
+  SmallVector<llvm::Constant *, 16> &getObjects() { return Objects; }
----------------
ojhunt wrote:

While you do lose the separate key/value option, the actual usage is still fairly ergonomic as you can just do

```cpp
for (auto [Key, Value] : getElements())
```

Or similar.

I also think these should return an ArrayRef just to prevent the perennial accidental copy behavior of refs

https://github.com/llvm/llvm-project/pull/185130


More information about the cfe-commits mailing list