taproot_c_scape/va.rs
1//! A hand-rolled SysV AMD64 `va_list`.
2//!
3//! Stable Rust can declare and call C variadic functions but cannot define
4//! them, so every variadic libc entry point here splits in two: a
5//! `#[unsafe(naked)]` shim generated by [`vararg_entry!`], and an ordinary
6//! `extern "C"` implementation function. The shim performs the callee half
7//! of the variadic protocol from psABI 3.5.7: it spills the argument
8//! registers into a register save area, builds a [`VaListTag`], and calls
9//! the implementation with the named arguments plus the tag's address.
10//! [`VaListTag::arg`] is `va_arg`.
11//!
12//! The constants in this file are the psABI's, cross-checked against the
13//! prologue and `va_arg` lowering gcc 15.2 emits for a variadic callee:
14//! six 8-byte GP slots at save area offsets 0..48, eight 16-byte SSE slots
15//! at 48..176 (`movaps`, so the area must be 16-aligned), `gp_offset`
16//! starting at 8 times the named GP count, `fp_offset` starting at 48,
17//! register fetches while the offsets stay below 48 and 176, and an
18//! 8-byte overflow cursor that starts at the first caller stack slot
19//! (entry `rsp + 8`).
20
21use core::ffi::c_void;
22
23/// One SysV AMD64 `va_list` element: the four-field cursor that C
24/// compilers give `__builtin_va_list` (psABI figure 3.34).
25#[repr(C)]
26#[derive(Clone, Copy, Debug)]
27pub struct VaListTag {
28 /// Byte offset into `reg_save_area` of the next GP argument. Starts
29 /// at 8 times the number of named GP arguments; 48 means exhausted.
30 pub gp_offset: u32,
31 /// Byte offset into `reg_save_area` of the next SSE argument. Starts
32 /// at 48 plus 16 per named SSE argument (our entries have none, so
33 /// always 48); 176 means exhausted.
34 pub fp_offset: u32,
35 /// The next stack-passed argument slot.
36 pub overflow_arg_area: *mut c_void,
37 /// The 176-byte register spill area the entry shim built.
38 pub reg_save_area: *mut c_void,
39}
40
41/// Where the GP slots in the register save area end: 6 slots of 8 bytes.
42const GP_AREA_END: u32 = 48;
43/// Where the SSE slots end: 48 plus 8 slots of 16 bytes.
44const SSE_AREA_END: u32 = 176;
45
46impl VaListTag {
47 /// `va_arg`: fetch the next variadic argument as a `T`.
48 ///
49 /// # Safety
50 ///
51 /// `T` must be the type the caller passed in this position, after C's
52 /// default argument promotions, and every earlier variadic argument
53 /// must already have been fetched with its own matching type.
54 #[inline]
55 pub unsafe fn arg<T: VaArg>(&mut self) -> T {
56 // SAFETY: the caller upholds `VaArg::va_arg`'s identical contract.
57 unsafe { T::va_arg(self) }
58 }
59
60 /// The psABI 3.5.7 fetch sequence for one INTEGER-class argument:
61 /// take the register slot at `gp_offset` while any remain, otherwise
62 /// fall through to the overflow area.
63 #[inline]
64 unsafe fn next_gp_slot(&mut self) -> *mut c_void {
65 if self.gp_offset < GP_AREA_END {
66 // SAFETY: `gp_offset` below 48 indexes one of the six GP
67 // slots the entry shim spilled.
68 let slot = unsafe { self.reg_save_area.byte_add(self.gp_offset as usize) };
69 self.gp_offset += 8;
70 slot
71 } else {
72 // SAFETY: register slots exhausted, so per `arg`'s contract
73 // the caller passed this argument on the stack.
74 unsafe { self.next_overflow_slot() }
75 }
76 }
77
78 /// The same sequence for one SSE-class argument: 16-byte register
79 /// slots from 48 up, then the overflow area.
80 #[inline]
81 unsafe fn next_sse_slot(&mut self) -> *mut c_void {
82 if self.fp_offset < SSE_AREA_END {
83 // SAFETY: `fp_offset` in 48..176 indexes one of the eight SSE
84 // slots the entry shim spilled.
85 let slot = unsafe { self.reg_save_area.byte_add(self.fp_offset as usize) };
86 self.fp_offset += 16;
87 slot
88 } else {
89 // SAFETY: as in `next_gp_slot`.
90 unsafe { self.next_overflow_slot() }
91 }
92 }
93
94 /// Take one 8-byte slot off the caller's stack area. Everything this
95 /// walker fetches is 8 bytes with at most 8-byte alignment, and the
96 /// area starts 8-aligned (entry `rsp + 8`), so the cursor never needs
97 /// the psABI's realignment step for 16-aligned types.
98 #[inline]
99 unsafe fn next_overflow_slot(&mut self) -> *mut c_void {
100 let slot = self.overflow_arg_area;
101 // SAFETY: the caller passed an argument in this slot, so the
102 // address one slot past it is still within (or one past) its
103 // argument area.
104 self.overflow_arg_area = unsafe { slot.byte_add(8) };
105 slot
106 }
107}
108
109/// Types [`VaListTag::arg`] can fetch.
110///
111/// C's default argument promotions mean narrower types never reach a
112/// variadic callee: `char`, `short` and `_Bool` arrive as `int`, and
113/// `float` arrives as `double`. That is why there is no `f32` impl and
114/// none below 32 bits: fetch the promoted type and narrow afterwards,
115/// exactly as C code must. `c_int`, `c_long` and friends are aliases of
116/// the primitives below on every x86_64 Linux target.
117pub trait VaArg: Copy {
118 /// Fetch one argument of this type off the walker.
119 ///
120 /// # Safety
121 ///
122 /// Same contract as [`VaListTag::arg`].
123 unsafe fn va_arg(tag: &mut VaListTag) -> Self;
124}
125
126/// INTEGER-class one-slot types.
127macro_rules! gp_va_arg {
128 ($($int:ty),*) => {$(
129 impl VaArg for $int {
130 #[inline]
131 unsafe fn va_arg(tag: &mut VaListTag) -> Self {
132 // SAFETY: `arg`'s contract puts a value of this type in
133 // the next GP slot; a 32-bit value occupies the slot's
134 // low bytes on this little-endian target, which is where
135 // a plain read of `Self` looks. Slots are 8-aligned.
136 unsafe { tag.next_gp_slot().cast::<Self>().read() }
137 }
138 }
139 )*};
140}
141
142gp_va_arg!(i32, u32, i64, u64, isize, usize);
143
144impl<T> VaArg for *const T {
145 #[inline]
146 unsafe fn va_arg(tag: &mut VaListTag) -> Self {
147 // SAFETY: pointers are INTEGER class, and a thin `*const T` is
148 // exactly one 8-aligned slot.
149 unsafe { tag.next_gp_slot().cast::<Self>().read() }
150 }
151}
152
153impl<T> VaArg for *mut T {
154 #[inline]
155 unsafe fn va_arg(tag: &mut VaListTag) -> Self {
156 // SAFETY: as for `*const T`.
157 unsafe { tag.next_gp_slot().cast::<Self>().read() }
158 }
159}
160
161impl VaArg for f64 {
162 #[inline]
163 unsafe fn va_arg(tag: &mut VaListTag) -> Self {
164 // SAFETY: doubles are SSE class; the value is the low 8 bytes of
165 // a 16-byte register slot or a whole 8-byte overflow slot, both
166 // at least 8-aligned.
167 unsafe { tag.next_sse_slot().cast::<f64>().read() }
168 }
169}
170
171/// Generates the `#[unsafe(naked)]` C entry point for a variadic libc
172/// function: the entry performs the variadic callee protocol and then
173/// calls an ordinary implementation function that receives the named
174/// arguments plus a `*mut VaListTag`.
175///
176/// ```ignore
177/// vararg_entry! {
178/// #[no_mangle]
179/// unsafe extern "C" fn printf(fmt: *const c_char, ...) -> c_int => printf_impl
180/// }
181/// ```
182///
183/// One to four named arguments are supported, and every named argument
184/// must be INTEGER class (an integer or a thin pointer): that covers the
185/// whole printf/execl/syslog/`__chk` surface. The declared return type
186/// (or none) must be INTEGER class or `f64`; it rides back through
187/// rax/xmm0 untouched.
188///
189/// Exported (macro_export) so the taproot cdylib crate can generate its
190/// scanf-family entries with the same emitter.
191#[macro_export]
192macro_rules! vararg_entry {
193 ($(#[$attr:meta])* unsafe extern "C" fn $name:ident(
194 $a1:ident: $t1:ty, ...
195 ) $(-> $ret:ty)? => $imp:path) => {
196 $crate::__vararg_entry_emit! {
197 [$(#[$attr])*] $name, ($a1: $t1), ($($ret)?), $imp,
198 gp = 8, tag_reg = "rsi"
199 }
200 };
201 ($(#[$attr:meta])* unsafe extern "C" fn $name:ident(
202 $a1:ident: $t1:ty, $a2:ident: $t2:ty, ...
203 ) $(-> $ret:ty)? => $imp:path) => {
204 $crate::__vararg_entry_emit! {
205 [$(#[$attr])*] $name, ($a1: $t1, $a2: $t2), ($($ret)?), $imp,
206 gp = 16, tag_reg = "rdx"
207 }
208 };
209 ($(#[$attr:meta])* unsafe extern "C" fn $name:ident(
210 $a1:ident: $t1:ty, $a2:ident: $t2:ty, $a3:ident: $t3:ty, ...
211 ) $(-> $ret:ty)? => $imp:path) => {
212 $crate::__vararg_entry_emit! {
213 [$(#[$attr])*] $name, ($a1: $t1, $a2: $t2, $a3: $t3), ($($ret)?), $imp,
214 gp = 24, tag_reg = "rcx"
215 }
216 };
217 ($(#[$attr:meta])* unsafe extern "C" fn $name:ident(
218 $a1:ident: $t1:ty, $a2:ident: $t2:ty, $a3:ident: $t3:ty, $a4:ident: $t4:ty, ...
219 ) $(-> $ret:ty)? => $imp:path) => {
220 $crate::__vararg_entry_emit! {
221 [$(#[$attr])*] $name, ($a1: $t1, $a2: $t2, $a3: $t3, $a4: $t4), ($($ret)?), $imp,
222 gp = 32, tag_reg = "r8"
223 }
224 };
225}
226
227/// The emitter behind [`vararg_entry!`]: one naked entry, with the
228/// psABI-derived constants baked in. `gp` is `8 * named_gp_args` and
229/// `tag_reg` is the GP argument register after the last named one.
230#[macro_export]
231#[doc(hidden)]
232macro_rules! __vararg_entry_emit {
233 (
234 [$($attr:tt)*] $name:ident, ($($arg:ident: $ty:ty),*), ($($ret:ty)?), $imp:path,
235 gp = $gp:literal, tag_reg = $tag_reg:literal
236 ) => {
237 // The entry hands the implementation the named arguments plus the
238 // tag; hold the two signatures together at compile time.
239 const _: unsafe extern "C" fn($($ty,)* *mut $crate::va::VaListTag) $(-> $ret)? = $imp;
240
241 $($attr)*
242 #[unsafe(naked)]
243 unsafe extern "C" fn $name($($arg: $ty),*) $(-> $ret)? {
244 ::core::arch::naked_asm!(
245 // The callee half of the variadic protocol (psABI 3.5.7).
246 //
247 // On entry rsp is 8 mod 16 (a 16-aligned stack plus the
248 // return address, psABI 3.2.2), so this 200-byte frame
249 // puts rsp back on a 16-byte boundary: the movaps stores
250 // below need that, and so does the ABI at the call below.
251 //
252 // [rsp + 0 .. 176) register save area
253 // [rsp + 176 .. 200) the VaListTag
254 "sub rsp, 200",
255 // Spill the six GP argument registers into slots 0..48.
256 // The named slots below gp_offset are never read back;
257 // spilling them anyway keeps the shim uniform.
258 "mov [rsp], rdi",
259 "mov [rsp + 8], rsi",
260 "mov [rsp + 16], rdx",
261 "mov [rsp + 24], rcx",
262 "mov [rsp + 32], r8",
263 "mov [rsp + 40], r9",
264 // AL carries the caller's count of vector registers used;
265 // skip the SSE spill when it is zero, exactly as compiled
266 // C prologues do.
267 "test al, al",
268 "je 2f",
269 "movaps [rsp + 48], xmm0",
270 "movaps [rsp + 64], xmm1",
271 "movaps [rsp + 80], xmm2",
272 "movaps [rsp + 96], xmm3",
273 "movaps [rsp + 112], xmm4",
274 "movaps [rsp + 128], xmm5",
275 "movaps [rsp + 144], xmm6",
276 "movaps [rsp + 160], xmm7",
277 "2:",
278 // Build the tag: gp_offset opens at 8 per named GP
279 // argument, fp_offset at 48 (no entry has named SSE
280 // arguments), overflow_arg_area at the first caller stack
281 // slot (entry rsp + 8, which is rsp + 208 now), and
282 // reg_save_area at the frame base.
283 concat!("mov dword ptr [rsp + 176], ", $gp),
284 "mov dword ptr [rsp + 180], 48",
285 "lea rax, [rsp + 208]",
286 "mov [rsp + 184], rax",
287 "mov [rsp + 192], rsp",
288 // The named arguments still sit in their registers; hand
289 // over the tag's address in the next slot and let the
290 // implementation run.
291 concat!("lea ", $tag_reg, ", [rsp + 176]"),
292 "call {imp}",
293 // The return value rides through rax (or xmm0) untouched.
294 "add rsp, 200",
295 "ret",
296 imp = sym $imp,
297 )
298 }
299 };
300}
301
302// Path-based imports for the crate's own callers and for the taproot
303// cdylib crate; `#[macro_export]` also places both at the crate root,
304// which is the `$crate` path the expansion rides on.
305#[doc(hidden)]
306pub use __vararg_entry_emit;
307pub use vararg_entry;
308
309#[cfg(test)]
310mod tests {
311 // `vararg_entry!` needs no import: `macro_rules!` scoping already
312 // carries it through the rest of the file.
313 use super::VaListTag;
314 use libc::{c_char, c_int, c_long, c_uint, c_void};
315
316 // The implementation halves: ordinary `extern "C"` functions that
317 // receive the named arguments plus the tag the naked entry built.
318
319 unsafe extern "C" fn sum_longs_impl(count: c_int, tag: *mut VaListTag) -> c_long {
320 // SAFETY: the entry hands us a live tag; each fetch below matches
321 // one `c_long` the test call sites pass.
322 unsafe {
323 let tag = &mut *tag;
324 let mut acc: c_long = 0;
325 for _ in 0..count {
326 acc = acc * 10 + tag.arg::<c_long>();
327 }
328 acc
329 }
330 }
331
332 unsafe extern "C" fn grab_impl(out: *mut u64, count: c_int, tag: *mut VaListTag) -> c_int {
333 // SAFETY: the fetch script mirrors the call site in
334 // `widths_and_pointers_recover_exactly`, slot for slot, and `out`
335 // has room for all eight recovered values.
336 unsafe {
337 let tag = &mut *tag;
338 *out.add(0) = tag.arg::<c_int>() as i64 as u64;
339 *out.add(1) = u64::from(tag.arg::<c_uint>());
340 *out.add(2) = tag.arg::<u64>();
341 *out.add(3) = tag.arg::<*mut u8>() as u64;
342 *out.add(4) = tag.arg::<usize>() as u64;
343 *out.add(5) = tag.arg::<i64>() as u64;
344 *out.add(6) = tag.arg::<*const c_void>() as u64;
345 *out.add(7) = tag.arg::<isize>() as u64;
346 }
347 count
348 }
349
350 unsafe extern "C" fn fgrab_impl(
351 out: *mut f64,
352 count: c_int,
353 check: c_int,
354 tag: *mut VaListTag,
355 ) -> c_int {
356 // SAFETY: the callers pass `count` doubles and an `out` with room
357 // for them.
358 unsafe {
359 let tag = &mut *tag;
360 for i in 0..count {
361 *out.add(i as usize) = tag.arg::<f64>();
362 }
363 }
364 check
365 }
366
367 unsafe extern "C" fn named4_impl(
368 a: c_int,
369 b: c_int,
370 c: c_int,
371 d: c_int,
372 tag: *mut VaListTag,
373 ) -> c_long {
374 // SAFETY: the caller passes three `c_long`s after the four named
375 // ints.
376 let (v1, v2, v3) = unsafe {
377 let tag = &mut *tag;
378 (
379 tag.arg::<c_long>(),
380 tag.arg::<c_long>(),
381 tag.arg::<c_long>(),
382 )
383 };
384 c_long::from(a)
385 + 2 * c_long::from(b)
386 + 3 * c_long::from(c)
387 + 4 * c_long::from(d)
388 + 1_000 * v1
389 + 1_000_000 * v2
390 + 1_000_000_000 * v3
391 }
392
393 unsafe extern "C" fn mixed_impl(iout: *mut i64, fout: *mut f64, tag: *mut VaListTag) -> c_int {
394 // SAFETY: the fetch script mirrors the call site in
395 // `mixed_classes_share_one_overflow_cursor`: four register ints,
396 // eight register doubles, then ints five and six and the ninth
397 // double off the shared overflow cursor, in call order.
398 unsafe {
399 let tag = &mut *tag;
400 for i in 0..4 {
401 *iout.add(i) = tag.arg::<i64>();
402 }
403 for i in 0..8 {
404 *fout.add(i) = tag.arg::<f64>();
405 }
406 *iout.add(4) = tag.arg::<i64>();
407 *iout.add(5) = tag.arg::<i64>();
408 *fout.add(8) = tag.arg::<f64>();
409 }
410 0
411 }
412
413 unsafe extern "C" fn execle_shape_impl(out: *mut *const c_char, tag: *mut VaListTag) -> c_int {
414 // SAFETY: the fetch script is the execle loop shape its call site
415 // mirrors: `*const c_char`s up to and including a null terminator,
416 // then one trailing envp-style pointer, and `out` has room for all
417 // of them.
418 unsafe {
419 let tag = &mut *tag;
420 let mut count = 0;
421 loop {
422 let ptr = tag.arg::<*const c_char>();
423 *out.add(count) = ptr;
424 count += 1;
425 if ptr.is_null() {
426 break;
427 }
428 }
429 *out.add(count) = tag.arg::<*const *const c_char>().cast();
430 count as c_int
431 }
432 }
433
434 // The generated naked entries, one per named-argument count 1..=4.
435
436 vararg_entry! {
437 #[no_mangle]
438 unsafe extern "C" fn __taproot_va_test_sum_longs(count: c_int, ...) -> c_long
439 => sum_longs_impl
440 }
441
442 vararg_entry! {
443 #[no_mangle]
444 unsafe extern "C" fn __taproot_va_test_grab(out: *mut u64, count: c_int, ...) -> c_int
445 => grab_impl
446 }
447
448 vararg_entry! {
449 #[no_mangle]
450 unsafe extern "C" fn __taproot_va_test_fgrab(
451 out: *mut f64,
452 count: c_int,
453 check: c_int,
454 ...
455 ) -> c_int => fgrab_impl
456 }
457
458 vararg_entry! {
459 #[no_mangle]
460 unsafe extern "C" fn __taproot_va_test_named4(
461 a: c_int,
462 b: c_int,
463 c: c_int,
464 d: c_int,
465 ...
466 ) -> c_long => named4_impl
467 }
468
469 vararg_entry! {
470 #[no_mangle]
471 unsafe extern "C" fn __taproot_va_test_mixed(iout: *mut i64, fout: *mut f64, ...) -> c_int
472 => mixed_impl
473 }
474
475 vararg_entry! {
476 #[no_mangle]
477 unsafe extern "C" fn __taproot_va_test_execle_shape(
478 out: *mut *const c_char,
479 ...
480 ) -> c_int => execle_shape_impl
481 }
482
483 /// The C-side view of the entries above. Living in a child module
484 /// keeps these declarations from colliding with the definitions' item
485 /// names; the linker pairs them up by symbol. Calling through these
486 /// makes the tests real-ABI round trips: rustc performs the caller
487 /// half of the variadic protocol (register assignment, AL, stack
488 /// slots), and the naked entries must undo it exactly.
489 mod decl {
490 use libc::{c_char, c_int, c_long};
491
492 unsafe extern "C" {
493 pub fn __taproot_va_test_sum_longs(count: c_int, ...) -> c_long;
494 pub fn __taproot_va_test_grab(out: *mut u64, count: c_int, ...) -> c_int;
495 pub fn __taproot_va_test_fgrab(
496 out: *mut f64,
497 count: c_int,
498 check: c_int,
499 ...
500 ) -> c_int;
501 pub fn __taproot_va_test_named4(
502 a: c_int,
503 b: c_int,
504 c: c_int,
505 d: c_int,
506 ...
507 ) -> c_long;
508 pub fn __taproot_va_test_mixed(iout: *mut i64, fout: *mut f64, ...) -> c_int;
509 pub fn __taproot_va_test_execle_shape(out: *mut *const c_char, ...) -> c_int;
510 }
511 }
512
513 #[test]
514 fn ints_cross_into_the_overflow_area() {
515 // One named plus eight variadic longs: five ride rsi..r9, and the
516 // last three come off the caller's stack through
517 // `overflow_arg_area`. The positional fold proves both the values
518 // and their order.
519 let got = unsafe {
520 decl::__taproot_va_test_sum_longs(
521 8,
522 1 as c_long,
523 2 as c_long,
524 3 as c_long,
525 4 as c_long,
526 5 as c_long,
527 6 as c_long,
528 7 as c_long,
529 8 as c_long,
530 )
531 };
532 assert_eq!(got, 12_345_678);
533 }
534
535 #[test]
536 fn no_fp_call_walks_registers_only() {
537 // No doubles anywhere, so rustc sets AL = 0 and the entry must
538 // skip the SSE spill entirely.
539 let got = unsafe { decl::__taproot_va_test_sum_longs(2, 40 as c_long, 2 as c_long) };
540 assert_eq!(got, 402);
541 }
542
543 #[test]
544 fn widths_and_pointers_recover_exactly() {
545 let mut in_reg: u8 = 0;
546 let on_stack: u16 = 0;
547 let preg: *mut u8 = &mut in_reg;
548 let pstack: *const c_void = (&on_stack as *const u16).cast();
549 let mut out = [0_u64; 8];
550 // Two named plus eight variadic: slots one to four ride rdx, rcx,
551 // r8 and r9; five to eight cross into the overflow area, with a
552 // pointer landing on each side of the boundary.
553 let echoed = unsafe {
554 decl::__taproot_va_test_grab(
555 out.as_mut_ptr(),
556 8,
557 -7_i32,
558 0xDEAD_BEEF_u32,
559 0x8000_0000_0000_0001_u64,
560 preg,
561 usize::MAX - 41,
562 i64::MIN + 2,
563 pstack,
564 isize::MIN + 9,
565 )
566 };
567 assert_eq!(echoed, 8);
568 assert_eq!(out[0], -7_i64 as u64);
569 assert_eq!(out[1], 0xDEAD_BEEF);
570 assert_eq!(out[2], 0x8000_0000_0000_0001);
571 assert_eq!(out[3], preg as u64);
572 assert_eq!(out[4], (usize::MAX - 41) as u64);
573 assert_eq!(out[5], (i64::MIN + 2) as u64);
574 assert_eq!(out[6], pstack as u64);
575 assert_eq!(out[7], (isize::MIN + 9) as u64);
576 }
577
578 #[test]
579 fn f64s_ride_xmm0_to_7_then_spill() {
580 const DOUBLES: [f64; 10] = [
581 0.5,
582 -3.25,
583 1.0e300,
584 f64::MIN_POSITIVE,
585 -0.0,
586 6.022e23,
587 9_007_199_254_740_992.0,
588 -1.0,
589 9.75,
590 1_234.5,
591 ];
592 let mut out = [0.0_f64; 10];
593 // Three named ints, then ten doubles: xmm0..7 carry eight of them
594 // (rustc sets AL = 8) and the last two arrive through the
595 // overflow area.
596 let echoed = unsafe {
597 decl::__taproot_va_test_fgrab(
598 out.as_mut_ptr(),
599 10,
600 0x5AFE,
601 DOUBLES[0],
602 DOUBLES[1],
603 DOUBLES[2],
604 DOUBLES[3],
605 DOUBLES[4],
606 DOUBLES[5],
607 DOUBLES[6],
608 DOUBLES[7],
609 DOUBLES[8],
610 DOUBLES[9],
611 )
612 };
613 assert_eq!(echoed, 0x5AFE);
614 // Bit equality, so the negative zero must survive too.
615 for (got, want) in out.iter().zip(DOUBLES) {
616 assert_eq!(got.to_bits(), want.to_bits());
617 }
618 }
619
620 #[test]
621 fn f64s_partial_register_walk() {
622 let mut out = [0.0_f64; 3];
623 // AL = 3: the entry still spills all eight SSE registers, but the
624 // walker only reads back the three that carry arguments.
625 let echoed =
626 unsafe { decl::__taproot_va_test_fgrab(out.as_mut_ptr(), 3, 7, 1.5, 2.5, -8.125) };
627 assert_eq!(echoed, 7);
628 assert_eq!(out, [1.5, 2.5, -8.125]);
629 }
630
631 #[test]
632 fn four_named_args_start_the_walk_at_32() {
633 // Four named ints occupy rdi..rcx, so gp_offset opens at 32: the
634 // first two variadic longs sit in r8 and r9, the third on the
635 // stack.
636 let got = unsafe {
637 decl::__taproot_va_test_named4(10, 20, 30, 40, 7 as c_long, 8 as c_long, 9 as c_long)
638 };
639 assert_eq!(got, 9_008_007_300);
640 }
641
642 #[test]
643 fn mixed_classes_share_one_overflow_cursor() {
644 let mut iout = [0_i64; 6];
645 let mut fout = [0.0_f64; 9];
646 // Six GP arguments fill rdi..r9 (two named pointers plus four
647 // variadic ints), so ints five and six go to the stack. Eight
648 // doubles fill xmm0..7, so the ninth goes to the stack after
649 // them. The overflow area therefore reads back, in call order:
650 // 55, 66, 8.5.
651 let rc = unsafe {
652 decl::__taproot_va_test_mixed(
653 iout.as_mut_ptr(),
654 fout.as_mut_ptr(),
655 11_i64,
656 22_i64,
657 33_i64,
658 44_i64,
659 0.5,
660 1.5,
661 2.5,
662 3.5,
663 4.5,
664 5.5,
665 6.5,
666 7.5,
667 55_i64,
668 66_i64,
669 8.5,
670 )
671 };
672 assert_eq!(rc, 0);
673 assert_eq!(iout, [11, 22, 33, 44, 55, 66]);
674 assert_eq!(fout, [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5]);
675 }
676
677 #[test]
678 fn the_execle_walk_collects_until_null_then_envp() {
679 // The execl/execle loop shape: one named argument, then `*const
680 // c_char`s until a null terminator, then (execle only) one more
681 // pointer. Six strings push the walk off the registers: rsi..r9
682 // carry the first five, and the sixth, the null and the envp
683 // pointer all come off the overflow area.
684 let argv = [
685 c"sh".as_ptr(),
686 c"-c".as_ptr(),
687 c"one".as_ptr(),
688 c"two".as_ptr(),
689 c"three".as_ptr(),
690 c"four".as_ptr(),
691 ];
692 let env = [c"X=1".as_ptr(), core::ptr::null()];
693 let envp: *const *const c_char = env.as_ptr();
694 let mut out = [core::ptr::null::<c_char>(); 8];
695 let walked = unsafe {
696 decl::__taproot_va_test_execle_shape(
697 out.as_mut_ptr(),
698 argv[0],
699 argv[1],
700 argv[2],
701 argv[3],
702 argv[4],
703 argv[5],
704 core::ptr::null::<c_char>(),
705 envp,
706 )
707 };
708 assert_eq!(walked, 7);
709 assert_eq!(&out[..6], &argv);
710 assert!(out[6].is_null());
711 assert_eq!(out[7], envp.cast::<c_char>());
712 }
713}