[llvm-branch-commits] [llvm] RuntimeLibcallsEmitter: Let a consumer's own library variant beat its exclusion (PR #221953)

Matt Arsenault via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Sep 8 04:33:21 PDT 2026


https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/221953

>From 8137c1dc0b090147396f8d1067a0a5543364c51f Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Thu, 13 Aug 2026 16:11:20 +0200
Subject: [PATCH] RuntimeLibcallsEmitter: Let a consumer's own library variant
 beat its exclusion

A target can pull a shared library via LibraryRef<Lib, [impls]> to drop some
impls, then re-add its own versions through a same-name library variant guarded
on that target. Previously the exclusion's setUnavailable calls were emitted at
the end of setAvailableLibFuncs_<name>, after every variant, so they clobbered
the target's own re-adds.

Defer emitting a variant until after the exclusions when it re-adds an impl its
own consumer excludes (same predicates), so the target's re-add wins while
the exclusion still suppresses every other variant's contribution.

This is yet unused infrastructure for future changes.

Co-authored-by: Claude (Opus 4.8) <noreply at anthropic.com>
---
 ...eLibcallEmitter-variant-beats-exclusion.td | 55 +++++++++++
 .../TableGen/Basic/RuntimeLibcallsEmitter.cpp | 98 ++++++++++++++-----
 2 files changed, 126 insertions(+), 27 deletions(-)
 create mode 100644 llvm/test/TableGen/RuntimeLibcallEmitter-variant-beats-exclusion.td

diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter-variant-beats-exclusion.td b/llvm/test/TableGen/RuntimeLibcallEmitter-variant-beats-exclusion.td
new file mode 100644
index 0000000000000..94abe0c0b1a1f
--- /dev/null
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter-variant-beats-exclusion.td
@@ -0,0 +1,55 @@
+// RUN: llvm-tblgen -gen-runtime-libcalls -I %p/../../include %s | FileCheck %s
+
+// Check that when a target excludes some impls from a shared library via
+// LibraryRef AND re-adds them through its own same-name library variant, the
+// variant's adds are emitted AFTER the exclusion's setUnavailable calls. This
+// lets a target's own re-adds win over its own opt-out, while the exclusion
+// still suppresses those impls for any other variant's contribution.
+
+include "llvm/IR/RuntimeLibcallsImpl.td"
+
+def ADD_F32 : RuntimeLibcall;
+def SUB_F32 : RuntimeLibcall;
+def SQRT_F64 : RuntimeLibcall;
+
+def __addsf3 : RuntimeLibcallImpl<ADD_F32>;
+def __subsf3 : RuntimeLibcallImpl<SUB_F32>;
+def sqrt : RuntimeLibcallImpl<SQRT_F64, "sqrt">;
+
+def IsARM : LibcallPredicate<[{TT.isARM()}]>;
+def IsOSWindows : LibcallPredicate<[{TT.isOSWindows()}]>;
+def isARM : RuntimeLibcallAvailability<(all_of IsARM)>;
+def isNotOSWindows : RuntimeLibcallAvailability<(not IsOSWindows)>;
+
+// The shared compiler-rt core provides __addsf3/__subsf3 unconditionally.
+def CompilerRt : LibcallLibrary<"compiler-rt", (add __addsf3, __subsf3)>;
+def Libm : LibcallLibrary<"libm", (add sqrt)>;
+
+// ARM's own compiler-rt variant re-adds __addsf3 on non-Windows ARM
+// only. The per-member predicate keeps it out of the shared core so
+// it stays a distinct re-add.
+def ARMCompilerRt : LibcallLibrary<"compiler-rt",
+    (add LibcallImpls<(add __addsf3), isNotOSWindows>), isARM>;
+
+// ARM excludes __addsf3 from the shared core (all ARM), then re-adds
+// it via ARMCompilerRt on non-Windows ARM.
+def ARMSystem : SystemRuntimeLibrary<isARM,
+    (add LibraryRef<CompilerRt, [__addsf3]>, Libm)>;
+
+// In the merged compiler-rt function: the shared core runs first,
+// then the exclusion's setUnavailable, then ARM's own variant re-add
+// (deferred past the exclusion because it re-adds an excluded impl on
+// the same consumer).
+//
+// CHECK: void llvm::RTLIB::RuntimeLibcallsInfo::setAvailableLibFuncs_compiler_rt(
+// CHECK: RTLIB::impl___addsf3, // __addsf3
+// CHECK-NEXT: RTLIB::impl___subsf3, // __subsf3
+// CHECK: setAvailable(Impl);
+// CHECK: if (TT.isARM()) {
+// CHECK-NEXT: setUnavailable(RTLIB::impl___addsf3); // __addsf3
+// CHECK-NEXT: }
+// CHECK: if (TT.isARM()) {
+// CHECK-NEXT: if (!(TT.isOSWindows())) {
+// CHECK-NEXT: static const RTLIB::LibcallImpl LibraryCalls_isNotOSWindows[] = {
+// CHECK-NEXT: RTLIB::impl___addsf3, // __addsf3
+// CHECK: setAvailable(Impl);
diff --git a/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp b/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
index 89483591a5448..7a72b1061c737 100644
--- a/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/RuntimeLibcallsEmitter.cpp
@@ -105,6 +105,23 @@ class RuntimeLibcallEmitter {
     std::vector<const RuntimeLibcallImpl *> Impls;
   };
 
