Files
wasmtime/cranelift/interpreter/src/address.rs
Afonso Bordado e9095050be cranelift-interpreter: Implement call_indirect and return_call_indirect (#5877)
* cranelift-interpreter: Implement `call_indirect`

* cranelift: Fix typo

* riscv64: Enable `call_indirect` tests
2023-02-25 13:16:59 +00:00

316 lines
10 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 `function` region, which has 1 "entry" bit
//! this address would have 32 - 1 - 2 = 29 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 | Function | 0b01 | 1 | 29 |
//! | 32 | Table | 0b10 | 5 | 25 |
//! | 32 | GlobalValue | 0b11 | 6 | 24 |
//! | 64 | Stack | 0b00 | 0 | 62 |
//! | 64 | Function | 0b01 | 1 | 61 |
//! | 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,
Function,
Table,
GlobalValue,
}
impl AddressRegion {
pub fn decode(bits: u64) -> Self {
assert!(bits < 4);
match bits {
0 => AddressRegion::Stack,
1 => AddressRegion::Function,
2 => AddressRegion::Table,
3 => AddressRegion::GlobalValue,
_ => unreachable!(),
}
}
pub fn encode(self) -> u64 {
match self {
AddressRegion::Stack => 0,
AddressRegion::Function => 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,
// We have two function "entries", one for libcalls, and
// another for user functions.
(_, AddressRegion::Function) => 1,
(AddressSize::_32, AddressRegion::Table) => 5,
(AddressSize::_32, AddressRegion::GlobalValue) => 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)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AddressFunctionEntry {
UserFunction = 0,
LibCall,
}
impl From<u64> for AddressFunctionEntry {
fn from(bits: u64) -> Self {
match bits {
0 => AddressFunctionEntry::UserFunction,
1 => AddressFunctionEntry::LibCall,
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn address_region_roundtrip_encode_decode() {
let all_regions = [
AddressRegion::Stack,
AddressRegion::Function,
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::Function, 0, 0),
(AddressSize::_32, AddressRegion::Function, 1, 1),
(AddressSize::_32, AddressRegion::Function, 0, 1024),
(AddressSize::_32, AddressRegion::Function, 1, 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::Function, 0, 0),
(AddressSize::_64, AddressRegion::Function, 1, 1),
(AddressSize::_64, AddressRegion::Function, 0, 1024),
(AddressSize::_64, AddressRegion::Function, 1, 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);
}
}
}