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

>From d09ba50e82f94ea448f75d68f88405346240a748 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 | 117 +++++++++++++++++--------
 2 files changed, 102 insertions(+), 36 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/Sparc.cpp 
b/clang/lib/CodeGen/Targets/Sparc.cpp
index 25f0009fe7a09..875fadf330ebd 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 53d215f479fae..f322229e20c43 100644
--- a/clang/test/CodeGen/Sparc/sparc-vaarg.c
+++ b/clang/test/CodeGen/Sparc/sparc-vaarg.c
@@ -6,13 +6,13 @@
 // CHECK-SAME: ptr noundef [[ARGS:%.*]]) #[[ATTR0:[0-9]+]] {
 // CHECK-NEXT:  [[ENTRY:.*:]]
 // CHECK-NEXT:    [[ARGS_ADDR:%.*]] = alloca ptr, align 4
-// CHECK-NEXT:    [[VARET:%.*]] = alloca i32, align 4
 // CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
 // CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
-// CHECK-NEXT:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], i32
-// CHECK-NEXT:    store i32 [[TMP1]], ptr [[VARET]], align 4
-// CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[VARET]], align 4
-// CHECK-NEXT:    ret i32 [[TMP2]]
+// 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);
@@ -20,68 +20,98 @@ int get_int(va_list *args) {
 
 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:    [[VARET:%.*]] = alloca i32, align 4
 // CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
 // CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
-// CHECK-NEXT:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], i32
-// CHECK-NEXT:    store i32 [[TMP1]], ptr [[VARET]], align 4
-// CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[VARET]], align 4
-// CHECK-NEXT:    ret i32 [[TMP2]]
+// 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:    [[VARET:%.*]] = alloca i64, align 8
 // CHECK-NEXT:    store ptr [[ARGS]], ptr [[ARGS_ADDR]], align 4
 // CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[ARGS_ADDR]], align 4
-// CHECK-NEXT:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], i64
-// CHECK-NEXT:    store i64 [[TMP1]], ptr [[VARET]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[VARET]], align 8
-// CHECK-NEXT:    ret i64 [[TMP2]]
+// 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 {
+struct Large {
   long long x;
 };
 
-// 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]] {
+// Aggregates are passed indirectly, note how ARGP_CUR is advanced by 4.
+// CHECK-LABEL: define dso_local void @get_struct_long_long(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret([[STRUCT_LARGE:%.*]]) 
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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], ptr
+// 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);
+struct Large get_struct_long_long(va_list *args) {
+ return va_arg(*args, struct Large);
 }
 
+struct Tiny {
+  char x;
+};
+
+// CHECK-LABEL: define dso_local void @get_struct_char(
+// CHECK-SAME: ptr dead_on_unwind noalias writable sret([[STRUCT_TINY:%.*]]) 
align 1 [[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 1 [[AGG_RESULT]], 
ptr align 1 [[TMP1]], i32 1, i1 false)
+// CHECK-NEXT:    ret void
+//
+struct Tiny get_struct_char(va_list *args) {
+ return va_arg(*args, struct Tiny);
+}
+
+// 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:    [[VARET:%.*]] = alloca fp128, align 8
 // 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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], fp128
-// CHECK-NEXT:    store fp128 [[TMP1]], ptr [[VARET]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load fp128, ptr [[VARET]], align 8
+// 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
@@ -93,15 +123,19 @@ long double get_long_double(va_list *args) {
 
 _Complex char complex_char_sink;
 
+// _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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], ptr
+// 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 1
+// 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
@@ -114,19 +148,22 @@ void get_complex_char(va_list *args) {
 
 _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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], ptr
-// CHECK-NEXT:    [[DOTREALP:%.*]] = getelementptr inbounds nuw { i32, i32 }, 
ptr [[TMP1]], i32 0, i32 0
-// CHECK-NEXT:    [[DOTREAL:%.*]] = load i32, ptr [[DOTREALP]], align 4
-// CHECK-NEXT:    [[DOTIMAGP:%.*]] = getelementptr inbounds nuw { i32, i32 }, 
ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT:    [[DOTIMAG:%.*]] = load i32, ptr [[DOTIMAGP]], align 4
-// CHECK-NEXT:    store i32 [[DOTREAL]], ptr @complex_int_sink, align 4
-// CHECK-NEXT:    store i32 [[DOTIMAG]], ptr getelementptr inbounds nuw (i8, 
ptr @complex_int_sink, i32 4), 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) {
@@ -135,13 +172,17 @@ void get_complex_int(va_list *args) {
 
 _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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], ptr
+// 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
@@ -156,13 +197,17 @@ void get_complex_long_long (va_list *args) {
 
 _Complex long double complex_long_double_sink;
 
+// _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:    [[TMP1:%.*]] = va_arg ptr [[TMP0]], ptr
+// 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

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

Reply via email to