Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
florianhartung committed Sep 26, 2024
1 parent 992a168 commit c18a672
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 58 deletions.
4 changes: 2 additions & 2 deletions src/core/reader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ pub mod span {
/// indexing unknown slices, as a [Span] does not validate the length of the indexed slice.
#[derive(Copy, Clone, Debug, Hash)]
pub struct Span {
pub(super) from: usize,
pub(super) len: usize,
pub(crate) from: usize,
pub(crate) len: usize,
}

impl Span {
Expand Down
80 changes: 80 additions & 0 deletions src/core/reader/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,86 @@ impl WasmReadable for FuncType {
}
}

/// <https://webassembly.github.io/spec/core/binary/instructions.html#binary-blocktype>
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockType {
Empty,
Returns(ValType),
Type(u32),
}

impl WasmReadable for BlockType {
fn read(wasm: &mut WasmReader) -> Result<Self> {
// FIXME: Use transactions for ValType::read
if wasm.peek_u8()? as i8 == 0x40 {
// Empty block type
let _ = wasm.read_u8().expect("read to succeed, as we just peeked");
Ok(BlockType::Empty)
} else if let Ok(val_ty) = ValType::read(wasm) {
// No parameters and given valtype as the result
Ok(BlockType::Returns(val_ty))
} else {
// An index to a function type
wasm.read_var_i33()
.and_then(|idx| idx.try_into().map_err(|_| Error::InvalidFuncTypeIdx))
.map(BlockType::Type)
}
}

fn read_unvalidated(wasm: &mut WasmReader) -> Self {
if wasm.peek_u8().unwrap_validated() as i8 == 0x40 {
// Empty block type
let _ = wasm.read_u8();

BlockType::Empty
} else if let Ok(val_ty) = ValType::read(wasm) {
// No parameters and given valtype as the result
BlockType::Returns(val_ty)
} else {
// An index to a function type
BlockType::Type(
wasm.read_var_i33()
.unwrap_validated()
.try_into()
.unwrap_validated(),
)
}
}
}

impl BlockType {
pub fn as_func_type(&self, func_types: &[FuncType]) -> Result<FuncType> {
match self {
BlockType::Empty => Ok(FuncType {
params: ResultType {
valtypes: Vec::new(),
},
returns: ResultType {
valtypes: Vec::new(),
},
}),
BlockType::Returns(val_type) => Ok(FuncType {
params: ResultType {
valtypes: Vec::new(),
},
returns: ResultType {
valtypes: [val_type.clone()].into(),
},
}),
BlockType::Type(type_idx) => {
let type_idx: usize = (*type_idx)
.try_into()
.map_err(|_| Error::InvalidFuncTypeIdx)?;

func_types
.get(type_idx)
.cloned()
.ok_or_else(|| Error::InvalidFuncTypeIdx)
}
}
}
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Limits {
pub min: u32,
Expand Down
14 changes: 13 additions & 1 deletion src/core/sidetable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@
//! back on the operand stack after unwinding. This behavior can be emulated by copying the
//! uppermost [`SidetableEntry::valcnt`] operands on the operand stack before taking a branch
//! into a structured control instruction.
//!
//! # Relevant instructions
//! **Sidetable jump origins (and how many ST entries they require)**
//! - br (1)
//! - br_if (1)
//! - br_table (num_labels + 1 for default label)
//! - if (2, maybe 1??)
//! **Sidetable jump targets**
//! - end of block
//! - loop
//! - else
//! - end of else block
//!
//! # Reference
//!
Expand Down Expand Up @@ -61,7 +74,6 @@ pub struct SidetableEntry {
pub pop_count: usize,
}

/// A helper used during validation to build the sidetable
pub struct SidetableBuilder {}

impl SidetableBuilder {
Expand Down
43 changes: 40 additions & 3 deletions src/execution/interpreter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ use crate::{
core::{
indices::{FuncIdx, GlobalIdx, LocalIdx},
reader::{
types::{memarg::MemArg, FuncType},
types::{memarg::MemArg, BlockType, FuncType},
WasmReadable, WasmReader,
},
sidetable::{self, Sidetable, SidetableEntry},
},
locals::Locals,
store::Store,
value,
unreachable_validated, value,
value_stack::Stack,
NumType, RuntimeError, ValType, Value,
};
Expand Down Expand Up @@ -116,7 +116,40 @@ pub(super) fn run<H: HookSet>(
wasm.move_start_to(func_to_call_inst.code_expr)
.unwrap_validated();
}
BR => {}
BLOCK => {
let _block_type = BlockType::read_unvalidated(&mut wasm);

// Nothing else to do. The sidetable is responsible for control flow.
}
IF => {
todo!("execute if instruction, low priority as if can be simulated with br_if and blocks")
}
ELSE => {
do_sidetable_control_transfer(&sidetable, &mut sidetable_pointer, &mut wasm, stack);
}
BR => {
let _target_label = wasm.read_var_u32().unwrap_validated();
do_sidetable_control_transfer(&sidetable, &mut sidetable_pointer, &mut wasm, stack);
}
BR_IF => {
let _target_label = wasm.read_var_u32().unwrap_validated();

let condition: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

if condition != 0 {
do_sidetable_control_transfer(
&sidetable,
&mut sidetable_pointer,
&mut wasm,
stack,
);
} else {
sidetable_pointer += 1;
}
}
BR_TABLE => {
todo!("execute BR_TABLE, Titzer stores multiple entries in the sidetable here, one for each label. See https://arxiv.org/pdf/2205.01183#lstlisting.1");
}
LOCAL_GET => {
stack.get_local(wasm.read_var_u32().unwrap_validated() as LocalIdx);
}
Expand Down Expand Up @@ -1420,6 +1453,7 @@ fn do_sidetable_control_transfer(
sidetable: &Sidetable,
sidetable_pointer: &mut usize,
wasm: &mut WasmReader,
stack: &mut Stack,
) {
let entry = *sidetable
.get(*sidetable_pointer)
Expand All @@ -1435,4 +1469,7 @@ fn do_sidetable_control_transfer(

*sidetable_pointer +=
usize::try_from(entry.delta_stp).expect("delta_stp to be negative for branches");
usize::try_from(entry.delta_stp).expect("delta_stp to be negative for branches");

stack.unwind(entry.val_count, entry.pop_count);
}
81 changes: 77 additions & 4 deletions src/execution/value_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use crate::execution::value::Value;
use crate::locals::Locals;
use crate::unreachable_validated;

/// The stack at runtime containing
/// 1. Values
/// 2. Labels
/// 3. Activations
/// The stack at runtime containing values
///
/// See <https://webassembly.github.io/spec/core/exec/runtime.html#stack>
#[derive(Default)]
Expand Down Expand Up @@ -52,6 +49,54 @@ impl Stack {
}
}

/// This unwinds the stack by popping the topmost `num_values_to_keep` values and storing them temporarily.
/// Then the next topmost `num_values_to_remove` values are discarded before the previously popped values are pushed back to the stack.
///
/// Example:
/// ```
/// BOTTOM TOP
/// -----------------------------------------------------------
/// | ... | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1 | 2 | 3 | 4 |
/// ---------------------------------------------------------
/// | num_values_to_remove | num_values_to_keep |
/// ```
/// becomes
///
/// ```
/// BOTTOM TOP
/// |----------------------------------
/// | ... | 1 | 8 | 9 | 1 | 2 | 3 | 4 |
/// |----------------------------------
/// | num_values_to_keep |
/// ```
///
// TODO Eventually this value stack should store raw bytes instead of enums on the stack. Then both `num_values` parameters should instead work with bytes.
pub fn unwind(&mut self, num_values_to_keep: usize, num_values_to_remove: usize) {
// FIXME: This is inefficient
let mut temporary_values = Vec::new();

for _ in 0..num_values_to_keep {
temporary_values.push(self.values.pop().unwrap_validated());
}

for _ in 0..num_values_to_remove {
self.values.pop().unwrap_validated();
}

// We should not have crossed a callframe boundary
debug_assert!(
self.frames
.last()
.map_or(true, |last_frame| self.values.len()
> last_frame.value_stack_base_idx),
"can not pop values past the current stackframe"
);

for value in temporary_values.into_iter().rev() {
self.values.push(value);
}
}

/// Copy a value of the given [ValType] from the value stack without removing it
pub fn peek_value(&self, ty: ValType) -> Value {
let value = self.values.last().unwrap_validated();
Expand Down Expand Up @@ -184,3 +229,31 @@ pub(crate) struct CallFrame {
/// Number of return values to retain on [`Stack::values`] when unwinding/popping a [`CallFrame`]
pub return_value_count: usize,
}

#[test]
fn test_stack_unwind() {
fn test_with_ten_example_numbers(num_keep: usize, num_pop: usize, expected: &[u32]) {
let mut stack = Stack::new();
for i in 0..10 {
stack.push_value(Value::I32(i));
}

stack.unwind(num_keep, num_pop);

let expected_values: Vec<Value> = expected.into_iter().copied().map(Value::I32).collect();

assert_eq!(&stack.values, &expected_values);
}

test_with_ten_example_numbers(2, 3, &[0, 1, 2, 3, 4, 8, 9]);

test_with_ten_example_numbers(0, 2, &[0, 1, 2, 3, 4, 5, 6, 7]);

test_with_ten_example_numbers(0, 0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

test_with_ten_example_numbers(4, 0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

test_with_ten_example_numbers(10, 0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

test_with_ten_example_numbers(1, 9, &[9]);
}
Loading

0 comments on commit c18a672

Please sign in to comment.