[clang] [clang] Fix /Fo output path collision for HIP multi-arch compilation (PR #215663)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 11 13:53:59 PDT 2026
https://github.com/ivarusic-amd created https://github.com/llvm/llvm-project/pull/215663
When compiling HIP for multiple offload archs with clang-cl's /Fo (e.g. --offload-arch=gfx900 --offload-arch=gfx90a:xnack+
/Fo:out.obj), every arch's intermediate device compile job wrote to the same literal /Fo path, so each arch silently overwrote theprevious one's object before packaging so only one arch's code ended up in the final binary.
Root cause: in GetNamedOutputPath(), the "output to a temp file" gate falls through to the literal user-specified path for -o only when AtTopLevel is true; intermediate (non-top-level) -o actions get a genuinely unique temp path. /Fo was unconditionally excluded from this gate regardless of AtTopLevel, so intermediate offloading-device sub-actions used the literal /Fo path directly instead.
Fix: narrow the /Fo exclusion so it only bypasses the temp-file route for non-offloading (host) actions - offloading device
sub-actions under /Fo now fall through to CreateTempOutputPath, matching how -o already behaves. Final top-level output still honors the literal /Fo path.
Added clang/test/Driver/hip-windows-multiarch-fo.hip, verified to fail before the fix
>From 7038bd3280972f110caa980b3d00ca6f5e8f6aac Mon Sep 17 00:00:00 2001
From: ivarusic-amd <ivarusic at amd.com>
Date: Tue, 11 Aug 2026 13:26:37 -0700
Subject: [PATCH] [clang] Fix /Fo output path collision for HIP multi-arch
compilation
---
clang/lib/Driver/Driver.cpp | 9 ++++++++-
.../test/Driver/hip-windows-multiarch-fo.hip | 20 +++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Driver/hip-windows-multiarch-fo.hip
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index d4719f37e5b4d..158d9fb1beae6 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -6692,8 +6692,15 @@ const char *Driver::GetNamedOutputPath(Compilation &C, const JobAction &JA,
}
// Output to a temporary file?
+ // /Fo is normally exempted from this so that its literal path is honored,
+ // but offloading device sub-actions (e.g. each arch of a multi-arch HIP
+ // compile) are not the final output and must still get a unique path here,
+ // the same way they would under -o; otherwise every arch collides on the
+ // single /Fo path.
if ((!AtTopLevel && !isSaveTempsEnabled() &&
- !C.getArgs().hasArg(options::OPT__SLASH_Fo)) ||
+ !(C.getArgs().hasArg(options::OPT__SLASH_Fo) &&
+ (JA.getOffloadingDeviceKind() == Action::OFK_None ||
+ JA.getOffloadingDeviceKind() == Action::OFK_Host))) ||
CCGenDiagnostics) {
StringRef Name = llvm::sys::path::filename(BaseInput);
return CreateTempOutputPath(Name.split('.').first);
diff --git a/clang/test/Driver/hip-windows-multiarch-fo.hip b/clang/test/Driver/hip-windows-multiarch-fo.hip
new file mode 100644
index 0000000000000..b997f233427fc
--- /dev/null
+++ b/clang/test/Driver/hip-windows-multiarch-fo.hip
@@ -0,0 +1,20 @@
+// REQUIRES: system-windows
+// REQUIRES: x86-registered-target
+// REQUIRES: amdgpu-registered-target
+
+// Each offload-arch device compile job must get its own unique output path
+// under /Fo, the same way -o already does via GetNamedOutputPath's temp-file
+// gate. Otherwise every arch's device object collides on the single literal
+// /Fo path and silently overwrites the others before packaging.
+
+// RUN: %clang_cl -### --target=x86_64-pc-windows-msvc -x hip \
+// RUN: --offload-arch=gfx900 --offload-arch=gfx90a:xnack+ \
+// RUN: -nogpuinc -nogpulib -Foout.obj -- %s 2>&1 | FileCheck %s
+
+// CHECK: "-target-cpu" "gfx900"{{.*}}"-o" "{{.*}}gfx900-{{[^"]*}}"
+// CHECK-NOT: "-o" "out.obj"
+// CHECK: "-target-cpu" "gfx90a" "-mxnack"{{.*}}"-o" "{{.*}}gfx90a at xnack+-{{[^"]*}}"
+// CHECK-NOT: "-o" "out.obj"
+// CHECK: "-o" "out.obj"
+
+void main() {}
More information about the cfe-commits
mailing list