unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
refactoring

[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:

  1. Override EmitVAArg in 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.

  2. 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.
  3. Update and expand tests to show correct codegen:

    For example, for int, the old code used the LLVM IR va_arg instruction:

    -// 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_arg according 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 emitVoidPtrVAArg encapsulates 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.