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:
committed by
GitHub
parent
9984e959cd
commit
db8fe0108f
@@ -11,8 +11,8 @@ use crate::step::{step, ControlFlow, StepError};
|
||||
use crate::value::{Value, ValueError};
|
||||
use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_codegen::ir::{
|
||||
ArgumentPurpose, Block, ExternalName, FuncRef, Function, GlobalValue, GlobalValueData, LibCall,
|
||||
StackSlot, TrapCode, Type, Value as ValueRef,
|
||||
ArgumentPurpose, Block, Endianness, ExternalName, FuncRef, Function, GlobalValue,
|
||||
GlobalValueData, LibCall, MemFlags, StackSlot, TrapCode, Type, Value as ValueRef,
|
||||
};
|
||||
use log::trace;
|
||||
use smallvec::SmallVec;
|
||||
@@ -192,10 +192,16 @@ pub struct InterpreterState<'a> {
|
||||
pub frame_offset: usize,
|
||||
pub stack: Vec<u8>,
|
||||
pub pinned_reg: DataValue,
|
||||
pub native_endianness: Endianness,
|
||||
}
|
||||
|
||||
impl Default for InterpreterState<'_> {
|
||||
fn default() -> Self {
|
||||
let native_endianness = if cfg!(target_endian = "little") {
|
||||
Endianness::Little
|
||||
} else {
|
||||
Endianness::Big
|
||||
};
|
||||
Self {
|
||||
functions: FunctionStore::default(),
|
||||
libcall_handler: |_, _| Err(TrapCode::UnreachableCodeReached),
|
||||
@@ -203,6 +209,7 @@ impl Default for InterpreterState<'_> {
|
||||
frame_offset: 0,
|
||||
stack: Vec::with_capacity(1024),
|
||||
pinned_reg: DataValue::U64(0),
|
||||
native_endianness,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +315,12 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
||||
Address::from_parts(size, AddressRegion::Stack, 0, final_offset)
|
||||
}
|
||||
|
||||
fn checked_load(&self, addr: Address, ty: Type) -> Result<DataValue, MemoryError> {
|
||||
fn checked_load(
|
||||
&self,
|
||||
addr: Address,
|
||||
ty: Type,
|
||||
mem_flags: MemFlags,
|
||||
) -> Result<DataValue, MemoryError> {
|
||||
let load_size = ty.bytes() as usize;
|
||||
let addr_start = addr.offset as usize;
|
||||
let addr_end = addr_start + load_size;
|
||||
@@ -324,10 +336,18 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
Ok(DataValue::read_from_slice(src, ty))
|
||||
Ok(match mem_flags.endianness(self.native_endianness) {
|
||||
Endianness::Big => DataValue::read_from_slice_be(src, ty),
|
||||
Endianness::Little => DataValue::read_from_slice_le(src, ty),
|
||||
})
|
||||
}
|
||||
|
||||
fn checked_store(&mut self, addr: Address, v: DataValue) -> Result<(), MemoryError> {
|
||||
fn checked_store(
|
||||
&mut self,
|
||||
addr: Address,
|
||||
v: DataValue,
|
||||
mem_flags: MemFlags,
|
||||
) -> Result<(), MemoryError> {
|
||||
let store_size = v.ty().bytes() as usize;
|
||||
let addr_start = addr.offset as usize;
|
||||
let addr_end = addr_start + store_size;
|
||||
@@ -343,7 +363,10 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
Ok(v.write_to_slice(dst))
|
||||
Ok(match mem_flags.endianness(self.native_endianness) {
|
||||
Endianness::Big => v.write_to_slice_be(dst),
|
||||
Endianness::Little => v.write_to_slice_le(dst),
|
||||
})
|
||||
}
|
||||
|
||||
fn function_address(
|
||||
@@ -493,9 +516,10 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
||||
global_type,
|
||||
}) => {
|
||||
let mut addr = Address::try_from(current_val)?;
|
||||
let mem_flags = MemFlags::trusted();
|
||||
// We can forego bounds checking here since its performed in `checked_load`
|
||||
addr.offset += offset as u64;
|
||||
current_val = self.checked_load(addr, global_type)?;
|
||||
current_val = self.checked_load(addr, global_type, mem_flags)?;
|
||||
}
|
||||
|
||||
// We are done resolving this, return the current value
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::address::{Address, AddressSize};
|
||||
use crate::interpreter::LibCallHandler;
|
||||
use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_codegen::ir::{
|
||||
ExternalName, FuncRef, Function, GlobalValue, LibCall, Signature, StackSlot, Type, Value,
|
||||
ExternalName, FuncRef, Function, GlobalValue, LibCall, MemFlags, Signature, StackSlot, Type,
|
||||
Value,
|
||||
};
|
||||
use cranelift_codegen::isa::CallConv;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
@@ -62,10 +63,20 @@ pub trait State<'a, V> {
|
||||
) -> Result<Address, MemoryError>;
|
||||
/// Retrieve a value `V` from memory at the given `address`, checking if it belongs either to the
|
||||
/// stack or to one of the heaps; the number of bytes loaded corresponds to the specified [Type].
|
||||
fn checked_load(&self, address: Address, ty: Type) -> Result<V, MemoryError>;
|
||||
fn checked_load(
|
||||
&self,
|
||||
address: Address,
|
||||
ty: Type,
|
||||
mem_flags: MemFlags,
|
||||
) -> Result<V, MemoryError>;
|
||||
/// Store a value `V` into memory at the given `address`, checking if it belongs either to the
|
||||
/// stack or to one of the heaps; the number of bytes stored corresponds to the specified [Type].
|
||||
fn checked_store(&mut self, address: Address, v: V) -> Result<(), MemoryError>;
|
||||
fn checked_store(
|
||||
&mut self,
|
||||
address: Address,
|
||||
v: V,
|
||||
mem_flags: MemFlags,
|
||||
) -> Result<(), MemoryError>;
|
||||
|
||||
/// Compute the address of a function given its name.
|
||||
fn function_address(
|
||||
@@ -182,11 +193,21 @@ where
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn checked_load(&self, _addr: Address, _ty: Type) -> Result<V, MemoryError> {
|
||||
fn checked_load(
|
||||
&self,
|
||||
_addr: Address,
|
||||
_ty: Type,
|
||||
_mem_flags: MemFlags,
|
||||
) -> Result<V, MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn checked_store(&mut self, _addr: Address, _v: V) -> Result<(), MemoryError> {
|
||||
fn checked_store(
|
||||
&mut self,
|
||||
_addr: Address,
|
||||
_v: V,
|
||||
_mem_flags: MemFlags,
|
||||
) -> Result<(), MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
||||
use cranelift_codegen::ir::{
|
||||
types, AbiParam, AtomicRmwOp, Block, BlockCall, ExternalName, FuncRef, Function,
|
||||
InstructionData, Opcode, TrapCode, Type, Value as ValueRef,
|
||||
InstructionData, MemFlags, Opcode, TrapCode, Type, Value as ValueRef,
|
||||
};
|
||||
use log::trace;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
@@ -482,8 +482,10 @@ where
|
||||
};
|
||||
|
||||
let addr_value = calculate_addr(types::I64, imm(), args()?)?;
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
let loaded = assign_or_memtrap(
|
||||
Address::try_from(addr_value).and_then(|addr| state.checked_load(addr, load_ty)),
|
||||
Address::try_from(addr_value)
|
||||
.and_then(|addr| state.checked_load(addr, load_ty, mem_flags)),
|
||||
);
|
||||
|
||||
match (loaded, kind) {
|
||||
@@ -505,33 +507,37 @@ where
|
||||
};
|
||||
|
||||
let addr_value = calculate_addr(types::I64, imm(), args_range(1..)?)?;
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
let reduced = if let Some(c) = kind {
|
||||
arg(0)?.convert(c)?
|
||||
} else {
|
||||
arg(0)?
|
||||
};
|
||||
continue_or_memtrap(
|
||||
Address::try_from(addr_value).and_then(|addr| state.checked_store(addr, reduced)),
|
||||
Address::try_from(addr_value)
|
||||
.and_then(|addr| state.checked_store(addr, reduced, mem_flags)),
|
||||
)
|
||||
}
|
||||
Opcode::StackLoad => {
|
||||
let load_ty = inst_context.controlling_type().unwrap();
|
||||
let slot = inst.stack_slot().unwrap();
|
||||
let offset = sum(imm(), args()?)? as u64;
|
||||
let mem_flags = MemFlags::trusted();
|
||||
assign_or_memtrap({
|
||||
state
|
||||
.stack_address(AddressSize::_64, slot, offset)
|
||||
.and_then(|addr| state.checked_load(addr, load_ty))
|
||||
.and_then(|addr| state.checked_load(addr, load_ty, mem_flags))
|
||||
})
|
||||
}
|
||||
Opcode::StackStore => {
|
||||
let arg = arg(0)?;
|
||||
let slot = inst.stack_slot().unwrap();
|
||||
let offset = sum(imm(), args_range(1..)?)? as u64;
|
||||
let mem_flags = MemFlags::trusted();
|
||||
continue_or_memtrap({
|
||||
state
|
||||
.stack_address(AddressSize::_64, slot, offset)
|
||||
.and_then(|addr| state.checked_store(addr, arg))
|
||||
.and_then(|addr| state.checked_store(addr, arg, mem_flags))
|
||||
})
|
||||
}
|
||||
Opcode::StackAddr => {
|
||||
@@ -1238,7 +1244,9 @@ where
|
||||
let op = inst.atomic_rmw_op().unwrap();
|
||||
let val = arg(1)?;
|
||||
let addr = arg(0)?.into_int()? as u64;
|
||||
let loaded = Address::try_from(addr).and_then(|addr| state.checked_load(addr, ctrl_ty));
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
let loaded = Address::try_from(addr)
|
||||
.and_then(|addr| state.checked_load(addr, ctrl_ty, mem_flags));
|
||||
let prev_val = match loaded {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Ok(ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e)))),
|
||||
@@ -1265,13 +1273,15 @@ where
|
||||
)
|
||||
.and_then(|v| Value::convert(v, ValueConversionKind::ToSigned)),
|
||||
}?;
|
||||
let stored =
|
||||
Address::try_from(addr).and_then(|addr| state.checked_store(addr, replace));
|
||||
let stored = Address::try_from(addr)
|
||||
.and_then(|addr| state.checked_store(addr, replace, mem_flags));
|
||||
assign_or_memtrap(stored.map(|_| prev_val_to_assign))
|
||||
}
|
||||
Opcode::AtomicCas => {
|
||||
let addr = arg(0)?.into_int()? as u64;
|
||||
let loaded = Address::try_from(addr).and_then(|addr| state.checked_load(addr, ctrl_ty));
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
let loaded = Address::try_from(addr)
|
||||
.and_then(|addr| state.checked_load(addr, ctrl_ty, mem_flags));
|
||||
let loaded_val = match loaded {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Ok(ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e)))),
|
||||
@@ -1280,7 +1290,7 @@ where
|
||||
let val_to_assign = if Value::eq(&loaded_val, &expected_val)? {
|
||||
let val_to_store = arg(2)?;
|
||||
Address::try_from(addr)
|
||||
.and_then(|addr| state.checked_store(addr, val_to_store))
|
||||
.and_then(|addr| state.checked_store(addr, val_to_store, mem_flags))
|
||||
.map(|_| loaded_val)
|
||||
} else {
|
||||
Ok(loaded_val)
|
||||
@@ -1290,17 +1300,20 @@ where
|
||||
Opcode::AtomicLoad => {
|
||||
let load_ty = inst_context.controlling_type().unwrap();
|
||||
let addr = arg(0)?.into_int()? as u64;
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
// We are doing a regular load here, this isn't actually thread safe.
|
||||
assign_or_memtrap(
|
||||
Address::try_from(addr).and_then(|addr| state.checked_load(addr, load_ty)),
|
||||
Address::try_from(addr)
|
||||
.and_then(|addr| state.checked_load(addr, load_ty, mem_flags)),
|
||||
)
|
||||
}
|
||||
Opcode::AtomicStore => {
|
||||
let val = arg(0)?;
|
||||
let addr = arg(1)?.into_int()? as u64;
|
||||
let mem_flags = inst.memflags().expect("instruction to have memory flags");
|
||||
// We are doing a regular store here, this isn't actually thread safe.
|
||||
continue_or_memtrap(
|
||||
Address::try_from(addr).and_then(|addr| state.checked_store(addr, val)),
|
||||
Address::try_from(addr).and_then(|addr| state.checked_store(addr, val, mem_flags)),
|
||||
)
|
||||
}
|
||||
Opcode::Fence => {
|
||||
|
||||
Reference in New Issue
Block a user