[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