Skip to content

Commit 961715a

Browse files
committed
Implement va_arg for Hexagon Linux musl targets
Implements proper variadic argument handling for hexagon-unknown-linux-musl targets using a 3-pointer VaList structure compatible with LLVM's HexagonBuiltinVaList implementation. * Handles register save area vs overflow area transition * Provides proper 4-byte and 8-byte alignment for arguments * Only activates for hexagon+musl targets via Arch::Hexagon & Env::Musl
1 parent ab67c37 commit 961715a

File tree

1 file changed

+87
-1
lines changed

1 file changed

+87
-1
lines changed

compiler/rustc_codegen_llvm/src/va_arg.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_codegen_ssa::traits::{
77
};
88
use rustc_middle::ty::Ty;
99
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
10-
use rustc_target::spec::Arch;
10+
use rustc_target::spec::{Arch, Env};
1111

1212
use crate::builder::Builder;
1313
use crate::llvm::{Type, Value};
@@ -780,6 +780,91 @@ fn x86_64_sysv64_va_arg_from_memory<'ll, 'tcx>(
780780
mem_addr
781781
}
782782

783+
fn emit_hexagon_va_arg<'ll, 'tcx>(
784+
bx: &mut Builder<'_, 'll, 'tcx>,
785+
list: OperandRef<'tcx, &'ll Value>,
786+
target_ty: Ty<'tcx>,
787+
) -> &'ll Value {
788+
// Implementation of va_arg for Hexagon musl target.
789+
// Based on LLVM's HexagonBuiltinVaList implementation.
790+
//
791+
// struct __va_list_tag {
792+
// void *__current_saved_reg_area_pointer;
793+
// void *__saved_reg_area_end_pointer;
794+
// void *__overflow_area_pointer;
795+
// };
796+
//
797+
// All variadic arguments are passed on the stack, but the musl implementation
798+
// uses a register save area for compatibility.
799+
let va_list_addr = list.immediate();
800+
let layout = bx.cx.layout_of(target_ty);
801+
let ptr_align_abi = bx.tcx().data_layout.pointer_align().abi;
802+
let ptr_size = bx.tcx().data_layout.pointer_size().bytes();
803+
804+
// Check if argument fits in register save area
805+
let maybe_reg = bx.append_sibling_block("va_arg.maybe_reg");
806+
let from_overflow = bx.append_sibling_block("va_arg.from_overflow");
807+
let end = bx.append_sibling_block("va_arg.end");
808+
809+
// Load the three pointers from va_list
810+
let current_ptr_addr = va_list_addr;
811+
let end_ptr_addr = bx.inbounds_ptradd(va_list_addr, bx.const_usize(ptr_size));
812+
let overflow_ptr_addr = bx.inbounds_ptradd(va_list_addr, bx.const_usize(2 * ptr_size));
813+
814+
let current_ptr = bx.load(bx.type_ptr(), current_ptr_addr, ptr_align_abi);
815+
let end_ptr = bx.load(bx.type_ptr(), end_ptr_addr, ptr_align_abi);
816+
let overflow_ptr = bx.load(bx.type_ptr(), overflow_ptr_addr, ptr_align_abi);
817+
818+
// Align current pointer based on argument type size (following LLVM's implementation)
819+
// Arguments <= 32 bits (4 bytes) use 4-byte alignment, > 32 bits use 8-byte alignment
820+
let type_size_bits = bx.cx.size_of(target_ty).bits();
821+
let arg_align = if type_size_bits > 32 {
822+
Align::from_bytes(8).unwrap()
823+
} else {
824+
Align::from_bytes(4).unwrap()
825+
};
826+
let aligned_current = round_pointer_up_to_alignment(bx, current_ptr, arg_align, bx.type_ptr());
827+
828+
// Calculate next pointer position (following LLVM's logic)
829+
// Arguments <= 32 bits take 4 bytes, > 32 bits take 8 bytes
830+
let arg_size = if type_size_bits > 32 { 8 } else { 4 };
831+
let next_ptr = bx.inbounds_ptradd(aligned_current, bx.const_usize(arg_size));
832+
833+
// Check if argument fits in register save area
834+
let fits_in_regs = bx.icmp(IntPredicate::IntULE, next_ptr, end_ptr);
835+
bx.cond_br(fits_in_regs, maybe_reg, from_overflow);
836+
837+
// Load from register save area
838+
bx.switch_to_block(maybe_reg);
839+
let reg_value_addr = aligned_current;
840+
// Update current pointer
841+
bx.store(next_ptr, current_ptr_addr, ptr_align_abi);
842+
bx.br(end);
843+
844+
// Load from overflow area (stack)
845+
bx.switch_to_block(from_overflow);
846+
847+
// Align overflow pointer using the same alignment rules
848+
let aligned_overflow =
849+
round_pointer_up_to_alignment(bx, overflow_ptr, arg_align, bx.type_ptr());
850+
851+
let overflow_value_addr = aligned_overflow;
852+
// Update overflow pointer - use the same size calculation
853+
let next_overflow = bx.inbounds_ptradd(aligned_overflow, bx.const_usize(arg_size));
854+
bx.store(next_overflow, overflow_ptr_addr, ptr_align_abi);
855+
856+
// IMPORTANT: Also update the current saved register area pointer to match
857+
// This synchronizes the pointers when switching to overflow area
858+
bx.store(next_overflow, current_ptr_addr, ptr_align_abi);
859+
bx.br(end);
860+
861+
// Return the value
862+
bx.switch_to_block(end);
863+
let value_addr =
864+
bx.phi(bx.type_ptr(), &[reg_value_addr, overflow_value_addr], &[maybe_reg, from_overflow]);
865+
bx.load(layout.llvm_type(bx), value_addr, layout.align.abi)
866+
}
867+
783868
fn emit_xtensa_va_arg<'ll, 'tcx>(
784869
bx: &mut Builder<'_, 'll, 'tcx>,
785870
list: OperandRef<'tcx, &'ll Value>,
@@ -964,6 +1049,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
9641049
// This includes `target.is_like_darwin`, which on x86_64 targets is like sysv64.
9651050
Arch::X86_64 => emit_x86_64_sysv64_va_arg(bx, addr, target_ty),
9661051
Arch::Xtensa => emit_xtensa_va_arg(bx, addr, target_ty),
1052+
Arch::Hexagon if target.env == Env::Musl => emit_hexagon_va_arg(bx, addr, target_ty),
9671053
// For all other architecture/OS combinations fall back to using
9681054
// the LLVM va_arg instruction.
9691055
// https://llvm.org/docs/LangRef.html#va-arg-instruction

0 commit comments

Comments
 (0)