cranelift: Add big and little endian memory accesses to interpreter (#5893)

* Added `mem_flags` parameter to `State::checked_{load,store}` as the means
for determining the endianness, typically derived from an instruction.

* Added `native_endianness` property to `InterpreterState` as fallback when
determining endianness, such as in cases where there are no memory flags
avaiable or set.

* Added `to_be` and `to_le` methods to `DataValue`.

* Added `AtomicCas` and `AtomicRmw` to list of instructions with retrievable
memory flags for `InstructionData::memflags`.

* Enabled `atomic-{cas,rmw}-subword-{big,little}.clif` for interpreter run
tests.
This commit is contained in:
Jan-Justin van Tonder
2023-03-02 12:57:01 +01:00
committed by GitHub
parent 9984e959cd
commit db8fe0108f
10 changed files with 183 additions and 40 deletions

View File

@@ -101,12 +101,55 @@ impl DataValue {
}
}
/// Write a [DataValue] to a slice.
fn swap_bytes(self) -> Self {
match self {
DataValue::I8(i) => DataValue::I8(i.swap_bytes()),
DataValue::I16(i) => DataValue::I16(i.swap_bytes()),
DataValue::I32(i) => DataValue::I32(i.swap_bytes()),
DataValue::I64(i) => DataValue::I64(i.swap_bytes()),
DataValue::I128(i) => DataValue::I128(i.swap_bytes()),
DataValue::U8(i) => DataValue::U8(i.swap_bytes()),
DataValue::U16(i) => DataValue::U16(i.swap_bytes()),
DataValue::U32(i) => DataValue::U32(i.swap_bytes()),
DataValue::U64(i) => DataValue::U64(i.swap_bytes()),
DataValue::U128(i) => DataValue::U128(i.swap_bytes()),
DataValue::F32(f) => DataValue::F32(Ieee32::with_bits(f.bits().swap_bytes())),
DataValue::F64(f) => DataValue::F64(Ieee64::with_bits(f.bits().swap_bytes())),
DataValue::V128(mut v) => {
v.reverse();
DataValue::V128(v)
}
DataValue::V64(mut v) => {
v.reverse();
DataValue::V64(v)
}
}
}
/// Converts `self` to big endian from target's endianness.
pub fn to_be(self) -> Self {
if cfg!(target_endian = "big") {
self
} else {
self.swap_bytes()
}
}
/// Converts `self` to little endian from target's endianness.
pub fn to_le(self) -> Self {
if cfg!(target_endian = "little") {
self
} else {
self.swap_bytes()
}
}
/// Write a [DataValue] to a slice in native-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn write_to_slice(&self, dst: &mut [u8]) {
pub fn write_to_slice_ne(&self, dst: &mut [u8]) {
match self {
DataValue::I8(i) => dst[..1].copy_from_slice(&i.to_ne_bytes()[..]),
DataValue::I16(i) => dst[..2].copy_from_slice(&i.to_ne_bytes()[..]),
@@ -121,12 +164,30 @@ impl DataValue {
};
}
/// Read a [DataValue] from a slice using a given [Type].
/// Write a [DataValue] to a slice in big-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn read_from_slice(src: &[u8], ty: Type) -> Self {
pub fn write_to_slice_be(&self, dst: &mut [u8]) {
self.clone().to_be().write_to_slice_ne(dst);
}
/// Write a [DataValue] to a slice in little-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn write_to_slice_le(&self, dst: &mut [u8]) {
self.clone().to_le().write_to_slice_ne(dst);
}
/// Read a [DataValue] from a slice using a given [Type] with native-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn read_from_slice_ne(src: &[u8], ty: Type) -> Self {
match ty {
types::I8 => DataValue::I8(i8::from_ne_bytes(src[..1].try_into().unwrap())),
types::I16 => DataValue::I16(i16::from_ne_bytes(src[..2].try_into().unwrap())),
@@ -152,15 +213,33 @@ impl DataValue {
}
}
/// Write a [DataValue] to a memory location.
pub unsafe fn write_value_to(&self, p: *mut u128) {
let size = self.ty().bytes() as usize;
self.write_to_slice(std::slice::from_raw_parts_mut(p as *mut u8, size));
/// Read a [DataValue] from a slice using a given [Type] in big-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn read_from_slice_be(src: &[u8], ty: Type) -> Self {
DataValue::read_from_slice_ne(src, ty).to_be()
}
/// Read a [DataValue] from a memory location using a given [Type].
/// Read a [DataValue] from a slice using a given [Type] in little-endian byte order.
///
/// # Panics:
///
/// Panics if the slice does not have enough space to accommodate the [DataValue]
pub fn read_from_slice_le(src: &[u8], ty: Type) -> Self {
DataValue::read_from_slice_ne(src, ty).to_le()
}
/// Write a [DataValue] to a memory location in native-endian byte order.
pub unsafe fn write_value_to(&self, p: *mut u128) {
let size = self.ty().bytes() as usize;
self.write_to_slice_ne(std::slice::from_raw_parts_mut(p as *mut u8, size));
}
/// Read a [DataValue] from a memory location using a given [Type] in native-endian byte order.
pub unsafe fn read_value_from(p: *const u128, ty: Type) -> Self {
DataValue::read_from_slice(
DataValue::read_from_slice_ne(
std::slice::from_raw_parts(p as *const u8, ty.bytes() as usize),
ty,
)

View File

@@ -403,7 +403,9 @@ impl InstructionData {
&InstructionData::Load { flags, .. }
| &InstructionData::LoadNoOffset { flags, .. }
| &InstructionData::Store { flags, .. }
| &InstructionData::StoreNoOffset { flags, .. } => Some(flags),
| &InstructionData::StoreNoOffset { flags, .. }
| &InstructionData::AtomicCas { flags, .. }
| &InstructionData::AtomicRmw { flags, .. } => Some(flags),
_ => None,
}
}