cranelift: Add stack support to the interpreter with virtual addresses (#3187)

* cranelift: Add stack support to the interpreter

We also change the approach for heap loads and stores.

Previously we would use the offset as the address to the heap. However,
this approach does not allow using the load/store instructions to
read/write from both the heap and the stack.

This commit changes the addressing mechanism of the interpreter. We now
return the real addresses from the addressing instructions
(stack_addr/heap_addr), and instead check if the address passed into
the load/store instructions points to an area in the heap or the stack.

* cranelift: Add virtual addresses to cranelift interpreter

Adds a  Virtual Addressing scheme that was discussed as a better
alternative to returning the real addresses.

The virtual addresses are split into 4 regions (stack, heap, tables and
global values), and the address itself is composed of an `entry` field
and an `offset` field. In general the `entry` field corresponds to the
instance of the resource (e.g. table5 is entry 5) and the `offset` field
is a byte offset inside that entry.

There is one exception to this which is the stack, where due to only
having one stack, the whole address is an offset field.

The number of bits in entry vs offset fields is variable with respect to
the `region` and the address size (32bits vs 64bits). This is done
because with 32 bit addresses we would have to compromise on heap size,
or have a small number of global values / tables. With 64 bit addresses
we do not have to compromise on this, but we need to support 32 bit
addresses.

* cranelift: Remove interpreter trap codes

* cranelift: Calculate frame_offset when entering or exiting a frame

* cranelift: Add safe read/write interface to DataValue

* cranelift: DataValue write full 128bit slot for booleans

* cranelift: Use DataValue accessors for trampoline.
This commit is contained in:
Afonso Bordado
2021-08-24 17:29:11 +01:00
committed by GitHub
parent f4ff7c350a
commit 2776074dfc
13 changed files with 1094 additions and 157 deletions

View File

@@ -4,7 +4,6 @@ use crate::ir::immediates::{Ieee32, Ieee64, Offset32};
use crate::ir::{types, ConstantData, Type}; use crate::ir::{types, ConstantData, Type};
use core::convert::TryInto; use core::convert::TryInto;
use core::fmt::{self, Display, Formatter}; use core::fmt::{self, Display, Formatter};
use core::ptr;
/// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value
/// that would be referred to by a [Value]. /// that would be referred to by a [Value].
@@ -74,36 +73,75 @@ impl DataValue {
} }
} }
/// Write a [DataValue] to a memory location. /// Write a [DataValue] to a slice.
pub unsafe fn write_value_to(&self, p: *mut u128) { ///
/// # 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 { match self {
DataValue::B(b) => ptr::write(p, if *b { -1i128 as u128 } else { 0u128 }), DataValue::B(true) => dst[..16].copy_from_slice(&[u8::MAX; 16][..]),
DataValue::I8(i) => ptr::write(p as *mut i8, *i), DataValue::B(false) => dst[..16].copy_from_slice(&[0; 16][..]),
DataValue::I16(i) => ptr::write(p as *mut i16, *i), DataValue::I8(i) => dst[..1].copy_from_slice(&i.to_le_bytes()[..]),
DataValue::I32(i) => ptr::write(p as *mut i32, *i), DataValue::I16(i) => dst[..2].copy_from_slice(&i.to_le_bytes()[..]),
DataValue::I64(i) => ptr::write(p as *mut i64, *i), DataValue::I32(i) => dst[..4].copy_from_slice(&i.to_le_bytes()[..]),
DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), DataValue::I64(i) => dst[..8].copy_from_slice(&i.to_le_bytes()[..]),
DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), DataValue::F32(f) => dst[..4].copy_from_slice(&f.bits().to_le_bytes()[..]),
DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), 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!(), _ => 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]. /// Read a [DataValue] from a memory location using a given [Type].
pub unsafe fn read_value_from(p: *const u128, ty: Type) -> Self { pub unsafe fn read_value_from(p: *const u128, ty: Type) -> Self {
match ty { DataValue::read_from_slice(
types::I8 => DataValue::I8(ptr::read(p as *const i8)), std::slice::from_raw_parts(p as *const u8, ty.bytes() as usize),
types::I16 => DataValue::I16(ptr::read(p as *const i16)), ty,
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!(),
}
} }
} }

