[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