diff --git a/llvm/include/llvm/Target/TargetVerifier.h b/llvm/include/llvm/Target/TargetVerifier.h new file mode 100644 index 0000000000000..eff846694ac10 --- /dev/null +++ b/llvm/include/llvm/Target/TargetVerifier.h @@ -0,0 +1,84 @@ +//===-- llvm/Target/TargetVerifier.h - LLVM IR Target 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 target verifier interfaces that can be used for some +// validation of input to the system, and for checking that transformations +// haven't done something bad. In contrast to the Verifier or Lint, the +// TargetVerifier looks for constructions invalid to a particular target +// machine. +// +// To see what specifically is checked, look at an individual backend's +// TargetVerifier. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TARGET_VERIFIER_H +#define LLVM_TARGET_VERIFIER_H + +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/TargetParser/Triple.h" + +namespace llvm { + +class Function; + +class TargetVerifierPass : public PassInfoMixin { +public: + virtual PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) = 0; +}; + +class TargetVerify { +protected: + void writeValues(ArrayRef Vs) { + for (const Value *V : Vs) { + if (!V) + continue; + if (isa(V)) { + MessagesStr << *V << '\n'; + } else { + V->printAsOperand(MessagesStr, true, Mod); + MessagesStr << '\n'; + } + } + } + + /// A check failed, so printout out the condition and the message. + /// + /// This provides a nice place to put a breakpoint if you want to see why + /// something is not correct. + void checkFailed(const Twine &Message) { MessagesStr << Message << '\n'; } + + /// A check failed (with values to print). + /// + /// This calls the Message-only version so that the above is easier to set + /// a breakpoint on. + template + 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; + + bool IsValid = true; + + TargetVerify(Module *Mod) + : Mod(Mod), TT(Mod->getTargetTriple()), MessagesStr(Messages) {} + + virtual bool run(Function &F) = 0; +}; + +} // namespace llvm + +#endif // LLVM_TARGET_VERIFIER_H diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.h b/llvm/lib/Target/AMDGPU/AMDGPU.h index 4ff761ec19b3c..c746b66731cd5 100644 --- a/llvm/lib/Target/AMDGPU/AMDGPU.h +++ b/llvm/lib/Target/AMDGPU/AMDGPU.h @@ -15,6 +15,7 @@ #include "llvm/Pass.h" #include "llvm/Support/AMDGPUAddrSpace.h" #include "llvm/Support/CodeGen.h" +#include "llvm/Target/TargetVerifier.h" namespace llvm { @@ -530,6 +531,12 @@ extern char &GCNRewritePartialRegUsesID; void initializeAMDGPUWaitSGPRHazardsLegacyPass(PassRegistry &); extern char &AMDGPUWaitSGPRHazardsLegacyID; +FunctionPass *createAMDGPUTargetVerifierLegacyPass(bool FatalErrors); +void initializeAMDGPUTargetVerifierLegacyPassPass(PassRegistry &); +struct AMDGPUTargetVerifierPass : public TargetVerifierPass { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) override; +}; + namespace AMDGPU { enum TargetIndex { TI_CONSTDATA_START, diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp index 90e3489ced923..89e045a92f3ea 100644 --- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp +++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp @@ -481,6 +481,9 @@ static cl::opt HasClosedWorldAssumption( cl::desc("Whether has closed-world assumption at link time"), cl::init(false), cl::Hidden); +static cl::opt VerifyTarget("amdgpu-verify-tgt", + cl::desc("Enable the target verifier")); + extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeAMDGPUTarget() { // Register the target RegisterTargetMachine X(getTheR600Target()); @@ -1376,6 +1379,8 @@ bool AMDGPUPassConfig::addGCPasses() { //===----------------------------------------------------------------------===// bool GCNPassConfig::addPreISel() { + if (VerifyTarget) + addPass(createAMDGPUTargetVerifierLegacyPass(false)); AMDGPUPassConfig::addPreISel(); if (TM->getOptLevel() > CodeGenOptLevel::None) @@ -1975,6 +1980,9 @@ AMDGPUCodeGenPassBuilder::AMDGPUCodeGenPassBuilder( } void AMDGPUCodeGenPassBuilder::addIRPasses(AddIRPass &addPass) const { + if (VerifyTarget) + addPass(AMDGPUTargetVerifierPass()); + if (RemoveIncompatibleFunctions && TM.getTargetTriple().isAMDGCN()) addPass(AMDGPURemoveIncompatibleFunctionsPass(TM)); diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetVerifier.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetVerifier.cpp new file mode 100644 index 0000000000000..3246b0ac0c624 --- /dev/null +++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetVerifier.cpp @@ -0,0 +1,170 @@ +//===-- AMDGPUTargetVerifier.cpp - AMDGPU -----------------------*- 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 target verifier interfaces that can be used for some +// validation of input to the system, and for checking that transformations +// haven't done something bad. In contrast to the Verifier or Lint, the +// TargetVerifier looks for constructions invalid to a particular target +// machine. +// +// To see what specifically is checked, look at an individual backend's +// TargetVerifier. +// +//===----------------------------------------------------------------------===// + +#include "AMDGPU.h" + +#include "llvm/IR/Function.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/IntrinsicsAMDGPU.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/Debug.h" + +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +// Check - We know that cond should be true, if not print an error message. +#define Check(C, ...) \ + do { \ + if (!(C)) { \ + TargetVerify::checkFailed(__VA_ARGS__); \ + } \ + } while (false) + +namespace llvm { + +class AMDGPUTargetVerify : public TargetVerify { +public: + AMDGPUTargetVerify(Module *Mod) : TargetVerify(Mod) {} + bool run(Function &F) override; +}; + +static bool isShader(CallingConv::ID CC) { + switch (CC) { + case CallingConv::AMDGPU_VS: + case CallingConv::AMDGPU_LS: + case CallingConv::AMDGPU_HS: + case CallingConv::AMDGPU_ES: + case CallingConv::AMDGPU_GS: + case CallingConv::AMDGPU_PS: + case CallingConv::AMDGPU_CS_Chain: + case CallingConv::AMDGPU_CS_ChainPreserve: + case CallingConv::AMDGPU_CS: + return true; + default: + return false; + } +} + +bool AMDGPUTargetVerify::run(Function &F) { + // Ensure shader calling convention returns void + if (isShader(F.getCallingConv())) + Check(F.getReturnType() == Type::getVoidTy(F.getContext()), + "Shaders must return void"); + + for (auto &BB : F) { + + for (auto &I : BB) { + + if (auto *CI = dyn_cast(&I)) { + // Ensure no kernel to kernel calls. + CallingConv::ID CalleeCC = CI->getCallingConv(); + if (CalleeCC == CallingConv::AMDGPU_KERNEL) { + CallingConv::ID CallerCC = + CI->getParent()->getParent()->getCallingConv(); + Check(CallerCC != CallingConv::AMDGPU_KERNEL, + "A kernel may not call a kernel", CI->getParent()->getParent()); + } + + // Ensure chain intrinsics are followed by unreachables. + if (CI->getIntrinsicID() == Intrinsic::amdgcn_cs_chain) + Check(isa_and_present(CI->getNextNode()), + "llvm.amdgcn.cs.chain must be followed by unreachable", CI); + } + } + } + + dbgs() << MessagesStr.str(); + if (!MessagesStr.str().empty()) { + IsValid = false; + return false; + } + return true; +} + +PreservedAnalyses AMDGPUTargetVerifierPass::run(Function &F, + FunctionAnalysisManager &AM) { + auto *Mod = F.getParent(); + + AMDGPUTargetVerify TV(Mod); + TV.run(F); + + dbgs() << TV.MessagesStr.str(); + if (!TV.MessagesStr.str().empty()) { + TV.IsValid = false; + return PreservedAnalyses::none(); + } + return PreservedAnalyses::all(); +} + +struct AMDGPUTargetVerifierLegacyPass : public FunctionPass { + static char ID; + + std::unique_ptr TV; + bool FatalErrors = false; + + AMDGPUTargetVerifierLegacyPass(bool FatalErrors) + : FunctionPass(ID), FatalErrors(FatalErrors) { + initializeAMDGPUTargetVerifierLegacyPassPass( + *PassRegistry::getPassRegistry()); + } + + bool doInitialization(Module &M) override { + TV = std::make_unique(&M); + return false; + } + + bool runOnFunction(Function &F) override { + if (!TV->run(F)) { + errs() << "in function " << F.getName() << '\n'; + if (FatalErrors) + report_fatal_error("broken function found, compilation aborted!"); + else + errs() << "broken function found, compilation aborted!\n"; + } + return false; + } + + bool doFinalization(Module &M) override { + bool IsValid = true; + for (Function &F : M) + if (F.isDeclaration()) + IsValid &= TV->run(F); + + if (!IsValid) { + if (FatalErrors) + report_fatal_error("broken module found, compilation aborted!"); + else + errs() << "broken module found, compilation aborted!\n"; + } + return false; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } +}; +char AMDGPUTargetVerifierLegacyPass::ID = 0; +FunctionPass *createAMDGPUTargetVerifierLegacyPass(bool FatalErrors) { + return new AMDGPUTargetVerifierLegacyPass(FatalErrors); +} +} // namespace llvm +INITIALIZE_PASS(AMDGPUTargetVerifierLegacyPass, "amdgpu-tgtverifier", + "AMDGPU Target Verifier", false, false) diff --git a/llvm/lib/Target/AMDGPU/CMakeLists.txt b/llvm/lib/Target/AMDGPU/CMakeLists.txt index 09a3096602fc3..bcfea0bf8ac94 100644 --- a/llvm/lib/Target/AMDGPU/CMakeLists.txt +++ b/llvm/lib/Target/AMDGPU/CMakeLists.txt @@ -110,6 +110,7 @@ add_llvm_target(AMDGPUCodeGen AMDGPUTargetMachine.cpp AMDGPUTargetObjectFile.cpp AMDGPUTargetTransformInfo.cpp + AMDGPUTargetVerifier.cpp AMDGPUWaitSGPRHazards.cpp AMDGPUUnifyDivergentExitNodes.cpp AMDGPUUnifyMetadata.cpp diff --git a/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-fail.ll b/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-fail.ll new file mode 100644 index 0000000000000..819e66bb46b0a --- /dev/null +++ b/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-fail.ll @@ -0,0 +1,6 @@ +; RUN: llc -mtriple=amdgcn -mcpu=gfx900 -amdgpu-verify-tgt -o - < %s 2>&1 | FileCheck %s + +define amdgpu_cs i32 @nonvoid_shader() { +; CHECK: Shaders must return void + ret i32 0 +} diff --git a/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-pass.ll b/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-pass.ll new file mode 100644 index 0000000000000..08854df01acb2 --- /dev/null +++ b/llvm/test/CodeGen/AMDGPU/tgt-verify-llc-pass.ll @@ -0,0 +1,6 @@ +; RUN: llc -mtriple=amdgcn -mcpu=gfx900 -amdgpu-verify-tgt %s -o - 2>&1 | FileCheck %s --allow-empty + +define amdgpu_cs void @void_shader() { +; CHECK-NOT: Shaders must return void + ret void +}