+  // A single LibcallLibrary variant, expanded into its per-predicate impl
+  // groups. Unconditional impls are tracked separately for cross-variant
+  // deduplication. A variant is Deferred when it re-adds an impl its own
+  // consumer excludes; deferred variants are emitted after the LibraryRef
+  // exclusions so the re-add wins over the opt-out.
+  struct ExpandedLibrary {
+    const Record *Lib;
+    DenseMap<PredicateWithCC, LibcallsWithCC> Pred2Funcs;
+    SetVector<PredicateWithCC> PredicateSorter;
+    SetVector<const RuntimeLibcallImpl *> Unconditional;
+    bool Deferred = false;
+  };
+
+  // Emit one variant's guarded `setAvailable` block into the enclosing
+  // `setAvailableLibFuncs_<name>` function.
+  void emitLibraryVariant(raw_ostream &OS, ExpandedLibrary &EL) const;
+
   // Emit a `setAvailableLibFuncs_<name>` member function for all LibcallLibrary
   // defs sharing \p Name, each gated by its own availability predicate. \p
   // Exclusions are emitted as guarded setUnavailable calls at the end.
@@ -502,6 +519,29 @@ static void emitLibFuncSuffix(raw_ostream &OS, StringRef Name) {
     OS << (isAlnum(C) || C == '_' ? C : '_');
 }
 
+void RuntimeLibcallEmitter::emitLibraryVariant(raw_ostream &OS,
+                                               ExpandedLibrary &EL) const {
+  AvailabilityPredicate LibPred(EL.Lib->getValueAsDef("Pred"));
+
+  if (!LibPred.isAlwaysAvailable()) {
+    OS << indent(2);
+    LibPred.emitIf(OS);
+  } else {
+    // Own block scope so per-variant `LibraryCalls` tables do not collide.
+    OS << indent(2) << "{\n";
+  }
+
+  emitPredicateGroups(OS, EL.Lib, EL.Pred2Funcs, EL.PredicateSorter,
+                      /*BaseIndent=*/2);
+
+  if (!LibPred.isAlwaysAvailable()) {
+    OS << indent(2);
+    LibPred.emitEndIf(OS);
+  } else {
+    OS << indent(2) << "}\n";
+  }
+}
+
 void RuntimeLibcallEmitter::emitLibraryFunction(
     raw_ostream &OS, StringRef Name, ArrayRef<const Record *> Libs,
     ArrayRef<LibraryExclusion> Exclusions) const {
@@ -512,15 +552,6 @@ void RuntimeLibcallEmitter::emitLibraryFunction(
         "EABI EABIVersion, StringRef ABIName, "
         "LongDoubleFormat LongDoubleFormat) {\n";
 
-  // Per-variant expansion. Unconditional impls are tracked separately for
-  // cross-variant deduplication.
-  struct ExpandedLibrary {
-    const Record *Lib;
-    DenseMap<PredicateWithCC, LibcallsWithCC> Pred2Funcs;
-    SetVector<PredicateWithCC> PredicateSorter;
-    SetVector<const RuntimeLibcallImpl *> Unconditional;
-  };
-
   SmallVector<ExpandedLibrary, 2> Expanded;
   for (const Record *Lib : Libs) {
     ExpandedLibrary EL;
@@ -599,29 +630,34 @@ void RuntimeLibcallEmitter::emitLibraryFunction(
     }
   }
 
-  // Emit each variant under its own Pred.
+  // Mark a variant deferred when it re-adds an impl its own consumer excludes
+  // (same triple, via LibraryRef). Such a variant must be emitted after the
+  // exclusion so the re-add wins while the exclusion still suppresses every
+  // other variant's contribution.
   for (ExpandedLibrary &EL : Expanded) {
-    AvailabilityPredicate LibPred(EL.Lib->getValueAsDef("Pred"));
-
-    if (!LibPred.isAlwaysAvailable()) {
-      OS << indent(2);
-      LibPred.emitIf(OS);
-    } else {
-      // Own block scope so per-variant `LibraryCalls` tables do not collide.
-      OS << indent(2) << "{\n";
-    }
+    const Record *ELPred = EL.Lib->getValueAsDef("Pred");
+    SetVector<const RuntimeLibcallImpl *> Impls;
+    for (const auto &[Key, Funcs] : EL.Pred2Funcs)
+      Impls.insert(Funcs.LibcallImpls.begin(), Funcs.LibcallImpls.end());
 
-    emitPredicateGroups(OS, EL.Lib, EL.Pred2Funcs, EL.PredicateSorter,
-                        /*BaseIndent=*/2);
-
-    if (!LibPred.isAlwaysAvailable()) {
-      OS << indent(2);
-      LibPred.emitEndIf(OS);
-    } else {
-      OS << indent(2) << "}\n";
+    for (const LibraryExclusion &Excl : Exclusions) {
+      if (Excl.TriplePred != ELPred)
+        continue;
+      if (any_of(Excl.Impls, [&](const RuntimeLibcallImpl *Impl) {
+            return Impls.contains(Impl);
+          })) {
+        EL.Deferred = true;
+        break;
+      }
     }
   }
 
+  // Emit each non-deferred variant under its own Pred.
+  for (ExpandedLibrary &EL : Expanded) {
+    if (!EL.Deferred)
+      emitLibraryVariant(OS, EL);
+  }
+
   // Emit each consumer's LibraryRef opt-outs.
   for (const LibraryExclusion &Excl : Exclusions) {
     OS << '\n' << indent(2);
@@ -637,6 +673,14 @@ void RuntimeLibcallEmitter::emitLibraryFunction(
     ExcludePred.emitEndIf(OS);
   }
 
+  // Deferred variants: emitted after exclusions so a target's own re-adds
+  // override its own LibraryRef opt-outs (the exclusion still applied above
+  // to every other variant's contributions).
+  for (ExpandedLibrary &EL : Expanded) {
+    if (EL.Deferred)
+      emitLibraryVariant(OS, EL);
+  }
+
   OS << "}\n\n";
 }
 



More information about the llvm-branch-commits mailing list