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 <[email protected]> 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) <[email protected]> --- ...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"; } _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
