[llvm] [SPIR-V] Expose an API call to initialize SPIRV target and translate input LLVM IR module to SPIR-V (PR #107216)
Vyacheslav Levytskyy via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 4 03:51:32 PDT 2024
https://github.com/VyacheslavLevytskyy created https://github.com/llvm/llvm-project/pull/107216
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.
>From 66f792ddd769ee70e04e858a56a3adc6070acc57 Mon Sep 17 00:00:00 2001
From: "Levytskyy, Vyacheslav" <vyacheslav.levytskyy at intel.com>
Date: Wed, 4 Sep 2024 03:41:54 -0700
Subject: [PATCH] the first draft of external SPIRV API
---
llvm/lib/Target/SPIRV/CMakeLists.txt | 1 +
llvm/lib/Target/SPIRV/SPIRV.cpp | 160 +++++++++++++++++++
llvm/unittests/Target/SPIRV/CMakeLists.txt | 2 +
llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp | 98 ++++++++++++
4 files changed, 261 insertions(+)
create mode 100644 llvm/lib/Target/SPIRV/SPIRV.cpp
create mode 100644 llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 5f8aea5fc8d84d..2c5c053fe72173 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
+ SPIRV.cpp
SPIRVAsmPrinter.cpp
SPIRVBuiltins.cpp
SPIRVCallLowering.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.cpp b/llvm/lib/Target/SPIRV/SPIRV.cpp
new file mode 100644
index 00000000000000..2636efb26128bf
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRV.cpp
@@ -0,0 +1,160 @@
+//===-- SPIRV.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/LinkAllAsmWriterComponents.h"
+// #include "llvm/CodeGen/LinkAllCodegenComponents.h"
+// #include "llvm/CodeGen/MIRParser/MIRParser.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+// #include "llvm/IR/AutoUpgrade.h"
+#include "llvm/IR/DataLayout.h"
+// #include "llvm/IR/DiagnosticInfo.h"
+// #include "llvm/IR/DiagnosticPrinter.h"
+#include "llvm/IR/LLVMContext.h"
+// #include "llvm/IR/LLVMRemarkStreamer.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Verifier.h"
+// #include "llvm/IRReader/IRReader.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/MC/MCTargetOptionsCommandFlags.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Pass.h"
+// #include "llvm/Remarks/HotnessThresholdParser.h"
+#include "llvm/Support/CommandLine.h"
+// #include "llvm/Support/Debug.h"
+// #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormattedStream.h"
+#include "llvm/Support/InitLLVM.h"
+// #include "llvm/Support/PluginLoader.h"
+// #include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/TargetSelect.h"
+// #include "llvm/Support/TimeProfiler.h"
+// #include "llvm/Support/ToolOutputFile.h"
+// #include "llvm/Support/WithColor.h"
+#include "llvm/Target/TargetLoweringObjectFile.h"
+#include "llvm/Target/TargetMachine.h"
+// #include "llvm/TargetParser/Host.h"
+#include "llvm/TargetParser/SubtargetFeature.h"
+#include "llvm/TargetParser/Triple.h"
+// #include "llvm/Transforms/Utils/Cloning.h"
+// #include <memory>
+#include <optional>
+// #include <ostream>
+#include <string>
+#include <utility>
+
+using namespace llvm;
+
+namespace {
+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
+
+extern "C" LLVM_EXTERNAL_VISIBILITY bool
+SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
+ const std::vector<std::string> &Opts) {
+ // Fallbacks for a Triple, MArch, Opt-level values.
+ static const std::string DefaultTriple = "spirv64-unknown-unknown";
+ static const std::string DefaultMArch = "";
+ static const llvm::CodeGenOptLevel OLevel = llvm::CodeGenOptLevel::None;
+
+ // Parse Opts as if it'd be command line argument.
+ std::string Errors;
+ raw_string_ostream ErrorStream(Errors);
+ parseSPIRVCommandLineOptions(Opts, &ErrorStream);
+ if (!Errors.empty()) {
+ ErrMsg = Errors;
+ return false;
+ }
+
+ // SPIR-V-specific target initialization.
+ InitializeSPIRVTarget();
+
+ Triple TargetTriple(M->getTargetTriple());
+ 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;
+}
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..7bc7d3446762a6
--- /dev/null
+++ b/llvm/unittests/Target/SPIRV/SPIRVAPITest.cpp
@@ -0,0 +1,98 @@
+//===- 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/IR/LegacyPassManager.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/BinaryFormat/Magic.h"
+#include "llvm/IR/Module.h"
+// #include "llvm/MC/TargetRegistry.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/SourceMgr.h"
+// #include "llvm/Support/TargetSelect.h"
+// #include "llvm/Target/TargetMachine.h"
+#include "gtest/gtest.h"
+#include <string>
+#include <utility>
+
+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:
+ /*
+ void SetUp() override {
+ EXPECT_TRUE(Status && Error.empty() && !Result.empty());
+ }
+ */
+
+ 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.");
+ }
+ return SPIRVTranslateModule(M.get(), Result, ErrMsg, Opts);
+ }
+
+ LLVMContext Context;
+ std::unique_ptr<Module> M;
+};
+
+TEST_F(SPIRVAPITest, checkTranslateExtError) {
+ StringRef Assembly = 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)
+ )";
+ std::string Result, Error;
+ std::vector<std::string> Opts;
+ 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, checkTranslateOk) {
+ StringRef Assemblies[] = {"", 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
+ }
+ )"};
+ for (StringRef &Assembly : Assemblies) {
+ std::string Result, Error;
+ std::vector<std::string> Opts;
+ bool Status = toSpirv(Assembly, Result, Error, Opts);
+ EXPECT_TRUE(Status && Error.empty() && !Result.empty());
+ EXPECT_EQ(identify_magic(Result), file_magic::spirv_object);
+ }
+}
+
+} // end namespace llvm
More information about the llvm-commits
mailing list