View File

@@ -421,6 +421,13 @@ impl Function {
self.dfg[dst] = self.dfg[src].clone(); self.dfg[dst] = self.dfg[src].clone();
self.layout.remove_inst(src); 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. /// Additional annotations for function display.

View File

@@ -23,7 +23,7 @@ use crate::ir::{
self, self,
condcodes::{FloatCC, IntCC}, condcodes::{FloatCC, IntCC},
trapcode::TrapCode, trapcode::TrapCode,
types, Block, FuncRef, JumpTable, MemFlags, SigRef, Type, Value, types, Block, FuncRef, JumpTable, MemFlags, SigRef, StackSlot, Type, Value,
}; };
use crate::isa; use crate::isa;
@@ -411,6 +411,15 @@ impl InstructionData {
} }
} }
/// If this instruction references a stack slot, return it
pub fn stack_slot(&self) -> Option<StackSlot> {
match self {
&InstructionData::StackStore { stack_slot, .. }
| &InstructionData::StackLoad { stack_slot, .. } => Some(stack_slot),
_ => None,
}
}
/// Return information about a call instruction. /// Return information about a call instruction.
/// ///
/// Any instruction that can call another function reveals its call signature here. /// Any instruction that can call another function reveals its call signature here.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,8 +1,7 @@
//! Provides functionality for compiling and running CLIF IR for `run` tests. //! 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::binemit::{NullRelocSink, NullStackMapSink, NullTrapSink};
use cranelift_codegen::data_value::DataValue; 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::ir::{condcodes::IntCC, Function, InstBuilder, Signature, Type};
use cranelift_codegen::isa::{BackendVariant, TargetIsa}; use cranelift_codegen::isa::{BackendVariant, TargetIsa};
use cranelift_codegen::{ir, settings, CodegenError, Context}; use cranelift_codegen::{ir, settings, CodegenError, Context};
@@ -204,7 +203,7 @@ impl UnboxedValues {
param.value_type param.value_type
); );
unsafe { 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. // Extract the returned values from this vector.
for (slot, param) in self.0.iter().zip(&signature.returns) { 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.push(value);
} }
returns 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. /// Compile a [Function] to its executable bytes in memory.

View File

@@ -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<Type> for AddressSize {
type Error = MemoryError;
fn try_from(ty: Type) -> Result<Self, Self::Error> {
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<Self, MemoryError> {
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<Address> for DataValue {
type Error = MemoryError;
fn try_from(addr: Address) -> Result<Self, Self::Error> {
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<DataValue> for Address {
type Error = MemoryError;
fn try_from(value: DataValue) -> Result<Self, Self::Error> {
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<u64> for Address {
type Error = MemoryError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
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);
}
}
}

View File

@@ -2,6 +2,7 @@
//! //!
//! This module partially contains the logic for interpreting Cranelift IR. //! This module partially contains the logic for interpreting Cranelift IR.
use crate::address::{Address, AddressRegion, AddressSize};
use crate::environment::{FuncIndex, FunctionStore}; use crate::environment::{FuncIndex, FunctionStore};
use crate::frame::Frame; use crate::frame::Frame;
use crate::instruction::DfgInstructionContext; use crate::instruction::DfgInstructionContext;
@@ -10,10 +11,11 @@ use crate::step::{step, ControlFlow, StepError};
use crate::value::ValueError; use crate::value::ValueError;
use cranelift_codegen::data_value::DataValue; use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; 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 log::trace;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::Debug; use std::fmt::Debug;
use std::iter;
use thiserror::Error; use thiserror::Error;
/// The Cranelift interpreter; this contains some high-level functions to control the interpreter's /// The Cranelift interpreter; this contains some high-level functions to control the interpreter's
@@ -80,6 +82,7 @@ impl<'a> Interpreter<'a> {
self.state self.state
.current_frame_mut() .current_frame_mut()
.set_all(parameters, arguments.to_vec()); .set_all(parameters, arguments.to_vec());
self.block(first_block) self.block(first_block)
} }
@@ -173,6 +176,9 @@ pub enum InterpreterError {
pub struct InterpreterState<'a> { pub struct InterpreterState<'a> {
pub functions: FunctionStore<'a>, pub functions: FunctionStore<'a>,
pub frame_stack: Vec<Frame<'a>>, pub frame_stack: Vec<Frame<'a>>,
/// Number of bytes from the bottom of the stack where the current frame's stack space is
pub frame_offset: usize,
pub stack: Vec<u8>,
pub heap: Vec<u8>, pub heap: Vec<u8>,
pub iflags: HashSet<IntCC>, pub iflags: HashSet<IntCC>,
pub fflags: HashSet<FloatCC>, pub fflags: HashSet<FloatCC>,
@@ -183,6 +189,8 @@ impl Default for InterpreterState<'_> {
Self { Self {
functions: FunctionStore::default(), functions: FunctionStore::default(),
frame_stack: vec![], frame_stack: vec![],
frame_offset: 0,
stack: Vec::with_capacity(1024),
heap: vec![0; 1024], heap: vec![0; 1024],
iflags: HashSet::new(), iflags: HashSet::new(),
fflags: 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) { 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)); self.frame_stack.push(Frame::new(function));
} }
fn pop_frame(&mut self) { 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<DataValue> { fn get_value(&self, name: ValueRef) -> Option<DataValue> {
@@ -257,30 +282,74 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
self.fflags.clear() self.fflags.clear()
} }
fn load_heap(&self, offset: usize, ty: Type) -> Result<DataValue, MemoryError> { fn stack_address(
if offset + 16 < self.heap.len() { &self,
let pointer = self.heap[offset..offset + 16].as_ptr() as *const _ as *const u128; size: AddressSize,
Ok(unsafe { DataValue::read_value_from(pointer, ty) }) slot: StackSlot,
} else { offset: u64,
Err(MemoryError::InsufficientMemory(offset, self.heap.len())) ) -> Result<Address, MemoryError> {
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> { fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result<Address, 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<DataValue, MemoryError> {
unimplemented!() unimplemented!()
} }
fn store_stack(&mut self, _offset: usize, _v: DataValue) -> Result<(), MemoryError> { fn checked_load(&self, addr: Address, ty: Type) -> Result<DataValue, MemoryError> {
unimplemented!() 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 { mod tests {
use super::*; use super::*;
use crate::step::CraneliftTrap; use crate::step::CraneliftTrap;
use cranelift_codegen::ir::immediates::Ieee32;
use cranelift_codegen::ir::TrapCode; use cranelift_codegen::ir::TrapCode;
use cranelift_reader::parse_functions; use cranelift_reader::parse_functions;
@@ -332,12 +400,12 @@ mod tests {
let mut env = FunctionStore::default(); let mut env = FunctionStore::default();
env.add(func.name.to_string(), &func); env.add(func.name.to_string(), &func);
let state = InterpreterState::default().with_function_store(env); 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 { assert_eq!(trap, CraneliftTrap::User(TrapCode::IntegerDivisionByZero));
ControlFlow::Trap(CraneliftTrap::User(TrapCode::IntegerDivisionByZero)) => {}
_ => panic!("Unexpected ControlFlow: {:?}", result),
}
} }
#[test] #[test]
@@ -395,20 +463,6 @@ mod tests {
assert_eq!(result, vec![DataValue::I32(0)]) 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] #[test]
fn state_flags() { fn state_flags() {
let mut state = InterpreterState::default(); let mut state = InterpreterState::default();
@@ -461,4 +515,209 @@ mod tests {
.unwrap_return(); .unwrap_return();
assert_eq!(result, vec![DataValue::I32(2)]); 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));
}
} }

View File

@@ -2,6 +2,7 @@
//! //!
//! This module is a project for interpreting Cranelift IR. //! This module is a project for interpreting Cranelift IR.
pub mod address;
pub mod environment; pub mod environment;
pub mod frame; pub mod frame;
pub mod instruction; pub mod instruction;

View File

@@ -1,7 +1,9 @@
//! Cranelift instructions modify the state of the machine; the [State] trait describes these //! Cranelift instructions modify the state of the machine; the [State] trait describes these
//! ways this can happen. //! 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::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 cranelift_entity::PrimaryMap;
use smallvec::SmallVec; use smallvec::SmallVec;
use thiserror::Error; use thiserror::Error;
@@ -57,25 +59,37 @@ pub trait State<'a, V> {
/// Clear all [IntCC] and [FloatCC] flags. /// Clear all [IntCC] and [FloatCC] flags.
fn clear_flags(&mut self); fn clear_flags(&mut self);
/// Retrieve a value `V` from the heap at the given `offset`; the number of bytes loaded /// Computes the stack address for this stack slot, including an offset.
/// corresponds to the specified [Type]. fn stack_address(
fn load_heap(&self, offset: usize, ty: Type) -> Result<V, MemoryError>; &self,
/// Store a value `V` into the heap at the given `offset`. The [Type] of `V` will determine size: AddressSize,
/// the number of bytes stored. slot: StackSlot,
fn store_heap(&mut self, offset: usize, v: V) -> Result<(), MemoryError>; offset: u64,
) -> Result<Address, MemoryError>;
/// Retrieve a value `V` from the stack at the given `offset`; the number of bytes loaded /// Computes a heap address
/// corresponds to the specified [Type]. fn heap_address(&self, size: AddressSize, offset: u64) -> Result<Address, MemoryError>;
fn load_stack(&self, offset: usize, ty: Type) -> Result<V, MemoryError>; /// Retrieve a value `V` from memory at the given `address`, checking if it belongs either to the
/// Store a value `V` on the stack at the given `offset`. The [Type] of `V` will determine /// stack or to one of the heaps; the number of bytes loaded corresponds to the specified [Type].
/// the number of bytes stored. fn checked_load(&self, address: Address, ty: Type) -> Result<V, MemoryError>;
fn store_stack(&mut self, offset: usize, v: V) -> Result<(), 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>;
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum MemoryError { pub enum MemoryError {
#[error("insufficient memory: asked for address {0} in memory of size {1}")] #[error("Invalid DataValue passed as an address: {0}")]
InsufficientMemory(usize, usize), 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. /// 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 clear_flags(&mut self) {}
fn load_heap(&self, _offset: usize, _ty: Type) -> Result<V, MemoryError> { fn stack_address(
&self,
_size: AddressSize,
_slot: StackSlot,
_offset: u64,
) -> Result<Address, MemoryError> {
unimplemented!() unimplemented!()
} }
fn store_heap(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { fn heap_address(&self, _size: AddressSize, _offset: u64) -> Result<Address, MemoryError> {
unimplemented!() unimplemented!()
} }
fn load_stack(&self, _offset: usize, _ty: Type) -> Result<V, MemoryError> { fn checked_load(&self, _addr: Address, _ty: Type) -> Result<V, MemoryError> {
unimplemented!() unimplemented!()
} }
fn store_stack(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { fn checked_store(&mut self, _addr: Address, _v: V) -> Result<(), MemoryError> {
unimplemented!() unimplemented!()
} }
} }

View File

@@ -1,8 +1,10 @@
//! The [step] function interprets a single Cranelift instruction given its [State] and //! The [step] function interprets a single Cranelift instruction given its [State] and
//! [InstructionContext]; the interpretation is generic over [Value]s. //! [InstructionContext]; the interpretation is generic over [Value]s.
use crate::address::{Address, AddressSize};
use crate::instruction::InstructionContext; use crate::instruction::InstructionContext;
use crate::state::{MemoryError, State}; use crate::state::{MemoryError, State};
use crate::value::{Value, ValueConversionKind, ValueError, ValueResult}; use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Value as ValueRef, types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Value as ValueRef,
@@ -87,6 +89,37 @@ where
Err(e) => Err(e), 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<u64> {
let imm = imm.convert(ValueConversionKind::ZeroExtend(ctrl_ty))?;
let args = args
.into_iter()
.map(|v| v.convert(ValueConversionKind::ZeroExtend(ctrl_ty)))
.collect::<ValueResult<SmallVec<[V; 1]>>>()?;
Ok(sum(imm, args)? as u64)
};
// Interpret a binary instruction with the given `op`, assigning the resulting value to the // Interpret a binary instruction with the given `op`, assigning the resulting value to the
// instruction's results. // instruction's results.
let binary = |op: fn(V, V) -> ValueResult<V>, let binary = |op: fn(V, V) -> ValueResult<V>,
@@ -250,7 +283,6 @@ where
| Opcode::Uload32x2Complex | Opcode::Uload32x2Complex
| Opcode::Sload32x2 | Opcode::Sload32x2
| Opcode::Sload32x2Complex => { | Opcode::Sload32x2Complex => {
let address = sum(imm(), args()?)? as usize;
let ctrl_ty = inst_context.controlling_type().unwrap(); let ctrl_ty = inst_context.controlling_type().unwrap();
let (load_ty, kind) = match inst.opcode() { let (load_ty, kind) = match inst.opcode() {
Opcode::Load | Opcode::LoadComplex => (ctrl_ty, None), Opcode::Load | Opcode::LoadComplex => (ctrl_ty, None),
@@ -286,13 +318,20 @@ where
| Opcode::Sload32x2Complex => unimplemented!(), | Opcode::Sload32x2Complex => unimplemented!(),
_ => unreachable!(), _ => unreachable!(),
}; };
let loaded = state.load_heap(address, load_ty)?;
let extended = if let Some(c) = kind { let addr_value = calculate_addr(imm(), args()?)?;
loaded.convert(c)? let loaded = assign_or_memtrap(
} else { Address::try_from(addr_value).and_then(|addr| state.checked_load(addr, load_ty)),
loaded );
};
ControlFlow::Assign(smallvec!(extended)) match (loaded, kind) {
(ControlFlow::Assign(ret), Some(c)) => ControlFlow::Assign(
ret.into_iter()
.map(|loaded| loaded.convert(c.clone()))
.collect::<ValueResult<SmallVec<[V; 1]>>>()?,
),
(cf, _) => cf,
}
} }
Opcode::Store Opcode::Store
| Opcode::StoreComplex | Opcode::StoreComplex
@@ -302,7 +341,6 @@ where
| Opcode::Istore16Complex | Opcode::Istore16Complex
| Opcode::Istore32 | Opcode::Istore32
| Opcode::Istore32Complex => { | Opcode::Istore32Complex => {
let address = sum(imm(), args_range(1..)?)? as usize;
let kind = match inst.opcode() { let kind = match inst.opcode() {
Opcode::Store | Opcode::StoreComplex => None, Opcode::Store | Opcode::StoreComplex => None,
Opcode::Istore8 | Opcode::Istore8Complex => { Opcode::Istore8 | Opcode::Istore8Complex => {
@@ -316,27 +354,49 @@ where
} }
_ => unreachable!(), _ => unreachable!(),
}; };
let addr_value = calculate_addr(imm(), args_range(1..)?)?;
let reduced = if let Some(c) = kind { let reduced = if let Some(c) = kind {
arg(0)?.convert(c)? arg(0)?.convert(c)?
} else { } else {
arg(0)? arg(0)?
}; };
state.store_heap(address, reduced)?; continue_or_memtrap(
ControlFlow::Continue Address::try_from(addr_value).and_then(|addr| state.checked_store(addr, reduced)),
)
} }
Opcode::StackLoad => { Opcode::StackLoad => {
let address = sum(imm(), args_range(1..)?)? as usize;
let load_ty = inst_context.controlling_type().unwrap(); let load_ty = inst_context.controlling_type().unwrap();
let loaded = state.load_stack(address, load_ty)?; let slot = inst.stack_slot().unwrap();
ControlFlow::Assign(smallvec!(loaded)) 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 => { Opcode::StackStore => {
let address = sum(imm(), args_range(1..)?)? as usize; let arg = arg(0)?;
let arg0 = arg(0)?; let slot = inst.stack_slot().unwrap();
state.store_stack(address, arg0)?; let offset = sum(imm(), args_range(1..)?)? as u64;
ControlFlow::Continue 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::GlobalValue => unimplemented!("GlobalValue"),
Opcode::SymbolValue => unimplemented!("SymbolValue"), Opcode::SymbolValue => unimplemented!("SymbolValue"),
Opcode::TlsValue => unimplemented!("TlsValue"), 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") 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)] #[derive(Error, Debug, PartialEq)]

View File

@@ -92,7 +92,7 @@ impl Display for ValueTypeClass {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum ValueConversionKind { pub enum ValueConversionKind {
/// Throw a [ValueError] if an exact conversion to [Type] is not possible; e.g. in `i32` to /// Throw a [ValueError] if an exact conversion to [Type] is not possible; e.g. in `i32` to
/// `i16`, convert `0x00001234` to `0x1234`. /// `i16`, convert `0x00001234` to `0x1234`.
@@ -234,6 +234,7 @@ impl Value for DataValue {
ValueConversionKind::Exact(ty) => match (self, ty) { ValueConversionKind::Exact(ty) => match (self, ty) {
// TODO a lot to do here: from bmask to ireduce to raw_bitcast... // 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::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), (DataValue::B(b), t) if t.is_bool() => DataValue::B(b),
(dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind), (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind),
}, },
@@ -273,14 +274,17 @@ impl Value for DataValue {
(types::I32, types::I64) => unimplemented!(), (types::I32, types::I64) => unimplemented!(),
_ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind),
}, },
ValueConversionKind::ZeroExtend(ty) => match (self.ty(), ty) { ValueConversionKind::ZeroExtend(ty) => match (self, ty) {
(types::I8, types::I16) => unimplemented!(), (DataValue::I8(_), types::I16) => unimplemented!(),
(types::I8, types::I32) => unimplemented!(), (DataValue::I8(_), types::I32) => unimplemented!(),
(types::I8, types::I64) => unimplemented!(), (DataValue::I8(_), types::I64) => unimplemented!(),
(types::I16, types::I32) => unimplemented!(), (DataValue::I16(_), types::I32) => unimplemented!(),
(types::I16, types::I64) => unimplemented!(), (DataValue::I16(_), types::I64) => unimplemented!(),
(types::I32, types::I64) => unimplemented!(), (DataValue::U32(n), types::I64) => DataValue::U64(n as u64),
_ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), (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 { ValueConversionKind::ToUnsigned => match self {
DataValue::I8(n) => DataValue::U8(n as u8), DataValue::I8(n) => DataValue::U8(n as u8),
@@ -340,7 +344,8 @@ impl Value for DataValue {
} }
fn add(self, other: Self) -> ValueResult<Self> { fn add(self, other: Self) -> ValueResult<Self> {
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<Self> { fn sub(self, other: Self) -> ValueResult<Self> {
@@ -396,7 +401,7 @@ impl Value for DataValue {
} }
fn and(self, other: Self) -> ValueResult<Self> { fn and(self, other: Self) -> ValueResult<Self> {
binary_match!(&(&self, &other); [I8, I16, I32, I64]) binary_match!(&(&self, &other); [B, I8, I16, I32, I64])
} }
fn or(self, other: Self) -> ValueResult<Self> { fn or(self, other: Self) -> ValueResult<Self> {