Skip to content

Commit

Permalink
wip: br_if and recursive fibonacci works, still a lot of sidetable cl…
Browse files Browse the repository at this point in the history
…ones
  • Loading branch information
florianhartung committed Oct 8, 2024
1 parent 6d83b21 commit a324247
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/core/reader/types/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub const BR: u8 = 0x0C;
pub const BR_IF: u8 = 0x0D;
pub const BR_TABLE: u8 = 0x0E;
pub const RETURN: u8 = 0x0F;
pub const DROP: u8 = 0x1A;
pub const LOCAL_GET: u8 = 0x20;
pub const LOCAL_SET: u8 = 0x21;
pub const LOCAL_TEE: u8 = 0x22;
Expand Down
45 changes: 32 additions & 13 deletions src/execution/interpreter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ pub(super) fn run<H: HookSet>(
.get(stack.current_stackframe().func_idx)
.unwrap_validated();

let sidetable: &Sidetable = &func_inst.sidetable;
let mut sidetable_pointer: usize = 0;

// Start reading the function's instructions
let mut wasm = WasmReader::new(wasm_bytecode);

Expand Down Expand Up @@ -109,52 +106,70 @@ pub(super) fn run<H: HookSet>(
let params = stack.pop_tail_iter(func_to_call_ty.params.valtypes.len());
let remaining_locals = func_to_call_inst.locals.iter().cloned();

let sidetable = func_to_call_inst.sidetable.clone();

trace!("Instruction: call [{func_to_call_idx:?}]");
let locals = Locals::new(params, remaining_locals);
stack.push_stackframe(func_to_call_idx, func_to_call_ty, locals, wasm.pc);

stack.push_stackframe(func_to_call_idx, func_to_call_ty, locals, wasm.pc, sidetable, 0);

wasm.move_start_to(func_to_call_inst.code_expr)
.unwrap_validated();
}
BLOCK => {
let _block_type = BlockType::read_unvalidated(&mut wasm);
trace!("reached block, ignoring because of sidetable");

// 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 => {
let sidetable = stack.current_stackframe().sidetable.clone();
let mut sidetable_pointer = stack.current_stackframe().sidetable_pointer;
do_sidetable_control_transfer(&sidetable, &mut sidetable_pointer, &mut wasm, stack);
stack.current_stackframe_mut().sidetable_pointer = sidetable_pointer;
}
BR => {
let _target_label = wasm.read_var_u32().unwrap_validated();

let sidetable = stack.current_stackframe().sidetable.clone();
let mut sidetable_pointer = stack.current_stackframe().sidetable_pointer;
do_sidetable_control_transfer(&sidetable, &mut sidetable_pointer, &mut wasm, stack);
stack.current_stackframe_mut().sidetable_pointer = sidetable_pointer;
}
BR_IF => {
let _target_label = wasm.read_var_u32().unwrap_validated();

trace!("Reached br_if");
let condition: i32 = stack.pop_value(ValType::NumType(NumType::I32)).into();
trace!("br_if condition is {condition}");

if condition != 0 {
do_sidetable_control_transfer(
&sidetable,
&mut sidetable_pointer,
&mut wasm,
stack,
);
let sidetable = stack.current_stackframe().sidetable.clone();
let mut sidetable_pointer = stack.current_stackframe().sidetable_pointer;
do_sidetable_control_transfer(&sidetable, &mut sidetable_pointer, &mut wasm, stack);
stack.current_stackframe_mut().sidetable_pointer = sidetable_pointer;
} else {
sidetable_pointer += 1;
stack.current_stackframe_mut().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 => {
trace!("executing local.get");
stack.get_local(wasm.read_var_u32().unwrap_validated() as LocalIdx);
}
LOCAL_SET => stack.set_local(wasm.read_var_u32().unwrap_validated() as LocalIdx),
LOCAL_TEE => stack.tee_local(wasm.read_var_u32().unwrap_validated() as LocalIdx),
LOCAL_SET => {
trace!("executing local.set");
stack.set_local(wasm.read_var_u32().unwrap_validated() as LocalIdx);
}
LOCAL_TEE => {
trace!("executing local.tee");
stack.tee_local(wasm.read_var_u32().unwrap_validated() as LocalIdx);
}
GLOBAL_GET => {
let global_idx = wasm.read_var_u32().unwrap_validated() as GlobalIdx;
let global = store.globals.get(global_idx).unwrap_validated();
Expand All @@ -167,6 +182,9 @@ pub(super) fn run<H: HookSet>(

global.value = stack.pop_value(global.global.ty.ty)
}
DROP => {
stack.pop_tail_iter(1); // pop_value takes a type. this works for now
}
I32_LOAD => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();
Expand Down Expand Up @@ -1455,6 +1473,7 @@ fn do_sidetable_control_transfer(
wasm: &mut WasmReader,
stack: &mut Stack,
) {
trace!("Fetching sidetable entry at {}", sidetable_pointer);
let entry = *sidetable
.get(*sidetable_pointer)
.expect("sidetable entry to exist");
Expand Down
4 changes: 2 additions & 2 deletions src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ where

// setting `usize::MAX` as return address for the outermost function ensures that we
// observably fail upon errornoeusly continuing execution after that function returns.
stack.push_stackframe(func_idx, func_ty, locals, usize::MAX);
stack.push_stackframe(func_idx, func_ty, locals, usize::MAX, func_inst.sidetable.clone(), 0);

// Run the interpreter
run(
Expand Down Expand Up @@ -227,7 +227,7 @@ where
// Prepare a new stack with the locals for the entry function
let mut stack = Stack::new();
let locals = Locals::new(params.into_iter(), func_inst.locals.iter().cloned());
stack.push_stackframe(func_idx, func_ty, locals, 0);
stack.push_stackframe(func_idx, func_ty, locals, 0, func_inst.sidetable.clone(), 0);

// Run the interpreter
run(
Expand Down
16 changes: 14 additions & 2 deletions src/execution/value_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloc::vec::{Drain, Vec};

use crate::core::indices::{FuncIdx, LocalIdx};
use crate::core::reader::types::{FuncType, ValType};
use crate::core::sidetable::Sidetable;
use crate::execution::assert_validated::UnwrapValidatedExt;
use crate::execution::value::Value;
use crate::locals::Locals;
Expand Down Expand Up @@ -88,7 +89,7 @@ impl Stack {
self.frames
.last()
.map_or(true, |last_frame| self.values.len()
> last_frame.value_stack_base_idx),
>= last_frame.value_stack_base_idx),
"can not pop values past the current stackframe"
);

Expand Down Expand Up @@ -124,7 +125,7 @@ impl Stack {

let stack_value = self.pop_value(local_ty);
debug_assert!(
self.values.len() > self.current_stackframe().value_stack_base_idx,
self.values.len() >= self.current_stackframe().value_stack_base_idx,
"can not pop values past the current stackframe"
);

Expand Down Expand Up @@ -180,13 +181,17 @@ impl Stack {
func_ty: &FuncType,
locals: Locals,
return_addr: usize,
sidetable: Sidetable,
sidetable_pointer: usize,
) {
self.frames.push(CallFrame {
func_idx,
locals,
return_addr,
value_stack_base_idx: self.values.len(),
return_value_count: func_ty.returns.valtypes.len(),
sidetable,
sidetable_pointer,
})
}

Expand Down Expand Up @@ -228,6 +233,13 @@ pub(crate) struct CallFrame {

/// Number of return values to retain on [`Stack::values`] when unwinding/popping a [`CallFrame`]
pub return_value_count: usize,

/// The sidetable used for control flow in the current [`CallFrame`]
/// FIXME: This is currently cloned for every callframe, which is pretty inefficient.
pub sidetable: Sidetable,

/// An index that points to the current sidetable entry
pub sidetable_pointer: usize,
}

#[test]
Expand Down
95 changes: 85 additions & 10 deletions src/validation/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fn read_instructions(
stack.push_label(LabelInfo::Block {
func_type,
sidetable_branch_indices: Vec::new(),
num_values_on_stack_before: stack.len(),
num_values_on_stack_before: stack.len_without_labels(),
});
}
BR => {
Expand All @@ -135,26 +135,97 @@ fn read_instructions(
.ok_or(Error::InvalidLabelIdx)?;

match label_info {
LabelInfo::Block { func_type, .. } => {
LabelInfo::Block {
func_type,
num_values_on_stack_before,
..
} => {
let block_return_types = &func_type.returns.valtypes;
// todo!("do branches require the top of the stack or the entire stack to be correct?");
stack.assert_val_types_on_top(&func_type.returns.valtypes)?;
stack.assert_val_types_on_top(block_return_types)?;

let Some(stack_len) = stack.len_without_labels() else {
// The stack contains an `Unspecified` element, so this instruction is practically unreachable.
// It does not matter if the Unspecified was before this Block/If/Loop started or after.
//
// We don't need to generate a sidetable entry and the validation of the top-most stack values has already be done, so we can just continue with the next instruction.
continue;
};

let num_values_on_stack_before = num_values_on_stack_before.expect("this to always be Some. We already checked through `stack.len_without_label` whether the stack contains Unspecified. If so, we ignored this instruction. Because we are in a block right now, the stack values below the block label cannot have changed, so any Unspecified entries below the block label are impossible.");

let stack_len = stack.len();
let val_count = block_return_types.len();
let pop_count = stack_len - num_values_on_stack_before - val_count;

error!("val: {}, pop: {}", val_count, pop_count);

// FIXME Now we actually need to modifiy the label info, so we have to borrow it again
let Some(LabelInfo::Block {
func_type,
sidetable_branch_indices,
num_values_on_stack_before,
..
}) = stack.find_nth_label_from_top_mut(label_idx)
else {
unreachable!("We just found this block")
};

let val_count = func_type.returns.valtypes.len();
sidetable_builder.0.push(IncompleteSidetableEntry {
ip: wasm.pc, // TODO maybe - 1?
delta_ip: None,
delta_stp: None,
val_count,
pop_count,
});

// Store index of new sidetable entry so it can be completed, when the end of this block is reached.
sidetable_branch_indices.push(sidetable_builder.0.len() - 1);
}
_ => todo!("handle branches for loops and ifs/elses"),
}
}
BR_IF => {
let label_idx = wasm.read_var_u32()? as LabelIdx;

todo!("pop_count has to ignore labels on the stack, which it currently doesn't");
let pop_count = stack_len - *num_values_on_stack_before - val_count;
// condition for if
stack.assert_pop_val_type(ValType::NumType(NumType::I32))?;
trace!("read br_if condition");

let label_info: &LabelInfo = stack
.find_nth_label_from_top(label_idx)
.ok_or(Error::InvalidLabelIdx)?;

match label_info {
LabelInfo::Block {
func_type,
num_values_on_stack_before,
..
} => {
let block_return_types = &func_type.returns.valtypes;
// todo!("do branches require the top of the stack or the entire stack to be correct?");
stack.assert_val_types_on_top(block_return_types)?;

let Some(stack_len) = stack.len_without_labels() else {
// The stack contains an `Unspecified` element, so this instruction is practically unreachable.
// It does not matter if the Unspecified was before this Block/If/Loop started or after.
//
// We don't need to generate a sidetable entry and the validation of the top-most stack values has already be done, so we can just continue with the next instruction.
continue;
};

let num_values_on_stack_before = num_values_on_stack_before.expect("this to always be Some. We already checked through `stack.len_without_label` whether the stack contains Unspecified. If so, we ignored this instruction. Because we are in a block right now, the stack values below the block label cannot have changed, so any Unspecified entries below the block label are impossible.");

let val_count = block_return_types.len();
let pop_count = stack_len - num_values_on_stack_before - val_count;

error!("val: {}, pop: {}", val_count, pop_count);

// FIXME Now we actually need to modifiy the label info, so we have to borrow it again
let Some(LabelInfo::Block {
sidetable_branch_indices,
..
}) = stack.find_nth_label_from_top_mut(label_idx)
else {
unreachable!("We just found this block")
};

sidetable_builder.0.push(IncompleteSidetableEntry {
ip: wasm.pc, // TODO maybe - 1?
Expand Down Expand Up @@ -287,11 +358,12 @@ fn read_instructions(
let local_ty = locals.get(local_idx).ok_or(Error::InvalidLocalIdx)?;
stack.assert_pop_val_type(*local_ty)?;
}
// local.set [t] -> [t]
// local.tee [t] -> [t]
LOCAL_TEE => {
let local_idx = wasm.read_var_u32()? as LocalIdx;
let local_ty = locals.get(local_idx).ok_or(Error::InvalidLocalIdx)?;
stack.assert_pop_val_type(*local_ty)?;
stack.push_valtype(*local_ty);
}
// global.get [] -> [t]
GLOBAL_GET => {
Expand All @@ -315,6 +387,9 @@ fn read_instructions(

stack.assert_pop_val_type(global.ty.ty)?;
}
DROP => {
stack.pop_valtype()?;
}
// i32.load [i32] -> [i32]
I32_LOAD => {
let _memarg = MemArg::read_unvalidated(wasm);
Expand Down
33 changes: 30 additions & 3 deletions src/validation/validation_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ impl ValidationStack {
self.stack.len()
}

/// Returns None if there is atleast one Unspecified entry.
/// This function is only used during sidetable generation and there those instructions, where atleast one unspecified is present, are unreachable during execution.
pub(super) fn len_without_labels(&self) -> Option<usize> {
let mut num_values = 0;
for entry in &self.stack {
match entry {
ValidationStackEntry::UnspecifiedValTypes => return None,
ValidationStackEntry::Label(_) => {}
ValidationStackEntry::Val(_) => num_values += 1,
}
}
Some(num_values)
}

pub(super) fn push_valtype(&mut self, valtype: ValType) {
self.stack.push(ValidationStackEntry::Val(valtype));
}
Expand Down Expand Up @@ -65,6 +79,19 @@ impl ValidationStack {
.ok_or(Error::InvalidValidationStackValType(None))
}

pub fn pop_valtype(&mut self) -> Result<()> {
match self.stack.last() {
Some(ValidationStackEntry::Val(_)) => {
self.stack.pop();
}
Some(ValidationStackEntry::UnspecifiedValTypes) => {}
Some(ValidationStackEntry::Label(li)) => return Err(Error::FoundLabel(li.clone())),
None => return Err(Error::InvalidValType),
};

Ok(())
}

/// Assert the top-most [`ValidationStackEntry`] is a specific [`ValType`], after popping it from the [`ValidationStack`]
///
/// # Returns
Expand Down Expand Up @@ -258,11 +285,11 @@ pub(crate) enum LabelInfo {
Block {
func_type: FuncType,
sidetable_branch_indices: Vec<usize>,
num_values_on_stack_before: usize,
num_values_on_stack_before: Option<usize>, // Is None if the block is pracically unreachable. We still have to validate everything, so this LabelInfo is still necessary.
},
Loop {
func_type: FuncType,
num_values_on_stack_before: usize,
num_values_on_stack_before: Option<usize>, // Is None if the block is pracically unreachable. We still have to validate everything, so this LabelInfo is still necessary.
first_sidetable_entry_index: usize,
},
If, // TODO
Expand All @@ -288,7 +315,7 @@ mod tests {
},
},
sidetable_branch_indices: Vec::new(),
num_values_on_stack_before: 0,
num_values_on_stack_before: Some(0),
}
}

Expand Down
Loading

0 comments on commit a324247

Please sign in to comment.