Skip to content

Commit abb0f5c

Browse files
authored
[SYCL] Add supplemental tool for SPIR-V to LLVM-IR conversions (#5157)
Tool name: spirv-to-ir-wrapper Function: Provides an interface used within the compiler driver toolchain which converts SPIR-V to LLVM-IR. Using llvm-spirv as the main conversion tool, the purpose of this is to be able to take binaries and convert them to LLVM-IR. The conversion only takes place if the input is SPIR-V, and if the LLVM-IR is provided it is simply copied and passed through. This tool is useful for handling generated fat objects/archives that contain SPIR-V as opposed to typical LLVM-IR. We do not know the type of files within the device objects, so we use this tool to make sure that file consumed after unbundling is always LLVM-IR. Example usage: spirv-to-ir-wrapper input.spv -o output.bc // creates LLVM-IR output.bc spirv-to-ir-wrapper input.bc -o output.bc // no conversion, creates output.bc
1 parent ecfdaa1 commit abb0f5c

File tree

5 files changed

+203
-0
lines changed

5 files changed

+203
-0
lines changed

llvm/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ set(LLVM_TEST_DEPENDS
135135
opt
136136
sancov
137137
sanstats
138+
spirv-to-ir-wrapper
138139
sycl-post-link
139140
split-file
140141
verify-uselistorder
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
; Check for passthrough abilities
2+
; RUN: llvm-as %s -o %t.bc
3+
; RUN: spirv-to-ir-wrapper %t.bc -o %t_1.bc
4+
; RUN: llvm-dis %t_1.bc -o %t_1.ll
5+
; RUN: FileCheck %s --input-file %t_1.ll
6+
7+
; Check for SPIR-V conversion
8+
; RUN: llvm-spirv %t.bc -o %t.spv
9+
; RUN: spirv-to-ir-wrapper %t.spv -o %t_2.bc
10+
; RUN: llvm-dis %t_2.bc -o %t_2.ll
11+
; RUN: FileCheck %s --input-file %t_2.ll
12+
13+
; CHECK: target datalayout
14+
; CHECK-NEXT: target triple = "spir-unknown-unknown"
15+
; CHECK: Function Attrs: nounwind
16+
; CHECK-NEXT: define spir_kernel void @foo(i32 addrspace(1)* %a)
17+
; CHECK-NEXT: entry:
18+
; CHECK-NEXT: %a.addr = alloca i32 addrspace(1)*, align 4
19+
; CHECK-NEXT: store i32 addrspace(1)* %a, i32 addrspace(1)** %a.addr, align 4
20+
; CHECK-NEXT: ret void
21+
22+
target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024"
23+
target triple = "spir-unknown-unknown"
24+
25+
; Function Attrs: nounwind
26+
define spir_kernel void @foo(i32 addrspace(1)* %a) #0 !kernel_arg_addr_space !1 !kernel_arg_access_qual !2 !kernel_arg_type !3 !kernel_arg_base_type !4 !kernel_arg_type_qual !5 {
27+
entry:
28+
%a.addr = alloca i32 addrspace(1)*, align 4
29+
store i32 addrspace(1)* %a, i32 addrspace(1)** %a.addr, align 4
30+
ret void
31+
}
32+
33+
attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
34+
35+
!opencl.enable.FP_CONTRACT = !{}
36+
!opencl.spir.version = !{!6}
37+
!opencl.ocl.version = !{!6}
38+
!opencl.used.extensions = !{!7}
39+
!opencl.used.optional.core.features = !{!7}
40+
!opencl.compiler.options = !{!7}
41+
42+
!1 = !{i32 1}
43+
!2 = !{!"none"}
44+
!3 = !{!"int*"}
45+
!4 = !{!"int*"}
46+
!5 = !{!""}
47+
!6 = !{i32 1, i32 2}
48+
!7 = !{}
49+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
set(LLVM_LINK_COMPONENTS
2+
Core
3+
Support
4+
SPIRVLib
5+
)
6+
7+
include_directories(
8+
${LLVM_EXTERNAL_LLVM_SPIRV_SOURCE_DIR}/include
9+
)
10+
11+
add_llvm_tool(spirv-to-ir-wrapper
12+
spirv-to-ir-wrapper.cpp
13+
)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//===--- spirv-to-ir-wrapper.cpp - Utility to convert to ir if needed -----===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This utility checks if the input file is SPIR-V based. If so, convert to IR
10+
// The input can be either SPIR-V or LLVM-IR. When LLVM-IR, copy the file to
11+
// the specified output.
12+
//
13+
// Uses llvm-spirv to perform the conversion if needed.
14+
//
15+
// The output file is used to allow for proper input and output flow within
16+
// the driver toolchain.
17+
//
18+
// Usage: spirv-to-ir-wrapper input.spv -o output.bc
19+
//
20+
//===----------------------------------------------------------------------===//
21+
22+
#include "LLVMSPIRVLib.h"
23+
#include "llvm/BinaryFormat/Magic.h"
24+
#include "llvm/Support/CommandLine.h"
25+
#include "llvm/Support/Error.h"
26+
#include "llvm/Support/FileSystem.h"
27+
#include "llvm/Support/InitLLVM.h"
28+
#include "llvm/Support/Path.h"
29+
#include "llvm/Support/Program.h"
30+
#include "llvm/Support/SourceMgr.h"
31+
#include "llvm/Support/StringSaver.h"
32+
33+
using namespace llvm;
34+
35+
// InputFilename - The filename to read from.
36+
static cl::opt<std::string> InputFilename(cl::Positional,
37+
cl::value_desc("<input spv file>"),
38+
cl::desc("<input file>"));
39+
40+
// Output - The filename to output to.
41+
static cl::opt<std::string> Output("o", cl::value_desc("output IR filename"),
42+
cl::desc("output filename"));
43+
44+
// LlvmSpirvOpts - The filename to output to.
45+
static cl::opt<std::string>
46+
LlvmSpirvOpts("llvm-spirv-opts", cl::value_desc("llvm-spirv options"),
47+
cl::desc("options to pass to llvm-spirv"));
48+
49+
static void error(const Twine &Message) {
50+
llvm::errs() << "spirv-to-ir-wrapper: " << Message << '\n';
51+
exit(1);
52+
}
53+
54+
// Convert the SPIR-V to LLVM-IR.
55+
static int convertSPIRVToLLVMIR(const char *Argv0) {
56+
// Find llvm-spirv. It is expected this resides in the same directory
57+
// as spirv-to-ir-wrapper.
58+
StringRef ParentPath = llvm::sys::path::parent_path(Argv0);
59+
llvm::ErrorOr<std::string> LlvmSpirvBinary =
60+
llvm::sys::findProgramByName("llvm-spirv", ParentPath);
61+
if (!LlvmSpirvBinary)
62+
LlvmSpirvBinary = llvm::sys::findProgramByName("llvm-spirv");
63+
64+
SmallVector<StringRef, 6> LlvmSpirvArgs = {"llvm-spirv", "-r", InputFilename,
65+
"-o", Output};
66+
67+
// Add any additional options specified by the user.
68+
SmallVector<const char *, 8> TargetArgs;
69+
llvm::BumpPtrAllocator BPA;
70+
llvm::StringSaver S(BPA);
71+
if (!LlvmSpirvOpts.empty()) {
72+
// Tokenize the string.
73+
llvm::cl::TokenizeGNUCommandLine(LlvmSpirvOpts, S, TargetArgs);
74+
std::copy(TargetArgs.begin(), TargetArgs.end(),
75+
std::back_inserter(LlvmSpirvArgs));
76+
}
77+
78+
return llvm::sys::ExecuteAndWait(LlvmSpirvBinary.get(), LlvmSpirvArgs);
79+
}
80+
81+
static int copyInputToOutput() {
82+
return llvm::sys::fs::copy_file(InputFilename, Output).value();
83+
}
84+
85+
static bool isSPIRVBinary(const std::string &File) {
86+
auto FileOrError = MemoryBuffer::getFile(File, /*IsText=*/false,
87+
/*RequiresNullTerminator=*/false);
88+
if (!FileOrError)
89+
return false;
90+
std::unique_ptr<MemoryBuffer> FileBuffer = std::move(*FileOrError);
91+
return SPIRV::isSpirvBinary(FileBuffer->getBuffer().str());
92+
}
93+
94+
static bool isLLVMIRBinary(const std::string &File) {
95+
if (File.size() < sizeof(unsigned))
96+
return false;
97+
98+
StringRef Ext = llvm::sys::path::has_extension(File)
99+
? llvm::sys::path::extension(File).drop_front()
100+
: "";
101+
llvm::file_magic Magic;
102+
llvm::identify_magic(File, Magic);
103+
104+
// Only .bc and bitcode files are to be considered.
105+
return (Ext == "bc" || Magic == llvm::file_magic::bitcode);
106+
}
107+
108+
static int checkInputFileIsAlreadyLLVM(const char *Argv0) {
109+
StringRef Ext = llvm::sys::path::has_extension(InputFilename)
110+
? llvm::sys::path::extension(InputFilename).drop_front()
111+
: "";
112+
if (Ext == "bc" || isLLVMIRBinary(InputFilename))
113+
return copyInputToOutput();
114+
if (Ext == "spv" || isSPIRVBinary(InputFilename))
115+
return convertSPIRVToLLVMIR(Argv0);
116+
117+
// We could not directly determine the input file, so we just copy it
118+
// to the output file.
119+
return copyInputToOutput();
120+
}
121+
122+
int main(int argc, char **argv) {
123+
InitLLVM X(argc, argv);
124+
125+
LLVMContext Context;
126+
cl::ParseCommandLineOptions(argc, argv, "spirv-to-ir-wrapper\n");
127+
128+
if (InputFilename.empty())
129+
error("No input file provided");
130+
131+
if (!llvm::sys::fs::exists(InputFilename))
132+
error("Input file \'" + InputFilename + "\' not found");
133+
134+
if (Output.empty())
135+
error("Output file not provided");
136+
137+
return checkInputFileIsAlreadyLLVM(argv[0]);
138+
}

sycl/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ add_custom_target(sycl-compiler
229229
llvm-spirv
230230
llvm-link
231231
llvm-objcopy
232+
spirv-to-ir-wrapper
232233
sycl-post-link
233234
opencl-aot
234235
)
@@ -292,6 +293,7 @@ set( SYCL_TOOLCHAIN_DEPLOY_COMPONENTS
292293
llvm-spirv
293294
llvm-link
294295
llvm-objcopy
296+
spirv-to-ir-wrapper
295297
sycl-post-link
296298
sycl-ls
297299
clang-resource-headers

0 commit comments

Comments
 (0)