[llvm] [SPIR-V] Expose an API call to initialize SPIRV target and translate input LLVM IR module to SPIR-V (PR #107216)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 4 07:03:06 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-spir-v
Author: Vyacheslav Levytskyy (VyacheslavLevytskyy)
<details>
<summary>Changes</summary>
The goal of this PR is to facilitate integration of SPIRV Backend into misc 3rd party tools and libraries by means of exposing an API call that translate LLVM module to SPIR-V and write results into a string as binary SPIR-V output, providing diagnostics on fail and means of configuring translation in a style of command line options.
An example of a use case may be Khronos Translator that provides bidirectional translation LLVM IR <=> SPIR-V, where LLVM IR => SPIR-V step may be substituted by the call to SPIR-V Backend API, implemented by this PR.
---
Full diff: https://github.com/llvm/llvm-project/pull/107216.diff
5 Files Affected:
- (modified) llvm/lib/Target/SPIRV/CMakeLists.txt (+1)
- (added) llvm/lib/Target/SPIRV/SPIRVAPI.cpp (+167)
- (added) llvm/lib/Target/SPIRV/SPIRVAPI.h (+23)
- (modified) llvm/unittests/Target/SPIRV/CMakeLists.txt (+2)
- (added) llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp (+152)
``````````diff
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 5f8aea5fc8d84d..df7869b1552caa 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -14,6 +14,7 @@ tablegen(LLVM SPIRVGenTables.inc -gen-searchable-tables)
add_public_tablegen_target(SPIRVCommonTableGen)
add_llvm_target(SPIRVCodeGen
+ SPIRVAPI.cpp
SPIRVAsmPrinter.cpp
SPIRVBuiltins.cpp
SPIRVCallLowering.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRVAPI.cpp b/llvm/lib/Target/SPIRV/SPIRVAPI.cpp
new file mode 100644
index 00000000000000..b4ada1947a4888
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVAPI.cpp
@@ -0,0 +1,167 @@
+//===-- SPIRVAPI.cpp - SPIR-V Backend API ---------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/CodeGen/CommandFlags.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Verifier.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/MC/MCTargetOptionsCommandFlags.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FormattedStream.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Target/TargetLoweringObjectFile.h"
+#include "llvm/Target/TargetMachine.h"
+#include "llvm/TargetParser/SubtargetFeature.h"
+#include "llvm/TargetParser/Triple.h"
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace llvm;
+
+namespace {
+
+// Mimic limited number of command line flags from llc to provide a better
+// user experience when passing options into the translate API call.
+static cl::opt<char> SpvOptLevel(" O", cl::Hidden, cl::Prefix, cl::init('0'));
+static cl::opt<std::string> SpvTargetTriple(" mtriple", cl::Hidden,
+ cl::init(""));
+
+// Utility to accept options in a command line style.
+void parseSPIRVCommandLineOptions(const std::vector<std::string> &Options,
+ raw_ostream *Errs) {
+ static constexpr const char *Origin = "SPIRVTranslateModule";
+ if (!Options.empty()) {
+ std::vector<const char *> Argv(1, Origin);
+ for (const auto &Arg : Options)
+ Argv.push_back(Arg.c_str());
+ cl::ParseCommandLineOptions(Argv.size(), Argv.data(), Origin, Errs);
+ }
+}
+
+std::once_flag InitOnceFlag;
+void InitializeSPIRVTarget() {
+ std::call_once(InitOnceFlag, []() {
+ LLVMInitializeSPIRVTargetInfo();
+ LLVMInitializeSPIRVTarget();
+ LLVMInitializeSPIRVTargetMC();
+ LLVMInitializeSPIRVAsmPrinter();
+ });
+}
+} // namespace
+
+namespace llvm {
+
+// The goal of this function is to facilitate integration of SPIRV Backend into
+// tools and libraries by means of exposing an API call that translate LLVM
+// module to SPIR-V and write results into a string as binary SPIR-V output,
+// providing diagnostics on fail and means of configuring translation in a style
+// of command line options.
+extern "C" LLVM_EXTERNAL_VISIBILITY bool
+SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
+ const std::vector<std::string> &Opts) {
+ // Fallbacks for option values.
+ static const std::string DefaultTriple = "spirv64-unknown-unknown";
+ static const std::string DefaultMArch = "";
+
+ // Parse Opts as if it'd be command line arguments.
+ std::string Errors;
+ raw_string_ostream ErrorStream(Errors);
+ parseSPIRVCommandLineOptions(Opts, &ErrorStream);
+ if (!Errors.empty()) {
+ ErrMsg = Errors;
+ return false;
+ }
+
+ llvm::CodeGenOptLevel OLevel;
+ if (auto Level = CodeGenOpt::parseLevel(SpvOptLevel)) {
+ OLevel = *Level;
+ } else {
+ ErrMsg = "Invalid optimization level!";
+ return false;
+ }
+
+ // SPIR-V-specific target initialization.
+ InitializeSPIRVTarget();
+
+ Triple TargetTriple(SpvTargetTriple.empty()
+ ? M->getTargetTriple()
+ : Triple::normalize(SpvTargetTriple));
+ if (TargetTriple.getTriple().empty()) {
+ TargetTriple.setTriple(DefaultTriple);
+ M->setTargetTriple(DefaultTriple);
+ }
+ const Target *TheTarget =
+ TargetRegistry::lookupTarget(DefaultMArch, TargetTriple, ErrMsg);
+ if (!TheTarget)
+ return false;
+
+ // A call to codegen::InitTargetOptionsFromCodeGenFlags(TargetTriple)
+ // hits the following assertion: llvm/lib/CodeGen/CommandFlags.cpp:78:
+ // llvm::FPOpFusion::FPOpFusionMode llvm::codegen::getFuseFPOps(): Assertion
+ // `FuseFPOpsView && "RegisterCodeGenFlags not created."' failed.
+ TargetOptions Options;
+ std::optional<Reloc::Model> RM;
+ std::optional<CodeModel::Model> CM;
+ std::unique_ptr<TargetMachine> Target =
+ std::unique_ptr<TargetMachine>(TheTarget->createTargetMachine(
+ TargetTriple.getTriple(), "", "", Options, RM, CM, OLevel));
+ if (!Target) {
+ ErrMsg = "Could not allocate target machine!";
+ return false;
+ }
+
+ if (M->getCodeModel())
+ Target->setCodeModel(*M->getCodeModel());
+
+ std::string DLStr = M->getDataLayoutStr();
+ Expected<DataLayout> MaybeDL = DataLayout::parse(
+ DLStr.empty() ? Target->createDataLayout().getStringRepresentation()
+ : DLStr);
+ if (!MaybeDL) {
+ ErrMsg = toString(MaybeDL.takeError());
+ return false;
+ }
+ M->setDataLayout(MaybeDL.get());
+
+ TargetLibraryInfoImpl TLII(Triple(M->getTargetTriple()));
+ legacy::PassManager PM;
+ PM.add(new TargetLibraryInfoWrapperPass(TLII));
+ LLVMTargetMachine &LLVMTM = static_cast<LLVMTargetMachine &>(*Target);
+ MachineModuleInfoWrapperPass *MMIWP =
+ new MachineModuleInfoWrapperPass(&LLVMTM);
+ const_cast<TargetLoweringObjectFile *>(LLVMTM.getObjFileLowering())
+ ->Initialize(MMIWP->getMMI().getContext(), *Target);
+
+ SmallString<4096> OutBuffer;
+ raw_svector_ostream OutStream(OutBuffer);
+ if (Target->addPassesToEmitFile(PM, OutStream, nullptr,
+ CodeGenFileType::ObjectFile)) {
+ ErrMsg = "Target machine cannot emit a file of this type";
+ return false;
+ }
+
+ PM.run(*M);
+ SpirvObj = OutBuffer.str();
+
+ return true;
+}
+
+} // namespace llvm
diff --git a/llvm/lib/Target/SPIRV/SPIRVAPI.h b/llvm/lib/Target/SPIRV/SPIRVAPI.h
new file mode 100644
index 00000000000000..c3786c6975a890
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVAPI.h
@@ -0,0 +1,23 @@
+//===-- SPIRVAPI.h - SPIR-V Backend API interface ---------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVAPI_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVAPI_H
+
+#include <string>
+#include <vector>
+
+namespace llvm {
+class Module;
+
+extern "C" bool SPIRVTranslateModule(Module *M, std::string &Buffer,
+ std::string &ErrMsg,
+ const std::vector<std::string> &Opts);
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVAPI_H
diff --git a/llvm/unittests/Target/SPIRV/CMakeLists.txt b/llvm/unittests/Target/SPIRV/CMakeLists.txt
index 83ae215c512ca2..e9fe4883e5b024 100644
--- a/llvm/unittests/Target/SPIRV/CMakeLists.txt
+++ b/llvm/unittests/Target/SPIRV/CMakeLists.txt
@@ -6,6 +6,7 @@ include_directories(
set(LLVM_LINK_COMPONENTS
Analysis
AsmParser
+ BinaryFormat
Core
SPIRVCodeGen
SPIRVAnalysis
@@ -14,5 +15,6 @@ set(LLVM_LINK_COMPONENTS
add_llvm_target_unittest(SPIRVTests
SPIRVConvergenceRegionAnalysisTests.cpp
+ SPIRVAPITest.cpp
)
diff --git a/llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp b/llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp
new file mode 100644
index 00000000000000..d58c1f3fe9b460
--- /dev/null
+++ b/llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp
@@ -0,0 +1,152 @@
+//===- llvm/unittest/CodeGen/SPIRVAPITest.cpp -----------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Test that SPIR-V Backend provides an API call that translates LLVM IR Module
+/// into SPIR-V.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/BinaryFormat/Magic.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/SourceMgr.h"
+#include "gtest/gtest.h"
+#include <gmock/gmock.h>
+#include <string>
+#include <utility>
+
+using ::testing::StartsWith;
+
+namespace llvm {
+
+extern "C" bool SPIRVTranslateModule(Module *M, std::string &Buffer,
+ std::string &ErrMsg,
+ const std::vector<std::string> &Opts);
+
+class SPIRVAPITest : public testing::Test {
+protected:
+ bool toSpirv(StringRef Assembly, std::string &Result, std::string &ErrMsg,
+ const std::vector<std::string> &Opts) {
+ SMDiagnostic ParseError;
+ M = parseAssemblyString(Assembly, ParseError, Context);
+ if (!M) {
+ ParseError.print("IR parsing failed: ", errs());
+ report_fatal_error("Can't parse input assembly.");
+ }
+ bool Status = SPIRVTranslateModule(M.get(), Result, ErrMsg, Opts);
+ if (!Status)
+ errs() << ErrMsg;
+ return Status;
+ }
+
+ LLVMContext Context;
+ std::unique_ptr<Module> M;
+
+ static constexpr StringRef ExtensionAssembly = R"(
+ define dso_local spir_func void @test1() {
+ entry:
+ %res1 = tail call spir_func i32 @_Z26__spirv_GroupBitwiseAndKHR(i32 2, i32 0, i32 0)
+ ret void
+ }
+
+ declare dso_local spir_func i32 @_Z26__spirv_GroupBitwiseAndKHR(i32, i32, i32)
+ )";
+ static constexpr StringRef OkAssembly = R"(
+ %struct = type { [1 x i64] }
+
+ define spir_kernel void @foo(ptr noundef byval(%struct) %arg) {
+ entry:
+ call spir_func void @bar(<2 x i32> noundef <i32 0, i32 1>)
+ ret void
+ }
+
+ define spir_func void @bar(<2 x i32> noundef) {
+ entry:
+ ret void
+ }
+ )";
+};
+
+TEST_F(SPIRVAPITest, checkTranslateOk) {
+ StringRef Assemblies[] = {"", OkAssembly};
+ // Those command line arguments that overlap with registered by llc/codegen
+ // are to be started with the ' ' symbol.
+ std::vector<std::string> SetOfOpts[] = {
+ {}, {"- mtriple=spirv32-unknown-unknown"}};
+ for (const auto &Opts : SetOfOpts) {
+ for (StringRef &Assembly : Assemblies) {
+ std::string Result, Error;
+ bool Status = toSpirv(Assembly, Result, Error, Opts);
+ EXPECT_TRUE(Status && Error.empty() && !Result.empty());
+ EXPECT_EQ(identify_magic(Result), file_magic::spirv_object);
+ }
+ }
+}
+
+TEST_F(SPIRVAPITest, checkTranslateError) {
+ std::string Result, Error;
+ bool Status =
+ toSpirv(OkAssembly, Result, Error, {"-mtriple=spirv32-unknown-unknown"});
+ EXPECT_FALSE(Status);
+ EXPECT_TRUE(Result.empty());
+ EXPECT_THAT(Error,
+ StartsWith("SPIRVTranslateModule: Unknown command line argument "
+ "'-mtriple=spirv32-unknown-unknown'"));
+ Status = toSpirv(OkAssembly, Result, Error, {"- O 5"});
+ EXPECT_FALSE(Status);
+ EXPECT_TRUE(Result.empty());
+ EXPECT_EQ(Error, "Invalid optimization level!");
+}
+
+TEST_F(SPIRVAPITest, checkTranslateSupportExtension) {
+ std::string Result, Error;
+ std::vector<std::string> Opts{
+ "--spirv-ext=+SPV_KHR_uniform_group_instructions"};
+ bool Status = toSpirv(ExtensionAssembly, Result, Error, Opts);
+ EXPECT_TRUE(Status && Error.empty() && !Result.empty());
+ EXPECT_EQ(identify_magic(Result), file_magic::spirv_object);
+}
+
+TEST_F(SPIRVAPITest, checkTranslateAllExtensions) {
+ std::string Result, Error;
+ std::vector<std::string> Opts{"--spirv-ext=all"};
+ bool Status = toSpirv(ExtensionAssembly, Result, Error, Opts);
+ EXPECT_TRUE(Status && Error.empty() && !Result.empty());
+ EXPECT_EQ(identify_magic(Result), file_magic::spirv_object);
+}
+
+#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
+TEST_F(SPIRVAPITest, checkTranslateExtensionError) {
+ std::string Result, Error;
+ std::vector<std::string> Opts;
+ EXPECT_DEATH_IF_SUPPORTED(
+ { toSpirv(ExtensionAssembly, Result, Error, Opts); },
+ "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the "
+ "following SPIR-V extension: SPV_KHR_uniform_group_instructions");
+}
+
+TEST_F(SPIRVAPITest, checkTranslateUnknownExtension) {
+ std::string Result, Error;
+ std::vector<std::string> Opts{"--spirv-ext=+SPV_XYZ_my_unknown_extension"};
+ EXPECT_DEATH_IF_SUPPORTED(
+ { toSpirv(ExtensionAssembly, Result, Error, Opts); },
+ "SPIRVTranslateModule: for the --spirv-ext option: Unknown SPIR-V");
+}
+
+TEST_F(SPIRVAPITest, checkTranslateWrongExtension) {
+ std::string Result, Error;
+ std::vector<std::string> Opts{"--spirv-ext=+SPV_KHR_subgroup_rotate"};
+ EXPECT_DEATH_IF_SUPPORTED(
+ { toSpirv(ExtensionAssembly, Result, Error, Opts); },
+ "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the "
+ "following SPIR-V extension: SPV_KHR_uniform_group_instructions");
+}
+#endif
+
+} // end namespace llvm
``````````
</details>
https://github.com/llvm/llvm-project/pull/107216
More information about the llvm-commits
mailing list