Skip to content

[SYCL] Add supplemental tool for SPIR-V to LLVM-IR conversions #5157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 30, 2021
Merged
1 change: 1 addition & 0 deletions llvm/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ set(LLVM_TEST_DEPENDS
opt
sancov
sanstats
spirv-to-ir-wrapper
sycl-post-link
split-file
verify-uselistorder
Expand Down
49 changes: 49 additions & 0 deletions llvm/test/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
; Check for passthrough abilities
; RUN: llvm-as %s -o %t.bc
; RUN: spirv-to-ir-wrapper %t.bc -o %t_1.bc
; RUN: llvm-dis %t_1.bc -o %t_1.ll
; RUN: FileCheck %s --input-file %t_1.ll

; Check for SPIR-V conversion
; RUN: llvm-spirv %t.bc -o %t.spv
; RUN: spirv-to-ir-wrapper %t.spv -o %t_2.bc
; RUN: llvm-dis %t_2.bc -o %t_2.ll
; RUN: FileCheck %s --input-file %t_2.ll

; CHECK: target datalayout
; CHECK-NEXT: target triple = "spir-unknown-unknown"
; CHECK: Function Attrs: nounwind
; CHECK-NEXT: define spir_kernel void @foo(i32 addrspace(1)* %a)
; CHECK-NEXT: entry:
; CHECK-NEXT: %a.addr = alloca i32 addrspace(1)*, align 4
; CHECK-NEXT: store i32 addrspace(1)* %a, i32 addrspace(1)** %a.addr, align 4
; CHECK-NEXT: ret void

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"
target triple = "spir-unknown-unknown"

; Function Attrs: nounwind
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 {
entry:
%a.addr = alloca i32 addrspace(1)*, align 4
store i32 addrspace(1)* %a, i32 addrspace(1)** %a.addr, align 4
ret void
}

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" }

!opencl.enable.FP_CONTRACT = !{}
!opencl.spir.version = !{!6}
!opencl.ocl.version = !{!6}
!opencl.used.extensions = !{!7}
!opencl.used.optional.core.features = !{!7}
!opencl.compiler.options = !{!7}

!1 = !{i32 1}
!2 = !{!"none"}
!3 = !{!"int*"}
!4 = !{!"int*"}
!5 = !{!""}
!6 = !{i32 1, i32 2}
!7 = !{}

13 changes: 13 additions & 0 deletions llvm/tools/spirv-to-ir-wrapper/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
set(LLVM_LINK_COMPONENTS
Core
Support
SPIRVLib
)

include_directories(
${LLVM_EXTERNAL_LLVM_SPIRV_SOURCE_DIR}/include
)

add_llvm_tool(spirv-to-ir-wrapper
spirv-to-ir-wrapper.cpp
)
138 changes: 138 additions & 0 deletions llvm/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//===--- spirv-to-ir-wrapper.cpp - Utility to convert to ir if needed -----===//
//
// 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 utility checks if the input file is SPIR-V based. If so, convert to IR
// The input can be either SPIR-V or LLVM-IR. When LLVM-IR, copy the file to
// the specified output.
//
// Uses llvm-spirv to perform the conversion if needed.
//
// The output file is used to allow for proper input and output flow within
// the driver toolchain.
//
// Usage: spirv-to-ir-wrapper input.spv -o output.bc
//
//===----------------------------------------------------------------------===//

#include "LLVMSPIRVLib.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/StringSaver.h"

using namespace llvm;

// InputFilename - The filename to read from.
static cl::opt<std::string> InputFilename(cl::Positional,
cl::value_desc("<input spv file>"),
cl::desc("<input file>"));

// Output - The filename to output to.
static cl::opt<std::string> Output("o", cl::value_desc("output IR filename"),
cl::desc("output filename"));

// LlvmSpirvOpts - The filename to output to.
static cl::opt<std::string>
LlvmSpirvOpts("llvm-spirv-opts", cl::value_desc("llvm-spirv options"),
cl::desc("options to pass to llvm-spirv"));

static void error(const Twine &Message) {
llvm::errs() << "spirv-to-ir-wrapper: " << Message << '\n';
exit(1);
}

// Convert the SPIR-V to LLVM-IR.
static int convertSPIRVToLLVMIR(const char *Argv0) {
// Find llvm-spirv. It is expected this resides in the same directory
// as spirv-to-ir-wrapper.
StringRef ParentPath = llvm::sys::path::parent_path(Argv0);
llvm::ErrorOr<std::string> LlvmSpirvBinary =
llvm::sys::findProgramByName("llvm-spirv", ParentPath);
if (!LlvmSpirvBinary)
LlvmSpirvBinary = llvm::sys::findProgramByName("llvm-spirv");

SmallVector<StringRef, 6> LlvmSpirvArgs = {"llvm-spirv", "-r", InputFilename,
"-o", Output};

// Add any additional options specified by the user.
SmallVector<const char *, 8> TargetArgs;
llvm::BumpPtrAllocator BPA;
llvm::StringSaver S(BPA);
if (!LlvmSpirvOpts.empty()) {
// Tokenize the string.
llvm::cl::TokenizeGNUCommandLine(LlvmSpirvOpts, S, TargetArgs);
std::copy(TargetArgs.begin(), TargetArgs.end(),
std::back_inserter(LlvmSpirvArgs));
}

return llvm::sys::ExecuteAndWait(LlvmSpirvBinary.get(), LlvmSpirvArgs);
}

static int copyInputToOutput() {
return llvm::sys::fs::copy_file(InputFilename, Output).value();
}

static bool isSPIRVBinary(const std::string &File) {
auto FileOrError = MemoryBuffer::getFile(File, /*IsText=*/false,
/*RequiresNullTerminator=*/false);
if (!FileOrError)
return false;
std::unique_ptr<MemoryBuffer> FileBuffer = std::move(*FileOrError);
return SPIRV::isSpirvBinary(FileBuffer->getBuffer().str());
}

static bool isLLVMIRBinary(const std::string &File) {
if (File.size() < sizeof(unsigned))
return false;

StringRef Ext = llvm::sys::path::has_extension(File)
? llvm::sys::path::extension(File).drop_front()
: "";
llvm::file_magic Magic;
llvm::identify_magic(File, Magic);

// Only .bc and bitcode files are to be considered.
return (Ext == "bc" || Magic == llvm::file_magic::bitcode);
}

static int checkInputFileIsAlreadyLLVM(const char *Argv0) {
StringRef Ext = llvm::sys::path::has_extension(InputFilename)
? llvm::sys::path::extension(InputFilename).drop_front()
: "";
if (Ext == "bc" || isLLVMIRBinary(InputFilename))
return copyInputToOutput();
if (Ext == "spv" || isSPIRVBinary(InputFilename))
return convertSPIRVToLLVMIR(Argv0);

// We could not directly determine the input file, so we just copy it
// to the output file.
return copyInputToOutput();
}

int main(int argc, char **argv) {
InitLLVM X(argc, argv);

LLVMContext Context;
cl::ParseCommandLineOptions(argc, argv, "spirv-to-ir-wrapper\n");

if (InputFilename.empty())
error("No input file provided");

if (!llvm::sys::fs::exists(InputFilename))
error("Input file \'" + InputFilename + "\' not found");

if (Output.empty())
error("Output file not provided");

return checkInputFileIsAlreadyLLVM(argv[0]);
}
2 changes: 2 additions & 0 deletions sycl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ add_custom_target(sycl-compiler
llvm-spirv
llvm-link
llvm-objcopy
spirv-to-ir-wrapper
sycl-post-link
opencl-aot
)
Expand Down Expand Up @@ -292,6 +293,7 @@ set( SYCL_TOOLCHAIN_DEPLOY_COMPONENTS
llvm-spirv
llvm-link
llvm-objcopy
spirv-to-ir-wrapper
sycl-post-link
sycl-ls
clang-resource-headers
Expand Down