[llvm] [Target] Add target-independent TargetVerifier dispatcher (PR #205703)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 27 14:12:25 PDT 2026


https://github.com/jofrn updated https://github.com/llvm/llvm-project/pull/205703

>From b33de648fc13d96d32d8e51929fd683e90ad63f7 Mon Sep 17 00:00:00 2001
From: jofrn <jo7frn1 at gmail.com>
Date: Sat, 20 Jun 2026 06:17:22 -0700
Subject: [PATCH] [Target] Add target-independent TargetVerifier dispatcher

Introduce a target-dependent IR verification framework that can be run
from target-independent locations.

TargetVerify is a base class each backend subclasses to check a function
for constructs that are invalid for a particular target. Backends
register a factory keyed by Triple::ArchType via registerTargetVerify(),
typically from their LLVMInitialize<Target>Target().

TargetVerifierPass (registered as "target-verifier") is the dispatcher:
it reads the module triple and, if a verifier is registered for that
architecture, runs the generic IR verifier followed by the target's
TargetVerify. It is a no-op for targets that have not registered a
verifier, so it is safe to schedule from generic, target-independent
pipelines (e.g. `opt -passes=target-verifier`).
---
 llvm/include/llvm/Target/TargetVerifier.h | 134 ++++++++++++++++++++++
 llvm/lib/Passes/PassBuilder.cpp           |   1 +
 llvm/lib/Passes/PassRegistry.def          |   1 +
 llvm/lib/Target/CMakeLists.txt            |   1 +
 llvm/lib/Target/TargetVerifier.cpp        |  82 +++++++++++++
 5 files changed, 219 insertions(+)
 create mode 100644 llvm/include/llvm/Target/TargetVerifier.h
 create mode 100644 llvm/lib/Target/TargetVerifier.cpp

diff --git a/llvm/include/llvm/Target/TargetVerifier.h b/llvm/include/llvm/Target/TargetVerifier.h
new file mode 100644
index 0000000000000..74230c7359248
--- /dev/null
+++ b/llvm/include/llvm/Target/TargetVerifier.h
@@ -0,0 +1,134 @@
+//===-- llvm/Target/TargetVerifier.h - Target IR Verifier -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a target-dependent verification framework that can be
+// instantiated and run from target-independent locations.
+//
+// In contrast to the generic Verifier or Lint, a TargetVerify looks for
+// constructs that are invalid for a *particular* target machine. Each backend
+// provides its own TargetVerify and registers a factory for it (keyed by
+// Triple::ArchType) via registerTargetVerify(), typically from its
+// LLVMInitialize<Target>Target().
+//
+// TargetVerifierPass is the target-independent dispatcher: it reads the
+// module's target triple and runs whichever TargetVerify (if any) was
+// registered for that architecture. Because dispatch happens at run time by
+// triple, the pass can be scheduled from generic, target-independent pipelines
+// (e.g. `opt -passes=target-verifier`); it is simply a no-op for targets that
+// have not registered a verifier.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TARGET_TARGETVERIFIER_H
+#define LLVM_TARGET_TARGETVERIFIER_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/IR/Value.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TargetParser/Triple.h"
+
+#include <memory>
+#include <string>
+
+namespace llvm {
+
+class Function;
+
+/// Base class for a single target's IR verification logic.
+///
+/// A backend subclasses this and implements run() to check a function for
+/// constructs that are invalid for that target. Failed checks are recorded in
+/// \p Messages (and clear \p IsValid) via checkFailed().
+class TargetVerify {
+protected:
+  void writeValues(ArrayRef<const Value *> Vs) {
+    for (const Value *V : Vs) {
+      if (!V)
+        continue;
+      if (isa<Instruction>(V))
+        MessagesStr << *V << '\n';
+      else {
+        V->printAsOperand(MessagesStr, true, Mod);
+        MessagesStr << '\n';
+      }
+    }
+  }
+
+  /// A check failed, so print the condition's message.
+  ///
+  /// This is a convenient place to put a breakpoint to see why something is
+  /// considered invalid for the target.
+  void checkFailed(const Twine &Message) {
+    IsValid = false;
+    MessagesStr << Message << '\n';
+  }
+
+  /// A check failed (with values to print).
+  template <typename T1, typename... Ts>
+  void checkFailed(const Twine &Message, const T1 &V1, const Ts &...Vs) {
+    checkFailed(Message);
+    writeValues({V1, Vs...});
+  }
+
+public:
+  Module *Mod;
+  Triple TT;
+
+  std::string Messages;
+  raw_string_ostream MessagesStr;
+
+  /// Whether every function verified so far is valid for the target.
+  bool IsValid = true;
+
+  TargetVerify(Module *Mod)
+      : Mod(Mod), TT(Mod->getTargetTriple()), MessagesStr(Messages) {}
+  virtual ~TargetVerify() = default;
+
+  /// Run this target's checks on \p F. Returns true if \p F is valid.
+  virtual bool run(Function &F) = 0;
+};
+
+/// Factory that creates a target's TargetVerify for a given module.
+using TargetVerifyFactory = TargetVerify *(*)(Module &);
+
+/// Register the TargetVerify factory for \p Arch. Backends call this from their
+/// LLVMInitialize<Target>Target() so that the target-independent
+/// TargetVerifierPass can dispatch to them by triple. Registering twice for the
+/// same arch overwrites the previous factory.
+LLVM_ABI void registerTargetVerify(Triple::ArchType Arch,
+                                   TargetVerifyFactory Factory);
+
+/// \returns the factory registered for \p Arch, or nullptr if none.
+LLVM_ABI const TargetVerifyFactory *getTargetVerify(Triple::ArchType Arch);
+
+/// Target-independent dispatcher pass.
+///
+/// Reads the module triple and, if a TargetVerify is registered for that
+/// triple's architecture, runs it over every defined function in the module.
+/// Safe to schedule from target-independent pipelines: it is a no-op when no
+/// verifier is registered for the target.
+class TargetVerifierPass : public PassInfoMixin<TargetVerifierPass> {
+  bool FatalErrors;
+
+public:
+  explicit TargetVerifierPass(bool FatalErrors = false)
+      : FatalErrors(FatalErrors) {}
+
+  LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+
+  static bool isRequired() { return true; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_TARGET_TARGETVERIFIER_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index e88b81a13fae9..92cbc8efe5a82 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -206,6 +206,7 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Regex.h"
 #include "llvm/Target/TargetMachine.h"
+#include "llvm/Target/TargetVerifier.h"
 #include "llvm/Transforms/AggressiveInstCombine/AggressiveInstCombine.h"
 #include "llvm/Transforms/CFGuard.h"
 #include "llvm/Transforms/Coroutines/CoroAnnotationElide.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 8f70c7cefa408..ef8487bbc292d 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -178,6 +178,7 @@ MODULE_PASS("strip-nonlinetable-debuginfo", StripNonLineTableDebugInfoPass())
 MODULE_PASS("strip-dead-cg-profile", StripDeadCGProfilePass())
 MODULE_PASS("trigger-crash-module", TriggerCrashModulePass())
 MODULE_PASS("trigger-verifier-error", TriggerVerifierErrorPass())
+MODULE_PASS("target-verifier", TargetVerifierPass())
 MODULE_PASS("tsan-module", ModuleThreadSanitizerPass())
 MODULE_PASS("tysan", TypeSanitizerPass())
 MODULE_PASS("verify", VerifierPass())
diff --git a/llvm/lib/Target/CMakeLists.txt b/llvm/lib/Target/CMakeLists.txt
index bcc13f942bf96..2b3b164e7119c 100644
--- a/llvm/lib/Target/CMakeLists.txt
+++ b/llvm/lib/Target/CMakeLists.txt
@@ -8,6 +8,7 @@ add_llvm_component_library(LLVMTarget
   TargetLoweringObjectFile.cpp
   TargetMachine.cpp
   TargetMachineC.cpp
+  TargetVerifier.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${LLVM_MAIN_INCLUDE_DIR}/llvm/Target
diff --git a/llvm/lib/Target/TargetVerifier.cpp b/llvm/lib/Target/TargetVerifier.cpp
new file mode 100644
index 0000000000000..6fe35a59e0c79
--- /dev/null
+++ b/llvm/lib/Target/TargetVerifier.cpp
@@ -0,0 +1,82 @@
+//===-- TargetVerifier.cpp - Target-dependent IR Verifier ------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the target-independent dispatcher for target-dependent
+// IR verification. See llvm/Target/TargetVerifier.h for the design.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Target/TargetVerifier.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Verifier.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+// The registry maps Triple::ArchType (stored as unsigned, since there is no
+// DenseMapInfo for the enum) to the factory that builds that target's
+// TargetVerify. It is populated by backends from LLVMInitialize<T>Target().
+static DenseMap<unsigned, TargetVerifyFactory> &getTargetVerifyRegistry() {
+  static DenseMap<unsigned, TargetVerifyFactory> Registry;
+  return Registry;
+}
+
+void llvm::registerTargetVerify(Triple::ArchType Arch,
+                                TargetVerifyFactory Factory) {
+  getTargetVerifyRegistry()[static_cast<unsigned>(Arch)] = Factory;
+}
+
+const TargetVerifyFactory *llvm::getTargetVerify(Triple::ArchType Arch) {
+  DenseMap<unsigned, TargetVerifyFactory> &Registry = getTargetVerifyRegistry();
+  auto It = Registry.find(static_cast<unsigned>(Arch));
+  if (It == Registry.end())
+    return nullptr;
+  return &It->second;
+}
+
+PreservedAnalyses TargetVerifierPass::run(Module &M, ModuleAnalysisManager &AM) {
+  // Use the module triple to decide which target-dependent verification to run.
+  // If the target did not register a verifier, there is nothing target-specific
+  // to do, so skip everything (including the generic verifier) and stay a no-op.
+  const TargetVerifyFactory *Factory =
+      getTargetVerify(M.getTargetTriple().getArch());
+  if (!Factory)
+    return PreservedAnalyses::all();
+
+  bool Broken = false;
+
+  // (1) Run the generic IR verifier. After the VerifierAMDGPU split, the
+  // generic verifier dispatches to each target's IR-level checks (e.g. the
+  // verifyAMDGPU* routines in VerifierAMDGPU.cpp for AMDGPU modules), so this
+  // is how the target's "<Target>Verifier" runs.
+  Broken |= verifyModule(M, &errs());
+
+  // (2) Run the target's subtarget/feature-dependent verifier (TargetVerify).
+  if (std::unique_ptr<TargetVerify> TV{(*Factory)(M)}) {
+    for (Function &F : M) {
+      if (F.isDeclaration())
+        continue;
+      if (!TV->run(F))
+        Broken = true;
+    }
+    TV->MessagesStr.flush();
+    if (!TV->Messages.empty())
+      errs() << TV->Messages;
+  }
+
+  if (Broken) {
+    if (FatalErrors)
+      report_fatal_error("broken module found, compilation aborted!");
+    errs() << "broken module found\n";
+    return PreservedAnalyses::none();
+  }
+  return PreservedAnalyses::all();
+}



More information about the llvm-commits mailing list