Closed
Description
The following three functions are the same semantically - they pack two u64
s into 16 bytes in big-endian order, but produce different assembly:
// Naive, and, I assume, idiomatic implementation. Bad assembly with several needless moves.
pub fn test(tup: (u64, u64)) -> [u8; 16] {
let first = (tup.0).to_be_bytes();
let second = (tup.1).to_be_bytes();
let mut key = [0u8; 16];
key[..8].copy_from_slice(&first[..]);
key[8..].copy_from_slice(&second[..]);
key
}
// Unsafe implementation. Good assembly .
pub fn test2(tup: (u64, u64)) -> [u8; 16] {
let first: [u8; 8] = (tup.0).to_be_bytes();
let second: [u8; 8] = (tup.1).to_be_bytes();
let packed: [[u8;8]; 2] = [first, second];
let key = unsafe { std::mem::transmute(packed) } ;
key
}
// No unsafe, good assembly .
pub fn test3(tup: (u64, u64)) -> [u8; 16] {
let first = (tup.0).to_be_bytes();
let second = (tup.1).to_be_bytes();
let mut key = [0u8; 16];
let (f, s) = key.split_at_mut(8);
f.copy_from_slice(&first[..]);
s.copy_from_slice(&second[..]);
key
}
Credits to @memoryruins for coming up with test3
.
Godbolt link: https://godbolt.org/z/4QaBp4
It is surprising that naive implementation (test1
) produces worse assembly than the other two. I was told I should report this here as a bug.
Meta
rustc 1.42.0 (as reported by godbolt)