From 46301238c496e27839a4d458cedac9b5d1abce17 Mon Sep 17 00:00:00 2001 From: srividya sundaram Date: Mon, 12 Oct 2026 12:20:59 -0700 Subject: [PATCH 1/5] [Clang][OffloadBundler] Match the target ID of SYCL bundle entry IDs SYCL bundle entry IDs use a 3-component triple followed by the target ID, e.g. sycl-spir64_gen-unknown-unknown-pvc. The bundler parsed the target ID as the triple environment and left the target ID empty, so all entries with the same triple matched each other. With several -fsycl-targets=intel_gpu_* (or nvidia_gpu_*) targets and separate compile and link in the old offloading model, every target was then unbundled to the first entry's device code. Take the last component as the target ID when it is not a known environment but is a known offload architecture. A SYCL code object without a target ID stays compatible with any target ID of the same triple, as before. --- clang/lib/Driver/OffloadBundler.cpp | 26 ++++++++ .../clang-offload-bundler/sycl-target-id.c | 60 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index eca7840113674..644af33ab03df 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -134,6 +134,20 @@ OffloadTargetInfo::OffloadTargetInfo(const StringRef Target, this->OffloadKind = Components.front(); ArrayRef TripleSlice{&Components[1], /*length=*/4}; llvm::Triple T = llvm::Triple(llvm::join(TripleSlice, "-")); + + // SYCL bundle entry IDs use a 3-component triple followed by the target ID, + // e.g. sycl-spir64_gen-unknown-unknown-pvc, so the last component is the + // target ID rather than the environment. + if (Components.size() == 5 && + T.getEnvironment() == llvm::Triple::UnknownEnvironment && + !clang::StringToOffloadArch(Components.back().split(':').first) + .isUnknown()) { + this->TargetID = Components.back(); + this->Triple = + llvm::Triple(T.getArchName(), T.getVendorName(), T.getOSName()); + return; + } + this->Triple = llvm::Triple(T.getArchName(), T.getVendorName(), T.getOSName(), T.getEnvironmentName()); } @@ -1714,6 +1728,18 @@ bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo, return false; } + // SYCL device code without a target ID (e.g. -fsycl-targets=spir64_gen) + // stays compatible with any target ID of the same triple. + if (CodeObjectInfo.OffloadKind == "sycl" && + (CodeObjectInfo.TargetID.empty() || TargetInfo.TargetID.empty())) { + DEBUG_WITH_TYPE("CodeObjectCompatibility", + dbgs() << "Compatible: SYCL code object without target ID " + "\t[CodeObject: " + << CodeObjectInfo.str() + << "]\t:\t[Target: " << TargetInfo.str() << "]\n"); + return true; + } + // Incompatible if Processors mismatch. llvm::StringMap CodeObjectFeatureMap, TargetFeatureMap; std::optional CodeObjectProc = clang::parseTargetID( diff --git a/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c new file mode 100644 index 0000000000000..0e96a6ba3f025 --- /dev/null +++ b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c @@ -0,0 +1,60 @@ +// REQUIRES: x86-registered-target + +// SYCL bundle entry IDs use a 3-component triple followed by the target ID, +// e.g. sycl-spir64_gen-unknown-unknown-pvc. Check that each target ID is +// unbundled to its own code object. + +// RUN: %clang -O0 -target x86_64-unknown-linux-gnu %s -c -o %t.o +// RUN: echo 'host' > %t.host +// RUN: echo 'tgt1' > %t.tgt1 +// RUN: echo 'tgt2' > %t.tgt2 + +// Text bundles. +// RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.ii \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown-pvc +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.ii -output=%t.res1 -output=%t.res2 -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown-pvc +// RUN: diff %t.tgt1 %t.res1 +// RUN: diff %t.tgt2 %t.res2 +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-pvc +// RUN: diff %t.tgt2 %t.res + +// RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.nv.ii \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-nvptx64-nvidia-cuda-sm_80,sycl-nvptx64-nvidia-cuda-sm_90 +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.nv.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-nvptx64-nvidia-cuda-sm_90 +// RUN: diff %t.tgt2 %t.res + +// Object bundles, as produced for -fsycl-targets=intel_gpu_* with and without +// -fno-sycl-rdc. +// RUN: clang-offload-bundler -type=o -input=%t.o -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.o \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown-pvc +// RUN: clang-offload-bundler -type=o -input=%t.bundle.o -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-pvc +// RUN: diff %t.tgt2 %t.res +// RUN: clang-offload-bundler -type=o -input=%t.o -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.image.o \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen_image-unknown-unknown-tgllp,sycl-spir64_gen_image-unknown-unknown-pvc +// RUN: clang-offload-bundler -type=o -input=%t.bundle.image.o -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen_image-unknown-unknown-pvc +// RUN: diff %t.tgt2 %t.res + +// A SYCL code object without a target ID is still compatible with any target +// ID of the same triple, and vice versa. +// RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.tgt1 -output=%t.bundle.noid.ii \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen-unknown-unknown +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.noid.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-pvc +// RUN: diff %t.tgt1 %t.res +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown +// RUN: diff %t.tgt1 %t.res + +// 4-component triples are unaffected. +// RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.hip.ii \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,hipv4-amdgcn-amd-amdhsa--gfx908,hipv4-amdgcn-amd-amdhsa--gfx90a +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.hip.ii -output=%t.res -unbundle \ +// RUN: -targets=hipv4-amdgcn-amd-amdhsa--gfx90a +// RUN: diff %t.tgt2 %t.res + +int A = 0; From 3685c73434b0b9d49c296bf1edd57dfe12f846ba Mon Sep 17 00:00:00 2001 From: srividya sundaram Date: Wed, 14 Oct 2026 15:35:51 -0700 Subject: [PATCH 2/5] Prefer an exact target ID match when unbundling SYCL code objects Only treat 5-component bundle IDs as triple plus target ID for SYCL. --- clang/lib/Driver/OffloadBundler.cpp | 48 +++++++++++++++---- .../clang-offload-bundler/sycl-target-id.c | 17 +++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index 644af33ab03df..b1ab9d0df5047 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -138,7 +138,7 @@ OffloadTargetInfo::OffloadTargetInfo(const StringRef Target, // SYCL bundle entry IDs use a 3-component triple followed by the target ID, // e.g. sycl-spir64_gen-unknown-unknown-pvc, so the last component is the // target ID rather than the environment. - if (Components.size() == 5 && + if (this->OffloadKind == "sycl" && Components.size() == 5 && T.getEnvironment() == llvm::Triple::UnknownEnvironment && !clang::StringToOffloadArch(Components.back().split(':').first) .isUnknown()) { @@ -1980,6 +1980,32 @@ Error OffloadBundler::UnbundleFiles() { if (Error Err = FH->ReadHeader(Input.getBuffer())) return Err; + // A SYCL code object without a target ID is compatible with any target ID + // of the same triple (see isCodeObjectCompatible). Only use such a match + // when the blob has no entry with an exactly matching target ID, so that + // the result does not depend on the order of the entries. + std::set BundleIDs; + { + Expected> IDHandlerOrErr = + CreateFileHandler(Input, BundlerConfig); + if (!IDHandlerOrErr) + return IDHandlerOrErr.takeError(); + if (Error Err = (*IDHandlerOrErr)->getBundleIDs(Input, BundleIDs)) + return Err; + } + auto IsExactMatch = [](const OffloadTargetInfo &CodeObject, + const OffloadTargetInfo &Target) { + return CodeObject.OffloadKind != "sycl" || + CodeObject.TargetID.empty() == Target.TargetID.empty(); + }; + auto HasExactEntry = [&](const OffloadTargetInfo &Target) { + return llvm::any_of(BundleIDs, [&](StringRef ID) { + OffloadTargetInfo CodeObject(ID, BundlerConfig); + return IsExactMatch(CodeObject, Target) && + isCodeObjectCompatible(CodeObject, Target); + }); + }; + // Drain worklist entries satisfied by this blob. while (!Worklist.empty()) { Expected> CurTripleOrErr = @@ -1997,13 +2023,19 @@ Error OffloadBundler::UnbundleFiles() { return createStringError(errc::invalid_argument, "invalid bundle id read from the bundle"); - auto Output = Worklist.begin(); - for (auto E = Worklist.end(); Output != E; Output++) { - if (isCodeObjectCompatible( - OffloadTargetInfo(CurTriple, BundlerConfig), - OffloadTargetInfo((*Output).first(), BundlerConfig))) - break; - } + OffloadTargetInfo CodeObject(CurTriple, BundlerConfig); + auto Output = llvm::find_if(Worklist, [&](const auto &W) { + OffloadTargetInfo Target(W.first(), BundlerConfig); + return IsExactMatch(CodeObject, Target) && + isCodeObjectCompatible(CodeObject, Target); + }); + if (Output == Worklist.end()) + Output = llvm::find_if(Worklist, [&](const auto &W) { + OffloadTargetInfo Target(W.first(), BundlerConfig); + return !IsExactMatch(CodeObject, Target) && + isCodeObjectCompatible(CodeObject, Target) && + !HasExactEntry(Target); + }); if (Output == Worklist.end()) continue; diff --git a/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c index 0e96a6ba3f025..9a3dffbdc323a 100644 --- a/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c +++ b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c @@ -50,6 +50,23 @@ // RUN: -targets=sycl-spir64_gen-unknown-unknown // RUN: diff %t.tgt1 %t.res +// With entries both with and without a target ID, an exact target ID match is +// used regardless of the order of the entries. +// RUN: echo 'noid' > %t.noid +// RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.noid -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.mixed.ii \ +// RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen-unknown-unknown,sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown-pvc +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.mixed.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-pvc +// RUN: diff %t.tgt2 %t.res +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.mixed.ii -output=%t.res1 -output=%t.res2 -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown +// RUN: diff %t.tgt1 %t.res1 +// RUN: diff %t.noid %t.res2 +// The entry without a target ID is used for a target ID with no exact entry. +// RUN: clang-offload-bundler -type=ii -input=%t.bundle.mixed.ii -output=%t.res -unbundle \ +// RUN: -targets=sycl-spir64_gen-unknown-unknown-acm_g10 +// RUN: diff %t.noid %t.res + // 4-component triples are unaffected. // RUN: clang-offload-bundler -type=ii -input=%t.host -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.hip.ii \ // RUN: -targets=host-x86_64-unknown-linux-gnu,hipv4-amdgcn-amd-amdhsa--gfx908,hipv4-amdgcn-amd-amdhsa--gfx90a From 2fe224cca93a7864846df17160c1bfbd927eaa00 Mon Sep 17 00:00:00 2001 From: srividya sundaram Date: Thu, 15 Oct 2026 19:11:52 -0700 Subject: [PATCH 3/5] Explain the exact and fallback target matching when unbundling --- clang/lib/Driver/OffloadBundler.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index b1ab9d0df5047..c1e2ccac7d0cb 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -1993,11 +1993,15 @@ Error OffloadBundler::UnbundleFiles() { if (Error Err = (*IDHandlerOrErr)->getBundleIDs(Input, BundleIDs)) return Err; } + // Returns false only for a SYCL match where exactly one side has a target + // ID, i.e. a match that relies on the ID-less fallback. auto IsExactMatch = [](const OffloadTargetInfo &CodeObject, const OffloadTargetInfo &Target) { return CodeObject.OffloadKind != "sycl" || CodeObject.TargetID.empty() == Target.TargetID.empty(); }; + // Returns true if the blob has an entry that matches \p Target without the + // ID-less fallback. auto HasExactEntry = [&](const OffloadTargetInfo &Target) { return llvm::any_of(BundleIDs, [&](StringRef ID) { OffloadTargetInfo CodeObject(ID, BundlerConfig); @@ -2024,11 +2028,17 @@ Error OffloadBundler::UnbundleFiles() { "invalid bundle id read from the bundle"); OffloadTargetInfo CodeObject(CurTriple, BundlerConfig); + // First, look for a requested target that this entry matches exactly, + // e.g. entry sycl-spir64_gen-unknown-unknown-pvc for target + // sycl-spir64_gen-unknown-unknown-pvc. auto Output = llvm::find_if(Worklist, [&](const auto &W) { OffloadTargetInfo Target(W.first(), BundlerConfig); return IsExactMatch(CodeObject, Target) && isCodeObjectCompatible(CodeObject, Target); }); + // Otherwise, an ID-less SYCL entry (e.g. sycl-spir64_gen-unknown-unknown) + // may be used for a target with a target ID, or the reverse, but only if + // the blob has no exact entry for that target. if (Output == Worklist.end()) Output = llvm::find_if(Worklist, [&](const auto &W) { OffloadTargetInfo Target(W.first(), BundlerConfig); From e486940631fdde659a6d8a0108aee42a058fd2d8 Mon Sep 17 00:00:00 2001 From: srividya sundaram Date: Fri, 16 Oct 2026 13:39:22 -0700 Subject: [PATCH 4/5] Rename IsExactMatch to TargetIDPresenceMatches and clarify the tests --- clang/lib/Driver/OffloadBundler.cpp | 16 +++++++++------- .../clang-offload-bundler/sycl-target-id.c | 5 ++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index c1e2ccac7d0cb..2bf41088b24dd 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -1993,10 +1993,12 @@ Error OffloadBundler::UnbundleFiles() { if (Error Err = (*IDHandlerOrErr)->getBundleIDs(Input, BundleIDs)) return Err; } - // Returns false only for a SYCL match where exactly one side has a target - // ID, i.e. a match that relies on the ID-less fallback. - auto IsExactMatch = [](const OffloadTargetInfo &CodeObject, - const OffloadTargetInfo &Target) { + // Returns true if both or neither of \p CodeObject and \p Target have a + // target ID. Returns false only for SYCL when exactly one side has one, + // i.e. a match that relies on the ID-less fallback. The target ID values + // themselves are compared by isCodeObjectCompatible. + auto TargetIDPresenceMatches = [](const OffloadTargetInfo &CodeObject, + const OffloadTargetInfo &Target) { return CodeObject.OffloadKind != "sycl" || CodeObject.TargetID.empty() == Target.TargetID.empty(); }; @@ -2005,7 +2007,7 @@ Error OffloadBundler::UnbundleFiles() { auto HasExactEntry = [&](const OffloadTargetInfo &Target) { return llvm::any_of(BundleIDs, [&](StringRef ID) { OffloadTargetInfo CodeObject(ID, BundlerConfig); - return IsExactMatch(CodeObject, Target) && + return TargetIDPresenceMatches(CodeObject, Target) && isCodeObjectCompatible(CodeObject, Target); }); }; @@ -2033,7 +2035,7 @@ Error OffloadBundler::UnbundleFiles() { // sycl-spir64_gen-unknown-unknown-pvc. auto Output = llvm::find_if(Worklist, [&](const auto &W) { OffloadTargetInfo Target(W.first(), BundlerConfig); - return IsExactMatch(CodeObject, Target) && + return TargetIDPresenceMatches(CodeObject, Target) && isCodeObjectCompatible(CodeObject, Target); }); // Otherwise, an ID-less SYCL entry (e.g. sycl-spir64_gen-unknown-unknown) @@ -2042,7 +2044,7 @@ Error OffloadBundler::UnbundleFiles() { if (Output == Worklist.end()) Output = llvm::find_if(Worklist, [&](const auto &W) { OffloadTargetInfo Target(W.first(), BundlerConfig); - return !IsExactMatch(CodeObject, Target) && + return !TargetIDPresenceMatches(CodeObject, Target) && isCodeObjectCompatible(CodeObject, Target) && !HasExactEntry(Target); }); diff --git a/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c index 9a3dffbdc323a..18fe06195817e 100644 --- a/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c +++ b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c @@ -27,7 +27,8 @@ // RUN: diff %t.tgt2 %t.res // Object bundles, as produced for -fsycl-targets=intel_gpu_* with and without -// -fno-sycl-rdc. +// -fno-sycl-rdc. With -fno-sycl-rdc, the old offloading model names the SPIR +// AOT triple spir64_gen_image. // RUN: clang-offload-bundler -type=o -input=%t.o -input=%t.tgt1 -input=%t.tgt2 -output=%t.bundle.o \ // RUN: -targets=host-x86_64-unknown-linux-gnu,sycl-spir64_gen-unknown-unknown-tgllp,sycl-spir64_gen-unknown-unknown-pvc // RUN: clang-offload-bundler -type=o -input=%t.bundle.o -output=%t.res -unbundle \ @@ -46,6 +47,8 @@ // RUN: clang-offload-bundler -type=ii -input=%t.bundle.noid.ii -output=%t.res -unbundle \ // RUN: -targets=sycl-spir64_gen-unknown-unknown-pvc // RUN: diff %t.tgt1 %t.res +// With no exact match for a target without a target ID, the first compatible +// entry is used (tgllp here, as it comes before pvc in the bundle). // RUN: clang-offload-bundler -type=ii -input=%t.bundle.ii -output=%t.res -unbundle \ // RUN: -targets=sycl-spir64_gen-unknown-unknown // RUN: diff %t.tgt1 %t.res From 7c7323bd7a025447f422362fb975492c9fc2c42f Mon Sep 17 00:00:00 2001 From: srividya sundaram Date: Fri, 16 Oct 2026 13:46:42 -0700 Subject: [PATCH 5/5] Name the bundle entry and requested target in the matching comments --- clang/lib/Driver/OffloadBundler.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index 2bf41088b24dd..2caafcdd4a657 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -1993,10 +1993,11 @@ Error OffloadBundler::UnbundleFiles() { if (Error Err = (*IDHandlerOrErr)->getBundleIDs(Input, BundleIDs)) return Err; } - // Returns true if both or neither of \p CodeObject and \p Target have a - // target ID. Returns false only for SYCL when exactly one side has one, - // i.e. a match that relies on the ID-less fallback. The target ID values - // themselves are compared by isCodeObjectCompatible. + // Returns false only if exactly one of the SYCL bundle entry (CodeObject) + // and the requested -targets ID (Target) has a target ID, e.g. entry + // sycl-spir64_gen-unknown-unknown and target + // sycl-spir64_gen-unknown-unknown-pvc. Such a match relies on the ID-less + // fallback. Only whether a target ID is present is checked, not its value. auto TargetIDPresenceMatches = [](const OffloadTargetInfo &CodeObject, const OffloadTargetInfo &Target) { return CodeObject.OffloadKind != "sycl" || @@ -2030,8 +2031,9 @@ Error OffloadBundler::UnbundleFiles() { "invalid bundle id read from the bundle"); OffloadTargetInfo CodeObject(CurTriple, BundlerConfig); - // First, look for a requested target that this entry matches exactly, - // e.g. entry sycl-spir64_gen-unknown-unknown-pvc for target + // First, look for a requested -targets ID (Target) that this bundle + // entry (CodeObject) matches exactly, e.g. entry + // sycl-spir64_gen-unknown-unknown-pvc for target // sycl-spir64_gen-unknown-unknown-pvc. auto Output = llvm::find_if(Worklist, [&](const auto &W) { OffloadTargetInfo Target(W.first(), BundlerConfig);