diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 8b25279045441..d0b62f2adeccf 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -401,6 +401,11 @@ class CodeGenOptions : public CodeGenOptionsBase { /// CUDA runtime back-end for incorporating them into host-side object file. std::string CudaGpuBinaryFileName; + /// Name of file passed with -fsycl-include-target-binary option. Used in + /// -fno-sycl-rdc mode to embed and register a per-TU finalized SYCL device + /// image into the host object at compile time. + std::string SYCLTargetBinaryFileName; + /// List of filenames passed in using the -fembed-offload-object option. These /// are offloading binaries containing device images and metadata. std::vector OffloadObjects; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 837407f0ba574..153629c786c9e 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9469,6 +9469,9 @@ def fcuda_is_device : Flag<["-"], "fcuda-is-device">, def fcuda_include_gpubinary : Separate<["-"], "fcuda-include-gpubinary">, HelpText<"Incorporate CUDA device-side binary into host object file.">, MarshallingInfoString>; +def fsycl_include_target_binary : Separate<["-"], "fsycl-include-target-binary">, + HelpText<"Incorporate SYCL device-side binary into host object file.">, + MarshallingInfoString>; def fcuda_allow_variadic_functions : Flag<["-"], "fcuda-allow-variadic-functions">, HelpText<"Deprecated; Allow variadic functions in CUDA device code.">; def fno_cuda_host_device_constexpr : Flag<["-"], "fno-cuda-host-device-constexpr">, diff --git a/clang/lib/CodeGen/CGSYCLRuntime.cpp b/clang/lib/CodeGen/CGSYCLRuntime.cpp index 6b1abe409c1d1..6ebbd650fbb6a 100644 --- a/clang/lib/CodeGen/CGSYCLRuntime.cpp +++ b/clang/lib/CodeGen/CGSYCLRuntime.cpp @@ -14,9 +14,12 @@ #include "CodeGenFunction.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" +#include "clang/Basic/DiagnosticFrontend.h" #include "clang/Basic/SourceLocation.h" #include "llvm/Analysis/OptimizationRemarkEmitter.h" +#include "llvm/Frontend/Offloading/OffloadWrapper.h" #include "llvm/IR/Instructions.h" +#include "llvm/Support/VirtualFileSystem.h" #include using namespace clang; @@ -131,6 +134,27 @@ bool CGSYCLRuntime::actOnGlobalVarEmit(CodeGenModule &CGM, const VarDecl &D, return true; } +void clang::CodeGen::embedSYCLNoRDCBinary(CodeGenModule &CGM) { + if (!CGM.getLangOpts().SYCLIsHost || + CGM.getCodeGenOpts().SYCLTargetBinaryFileName.empty()) + return; + auto BinaryOrErr = CGM.getFileSystem()->getBufferForFile( + CGM.getCodeGenOpts().SYCLTargetBinaryFileName, /*MaxSize=*/-1, + /*RequiresNullTerminator=*/false); + if (std::error_code EC = BinaryOrErr.getError()) { + CGM.getDiags().Report(diag::err_cannot_open_file) + << CGM.getCodeGenOpts().SYCLTargetBinaryFileName << EC.message(); + return; + } + llvm::ArrayRef Buffer((*BinaryOrErr)->getBufferStart(), + (*BinaryOrErr)->getBufferSize()); + if (llvm::Error E = llvm::offloading::wrapSYCLBinaries( + CGM.getModule(), Buffer, llvm::offloading::SYCLJITOptions{})) + CGM.getDiags().Report(diag::err_fe_linking_module) + << CGM.getCodeGenOpts().SYCLTargetBinaryFileName + << llvm::toString(std::move(E)); +} + bool Util::matchQualifiedTypeName(const CXXRecordDecl *RecTy, ArrayRef Scopes) { // The idea: check the declaration context chain starting from the type diff --git a/clang/lib/CodeGen/CGSYCLRuntime.h b/clang/lib/CodeGen/CGSYCLRuntime.h index 85a18e4bb590e..ed750153a6fe5 100644 --- a/clang/lib/CodeGen/CGSYCLRuntime.h +++ b/clang/lib/CodeGen/CGSYCLRuntime.h @@ -51,6 +51,10 @@ class CGSYCLRuntime { llvm::Value *Addr); }; +/// Embeds the per-TU finalized SYCL device binary into the host module when +/// -fsycl-include-target-binary is set (-fno-sycl-rdc compile step). +void embedSYCLNoRDCBinary(CodeGenModule &CGM); + } // namespace CodeGen } // namespace clang diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 894781d6a1b77..c249b028ac44b 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -50,9 +50,9 @@ #include "clang/CodeGen/BackendUtil.h" #include "clang/CodeGen/ConstantInitBuilder.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Sema/Sema.h" #include "clang/Sema/SemaSYCL.h" -#include "clang/Lex/Preprocessor.h" #include "llvm/ABI/IRTypeMapper.h" #include "llvm/ABI/TargetInfo.h" #include "llvm/ADT/STLExtras.h" @@ -1257,6 +1257,7 @@ void CodeGenModule::Release() { if (llvm::Function *CudaCtorFunction = CUDARuntime->finalizeModule()) AddGlobalCtor(CudaCtorFunction); } + embedSYCLNoRDCBinary(*this); if (OpenMPRuntime) { OpenMPRuntime->createOffloadEntriesAndInfoMetadata(); OpenMPRuntime->clear(); diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 7a0d81674601d..0f7c64279af45 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -8339,9 +8339,26 @@ Driver::BuildOffloadingActions(Compilation &C, llvm::opt::DerivedArgList &Args, DDep.add(*PackagerAction, *C.getOffloadToolChains().first->second, /*BA=*/{}, Action::OFK_HIP); + } else if (C.isOffloadingHostKind(Action::OFK_SYCL) && + tools::SYCL::shouldDoPerObjectFileLinking(C) && + !isa(HostAction)) { + // SYCL -fno-sycl-rdc at compile time (-c): finalize this TU's device code + // immediately via clang-linker-wrapper --sycl-device-link --no-sycl-rdc, + // producing a self-contained device image. The image is passed to the host + // cc1 via -fsycl-include-target-binary and embedded+registered at compile + // time. The final link step does no SYCL device work. This mirrors the + // CUDA/HIP -fno-gpu-rdc per-TU finalize model; downstream uses + // clang-linker-wrapper instead of clang-sycl-linker. + Action *PackagerAction = + C.MakeAction(OffloadActions, types::TY_Image); + ActionList AL{PackagerAction}; + Action *FinalizeAction = + C.MakeAction(AL, types::TY_Image); + DDep.add(*FinalizeAction, *C.getSingleOffloadToolChain(), + /*BA=*/{}, Action::OFK_SYCL); } else { - // Package all the offloading actions into a single output that can be - // embedded in the host and linked. + // RDC (default): package raw device bitcode to be embedded in the host + // object and device-linked across all TUs at final link time. Action *PackagerAction = C.MakeAction(OffloadActions, types::TY_Image); DDep.add(*PackagerAction, *C.getSingleOffloadToolChain(), diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 15b81a8f94adc..866c953a6e3a0 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -9149,10 +9149,17 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back(CudaDeviceInput->getFilename()); } else if (!HostOffloadingInputs.empty()) { if ((IsCuda || IsHIP) && !IsRDCMode) { + // CUDA/HIP no-RDC: device image finalized per-TU, embed at compile time. assert(HostOffloadingInputs.size() == 1 && "Only one input expected"); CmdArgs.push_back("-fcuda-include-gpubinary"); CmdArgs.push_back(HostOffloadingInputs.front().getFilename()); + } else if (IsSYCL && !IsRDCMode) { + // SYCL no-RDC: per-TU finalized device image, embed at compile time. + assert(HostOffloadingInputs.size() == 1 && "One finalized image per TU"); + CmdArgs.push_back("-fsycl-include-target-binary"); + CmdArgs.push_back(HostOffloadingInputs.front().getFilename()); } else { + // RDC: embed raw device bitcode for cross-TU device link at link time. for (const InputInfo Input : HostOffloadingInputs) CmdArgs.push_back(Args.MakeArgString("-fembed-offload-object=" + TC.getInputFilename(Input))); @@ -12154,6 +12161,24 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA, if (Args.hasArg(options::OPT_fsycl_link_EQ)) CmdArgs.push_back(Args.MakeArgString("--sycl-device-link")); + // Propagate [no-]rdc mode to the linker wrapper for the SYCL case. + // The default behaviour is rdc mode ON, which requires no special flags. + // In order to enable non-rdc mode, we pass --no-sycl-rdc to the linker + // wrapper. Note: -f[no-]sycl-rdc is an alias of [no-]gpu_rdc. + bool IsSYCLNoRDC = + !Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, + /*default=*/true); + if (IsSYCLNoRDC) { + CmdArgs.push_back("--no-sycl-rdc"); + // isDeviceOffloading(OFK_SYCL) distinguishes the per-TU finalizer + // (OFK_SYCL, from BuildOffloadingActions) from the final link-time + // invocation (OFK_Host) -- both produce TY_Image so output type alone + // is not a reliable discriminator. + if (C.getDriver().getFinalPhase(C.getArgs()) != phases::Link && + JA.isDeviceOffloading(Action::OFK_SYCL)) + CmdArgs.push_back("--sycl-device-link"); + } + // -sycl-device-library-location= provides the location in which the // SYCL device libraries can be found. SmallString<128> DeviceLibDir(D.Dir); diff --git a/clang/test/Driver/sycl-no-rdc-compile-step.cpp b/clang/test/Driver/sycl-no-rdc-compile-step.cpp new file mode 100644 index 0000000000000..80f24616fef77 --- /dev/null +++ b/clang/test/Driver/sycl-no-rdc-compile-step.cpp @@ -0,0 +1,29 @@ +/// Tests for -fno-sycl-rdc at the compile step (-c) with --offload-new-driver. +/// Verifies that the driver inserts a per-TU clang-linker-wrapper finalize +/// action and routes the result to -fsycl-include-target-binary on the host +/// cc1, instead of deferring device processing to link time. + +// RUN: touch %t.cpp + +// -fno-sycl-rdc -c: per-TU linker-wrapper with --sycl-device-link, host cc1 gets -fsycl-include-target-binary. +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu \ +// RUN: -fsycl -fno-sycl-rdc -c %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-COMPILE --implicit-check-not=-fembed-offload-object %s +// CHK-COMPILE: clang-linker-wrapper{{.*}} "--no-sycl-rdc"{{.*}} "--sycl-device-link" +// CHK-COMPILE: "-fsycl-is-host"{{.*}} "-fsycl-include-target-binary" + +// Default RDC -c: -fembed-offload-object appears, no -fsycl-include-target-binary or --no-sycl-rdc. +// RUN: %clang -### --offload-new-driver --target=x86_64-unknown-linux-gnu \ +// RUN: -fsycl -c %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-RDC %s +// CHK-RDC: -fembed-offload-object +// CHK-RDC-NOT: -fsycl-include-target-binary +// CHK-RDC-NOT: --no-sycl-rdc + +// -fno-sycl-rdc at link step: --no-sycl-rdc forwarded, --sycl-device-link must NOT appear. +// RUN: touch %t.o +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu \ +// RUN: -fsycl -fno-sycl-rdc %t.o 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-LINK %s +// CHK-LINK: clang-linker-wrapper{{.*}} "--no-sycl-rdc" +// CHK-LINK-NOT: clang-linker-wrapper{{.*}} "--sycl-device-link" diff --git a/clang/test/Driver/sycl-no-rdc-new-driver.cpp b/clang/test/Driver/sycl-no-rdc-new-driver.cpp new file mode 100644 index 0000000000000..df63550f454a3 --- /dev/null +++ b/clang/test/Driver/sycl-no-rdc-new-driver.cpp @@ -0,0 +1,71 @@ +/// Tests for -f[no-]sycl-rdc with --offload-new-driver. + +// Verifies that --no-sycl-rdc is propagated to clang-linker-wrapper when +// -fno-sycl-rdc is passed. RDC is ON by default; --no-sycl-rdc signals +// RDC is OFF. + +// RUN: touch %t.cpp + +// Default (no flag): RDC is ON by default for SYCL, so --no-sycl-rdc should NOT appear. +// RUN: %clang -### --offload-new-driver --target=x86_64-unknown-linux-gnu -fsycl %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-DEFAULT %s +// CHK-DEFAULT-NOT: --no-sycl-rdc + +// -fno-sycl-rdc: --no-sycl-rdc should appear. +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu -fsycl -fno-sycl-rdc %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-NO-RDC %s +// CHK-NO-RDC: clang-linker-wrapper{{.*}} "--no-sycl-rdc" + +// AOT Intel GPU target, default RDC: --no-sycl-rdc should NOT appear. +// RUN: %clang -### --offload-new-driver --target=x86_64-unknown-linux-gnu -fsycl -fsycl-targets=intel_gpu_pvc %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-AOT-RDC %s +// CHK-AOT-RDC-NOT: --no-sycl-rdc + +// AOT Intel GPU target + -fno-sycl-rdc: --no-sycl-rdc should appear. +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu -fsycl -fsycl-targets=intel_gpu_pvc -fno-sycl-rdc %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-AOT-NO-RDC %s +// CHK-AOT-NO-RDC: clang-linker-wrapper{{.*}} "--no-sycl-rdc" + +// -fno-sycl-rdc -flto -c: per-TU device link still happens, --sycl-device-link present. +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu -fsycl -fno-sycl-rdc -flto -c %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-NO-RDC-LTO-C %s +// CHK-NO-RDC-LTO-C: clang-linker-wrapper{{.*}} "--no-sycl-rdc"{{.*}} "--sycl-device-link" + +// -fno-sycl-rdc -flto (link step): --sycl-device-link should NOT appear. +// RUN: %clang -### --offload-new-driver -Werror --target=x86_64-unknown-linux-gnu -fsycl -fno-sycl-rdc -flto %t.cpp 2>&1 \ +// RUN: | FileCheck -check-prefix=CHK-NO-RDC-LTO %s +// CHK-NO-RDC-LTO: clang-linker-wrapper{{.*}} "--no-sycl-rdc" +// CHK-NO-RDC-LTO-NOT: --sycl-device-link + +// Verify pipeline with --offload-new-driver -fno-sycl-rdc. +// RUN: touch %t1.cpp +// RUN: touch %t2.cpp +// RUN: %clang -### --offload-new-driver --target=x86_64-unknown-linux-gnu -fsycl -fno-sycl-rdc %t1.cpp %t2.cpp 2>&1 -ccc-print-phases | FileCheck %s --check-prefix=CHECK-PIPELINE + +// CHECK-PIPELINE: 0: input, "{{.*}}1.cpp", c++, (host-sycl) +// CHECK-PIPELINE: 1: preprocessor, {0}, c++-cpp-output, (host-sycl) +// CHECK-PIPELINE: 2: compiler, {1}, ir, (host-sycl) +// CHECK-PIPELINE: 3: input, "{{.*}}1.cpp", c++, (device-sycl) +// CHECK-PIPELINE: 4: preprocessor, {3}, c++-cpp-output, (device-sycl) +// CHECK-PIPELINE: 5: compiler, {4}, ir, (device-sycl) +// CHECK-PIPELINE: 6: backend, {5}, ir, (device-sycl) +// CHECK-PIPELINE: 7: offload, "device-sycl (spir64-unknown-unknown)" {6}, ir +// CHECK-PIPELINE: 8: llvm-offload-binary, {7}, image, (device-sycl) +// CHECK-PIPELINE: 9: clang-linker-wrapper, {8}, image, (device-sycl) +// CHECK-PIPELINE: 10: offload, "host-sycl (x86_64-unknown-linux-gnu)" {2}, "device-sycl (x86_64-unknown-linux-gnu)" {9}, ir +// CHECK-PIPELINE: 11: backend, {10}, assembler, (host-sycl) +// CHECK-PIPELINE: 12: assembler, {11}, object, (host-sycl) +// CHECK-PIPELINE: 13: input, "{{.*}}2.cpp", c++, (host-sycl) +// CHECK-PIPELINE: 14: preprocessor, {13}, c++-cpp-output, (host-sycl) +// CHECK-PIPELINE: 15: compiler, {14}, ir, (host-sycl) +// CHECK-PIPELINE: 16: input, "{{.*}}2.cpp", c++, (device-sycl) +// CHECK-PIPELINE: 17: preprocessor, {16}, c++-cpp-output, (device-sycl) +// CHECK-PIPELINE: 18: compiler, {17}, ir, (device-sycl) +// CHECK-PIPELINE: 19: backend, {18}, ir, (device-sycl) +// CHECK-PIPELINE: 20: offload, "device-sycl (spir64-unknown-unknown)" {19}, ir +// CHECK-PIPELINE: 21: llvm-offload-binary, {20}, image, (device-sycl) +// CHECK-PIPELINE: 22: clang-linker-wrapper, {21}, image, (device-sycl) +// CHECK-PIPELINE: 23: offload, "host-sycl (x86_64-unknown-linux-gnu)" {15}, "device-sycl (x86_64-unknown-linux-gnu)" {22}, ir +// CHECK-PIPELINE: 24: backend, {23}, assembler, (host-sycl) +// CHECK-PIPELINE: 25: assembler, {24}, object, (host-sycl) +// CHECK-PIPELINE: 26: clang-linker-wrapper, {12, 25}, image, (host-sycl) diff --git a/sycl/doc/design/NonRelocatableDeviceCode.md b/sycl/doc/design/NonRelocatableDeviceCode.md index 8b74beca63be0..edb843185a98c 100644 --- a/sycl/doc/design/NonRelocatableDeviceCode.md +++ b/sycl/doc/design/NonRelocatableDeviceCode.md @@ -1,5 +1,7 @@ # Non-Relocatable Device Code +Note: This document reflects the design of NoRDC Mode support in the Old Offload Model. + ## Overview By default, SYCL allows device code to be relocatable, where function calls outside of the current translation unit are allowed using the `SYCL_EXTERNAL` attribute. diff --git a/sycl/doc/design/OffloadDesign.md b/sycl/doc/design/OffloadDesign.md index 35a86d2184ea2..7f4b0ea0dc4f0 100644 --- a/sycl/doc/design/OffloadDesign.md +++ b/sycl/doc/design/OffloadDesign.md @@ -243,6 +243,31 @@ are needed to pass along this information. *Table: Ahead of Time Info* +### NoRDC Mode support + +For the Old Offload Model support of NoRDC Mode see [NonRelocatableDeviceCode.md](NonRelocatableDeviceCode.md). + +The default compiler behavior is `-fsycl-rdc`, which incorporates linking of device code. +If `-fno-sycl-rdc` is specified, the compiler skips linking of device code and performs +offload processing on every module individually. + +With the new offload model, `-fno-sycl-rdc` can be specified at the link step: +``` +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 input1.cpp -c -o object1.o +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 input2.cpp -c -o object2.o +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 -fno-sycl-rdc object1.o object2.o -o a.out +``` + +It can also be specified at the compile step: +``` +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 -fno-sycl-rdc input1.cpp -c -o object1.o +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 -fno-sycl-rdc input2.cpp -c -o object2.o +clang++ --offload-new-driver -fsycl -fsycl-targets=T1,T2 object1.o object2.o -o a.out +``` +In this case `clang-linker-wrapper --sycl-device-link --no-sycl-rdc` is invoked per +translation unit at compile time to finalize each TU's device code independently, +embedding the result directly into the host object. + #### Format of the --device-compiler Option The `--device-compiler` option uses the format `--device-compiler=[:][=]` where: - `` : specifies the offloading kind (e.g., sycl, hip, openmp) and is optional. diff --git a/sycl/test-e2e/AOT/early_aot.cpp b/sycl/test-e2e/AOT/early_aot.cpp index 49385d7857fa5..2537d92f494ce 100644 --- a/sycl/test-e2e/AOT/early_aot.cpp +++ b/sycl/test-e2e/AOT/early_aot.cpp @@ -1,13 +1,15 @@ // Test early-AOT behaviors with -fsycl -fno-sycl-rdc. This targets spir64_gen // REQUIRES: ocloc, gpu, target-spir -// XFAIL: new-offload-model -// XFAIL-TRACKER: CMPLRLLVM-51875 + +// Note: For early AOT, -fno-sycl-rdc must be specified at the compilation step +// for the Old Offload Model. The New Offload Model supports -fno-sycl-rdc at +// both the compile and link steps. // Build the early AOT device binaries // RUN: %clangxx -fsycl -fsycl-targets=spir64_gen -Xsycl-target-backend=spir64_gen %gpu_aot_target_opts -fno-sycl-rdc -c -DADD_CPP %s -o %t_add.o // RUN: %clangxx -fsycl -fsycl-targets=spir64_gen -Xsycl-target-backend=spir64_gen %gpu_aot_target_opts -fno-sycl-rdc -c -DSUB_CPP %s -o %t_sub.o -// RUN: %clangxx -fsycl -DMAIN_CPP %s %t_add.o %t_sub.o -o %t.out +// RUN: %clangxx -fsycl -fno-sycl-rdc -DMAIN_CPP %s %t_add.o %t_sub.o -o %t.out // RUN: %{run} %t.out