[Sparc][clang] implement va_arg in the clang frontend
Project: llvm/llvm-project | Language: C++ | Concepts: refactoring, ABI, variadic arguments, code generation | Difficulty: senior
Link: https://github.com/llvm/llvm-project/pull/214981
The problem
On the Sparc target, the LLVM IR-level va_arg instruction was broken and did not correctly implement the ABI for variadic argument access. It always read arguments as if they were passed directly, which is inconsistent with how arguments are actually passed (sometimes indirectly) and diverges from GCC's behavior. This led to incorrect code generation for variadic functions, especially for types like aggregates, long double, and complex numbers.
Why it's non-trivial
A naive fix would just tweak the LLVM IR or patch up va_arg handling in the backend. However, the correct way to handle variadic arguments depends on ABI details (e.g., when to pass by pointer vs. value, alignment, slot size, etc.), which are only fully known in the frontend (Clang), not at the LLVM IR level. Each target's ABI can be subtle, so the logic must be replicated in the frontend with full type and ABI information.
The approach
The PR implements target-specific lowering of va_arg for Sparc in the Clang frontend, similar to what was already done for Sparc64. It overrides the EmitVAArg method in the Sparc ABI info class and uses Clang's emitVoidPtrVAArg helper to handle all ABI-specific details.
Key code changes:
-
Override
EmitVAArgin the ABI info class:+ RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty, + AggValueSlot Slot) const override;This declares that SparcV8ABIInfo will provide its own logic for emitting code to fetch variadic arguments.
-
Implement the ABI-specific logic in
EmitVAArg: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); }This code determines, per the Sparc ABI:
- Whether the argument is passed directly or indirectly (
IsIndirect). - That alignment above the slot size (4 bytes) is ignored.
- That small values are right-adjusted in their slot (important for types like
_Complex char). - It then delegates to Clang's generic helper, which generates the correct IR for these ABI rules.
- Whether the argument is passed directly or indirectly (
-
Update and expand tests to show correct codegen:
For example, for
int, the old code used the LLVM IRva_arginstruction:-// 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]]The new code shows explicit pointer arithmetic and loads, matching the ABI:
+// 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]]Similar updates are made for aggregates, enums, long double, and complex types, showing correct pointer handling and alignment.
From the review thread
No notable review discussion on this PR.
Takeaways
- Correct handling of variadic arguments requires frontend ABI knowledge. The frontend must lower
va_argaccording to the target's ABI; relying on generic IR-level instructions can lead to subtle and severe bugs. - Use shared helpers for ABI-specific codegen logic. Clang's
emitVoidPtrVAArgencapsulates common patterns, reducing duplication and the risk of inconsistencies across targets. - Tests should check ABI details, not just type correctness. Good tests assert not only that the right type is returned, but that pointer arithmetic, alignment, and indirect/direct accesses match the ABI specification.