https://github.com/folkertdev created 
https://github.com/llvm/llvm-project/pull/214981

Fixes https://github.com/llvm/llvm-project/issues/214977

The LLVM `va_arg` is extremely broken on many target (e.g. 
https://github.com/llvm/llvm-project/issues/141361). Instead the construct 
should be lowered in clang, which has more ABI information available. Sparc64 
already does this, this PR does the same for Sparc. That does mean that each 
frontend has to replicate this logic, unfortunate but fine.

The LLVM `va_arg` for sparc unconditionally reads arguments as direct. That is 
inconsistent even with clang's own logic for passing arguments, and diverges 
from GCC. Using the standard `emitVoidPtrVAArg` helper in clang makes it much 
easier to handle this correctly.

For values of size 8 that are passed directly, codegen is a bit suboptimal at 
the moment (see https://github.com/llvm/llvm-project/issues/214594). That seems 
fine though, and will probably be fixed soon. 

>From e9667ec8e225be34fa2fa346c94569394a6eef96 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <[email protected]>
Date: Sat, 8 Aug 2026 17:39:12 +0200
Subject: [PATCH] [Sparc][clang] implement `va_arg` in the clang frontend

---
 clang/lib/CodeGen/Targets/Sparc.cpp    |  21 +++
 clang/test/CodeGen/Sparc/sparc-vaarg.c | 202 ++++++++++++++++++++++---
 2 files changed, 205 insertions(+), 18 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/Sparc.cpp 
b/clang/lib/CodeGen/Targets/Sparc.cpp
index e23771653e251..4f765443c73c1 100644
--- a/clang/lib/CodeGen/Targets/Sparc.cpp
+++ b/clang/lib/CodeGen/Targets/Sparc.cpp
@@ -34,6 +34,8 @@ class SparcV8ABIInfo : public DefaultABIInfo {
   ABIArgInfo classifyComplexType(const ComplexType *Ty, bool IsRet) const;
   ABIArgInfo classifyReturnType(QualType RetTy) const;
   ABIArgInfo classifyArgumentType(QualType Ty) const;
+  RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty,
+                   AggValueSlot Slot) const override;
   void computeInfo(CGFunctionInfo &FI) const override;
 };
 } // end anonymous namespace
@@ -94,6 +96,25 @@ void SparcV8ABIInfo::computeInfo(CGFunctionInfo &FI) const {
     Arg.info = classifyArgumentType(Arg.type);
 }
 
