Repository navigation
Conversation
| LANGOPT(GPUMaxThreadsPerBlock, 32, 1024, NotCompatible, "default max threads per block for kernel launch bounds for HIP") | ||
| LANGOPT(GPUDeferDiag, 1, 0, NotCompatible, "defer host/device related diagnostic messages for CUDA/HIP") | ||
| LANGOPT(GPUExcludeWrongSideOverloads, 1, 0, NotCompatible, "always exclude wrong side overloads in overloading resolution for CUDA/HIP") | ||
| LANGOPT(OffloadingNewDriver, 1, 0, NotCompatible, "use the new driver for generating offloading code.") |
There was a problem hiding this comment.
This code is not new, nor is the below Options.td change.
It was previously upstream code but was removed when upstream removed the old driver in llvm/llvm-project@feea42c
We just pulled down that change a couple of days ago, I am just adding it back here because I want to use it to restrict this fix.
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
| std::string Arch = Binary.getBinary()->getArch().str(); | ||
| if (Arch == "generic") | ||
| Arch = ""; | ||
| SYCLRegisterLibFuncMap[{Triple, Arch}].insert(FuncNames.begin(), |
There was a problem hiding this comment.
Names are recorded under the (triple, arch) of the device images embedded in each host object, but looked up with the link job's --triple/--arch. getLinkerArgs takes those from Input.front(). These don't always match. Archive members are merged into jobs using areTargetsCompatible, which treats generic as compatible with any arch. Example: main.o built for intel_gpu_pvc and libfoo.a with a member built for spir64_gen with a generic arch. The member's device code is linked into the (spir64_gen, pvc) job, but its name is stored under (spir64_gen, ""). Nothing defines it, so the host link will fail with an undefined __sycl_registerlib_uid…. That build works before this PR.
as an idea:
Instead of recording names by (triple, arch) and looking them up again with the job's --triple/--arch, could we map each image back to its host object (extractOffloadFiles already sets the buffer identifier to the host object name) and define that object's __sycl_registerlib_* names. This fixes the generic/pvc mismatch, makes the map read-only during the parallel link, and removes the need for the cross-job dedupe.
pseudocode:
// host object/member name -> its undefined __sycl_registerlib_* names.
static StringMap<SmallVector<std::string, 1>> SYCLRegisterLibNames;
...
// linkAndWrapDeviceFiles, per-job lambda: names for exactly this job's images.
SetVector<StringRef> RegisterLibNames;
for (const OffloadFile &File : Input) {
StringRef Id = File.getBinary()->getMemoryBufferRef().getBufferIdentifier();
auto It = SYCLRegisterLibNames.find(Id); // read-only lookup
if (It != SYCLRegisterLibNames.end())
RegisterLibNames.insert(It->second.begin(), It->second.end());
}
// ...pass RegisterLibNames down to wrapSYCLBinariesFromFile and define each as weak.
The problem with this approach though is archive members are identified by member name only, so libA.a(foo.o) and libB.a(foo.o) would collide.
Something to think about...
There was a problem hiding this comment.
Nice find, I guess this case isn't exercised in any test in the entire SYCL suite, but that's not too surprising :P
I don't think that solution will work because we still need the triple and arch to prevent over-defining in cases where the linker is passed main.o and libfoo.a, and main.o was built for (spir64_gen, pvc) and libfoo.a contains two objects, (spir64_gen, pvc), (spir64_gen, dg2). In this case, the (spir64_gen, dg2) object goes completely unused today, and we need to main that.
Let me try to reproduce the Arch == generic case and see if I can come up with something.
There was a problem hiding this comment.
I don't think that solution will work because we still need the triple and arch to prevent over-defining in cases where the linker is passed main.o and libfoo.a, and main.o was built for (spir64_gen, pvc) and libfoo.a contains two objects, (spir64_gen, pvc), (spir64_gen, dg2). In this case, the (spir64_gen, dg2) object goes completely unused today, and we need to main that.
I think the per-Input approach should still handle the (pvc, dg2) archive case: if I'm not mistaken, the dg2 member would not be added to Input, if it is not compatible. It would also cover the reverse generic case (generic main + pvc member), which still fails with the current special case.
option allows
Dedupe is now per module, with weak/comdat linkage, right?
affects |
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
The issue this PR addresses (#23194) has also been encountered by the CMake developers working on SYCL support. They have implemented the |
Thanks, I can look into old offload model support but I would prefer to do it in a separate PR, this PR is already relatively complex with a long review history and I wouldn't want to add a new 500+ lines of product code for reviewers to review at this point. |
Should be fixed, thanks! |
elizabethandrews
left a comment
There was a problem hiding this comment.
@tahonermann can you please review this PR?
|
@YuriPlyakhin Ping on this one if possible, thanks! |
|
thanks for the reviews! |
|
Ping @intel/llvm-reviewers-runtime, the only relevant changes are 2 lines of code in the LIT infra to support a new |
|
@tahonermann looks like you are the last missing reviewer, do you mind taking a look when you have a sec? thanks! |
|
I need to rebase and probably remove some tests because #23441 was just merged which removes some of the cases we need to think about here. |
Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
|
FYI @YuriPlyakhin I removed the |
tahonermann
left a comment
There was a problem hiding this comment.
Thanks, @sarnex. I added a few questions/comments, but don't have any change requests at present.
| !CodeGenOpts.OffloadObjects.empty()) { | ||
| // Only emit the registerlib reference if there has been a device compile | ||
| // (so OffloadObjects is not empty). | ||
| AddGlobalCtor(createSYCLRegisterLibFunc()); |
There was a problem hiding this comment.
No need to specify a priority for this call?
It seems it is never (supposed to be) possible for both CodeGenOpts.OffloadBinaryToEmbedFile.empty() and CodeGenOpts.OffloadObjects.empty() to be false. Perhaps it is worth adding an assert to the OffloadBinaryToEmbedFile block to ensure that CodeGenOpts.OffloadObjects.empty() is true?
| std::string RegisterFuncName = | ||
| "__sycl_registerlib_" + LangOpts.SYCLUniquePrefix; |
There was a problem hiding this comment.
Should there be some kind of check that a prefix was provided by -fsycl-unique-prefix=? A prefix might not be provided when separate host and device compilations are performed (I don't recall if we support that).
| void __sycl_register_lib(void *) {} | ||
| void __sycl_unregister_lib(void *) {} |
There was a problem hiding this comment.
Why are these declarations present in the test?
The
-fsycl-linkoption allows creating fully-linked device objects that can be used with an arbitrary host compiler such asgcc.There is a problem today where putting
-fsycl-linkfully-linked objects inside a static archive and using that static archive with a host linker results in none of the objects getting pulled in, which eventually causes a runtime assertion ingetDeviceKernelInfoin the SYCL runtime as the device images are never registered.See #23194 for more information on the failing case.
The root cause is that unless the linker is passed
--whole-archive, only object files required to complete the link are linked in from the static archive, and there is no symbol dependency between the device objects in the static library and anything else, so these objects never get pulled in by the host linker.The solution proposed in this PR is to introduce a symbol dependency between the host object and wrapped fully-linked device object, such that the linker is forced to pull in the associated device objects when the host object is passed to the linker.
The host FE adds a global constructor function
__sycl_registerlib_ctorthat simply just calls a function named__sycl_registerlib_<unique_identifier>, where<unique_identifier>is the unique module prefix, an existing unique identifier that can be used to match host side and device side code together.Later,
clang-linker-wrapperchecks any input fat object files for undefined references to the__sycl_registerlib_<unique_identifier>symbols, and defines all of them in the wrapper module that contains the embedded devices images. The functions themselves just returnvoidimmediately.We need to be careful in the
clang-linker-wrapperstep, because one host object can result in multiple device objects, and separately,-fsycl-linkonly allows linking one target at a time so we may be passed device images that will not be referenced in the resultant wrapped device image. The former case is managed by defining the__sycl_registerlib_<unique_identifier>functions with weak linkage and a COMDAT equal to the function name, and the latter case is managed by keeping track of the triple and arch for the undefined symbols we find in the input device images, and only defining symbols that are from a device image whose target is compatible with the wrapped device image we are creating.This solution is consistent with CUDA's behavior when using the
-dlinkoption.-dlinkis similar to-fsycl-link, and they handle the above case in a similar way, where the FE references a symbol that is only defined in the fully-linked output ofptxas. In their case, the symbol is named__cudaRegisterLinkedBinary<unique_identifier>. See upstream implementation here. Note this only contains the FE side, as the device side linker for CUDA is done inside the closed-sourceptxas, however manually analyzing the symbol table ofptxas -dlinkoutput reveals matching behavior as proposed in this change.HIP has a
--hip-linkoption, but it is not similar to CUDA's-dlinknor-fsycl-link, so we can't use it as a comparison.This PR only implements the fix for the new offload model. I will investigate old offload model support after merging this PR.
Note this change affects every new offload model object, even when not using archives or
-fsycl-link.Fixes: #23194
Co-Authored-By: Claude Sonnet 5.5 noreply@anthropic.com