[llvm] [orc-rt] Add MemProt, MemLifetime, AllocGroup, and AllocGroupMap. (PR #157078)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 5 04:39:06 PDT 2025


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/157078

MemProt and MemLifetime are enum classes representing memory protection (Read | Write | Exec) and lifetime policy (Standard or Finalize-only) respectively.

An AllocGroup is a compressed (MemProt, MemLifetime) pair.

AllocGroupSmallMap<T> is a compressed map of AllocGroup -> T.

These utilities will be used in upcoming memory management APIs in the ORC runtime.

>From 1c8170cbb0a19606a4c5d4cf3d7196c448a88f0c Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Fri, 5 Sep 2025 21:31:48 +1000
Subject: [PATCH] [orc-rt] Add MemProt, MemLifetime, AllocGroup, and
 AllocGroupMap.

MemProt and MemLifetime are enum classes representing memory protection
(Read | Write | Exec) and lifetime policy (Standard or Finalize-only)
respectively.

An AllocGroup is a compressed (MemProt, MemLifetime) pair.

AllocGroupSmallMap<T> is a compressed map of AllocGroup -> T.

These utilities will be used in upcoming memory management APIs in the ORC
runtime.
---
 orc-rt/include/CMakeLists.txt        |   1 +
 orc-rt/include/orc-rt/BitmaskEnum.h  |   9 ++
 orc-rt/include/orc-rt/MemoryFlags.h  | 136 +++++++++++++++++++++++++++
 orc-rt/unittests/CMakeLists.txt      |   1 +
 orc-rt/unittests/MemoryFlagsTest.cpp |  89 ++++++++++++++++++
 5 files changed, 236 insertions(+)
 create mode 100644 orc-rt/include/orc-rt/MemoryFlags.h
 create mode 100644 orc-rt/unittests/MemoryFlagsTest.cpp

diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index eb0088fe4061a..07a7e52061d6c 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -9,6 +9,7 @@ set(ORC_RT_HEADERS
     orc-rt/IntervalMap.h
     orc-rt/IntervalSet.h
     orc-rt/Math.h
+    orc-rt/MemoryFlags.h
     orc-rt/RTTI.h
     orc-rt/WrapperFunction.h
     orc-rt/SimplePackedSerialization.h
diff --git a/orc-rt/include/orc-rt/BitmaskEnum.h b/orc-rt/include/orc-rt/BitmaskEnum.h
index f3aff3287a2ff..a2edce6c795f3 100644
--- a/orc-rt/include/orc-rt/BitmaskEnum.h
+++ b/orc-rt/include/orc-rt/BitmaskEnum.h
@@ -17,6 +17,7 @@
 #define ORC_RT_BITMASKENUM_H
 
 #include "Math.h"
+#include "bit.h"
 
 #include <cassert>
 #include <type_traits>
@@ -114,6 +115,14 @@ constexpr std::underlying_type_t<E> bitmask_enum_to_underlying(E Val) noexcept {
   return U;
 }
 
+template <typename E, typename _ = std::enable_if_t<is_bitmask_enum_v<E>>>
+struct bitmask_enum_num_bits {
+  static constexpr int value = bit_width(largest_bitmask_enum_bit<E>::value);
+};
+
+template <typename E>
+inline constexpr int bitmask_enum_num_bits_v = bitmask_enum_num_bits<E>::value;
+
 template <typename E, typename = std::enable_if_t<is_bitmask_enum_v<E>>>
 constexpr E operator~(E Val) noexcept {
   return static_cast<E>(~bitmask_enum_to_underlying(Val) &
diff --git a/orc-rt/include/orc-rt/MemoryFlags.h b/orc-rt/include/orc-rt/MemoryFlags.h
new file mode 100644
index 0000000000000..24384e62d8b09
--- /dev/null
+++ b/orc-rt/include/orc-rt/MemoryFlags.h
@@ -0,0 +1,136 @@
+//===--------- MemoryFlags.h -- Memory allocation flags ---------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Memory allocation flags.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_MEMORYFLAGS_H
+#define ORC_RT_MEMORYFLAGS_H
+
+#include "orc-rt/BitmaskEnum.h"
+#include "orc-rt/bit.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+namespace orc_rt {
+
+/// Describes Read/Write/Exec permissions for memory.
+enum class MemProt : unsigned {
+  None = 0,
+  Read = 1U << 0,
+  Write = 1U << 1,
+  Exec = 1U << 2,
+  ORC_RT_MARK_AS_BITMASK_ENUM(/* LargestValue = */ Exec)
+};
+
+/// Describes a memory lifetime policy.
+enum class MemLifetime : unsigned {
+  /// Standard memory should be deallocated by the corresponding call to
+  /// deallocate.
+  Standard,
+
+  /// Finalize memory should be deallocated at the end of the finalization
+  /// process.
+  Finalize
+};
+
+/// A pair of memory protections and lifetime policy.
+class AllocGroup {
+private:
+  static constexpr int NumProtBits = bitmask_enum_num_bits_v<MemProt>;
+  static constexpr int NumLifetimeBits = 1;
+  static constexpr int NumBits = NumProtBits + NumLifetimeBits;
+
+  typedef uint8_t underlying_type;
+
+  static_assert(NumBits <= std::numeric_limits<underlying_type>::digits,
+                "Not enough bits to hold (prot, lifetime) pair");
+
+  constexpr static underlying_type ProtMask = (1U << NumProtBits) - 1;
+  constexpr static underlying_type LifetimeMask = (1U << NumLifetimeBits) - 1;
+
+public:
+  static constexpr size_t MaxValues = 1U << NumBits;
+
+  AllocGroup() = default;
+  AllocGroup(MemProt MP, MemLifetime ML = MemLifetime::Standard)
+      : Id((static_cast<underlying_type>(ML) << NumProtBits) |
+           static_cast<underlying_type>(MP)) {}
+
+  MemProt getMemProt() const { return static_cast<MemProt>(Id & ProtMask); }
+
+  MemLifetime getMemLifetime() const {
+    return static_cast<MemLifetime>((Id >> NumProtBits) & LifetimeMask);
+  }
+
+  friend bool operator==(const AllocGroup &LHS, const AllocGroup &RHS) {
+    return LHS.Id == RHS.Id;
+  }
+
+  friend bool operator!=(const AllocGroup &LHS, const AllocGroup &RHS) {
+    return !(LHS == RHS);
+  }
+
+  friend bool operator<(const AllocGroup &LHS, const AllocGroup &RHS) {
+    return LHS.Id < RHS.Id;
+  }
+
+private:
+  underlying_type Id = 0;
+};
+
+/// A specialized small-map for AllocGroups.
+///
+/// Iteration order is guaranteed to match key ordering.
+template <typename T> class AllocGroupSmallMap {
+private:
+  using ElemT = std::pair<AllocGroup, T>;
+  using VectorTy = std::vector<ElemT>;
+
+  static bool compareKey(const ElemT &E, const AllocGroup &G) {
+    return E.first < G;
+  }
+
+public:
+  using iterator = typename VectorTy::iterator;
+
+  AllocGroupSmallMap() = default;
+  AllocGroupSmallMap(std::initializer_list<std::pair<AllocGroup, T>> Inits)
+      : Elems(Inits) {
+    std::sort(Elems, [](const ElemT &LHS, const ElemT &RHS) {
+      return LHS.first < RHS.first;
+    });
+  }
+
+  iterator begin() { return Elems.begin(); }
+  iterator end() { return Elems.end(); }
+  iterator find(AllocGroup G) {
+    auto I = std::lower_bound(Elems.begin(), Elems.end(), G, compareKey);
+    return (I == end() || I->first == G) ? I : end();
+  }
+
+  bool empty() const { return Elems.empty(); }
+  size_t size() const { return Elems.size(); }
+
+  T &operator[](AllocGroup G) {
+    auto I = std::lower_bound(Elems.begin(), Elems.end(), G, compareKey);
+    if (I == Elems.end() || I->first != G)
+      I = Elems.insert(I, std::make_pair(G, T()));
+    return I->second;
+  }
+
+private:
+  VectorTy Elems;
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_MEMORYFLAGS_H
diff --git a/orc-rt/unittests/CMakeLists.txt b/orc-rt/unittests/CMakeLists.txt
index b390e27110d00..55e089a539725 100644
--- a/orc-rt/unittests/CMakeLists.txt
+++ b/orc-rt/unittests/CMakeLists.txt
@@ -19,6 +19,7 @@ add_orc_rt_unittest(CoreTests
   IntervalMapTest.cpp
   IntervalSetTest.cpp
   MathTest.cpp
+  MemoryFlagsTest.cpp
   RTTITest.cpp
   SimplePackedSerializationTest.cpp
   WrapperFunctionBufferTest.cpp
diff --git a/orc-rt/unittests/MemoryFlagsTest.cpp b/orc-rt/unittests/MemoryFlagsTest.cpp
new file mode 100644
index 0000000000000..9b77b188adb98
--- /dev/null
+++ b/orc-rt/unittests/MemoryFlagsTest.cpp
@@ -0,0 +1,89 @@
+//===- MemoryFlags.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for orc-rt's MemoryFlags.h APIs.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/MemoryFlags.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+TEST(MemProtTest, Basics) {
+  MemProt MPNone = MemProt::None;
+
+  EXPECT_EQ(MPNone & MemProt::Read, MemProt::None);
+  EXPECT_EQ(MPNone & MemProt::Write, MemProt::None);
+  EXPECT_EQ(MPNone & MemProt::Exec, MemProt::None);
+
+  EXPECT_EQ(MPNone | MemProt::Read, MemProt::Read);
+  EXPECT_EQ(MPNone | MemProt::Write, MemProt::Write);
+  EXPECT_EQ(MPNone | MemProt::Exec, MemProt::Exec);
+
+  MemProt MPAll = MemProt::Read | MemProt::Write | MemProt::Exec;
+  EXPECT_EQ(MPAll & MemProt::Read, MemProt::Read);
+  EXPECT_EQ(MPAll & MemProt::Write, MemProt::Write);
+  EXPECT_EQ(MPAll & MemProt::Exec, MemProt::Exec);
+}
+
+TEST(AllocGroupTest, Default) {
+  AllocGroup G;
+  EXPECT_EQ(G.getMemProt(), MemProt::None);
+  EXPECT_EQ(G.getMemLifetime(), MemLifetime::Standard);
+}
+
+TEST(AllocGroupTest, InitMemProtOnly) {
+  AllocGroup G(MemProt::Read | MemProt::Write);
+  EXPECT_EQ(G.getMemProt(), MemProt::Read | MemProt::Write);
+  EXPECT_EQ(G.getMemLifetime(), MemLifetime::Standard);
+}
+
+TEST(AllocGroupTest, InitMemProtAndLifetime) {
+  AllocGroup G(MemProt::Read | MemProt::Write, MemLifetime::Finalize);
+  EXPECT_EQ(G.getMemProt(), MemProt::Read | MemProt::Write);
+  EXPECT_EQ(G.getMemLifetime(), MemLifetime::Finalize);
+}
+
+TEST(AllocGroupTest, Equality) {
+  AllocGroup G(MemProt::Read, MemLifetime::Standard);
+  EXPECT_EQ(G, AllocGroup(MemProt::Read, MemLifetime::Standard));
+  EXPECT_NE(G, AllocGroup(MemProt::Write, MemLifetime::Standard));
+  EXPECT_NE(G, AllocGroup(MemProt::Read, MemLifetime::Finalize));
+}
+
+TEST(AllocGroupTest, LessThan) {
+  // Check that AllocGroups can be compared via less-than so that they can be
+  // used as keys in standard containers.
+  EXPECT_LT(AllocGroup(MemProt::Read, MemLifetime::Standard),
+            AllocGroup(MemProt::Write, MemLifetime::Standard));
+  EXPECT_LT(AllocGroup(MemProt::Exec, MemLifetime::Standard),
+            AllocGroup(MemProt::Read, MemLifetime::Finalize));
+}
+
+TEST(AllocGroupSmallMap, EmptyMap) {
+  AllocGroupSmallMap<bool> EM;
+  EXPECT_TRUE(EM.empty());
+  EXPECT_EQ(EM.size(), 0u);
+}
+
+TEST(AllocGroupSmallMap, NonEmptyMap) {
+  AllocGroupSmallMap<unsigned> NEM;
+  NEM[MemProt::Read] = 42;
+
+  EXPECT_FALSE(NEM.empty());
+  EXPECT_EQ(NEM.size(), 1U);
+  EXPECT_EQ(NEM[MemProt::Read], 42U);
+  EXPECT_EQ(NEM.find(MemProt::Read), NEM.begin());
+  EXPECT_EQ(NEM.find(MemProt::Read | MemProt::Write), NEM.end());
+
+  NEM[MemProt::Read | MemProt::Write] = 7;
+  EXPECT_EQ(NEM.size(), 2U);
+  EXPECT_EQ(NEM.begin()->second, 42U);
+  EXPECT_EQ((NEM.begin() + 1)->second, 7U);
+}



More information about the llvm-commits mailing list