diff --git a/cranelift/codegen/src/data_value.rs b/cranelift/codegen/src/data_value.rs index 250d70ccbf..7f4d1b15a7 100644 --- a/cranelift/codegen/src/data_value.rs +++ b/cranelift/codegen/src/data_value.rs @@ -4,7 +4,6 @@ use crate::ir::immediates::{Ieee32, Ieee64, Offset32}; use crate::ir::{types, ConstantData, Type}; use core::convert::TryInto; use core::fmt::{self, Display, Formatter}; -use core::ptr; /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value /// that would be referred to by a [Value]. @@ -74,36 +73,75 @@ impl DataValue { } } - /// Write a [DataValue] to a memory location. - pub unsafe fn write_value_to(&self, p: *mut u128) { + /// Write a [DataValue] to a slice. + /// + /// # Panics: + /// + /// Panics if the slice does not have enough space to accommodate the [DataValue] + pub fn write_to_slice(&self, dst: &mut [u8]) { match self { - DataValue::B(b) => ptr::write(p, if *b { -1i128 as u128 } else { 0u128 }), - DataValue::I8(i) => ptr::write(p as *mut i8, *i), - DataValue::I16(i) => ptr::write(p as *mut i16, *i), - DataValue::I32(i) => ptr::write(p as *mut i32, *i), - DataValue::I64(i) => ptr::write(p as *mut i64, *i), - DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), - DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), - DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), + DataValue::B(true) => dst[..16].copy_from_slice(&[u8::MAX; 16][..]), + DataValue::B(false) => dst[..16].copy_from_slice(&[0; 16][..]), + DataValue::I8(i) => dst[..1].copy_from_slice(&i.to_le_bytes()[..]), + DataValue::I16(i) => dst[..2].copy_from_slice(&i.to_le_bytes()[..]), + DataValue::I32(i) => dst[..4].copy_from_slice(&i.to_le_bytes()[..]), + DataValue::I64(i) => dst[..8].copy_from_slice(&i.to_le_bytes()[..]), + DataValue::F32(f) => dst[..4].copy_from_slice(&f.bits().to_le_bytes()[..]), + DataValue::F64(f) => dst[..8].copy_from_slice(&f.bits().to_le_bytes()[..]), + DataValue::V128(v) => dst[..16].copy_from_slice(&v[..]), + _ => unimplemented!(), + }; + } + + /// Read a [DataValue] from a slice using a given [Type]. + /// + /// # Panics: + /// + /// Panics if the slice does not have enough space to accommodate the [DataValue] + pub fn read_from_slice(src: &[u8], ty: Type) -> Self { + match ty { + types::I8 => DataValue::I8(i8::from_le_bytes(src[..1].try_into().unwrap())), + types::I16 => DataValue::I16(i16::from_le_bytes(src[..2].try_into().unwrap())), + types::I32 => DataValue::I32(i32::from_le_bytes(src[..4].try_into().unwrap())), + types::I64 => DataValue::I64(i64::from_le_bytes(src[..8].try_into().unwrap())), + types::F32 => DataValue::F32(Ieee32::with_bits(u32::from_le_bytes( + src[..4].try_into().unwrap(), + ))), + types::F64 => DataValue::F64(Ieee64::with_bits(u64::from_le_bytes( + src[..8].try_into().unwrap(), + ))), + _ if ty.is_bool() => { + // Only `ty.bytes()` are guaranteed to be written + // so we can only test the first n bytes of `src` + + let size = ty.bytes() as usize; + DataValue::B(src[..size].iter().any(|&i| i != 0)) + } + _ if ty.is_vector() && ty.bytes() == 16 => { + DataValue::V128(src[..16].try_into().unwrap()) + } _ => unimplemented!(), } } + /// Write a [DataValue] to a memory location. + pub unsafe fn write_value_to(&self, p: *mut u128) { + // Since `DataValue` does not have type info for bools we always + // write out a full 16 byte slot. + let size = match self.ty() { + ty if ty.is_bool() => 16, + ty => ty.bytes() as usize, + }; + + self.write_to_slice(std::slice::from_raw_parts_mut(p as *mut u8, size)); + } + /// Read a [DataValue] from a memory location using a given [Type]. pub unsafe fn read_value_from(p: *const u128, ty: Type) -> Self { - match ty { - types::I8 => DataValue::I8(ptr::read(p as *const i8)), - types::I16 => DataValue::I16(ptr::read(p as *const i16)), - types::I32 => DataValue::I32(ptr::read(p as *const i32)), - types::I64 => DataValue::I64(ptr::read(p as *const i64)), - types::F32 => DataValue::F32(ptr::read(p as *const Ieee32)), - types::F64 => DataValue::F64(ptr::read(p as *const Ieee64)), - _ if ty.is_bool() => DataValue::B(ptr::read(p) != 0), - _ if ty.is_vector() && ty.bytes() == 16 => { - DataValue::V128(ptr::read(p as *const [u8; 16])) - } - _ => unimplemented!(), - } + DataValue::read_from_slice( + std::slice::from_raw_parts(p as *const u8, ty.bytes() as usize), + ty, + ) } } diff --git a/cranelift/codegen/src/ir/function.rs b/cranelift/codegen/src/ir/function.rs index 1b579e18a1..7b1c957518 100644 --- a/cranelift/codegen/src/ir/function.rs +++ b/cranelift/codegen/src/ir/function.rs @@ -421,6 +421,13 @@ impl Function { self.dfg[dst] = self.dfg[src].clone(); self.layout.remove_inst(src); } + + /// Size occupied by all stack slots associated with this function. + /// + /// Does not include any padding necessary due to offsets + pub fn stack_size(&self) -> u32 { + self.stack_slots.values().map(|ss| ss.size).sum() + } } /// Additional annotations for function display. diff --git a/cranelift/codegen/src/ir/instructions.rs b/cranelift/codegen/src/ir/instructions.rs index 4981ad8ddc..cd58976c11 100644 --- a/cranelift/codegen/src/ir/instructions.rs +++ b/cranelift/codegen/src/ir/instructions.rs @@ -23,7 +23,7 @@ use crate::ir::{ self, condcodes::{FloatCC, IntCC}, trapcode::TrapCode, - types, Block, FuncRef, JumpTable, MemFlags, SigRef, Type, Value, + types, Block, FuncRef, JumpTable, MemFlags, SigRef, StackSlot, Type, Value, }; use crate::isa; @@ -411,6 +411,15 @@ impl InstructionData { } } + /// If this instruction references a stack slot, return it + pub fn stack_slot(&self) -> Option { + match self { + &InstructionData::StackStore { stack_slot, .. } + | &InstructionData::StackLoad { stack_slot, .. } => Some(stack_slot), + _ => None, + } + } + /// Return information about a call instruction. /// /// Any instruction that can call another function reveals its call signature here. diff --git a/cranelift/filetests/filetests/runtests/stack-addr-32.clif b/cranelift/filetests/filetests/runtests/stack-addr-32.clif new file mode 100644 index 0000000000..d6a0ab8532 --- /dev/null +++ b/cranelift/filetests/filetests/runtests/stack-addr-32.clif @@ -0,0 +1,85 @@ +test interpret + +function %stack_addr_iadd(i64) -> b1 { + ss0 = explicit_slot 16 + +block0(v0: i64): + v1 = stack_addr.i32 ss0 + v2 = iadd_imm.i32 v1, 8 + + stack_store.i64 v0, ss0+8 + v3 = load.i64 v2 + + v5 = iadd_imm.i64 v0, 20 + store.i64 v5, v2 + v6 = stack_load.i64 ss0+8 + + v7 = icmp eq v0, v3 + v8 = icmp eq v5, v6 + v9 = band v7, v8 + return v9 +} +; run: %stack_addr_iadd(0) == true +; run: %stack_addr_iadd(1) == true +; run: %stack_addr_iadd(-1) == true + + +function %stack_addr_32(i64) -> b1 { + ss0 = explicit_slot 24 + +block0(v0: i64): + v1 = stack_addr.i32 ss0 + stack_store.i64 v0, ss0 + v2 = load.i64 v1 + v3 = icmp eq v0, v2 + + v4 = stack_addr.i32 ss0+8 + store.i64 v0, v4 + v5 = stack_load.i64 ss0+8 + v6 = icmp eq v0, v5 + + v7 = stack_addr.i32 ss0+16 + store.i64 v0, v7 + v8 = load.i64 v7 + v9 = icmp eq v0, v8 + + v10 = band v3, v6 + v11 = band v10, v9 + return v11 +} +; run: %stack_addr_32(0) == true +; run: %stack_addr_32(1) == true +; run: %stack_addr_32(-1) == true + + + +function %addr32_64(i64) -> b1 { + ss0 = explicit_slot 16 + +block0(v0: i64): + v1 = stack_addr.i32 ss0+8 + v2 = stack_addr.i64 ss0+8 + + store.i64 v0, v1 + v3 = load.i64 v2 + + v4 = icmp eq v3, v0 + + return v4 +} +; run: %addr32_64(0) == true +; run: %addr32_64(1) == true +; run: %addr32_64(-1) == true + + +function %multi_slot_different_addrs() -> b1 { + ss0 = explicit_slot 8 + ss1 = explicit_slot 8 + +block0: + v0 = stack_addr.i32 ss0 + v1 = stack_addr.i32 ss1 + v2 = icmp ne v0, v1 + return v2 +} +; run: %multi_slot_diffe() == true diff --git a/cranelift/filetests/filetests/runtests/stack-addr-64.clif b/cranelift/filetests/filetests/runtests/stack-addr-64.clif new file mode 100644 index 0000000000..0e59e7c410 --- /dev/null +++ b/cranelift/filetests/filetests/runtests/stack-addr-64.clif @@ -0,0 +1,56 @@ +test interpret +test run +target x86_64 machinst +target s390x +target aarch64 + + +function %stack_addr_iadd(i64) -> b1 { + ss0 = explicit_slot 16 + +block0(v0: i64): + v1 = stack_addr.i64 ss0 + v2 = iadd_imm.i64 v1, 8 + + stack_store.i64 v0, ss0+8 + v3 = load.i64 v2 + + v5 = iadd_imm.i64 v0, 20 + store.i64 v5, v2 + v6 = stack_load.i64 ss0+8 + + v7 = icmp eq v0, v3 + v8 = icmp eq v5, v6 + v9 = band v7, v8 + return v9 +} +; run: %stack_addr_iadd(0) == true +; run: %stack_addr_iadd(1) == true +; run: %stack_addr_iadd(-1) == true + +function %stack_addr_64(i64) -> b1 { + ss0 = explicit_slot 24 + +block0(v0: i64): + v1 = stack_addr.i64 ss0 + stack_store.i64 v0, ss0 + v2 = load.i64 v1 + v3 = icmp eq v0, v2 + + v4 = stack_addr.i64 ss0+8 + store.i64 v0, v4 + v5 = stack_load.i64 ss0+8 + v6 = icmp eq v0, v5 + + v7 = stack_addr.i64 ss0+16 + store.i64 v0, v7 + v8 = load.i64 v7 + v9 = icmp eq v0, v8 + + v10 = band v3, v6 + v11 = band v10, v9 + return v11 +} +; run: %stack_addr_64(0) == true +; run: %stack_addr_64(1) == true +; run: %stack_addr_64(-1) == true diff --git a/cranelift/filetests/filetests/runtests/stack.clif b/cranelift/filetests/filetests/runtests/stack.clif new file mode 100644 index 0000000000..0a7323c4d9 --- /dev/null +++ b/cranelift/filetests/filetests/runtests/stack.clif @@ -0,0 +1,130 @@ +test interpret +test run +target x86_64 machinst +target s390x +target aarch64 + +function %stack_simple(i64) -> i64 { + ss0 = explicit_slot 8 + +block0(v0: i64): + stack_store.i64 v0, ss0 + v1 = stack_load.i64 ss0 + return v1 +} +; run: %stack_simple(0) == 0 +; run: %stack_simple(1) == 1 +; run: %stack_simple(-1) == -1 + + +function %slot_offset(i64) -> i64 { + ss0 = explicit_slot 8, offset 8 + +block0(v0: i64): + stack_store.i64 v0, ss0 + v1 = stack_load.i64 ss0 + return v1 +} +; run: %slot_offset(0) == 0 +; run: %slot_offset(1) == 1 +; run: %slot_offset(-1) == -1 + +function %stack_offset(i64) -> i64 { + ss0 = explicit_slot 16 + +block0(v0: i64): + stack_store.i64 v0, ss0+8 + v1 = stack_load.i64 ss0+8 + return v1 +} +; run: %stack_offset(0) == 0 +; run: %stack_offset(1) == 1 +; run: %stack_offset(-1) == -1 + + +function %offset_unaligned(i64) -> i64 { + ss0 = explicit_slot 11 + +block0(v0: i64): + stack_store.i64 v0, ss0+3 + v1 = stack_load.i64 ss0+3 + return v1 +} +; run: %offset_unaligned(0) == 0 +; run: %offset_unaligned(1) == 1 +; run: %offset_unaligned(-1) == -1 + + + +function %multi_slot_stack(i64, i64) -> i64 { + ss0 = explicit_slot 8 + ss1 = explicit_slot 8 + +block0(v0: i64, v1: i64): + stack_store.i64 v0, ss0 + stack_store.i64 v1, ss1 + v2 = stack_load.i64 ss0 + v3 = stack_load.i64 ss1 + v4 = iadd.i64 v2, v3 + return v4 +} +; run: %multi_slot_stack(0, 1) == 1 +; run: %multi_slot_stack(1, 2) == 3 + + + +function %multi_slot_out_of_bounds_writes(i8, i64) -> i8, i64 { + ss0 = explicit_slot 1 + ss1 = explicit_slot 8 + +block0(v0: i8, v1: i64): + stack_store.i8 v0, ss0 + stack_store.i64 v1, ss1 + v2 = stack_load.i8 ss0 + v3 = stack_load.i64 ss1 + return v2, v3 +} +; run: %multi_slot_out_o(10, 1) == [10, 1] +; run: %multi_slot_out_o(0, 2) == [0, 2] + + +function %multi_slot_offset_writes(i8, i64) -> i8, i64 { + ss0 = explicit_slot 8, offset 8 + ss1 = explicit_slot 8 + +block0(v0: i8, v1: i64): + stack_store.i8 v0, ss0 + stack_store.i64 v1, ss1 + v2 = stack_load.i8 ss0 + v3 = stack_load.i64 ss1 + return v2, v3 +} +; run: %multi_slot_offse(0, 1) == [0, 1] +; run: %multi_slot_offse(1, 2) == [1, 2] + +function %slot_offset_negative(i64, i64) -> i64, i64 { + ss0 = explicit_slot 8 + ss1 = explicit_slot 8, offset -8 + +block0(v0: i64, v1: i64): + stack_store.i64 v0, ss0 + stack_store.i64 v1, ss1 + v2 = stack_load.i64 ss0 + v3 = stack_load.i64 ss1 + return v2, v3 +} +; run: %slot_offset_nega(0, 1) == [0, 1] +; run: %slot_offset_nega(2, 3) == [2, 3] + + +function %huge_slots(i64) -> i64 { + ss0 = explicit_slot 1048576 ; 1MB Slot + +block0(v0: i64): + stack_store.i64 v0, ss0+1048568 ; Store at 1MB - 8bytes + v1 = stack_load.i64 ss0+1048568 + return v1 +} +; run: %huge_slots(0) == 0 +; run: %huge_slots(1) == 1 +; run: %huge_slots(-1) == -1 diff --git a/cranelift/filetests/src/function_runner.rs b/cranelift/filetests/src/function_runner.rs index d4e20c39a8..1db16c2232 100644 --- a/cranelift/filetests/src/function_runner.rs +++ b/cranelift/filetests/src/function_runner.rs @@ -1,8 +1,7 @@ //! Provides functionality for compiling and running CLIF IR for `run` tests. -use core::{mem, ptr}; +use core::mem; use cranelift_codegen::binemit::{NullRelocSink, NullStackMapSink, NullTrapSink}; use cranelift_codegen::data_value::DataValue; -use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; use cranelift_codegen::ir::{condcodes::IntCC, Function, InstBuilder, Signature, Type}; use cranelift_codegen::isa::{BackendVariant, TargetIsa}; use cranelift_codegen::{ir, settings, CodegenError, Context}; @@ -204,7 +203,7 @@ impl UnboxedValues { param.value_type ); unsafe { - Self::write_value_to(arg, slot); + arg.write_value_to(slot); } } @@ -224,50 +223,12 @@ impl UnboxedValues { // Extract the returned values from this vector. for (slot, param) in self.0.iter().zip(&signature.returns) { - let value = unsafe { Self::read_value_from(slot, param.value_type) }; + let value = unsafe { DataValue::read_value_from(slot, param.value_type) }; returns.push(value); } returns } - - /// Write a [DataValue] to a memory location. - unsafe fn write_value_to(v: &DataValue, p: *mut u128) { - match v { - DataValue::B(b) => ptr::write(p as *mut bool, *b), - DataValue::I8(i) => ptr::write(p as *mut i8, *i), - DataValue::I16(i) => ptr::write(p as *mut i16, *i), - DataValue::I32(i) => ptr::write(p as *mut i32, *i), - DataValue::I64(i) => ptr::write(p as *mut i64, *i), - DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), - DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), - DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), - _ => unimplemented!(), - } - } - - /// Read a [DataValue] from a memory location using a given [Type]. - unsafe fn read_value_from(p: *const u128, ty: Type) -> DataValue { - match ty { - ir::types::I8 => DataValue::I8(ptr::read(p as *const i8)), - ir::types::I16 => DataValue::I16(ptr::read(p as *const i16)), - ir::types::I32 => DataValue::I32(ptr::read(p as *const i32)), - ir::types::I64 => DataValue::I64(ptr::read(p as *const i64)), - ir::types::F32 => DataValue::F32(ptr::read(p as *const Ieee32)), - ir::types::F64 => DataValue::F64(ptr::read(p as *const Ieee64)), - _ if ty.is_bool() => match ty.bytes() { - 1 => DataValue::B(ptr::read(p as *const i8) != 0), - 2 => DataValue::B(ptr::read(p as *const i16) != 0), - 4 => DataValue::B(ptr::read(p as *const i32) != 0), - 8 => DataValue::B(ptr::read(p as *const i64) != 0), - _ => unimplemented!(), - }, - _ if ty.is_vector() && ty.bytes() == 16 => { - DataValue::V128(ptr::read(p as *const [u8; 16])) - } - _ => unimplemented!(), - } - } } /// Compile a [Function] to its executable bytes in memory. diff --git a/cranelift/interpreter/src/address.rs b/cranelift/interpreter/src/address.rs new file mode 100644 index 0000000000..1d1f831141 --- /dev/null +++ b/cranelift/interpreter/src/address.rs @@ -0,0 +1,297 @@ +//! Virtual Addressing Scheme for the Interpreter +//! +//! The interpreter uses virtual memory addresses for its memory operations. These addresses +//! are obtained by the various `_addr` instructions (e.g. `stack_addr`) and can be either 32 or 64 +//! bits. +//! +//! Addresses are composed of 3 fields: "region", "entry" and offset. +//! +//! "region" refers to the type of memory that this address points to. +//! "entry" refers to which instance of this memory the address points to (e.g table1 would be +//! "entry" 1 of a `Table` region address). +//! The last field is the "offset", which refers to the offset within the entry. +//! +//! The address has the "region" field as the 2 most significant bits. The following bits +//! are the "entry" field, the amount of "entry" bits depends on the size of the address and +//! the "region" of the address. The remaining bits belong to the "offset" field +//! +//! An example address could be a 32 bit address, in the `heap` region, which has 2 "entry" bits +//! this address would have 32 - 2 - 2 = 28 offset bits. +//! +//! The only exception to this is the "stack" region, where, because we only have a single "stack" +//! we have 0 "entry" bits, and thus is all offset. +//! +//! | address size | address kind | region value (2 bits) | entry bits (#) | offset bits (#) | +//! |--------------|--------------|-----------------------|----------------|-----------------| +//! | 32 | Stack | 0b00 | 0 | 30 | +//! | 32 | Heap | 0b01 | 2 | 28 | +//! | 32 | Table | 0b10 | 5 | 25 | +//! | 32 | GlobalValue | 0b11 | 6 | 24 | +//! | 64 | Stack | 0b00 | 0 | 62 | +//! | 64 | Heap | 0b01 | 6 | 56 | +//! | 64 | Table | 0b10 | 10 | 52 | +//! | 64 | GlobalValue | 0b11 | 12 | 50 | + +use crate::state::MemoryError; +use cranelift_codegen::data_value::DataValue; +use cranelift_codegen::ir::{types, Type}; +use std::convert::TryFrom; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AddressSize { + _32, + _64, +} + +impl AddressSize { + pub fn bits(&self) -> u64 { + match self { + AddressSize::_64 => 64, + AddressSize::_32 => 32, + } + } +} + +impl TryFrom for AddressSize { + type Error = MemoryError; + + fn try_from(ty: Type) -> Result { + match ty { + types::I64 => Ok(AddressSize::_64), + types::I32 => Ok(AddressSize::_32), + _ => Err(MemoryError::InvalidAddressType(ty)), + } + } +} + +/// Virtual Address region +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AddressRegion { + Stack, + Heap, + Table, + GlobalValue, +} + +impl AddressRegion { + pub fn decode(bits: u64) -> Self { + assert!(bits < 4); + match bits { + 0 => AddressRegion::Stack, + 1 => AddressRegion::Heap, + 2 => AddressRegion::Table, + 3 => AddressRegion::GlobalValue, + _ => unreachable!(), + } + } + + pub fn encode(self) -> u64 { + match self { + AddressRegion::Stack => 0, + AddressRegion::Heap => 1, + AddressRegion::Table => 2, + AddressRegion::GlobalValue => 3, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Address { + pub size: AddressSize, + pub region: AddressRegion, + pub entry: u64, + pub offset: u64, +} + +impl Address { + pub fn from_parts( + size: AddressSize, + region: AddressRegion, + entry: u64, + offset: u64, + ) -> Result { + let entry_bits = Address::entry_bits(size, region); + let offset_bits = Address::offset_bits(size, region); + + let max_entries = (1 << entry_bits) - 1; + let max_offset = (1 << offset_bits) - 1; + + if entry > max_entries { + return Err(MemoryError::InvalidEntry { + entry, + max: max_entries, + }); + } + + if offset > max_offset { + return Err(MemoryError::InvalidOffset { + offset, + max: max_offset, + }); + } + + Ok(Address { + size, + region, + entry, + offset, + }) + } + + fn entry_bits(size: AddressSize, region: AddressRegion) -> u64 { + match (size, region) { + // We only have one stack, so the whole address is offset + (_, AddressRegion::Stack) => 0, + + (AddressSize::_32, AddressRegion::Heap) => 2, + (AddressSize::_32, AddressRegion::Table) => 5, + (AddressSize::_32, AddressRegion::GlobalValue) => 6, + + (AddressSize::_64, AddressRegion::Heap) => 6, + (AddressSize::_64, AddressRegion::Table) => 10, + (AddressSize::_64, AddressRegion::GlobalValue) => 12, + } + } + + fn offset_bits(size: AddressSize, region: AddressRegion) -> u64 { + let region_bits = 2; + let entry_bits = Address::entry_bits(size, region); + size.bits() - entry_bits - region_bits + } +} + +impl TryFrom
for DataValue { + type Error = MemoryError; + + fn try_from(addr: Address) -> Result { + let entry_bits = Address::entry_bits(addr.size, addr.region); + let offset_bits = Address::offset_bits(addr.size, addr.region); + + let entry = addr.entry << offset_bits; + let region = addr.region.encode() << (entry_bits + offset_bits); + + let value = region | entry | addr.offset; + Ok(match addr.size { + AddressSize::_32 => DataValue::I32(value as u32 as i32), + AddressSize::_64 => DataValue::I64(value as i64), + }) + } +} + +impl TryFrom for Address { + type Error = MemoryError; + + fn try_from(value: DataValue) -> Result { + let addr = match value { + DataValue::U32(v) => v as u64, + DataValue::I32(v) => v as u32 as u64, + DataValue::U64(v) => v, + DataValue::I64(v) => v as u64, + _ => { + return Err(MemoryError::InvalidAddress(value)); + } + }; + + let size = match value { + DataValue::U32(_) | DataValue::I32(_) => AddressSize::_32, + DataValue::U64(_) | DataValue::I64(_) => AddressSize::_64, + _ => unreachable!(), + }; + + let region = AddressRegion::decode(addr >> (size.bits() - 2)); + + let entry_bits = Address::entry_bits(size, region); + let offset_bits = Address::offset_bits(size, region); + + let entry = (addr >> offset_bits) & ((1 << entry_bits) - 1); + let offset = addr & ((1 << offset_bits) - 1); + + Address::from_parts(size, region, entry, offset) + } +} + +impl TryFrom for Address { + type Error = MemoryError; + + fn try_from(value: u64) -> Result { + let dv = if value > u32::MAX as u64 { + DataValue::U64(value) + } else { + DataValue::U32(value as u32) + }; + + Address::try_from(dv) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::convert::TryInto; + + #[test] + fn address_region_roundtrip_encode_decode() { + let all_regions = [ + AddressRegion::Stack, + AddressRegion::Heap, + AddressRegion::Table, + AddressRegion::GlobalValue, + ]; + + for region in all_regions { + assert_eq!(AddressRegion::decode(region.encode()), region); + } + } + + #[test] + fn address_roundtrip() { + let test_addresses = [ + (AddressSize::_32, AddressRegion::Stack, 0, 0), + (AddressSize::_32, AddressRegion::Stack, 0, 1), + (AddressSize::_32, AddressRegion::Stack, 0, 1024), + (AddressSize::_32, AddressRegion::Stack, 0, 0x3FFF_FFFF), + (AddressSize::_32, AddressRegion::Heap, 0, 0), + (AddressSize::_32, AddressRegion::Heap, 1, 1), + (AddressSize::_32, AddressRegion::Heap, 3, 1024), + (AddressSize::_32, AddressRegion::Heap, 3, 0x0FFF_FFFF), + (AddressSize::_32, AddressRegion::Table, 0, 0), + (AddressSize::_32, AddressRegion::Table, 1, 1), + (AddressSize::_32, AddressRegion::Table, 31, 0x1FF_FFFF), + (AddressSize::_32, AddressRegion::GlobalValue, 0, 0), + (AddressSize::_32, AddressRegion::GlobalValue, 1, 1), + (AddressSize::_32, AddressRegion::GlobalValue, 63, 0xFF_FFFF), + (AddressSize::_64, AddressRegion::Stack, 0, 0), + (AddressSize::_64, AddressRegion::Stack, 0, 1), + ( + AddressSize::_64, + AddressRegion::Stack, + 0, + 0x3FFFFFFF_FFFFFFFF, + ), + (AddressSize::_64, AddressRegion::Heap, 0, 0), + (AddressSize::_64, AddressRegion::Heap, 1, 1), + (AddressSize::_64, AddressRegion::Heap, 3, 1024), + (AddressSize::_64, AddressRegion::Heap, 3, 0x0FFF_FFFF), + (AddressSize::_64, AddressRegion::Table, 0, 0), + (AddressSize::_64, AddressRegion::Table, 1, 1), + (AddressSize::_64, AddressRegion::Table, 31, 0x1FF_FFFF), + (AddressSize::_64, AddressRegion::GlobalValue, 0, 0), + (AddressSize::_64, AddressRegion::GlobalValue, 1, 1), + (AddressSize::_64, AddressRegion::GlobalValue, 63, 0xFF_FFFF), + ]; + + for (size, region, entry, offset) in test_addresses { + let original = Address { + size, + region, + entry, + offset, + }; + + let dv: DataValue = original.clone().try_into().unwrap(); + let addr = dv.try_into().unwrap(); + + assert_eq!(original, addr); + } + } +} diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs index c8a81c87e5..5b1925d7e6 100644 --- a/cranelift/interpreter/src/interpreter.rs +++ b/cranelift/interpreter/src/interpreter.rs @@ -2,6 +2,7 @@ //! //! This module partially contains the logic for interpreting Cranelift IR. +use crate::address::{Address, AddressRegion, AddressSize}; use crate::environment::{FuncIndex, FunctionStore}; use crate::frame::Frame; use crate::instruction::DfgInstructionContext; @@ -10,10 +11,11 @@ use crate::step::{step, ControlFlow, StepError}; use crate::value::ValueError; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; -use cranelift_codegen::ir::{Block, FuncRef, Function, Type, Value as ValueRef}; +use cranelift_codegen::ir::{Block, FuncRef, Function, StackSlot, Type, Value as ValueRef}; use log::trace; use std::collections::HashSet; use std::fmt::Debug; +use std::iter; use thiserror::Error; /// The Cranelift interpreter; this contains some high-level functions to control the interpreter's @@ -80,6 +82,7 @@ impl<'a> Interpreter<'a> { self.state .current_frame_mut() .set_all(parameters, arguments.to_vec()); + self.block(first_block) } @@ -173,6 +176,9 @@ pub enum InterpreterError { pub struct InterpreterState<'a> { pub functions: FunctionStore<'a>, pub frame_stack: Vec>, + /// Number of bytes from the bottom of the stack where the current frame's stack space is + pub frame_offset: usize, + pub stack: Vec, pub heap: Vec, pub iflags: HashSet, pub fflags: HashSet, @@ -183,6 +189,8 @@ impl Default for InterpreterState<'_> { Self { functions: FunctionStore::default(), frame_stack: vec![], + frame_offset: 0, + stack: Vec::with_capacity(1024), heap: vec![0; 1024], iflags: HashSet::new(), fflags: HashSet::new(), @@ -222,10 +230,27 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> { } fn push_frame(&mut self, function: &'a Function) { + if let Some(frame) = self.frame_stack.iter().last() { + self.frame_offset += frame.function.stack_size() as usize; + } + + // Grow the stack by the space necessary for this frame + self.stack + .extend(iter::repeat(0).take(function.stack_size() as usize)); + self.frame_stack.push(Frame::new(function)); } fn pop_frame(&mut self) { - self.frame_stack.pop(); + if let Some(frame) = self.frame_stack.pop() { + // Shorten the stack after exiting the frame + self.stack + .truncate(self.stack.len() - frame.function.stack_size() as usize); + + // Reset frame_offset to the start of this function + if let Some(frame) = self.frame_stack.iter().last() { + self.frame_offset -= frame.function.stack_size() as usize; + } + } } fn get_value(&self, name: ValueRef) -> Option { @@ -257,30 +282,74 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> { self.fflags.clear() } - fn load_heap(&self, offset: usize, ty: Type) -> Result { - if offset + 16 < self.heap.len() { - let pointer = self.heap[offset..offset + 16].as_ptr() as *const _ as *const u128; - Ok(unsafe { DataValue::read_value_from(pointer, ty) }) - } else { - Err(MemoryError::InsufficientMemory(offset, self.heap.len())) + fn stack_address( + &self, + size: AddressSize, + slot: StackSlot, + offset: u64, + ) -> Result { + let stack_slots = &self.get_current_function().stack_slots; + let stack_slot = &stack_slots[slot]; + + // offset must be `0 <= Offset < sizeof(SS)` + if offset >= stack_slot.size as u64 { + return Err(MemoryError::InvalidOffset { + offset, + max: stack_slot.size as u64, + }); } + + // Calculate the offset from the current frame to the requested stack slot + let slot_offset: u64 = stack_slots + .keys() + .filter(|k| k < &slot) + .map(|k| stack_slots[k].size as u64) + .sum(); + + let final_offset = self.frame_offset as u64 + slot_offset + offset; + Address::from_parts(size, AddressRegion::Stack, 0, final_offset) } - fn store_heap(&mut self, offset: usize, v: DataValue) -> Result<(), MemoryError> { - if offset + 16 < self.heap.len() { - let pointer = self.heap[offset..offset + 16].as_mut_ptr() as *mut _ as *mut u128; - Ok(unsafe { v.write_value_to(pointer) }) - } else { - Err(MemoryError::InsufficientMemory(offset, self.heap.len())) - } - } - - fn load_stack(&self, _offset: usize, _ty: Type) -> Result { + fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result { unimplemented!() } - fn store_stack(&mut self, _offset: usize, _v: DataValue) -> Result<(), MemoryError> { - unimplemented!() + fn checked_load(&self, addr: Address, ty: Type) -> Result { + let load_size = ty.bytes() as usize; + + let src = match addr.region { + AddressRegion::Stack => { + let addr_start = addr.offset as usize; + let addr_end = addr_start + load_size; + if addr_end > self.stack.len() { + return Err(MemoryError::OutOfBoundsLoad { addr, load_size }); + } + + &self.stack[addr_start..addr_end] + } + _ => unimplemented!(), + }; + + Ok(DataValue::read_from_slice(src, ty)) + } + + fn checked_store(&mut self, addr: Address, v: DataValue) -> Result<(), MemoryError> { + let store_size = v.ty().bytes() as usize; + + let dst = match addr.region { + AddressRegion::Stack => { + let addr_start = addr.offset as usize; + let addr_end = addr_start + store_size; + if addr_end > self.stack.len() { + return Err(MemoryError::OutOfBoundsStore { addr, store_size }); + } + + &mut self.stack[addr_start..addr_end] + } + _ => unimplemented!(), + }; + + Ok(v.write_to_slice(dst)) } } @@ -288,7 +357,6 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> { mod tests { use super::*; use crate::step::CraneliftTrap; - use cranelift_codegen::ir::immediates::Ieee32; use cranelift_codegen::ir::TrapCode; use cranelift_reader::parse_functions; @@ -332,12 +400,12 @@ mod tests { let mut env = FunctionStore::default(); env.add(func.name.to_string(), &func); let state = InterpreterState::default().with_function_store(env); - let result = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); + let trap = Interpreter::new(state) + .call_by_name("%test", &[]) + .unwrap() + .unwrap_trap(); - match result { - ControlFlow::Trap(CraneliftTrap::User(TrapCode::IntegerDivisionByZero)) => {} - _ => panic!("Unexpected ControlFlow: {:?}", result), - } + assert_eq!(trap, CraneliftTrap::User(TrapCode::IntegerDivisionByZero)); } #[test] @@ -395,20 +463,6 @@ mod tests { assert_eq!(result, vec![DataValue::I32(0)]) } - #[test] - fn state_heap_roundtrip() -> Result<(), MemoryError> { - let mut state = InterpreterState::default(); - let mut roundtrip = |dv: DataValue| { - state.store_heap(0, dv.clone())?; - assert_eq!(dv, state.load_heap(0, dv.ty())?); - Ok(()) - }; - - roundtrip(DataValue::B(true))?; - roundtrip(DataValue::I64(42))?; - roundtrip(DataValue::F32(Ieee32::from(0.42))) - } - #[test] fn state_flags() { let mut state = InterpreterState::default(); @@ -461,4 +515,209 @@ mod tests { .unwrap_return(); assert_eq!(result, vec![DataValue::I32(2)]); } + + // Verifies that writing to the stack on a called function does not overwrite the parents + // stack slots. + #[test] + fn stack_slots_multi_functions() { + let code = " + function %callee(i64, i64) -> i64 { + ss0 = explicit_slot 8 + ss1 = explicit_slot 8 + + block0(v0: i64, v1: i64): + stack_store.i64 v0, ss0 + stack_store.i64 v1, ss1 + v2 = stack_load.i64 ss0 + v3 = stack_load.i64 ss1 + v4 = iadd.i64 v2, v3 + return v4 + } + + function %caller(i64, i64, i64, i64) -> i64 { + fn0 = %callee(i64, i64) -> i64 + ss0 = explicit_slot 8 + ss1 = explicit_slot 8 + + block0(v0: i64, v1: i64, v2: i64, v3: i64): + stack_store.i64 v0, ss0 + stack_store.i64 v1, ss1 + + v4 = call fn0(v2, v3) + + v5 = stack_load.i64 ss0 + v6 = stack_load.i64 ss1 + + v7 = iadd.i64 v4, v5 + v8 = iadd.i64 v7, v6 + + return v8 + }"; + + let mut env = FunctionStore::default(); + let funcs = parse_functions(code).unwrap().to_vec(); + funcs.iter().for_each(|f| env.add(f.name.to_string(), f)); + + let state = InterpreterState::default().with_function_store(env); + let result = Interpreter::new(state) + .call_by_name( + "%caller", + &[ + DataValue::I64(3), + DataValue::I64(5), + DataValue::I64(7), + DataValue::I64(11), + ], + ) + .unwrap() + .unwrap_return(); + + assert_eq!(result, vec![DataValue::I64(26)]) + } + + #[test] + fn out_of_slot_write_traps() { + let code = " + function %stack_write() { + ss0 = explicit_slot 8 + + block0: + v0 = iconst.i64 10 + stack_store.i64 v0, ss0+8 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_write", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } + + #[test] + fn partial_out_of_slot_write_traps() { + let code = " + function %stack_write() { + ss0 = explicit_slot 8 + + block0: + v0 = iconst.i64 10 + stack_store.i64 v0, ss0+4 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_write", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } + + #[test] + fn out_of_slot_read_traps() { + let code = " + function %stack_load() { + ss0 = explicit_slot 8 + + block0: + v0 = stack_load.i64 ss0+8 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_load", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } + + #[test] + fn partial_out_of_slot_read_traps() { + let code = " + function %stack_load() { + ss0 = explicit_slot 8 + + block0: + v0 = stack_load.i64 ss0+4 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_load", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } + + #[test] + fn partial_out_of_slot_read_by_addr_traps() { + let code = " + function %stack_load() { + ss0 = explicit_slot 8 + + block0: + v0 = stack_addr.i64 ss0 + v1 = iconst.i64 4 + v2 = iadd.i64 v0, v1 + v3 = load.i64 v2 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_load", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } + + #[test] + fn partial_out_of_slot_write_by_addr_traps() { + let code = " + function %stack_store() { + ss0 = explicit_slot 8 + + block0: + v0 = stack_addr.i64 ss0 + v1 = iconst.i64 4 + v2 = iadd.i64 v0, v1 + store.i64 v1, v2 + return + }"; + + let func = parse_functions(code).unwrap().into_iter().next().unwrap(); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let trap = Interpreter::new(state) + .call_by_name("%stack_store", &[]) + .unwrap() + .unwrap_trap(); + + assert_eq!(trap, CraneliftTrap::User(TrapCode::HeapOutOfBounds)); + } } diff --git a/cranelift/interpreter/src/lib.rs b/cranelift/interpreter/src/lib.rs index 9ebbcdc0b7..9c9ab78336 100644 --- a/cranelift/interpreter/src/lib.rs +++ b/cranelift/interpreter/src/lib.rs @@ -2,6 +2,7 @@ //! //! This module is a project for interpreting Cranelift IR. +pub mod address; pub mod environment; pub mod frame; pub mod instruction; diff --git a/cranelift/interpreter/src/state.rs b/cranelift/interpreter/src/state.rs index 837c2f5849..22381c8937 100644 --- a/cranelift/interpreter/src/state.rs +++ b/cranelift/interpreter/src/state.rs @@ -1,7 +1,9 @@ //! Cranelift instructions modify the state of the machine; the [State] trait describes these //! ways this can happen. +use crate::address::{Address, AddressSize}; +use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; -use cranelift_codegen::ir::{FuncRef, Function, Type, Value}; +use cranelift_codegen::ir::{FuncRef, Function, StackSlot, Type, Value}; use cranelift_entity::PrimaryMap; use smallvec::SmallVec; use thiserror::Error; @@ -57,25 +59,37 @@ pub trait State<'a, V> { /// Clear all [IntCC] and [FloatCC] flags. fn clear_flags(&mut self); - /// Retrieve a value `V` from the heap at the given `offset`; the number of bytes loaded - /// corresponds to the specified [Type]. - fn load_heap(&self, offset: usize, ty: Type) -> Result; - /// Store a value `V` into the heap at the given `offset`. The [Type] of `V` will determine - /// the number of bytes stored. - fn store_heap(&mut self, offset: usize, v: V) -> Result<(), MemoryError>; - - /// Retrieve a value `V` from the stack at the given `offset`; the number of bytes loaded - /// corresponds to the specified [Type]. - fn load_stack(&self, offset: usize, ty: Type) -> Result; - /// Store a value `V` on the stack at the given `offset`. The [Type] of `V` will determine - /// the number of bytes stored. - fn store_stack(&mut self, offset: usize, v: V) -> Result<(), MemoryError>; + /// Computes the stack address for this stack slot, including an offset. + fn stack_address( + &self, + size: AddressSize, + slot: StackSlot, + offset: u64, + ) -> Result; + /// Computes a heap address + fn heap_address(&self, size: AddressSize, offset: u64) -> Result; + /// 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; + /// 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>; } #[derive(Error, Debug)] pub enum MemoryError { - #[error("insufficient memory: asked for address {0} in memory of size {1}")] - InsufficientMemory(usize, usize), + #[error("Invalid DataValue passed as an address: {0}")] + InvalidAddress(DataValue), + #[error("Invalid type for address: {0}")] + InvalidAddressType(Type), + #[error("Requested an the entry {entry} but only {max} entries are allowed")] + InvalidEntry { entry: u64, max: u64 }, + #[error("Requested an offset of {offset} but max was {max}")] + InvalidOffset { offset: u64, max: u64 }, + #[error("Load of {load_size} bytes is larger than available size at address {addr:?}")] + OutOfBoundsLoad { addr: Address, load_size: usize }, + #[error("Store of {store_size} bytes is larger than available size at address {addr:?}")] + OutOfBoundsStore { addr: Address, store_size: usize }, } /// This dummy state allows interpretation over an immutable mapping of values in a single frame. @@ -128,19 +142,24 @@ where fn clear_flags(&mut self) {} - fn load_heap(&self, _offset: usize, _ty: Type) -> Result { + fn stack_address( + &self, + _size: AddressSize, + _slot: StackSlot, + _offset: u64, + ) -> Result { unimplemented!() } - fn store_heap(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { + fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result { unimplemented!() } - fn load_stack(&self, _offset: usize, _ty: Type) -> Result { + fn checked_load(&self, _addr: Address, _ty: Type) -> Result { unimplemented!() } - fn store_stack(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { + fn checked_store(&mut self, _addr: Address, _v: V) -> Result<(), MemoryError> { unimplemented!() } } diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs index aed999af48..9319b9a0fe 100644 --- a/cranelift/interpreter/src/step.rs +++ b/cranelift/interpreter/src/step.rs @@ -1,8 +1,10 @@ //! The [step] function interprets a single Cranelift instruction given its [State] and //! [InstructionContext]; the interpretation is generic over [Value]s. +use crate::address::{Address, AddressSize}; use crate::instruction::InstructionContext; use crate::state::{MemoryError, State}; use crate::value::{Value, ValueConversionKind, ValueError, ValueResult}; +use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; use cranelift_codegen::ir::{ types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Value as ValueRef, @@ -87,6 +89,37 @@ where Err(e) => Err(e), }; + let memerror_to_trap = |e: MemoryError| match e { + MemoryError::InvalidAddress(_) => TrapCode::HeapOutOfBounds, + MemoryError::InvalidAddressType(_) => TrapCode::HeapOutOfBounds, + MemoryError::InvalidOffset { .. } => TrapCode::HeapOutOfBounds, + MemoryError::InvalidEntry { .. } => TrapCode::HeapOutOfBounds, + MemoryError::OutOfBoundsStore { .. } => TrapCode::HeapOutOfBounds, + MemoryError::OutOfBoundsLoad { .. } => TrapCode::HeapOutOfBounds, + }; + + // Assigns or traps depending on the value of the result + let assign_or_memtrap = |res| match res { + Ok(v) => assign(v), + Err(e) => ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e))), + }; + + // Continues or traps depending on the value of the result + let continue_or_memtrap = |res| match res { + Ok(_) => ControlFlow::Continue, + Err(e) => ControlFlow::Trap(CraneliftTrap::User(memerror_to_trap(e))), + }; + + let calculate_addr = |imm: V, args: SmallVec<[V; 1]>| -> ValueResult { + let imm = imm.convert(ValueConversionKind::ZeroExtend(ctrl_ty))?; + let args = args + .into_iter() + .map(|v| v.convert(ValueConversionKind::ZeroExtend(ctrl_ty))) + .collect::>>()?; + + Ok(sum(imm, args)? as u64) + }; + // Interpret a binary instruction with the given `op`, assigning the resulting value to the // instruction's results. let binary = |op: fn(V, V) -> ValueResult, @@ -250,7 +283,6 @@ where | Opcode::Uload32x2Complex | Opcode::Sload32x2 | Opcode::Sload32x2Complex => { - let address = sum(imm(), args()?)? as usize; let ctrl_ty = inst_context.controlling_type().unwrap(); let (load_ty, kind) = match inst.opcode() { Opcode::Load | Opcode::LoadComplex => (ctrl_ty, None), @@ -286,13 +318,20 @@ where | Opcode::Sload32x2Complex => unimplemented!(), _ => unreachable!(), }; - let loaded = state.load_heap(address, load_ty)?; - let extended = if let Some(c) = kind { - loaded.convert(c)? - } else { - loaded - }; - ControlFlow::Assign(smallvec!(extended)) + + let addr_value = calculate_addr(imm(), args()?)?; + let loaded = assign_or_memtrap( + Address::try_from(addr_value).and_then(|addr| state.checked_load(addr, load_ty)), + ); + + match (loaded, kind) { + (ControlFlow::Assign(ret), Some(c)) => ControlFlow::Assign( + ret.into_iter() + .map(|loaded| loaded.convert(c.clone())) + .collect::>>()?, + ), + (cf, _) => cf, + } } Opcode::Store | Opcode::StoreComplex @@ -302,7 +341,6 @@ where | Opcode::Istore16Complex | Opcode::Istore32 | Opcode::Istore32Complex => { - let address = sum(imm(), args_range(1..)?)? as usize; let kind = match inst.opcode() { Opcode::Store | Opcode::StoreComplex => None, Opcode::Istore8 | Opcode::Istore8Complex => { @@ -316,27 +354,49 @@ where } _ => unreachable!(), }; + + let addr_value = calculate_addr(imm(), args_range(1..)?)?; let reduced = if let Some(c) = kind { arg(0)?.convert(c)? } else { arg(0)? }; - state.store_heap(address, reduced)?; - ControlFlow::Continue + continue_or_memtrap( + Address::try_from(addr_value).and_then(|addr| state.checked_store(addr, reduced)), + ) } Opcode::StackLoad => { - let address = sum(imm(), args_range(1..)?)? as usize; let load_ty = inst_context.controlling_type().unwrap(); - let loaded = state.load_stack(address, load_ty)?; - ControlFlow::Assign(smallvec!(loaded)) + let slot = inst.stack_slot().unwrap(); + let offset = sum(imm(), args()?)? as u64; + assign_or_memtrap({ + state + .stack_address(AddressSize::_64, slot, offset) + .and_then(|addr| state.checked_load(addr, load_ty)) + }) } Opcode::StackStore => { - let address = sum(imm(), args_range(1..)?)? as usize; - let arg0 = arg(0)?; - state.store_stack(address, arg0)?; - ControlFlow::Continue + let arg = arg(0)?; + let slot = inst.stack_slot().unwrap(); + let offset = sum(imm(), args_range(1..)?)? as u64; + continue_or_memtrap({ + state + .stack_address(AddressSize::_64, slot, offset) + .and_then(|addr| state.checked_store(addr, arg)) + }) + } + Opcode::StackAddr => { + let load_ty = inst_context.controlling_type().unwrap(); + let slot = inst.stack_slot().unwrap(); + let offset = sum(imm(), args()?)? as u64; + assign_or_memtrap({ + AddressSize::try_from(load_ty).and_then(|addr_size| { + let addr = state.stack_address(addr_size, slot, offset)?; + let dv = DataValue::try_from(addr)?; + Ok(dv.into()) + }) + }) } - Opcode::StackAddr => unimplemented!("StackAddr"), Opcode::GlobalValue => unimplemented!("GlobalValue"), Opcode::SymbolValue => unimplemented!("SymbolValue"), Opcode::TlsValue => unimplemented!("TlsValue"), @@ -724,6 +784,16 @@ impl<'a, V> ControlFlow<'a, V> { panic!("expected the control flow to be in the return state") } } + + /// For convenience, we can unwrap the [ControlFlow] state assuming that it is a + /// [ControlFlow::Trap], panicking otherwise. + pub fn unwrap_trap(self) -> CraneliftTrap { + if let ControlFlow::Trap(trap) = self { + trap + } else { + panic!("expected the control flow to be a trap") + } + } } #[derive(Error, Debug, PartialEq)] diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs index 7f8bbf8bc0..b5c265855e 100644 --- a/cranelift/interpreter/src/value.rs +++ b/cranelift/interpreter/src/value.rs @@ -92,7 +92,7 @@ impl Display for ValueTypeClass { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ValueConversionKind { /// Throw a [ValueError] if an exact conversion to [Type] is not possible; e.g. in `i32` to /// `i16`, convert `0x00001234` to `0x1234`. @@ -234,6 +234,7 @@ impl Value for DataValue { ValueConversionKind::Exact(ty) => match (self, ty) { // TODO a lot to do here: from bmask to ireduce to raw_bitcast... (DataValue::I64(n), types::I32) => DataValue::I32(i32::try_from(n)?), + (DataValue::I64(n), types::I64) => DataValue::I64(n), (DataValue::B(b), t) if t.is_bool() => DataValue::B(b), (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind), }, @@ -273,14 +274,17 @@ impl Value for DataValue { (types::I32, types::I64) => unimplemented!(), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, - ValueConversionKind::ZeroExtend(ty) => match (self.ty(), ty) { - (types::I8, types::I16) => unimplemented!(), - (types::I8, types::I32) => unimplemented!(), - (types::I8, types::I64) => unimplemented!(), - (types::I16, types::I32) => unimplemented!(), - (types::I16, types::I64) => unimplemented!(), - (types::I32, types::I64) => unimplemented!(), - _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + ValueConversionKind::ZeroExtend(ty) => match (self, ty) { + (DataValue::I8(_), types::I16) => unimplemented!(), + (DataValue::I8(_), types::I32) => unimplemented!(), + (DataValue::I8(_), types::I64) => unimplemented!(), + (DataValue::I16(_), types::I32) => unimplemented!(), + (DataValue::I16(_), types::I64) => unimplemented!(), + (DataValue::U32(n), types::I64) => DataValue::U64(n as u64), + (DataValue::I32(n), types::I64) => DataValue::I64(n as u32 as i64), + (DataValue::U64(n), types::I64) => DataValue::U64(n), + (DataValue::I64(n), types::I64) => DataValue::I64(n), + (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind), }, ValueConversionKind::ToUnsigned => match self { DataValue::I8(n) => DataValue::U8(n as u8), @@ -340,7 +344,8 @@ impl Value for DataValue { } fn add(self, other: Self) -> ValueResult { - binary_match!(wrapping_add(&self, &other); [I8, I16, I32, I64, I128]) // TODO: floats must handle NaNs, +/-0 + // TODO: floats must handle NaNs, +/-0 + binary_match!(wrapping_add(&self, &other); [I8, I16, I32, I64, I128, U8, U16, U32, U64, U128]) } fn sub(self, other: Self) -> ValueResult { @@ -396,7 +401,7 @@ impl Value for DataValue { } fn and(self, other: Self) -> ValueResult { - binary_match!(&(&self, &other); [I8, I16, I32, I64]) + binary_match!(&(&self, &other); [B, I8, I16, I32, I64]) } fn or(self, other: Self) -> ValueResult {