[llvm-branch-commits] [llvm] RuntimeLibcalls: Reuse AssemblerPredicate's operators for libcalls (PR #210651)
Matt Arsenault via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jul 20 02:10:06 PDT 2026
https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/210651
>From 87febc44fb751d29cc68b6ed598cbd0224eaa5d2 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Sat, 18 Jul 2026 07:31:48 +0200
Subject: [PATCH] RuntimeLibcalls: Reuse AssemblerPredicate's operators for
libcalls
Allow specifying RuntimeLibcall's availability in terms of individual
triple properties composed with logical operators.
Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>
---
llvm/include/llvm/IR/RuntimeLibcallsImpl.td | 31 +++++++++-
...timeLibcallEmitter-predicate-dag-errors.td | 42 +++++++++++++
.../RuntimeLibcallEmitter-predicate-dag.td | 60 +++++++++++++++++++
llvm/utils/TableGen/Basic/CMakeLists.txt | 1 +
.../TableGen/Basic/PredicateExpanderDag.cpp | 56 +++++++++++++++++
.../TableGen/Basic/PredicateExpanderDag.h | 41 +++++++++++++
llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp | 22 +++++++
llvm/utils/TableGen/Basic/RuntimeLibcalls.h | 19 +++++-
.../TableGen/Common/SubtargetFeatureInfo.cpp | 59 ++++++++----------
9 files changed, 291 insertions(+), 40 deletions(-)
create mode 100644 llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag-errors.td
create mode 100644 llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag.td
create mode 100644 llvm/utils/TableGen/Basic/PredicateExpanderDag.cpp
create mode 100644 llvm/utils/TableGen/Basic/PredicateExpanderDag.h
diff --git a/llvm/include/llvm/IR/RuntimeLibcallsImpl.td b/llvm/include/llvm/IR/RuntimeLibcallsImpl.td
index 8d4eced6aea4f..e0a6b57bdd2e4 100644
--- a/llvm/include/llvm/IR/RuntimeLibcallsImpl.td
+++ b/llvm/include/llvm/IR/RuntimeLibcallsImpl.td
@@ -8,13 +8,40 @@
include "llvm/TableGen/SetTheory.td"
+// Dag operators for composing predicates.
+def all_of;
+def any_of;
+def not;
+
+// Composable predicate leaf expressed as a C++ boolean over an
+// `Triple TT`. Generally should check a single triple property.
+class LibcallPredicate<code cond> {
+ code Cond = cond;
+}
+
// Predicate for whether a libcall exists for the target ABI. This is
// a module level property that should only be computed based on the
// triple.
-class RuntimeLibcallAvailability<code cond> {
- // Expression of an llvm::Triple named TT for whether a libcall
+//
+// Two forms are accepted:
+// - a raw C++ `code` condition over `Triple TT`
+// - a `dag` of LibcallPredicate leaves combined with all_of/any_of/not,
+// which the emitter lowers to the equivalent C++ boolean.
+// The dag form takes precedence when present.
+//
+// TODO: Remove the C++ form
+class RuntimeLibcallAvailability<code cond = [{}]> {
+ // Expression of an Triple named TT for whether a libcall
// should exist.
code Cond = cond;
+
+ // Composable form: (all_of/any_of/not <LibcallPredicate>...). Unset (?)
+ // means the `Cond` code form is used instead.
+ dag CondDag = ?;
+}
+
+class RuntimeLibcallAvailabilityDag<dag cond> : RuntimeLibcallAvailability<[{}]> {
+ let CondDag = cond;
}
// Predicate for whether a libcall should be used for the current
diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag-errors.td b/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag-errors.td
new file mode 100644
index 0000000000000..e81c427174b89
--- /dev/null
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag-errors.td
@@ -0,0 +1,42 @@
+// Error cases for the composable predicate dag (all_of/any_of/not over
+// LibcallPredicate leaves).
+
+// RUN: not llvm-tblgen -gen-runtime-libcalls -I %p/../../include -DERR_NOT_ATOM %s 2>&1 | FileCheck -check-prefix=NOT-ATOM %s
+// RUN: not llvm-tblgen -gen-runtime-libcalls -I %p/../../include -DERR_BAD_OP %s 2>&1 | FileCheck -check-prefix=BAD-OP %s
+// RUN: not llvm-tblgen -gen-runtime-libcalls -I %p/../../include -DERR_NOT_ARITY %s 2>&1 | FileCheck -check-prefix=NOT-ARITY %s
+// RUN: not llvm-tblgen -gen-runtime-libcalls -I %p/../../include -DERR_EMPTY %s 2>&1 | FileCheck -check-prefix=EMPTY %s
+
+include "llvm/IR/RuntimeLibcallsImpl.td"
+
+def FUNC0 : RuntimeLibcall;
+def impl0 : RuntimeLibcallImpl<FUNC0>;
+
+def IsA : LibcallPredicate<[{TT.isAArch64()}]>;
+def IsB : LibcallPredicate<[{TT.isOSDarwin()}]>;
+def NotAnAtom : RuntimeLibcall;
+
+def isArch : RuntimeLibcallAvailability<[{isArch()}]>;
+
+#ifdef ERR_NOT_ATOM
+// NOT-ATOM: error: predicate dag leaf 'NotAnAtom' is not a LibcallPredicate
+def badpred : RuntimeLibcallAvailabilityDag<(all_of NotAnAtom)>;
+#endif
+
+#ifdef ERR_BAD_OP
+// A defined record that isn't all_of/any_of/not used as the dag operator.
+// BAD-OP: error: unknown predicate dag operator 'IsA'; expected all_of/any_of/not
+def badpred : RuntimeLibcallAvailabilityDag<(IsA IsB)>;
+#endif
+
+#ifdef ERR_NOT_ARITY
+// NOT-ARITY: error: 'not' takes exactly one operand
+def badpred : RuntimeLibcallAvailabilityDag<(not IsA, IsB)>;
+#endif
+
+#ifdef ERR_EMPTY
+// EMPTY: error: 'all_of' requires at least one operand
+def badpred : RuntimeLibcallAvailabilityDag<(all_of)>;
+#endif
+
+def Sys : SystemRuntimeLibrary<isArch,
+ (add LibcallImpls<(add impl0), badpred>)>;
diff --git a/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag.td b/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag.td
new file mode 100644
index 0000000000000..252bba6b00392
--- /dev/null
+++ b/llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag.td
@@ -0,0 +1,60 @@
+// RUN: llvm-tblgen -gen-runtime-libcalls -I %p/../../include %s | FileCheck %s
+
+// Check logical operators over LibcallPredicate work
+
+include "llvm/IR/RuntimeLibcallsImpl.td"
+
+def FUNC0 : RuntimeLibcall;
+def FUNC1 : RuntimeLibcall;
+def FUNC2 : RuntimeLibcall;
+def FUNC3 : RuntimeLibcall;
+def FUNC4 : RuntimeLibcall;
+
+def impl0 : RuntimeLibcallImpl<FUNC0>;
+def impl1 : RuntimeLibcallImpl<FUNC1>;
+def impl2 : RuntimeLibcallImpl<FUNC2>;
+def impl3 : RuntimeLibcallImpl<FUNC3>;
+def impl4 : RuntimeLibcallImpl<FUNC4>;
+
+def IsAArch64 : LibcallPredicate<[{TT.isAArch64()}]>;
+def IsOSDarwin : LibcallPredicate<[{TT.isOSDarwin()}]>;
+def IsGNUEnv : LibcallPredicate<[{TT.isGNUEnvironment()}]>;
+def IsWindowsArm64EC : LibcallPredicate<[{TT.isWindowsArm64EC()}]>;
+
+def isAArch64_ExceptArm64EC
+ : RuntimeLibcallAvailabilityDag<(all_of IsAArch64, (not IsWindowsArm64EC))>;
+
+def isDarwinOrGNU : RuntimeLibcallAvailabilityDag<(any_of IsOSDarwin, IsGNUEnv)>;
+
+def isNotDarwin : RuntimeLibcallAvailabilityDag<(not IsOSDarwin)>;
+
+// Nested any_of inside all_of, plus a negated leaf.
+def isAArch64DarwinOrGNU : RuntimeLibcallAvailabilityDag<
+ (all_of IsAArch64, (any_of IsOSDarwin, IsGNUEnv), (not IsWindowsArm64EC))>;
+
+// Not wrapping a nested any_of.
+def isAArch64OrNotDarwinGNU : RuntimeLibcallAvailabilityDag<
+ (any_of IsAArch64, (not (any_of IsOSDarwin, IsGNUEnv)))>;
+
+def isTargetArch : RuntimeLibcallAvailability<[{isTargetArch()}]>;
+
+def TheSystemLibrary : SystemRuntimeLibrary<isTargetArch,
+ (add LibcallImpls<(add impl0), isAArch64_ExceptArm64EC>,
+ LibcallImpls<(add impl1), isDarwinOrGNU>,
+ LibcallImpls<(add impl2), isNotDarwin>,
+ LibcallImpls<(add impl3), isAArch64DarwinOrGNU>,
+ LibcallImpls<(add impl4), isAArch64OrNotDarwinGNU>)
+>;
+
+// Predicate groups emit sorted by predicate def name
+
+// CHECK: if (TT.isAArch64() && (TT.isOSDarwin() || TT.isGNUEnvironment()) && !TT.isWindowsArm64EC()) {
+// CHECK: RTLIB::impl_impl3
+// CHECK: if (TT.isAArch64() || !(TT.isOSDarwin() || TT.isGNUEnvironment())) {
+// CHECK: RTLIB::impl_impl4
+// CHECK: if (TT.isAArch64() && !TT.isWindowsArm64EC()) {
+// CHECK: RTLIB::impl_impl0
+// CHECK: if (TT.isOSDarwin() || TT.isGNUEnvironment()) {
+// CHECK: RTLIB::impl_impl1
+// CHECK: if (!TT.isOSDarwin()) {
+// CHECK: RTLIB::impl_impl2
diff --git a/llvm/utils/TableGen/Basic/CMakeLists.txt b/llvm/utils/TableGen/Basic/CMakeLists.txt
index 01ab8a0ef2250..f21437026b85c 100644
--- a/llvm/utils/TableGen/Basic/CMakeLists.txt
+++ b/llvm/utils/TableGen/Basic/CMakeLists.txt
@@ -14,6 +14,7 @@ add_llvm_library(LLVMTableGenBasic OBJECT EXCLUDE_FROM_ALL DISABLE_LLVM_LINK_LLV
CodeGenIntrinsics.cpp
DirectiveEmitter.cpp
IntrinsicEmitter.cpp
+ PredicateExpanderDag.cpp
RISCVTargetDefEmitter.cpp
RuntimeLibcallsEmitter.cpp
RuntimeLibcalls.cpp
diff --git a/llvm/utils/TableGen/Basic/PredicateExpanderDag.cpp b/llvm/utils/TableGen/Basic/PredicateExpanderDag.cpp
new file mode 100644
index 0000000000000..e9f5c7a468458
--- /dev/null
+++ b/llvm/utils/TableGen/Basic/PredicateExpanderDag.cpp
@@ -0,0 +1,56 @@
+//===- PredicateExpanderDag.cpp - Composable predicate dag lowering -------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "PredicateExpanderDag.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TableGen/Error.h"
+#include "llvm/TableGen/Record.h"
+
+using namespace llvm;
+
+bool llvm::emitPredicateDag(
+ const Record *Owner, const Init &Val, bool ParenIfBinOp, raw_ostream &OS,
+ function_ref<bool(const Init &, raw_ostream &)> EmitLeaf) {
+ if (const auto *D = dyn_cast<DagInit>(&Val)) {
+ const auto *Op = dyn_cast<DefInit>(D->getOperator());
+ if (!Op)
+ PrintFatalError(Owner, "invalid predicate dag operator");
+ StringRef OpName = Op->getDef()->getName();
+ if (OpName == "not") {
+ if (D->getNumArgs() != 1)
+ PrintFatalError(Owner, "'not' takes exactly one operand");
+ OS << '!';
+ return emitPredicateDag(Owner, *D->getArg(0), /*ParenIfBinOp=*/true, OS,
+ EmitLeaf);
+ }
+ if (OpName == "any_of" || OpName == "all_of") {
+ if (D->getNumArgs() == 0)
+ PrintFatalError(Owner,
+ "'" + OpName + "' requires at least one operand");
+ bool Paren = D->getNumArgs() > 1 && std::exchange(ParenIfBinOp, true);
+ if (Paren)
+ OS << '(';
+ ListSeparator LS(OpName == "any_of" ? " || " : " && ");
+ for (const Init *Arg : D->getArgs()) {
+ OS << LS;
+ if (emitPredicateDag(Owner, *Arg, ParenIfBinOp, OS, EmitLeaf))
+ return true;
+ }
+ if (Paren)
+ OS << ')';
+ return false;
+ }
+ PrintFatalError(Owner, "unknown predicate dag operator '" + OpName +
+ "'; expected all_of/any_of/not");
+ }
+
+ // Any non-dag operand (or the base leaf) is emitted by the caller, which
+ // diagnoses leaf-level errors specific to its consumer.
+ return EmitLeaf(Val, OS);
+}
diff --git a/llvm/utils/TableGen/Basic/PredicateExpanderDag.h b/llvm/utils/TableGen/Basic/PredicateExpanderDag.h
new file mode 100644
index 0000000000000..10ee4cc6cfde8
--- /dev/null
+++ b/llvm/utils/TableGen/Basic/PredicateExpanderDag.h
@@ -0,0 +1,41 @@
+//===- PredicateExpanderDag.h - Composable predicate dag lowering ---------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Shared walker that lowers a composable predicate dag of the form
+//
+// (all_of / any_of / not <leaf> ...)
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_UTILS_TABLEGEN_BASIC_PREDICATEEXPANDERDAG_H
+#define LLVM_UTILS_TABLEGEN_BASIC_PREDICATEEXPANDERDAG_H
+
+#include "llvm/ADT/STLFunctionalExtras.h"
+
+namespace llvm {
+
+class Init;
+class Record;
+class raw_ostream;
+
+/// Walk a composable predicate dag rooted at \p Val and emit the combined
+/// boolean expression to \p OS.
+///
+/// The structural operators handled are the dags `(not X)` and `(all_of ...)` /
+/// `(any_of ...)`. Any non-dag operand (and the base leaf) is delegated to \p
+/// EmitLeaf, which emits the leaf test and returns true on error.
+///
+/// If \p ParenIfBinOp is true, a surrounding pair of parentheses is emitted
+/// when \p Val lowers to a binary (`&&` / `||`) expression.
+bool emitPredicateDag(const Record *Owner, const Init &Val, bool ParenIfBinOp,
+ raw_ostream &OS,
+ function_ref<bool(const Init &, raw_ostream &)> EmitLeaf);
+
+} // namespace llvm
+
+#endif // LLVM_UTILS_TABLEGEN_BASIC_PREDICATEEXPANDERDAG_H
diff --git a/llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp b/llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp
index 1e609a2a8880b..9c4a9107a6849 100644
--- a/llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp
+++ b/llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp
@@ -7,10 +7,32 @@
//===----------------------------------------------------------------------===//
#include "RuntimeLibcalls.h"
+#include "PredicateExpanderDag.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/Error.h"
using namespace llvm;
+std::string AvailabilityPredicate::lowerCondDag(const Record *Owner,
+ const Init *Val,
+ bool ParenIfBinOp) {
+ // Leaf: a LibcallPredicate whose Cond is a C++ boolean over `TT`.
+ auto EmitLeaf = [&](const Init &Leaf, raw_ostream &OS) -> bool {
+ const auto *DI = dyn_cast<DefInit>(&Leaf);
+ if (!DI || !DI->getDef()->isSubClassOf("LibcallPredicate"))
+ PrintFatalError(Owner, "predicate dag leaf '" + Leaf.getAsString() +
+ "' is not a LibcallPredicate");
+ OS << DI->getDef()->getValueAsString("Cond");
+ return false;
+ };
+
+ std::string Result;
+ raw_string_ostream OS(Result);
+ emitPredicateDag(Owner, *Val, ParenIfBinOp, OS, EmitLeaf);
+ return Result;
+}
+
RuntimeLibcalls::RuntimeLibcalls(const RecordKeeper &Records) {
ArrayRef<const Record *> AllRuntimeLibcalls =
Records.getAllDerivedDefinitions("RuntimeLibcall");
diff --git a/llvm/utils/TableGen/Basic/RuntimeLibcalls.h b/llvm/utils/TableGen/Basic/RuntimeLibcalls.h
index 6c9897602b2fa..771292e47ee79 100644
--- a/llvm/utils/TableGen/Basic/RuntimeLibcalls.h
+++ b/llvm/utils/TableGen/Basic/RuntimeLibcalls.h
@@ -18,12 +18,19 @@ namespace llvm {
class AvailabilityPredicate {
const Record *TheDef;
- StringRef PredicateString;
+ std::string PredicateString;
public:
AvailabilityPredicate(const Record *Def) : TheDef(Def) {
- if (TheDef)
- PredicateString = TheDef->getValueAsString("Cond");
+ if (!TheDef)
+ return;
+ if (const RecordVal *RV = TheDef->getValue("CondDag")) {
+ if (const auto *Dag = dyn_cast_or_null<DagInit>(RV->getValue())) {
+ PredicateString = lowerCondDag(TheDef, Dag);
+ return;
+ }
+ }
+ PredicateString = TheDef->getValueAsString("Cond").str();
}
const Record *getDef() const { return TheDef; }
@@ -40,6 +47,12 @@ class AvailabilityPredicate {
if (TheDef)
OS << '_' << TheDef->getName();
}
+
+private:
+ // Lower a (all_of/any_of/not <LibcallPredicate>...) dag to a C++ boolean
+ // expression.
+ static std::string lowerCondDag(const Record *Owner, const Init *Val,
+ bool ParenIfBinOp = false);
};
class RuntimeLibcalls;
diff --git a/llvm/utils/TableGen/Common/SubtargetFeatureInfo.cpp b/llvm/utils/TableGen/Common/SubtargetFeatureInfo.cpp
index a426bf4ef4b77..f0afbd1b6ddd1 100644
--- a/llvm/utils/TableGen/Common/SubtargetFeatureInfo.cpp
+++ b/llvm/utils/TableGen/Common/SubtargetFeatureInfo.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "SubtargetFeatureInfo.h"
+#include "Basic/PredicateExpanderDag.h"
#include "Types.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/TableGen/Error.h"
@@ -127,40 +128,27 @@ void SubtargetFeatureInfo::emitComputeAvailableFeatures(
OS << "}\n\n";
}
+// Emit a feature-bit test leaf: FB[<TargetName>::<feature>]. Returns true on
+// error (the leaf is not a SubtargetFeature).
+static bool emitFeatureBitLeaf(StringRef TargetName, const Init &Val,
+ raw_ostream &OS) {
+ const auto *D = dyn_cast<DefInit>(&Val);
+ if (!D || !D->getDef()->isSubClassOf("SubtargetFeature"))
+ return true;
+ OS << "FB[" << TargetName << "::" << D->getAsString() << ']';
+ return false;
+}
+
// If ParenIfBinOp is true, print a surrounding () if Val uses && or ||.
-static bool emitFeaturesAux(StringRef TargetName, const Init &Val,
- bool ParenIfBinOp, raw_ostream &OS) {
- if (auto *D = dyn_cast<DefInit>(&Val)) {
- if (!D->getDef()->isSubClassOf("SubtargetFeature"))
- return true;
- OS << "FB[" << TargetName << "::" << D->getAsString() << "]";
- return false;
- }
- if (auto *D = dyn_cast<DagInit>(&Val)) {
- auto *Op = dyn_cast<DefInit>(D->getOperator());
- if (!Op)
- return true;
- StringRef OpName = Op->getDef()->getName();
- if (OpName == "not" && D->getNumArgs() == 1) {
- OS << '!';
- return emitFeaturesAux(TargetName, *D->getArg(0), true, OS);
- }
- if ((OpName == "any_of" || OpName == "all_of") && D->getNumArgs() > 0) {
- bool Paren = D->getNumArgs() > 1 && std::exchange(ParenIfBinOp, true);
- if (Paren)
- OS << '(';
- ListSeparator LS(OpName == "any_of" ? " || " : " && ");
- for (auto *Arg : D->getArgs()) {
- OS << LS;
- if (emitFeaturesAux(TargetName, *Arg, ParenIfBinOp, OS))
- return true;
- }
- if (Paren)
- OS << ')';
- return false;
- }
- }
- return true;
+// Structural errors in the dag are diagnosed against \p Owner by the walker;
+// a leaf that is not a SubtargetFeature is reported here via the true return.
+static bool emitFeaturesAux(const Record *Owner, StringRef TargetName,
+ const Init &Val, bool ParenIfBinOp,
+ raw_ostream &OS) {
+ return emitPredicateDag(Owner, Val, ParenIfBinOp, OS,
+ [&](const Init &Leaf, raw_ostream &OS) {
+ return emitFeatureBitLeaf(TargetName, Leaf, OS);
+ });
}
void SubtargetFeatureInfo::emitPredicateCheck(
@@ -190,7 +178,7 @@ void SubtargetFeatureInfo::emitMCPredicateCheck(
bool ParenIfBinOp = range_size(MCPredicates) > 1;
for (const Record *R : MCPredicates) {
OS << LS;
- if (emitFeaturesAux(TargetName, *R->getValueAsDag("AssemblerCondDag"),
+ if (emitFeaturesAux(R, TargetName, *R->getValueAsDag("AssemblerCondDag"),
ParenIfBinOp, OS))
PrintFatalError(R, "Invalid AssemblerCondDag!");
}
@@ -211,7 +199,8 @@ void SubtargetFeatureInfo::emitComputeAssemblerAvailableFeatures(
const Record *Def = SFI.TheDef;
OS << " if (";
- if (emitFeaturesAux(TargetName, *Def->getValueAsDag("AssemblerCondDag"),
+ if (emitFeaturesAux(Def, TargetName,
+ *Def->getValueAsDag("AssemblerCondDag"),
/*ParenIfBinOp=*/false, OS))
PrintFatalError(Def, "Invalid AssemblerCondDag!");
More information about the llvm-branch-commits
mailing list