+RValue SparcV8ABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
+                                 QualType Ty, AggValueSlot Slot) const {
+  CharUnits SlotSize = CharUnits::fromQuantity(4);
+  auto TInfo = getContext().getTypeInfoInChars(Ty);
+
+  // E.g. long double, larger _Complex and aggregate values are indirect.
+  bool IsIndirect = classifyArgumentType(Ty).isIndirect();
+
+  // An alignment higher than the slot size is not respected.
+  bool AllowHigherAlign = false;
+
+  // Force values smaller than a slot (e.g. _Complex char)
+  // into the right-most bytes.
+  bool ForceRightAdjust = true;
+
+  return emitVoidPtrVAArg(CGF, VAListAddr, Ty, IsIndirect, TInfo, SlotSize,
+                          AllowHigherAlign, Slot, ForceRightAdjust);
+}
+
 namespace {
 class SparcV8TargetCodeGenInfo : public TargetCodeGenInfo {
 public:
diff --git a/clang/test/CodeGen/Sparc/sparc-vaarg.c 
b/clang/test/CodeGen/Sparc/sparc-vaarg.c
index b78949e09350c..e1d2e2e3c8ec2 100644
--- a/clang/test/CodeGen/Sparc/sparc-vaarg.c
+++ b/clang/test/CodeGen/Sparc/sparc-vaarg.c
@@ -1,34 +1,200 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 6
 // RUN: %clang_cc1 -triple sparc -emit-llvm -o - %s | FileCheck %s
 #include <stdarg.h>
 
-// CHECK-LABEL: define{{.*}} i32 @get_int
-// CHECK: [[RESULT:%[a-z_0-9]+]] = va_arg {{.*}}, i32{{$}}
-// CHECK: store i32 [[RESULT]], ptr [[LOC:%[a-z_0-9]+]]
-// CHECK: [[RESULT2:%[a-z_0-9]+]] = load i32, ptr [[LOC]]
-// CHECK: ret i32 [[RESULT2]]
+// CHECK-LABEL: define dso_local i32 @get_int(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    ret i32 [[TMP1]]
+//
 int get_int(va_list *args) {
   return va_arg(*args, int);
 }
 
+enum RGB { R = 1, G = 2, B = 3 };
+
+// Enums are passed like integers.
+// CHECK-LABEL: define dso_local i32 @get_enum(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    ret i32 [[TMP1]]
+//
+enum RGB get_enum(va_list *args) {
+  return va_arg(*args, enum RGB);
+}
+
+// long long is passed directly, note how ARGP_CUR is advanced by 8.
+// The read is under-aligned however, the ARGP_CUR is only aligned to a slot.
+// CHECK-LABEL: define dso_local i64 @get_long_long(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 8
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load i64, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    ret i64 [[TMP1]]
+//
+long long get_long_long(va_list *args) {
+  return va_arg(*args, long long);
+}
+
 struct Foo {
-  int x;
+  long long x;
 };
 
-struct Foo dest;
+// Aggregates are passed indirectly, note how ARGP_CUR is advanced by 4.
+// CHECK-LABEL: define dso_local void @get_struct(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret([[STRUCT_FOO:%.*]]) 
align 8 [[AGG_RESULT:%.*]], ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i32(ptr align 8 [[AGG_RESULT]], 
ptr align 8 [[TMP1]], i32 8, i1 false)
+// CHECK-NEXT:    ret void
+//
+struct Foo get_struct(va_list *args) {
+ return va_arg(*args, struct Foo);
+}
+
+// long double is passed indirectly, note how ARGP_CUR is advanced by 4.
+// CHECK-LABEL: define dso_local void @get_long_double(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret(fp128) align 8 
[[AGG_RESULT:%.*]], ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    [[TMP2:%.*]] = load fp128, ptr [[TMP1]], align 8
+// CHECK-NEXT:    store fp128 [[TMP2]], ptr [[AGG_RESULT]], align 8
+// CHECK-NEXT:    [[TMP3:%.*]] = load fp128, ptr [[AGG_RESULT]], align 8
+// CHECK-NEXT:    store fp128 [[TMP3]], ptr [[AGG_RESULT]], align 8
+// CHECK-NEXT:    ret void
+//
+long double get_long_double(va_list *args) {
+  return va_arg(*args, long double);
+}
+
+_Complex char complex_char_sink;
 
-// CHECK-LABEL: define{{.*}} void @get_struct
-// CHECK: [[RESULT:%[a-z_0-9]+]] = va_arg {{.*}}, ptr{{$}}
-// CHECK: call void @llvm.memcpy{{.*}}@dest{{.*}}, ptr align {{[0-9]+}} 
[[RESULT]]
-void get_struct(va_list *args) {
- dest = va_arg(*args, struct Foo);
+// _Complex char is passed in the right-most bytes of the slot, note the 
getelementptr with a value of 2. 
+// CHECK-LABEL: define dso_local void @get_complex_char(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR]], 
i32 2
+// CHECK-NEXT:    [[DOTREALP:%.*]] = getelementptr inbounds nuw { i8, i8 }, 
ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT:    [[DOTREAL:%.*]] = load i8, ptr [[DOTREALP]], align 2
+// CHECK-NEXT:    [[DOTIMAGP:%.*]] = getelementptr inbounds nuw { i8, i8 }, 
ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT:    [[DOTIMAG:%.*]] = load i8, ptr [[DOTIMAGP]], align 1
+// CHECK-NEXT:    store i8 [[DOTREAL]], ptr @complex_char_sink, align 1
+// CHECK-NEXT:    store i8 [[DOTIMAG]], ptr getelementptr inbounds nuw (i8, 
ptr @complex_char_sink, i32 1), align 1
+// CHECK-NEXT:    ret void
+//
+void get_complex_char(va_list *args) {
+  complex_char_sink = va_arg(*args, _Complex char);
 }
 
-enum E { Foo_one = 1 };
+_Complex int complex_int_sink;
+
+// _Complex int is passed directly, note how ARGP_CUR is advanced by 8.
+// CHECK-LABEL: define dso_local void @get_complex_int(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 8
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_CUR_REALP:%.*]] = getelementptr inbounds nuw { i32, 
i32 }, ptr [[ARGP_CUR]], i32 0, i32 0
+// CHECK-NEXT:    [[ARGP_CUR_REAL:%.*]] = load i32, ptr [[ARGP_CUR_REALP]], 
align 4
+// CHECK-NEXT:    [[ARGP_CUR_IMAGP:%.*]] = getelementptr inbounds nuw { i32, 
i32 }, ptr [[ARGP_CUR]], i32 0, i32 1
+// CHECK-NEXT:    [[ARGP_CUR_IMAG:%.*]] = load i32, ptr [[ARGP_CUR_IMAGP]], 
align 4
+// CHECK-NEXT:    store i32 [[ARGP_CUR_REAL]], ptr @complex_int_sink, align 4
+// CHECK-NEXT:    store i32 [[ARGP_CUR_IMAG]], ptr getelementptr inbounds nuw 
(i8, ptr @complex_int_sink, i32 4), align 4
+// CHECK-NEXT:    ret void
+//
+void get_complex_int(va_list *args) {
+  complex_int_sink = va_arg(*args, _Complex int);
+}
+
+_Complex long long complex_long_long_sink;
+
+// _Complex long long is passed indirectly, note how ARGP_CUR is advanced by 4.
+// CHECK-LABEL: define dso_local void @get_complex_long_long(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    [[DOTREALP:%.*]] = getelementptr inbounds nuw { i64, i64 }, 
ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT:    [[DOTREAL:%.*]] = load i64, ptr [[DOTREALP]], align 8
+// CHECK-NEXT:    [[DOTIMAGP:%.*]] = getelementptr inbounds nuw { i64, i64 }, 
ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT:    [[DOTIMAG:%.*]] = load i64, ptr [[DOTIMAGP]], align 8
+// CHECK-NEXT:    store i64 [[DOTREAL]], ptr @complex_long_long_sink, align 8
+// CHECK-NEXT:    store i64 [[DOTIMAG]], ptr getelementptr inbounds nuw (i8, 
ptr @complex_long_long_sink, i32 8), align 8
+// CHECK-NEXT:    ret void
+//
+void get_complex_long_long (va_list *args) {
+  complex_long_long_sink = va_arg(*args, _Complex long long );
+}
 
