Change:
I tried this code: I expected to see this happen: test suite passing Instead, this happened: The return failure is: The pass failure is: : This occurs in every version since the test was . I see this with 1.79.0 and 1.80.0 as well as my 1.81.0 beta build (). I haven't dug in to all of the nitty-gritty yet, but I have a feeling may be potentially incorrect?
Indeed, if I remove those two lines, it passes the tests correctly: But I'm not sure why they are there and if this could possibly cause breakage elsewhere. I'd be happy to open a PR with this change for comment if that is the best way forward, or we can continue to discuss here.
Indeed, there is fallout - this test used to pass:
Reading over the ELF ABI specification and how LLVM and GCC implement this, arguments and return values should be treated the same. So, trying to duplicate the logic from in yielded me: and gave me: which is actually the first time I've ever seen Rust complete its test suite fully on a big endian PPC64/musl environment! Since the logic doesn't vary between endian in , I'm not sure why it should in . Unfortunately, I don't have any spare hardware to try and run little-endian, so I can't test if this change actually does the right thing on LE. I suppose since the original code had a BE case which was suspiciously similar to the first half of , I could do the code as another branch there. But that feels ugly, and I'd like to know if this simpler way would work on both endians before trying to shoehorn deeper nested branches.

Discussion:
}) } <del> fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>, abi: ABI) </del> <ins> fn classify<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, abi: ABI, is_ret: bool) </ins> where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { <del> if !ret.layout.is_sized() { </del> <ins> if arg.is_ignore() || !arg.layout.is_sized() { </ins> // Not touching this... return; } <del> if !ret.layout.is_aggregate() { ret.extend_integer_width_to(64); </del> <ins> if !arg.layout.is_aggregate() { arg.extend_integer_width_to(64); </ins> return; } // The ELFv1 ABI doesn't return aggregates in registers <del> if abi == ELFv1 { ret.make_indirect(); return; } if let Some(uniform) = is_homogeneous_aggregate(cx, ret, abi) { ret.cast_to(uniform); return; } let size = ret.layout.size; let bits = size.bits(); if bits <= 128 { let unit = if cx.data_layout().endian == Endian::Big { Reg { kind: RegKind::Integer, size } } else if bits <= 8 { Reg::i8() } else if bits <= 16 { Reg::i16() } else if bits <= 32 { Reg::i32() } else { Reg::i64() }; ret.cast_to(Uniform::new(unit, size)); return; } ret.make_indirect(); } fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, abi: ABI) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !arg.layout.is_sized() { // Not touching this... return; } if !arg.layout.is_aggregate() { arg.extend_integer_width_to(64); </del> <ins> if is_ret && abi == ELFv1 { arg.make_indirect(); </ins> return; }

} let size = arg.layout.size; <del> if size.bits() <= 64 { </del> <ins> if is_ret && size.bits() > 128 { // Non-homogeneous aggregates larger than two doublewords are returned indirectly. arg.make_indirect(); } else if size.bits() <= 64 { </ins> // Aggregates smaller than a doubleword should appear in // the least-significant bits of the parameter doubleword. arg.cast_to(Reg { kind: RegKind::Integer, size })

} }; <del> if !fn_abi.ret.is_ignore() { classify_ret(cx, &mut fn_abi.ret, abi); } </del> <ins> classify(cx, &mut fn_abi.ret, abi, true); </ins> for arg in fn_abi.args.iter_mut() { <del> if arg.is_ignore() { continue; } classify_arg(cx, arg, abi); </del> <ins> classify(cx, arg, abi, false); </ins> } }

<ins> //@ revisions: elfv1-be elfv2-be elfv2-le //@ assembly-output: emit-asm //@ compile-flags: -O //@[elfv1-be] compile-flags: --target powerpc64-unknown-linux-gnu //@[elfv1-be] needs-llvm-components: powerpc //@[elfv2-be] compile-flags: --target powerpc64-unknown-linux-musl //@[elfv2-be] needs-llvm-components: powerpc //@[elfv2-le] compile-flags: --target powerpc64le-unknown-linux-gnu //@[elfv2-le] needs-llvm-components: powerpc //@[elfv1-be] filecheck-flags: --check-prefix be //@[elfv2-be] filecheck-flags: --check-prefix be #![feature(no_core, lang_items)] #![no_std] #![no_core] #![crate_type = "lib"] #[lang = "sized"] trait Sized {} #[lang = "copy"] trait Copy {} #[lang = "freeze"] trait Freeze {} #[lang = "unpin"] trait Unpin {} impl Copy for u8 {} impl Copy for u16 {} impl Copy for u32 {} impl Copy for FiveU32s {} impl Copy for FiveU16s {} impl Copy for ThreeU8s {} #[repr(C)] struct FiveU32s(u32, u32, u32, u32, u32); #[repr(C)] struct FiveU16s(u16, u16, u16, u16, u16); #[repr(C)] struct ThreeU8s(u8, u8, u8); // CHECK-LABEL: read_large // be: lwz [[REG1:.*]], 16(4) // be-NEXT: stw [[REG1]], 16(3) // be-NEXT: ld [[REG2:.*]], 8(4) // be-NEXT: ld [[REG3:.*]], 0(4) // be-NEXT: std [[REG2]], 8(3) // be-NEXT: std [[REG3]], 0(3) // elfv2-le: lxvd2x [[REG1:.*]], 0, 4 // elfv2-le-NEXT: lwz [[REG2:.*]], 16(4) // elfv2-le-NEXT: stw [[REG2]], 16(3) // elfv2-le-NEXT: stxvd2x [[REG1]], 0, 3 // CHECK-NEXT: blr #[no_mangle] extern "C" fn read_large(x: &FiveU32s) -> FiveU32s { *x } // CHECK-LABEL: read_medium // elfv1-be: lhz [[REG1:.*]], 8(4) // elfv1-be-NEXT: ld [[REG2:.*]], 0(4) // elfv1-be-NEXT: sth [[REG1]], 8(3) // elfv1-be-NEXT: std [[REG2]], 0(3) // elfv2-be: lhz [[REG1:.*]], 8(3) // elfv2-be-NEXT: ld 3, 0(3) // elfv2-be-NEXT: sldi 4, [[REG1]], 48 // elfv2-le: ld [[REG1:.*]], 0(3) // elfv2-le-NEXT: lhz 4, 8(3) // elfv2-le-NEXT: mr 3, [[REG1]] // CHECK-NEXT: blr #[no_mangle] extern "C" fn read_medium(x: &FiveU16s) -> FiveU16s { *x } // CHECK-LABEL: read_small // elfv1-be: lbz [[REG1:.*]], 2(4) // elfv1-be-NEXT: lhz [[REG2:.*]], 0(4) // elfv1-be-NEXT: stb [[REG1]], 2(3) // elfv1-be-NEXT: sth [[REG2]], 0(3) // elfv2-be: lhz [[REG1:.*]], 0(3) // elfv2-be-NEXT: lbz 3, 2(3) // elfv2-be-NEXT: rldimi 3, [[REG1]], 8, 0 // elfv2-le: lbz [[REG1:.*]], 2(3) // elfv2-le-NEXT: lhz 3, 0(3) // elfv2-le-NEXT: rldimi 3, [[REG1]], 16, 0 // CHECK-NEXT: blr #[no_mangle] extern "C" fn read_small(x: &ThreeU8s) -> ThreeU8s { *x } // CHECK-LABEL: write_large // CHECK: std 3, 0(6) // be-NEXT: rldicl [[REG1:.*]], 5, 32, 32 // CHECK-NEXT: std 4, 8(6) // be-NEXT: stw [[REG1]], 16(6) // elfv2-le-NEXT: stw 5, 16(6) // CHECK-NEXT: blr #[no_mangle] extern "C" fn write_large(x: FiveU32s, dest: &mut FiveU32s) { *dest = x; } // CHECK-LABEL: write_medium // CHECK: std 3, 0(5) // be-NEXT: rldicl [[REG1:.*]], 4, 16, 48 // be-NEXT: sth [[REG1]], 8(5) // elfv2-le-NEXT: sth 4, 8(5) // CHECK-NEXT: blr #[no_mangle] extern "C" fn write_medium(x: FiveU16s, dest: &mut FiveU16s) { *dest = x; } // CHECK-LABEL: write_small // be: stb 3, 2(4) // be-NEXT: srwi [[REG1:.*]], 3, 8 // be-NEXT: sth [[REG1]], 0(4) // The order these instructions are emitted in changed in LLVM 18. // elfv2-le-DAG: sth 3, 0(4) // elfv2-le-DAG: srwi [[REG1:.*]], 3, 16 // elfv2-le-NEXT: stb [[REG1]], 2(4) // CHECK-NEXT: blr #[no_mangle] extern "C" fn write_small(x: ThreeU8s, dest: &mut ThreeU8s) { *dest = x; } </ins>