@@ -10,15 +10,23 @@ use crate::type_::Type;
1010use crate :: type_of:: LayoutLlvmExt ;
1111use crate :: value:: Value ;
1212
13+ fn round_up_to_alignment < ' ll > (
14+ bx : & mut Builder < ' _ , ' ll , ' _ > ,
15+ mut value : & ' ll Value ,
16+ align : Align ,
17+ ) -> & ' ll Value {
18+ value = bx. add ( value, bx. cx ( ) . const_i32 ( align. bytes ( ) as i32 - 1 ) ) ;
19+ return bx. and ( value, bx. cx ( ) . const_i32 ( -( align. bytes ( ) as i32 ) ) ) ;
20+ }
21+
1322fn round_pointer_up_to_alignment < ' ll > (
1423 bx : & mut Builder < ' _ , ' ll , ' _ > ,
1524 addr : & ' ll Value ,
1625 align : Align ,
1726 ptr_ty : & ' ll Type ,
1827) -> & ' ll Value {
1928 let mut ptr_as_int = bx. ptrtoint ( addr, bx. cx ( ) . type_isize ( ) ) ;
20- ptr_as_int = bx. add ( ptr_as_int, bx. cx ( ) . const_i32 ( align. bytes ( ) as i32 - 1 ) ) ;
21- ptr_as_int = bx. and ( ptr_as_int, bx. cx ( ) . const_i32 ( -( align. bytes ( ) as i32 ) ) ) ;
29+ ptr_as_int = round_up_to_alignment ( bx, ptr_as_int, align) ;
2230 bx. inttoptr ( ptr_as_int, ptr_ty)
2331}
2432
@@ -270,6 +278,106 @@ fn emit_s390x_va_arg<'ll, 'tcx>(
270278 bx. load ( val_type, val_addr, layout. align . abi )
271279}
272280
281+ fn emit_xtensa_va_arg < ' ll , ' tcx > (
282+ bx : & mut Builder < ' _ , ' ll , ' tcx > ,
283+ list : OperandRef < ' tcx , & ' ll Value > ,
284+ target_ty : Ty < ' tcx > ,
285+ ) -> & ' ll Value {
286+ // Implementation of va_arg for Xtensa. There doesn't seem to be an authoritative source for
287+ // this, other than "what GCC does".
288+ //
289+ // The va_list type has three fields:
290+ // struct __va_list_tag {
291+ // int32_t *va_stk; // Arguments passed on the stack
292+ // int32_t *va_reg; // Arguments passed in registers, saved to memory by the prologue.
293+ // int32_t va_ndx; // Offset into the arguments, in bytes
294+ // };
295+ //
296+ // The first 24 bytes (equivalent to 6 registers) come from va_reg, the rest from va_stk.
297+ // Thus if va_ndx is less than 24, the next va_arg *may* read from va_reg,
298+ // otherwise it must come from va_stk.
299+ //
300+ // Primitive arguments are never split between registers and the stack. For example, if loading an 8 byte
301+ // primitive value and va_ndx = 20, we instead bump the offset and read everything from va_stk.
302+ let va_list_addr = list. immediate ( ) ;
303+ // FIXME: handle multi-field structs that split across regsave/stack?
304+ let layout = bx. cx . layout_of ( target_ty) ;
305+ let from_stack = bx. append_sibling_block ( "va_arg.from_stack" ) ;
306+ let from_regsave = bx. append_sibling_block ( "va_arg.from_regsave" ) ;
307+ let end = bx. append_sibling_block ( "va_arg.end" ) ;
308+
309+ // (*va).va_ndx
310+ let va_reg_offset = 4 ;
311+ let va_ndx_offset = va_reg_offset + 4 ;
312+ let offset_ptr =
313+ bx. inbounds_gep ( bx. type_i8 ( ) , va_list_addr, & [ bx. cx . const_usize ( va_ndx_offset) ] ) ;
314+
315+ let offset = bx. load ( bx. type_i32 ( ) , offset_ptr, bx. tcx ( ) . data_layout . i32_align . abi ) ;
316+ let offset = round_up_to_alignment ( bx, offset, layout. align . abi ) ;
317+
318+ let slot_size = layout. size . align_to ( Align :: from_bytes ( 4 ) . unwrap ( ) ) . bytes ( ) as i32 ;
319+
320+ // Update the offset in va_list, by adding the slot's size.
321+ let offset_next = bx. add ( offset, bx. const_i32 ( slot_size) ) ;
322+
323+ // Figure out where to look for our value. We do that by checking the end of our slot (offset_next).
324+ // If that is within the regsave area, then load from there. Otherwise load from the stack area.
325+ let regsave_size = bx. const_i32 ( 24 ) ;
326+ let use_regsave = bx. icmp ( IntPredicate :: IntULE , offset_next, regsave_size) ;
327+ bx. cond_br ( use_regsave, from_regsave, from_stack) ;
328+
329+ bx. switch_to_block ( from_regsave) ;
330+ // update va_ndx
331+ bx. store ( offset_next, offset_ptr, bx. tcx ( ) . data_layout . pointer_align . abi ) ;
332+
333+ // (*va).va_reg
334+ let regsave_area_ptr =
335+ bx. inbounds_gep ( bx. type_i8 ( ) , va_list_addr, & [ bx. cx . const_usize ( va_reg_offset) ] ) ;
336+ let regsave_area =
337+ bx. load ( bx. type_ptr ( ) , regsave_area_ptr, bx. tcx ( ) . data_layout . pointer_align . abi ) ;
338+ let regsave_value_ptr = bx. inbounds_gep ( bx. type_i8 ( ) , regsave_area, & [ offset] ) ;
339+ bx. br ( end) ;
340+
341+ bx. switch_to_block ( from_stack) ;
342+
343+ // The first time we switch from regsave to stack we needs to adjust our offsets a bit.
344+ // va_stk is set up such that the first stack argument is always at va_stk + 32.
345+ // The corrected offset is written back into the va_list struct.
346+
347+ // let offset_corrected = cmp::max(offset, 32);
348+ let stack_offset_start = bx. const_i32 ( 32 ) ;
349+ let needs_correction = bx. icmp ( IntPredicate :: IntULE , offset, stack_offset_start) ;
350+ let offset_corrected = bx. select ( needs_correction, stack_offset_start, offset) ;
351+
352+ // let offset_next_corrected = offset_corrected + slot_size;
353+ // va_ndx = offset_next_corrected;
354+ let offset_next_corrected = bx. add ( offset_next, bx. const_i32 ( slot_size) ) ;
355+ // update va_ndx
356+ bx. store ( offset_next_corrected, offset_ptr, bx. tcx ( ) . data_layout . pointer_align . abi ) ;
357+
358+ // let stack_value_ptr = unsafe { (*va).va_stk.byte_add(offset_corrected) };
359+ let stack_area_ptr = bx. inbounds_gep ( bx. type_i8 ( ) , va_list_addr, & [ bx. cx . const_usize ( 0 ) ] ) ;
360+ let stack_area = bx. load ( bx. type_ptr ( ) , stack_area_ptr, bx. tcx ( ) . data_layout . pointer_align . abi ) ;
361+ let stack_value_ptr = bx. inbounds_gep ( bx. type_i8 ( ) , stack_area, & [ offset_corrected] ) ;
362+ bx. br ( end) ;
363+
364+ bx. switch_to_block ( end) ;
365+
366+ // On big-endian, for values smaller than the slot size we'd have to align the read to the end
367+ // of the slot rather than the start. While the ISA and GCC support big-endian, all the Xtensa
368+ // targets supported by rustc are litte-endian so don't worry about it.
369+
370+ // if from_regsave {
371+ // unsafe { *regsave_value_ptr }
372+ // } else {
373+ // unsafe { *stack_value_ptr }
374+ // }
375+ assert ! ( bx. tcx( ) . sess. target. endian == Endian :: Little ) ;
376+ let value_ptr =
377+ bx. phi ( bx. type_ptr ( ) , & [ regsave_value_ptr, stack_value_ptr] , & [ from_regsave, from_stack] ) ;
378+ return bx. load ( layout. llvm_type ( bx) , value_ptr, layout. align . abi ) ;
379+ }
380+
273381pub ( super ) fn emit_va_arg < ' ll , ' tcx > (
274382 bx : & mut Builder < ' _ , ' ll , ' tcx > ,
275383 addr : OperandRef < ' tcx , & ' ll Value > ,
@@ -302,6 +410,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
302410 let indirect: bool = target_ty_size > 8 || !target_ty_size. is_power_of_two ( ) ;
303411 emit_ptr_va_arg ( bx, addr, target_ty, indirect, Align :: from_bytes ( 8 ) . unwrap ( ) , false )
304412 }
413+ "xtensa" => emit_xtensa_va_arg ( bx, addr, target_ty) ,
305414 // For all other architecture/OS combinations fall back to using
306415 // the LLVM va_arg instruction.
307416 // https://llvm.org/docs/LangRef.html#va-arg-instruction
0 commit comments