diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt index 83e83b1dbe8b9..73f1b3ae7d3e5 100644 --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -135,6 +135,7 @@ set(LLVM_TEST_DEPENDS opt sancov sanstats + spirv-to-ir-wrapper sycl-post-link split-file verify-uselistorder diff --git a/llvm/test/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.ll b/llvm/test/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.ll new file mode 100644 index 0000000000000..05bf5cce9f20c --- /dev/null +++ b/llvm/test/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.ll @@ -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 = !{} + diff --git a/llvm/tools/spirv-to-ir-wrapper/CMakeLists.txt b/llvm/tools/spirv-to-ir-wrapper/CMakeLists.txt new file mode 100644 index 0000000000000..09f319dcf8896 --- /dev/null +++ b/llvm/tools/spirv-to-ir-wrapper/CMakeLists.txt @@ -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 + ) diff --git a/llvm/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.cpp b/llvm/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.cpp new file mode 100644 index 0000000000000..a54c805b760ee --- /dev/null +++ b/llvm/tools/spirv-to-ir-wrapper/spirv-to-ir-wrapper.cpp @@ -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 InputFilename(cl::Positional, + cl::value_desc(""), + cl::desc("")); + +// Output - The filename to output to. +static cl::opt Output("o", cl::value_desc("output IR filename"), + cl::desc("output filename")); + +// LlvmSpirvOpts - The filename to output to. +static cl::opt + 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 LlvmSpirvBinary = + llvm::sys::findProgramByName("llvm-spirv", ParentPath); + if (!LlvmSpirvBinary) + LlvmSpirvBinary = llvm::sys::findProgramByName("llvm-spirv"); + + SmallVector LlvmSpirvArgs = {"llvm-spirv", "-r", InputFilename, + "-o", Output}; + + // Add any additional options specified by the user. + SmallVector 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 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]); +} diff --git a/sycl/CMakeLists.txt b/sycl/CMakeLists.txt index 2718ebd141a42..1f7fdb0411e63 100644 --- a/sycl/CMakeLists.txt +++ b/sycl/CMakeLists.txt @@ -229,6 +229,7 @@ add_custom_target(sycl-compiler llvm-spirv llvm-link llvm-objcopy + spirv-to-ir-wrapper sycl-post-link opencl-aot ) @@ -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