[llvm] e4c30c1 - [SPIRV] Extend lowering of variadic functions (#178980)

via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 13 10:22:30 PST 2026


Author: Nick Sarnie
Date: 2026-02-13T18:22:25Z
New Revision: e4c30c15c87fb42432a614805d2e48ef39a8da00

URL: https://github.com/llvm/llvm-project/commit/e4c30c15c87fb42432a614805d2e48ef39a8da00
DIFF: https://github.com/llvm/llvm-project/commit/e4c30c15c87fb42432a614805d2e48ef39a8da00.diff

LOG: [SPIRV] Extend lowering of variadic functions (#178980)

Variadic function lowering for SPIR-V was initially added in
https://github.com/llvm/llvm-project/pull/175076.

However, I tried a full OpenMP offloading example that includes a vararg
call and hit a few issues:

1) The OpenMP Deivce library function `ompx::printf` was incorrectly
being considered a builtin `printf` function that would be handled
specifically by the SPIR-V backend.

The fix here is to remove the `printf` special handling.

2) We were getting an assert in ModuleVerifier saying the LLVM lifetime
intrinsics were being called with an argument that was neither an
`alloca` ptr or `poison`. The problem is the `alloca` was replaced with
a SPIR-V intrinsic `alloca` in `SPIRVPrepareFunctions`, but the lifetime
intrinsic added in `ExpandVariadics` was not lowered to the SPIR-V
lifetime intrinsic since `ExpandVariadics` is run after
`SPIRVPrepareFunctions`,

The fix here is to just run `ExpandVariadics` first.

3) There were `va` intrinsics taking in a `addrspace(4)` pointer that
were not being expanded.

The fix here is to extend `ExpandVariadics` to support expanding `va`
intrinsics with target-specific address spaces.

---------

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
Co-authored-by: Joseph Huber <huberjn at outlook.com>

Added: 
    llvm/test/CodeGen/SPIRV/function/variadics-lowering-namespace-printf.ll

Modified: 
    llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
    llvm/lib/Transforms/IPO/ExpandVariadics.cpp
    llvm/test/CodeGen/SPIRV/llc-pipeline.ll
    llvm/test/CodeGen/SPIRV/printf.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 301fe3d487565..1759b34af3e90 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -178,14 +178,16 @@ void SPIRVPassConfig::addIRPasses() {
 
   TargetPassConfig::addIRPasses();
 
-  addPass(createSPIRVRegularizerPass());
-  addPass(createSPIRVPrepareFunctionsPass(TM));
-  addPass(createSPIRVPrepareGlobalsPass());
-
   // Variadic function calls aren't supported in shader code.
+  // This needs to come before SPIRVPrepareFunctions because this
+  // may introduce intrinsic calls.
   if (!TM.getSubtargetImpl()->isShader()) {
     addPass(createExpandVariadicsPass(ExpandVariadicsMode::Lowering));
   }
+
+  addPass(createSPIRVRegularizerPass());
+  addPass(createSPIRVPrepareFunctionsPass(TM));
+  addPass(createSPIRVPrepareGlobalsPass());
 }
 
 void SPIRVPassConfig::addISelPrepare() {

diff  --git a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
index a908db0d1830a..97b95cce2a407 100644
--- a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
+++ b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
@@ -53,6 +53,7 @@
 
 #include "llvm/Transforms/IPO/ExpandVariadics.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/Demangle/Demangle.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
@@ -124,13 +125,16 @@ class VariadicABIInfo {
   };
   virtual VAArgSlotInfo slotInfo(const DataLayout &DL, Type *Parameter) = 0;
 
-  // Per-target overrides of special symbols.
-  virtual bool ignoreFunction(Function *F) { return false; }
-
   // Targets implemented so far all have the same trivial lowering for these
   bool vaEndIsNop() { return true; }
   bool vaCopyIsMemcpy() { return true; }
 
+  // Any additional address spaces used in va intrinsics that should be
+  // expanded.
+  virtual SmallVector<unsigned> getTargetSpecificVaIntrinAddrSpaces() const {
+    return {};
+  }
+
   virtual ~VariadicABIInfo() = default;
 };
 
@@ -238,9 +242,6 @@ class ExpandVariadics : public ModulePass {
         F->hasFnAttribute(Attribute::Naked))
       return false;
 
