-
Notifications
You must be signed in to change notification settings - Fork 13.3k
[Clang][SYCL] Introduce clang-sycl-linker to link SYCL offloading device code (Part 1 of many) #112245
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
[Clang][SYCL] Introduce clang-sycl-linker to link SYCL offloading device code (Part 1 of many) #112245
Changes from 11 commits
eff4a03
753edc3
679436a
307c74e
21a7b1c
e38ded0
b78b2ef
e69ddfd
bab77c0
23fb5c2
411bb3a
d127523
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
======================= | ||
Clang SYCL Linker | ||
======================= | ||
|
||
.. contents:: | ||
:local: | ||
|
||
.. _clang-sycl-linker: | ||
|
||
Introduction | ||
============ | ||
|
||
This tool works as a wrapper around the SYCL device code linking process. | ||
The purpose of this tool is to provide an interface to link SYCL device bitcode | ||
in LLVM IR format, SYCL device bitcode in SPIR-V IR format, and native binary | ||
objects, and then use the SPIR-V LLVM Translator tool on fully linked device | ||
objects to produce the final output. | ||
After the linking stage, the fully linked device code in LLVM IR format may | ||
undergo several SYCL-specific finalization steps before the SPIR-V code | ||
generation step. | ||
The tool will also support the Ahead-Of-Time (AOT) compilation flow. AOT | ||
compilation is the process of invoking the back-end at compile time to produce | ||
the final binary, as opposed to just-in-time (JIT) compilation when final code | ||
generation is deferred until application runtime. | ||
|
||
Device code linking for SYCL offloading has several known quirks that | ||
make it difficult to use in a unified offloading setting. Two of the primary | ||
issues are: | ||
1. Several finalization steps are required to be run on the fully linked LLVM | ||
IR bitcode to guarantee conformance to SYCL standards. This step is unique to | ||
the SYCL offloading compilation flow. | ||
2. The SPIR-V LLVM Translator tool is an external tool and hence SPIR-V IR code | ||
generation cannot be done as part of LTO. This limitation can be lifted once | ||
the SPIR-V backend is available as a viable LLVM backend. | ||
|
||
This tool has been proposed to work around these issues. | ||
|
||
Usage | ||
===== | ||
|
||
This tool can be used with the following options. Several of these options will | ||
be passed down to downstream tools like 'llvm-link', 'llvm-spirv', etc. | ||
|
||
.. code-block:: console | ||
|
||
OVERVIEW: A utility that wraps around the SYCL device code linking process. | ||
This enables linking and code generation for SPIR-V JIT targets and AOT | ||
targets. | ||
|
||
USAGE: clang-sycl-linker [options] | ||
|
||
OPTIONS: | ||
--arch <value> Specify the name of the target architecture. | ||
--dry-run Print generated commands without running. | ||
-g Specify that this was a debug compile. | ||
-help-hidden Display all available options | ||
-help Display available options (--help-hidden for more) | ||
--library-path=<dir> Set the library path for SYCL device libraries | ||
--device-libs=<value> A comma separated list of device libraries that are linked during the device link | ||
-o <path> Path to file to write output | ||
--save-temps Save intermediate results | ||
--triple <value> Specify the target triple. | ||
--version Display the version number and exit | ||
-v Print verbose information | ||
-spirv-dump-device-code=<dir> Directory to dump SPIR-V IR code into | ||
-is-windows-msvc-env Specify if we are compiling under windows environment | ||
-llvm-spirv-options=<value> Pass options to llvm-spirv tool | ||
--llvm-spirv-path=<dir> Set the system llvm-spirv path | ||
|
||
Example | ||
======= | ||
|
||
This tool is intended to be invoked when targeting any of the target offloading | ||
toolchains. When the --sycl-link option is passed to the clang driver, the | ||
driver will invoke the linking job of the target offloading toolchain, which in | ||
turn will invoke this tool. This tool can be used to create one or more fully | ||
linked device images that are ready to be wrapped and linked with host code to | ||
generate the final executable. | ||
|
||
.. code-block:: console | ||
|
||
clang-sycl-linker --triple spirv64 --arch native input.bc |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6728,7 +6728,10 @@ def fsycl : Flag<["-"], "fsycl">, | |
def fno_sycl : Flag<["-"], "fno-sycl">, | ||
Visibility<[ClangOption, CLOption]>, | ||
Group<sycl_Group>, HelpText<"Disables SYCL kernels compilation for device">; | ||
|
||
def sycl_link : Flag<["--"], "sycl-link">, Flags<[HelpHidden]>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this necessary? Shouldn't it do this automatically if we pass in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use this option to select between spirv-linker which is the standard linking tool for spir-v IR (but not mature enough for us to use it in our pipeline) and the clang-sycl-linker. Thanks |
||
Visibility<[ClangOption, CLOption]>, | ||
Group<sycl_Group>, HelpText<"Perform link through clang-sycl-linker via the target " | ||
"offloading toolchain.">; | ||
// OS-specific options | ||
let Flags = [TargetSpecific] in { | ||
defm android_pad_segment : BooleanFFlag<"android-pad-segment">, Group<f_Group>; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,7 +95,21 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA, | |
CmdArgs.push_back("-o"); | ||
CmdArgs.push_back(Output.getFilename()); | ||
|
||
// Use of --sycl-link will call the clang-sycl-linker instead of | ||
// the default linker (spirv-link). | ||
if (Args.hasArg(options::OPT_sycl_link)) | ||
Linker = ToolChain.GetProgramPath("clang-sycl-linker"); | ||
C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), | ||
Args.MakeArgString(Linker), CmdArgs, | ||
Inputs, Output)); | ||
} | ||
|
||
SPIRVToolChain::SPIRVToolChain(const Driver &D, const llvm::Triple &Triple, | ||
const ArgList &Args) | ||
: ToolChain(D, Triple, Args) { | ||
// TODO: Revisit need/use of --sycl-link option once SYCL toolchain is | ||
// available and SYCL linking support is moved there. | ||
NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay I'm guessing this is to separate this from an existing SPIR-V link step? We could possibly put that in a different toolchain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can eventually move this to SYCL toolchain when it is available upstream. Thanks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, we can put that as a |
||
} | ||
|
||
bool SPIRVToolChain::HasNativeLLVMSupport() const { return NativeLLVMSupport; } |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||||||
// Tests the clang-sycl-linker tool. | ||||||||||
// | ||||||||||
// Test a simple case without arguments. | ||||||||||
// RUN: %clangxx -emit-llvm -c %s -o %t_1.bc | ||||||||||
// RUN: %clangxx -emit-llvm -c %s -o %t_2.bc | ||||||||||
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=SIMPLE | ||||||||||
// SIMPLE: "{{.*}}llvm-link{{.*}}" {{.*}}.bc {{.*}}.bc -o [[FIRSTLLVMLINKOUT:.*]].bc --suppress-warnings | ||||||||||
// SIMPLE-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[FIRSTLLVMLINKOUT]].bc | ||||||||||
// | ||||||||||
// Test that llvm-link is not called when only one input is present. | ||||||||||
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=SIMPLE-NO-LINK | ||||||||||
// SIMPLE-NO-LINK: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv {{.*}}.bc | ||||||||||
// | ||||||||||
// Test a simple case with device library files specified. | ||||||||||
// RUN: touch %T/lib1.bc | ||||||||||
// RUN: touch %T/lib2.bc | ||||||||||
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc --library-path=%T --device-libs=lib1.bc,lib2.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=DEVLIBS | ||||||||||
// DEVLIBS: "{{.*}}llvm-link{{.*}}" {{.*}}.bc {{.*}}.bc -o [[FIRSTLLVMLINKOUT:.*]].bc --suppress-warnings | ||||||||||
// DEVLIBS-NEXT: "{{.*}}llvm-link{{.*}}" -only-needed [[FIRSTLLVMLINKOUT]].bc {{.*}}lib1.bc {{.*}}lib2.bc -o [[SECONDLLVMLINKOUT:.*]].bc --suppress-warnings | ||||||||||
// DEVLIBS-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[SECONDLLVMLINKOUT]].bc | ||||||||||
// | ||||||||||
// Test a simple case with .o (fat object) as input. | ||||||||||
// TODO: Remove this test once fat object support is added. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's correct. Currently trying to make the linker wrapper a bit leaner, but the expected flow should be
My general assertion is that every target should have a way to get a usable image via a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, the flow is this. clang-inker-wrapper reads the fat object and extracts bitcode files. These bitcode files are then fed to clang++ as inputs. Thanks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We don't want that. We want to link object files with the code for a single target (i.e. offload device target). We don't want I suggest we update the comment by replacing "fat object" to just "object". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the idea is that the device toolchain should work in the same way that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adjusted comment. Thanks
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can I please update this in one of the upcoming PRs? I do not want to trigger another round of testing here. Thanks |
||||||||||
// RUN: %clangxx -c %s -o %t.o | ||||||||||
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t.o -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=FILETYPEERROR | ||||||||||
// FILETYPEERROR: Unsupported file type | ||||||||||
// | ||||||||||
// Test to see if device library related errors are emitted. | ||||||||||
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t.bc --library-path=%T --device-libs= -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=DEVLIBSERR1 | ||||||||||
// DEVLIBSERR1: Number of device library files cannot be zero | ||||||||||
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t.bc --library-path=%T --device-libs=lib1.bc,lib2.bc,lib3.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=DEVLIBSERR2 | ||||||||||
// DEVLIBSERR2: '{{.*}}lib3.bc' SYCL device library file is not found | ||||||||||
// | ||||||||||
// Test if correct set of llvm-spirv options are emitted for windows environment. | ||||||||||
// RUN: clang-sycl-linker --dry-run -triple spirv64 --is-windows-msvc-env %t.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=LLVMOPTSWIN | ||||||||||
// LLVMOPTSWIN: -spirv-debug-info-version=ocl-100 -spirv-allow-extra-diexpressions -spirv-allow-unknown-intrinsics=llvm.genx. -spirv-ext= | ||||||||||
// | ||||||||||
// Test if correct set of llvm-spirv options are emitted for linux environment. | ||||||||||
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t.bc -o a.spv 2>&1 \ | ||||||||||
// RUN: | FileCheck %s --check-prefix=LLVMOPTSLIN | ||||||||||
// LLVMOPTSLIN: -spirv-debug-info-version=nonsemantic-shader-200 -spirv-allow-unknown-intrinsics=llvm.genx. -spirv-ext= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Tests the driver when linking LLVM IR bitcode files and targeting SPIR-V | ||
// architecture. | ||
// | ||
// Test that -Xlinker options are being passed to clang-sycl-linker. | ||
// RUN: touch %t.bc | ||
// RUN: %clangxx -### --target=spirv64 --sycl-link -Xlinker --llvm-spirv-path=/tmp \ | ||
// RUN: -Xlinker --library-path=/tmp -Xlinker --device-libs=lib1.bc,lib2.bc %t.bc 2>&1 \ | ||
// RUN: | FileCheck %s -check-prefix=XLINKEROPTS | ||
// XLINKEROPTS: "{{.*}}clang-sycl-linker{{.*}}" "--llvm-spirv-path=/tmp" "--library-path=/tmp" "--device-libs=lib1.bc,lib2.bc" "{{.*}}.bc" "-o" "a.out" | ||
asudarsa marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
set(LLVM_LINK_COMPONENTS | ||
${LLVM_TARGETS_TO_BUILD} | ||
Option | ||
) | ||
|
||
set(LLVM_TARGET_DEFINITIONS SYCLLinkOpts.td) | ||
tablegen(LLVM SYCLLinkOpts.inc -gen-opt-parser-defs) | ||
add_public_tablegen_target(SYCLLinkerOpts) | ||
|
||
if(NOT CLANG_BUILT_STANDALONE) | ||
set(tablegen_deps intrinsics_gen SYCLLinkerOpts) | ||
endif() | ||
|
||
add_clang_tool(clang-sycl-linker | ||
ClangSYCLLinker.cpp | ||
|
||
DEPENDS | ||
${tablegen_deps} | ||
) | ||
|
||
set(CLANG_SYCL_LINKER_LIB_DEPS | ||
clangBasic | ||
) | ||
|
||
target_link_libraries(clang-sycl-linker | ||
PRIVATE | ||
${CLANG_SYCL_LINKER_LIB_DEPS} | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are device libs and regular input distinct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regular inputs for SYCL offloading are linked using default settings whereas SYCL device libs are linked with the '--only-needed' flag.
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's kind of the behavior of static libraries, and LTO could just optimize them out if they're unused. But I suppose it's not terribly important to get it perfect on the first pass.