Change:
We encountered this with WebRender, where we exposed a method in Rust to the Gecko, and the last parameters supplied appeared random on the Rust side. I made a reduced test case adapted from it that you can grab at: It is a very simple test case where the C++ program puts all the parameters on the stack, prints the expected values of some of them, and calls the Rust method. The Rust method prints the values it received. We have enough parameters to fill up the registers used in the calling convention, and once we get to pushing things on the stack, it goes wrong. You can see from the output that they mismatch. ./build- && ./run- outputs c++ expect width=7 height=8 c++ expect slice=9 10 11 12 c++ expect outset=13. 13. 13. 13. c++ expect repeat h=0 v=0 got rust image 6 width 7 height 8 got rust slice SideOffsets { top: 9, right: 10, bottom: 11, left: 12 } got rust outset LayoutGeometry { top: 0.0, left: 0.0, width: 0.0, height: 0.0 } got rust repeat h=26 v=58 Note that the earlier parameters are correct, but the last 3 (outset, repeat h and repeat v) are all incorrect.
Is there an upstream issue in WebRender you can link? What platform is this happening on?
The Gecko side integrating with WebRender was disabled, and I have been working to re-enable this in You can find the code in question at: I intend to just work around this using structs for the moment as part of the aforementioned bug.
We're seeing this on all of our x64 platforms (win, mac, linux; all using clang). It looks like a pretty clear bug in our sysv callee ABI for x64 when there are lots of arguments.
<details<summaryrustc callee llvm-ir </summary</details<details<summaryclang caller llvm-ir </summary</detailsJust the signatures:
Note to self: can go negative from scalar integer arguments after the 6 integer registers have been exhausted and then is always true. Will try to fix soon (also I wonder if this is a stable-to-stable regression from years ago?).
Looks like this was working circa 1.0, but has been broken at least since 1.12. Oof.
Do you need this fixed on beta as well, btw?
it would certainly be great if you uplifted this, but we have to work around it for the next few weeks anyway

Discussion:
target } <ins> const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9 const MAX_SSE_REGS: usize = 8; // XMM0-7 </ins> pub fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnType<'a, Ty>) where Ty: TyLayoutMethods<'a, C> + Copy, C: LayoutOf<Ty = Ty, TyLayout = TyLayout<'a, Ty>> + HasDataLayout { <del> let mut int_regs = 6; // RDI, RSI, RDX, RCX, R8, R9 let mut sse_regs = 8; // XMM0-7 </del> <ins> let mut int_regs = MAX_INT_REGS; let mut sse_regs = MAX_SSE_REGS; </ins> let mut x86_64_ty = |arg: &mut ArgType<'a, Ty>, is_arg: bool| { let mut cls_or_mem = classify_arg(cx, arg); <del> let mut needed_int = 0; let mut needed_sse = 0; </del> if is_arg { if let Ok(cls) = cls_or_mem { <ins> let mut needed_int = 0; let mut needed_sse = 0; </ins> for &c in &cls { match c { Some(Class::Int) => needed_int += 1,

_ => {} } } <del> if arg.layout.is_aggregate() && (int_regs < needed_int || sse_regs < needed_sse) { cls_or_mem = Err(Memory); </del> <ins> match (int_regs.checked_sub(needed_int), sse_regs.checked_sub(needed_sse)) { (Some(left_int), Some(left_sse)) => { int_regs = left_int; sse_regs = left_sse; } _ => { // Not enough registers for this argument, so it will be // passed on the stack, but we only mark aggregates // explicitly as indirect `byval` arguments, as LLVM will // automatically put immediates on the stack itself. if arg.layout.is_aggregate() { cls_or_mem = Err(Memory); } } </ins> } } }

} else { // `sret` parameter thus one less integer register available arg.make_indirect(); <ins> // NOTE(eddyb) return is handled first, so no registers // should've been used yet. assert_eq!(int_regs, MAX_INT_REGS); </ins> int_regs -= 1; } } Ok(ref cls) => { // split into sized chunks passed individually <del> int_regs -= needed_int; sse_regs -= needed_sse; </del> if arg.layout.is_aggregate() { let size = arg.layout.size; arg.cast_to(cast_target(cls, size))

return f.c; } <ins> struct quad_floats { float a; float b; float c; float d; }; float get_c_exhaust_sysv64_ints( void *a, void *b, void *c, void *d, void *e, void *f, // `f` used the last integer register, so `g` goes on the stack. // It also used to bring the "count of available integer registers" down to // `-1` which broke the next SSE-only aggregate argument (`h`) - see #62350. void *g, struct quad_floats h ) { return h.c; } </ins> // Calculates the average of `(x + y) / n` where x: i64, y: f64. There must be exactly n pairs // passed as variadic arguments. There are two versions of this function: the // variadic one, and the one that takes a `va_list`.

// extern-return-TwoU64s // foreign-fn-with-byval // issue-28676 <ins> // issue-62350-sysv-neg-reg-counts </ins> // struct-return // ignore-android

#[derive(Copy, Clone)] pub struct Quad { a: u64, b: u64, c: u64, d: u64 } <ins> #[derive(Copy, Clone)] pub struct QuadFloats { a: f32, b: f32, c: f32, d: f32 } </ins> #[repr(C)] #[derive(Copy, Clone)] pub struct Floats { a: f64, b: u8, c: f64 }

pub fn get_z(x: S) -> u64; pub fn get_c_many_params(_: *const (), _: *const (), _: *const (), _: *const (), f: Quad) -> u64; <ins> pub fn get_c_exhaust_sysv64_ints( _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), h: QuadFloats, ) -> f32; </ins> pub fn rust_dbg_abi_1(q: Quad) -> Quad; pub fn rust_dbg_abi_2(f: Floats) -> Floats; }

test(); } <ins> fn test_62350() { use std::ptr; unsafe { let null = ptr::null(); let q = QuadFloats { a: 10.2, b: 20.3, c: 30.4, d: 40.5 }; assert_eq!( get_c_exhaust_sysv64_ints(null, null, null, null, null, null, null, q), q.c, ); } } pub fn issue_62350() { test_62350(); } </ins> fn test1() { unsafe { let q = Quad { a: 0xaaaa_aaaa_aaaa_aaaa,

extern_return_twou64s(); foreign_fn_with_byval(); issue_28676(); <ins> issue_62350(); </ins> struct_return(); }

<ins> // run-pass #![allow(dead_code)] #![allow(improper_ctypes)] // ignore-wasm32-bare no libc to test ffi with #[derive(Copy, Clone)] pub struct QuadFloats { a: f32, b: f32, c: f32, d: f32 } mod rustrt { use super::QuadFloats; #[link(name = "rust_test_helpers", kind = "static")] extern { pub fn get_c_exhaust_sysv64_ints( _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), _: *const (), h: QuadFloats, ) -> f32; } } fn test() { unsafe { let null = std::ptr::null(); let q = QuadFloats { a: 10.2, b: 20.3, c: 30.4, d: 40.5 }; assert_eq!( rustrt::get_c_exhaust_sysv64_ints(null, null, null, null, null, null, null, q), q.c, ); } } pub fn main() { test(); } </ins>