Skip to content

RuntimeLibcalls: Add bitset for available libcalls #150869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

arsenm
Copy link
Contributor

@arsenm arsenm commented Jul 28, 2025

This is a step towards separating the set of available libcalls
from the lowering decision of which call to use. Libcall recognition
now directly checks availability instead of indirectly checking through
the lowering table.

@llvmbot
Copy link
Member

llvmbot commented Jul 28, 2025

@llvm/pr-subscribers-llvm-adt
@llvm/pr-subscribers-tablegen
@llvm/pr-subscribers-backend-x86

@llvm/pr-subscribers-llvm-ir

Author: Matt Arsenault (arsenm)

Changes

This is a step towards separating the set of available libcalls
from the lowering decision of which call to use. Libcall recognition
now directly checks availability instead of indirectly checking through
the lowering table.


Full diff: https://github.com/llvm/llvm-project/pull/150869.diff

6 Files Affected:

  • (modified) llvm/include/llvm/IR/RuntimeLibcalls.h (+60)
  • (modified) llvm/lib/IR/RuntimeLibcalls.cpp (+2-6)
  • (modified) llvm/test/TableGen/RuntimeLibcallEmitter-calling-conv.td (+15)
  • (modified) llvm/test/TableGen/RuntimeLibcallEmitter-conflict-warning.td (+15)
  • (modified) llvm/test/TableGen/RuntimeLibcallEmitter.td (+20)
  • (modified) llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp (+24)
diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.h b/llvm/include/llvm/IR/RuntimeLibcalls.h
index ad3bb4eca1c56..0de7d14bd0f60 100644
--- a/llvm/include/llvm/IR/RuntimeLibcalls.h
+++ b/llvm/include/llvm/IR/RuntimeLibcalls.h
@@ -53,8 +53,64 @@ static inline auto libcall_impls() {
   return enum_seq(static_cast<RTLIB::LibcallImpl>(1), RTLIB::NumLibcallImpls);
 }
 
