[llvm-branch-commits] [llvm] RuntimeLibcalls: Reuse AssemblerPredicate's operators for libcalls (PR #210651)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Jul 19 23:35:40 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-tablegen

Author: Matt Arsenault (arsenm)

<details>
<summary>Changes</summary>

Allow specifying RuntimeLibcall's availability in terms of individual
triple properties composed with logical operators.

Co-authored-by: Claude (Claude-Opus-4.8) <noreply@<!-- -->anthropic.com>

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


9 Files Affected:

- (modified) llvm/include/llvm/IR/RuntimeLibcallsImpl.td (+29-2) 
- (added) llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag-errors.td (+42) 
- (added) llvm/test/TableGen/RuntimeLibcallEmitter-predicate-dag.td (+60) 
- (modified) llvm/utils/TableGen/Basic/CMakeLists.txt (+1) 
- (added) llvm/utils/TableGen/Basic/PredicateExpanderDag.cpp (+56) 
- (added) llvm/utils/TableGen/Basic/PredicateExpanderDag.h (+41) 
- (modified) llvm/utils/TableGen/Basic/RuntimeLibcalls.cpp (+22) 
- (modified) llvm/utils/TableGen/Basic/RuntimeLibcalls.h (+16-3) 
- (modified) llvm/utils/TableGen/Common/SubtargetFeatureInfo.cpp (+24-35) 


``````````diff
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!");
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/210651


More information about the llvm-branch-commits mailing list