[llvm] [ADT] Add and use (for AArch64) `ValueWithSentinel<T, Sentinel>` (PR #158120)

Benjamin Maxwell via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 11 10:22:36 PDT 2025


https://github.com/MacDue created https://github.com/llvm/llvm-project/pull/158120

This intends to add a slightly safer std::optional-like interface over checking for sentinel values. This is used a few times on AArch64, but it looks like it could apply to other places/targets too.

>From 6f14b6da1355898c070e4bf836ca05974d65b5a7 Mon Sep 17 00:00:00 2001
From: Benjamin Maxwell <benjamin.maxwell at arm.com>
Date: Thu, 11 Sep 2025 14:51:51 +0000
Subject: [PATCH] [ADT] Add and use (for AArch64) `ValueWithSentinel<T,
 Sentinel>`

This intends to add a slightly safer std::optional-like interface over
checking for sentinel values. This is used a few times on AArch64, but
it looks like it could apply to other places/targets too.
---
 llvm/include/llvm/ADT/ValueWithSentinel.h     | 75 +++++++++++++++++++
 .../Target/AArch64/AArch64FrameLowering.cpp   | 31 ++++----
 .../Target/AArch64/AArch64ISelLowering.cpp    |  8 +-
 .../AArch64/AArch64MachineFunctionInfo.h      | 31 ++++----
 llvm/unittests/ADT/CMakeLists.txt             |  1 +
 llvm/unittests/ADT/ValueWithSentinelTest.cpp  | 37 +++++++++
 6 files changed, 149 insertions(+), 34 deletions(-)
 create mode 100644 llvm/include/llvm/ADT/ValueWithSentinel.h
 create mode 100644 llvm/unittests/ADT/ValueWithSentinelTest.cpp

diff --git a/llvm/include/llvm/ADT/ValueWithSentinel.h b/llvm/include/llvm/ADT/ValueWithSentinel.h
new file mode 100644
index 0000000000000..011d03dcdefc9
--- /dev/null
+++ b/llvm/include/llvm/ADT/ValueWithSentinel.h
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file defines the ValueWithSentinel class, which is a type akin to a
+/// std::optional, but uses a sentinel rather than an additional "valid" flag.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_VALUEWITHSENTINEL_H
+#define LLVM_ADT_VALUEWITHSENTINEL_H
+
+#include <cassert>
+#include <limits>
+#include <utility>
+
+namespace llvm {
+
+template <typename T, T Sentinel> class ValueWithSentinel {
+public:
+  ValueWithSentinel() = default;
+
+  ValueWithSentinel(T Value) : Value(std::move(Value)) {
+    assert(Value != Sentinel && "Value is sentinel (use default constructor)");
+  };
+
+  ValueWithSentinel &operator=(T const &NewValue) {
+    assert(NewValue != Sentinel && "Assigned to sentinel (use .clear())");
+    Value = NewValue;
+    return *this;
+  }
+
+  bool operator==(ValueWithSentinel const &Other) const {
+    return Value == Other.Value;
+  }
+
+  bool operator!=(ValueWithSentinel const &Other) const {
+    return !(*this == Other);
+  }
+
+  T &operator*() {
+    assert(has_value() && "Invalid value");
+    return Value;
+  }
+  const T &operator*() const {
+    return const_cast<ValueWithSentinel &>(*this).operator*();
+  }
+
+  T *operator->() { return &operator*(); }
+  T const *operator->() const { return &operator*(); }
+
+  T &value() { return operator*(); }
+  T const &value() const { return operator*(); }
+
+  bool has_value() const { return Value != Sentinel; }
+  explicit operator bool() const { return has_value(); }
+
+  void clear() { Value = Sentinel; }
+
+private:
+  T Value{Sentinel};
+};
+
+template <typename T>
+using ValueWithSentinelNumericMax =
+    ValueWithSentinel<T, std::numeric_limits<T>::max()>;
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index 175b5e04d82ff..45a56c409ab9c 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -3479,7 +3479,7 @@ bool AArch64FrameLowering::assignCalleeSavedSpillSlots(
   }
 
   Register LastReg = 0;
-  int HazardSlotIndex = std::numeric_limits<int>::max();
+  ValueWithSentinelNumericMax<int> HazardSlotIndex;
   for (auto &CS : CSI) {
     MCRegister Reg = CS.getReg();
     const TargetRegisterClass *RC = RegInfo->getMinimalPhysRegClass(Reg);
@@ -3488,16 +3488,16 @@ bool AArch64FrameLowering::assignCalleeSavedSpillSlots(
     if (AFI->hasStackHazardSlotIndex() &&
         (!LastReg || !AArch64InstrInfo::isFpOrNEON(LastReg)) &&
         AArch64InstrInfo::isFpOrNEON(Reg)) {
-      assert(HazardSlotIndex == std::numeric_limits<int>::max() &&
+      assert(!HazardSlotIndex.has_value() &&
              "Unexpected register order for hazard slot");
       HazardSlotIndex = MFI.CreateStackObject(StackHazardSize, Align(8), true);
-      LLVM_DEBUG(dbgs() << "Created CSR Hazard at slot " << HazardSlotIndex
+      LLVM_DEBUG(dbgs() << "Created CSR Hazard at slot " << *HazardSlotIndex
                         << "\n");
-      AFI->setStackHazardCSRSlotIndex(HazardSlotIndex);
-      if ((unsigned)HazardSlotIndex < MinCSFrameIndex)
-        MinCSFrameIndex = HazardSlotIndex;
-      if ((unsigned)HazardSlotIndex > MaxCSFrameIndex)
-        MaxCSFrameIndex = HazardSlotIndex;
+      AFI->setStackHazardCSRSlotIndex(*HazardSlotIndex);
+      if ((unsigned)*HazardSlotIndex < MinCSFrameIndex)
+        MinCSFrameIndex = *HazardSlotIndex;
+      if ((unsigned)*HazardSlotIndex > MaxCSFrameIndex)
+        MaxCSFrameIndex = *HazardSlotIndex;
     }
 
     unsigned Size = RegInfo->getSpillSize(*RC);
@@ -3524,16 +3524,15 @@ bool AArch64FrameLowering::assignCalleeSavedSpillSlots(
   }
 
   // Add hazard slot in the case where no FPR CSRs are present.
-  if (AFI->hasStackHazardSlotIndex() &&
-      HazardSlotIndex == std::numeric_limits<int>::max()) {
+  if (AFI->hasStackHazardSlotIndex() && !HazardSlotIndex.has_value()) {
     HazardSlotIndex = MFI.CreateStackObject(StackHazardSize, Align(8), true);
-    LLVM_DEBUG(dbgs() << "Created CSR Hazard at slot " << HazardSlotIndex
+    LLVM_DEBUG(dbgs() << "Created CSR Hazard at slot " << *HazardSlotIndex
                       << "\n");
-    AFI->setStackHazardCSRSlotIndex(HazardSlotIndex);
-    if ((unsigned)HazardSlotIndex < MinCSFrameIndex)
-      MinCSFrameIndex = HazardSlotIndex;
-    if ((unsigned)HazardSlotIndex > MaxCSFrameIndex)
-      MaxCSFrameIndex = HazardSlotIndex;
+    AFI->setStackHazardCSRSlotIndex(*HazardSlotIndex);
+    if ((unsigned)*HazardSlotIndex < MinCSFrameIndex)
+      MinCSFrameIndex = *HazardSlotIndex;
+    if ((unsigned)*HazardSlotIndex > MaxCSFrameIndex)
+      MaxCSFrameIndex = *HazardSlotIndex;
   }
 
   return true;
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 5ffaf2c49b4c0..9153c470004df 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -3061,10 +3061,10 @@ AArch64TargetLowering::EmitInitTPIDR2Object(MachineInstr &MI,
     BuildMI(*BB, MI, MI.getDebugLoc(), TII->get(AArch64::STPXi))
         .addReg(MI.getOperand(0).getReg())
         .addReg(MI.getOperand(1).getReg())
-        .addFrameIndex(TPIDR2.FrameIndex)
+        .addFrameIndex(*TPIDR2.FrameIndex)
         .addImm(0);
   } else
-    MFI.RemoveStackObject(TPIDR2.FrameIndex);
+    MFI.RemoveStackObject(*TPIDR2.FrameIndex);
 
   BB->remove_instr(&MI);
   return BB;
@@ -9399,7 +9399,7 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
   if (RequiresLazySave) {
     TPIDR2Object &TPIDR2 = FuncInfo->getTPIDR2Obj();
     SDValue TPIDR2ObjAddr = DAG.getFrameIndex(
-        TPIDR2.FrameIndex,
+        *TPIDR2.FrameIndex,
         DAG.getTargetLoweringInfo().getFrameIndexTy(DAG.getDataLayout()));
     Chain = DAG.getNode(
         ISD::INTRINSIC_VOID, DL, MVT::Other, Chain,
@@ -9956,7 +9956,7 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
     // RESTORE_ZA pseudo.
     SDValue Glue;
     SDValue TPIDR2Block = DAG.getFrameIndex(
-        TPIDR2.FrameIndex,
+        *TPIDR2.FrameIndex,
         DAG.getTargetLoweringInfo().getFrameIndexTy(DAG.getDataLayout()));
     Result = DAG.getCopyToReg(Result, DL, AArch64::X0, TPIDR2Block, Glue);
     Result =
diff --git a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
index 993cff112ba84..def796aaaface 100644
--- a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
+++ b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
@@ -18,6 +18,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/ValueWithSentinel.h"
 #include "llvm/CodeGen/CallingConvLower.h"
 #include "llvm/CodeGen/MIRYamlMapping.h"
 #include "llvm/CodeGen/MachineFrameInfo.h"
@@ -38,7 +39,7 @@ class AArch64Subtarget;
 class MachineInstr;
 
 struct TPIDR2Object {
-  int FrameIndex = std::numeric_limits<int>::max();
+  ValueWithSentinelNumericMax<int> FrameIndex;
   unsigned Uses = 0;
 };
 
@@ -114,8 +115,8 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
   /// The stack slots used to add space between FPR and GPR accesses when using
   /// hazard padding. StackHazardCSRSlotIndex is added between GPR and FPR CSRs.
   /// StackHazardSlotIndex is added between (sorted) stack objects.
-  int StackHazardSlotIndex = std::numeric_limits<int>::max();
-  int StackHazardCSRSlotIndex = std::numeric_limits<int>::max();
+  ValueWithSentinelNumericMax<int> StackHazardSlotIndex;
+  ValueWithSentinelNumericMax<int> StackHazardCSRSlotIndex;
 
   /// True if this function has a subset of CSRs that is handled explicitly via
   /// copies.
@@ -205,7 +206,7 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
   bool HasSwiftAsyncContext = false;
 
   /// The stack slot where the Swift asynchronous context is stored.
-  int SwiftAsyncContextFrameIdx = std::numeric_limits<int>::max();
+  ValueWithSentinelNumericMax<int> SwiftAsyncContextFrameIdx;
 
   bool IsMTETagged = false;
 
@@ -372,16 +373,16 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
         MaxOffset = std::max<int64_t>(Offset + ObjSize, MaxOffset);
       }
 
-      if (SwiftAsyncContextFrameIdx != std::numeric_limits<int>::max()) {
+      if (SwiftAsyncContextFrameIdx.has_value()) {
         int64_t Offset = MFI.getObjectOffset(getSwiftAsyncContextFrameIdx());
         int64_t ObjSize = MFI.getObjectSize(getSwiftAsyncContextFrameIdx());
         MinOffset = std::min<int64_t>(Offset, MinOffset);
         MaxOffset = std::max<int64_t>(Offset + ObjSize, MaxOffset);
       }
 
-      if (StackHazardCSRSlotIndex != std::numeric_limits<int>::max()) {
-        int64_t Offset = MFI.getObjectOffset(StackHazardCSRSlotIndex);
-        int64_t ObjSize = MFI.getObjectSize(StackHazardCSRSlotIndex);
+      if (StackHazardCSRSlotIndex.has_value()) {
+        int64_t Offset = MFI.getObjectOffset(*StackHazardCSRSlotIndex);
+        int64_t ObjSize = MFI.getObjectSize(*StackHazardCSRSlotIndex);
         MinOffset = std::min<int64_t>(Offset, MinOffset);
         MaxOffset = std::max<int64_t>(Offset + ObjSize, MaxOffset);
       }
@@ -447,16 +448,16 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
   void setVarArgsFPRSize(unsigned Size) { VarArgsFPRSize = Size; }
 
   bool hasStackHazardSlotIndex() const {
-    return StackHazardSlotIndex != std::numeric_limits<int>::max();
+    return StackHazardSlotIndex.has_value();
   }
-  int getStackHazardSlotIndex() const { return StackHazardSlotIndex; }
+  int getStackHazardSlotIndex() const { return *StackHazardSlotIndex; }
   void setStackHazardSlotIndex(int Index) {
-    assert(StackHazardSlotIndex == std::numeric_limits<int>::max());
+    assert(!StackHazardSlotIndex.has_value());
     StackHazardSlotIndex = Index;
   }
-  int getStackHazardCSRSlotIndex() const { return StackHazardCSRSlotIndex; }
+  int getStackHazardCSRSlotIndex() const { return *StackHazardCSRSlotIndex; }
   void setStackHazardCSRSlotIndex(int Index) {
-    assert(StackHazardCSRSlotIndex == std::numeric_limits<int>::max());
+    assert(!StackHazardCSRSlotIndex.has_value());
     StackHazardCSRSlotIndex = Index;
   }
 
@@ -574,7 +575,9 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
   void setSwiftAsyncContextFrameIdx(int FI) {
     SwiftAsyncContextFrameIdx = FI;
   }
-  int getSwiftAsyncContextFrameIdx() const { return SwiftAsyncContextFrameIdx; }
+  int getSwiftAsyncContextFrameIdx() const {
+    return *SwiftAsyncContextFrameIdx;
+  }
 
   bool needsDwarfUnwindInfo(const MachineFunction &MF) const;
   bool needsAsyncDwarfUnwindInfo(const MachineFunction &MF) const;
diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt
index dafd73518aedb..ef46ea2f8a083 100644
--- a/llvm/unittests/ADT/CMakeLists.txt
+++ b/llvm/unittests/ADT/CMakeLists.txt
@@ -93,6 +93,7 @@ add_llvm_unittest(ADTTests
   TwineTest.cpp
   TypeSwitchTest.cpp
   TypeTraitsTest.cpp
+  ValueWithSentinelTest.cpp
   )
 
 target_link_libraries(ADTTests PRIVATE LLVMTestingSupport)
diff --git a/llvm/unittests/ADT/ValueWithSentinelTest.cpp b/llvm/unittests/ADT/ValueWithSentinelTest.cpp
new file mode 100644
index 0000000000000..b02434c73707c
--- /dev/null
+++ b/llvm/unittests/ADT/ValueWithSentinelTest.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/ValueWithSentinel.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+TEST(ValueWithSentinelTest, Basic) {
+  ValueWithSentinelNumericMax<int> Value;
+  EXPECT_FALSE(Value.has_value());
+
+  Value = 1000;
+  EXPECT_TRUE(Value.has_value());
+
+  EXPECT_EQ(Value, 1000);
+  EXPECT_EQ(Value.value(), 1000);
+
+  Value.clear();
+  EXPECT_FALSE(Value.has_value());
+
+  ValueWithSentinelNumericMax<int> OtherValue(99);
+  EXPECT_TRUE(OtherValue.has_value());
+  EXPECT_NE(Value, OtherValue);
+
+  Value = OtherValue;
+  EXPECT_EQ(Value, OtherValue);
+}
+
+} // end anonymous namespace



More information about the llvm-commits mailing list