You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This seems to be an oversight in the implementation (this also means the tests are probably doing something wrong too, they compare output with clang, it shouldn't have been passing for aarch64_be)
These intrinsics were doing some unnecessary swizzling but we're not sure if removing this has changed their behaviour. Our tests pass before and after the above fix and they appear to be working correctly.
Can I confirm that you're running on a BE platform and that the above fix works for you @usamoi ? (it hasn't made it into nightly rust yet, happy to wait until it does)
Hey Arm-interested people! This issue could use some guidance on how it can be
resolved on Arm platforms.
Could one of you weigh in please? In case it's useful, here are some instructions for tackling these sorts of bugs.
Thanks!
If a vector is loaded and then bitconverted to a different vector type before being stored, the round trip will currently be broken.
[... follow the image on the right]
This would produce a code sequence such as that in the figure on the right. The mismatched LD1 and ST1 cause the stored data to differ from the loaded data.
Note2: Transmute:
copies the bits from the source value into the destination value"
element 0 uses the least significant bits of the register
thus lane 0 is always the least significant bits of the register regardless of endianness. docs
Note4: vreinterpretq_u16_u8 Loads into Vd.16B and stores in Vd.8H changing the arrangement specifier from 16B (16bytes) to 8H (8 16bit halfwords). Also see Arms documentation here for some more examples and more indepth detail.
Tracing the code
Note: byte_view is the bytes produced with a hex dump NOT the numbers printed as hexidecimal.
To make things slightly easier to follow in the annotated code below I have expanded;
let y:[u8;16] = std::mem::transmute(f(x));
To
let z = f(x);let y:[u8;16] = std::mem::transmute(z);
//start_bytes = 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f// no byte swapping - just defining the arraylet src = [0_u8,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];// no byte swapping - This is because the resultant type is essentially the same as above// byte_view = 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0flet x:uint8x16_t = core::mem::transmute(src);// ld1 { v0.16b } -> st1 { v0.8h }// We are interpreting each pair of bytes as a big-endian halfwords;// [00 01] [02 03] [04 05] [06 07] [08 09] [0a 0b] [0c 0d] [0e 0f]// numerical_view = [256, 770, 1284, 1798, 2312, 2826, 3340, 3854]// which becomes in memory (because of big-endian element reordering rules)// byte_view = 01 00 03 02 05 04 07 06 09 08 0b 0a 0d 0c 0f 0elet z = f(x);// vreinterpretq_u16_u8(x);// no byte swapping - Note that this matches to LLVMs documentation of// "the round trip will currently be broken"// byte_view = 01 00 03 02 05 04 07 06 09 08 0b 0a 0d 0c 0f 0e// numerical_view = [1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14]let y:[u8;16] = std::mem::transmute(z);
If we take x[0] = 0u8 and x[1] = 1u8, then in big-endian mode the register holds the bits as ...0x0100. Interpreting those bits in place as a u16 via vreinterpret yields the value 256. By contrast, transmute operates on the memory layout, where the bytes appear as 0x00 01, producing the value 1. This explains why what you are seeing, while surprising, is in fact correct behaviour according to the ARM and LLVM big-endian NEON models, even though it looks counter-intuitive at first glance. To get what you expect back you'd want to use another of the vreinterpret intrinsics as opposed to a transmute.
Activity
sayantn commentedon Sep 6, 2025
This seems to be an oversight in the implementation (this also means the tests are probably doing something wrong too, they compare output with clang, it shouldn't have been passing for aarch64_be)
adamgemmell commentedon Sep 15, 2025
These intrinsics were doing some unnecessary swizzling but we're not sure if removing this has changed their behaviour. Our tests pass before and after the above fix and they appear to be working correctly.
Can I confirm that you're running on a BE platform and that the above fix works for you @usamoi ? (it hasn't made it into nightly rust yet, happy to wait until it does)
usamoi commentedon Sep 15, 2025
I don't have a aarch64_be machine, but the output becomes correct on qemu now.
And,
objdumpshows it's compiled to anopnow.sayantn commentedon Sep 22, 2025
#1924 reverts the PR because it made the testsuites fail, see #1923. We would probably need to discuss where it is going wrong
sayantn commentedon Oct 5, 2025
@rustbot ping arm
pinging people with more ARM expertise
rustbot commentedon Oct 5, 2025
Hey Arm-interested people! This issue could use some guidance on how it can be
resolved on Arm platforms.
Could one of you weigh in please? In case it's useful, here are some
instructions for tackling these sorts of bugs.
Thanks!
cc @adamgemmell @davidtwco @hug-dev @jacobbramley @JamieCunliffe @joaopaulocarreiro @raw-bin @Stammark
Jamesbarford commentedon Nov 19, 2025
Helpful information from the docs
Note1: LLVM Documentation (https://llvm.org/docs/BigEndianNEON.html#bitconverts);
Note2: Transmute:
docs
Note3: On lanes:
thus lane 0 is always the least significant bits of the register regardless of endianness. docs
Note4:
vreinterpretq_u16_u8Loads intoVd.16Band stores inVd.8Hchanging the arrangement specifier from16B(16bytes) to8H(8 16bit halfwords). Also see Arms documentation here for some more examples and more indepth detail.Tracing the code
Note:
byte_viewis the bytes produced with a hex dump NOT the numbers printed as hexidecimal.To make things slightly easier to follow in the annotated code below I have expanded;
To
If we take
x[0] = 0u8andx[1] = 1u8, then in big-endian mode the register holds the bits as...0x0100. Interpreting those bits in place as au16viavreinterpretyields the value256. By contrast, transmute operates on the memory layout, where the bytes appear as0x00 01, producing the value1. This explains why what you are seeing, while surprising, is in fact correct behaviour according to the ARM and LLVM big-endian NEON models, even though it looks counter-intuitive at first glance. To get what you expect back you'd want to use another of thevreinterpretintrinsics as opposed to atransmute.usamoi commentedon Nov 19, 2025
@Jamesbarford Thank you for the explanation! Since it's expected, I'm closing this issue.