-    if (ABI->ignoreFunction(F))
-      return false;
-
     if (!isValidCallingConv(F))
       return false;
 
@@ -364,13 +365,21 @@ bool ExpandVariadics::runOnModule(Module &M) {
   // variadic functions have also been replaced.
 
   {
-    // 0 and AllocaAddrSpace are sufficient for the targets implemented so far
     unsigned Addrspace = 0;
     Changed |= expandVAIntrinsicUsersWithAddrspace(M, Builder, Addrspace);
 
     Addrspace = DL.getAllocaAddrSpace();
     if (Addrspace != 0)
       Changed |= expandVAIntrinsicUsersWithAddrspace(M, Builder, Addrspace);
+
+    // Process any addrspaces targets declare to be important.
+    const SmallVector<unsigned> &TargetASVec =
+        ABI->getTargetSpecificVaIntrinAddrSpaces();
+    for (unsigned TargetAS : TargetASVec) {
+      if (TargetAS == 0 || TargetAS == DL.getAllocaAddrSpace())
+        continue;
+      Changed |= expandVAIntrinsicUsersWithAddrspace(M, Builder, TargetAS);
+    }
   }
 
   if (Mode != ExpandVariadicsMode::Lowering)
@@ -618,9 +627,6 @@ bool ExpandVariadics::expandCall(Module &M, IRBuilder<> &Builder, CallBase *CB,
   bool Changed = false;
   const DataLayout &DL = M.getDataLayout();
 
-  if (ABI->ignoreFunction(CB->getCalledFunction()))
-    return Changed;
-
   if (!expansionApplicableToFunctionCall(CB)) {
     if (rewriteABI())
       report_fatal_error("Cannot lower callbase instruction");
@@ -978,10 +984,9 @@ struct SPIRV final : public VariadicABIInfo {
     return {A, false};
   }
 
-  // The SPIR-V backend has special handling for SPIR-V mangled printf
-  // functions.
-  bool ignoreFunction(Function *F) override {
-    return F->getName().starts_with('_') && F->getName().contains("printf");
+  // We will likely see va intrinsics in the generic addrspace (4).
+  SmallVector<unsigned> getTargetSpecificVaIntrinAddrSpaces() const override {
+    return {4};
   }
 };
 
@@ -1041,6 +1046,7 @@ std::unique_ptr<VariadicABIInfo> VariadicABIInfo::create(const Triple &T) {
   }
 
   case Triple::spirv:
+  case Triple::spirv32:
   case Triple::spirv64: {
     return std::make_unique<SPIRV>();
   }

diff  --git a/llvm/test/CodeGen/SPIRV/function/variadics-lowering-namespace-printf.ll b/llvm/test/CodeGen/SPIRV/function/variadics-lowering-namespace-printf.ll
new file mode 100644
index 0000000000000..6e2fda6a7e5eb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/function/variadics-lowering-namespace-printf.ll
@@ -0,0 +1,41 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_INTEL_function_pointers < %s 2>&1 | FileCheck %s
+
+; CHECK: OpName [[MAIN:%.*]] "main"
+; CHECK: [[MAIN]] = OpFunction
+; CHECK-NOT: OPFunctionEnd
+; CHECK: OpLifetimeStart
+; CHECK-NOT: OPFunctionEnd
+; CHECK: OpLifetimeStop
+; CHECK: OpFunctionEnd
+
+ at .str = private unnamed_addr addrspace(1) constant [3 x i8] c"%s\00", align 1
+ at .str.1 = private unnamed_addr addrspace(1) constant [4 x i8] c"hey\00", align 1
+
+declare spir_func noundef i32 @_Z7vprintfPKcz(ptr addrspace(4) noundef %0, ...) addrspace(9)
+
+define spir_func noundef i32 @_ZN4ompx6printfEPKcz(ptr addrspace(4) noundef %Format, ...) addrspace(9) {
+entry:
+  %retval = alloca i32, align 4
+  %Format.addr = alloca ptr addrspace(4), align 8
+  %vlist = alloca ptr addrspace(4), align 8
+  %retval.ascast = addrspacecast ptr %retval to ptr addrspace(4)
+  %Format.addr.ascast = addrspacecast ptr %Format.addr to ptr addrspace(4)
+  %vlist.ascast = addrspacecast ptr %vlist to ptr addrspace(4)
+  store ptr addrspace(4) %Format, ptr addrspace(4) %Format.addr.ascast, align 8
+  call addrspace(9) void @llvm.va_start.p4(ptr addrspace(4) %vlist.ascast)
+  %0 = load ptr addrspace(4), ptr addrspace(4) %Format.addr.ascast, align 8
+  %1 = load ptr addrspace(4), ptr addrspace(4) %vlist.ascast, align 8
+  %call = call spir_func noundef addrspace(9) i32 (ptr addrspace(4), ...) @_Z7vprintfPKcz(ptr addrspace(4) noundef %0, ptr addrspace(4) noundef %1)
+  ret i32 %call
+}
+
+declare void @llvm.va_start.p4(ptr addrspace(4)) addrspace(9)
+
+define noundef i32 @main() addrspace(9) {
+entry:
+  %retval = alloca i32, align 4
+  %retval.ascast = addrspacecast ptr %retval to ptr addrspace(4)
+  store i32 0, ptr addrspace(4) %retval.ascast, align 4
+  %call = call spir_func noundef addrspace(9) i32 (ptr addrspace(4), ...) @_ZN4ompx6printfEPKcz(ptr addrspace(4) noundef addrspacecast (ptr addrspace(1) @.str to ptr addrspace(4)), ptr addrspace(4) noundef addrspacecast (ptr addrspace(1) @.str.1 to ptr addrspace(4))) #5
+  ret i32 0
+}

diff  --git a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
index 70ae7081ad5d5..a4fcd9e6d9223 100644
--- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
+++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
@@ -31,10 +31,11 @@
 ; SPIRV-O0-NEXT:      Instrument function entry/exit with calls to e.g. mcount() (post inlining)
 ; SPIRV-O0-NEXT:      Scalarize Masked Memory Intrinsics
 ; SPIRV-O0-NEXT:      Expand reduction intrinsics
+; SPIRV-O0-NEXT:    Expand variadic functions
+; SPIRV-O0-NEXT:    FunctionPass Manager
 ; SPIRV-O0-NEXT:      SPIR-V Regularizer
 ; SPIRV-O0-NEXT:    SPIRV prepare functions
 ; SPIRV-O0-NEXT:    SPIRV prepare global variables
-; SPIRV-O0-NEXT:    Expand variadic functions
 ; SPIRV-O0-NEXT:    FunctionPass Manager
 ; SPIRV-O0-NEXT:      Lower invoke and unwind, for unwindless code generators
 ; SPIRV-O0-NEXT:      Remove unreachable blocks from the CFG
@@ -136,10 +137,11 @@
 ; SPIRV-Opt-NEXT:      Instrument function entry/exit with calls to e.g. mcount() (post inlining)
 ; SPIRV-Opt-NEXT:      Scalarize Masked Memory Intrinsics
 ; SPIRV-Opt-NEXT:      Expand reduction intrinsics
+; SPIRV-Opt-NEXT:    Expand variadic functions
+; SPIRV-Opt-NEXT:    FunctionPass Manager
 ; SPIRV-Opt-NEXT:      SPIR-V Regularizer
 ; SPIRV-Opt-NEXT:    SPIRV prepare functions
 ; SPIRV-Opt-NEXT:    SPIRV prepare global variables
-; SPIRV-Opt-NEXT:    Expand variadic functions
 ; SPIRV-Opt-NEXT:    FunctionPass Manager
 ; SPIRV-Opt-NEXT:      Dominator Tree Construction
 ; SPIRV-Opt-NEXT:      Natural Loop Information

diff  --git a/llvm/test/CodeGen/SPIRV/printf.ll b/llvm/test/CodeGen/SPIRV/printf.ll
index 483fc1f244e57..f2747901c54e0 100644
--- a/llvm/test/CodeGen/SPIRV/printf.ll
+++ b/llvm/test/CodeGen/SPIRV/printf.ll
@@ -6,33 +6,35 @@
 
 ; CHECK: %[[#ExtImport:]] = OpExtInstImport "OpenCL.std"
 ; CHECK: %[[#Char:]] = OpTypeInt 8 0
-; CHECK: %[[#CharPtr:]] = OpTypePointer UniformConstant %[[#Char]]
+; CHECK: %[[#ConstCharPtr:]] = OpTypePointer UniformConstant %[[#Char]]
+; CHECK: %[[#VarargStruct:]] = OpTypeStruct %[[#Char]]
+; CHECK: %[[#VarargStructPtr:]] = OpTypePointer Function %[[#VarargStruct]]
+; CHECK: %[[#CharPtr:]] = OpTypePointer Function %[[#Char]]
+; CHECK: %[[#IntConst:]] = OpConstant %[[#Char]] 97
 ; CHECK: %[[#GV:]] = OpVariable %[[#]] UniformConstant %[[#]]
 ; CHECK: OpFunction
-; CHECK: %[[#Arg1:]] = OpFunctionParameter
-; CHECK: %[[#Arg2:]] = OpFunctionParameter
-; CHECK: %[[#CastedGV:]] = OpBitcast %[[#CharPtr]] %[[#GV]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#CastedGV]] %[[#ArgConst:]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#CastedGV]] %[[#ArgConst]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#Arg1]] %[[#ArgConst:]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#Arg1]] %[[#ArgConst]]
-; CHECK-NEXT: %[[#CastedArg2:]] = OpBitcast %[[#CharPtr]] %[[#Arg2]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#CastedArg2]] %[[#ArgConst]]
-; CHECK-NEXT: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#CastedArg2]] %[[#ArgConst]]
+; CHECK: %[[#Arg:]] = OpFunctionParameter
+; CHECK: %[[#VarargBuffer1:]] = OpVariable %[[#VarargStructPtr]] Function
+; CHECK: %[[#VarargBuffer2:]] = OpVariable %[[#VarargStructPtr]] Function
+; CHECK: %[[#CastedBuffer1:]] = OpBitcast %[[#CharPtr]] %[[#VarargBuffer1]]
+; CHECK: %[[#GEP1:]] = OpInBoundsPtrAccessChain %[[#CharPtr]] %[[#VarargBuffer1]]
+; CHECK: OpStore %[[#GEP1]] %[[#IntConst]] Aligned 1
+; CHECK: %[[#CastedGV:]] = OpBitcast %[[#ConstCharPtr]] %[[#GV]]
+; CHECK: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#CastedGV]] %[[#CastedBuffer1:]]
+; CHECK: %[[#CastedBuffer2:]] = OpBitcast %[[#CharPtr]] %[[#VarargBuffer2]]
+; CHECK: %[[#GEP2:]] = OpInBoundsPtrAccessChain %[[#CharPtr]] %[[#VarargBuffer2]]
+; CHECK: OpStore %[[#GEP2]] %[[#IntConst]] Aligned 1
+; CHECK: OpExtInst %[[#]] %[[#ExtImport]] printf %[[#Arg]] %[[#CastedBuffer2:]]
 ; CHECK: OpFunctionEnd
 
 %struct = type { [6 x i8] }
 
 @FmtStr = internal addrspace(2) constant [6 x i8] c"c=%c\0A\00", align 1
 
-define spir_kernel void @foo(ptr addrspace(2) %_arg_fmt1, ptr addrspace(2) byval(%struct) %_arg_fmt2) {
+define spir_kernel void @foo(ptr addrspace(2) %_arg_fmt) {
 entry:
   %r1 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z6printfPU3AS2Kcz(ptr addrspace(2) @FmtStr, i8 signext 97)
-  %r2 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z18__spirv_ocl_printfPU3AS2Kcz(ptr addrspace(2) @FmtStr, i8 signext 97)
-  %r3 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z6printfPU3AS2Kcz(ptr addrspace(2) %_arg_fmt1, i8 signext 97)
-  %r4 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z18__spirv_ocl_printfPU3AS2Kcz(ptr addrspace(2) %_arg_fmt1, i8 signext 97)
-  %r5 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z6printfPU3AS2Kcz(ptr addrspace(2) %_arg_fmt2, i8 signext 97)
-  %r6 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z18__spirv_ocl_printfPU3AS2Kcz(ptr addrspace(2) %_arg_fmt2, i8 signext 97)
+  %r2 = tail call spir_func i32 (ptr addrspace(2), ...) @_Z18__spirv_ocl_printfPU3AS2Kcz(ptr addrspace(2) %_arg_fmt, i8 signext 97)
   ret void
 }
 


        


More information about the llvm-commits mailing list