https://github.com/yxsamliu updated 
https://github.com/llvm/llvm-project/pull/233184

>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 1/2] [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 \

>From 6e02e90fb3a1f7884abd243499d49006500bf191 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <[email protected]>
Date: Sun, 18 Oct 2026 14:20:12 -0400
Subject: [PATCH 2/2] [clang][Driver] Drop unused HIP wrapper flag from
 device-only test

---
 clang/test/Driver/offload-parallel-device-cc1.cu | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/Driver/offload-parallel-device-cc1.cu 
b/clang/test/Driver/offload-parallel-device-cc1.cu
index 769e8b822e81a1..b44d9dfe4dd97a 100644
--- a/clang/test/Driver/offload-parallel-device-cc1.cu
+++ b/clang/test/Driver/offload-parallel-device-cc1.cu
@@ -20,7 +20,7 @@
 // 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:   -nostdinc -nogpuinc -nogpulib \
 // RUN:   --offload-arch=gfx900 --offload-arch=gfx906 --offload-jobs=2 \
 // RUN:   --offload-device-only -O3 -c -Werror %s -o %t/hip-dev.o
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to