[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