https://github.com/yxsamliu created https://github.com/llvm/llvm-project/pull/233184
`--offload-jobs=N` runs independent device compilations in parallel, but it had no effect when only device code is compiled; multi-target device-only builds still compiled each target one at a time. Two things got in the way. The device-only compile produces an object directly, and that step was not treated as one that can run in parallel. Each target's compile is also followed by its own device link, so the compiles that could run together are not next to each other, and only neighboring steps were grouped for parallel execution. Treat the object-producing device compile as parallelizable, and group same-kind compiles even when other independent steps sit between them. The grouped compiles do not depend on those in-between steps, so the output is unchanged and only the build time drops. >From 1feef69c8ee948d8b7a449ae99d0f3df0b887cce Mon Sep 17 00:00:00 2001 From: "Yaxun (Sam) Liu" <[email protected]> Date: Sat, 17 Oct 2026 11:30:35 -0400 Subject: [PATCH] [clang][Driver] Honor `--offload-jobs` in device-only compilation `--offload-jobs=N` runs independent device compilations in parallel, but it had no effect when only device code is compiled; multi-target device-only builds still compiled each target one at a time. Two things got in the way. The device-only compile produces an object directly, and that step was not treated as one that can run in parallel. Each target's compile is also followed by its own device link, so the compiles that could run together are not next to each other, and only neighboring steps were grouped for parallel execution. Treat the object-producing device compile as parallelizable, and group same-kind compiles even when other independent steps sit between them. The grouped compiles do not depend on those in-between steps, so the output is unchanged and only the build time drops. --- clang/lib/Driver/Compilation.cpp | 73 ++++++++++--------- clang/lib/Driver/Driver.cpp | 3 +- .../Driver/offload-parallel-device-cc1.cu | 7 ++ 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp index c81c4445a29f95..e9de5ab4d8de73 100644 --- a/clang/lib/Driver/Compilation.cpp +++ b/clang/lib/Driver/Compilation.cpp @@ -16,6 +16,7 @@ #include "clang/Driver/Util.h" #include "clang/Options/Options.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/OptSpecifier.h" #include "llvm/Option/Option.h" @@ -291,40 +292,40 @@ struct ParallelJobResult { std::string Error; }; -struct ParallelOffloadJobGroupResult { - size_t NumJobs = 0; -}; - -static std::optional<ParallelOffloadJobGroupResult> +// Collect the device jobs that share StartIndex's parallel group and can run +// concurrently, run them on a thread pool, and record them in \p Done. Group +// members need not be adjacent in the job list: in device-only compilation each +// target's compile is followed by its own device link, so the compiles that +// share a group are separated by those links. Returns true if a parallel group +// (including the start job) was executed. +static bool tryExecuteParallelOffloadJobGroup(const Driver &D, const ArgList &Args, ArrayRef<std::optional<StringRef>> Redirects, const JobList::list_type &JobStorage, size_t StartIndex, + llvm::SmallPtrSetImpl<const Command *> &Done, FailingCommandList &FailingCommands) { const Command &Job = *JobStorage[StartIndex]; if (!CanRunInParallelOffloadJobGroup(Job)) - return std::nullopt; + return false; - SmallVector<const Command *, 4> ParallelJobs; - for (size_t I = StartIndex; I < JobStorage.size(); ++I) { + // The start job's dependencies precede it in the topologically ordered job + // list and have already run, so it is always safe to run here. + SmallVector<const Command *, 4> ParallelJobs{&Job}; + for (size_t I = StartIndex + 1; I < JobStorage.size(); ++I) { const Command &Candidate = *JobStorage[I]; - if (ActionFailed(&Candidate.getSource(), FailingCommands)) - break; - - if (!CanRunInParallelOffloadJobGroup(Candidate)) - break; - - if (!SameParallelOffloadJobGroup(Job, Candidate)) - break; - - if (!HasDistinctBoundArch(Candidate, ParallelJobs)) - break; + if (Done.contains(&Candidate) || + ActionFailed(&Candidate.getSource(), FailingCommands) || + !CanRunInParallelOffloadJobGroup(Candidate) || + !SameParallelOffloadJobGroup(Job, Candidate) || + !HasDistinctBoundArch(Candidate, ParallelJobs)) + continue; if (!llvm::all_of(ParallelJobs, [&](const Command *Other) { return ActionsAreIndependent(&Candidate.getSource(), &Other->getSource()); })) - break; + continue; ParallelJobs.push_back(&Candidate); } @@ -332,7 +333,7 @@ tryExecuteParallelOffloadJobGroup(const Driver &D, const ArgList &Args, std::optional<llvm::ThreadPoolStrategy> Strategy = getParallelOffloadJobsStrategy(Args, ParallelJobs.size()); if (!Strategy) - return std::nullopt; + return false; SmallVector<ParallelJobResult, 4> Results(ParallelJobs.size()); llvm::DefaultThreadPool Pool(*Strategy); @@ -359,7 +360,9 @@ tryExecuteParallelOffloadJobGroup(const Driver &D, const ArgList &Args, } } - return ParallelOffloadJobGroupResult{ParallelJobs.size()}; + for (const Command *ParallelJob : ParallelJobs) + Done.insert(ParallelJob); + return true; } void Compilation::ExecuteJobs(const JobList &Jobs, @@ -375,23 +378,22 @@ void Compilation::ExecuteJobs(const JobList &Jobs, !getDriver().IsCLMode() && !getArgs().hasArg(options::OPT_v) && Redirects.empty() && !PostCallback; + // Jobs run as part of a parallel offload group. Group members are not + // necessarily adjacent, so track them and skip them when the loop reaches + // them. + llvm::SmallPtrSet<const Command *, 16> Done; const auto &JobStorage = Jobs.getJobs(); - for (size_t I = 0; I < JobStorage.size();) { + for (size_t I = 0; I < JobStorage.size(); ++I) { const auto &Job = *JobStorage[I]; - if (ActionFailed(&Job.getSource(), FailingCommands)) { - ++I; + if (Done.contains(&Job)) + continue; + if (ActionFailed(&Job.getSource(), FailingCommands)) continue; - } - if (CanRunJobsInParallel) { - if (std::optional<ParallelOffloadJobGroupResult> Result = - tryExecuteParallelOffloadJobGroup(getDriver(), getArgs(), - Redirects, JobStorage, I, - FailingCommands)) { - I += Result->NumJobs; - continue; - } - } + if (CanRunJobsInParallel && + tryExecuteParallelOffloadJobGroup(getDriver(), getArgs(), Redirects, + JobStorage, I, Done, FailingCommands)) + continue; const Command *FailingCommand = nullptr; if (int Res = ExecuteCommand(Job, FailingCommand, LogOnly)) { @@ -400,7 +402,6 @@ void Compilation::ExecuteJobs(const JobList &Jobs, if (TheDriver.IsCLMode()) return; } - ++I; } } diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index b2dbc32f1b89fd..4a064219fbfb34 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -4530,7 +4530,8 @@ Action *Driver::ConstructPhaseAction( static bool isOffloadDeviceCC1JobCandidate(Command &Job) { const Action &Source = Job.getSource(); - if (!isa<CompileJobAction>(Source) && !isa<BackendJobAction>(Source)) + if (!isa<CompileJobAction>(Source) && !isa<BackendJobAction>(Source) && + !isa<AssembleJobAction>(Source)) return false; if (Job.getBoundArch().empty() && !Source.getOffloadingArch().empty()) diff --git a/clang/test/Driver/offload-parallel-device-cc1.cu b/clang/test/Driver/offload-parallel-device-cc1.cu index ff68770926aa17..769e8b822e81a1 100644 --- a/clang/test/Driver/offload-parallel-device-cc1.cu +++ b/clang/test/Driver/offload-parallel-device-cc1.cu @@ -17,6 +17,13 @@ // RUN: --cuda-gpu-arch=sm_70 --cuda-gpu-arch=sm_80 --offload-jobs=2 \ // RUN: --cuda-device-only -S -Werror %s +// Device-only compilation to an object parallelizes the per-target compiles +// even though each is followed by its own device link. +// RUN: %clang -x hip --target=x86_64-unknown-linux-gnu \ +// RUN: -nostdinc -nogpuinc -nohipwrapperinc -nogpulib \ +// RUN: --offload-arch=gfx900 --offload-arch=gfx906 --offload-jobs=2 \ +// RUN: --offload-device-only -O3 -c -Werror %s -o %t/hip-dev.o + // RUN: not %clang -x cuda --target=x86_64-unknown-linux-gnu \ // RUN: -nocudainc -nocudalib \ // RUN: --cuda-gpu-arch=sm_70 --cuda-gpu-arch=sm_80 --offload-jobs=0x4 \ _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
