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

Joseph Huber via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 13 09:25:22 PST 2026


https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/178980

>From 6fa0681bf8fd47cfe706c5fd795b43c36df2832f Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Fri, 30 Jan 2026 14:09:44 -0800
Subject: [PATCH 1/2] [SPIRV] Extend lowering of variadic functions

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp  | 10 +++--
 llvm/lib/Transforms/IPO/ExpandVariadics.cpp   | 27 +++++++++++-
 .../variadics-lowering-namespace-printf.ll    | 41 +++++++++++++++++++
 llvm/test/CodeGen/SPIRV/llc-pipeline.ll       |  6 ++-
 4 files changed, 76 insertions(+), 8 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/function/variadics-lowering-namespace-printf.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 2d70972d6fbdb..35edfa1846421 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -176,14 +176,16 @@ TargetPassConfig *SPIRVTargetMachine::createPassConfig(PassManagerBase &PM) {
 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..78e402e476332 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"
@@ -131,6 +132,12 @@ class VariadicABIInfo {
   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;
 };
 
@@ -364,13 +371,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)
@@ -981,7 +996,15 @@ struct SPIRV final : public VariadicABIInfo {
   // 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");
+    StringRef DemangledName =
+        llvm::itaniumDemangle(F->getName(), /*ParseArgs=*/false);
+    return DemangledName == "printf" ||
+           (DemangledName.starts_with('_') && DemangledName.contains("printf"));
+  }
+
+  // We will likely see va intrinsics in the generic addrspace (4).
+  SmallVector<unsigned> getTargetSpecificVaIntrinAddrSpaces() const override {
+    return {4};
   }
 };
 
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 2f8b9decaaba3..1c17dfd6df28f 100644
--- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
+++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
@@ -30,10 +30,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
@@ -134,10 +135,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

>From 5fb133e6b0609afdfb4dc5e99048c3106067a5a9 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Mon, 2 Feb 2026 11:49:44 -0800
Subject: [PATCH 2/2] remove printf special handling

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 llvm/lib/Transforms/IPO/ExpandVariadics.cpp | 19 +----------
 llvm/test/CodeGen/SPIRV/printf.ll           | 36 +++++++++++----------
 2 files changed, 20 insertions(+), 35 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
index 78e402e476332..97b95cce2a407 100644
--- a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
+++ b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
@@ -125,9 +125,6 @@ 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; }
@@ -245,9 +242,6 @@ class ExpandVariadics : public ModulePass {
         F->hasFnAttribute(Attribute::Naked))
       return false;
 
-    if (ABI->ignoreFunction(F))
-      return false;
-
     if (!isValidCallingConv(F))
       return false;
 
@@ -633,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");
@@ -993,15 +984,6 @@ 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 {
-    StringRef DemangledName =
-        llvm::itaniumDemangle(F->getName(), /*ParseArgs=*/false);
-    return DemangledName == "printf" ||
-           (DemangledName.starts_with('_') && DemangledName.contains("printf"));
-  }
-
   // We will likely see va intrinsics in the generic addrspace (4).
   SmallVector<unsigned> getTargetSpecificVaIntrinAddrSpaces() const override {
     return {4};
@@ -1064,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/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