-enum E enum_dest;
+_Complex long double complex_long_double_sink;
 
-// CHECK-LABEL: define{{.*}} void @get_enum
-// CHECK: va_arg ptr {{.*}}, i32
-void get_enum(va_list *args) {
-  enum_dest = va_arg(*args, enum E);
+// _Complex long double is passed indirectly, note how ARGP_CUR is advanced by 
4.
+// CHECK-LABEL: define dso_local void @get_complex_long_double(
+// CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
+// CHECK-NEXT:    [[ARGP_CUR:%.*]] = load ptr, ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr 
[[ARGP_CUR]], i32 4
+// CHECK-NEXT:    store ptr [[ARGP_NEXT]], ptr [[TMP0]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT:    [[DOTREALP:%.*]] = getelementptr inbounds nuw { fp128, fp128 
}, ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT:    [[DOTREAL:%.*]] = load fp128, ptr [[DOTREALP]], align 8
+// CHECK-NEXT:    [[DOTIMAGP:%.*]] = getelementptr inbounds nuw { fp128, fp128 
}, ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT:    [[DOTIMAG:%.*]] = load fp128, ptr [[DOTIMAGP]], align 8
+// CHECK-NEXT:    store fp128 [[DOTREAL]], ptr @complex_long_double_sink, 
align 8
+// CHECK-NEXT:    store fp128 [[DOTIMAG]], ptr getelementptr inbounds nuw (i8, 
ptr @complex_long_double_sink, i32 16), align 8
+// CHECK-NEXT:    ret void
+//
+void get_complex_long_double(va_list *args) {
+  complex_long_double_sink = va_arg(*args, _Complex long double);
 }

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

Reply via email to