diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index eca7840113674..2caafcdd4a657 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 (this->OffloadKind == "sycl" && 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( @@ -1954,6 +1980,39 @@ 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; + } + // 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" || + 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); + return TargetIDPresenceMatches(CodeObject, Target) && + isCodeObjectCompatible(CodeObject, Target); + }); + }; + // Drain worklist entries satisfied by this blob. while (!Worklist.empty()) { Expected> CurTripleOrErr = @@ -1971,13 +2030,26 @@ 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); + // 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); + return TargetIDPresenceMatches(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); + return !TargetIDPresenceMatches(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 new file mode 100644 index 0000000000000..18fe06195817e --- /dev/null +++ b/clang/test/OffloadTools/clang-offload-bundler/sycl-target-id.c @@ -0,0 +1,80 @@ +// 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. 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 \ +// 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 +// 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 + +// 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 +// 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;