* cranelift-interpreter: Implement `call_indirect` * cranelift: Fix typo * riscv64: Enable `call_indirect` tests
316 lines
10 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|