[llvm] [CHERI] Add a Support utility for determining alignment requirements of CHERI capabilities. (PR #197402)

Owen Anderson via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 7 13:08:55 PDT 2026


https://github.com/resistor updated https://github.com/llvm/llvm-project/pull/197402

>From e12d526a9cb8872818f5984e76dbd9d9ec28019f Mon Sep 17 00:00:00 2001
From: Owen Anderson <resistor at mac.com>
Date: Wed, 13 May 2026 11:45:22 +0200
Subject: [PATCH] [CHERI] Add a Support utility for determining alignment
 requirements of CHERI capabilities.

On CHERI systems with compressed bounds representations (which is all of them that anyone cares about today), one of the tradeoffs to achieve that compression is a requirement for larger allocations to be more highly aligned. This impacts both code generation and linking in places where globals need to be aligned and/or padded based on this requirement. The specific alignment requirements vary by capability format.

Co-authored-by: Alex Richardson <alexrichardson at google.com>
---
 .../llvm/Support/CHERICapabilityFormat.h      | 53 +++++++++++
 llvm/lib/Support/CHERICapabilityFormat.cpp    | 77 ++++++++++++++++
 llvm/lib/Support/CMakeLists.txt               |  1 +
 .../Support/CHERICapabilityFormatTest.cpp     | 91 +++++++++++++++++++
 llvm/unittests/Support/CMakeLists.txt         |  1 +
 5 files changed, 223 insertions(+)
 create mode 100644 llvm/include/llvm/Support/CHERICapabilityFormat.h
 create mode 100644 llvm/lib/Support/CHERICapabilityFormat.cpp
 create mode 100644 llvm/unittests/Support/CHERICapabilityFormatTest.cpp

diff --git a/llvm/include/llvm/Support/CHERICapabilityFormat.h b/llvm/include/llvm/Support/CHERICapabilityFormat.h
new file mode 100644
index 0000000000000..51472944c9993
--- /dev/null
+++ b/llvm/include/llvm/Support/CHERICapabilityFormat.h
@@ -0,0 +1,53 @@
+//===--- CHERICapabilityFormat.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_CHERICAPABILITYFORMAT_H
+#define LLVM_SUPPORT_CHERICAPABILITYFORMAT_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/Alignment.h"
+
+namespace llvm {
+
+template <typename Derived, typename AddressType>
+struct CHERICapabilityFormatBase {
+  CHERICapabilityFormatBase() = delete;
+
+  static constexpr AddressType AddressMask = ~static_cast<AddressType>(0);
+
+  /// Returns the "alignment mask" for an allocation of size \p Length. This
+  /// mask is 0 where the capability format alignment requires the
+  /// address to be 0, and 1 otherwise.
+  static AddressType getAlignmentMask(AddressType Length);
+
+  /// Returns the required alignment for an allocation of size \p Length.
+  static Align getRequiredAlignment(AddressType Length);
+
+  /// Returns \p Length rounded up to the nearest representable allocation
+  /// length.
+  static AddressType getRepresentableLength(AddressType Length);
+};
+
+template <typename AddressType, unsigned MW, unsigned MAX_E>
+struct RVYCapabilityFormat
+    : public CHERICapabilityFormatBase<
+          RVYCapabilityFormat<AddressType, MW, MAX_E>, AddressType> {
+  static AddressType getAlignmentMask(uint64_t Length);
+};
+
+using RV32YCapabilityFormat = RVYCapabilityFormat<uint32_t, 10, 24>;
+using RV64YCapabilityFormat = RVYCapabilityFormat<uint64_t, 14, 24>;
+
+struct CHERIoTCapabilityFormat
+    : public CHERICapabilityFormatBase<CHERIoTCapabilityFormat, uint32_t> {
+  static uint32_t getAlignmentMask(uint32_t Length);
+};
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/Support/CHERICapabilityFormat.cpp b/llvm/lib/Support/CHERICapabilityFormat.cpp
new file mode 100644
index 0000000000000..1429ea79d5411
--- /dev/null
+++ b/llvm/lib/Support/CHERICapabilityFormat.cpp
@@ -0,0 +1,77 @@
+//===- CHERICapabilityFormat.cpp ------------------------------------------===//
+//
+// 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/Support/CHERICapabilityFormat.h"
+#include "llvm/ADT/bit.h"
+
+namespace llvm {
+
+template <typename Derived, typename AddressType>
+Align CHERICapabilityFormatBase<Derived, AddressType>::getRequiredAlignment(
+    AddressType Length) {
+  return Align((~Derived::getAlignmentMask(Length) + 1) & AddressMask);
+}
+
+template <typename Derived, typename AddressType>
+AddressType
+CHERICapabilityFormatBase<Derived, AddressType>::getRepresentableLength(
+    AddressType Length) {
+  AddressType Mask = Derived::getAlignmentMask(Length);
+  return (Length + ~Mask) & Mask;
+}
+
+template <typename AddressType, unsigned MW, unsigned MAX_E>
+AddressType
+RVYCapabilityFormat<AddressType, MW, MAX_E>::getAlignmentMask(uint64_t Length) {
+  static constexpr unsigned int IE_TAKE_BITS = 3;
+
+  if (Length == 0)
+    return RVYCapabilityFormat::AddressMask;
+
+  // Extract bits that overflow the uncompressed mantissa window.
+  uint64_t Slice = static_cast<uint64_t>(Length) >> (MW - 1);
+  unsigned int E = 64 - llvm::countl_zero(Slice);
+  // We use internal exponent if length overflows OR the denormal boundary
+  // bit is set.
+  bool IE = (E != 0) || ((static_cast<uint64_t>(Length) >> (MW - 2)) & 1);
+  // Include bits used by the internal exponent for the shift value.
+  unsigned int Eprime = IE ? (E + IE_TAKE_BITS) : 0;
+
+  assert(E <= MAX_E && "Raw exponent exceeds architecture maximum");
+  assert(Eprime <= sizeof(AddressType) * 8 &&
+         "Shift amount exceeds integer width");
+
+  // Left-shift ~0 to mask out the lost precision bits
+  return RVYCapabilityFormat::AddressMask << Eprime;
+}
+
+template struct CHERICapabilityFormatBase<RVYCapabilityFormat<uint32_t, 10, 24>,
+                                          uint32_t>;
+template struct CHERICapabilityFormatBase<RVYCapabilityFormat<uint64_t, 14, 24>,
+                                          uint64_t>;
+template struct RVYCapabilityFormat<uint32_t, 10, 24>;
+template struct RVYCapabilityFormat<uint64_t, 14, 24>;
+
+uint32_t CHERIoTCapabilityFormat::getAlignmentMask(uint32_t Length) {
+  // Per section 7.13.4 and table 7.4 in the v1.0 CHERIoT specification.
+  constexpr uint32_t NINE_SET_BITS = 511;
+  uint32_t E;
+  if (Length > NINE_SET_BITS << 14)
+    E = 24;
+  else {
+    E = Length > NINE_SET_BITS ? 32 - llvm::countl_zero(Length) - 9 : 0;
+    if (Length > NINE_SET_BITS << E)
+      ++E;
+    assert(E <= 14 && "CHERIoT capabilities cannot encode E between 14 and 24");
+  }
+  return CHERIoTCapabilityFormat::AddressMask << E;
+}
+
+template struct CHERICapabilityFormatBase<CHERIoTCapabilityFormat, uint32_t>;
+
+} // namespace llvm
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 99205b8809036..d5b9947ebd4e3 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -165,6 +165,7 @@ add_llvm_component_library(LLVMSupport
   BuryPointer.cpp
   CachePruning.cpp
   Caching.cpp
+  CHERICapabilityFormat.cpp
   circular_raw_ostream.cpp
   Chrono.cpp
   COM.cpp
diff --git a/llvm/unittests/Support/CHERICapabilityFormatTest.cpp b/llvm/unittests/Support/CHERICapabilityFormatTest.cpp
new file mode 100644
index 0000000000000..1109bf9d19a40
--- /dev/null
+++ b/llvm/unittests/Support/CHERICapabilityFormatTest.cpp
@@ -0,0 +1,91 @@
+#include "llvm/Support/CHERICapabilityFormat.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+TEST(CHERICapabilityFormat, RV32Y) {
+  using RV32Y = RV32YCapabilityFormat;
+
+  EXPECT_EQ(RV32Y::AddressMask, 0xFFFFFFFF);
+
+  // Lengths up to 255 are byte-aligned.
+  for (uint64_t Len = 1; Len <= 255; ++Len) {
+    EXPECT_EQ(RV32Y::getRepresentableLength(Len), Len);
+    EXPECT_EQ(RV32Y::getRequiredAlignment(Len), 1);
+    EXPECT_EQ(RV32Y::getAlignmentMask(Len), 0xFFFFFFFF);
+  }
+
+  // Lengths up to 511 are 8-byte-aligned.
+  for (uint64_t Len = 256; Len <= 511; ++Len) {
+    EXPECT_EQ(RV32Y::getRepresentableLength(Len), (Len + 7) & 0xFFFFFFF8);
+    EXPECT_EQ(RV32Y::getRequiredAlignment(Len), 8);
+    EXPECT_EQ(RV32Y::getAlignmentMask(Len), 0xFFFFFFF8);
+  }
+
+  // Lengths up to 1023 are 16-byte-aligned.
+  for (uint64_t Len = 512; Len <= 1023; ++Len) {
+    EXPECT_EQ(RV32Y::getRepresentableLength(Len), (Len + 15) & 0xFFFFFFF0);
+    EXPECT_EQ(RV32Y::getRequiredAlignment(Len), 16);
+    EXPECT_EQ(RV32Y::getAlignmentMask(Len), 0xFFFFFFF0);
+  }
+}
+
+TEST(CHERICapabilityFormat, RV64Y) {
+  using RV64Y = RV64YCapabilityFormat;
+
+  EXPECT_EQ(RV64Y::AddressMask, 0xFFFFFFFFFFFFFFFF);
+
+  // Lengths up to 4095 are byte-aligned.
+  for (uint64_t Len = 1; Len <= 4095; ++Len) {
+    EXPECT_EQ(RV64Y::getRepresentableLength(Len), Len);
+    EXPECT_EQ(RV64Y::getRequiredAlignment(Len), 1);
+    EXPECT_EQ(RV64Y::getAlignmentMask(Len), 0xFFFFFFFFFFFFFFFF);
+  }
+
+  // Lengths up to 8191 are 8-byte-aligned.
+  for (uint64_t Len = 4096; Len <= 8191; ++Len) {
+    assert(RV64Y::getRepresentableLength(Len) ==
+           ((Len + 7) & 0xFFFFFFFFFFFFFFF8));
+    EXPECT_EQ(RV64Y::getRequiredAlignment(Len), 8);
+    EXPECT_EQ(RV64Y::getAlignmentMask(Len), 0xFFFFFFFFFFFFFFF8);
+  }
+
+  // Lengths up to 16383 are 16-byte-aligned.
+  for (uint64_t Len = 8192; Len <= 16383; ++Len) {
+    EXPECT_EQ(RV64Y::getRepresentableLength(Len),
+              (Len + 15) & 0xFFFFFFFFFFFFFFF0);
+    EXPECT_EQ(RV64Y::getRequiredAlignment(Len), 16);
+    EXPECT_EQ(RV64Y::getAlignmentMask(Len), 0xFFFFFFFFFFFFFFF0);
+  }
+}
+
+TEST(CHERICapabilityFormat, CHERIoT) {
+  using CHERIoT = CHERIoTCapabilityFormat;
+
+  EXPECT_EQ(CHERIoT::AddressMask, 0xFFFFFFFF);
+
+  // Lengths up to 511 are byte-aligned.
+  for (uint64_t Len = 1; Len <= 511; ++Len) {
+    EXPECT_EQ(CHERIoT::getRepresentableLength(Len), Len);
+    assert(CHERIoT::getRequiredAlignment(Len) == 1);
+    EXPECT_EQ(CHERIoT::getAlignmentMask(Len), 0xFFFFFFFF);
+  }
+
+  // Lengths up to 1022 are 2-byte-aligned.
+  for (uint64_t Len = 512; Len <= 1022; ++Len) {
+    EXPECT_EQ(CHERIoT::getRepresentableLength(Len), (Len + 1) & 0xFFFFFFFE);
+    EXPECT_EQ(CHERIoT::getRequiredAlignment(Len), 2);
+    EXPECT_EQ(CHERIoT::getAlignmentMask(Len), 0xFFFFFFFE);
+  }
+
+  // Lengths up to 2044 are 4-byte-aligned.
+  for (uint64_t Len = 1023; Len <= 2044; ++Len) {
+    EXPECT_EQ(CHERIoT::getRepresentableLength(Len), (Len + 3) & 0xFFFFFFFC);
+    assert(CHERIoT::getRequiredAlignment(Len) == 4);
+    EXPECT_EQ(CHERIoT::getAlignmentMask(Len), 0xFFFFFFFC);
+  }
+}
+
+} // namespace
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index a8bf96bbe096f..e808e668a87ad 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -21,6 +21,7 @@ add_llvm_unittest(SupportTests
   Caching.cpp
   Casting.cpp
   CheckedArithmeticTest.cpp
+  CHERICapabilityFormatTest.cpp
   Chrono.cpp
   CommandLineTest.cpp
   CompressionTest.cpp



More information about the llvm-commits mailing list