wasm_interp: implement calls with arguments

This commit is contained in:
Brian Carroll 2022-11-23 22:01:36 +00:00
parent 3aeab0dbd7
commit 6523b38847
No known key found for this signature in database
GPG key ID: 5C7B2EC4101703C0
5 changed files with 160 additions and 74 deletions

View file

@ -3,15 +3,15 @@ use bumpalo::{collections::Vec, Bump};
use roc_wasm_module::{parse::Parse, Value, ValueType};
use std::iter::repeat;
use crate::ValueStack;
/// Struct-of-Arrays storage for the call stack.
/// Type info is packed to avoid wasting space on padding.
/// However we store 64 bits for every local, even 32-bit values, for easy random access.
#[derive(Debug)]
pub struct CallStack<'a> {
/// return addresses (one entry per frame)
return_addrs: Vec<'a, u32>,
/// number of nested blocks in each frame
return_block_depths: Vec<'a, u32>,
/// return addresses and nested block depths (one entry per frame)
return_addrs_and_block_depths: Vec<'a, (u32, u32)>,
/// frame offsets into the `locals`, `is_float`, and `is_64` vectors (one entry per frame)
frame_offsets: Vec<'a, u32>,
/// binary data for local variables (one entry per local)
@ -35,8 +35,7 @@ Not clear if this would be better! Stack access pattern is pretty cache-friendly
impl<'a> CallStack<'a> {
pub fn new(arena: &'a Bump) -> Self {
CallStack {
return_addrs: Vec::with_capacity_in(256, arena),
return_block_depths: Vec::with_capacity_in(256, arena),
return_addrs_and_block_depths: Vec::with_capacity_in(256, arena),
frame_offsets: Vec::with_capacity_in(256, arena),
locals_data: Vec::with_capacity_in(16 * 256, arena),
is_float: BitVec::with_capacity(256),
@ -49,17 +48,31 @@ impl<'a> CallStack<'a> {
&mut self,
return_addr: u32,
return_block_depth: u32,
n_args: u32,
value_stack: &mut ValueStack<'a>,
code_bytes: &[u8],
pc: &mut usize,
) {
self.return_addrs.push(return_addr);
self.return_block_depths.push(return_block_depth);
self.return_addrs_and_block_depths
.push((return_addr, return_block_depth));
let frame_offset = self.is_64.len();
self.frame_offsets.push(frame_offset as u32);
let mut total = 0;
// Make space for arguments
self.is_64.extend(repeat(false).take(n_args as usize));
self.is_float.extend(repeat(false).take(n_args as usize));
self.locals_data.extend(repeat(0).take(n_args as usize));
// Pop arguments off the value stack and into locals
for i in (0..n_args).rev() {
let arg = value_stack.pop();
self.set_local_help(i, arg);
}
// Parse local variable declarations in the function header. They're grouped by type.
let group_count = u32::parse((), code_bytes, pc).unwrap();
for _ in 0..group_count {
let local_group_count = u32::parse((), code_bytes, pc).unwrap();
for _ in 0..local_group_count {
let (group_size, ty) = <(u32, ValueType)>::parse((), code_bytes, pc).unwrap();
let n = group_size as usize;
total += n;
@ -72,12 +85,12 @@ impl<'a> CallStack<'a> {
}
/// On returning from a Wasm call, drop its locals and retrieve the return address
pub fn pop_frame(&mut self) -> Option<u32> {
pub fn pop_frame(&mut self) -> Option<(u32, u32)> {
let frame_offset = self.frame_offsets.pop()? as usize;
self.locals_data.truncate(frame_offset);
self.is_64.truncate(frame_offset);
self.is_64.truncate(frame_offset);
self.return_addrs.pop()
self.return_addrs_and_block_depths.pop()
}
pub fn get_local(&self, local_index: u32) -> Value {
@ -103,28 +116,29 @@ impl<'a> CallStack<'a> {
}
pub fn set_local(&mut self, local_index: u32, value: Value) {
let type_check_ok = self.set_local_help(local_index, value);
debug_assert!(type_check_ok);
}
fn set_local_help(&mut self, local_index: u32, value: Value) -> bool {
let frame_offset = *self.frame_offsets.last().unwrap();
let index = (frame_offset + local_index) as usize;
match value {
Value::I32(x) => {
self.locals_data[index] = u64::from_ne_bytes((x as i64).to_ne_bytes());
debug_assert!(!self.is_64[index]);
debug_assert!(!self.is_float[index]);
!self.is_64[index] && !self.is_float[index]
}
Value::I64(x) => {
self.locals_data[index] = u64::from_ne_bytes((x).to_ne_bytes());
debug_assert!(!self.is_float[index]);
debug_assert!(self.is_64[index]);
!self.is_float[index] && self.is_64[index]
}
Value::F32(x) => {
self.locals_data[index] = x.to_bits() as u64;
debug_assert!(self.is_float[index]);
debug_assert!(!self.is_64[index]);
self.is_float[index] && !self.is_64[index]
}
Value::F64(x) => {
self.locals_data[index] = x.to_bits();
debug_assert!(self.is_float[index]);
debug_assert!(self.is_64[index]);
self.is_float[index] && self.is_64[index]
}
}
}
@ -143,19 +157,20 @@ mod tests {
assert_eq!(call_stack.get_local(index), value);
}
fn setup(call_stack: &mut CallStack<'_>) {
fn setup<'a>(arena: &'a Bump, call_stack: &mut CallStack<'a>) {
let mut buffer = vec![];
let mut cursor = 0;
let mut vs = ValueStack::new(arena);
// Push a other few frames before the test frame, just to make the scenario more typical.
[(1u32, ValueType::I32)].serialize(&mut buffer);
call_stack.push_frame(0x11111, 0, &buffer, &mut cursor);
call_stack.push_frame(0x11111, 0, 0, &mut vs, &buffer, &mut cursor);
[(2u32, ValueType::I32)].serialize(&mut buffer);
call_stack.push_frame(0x22222, 0, &buffer, &mut cursor);
call_stack.push_frame(0x22222, 0, 0, &mut vs, &buffer, &mut cursor);
[(3u32, ValueType::I32)].serialize(&mut buffer);
call_stack.push_frame(0x33333, 0, &buffer, &mut cursor);
call_stack.push_frame(0x33333, 0, 0, &mut vs, &buffer, &mut cursor);
// Create a test call frame with local variables of every type
[
@ -165,14 +180,15 @@ mod tests {
(1u32, ValueType::F64),
]
.serialize(&mut buffer);
call_stack.push_frame(RETURN_ADDR, 0, &buffer, &mut cursor);
call_stack.push_frame(RETURN_ADDR, 0, 0, &mut vs, &buffer, &mut cursor);
}
#[test]
fn test_all() {
let arena = Bump::new();
let mut call_stack = CallStack::new(&arena);
setup(&mut call_stack);
setup(&arena, &mut call_stack);
test_get_set(&mut call_stack, 0, Value::I32(123));
test_get_set(&mut call_stack, 8, Value::I64(123456));
@ -191,7 +207,7 @@ mod tests {
test_get_set(&mut call_stack, 14, Value::F64(f64::MIN));
test_get_set(&mut call_stack, 14, Value::F64(f64::MAX));
assert_eq!(call_stack.pop_frame(), Some(RETURN_ADDR));
assert_eq!(call_stack.pop_frame(), Some((RETURN_ADDR, 0)));
}
#[test]
@ -199,7 +215,7 @@ mod tests {
fn test_type_error_i32() {
let arena = Bump::new();
let mut call_stack = CallStack::new(&arena);
setup(&mut call_stack);
setup(&arena, &mut call_stack);
test_get_set(&mut call_stack, 0, Value::F32(1.01));
}
@ -208,7 +224,7 @@ mod tests {
fn test_type_error_i64() {
let arena = Bump::new();
let mut call_stack = CallStack::new(&arena);
setup(&mut call_stack);
setup(&arena, &mut call_stack);
test_get_set(&mut call_stack, 8, Value::F32(1.01));
}
@ -217,7 +233,7 @@ mod tests {
fn test_type_error_f32() {
let arena = Bump::new();
let mut call_stack = CallStack::new(&arena);
setup(&mut call_stack);
setup(&arena, &mut call_stack);
test_get_set(&mut call_stack, 12, Value::I32(123));
}
@ -226,7 +242,7 @@ mod tests {
fn test_type_error_f64() {
let arena = Bump::new();
let mut call_stack = CallStack::new(&arena);
setup(&mut call_stack);
setup(&arena, &mut call_stack);
test_get_set(&mut call_stack, 14, Value::I32(123));
}
}