+/// Manage a bitset representing the list of available libcalls for a module.
+///
+/// Most of this exists because std::bitset cannot be statically constructed in
+/// a size large enough before c++23
+class LibcallImplBitset {
+private:
+  using BitWord = uint64_t;
+  static constexpr unsigned BitWordSize = sizeof(BitWord) * CHAR_BIT;
+  static constexpr size_t NumArrayElts =
+      divideCeil(RTLIB::NumLibcallImpls, BitWordSize);
+  using Storage = BitWord[NumArrayElts];
+
+  Storage Bits = {};
+
+  /// Get bitmask for \p Impl in its Bits element.
+  static constexpr BitWord getBitmask(RTLIB::LibcallImpl Impl) {
+    unsigned Idx = static_cast<unsigned>(Impl);
+    return BitWord(1) << (Idx % BitWordSize);
+  }
+
+  /// Get index of array element of Bits for \p Impl
+  static constexpr unsigned getArrayIdx(RTLIB::LibcallImpl Impl) {
+    return static_cast<unsigned>(Impl) / BitWordSize;
+  }
+
+public:
+  constexpr LibcallImplBitset() = default;
+  constexpr LibcallImplBitset(const Storage &Src) {
+    for (size_t I = 0; I != NumArrayElts; ++I)
+      Bits[I] = Src[I];
+  }
+
+  /// Check if a LibcallImpl is available.
+  constexpr bool test(RTLIB::LibcallImpl Impl) const {
+    BitWord Mask = getBitmask(Impl);
+    return (Bits[getArrayIdx(Impl)] & Mask) != 0;
+  }
+
+  /// Mark a LibcallImpl as available
+  void set(RTLIB::LibcallImpl Impl) {
+    assert(Impl != RTLIB::Unsupported && "cannot enable unsupported libcall");
+    Bits[getArrayIdx(Impl)] |= getBitmask(Impl);
+  }
+
+  /// Mark a LibcallImpl as unavailable
+  void unset(RTLIB::LibcallImpl Impl) {
+    assert(Impl != RTLIB::Unsupported && "cannot enable unsupported libcall");
+    Bits[getArrayIdx(Impl)] &= ~getBitmask(Impl);
+  }
+};
+
 /// A simple container for information about the supported runtime calls.
 struct RuntimeLibcallsInfo {
+private:
+  /// Bitset of libcalls a module may emit a call to.
+  LibcallImplBitset AvailableLibcallImpls;
+
+public:
   explicit RuntimeLibcallsInfo(
       const Triple &TT,
       ExceptionHandling ExceptionModel = ExceptionHandling::None,
@@ -132,6 +188,10 @@ struct RuntimeLibcallsInfo {
     return ImplToLibcall[Impl];
   }
 
+  bool isAvailable(RTLIB::LibcallImpl Impl) const {
+    return AvailableLibcallImpls.test(Impl);
+  }
+
   /// Check if this is valid libcall for the current module, otherwise
   /// RTLIB::Unsupported.
   RTLIB::LibcallImpl getSupportedLibcallImpl(StringRef FuncName) const;
diff --git a/llvm/lib/IR/RuntimeLibcalls.cpp b/llvm/lib/IR/RuntimeLibcalls.cpp
index 8c90c52141dc7..569f63f28db77 100644
--- a/llvm/lib/IR/RuntimeLibcalls.cpp
+++ b/llvm/lib/IR/RuntimeLibcalls.cpp
@@ -114,12 +114,8 @@ RuntimeLibcallsInfo::getSupportedLibcallImpl(StringRef FuncName) const {
   for (auto I = Range.begin(); I != Range.end(); ++I) {
     RTLIB::LibcallImpl Impl =
         static_cast<RTLIB::LibcallImpl>(I - RuntimeLibcallNameOffsets.begin());
-
-    // FIXME: This should not depend on looking up ImplToLibcall, only the list
-    // of libcalls for the module.
-    RTLIB::LibcallImpl Recognized = LibcallImpls[ImplToLibcall[Impl]];
-    if (Recognized != RTLIB::Unsupported)
-      return Recognized;
+    if (isAvailable(Impl))
+      return Impl;
   }
 
   return RTLIB::Unsupported;
diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter-calling-conv.td b/llvm/test/TableGen/RuntimeLibcallEmitter-calling-conv.td
index 49d5ecaa0e5c5..c2aeae285a427 100644
--- a/llvm/test/TableGen/RuntimeLibcallEmitter-calling-conv.td
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter-calling-conv.td
@@ -48,6 +48,11 @@ def MSP430LibraryWithCondCC : SystemRuntimeLibrary<isMSP430,
 // CHECK-NEXT:     Entry = DefaultCC;
 // CHECK-NEXT:   }
 // CHECK-EMPTY:
+// CHECK-NEXT:   static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:     0x0000000000001a
+// CHECK-NEXT:   });
+// CHECK-NEXT:   AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:    static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:        {RTLIB::MALLOC, RTLIB::malloc}, // malloc
 // CHECK-NEXT:    };
@@ -70,6 +75,11 @@ def MSP430LibraryWithCondCC : SystemRuntimeLibrary<isMSP430,
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT: if (TT.getArch() == Triple::avr) {
+// CHECK-NEXT:   static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:     0x0000000000001a
+// CHECK-NEXT:   });
+// CHECK-NEXT:   AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:   static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:       {RTLIB::MALLOC, RTLIB::malloc}, // malloc
 // CHECK-NEXT:   };
@@ -92,6 +102,11 @@ def MSP430LibraryWithCondCC : SystemRuntimeLibrary<isMSP430,
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT:  if (TT.getArch() == Triple::msp430) {
+// CHECK-NEXT:    static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:      0x0000000000001a
+// CHECK-NEXT:    });
+// CHECK-NEXT:    AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:    static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:        {RTLIB::MALLOC, RTLIB::malloc}, // malloc
 // CHECK-NEXT:    };
diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter-conflict-warning.td b/llvm/test/TableGen/RuntimeLibcallEmitter-conflict-warning.td
index 00c9d53846326..2a8739335a8dd 100644
--- a/llvm/test/TableGen/RuntimeLibcallEmitter-conflict-warning.td
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter-conflict-warning.td
@@ -25,6 +25,11 @@ def dup1 : RuntimeLibcallImpl<ANOTHER_DUP>;
 // func_a and func_b both provide SOME_FUNC.
 
 // CHECK: if (isTargetArchA()) {
+// CHECK-NEXT: static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:   0x00000000000018
+// CHECK-NEXT: });
+// CHECK-NEXT: AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT: static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:   {RTLIB::SOME_FUNC, RTLIB::func_b}, // func_b
 // CHECK-NEXT: };
@@ -35,6 +40,11 @@ def TheSystemLibraryA : SystemRuntimeLibrary<isTargetArchA,
 >;
 
 // CHECK: if (isTargetArchB()) {
+// CHECK-NEXT: static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:   0x00000000000058
+// CHECK-NEXT: });
+// CHECK-NEXT: AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT: static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:   {RTLIB::OTHER_FUNC, RTLIB::other_func}, // other_func
 // CHECK-NEXT:  {RTLIB::SOME_FUNC, RTLIB::func_a}, // func_a
@@ -46,6 +56,11 @@ def TheSystemLibraryB : SystemRuntimeLibrary<isTargetArchB,
 >;
 
 // CHECK: if (isTargetArchC()) {
+// CHECK-NEXT: static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:   0x0000000000007e
+// CHECK-NEXT: });
+// CHECK-NEXT: AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT: static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:   {RTLIB::ANOTHER_DUP, RTLIB::dup1}, // dup1
 // CHECK-NEXT:   {RTLIB::OTHER_FUNC, RTLIB::other_func}, // other_func
diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter.td b/llvm/test/TableGen/RuntimeLibcallEmitter.td
index 642f8b85a89c6..fe4159201574b 100644
--- a/llvm/test/TableGen/RuntimeLibcallEmitter.td
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter.td
@@ -157,6 +157,11 @@ def BlahLibrary : SystemRuntimeLibrary<isBlahArch, (add calloc, LibraryWithCondi
 // CHECK-NEXT:  };
 // CHECK-EMPTY:
 // CHECK-NEXT: if (TT.getArch() == Triple::blah) {
+// CHECK-NEXT:     static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:       0x000000000000fc
+// CHECK-NEXT:     });
+// CHECK-NEXT:     AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:     static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:         {RTLIB::BZERO, RTLIB::bzero}, // bzero
 // CHECK-NEXT:         {RTLIB::CALLOC, RTLIB::calloc}, // calloc
@@ -194,6 +199,11 @@ def BlahLibrary : SystemRuntimeLibrary<isBlahArch, (add calloc, LibraryWithCondi
 // CHECK-NEXT: }
 // CHECK-EMPTY:
 // CHECK-NEXT: if (TT.getArch() == Triple::buzz) {
+// CHECK-NEXT:    static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:      0x00000000000118
+// CHECK-NEXT:    });
+// CHECK-NEXT:    AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:    static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:        {RTLIB::SHL_I32, RTLIB::__ashlsi3}, // __ashlsi3
 // CHECK-NEXT:        {RTLIB::SQRT_F80, RTLIB::sqrtl_f80}, // sqrtl
@@ -208,6 +218,11 @@ def BlahLibrary : SystemRuntimeLibrary<isBlahArch, (add calloc, LibraryWithCondi
 // CHECK-NEXT: }
 // CHECK-EMPTY:
 // CHECK-NEXT: if (TT.getArch() == Triple::foo) {
+// CHECK-NEXT:    static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:      0x000000000000a4
+// CHECK-NEXT:    });
+// CHECK-NEXT:    AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:    static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:        {RTLIB::BZERO, RTLIB::bzero}, // bzero
 // CHECK-NEXT:        {RTLIB::SQRT_F128, RTLIB::sqrtl_f128}, // sqrtl
@@ -232,6 +247,11 @@ def BlahLibrary : SystemRuntimeLibrary<isBlahArch, (add calloc, LibraryWithCondi
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT: if (TT.getArch() == Triple::simple) {
+// CHECK-NEXT:    static constexpr LibcallImplBitset SystemAvailableImpls({
+// CHECK-NEXT:      0x00000000000158
+// CHECK-NEXT:    });
+// CHECK-NEXT:    AvailableLibcallImpls = SystemAvailableImpls;
+// CHECK-EMPTY:
 // CHECK-NEXT:    static const LibcallImplPair LibraryCalls[] = {
 // CHECK-NEXT:        {RTLIB::CALLOC, RTLIB::calloc}, // calloc
 // CHECK-NEXT:        {RTLIB::SHL_I32, RTLIB::__ashlsi3}, // __ashlsi3
diff --git a/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp b/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
index 412431b96d030..853209f94ea2c 100644
--- a/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TableGen/Error.h"
@@ -402,6 +403,10 @@ void RuntimeLibcallEmitter::emitSystemRuntimeLibrarySetCalls(
         PredicateWithCC()); // No predicate or CC override first.
 
     DenseMap<PredicateWithCC, LibcallsWithCC> Pred2Funcs;
+
+    SmallVector<uint64_t, 32> BitsetValues(
+        divideCeil(RuntimeLibcallImplDefList.size(), 64));
+
     for (const Record *Elt : *Elements) {
       const RuntimeLibcallImpl *LibCallImpl = getRuntimeLibcallImpl(Elt);
       if (!LibCallImpl) {
@@ -410,6 +415,9 @@ void RuntimeLibcallEmitter::emitSystemRuntimeLibrarySetCalls(
         continue;
       }
 
+      size_t Idx = LibCallImpl->getEnumVal();
+      BitsetValues[Idx / 64] |= uint64_t(1) << (Idx % 64);
+
       auto It = Func2Preds.find(LibCallImpl);
       if (It == Func2Preds.end()) {
         Pred2Funcs[PredicateWithCC()].LibcallImpls.push_back(LibCallImpl);
@@ -427,6 +435,22 @@ void RuntimeLibcallEmitter::emitSystemRuntimeLibrarySetCalls(
       }
     }
 
+    OS << "    static constexpr LibcallImplBitset SystemAvailableImpls({\n"
+       << indent(6);
+
+    ListSeparator LS;
+    unsigned EntryCount = 0;
+    for (uint64_t Bits : BitsetValues) {
+      if (EntryCount++ == 4) {
+        EntryCount = 1;
+        OS << ",\n" << indent(6);
+      } else
+        OS << LS;
+      OS << format_hex(Bits, 16);
+    }
+    OS << "\n    });\n"
+          "    AvailableLibcallImpls = SystemAvailableImpls;\n\n";
+
     SmallVector<PredicateWithCC, 0> SortedPredicates =
         PredicateSorter.takeVector();
 

@arsenm arsenm force-pushed the users/arsenm/tablegen/emit-runtime-libcalls-available-bitset branch from 61b2148 to 78b0865 Compare July 28, 2025 03:26
@arsenm arsenm force-pushed the users/arsenm/runtime-libcalls/use-tablegen-default-handling branch from 5843fba to a8d3505 Compare August 3, 2025 00:41
@arsenm arsenm force-pushed the users/arsenm/tablegen/emit-runtime-libcalls-available-bitset branch 2 times, most recently from 1481306 to c992d2b Compare August 3, 2025 16:32
@arsenm arsenm force-pushed the users/arsenm/runtime-libcalls/use-tablegen-default-handling branch from a8d3505 to 744161e Compare August 3, 2025 16:32
Base automatically changed from users/arsenm/runtime-libcalls/use-tablegen-default-handling to main August 3, 2025 23:32
@arsenm arsenm force-pushed the users/arsenm/tablegen/emit-runtime-libcalls-available-bitset branch from c992d2b to d3ad2c4 Compare August 4, 2025 06:18
/// Manage a bitset representing the list of available libcalls for a module.
///
/// Most of this exists because std::bitset cannot be statically constructed in
/// a size large enough before c++23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this what llvm/include/llvm/ADT/Bitset.h is for?

@arsenm arsenm force-pushed the users/arsenm/tablegen/emit-runtime-libcalls-available-bitset branch from d3ad2c4 to 7b13ce5 Compare August 4, 2025 13:07
@@ -401,7 +402,13 @@ void RuntimeLibcallEmitter::emitSystemRuntimeLibrarySetCalls(
PredicateSorter.insert(
PredicateWithCC()); // No predicate or CC override first.

constexpr unsigned BitsPerStorageElt = sizeof(uintptr_t) * CHAR_BIT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When cross-building the compiler, TableGen is build for and runs on host, but the compiler will run on target, which may have different uintptr_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure there's a ready made way to figure out what the generated target is from here. I guess it would be easier to make the Bitset underlying type configurable to always use uint64_t

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be easier to make the Bitset underlying type configurable to always use uint64_t

That sounds good to me

kuhar
kuhar previously requested changes Aug 4, 2025
Copy link
Member

@kuhar kuhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you land the bitset changes in a separate PR? We prefer to keep ADT changes isolated since they affect the virtually the whole project, so any reverts rebuilt the whole tree.

arsenm added 3 commits August 5, 2025 02:23
This is a step towards separating the set of available libcalls
from the lowering decision of which call to use. Libcall recognition
now directly checks availability instead of indirectly checking through
the lowering table.
@arsenm arsenm force-pushed the users/arsenm/tablegen/emit-runtime-libcalls-available-bitset branch from 7b13ce5 to b2cb9ad Compare August 4, 2025 17:23
@llvmbot llvmbot added the cmake Build system in general and CMake in particular label Aug 4, 2025
@kuhar kuhar requested review from kuhar and removed request for kuhar August 4, 2025 17:48
@kuhar kuhar dismissed their stale review August 4, 2025 17:48

Unblocking for ADT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:X86 cmake Build system in general and CMake in particular llvm:adt llvm:ir tablegen
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants