Files
wasmtime/cranelift/interpreter/src/address.rs
Afonso Bordado 2776074dfc 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.
2021-08-24 09:29:11 -07:00

298 lines
9.9 KiB
Rust

//! 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);
}
}
}