Skip to content

Commit

Permalink
feat: implement simple linker
Browse files Browse the repository at this point in the history
Signed-off-by: George Cosma <[email protected]>
  • Loading branch information
george-cosma committed Sep 26, 2024
1 parent ee05bf3 commit 9e29849
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 108 deletions.
29 changes: 29 additions & 0 deletions src/execution/execution_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;

use crate::core::reader::types::FuncType;
use crate::core::reader::WasmReader;
use crate::execution::Store;

/// ExecutionInfo is a compilation of relevant information needed by the [interpreter loop](
/// crate::execution::interpreter_loop::run). The lifetime annotation `'r` represents that this structure needs to be
/// valid at least as long as the [RuntimeInstance](crate::execution::RuntimeInstance) that creates it.
pub struct ExecutionInfo<'r> {
pub name: String,
pub wasm_bytecode: &'r [u8],
pub wasm_reader: WasmReader<'r>,
pub fn_types: Vec<FuncType>,
pub store: Store,
}

impl<'r> ExecutionInfo<'r> {
pub fn new(name: &str, wasm_bytecode: &'r [u8], fn_types: Vec<FuncType>, store: Store) -> Self {
ExecutionInfo {
name: name.to_string(),
wasm_bytecode,
wasm_reader: WasmReader::new(wasm_bytecode),
fn_types,
store,
}
}
}
164 changes: 126 additions & 38 deletions src/execution/interpreter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ use crate::{
assert_validated::UnwrapValidatedExt,
core::{
indices::{FuncIdx, GlobalIdx, LocalIdx},
reader::{
types::{memarg::MemArg, FuncType},
WasmReadable, WasmReader,
},
reader::{types::memarg::MemArg, WasmReadable},
},
execution::execution_info::ExecutionInfo,
execution::Lut,
locals::Locals,
store::Store,
store::FuncInst,
value,
value_stack::Stack,
NumType, RuntimeError, ValType, Value,
Expand All @@ -33,21 +32,22 @@ use crate::execution::hooks::HookSet;

/// Interprets a functions. Parameters and return values are passed on the stack.
pub(super) fn run<H: HookSet>(
wasm_bytecode: &[u8],
types: &[FuncType],
store: &mut Store,
modules: &mut [ExecutionInfo],
current_module: &mut usize,
lut: &Lut,
stack: &mut Stack,
mut hooks: H,
) -> Result<(), RuntimeError> {
let func_inst = store
let func_inst = modules[*current_module]
.store
.funcs
.get(stack.current_stackframe().func_idx)
.unwrap_validated()
.try_into_local()
.unwrap_validated();

// Start reading the function's instructions
let mut wasm = WasmReader::new(wasm_bytecode);
let mut wasm = &mut modules[*current_module].wasm_reader;

// unwrap is sound, because the validation assures that the function points to valid subslice of the WASM binary
wasm.move_start_to(func_inst.code_expr).unwrap();
Expand All @@ -56,13 +56,14 @@ pub(super) fn run<H: HookSet>(
loop {
// call the instruction hook
#[cfg(feature = "hooks")]
hooks.instruction_hook(wasm_bytecode, wasm.pc);
hooks.instruction_hook(modules[*current_module].wasm_bytecode, wasm.pc);

let first_instr_byte = wasm.read_u8().unwrap_validated();

match first_instr_byte {
END => {
let maybe_return_address = stack.pop_stackframe();
let (return_module, maybe_return_address) = stack.pop_stackframe();
*current_module = return_module;

// We finished this entire invocation if there is no stackframe left. If there are
// one or more stack frames, we need to continue from where the callee was called
Expand All @@ -72,15 +73,23 @@ pub(super) fn run<H: HookSet>(
}

trace!("end of function reached, returning to previous stack frame");
wasm = &mut modules[return_module].wasm_reader;
wasm.pc = maybe_return_address;
}
RETURN => {
trace!("returning from function");

let func_to_call_idx = stack.current_stackframe().func_idx;

let func_to_call_inst = store.funcs.get(func_to_call_idx).unwrap_validated();
let func_to_call_ty = types.get(func_to_call_inst.ty()).unwrap_validated();
let func_to_call_inst = modules[*current_module]
.store
.funcs
.get(func_to_call_idx)
.unwrap_validated();
let func_to_call_ty = modules[*current_module]
.fn_types
.get(func_to_call_inst.ty())
.unwrap_validated();

let ret_vals = stack
.pop_tail_iter(func_to_call_ty.returns.valtypes.len())
Expand All @@ -96,29 +105,71 @@ pub(super) fn run<H: HookSet>(
}

trace!("end of function reached, returning to previous stack frame");
wasm.pc = stack.pop_stackframe();
let (return_module, return_pc) = stack.pop_stackframe();
*current_module = return_module;
wasm = &mut modules[return_module].wasm_reader;
wasm.pc = return_pc;
}
CALL => {
let func_to_call_idx = wasm.read_var_u32().unwrap_validated() as FuncIdx;

// TODO: if it is imported, defer to linking
let func_to_call_inst = store
let func_to_call_inst = modules[*current_module]
.store
.funcs
.get(func_to_call_idx)
.unwrap_validated()
.try_into_local()
.expect("TODO: call imported functions");
let func_to_call_ty = types.get(func_to_call_inst.ty).unwrap_validated();
.unwrap_validated();

let func_to_call_ty = modules[*current_module]
.fn_types
.get(func_to_call_inst.ty())
.unwrap_validated();
let params = stack.pop_tail_iter(func_to_call_ty.params.valtypes.len());
let remaining_locals = func_to_call_inst.locals.iter().cloned();

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);

wasm.move_start_to(func_to_call_inst.code_expr)
.unwrap_validated();
match func_to_call_inst {
FuncInst::Local(local_func_inst) => {
let remaining_locals = local_func_inst.locals.iter().cloned();
let locals = Locals::new(params, remaining_locals);

stack.push_stackframe(
*current_module,
func_to_call_idx,
func_to_call_ty,
locals,
wasm.pc,
);

wasm.move_start_to(local_func_inst.code_expr)
.unwrap_validated();
}
FuncInst::Imported(_imported_func_inst) => {
let (next_module, next_func_idx) = lut
.lookup(*current_module, func_to_call_idx)
.expect("invalid state for lookup");

let local_func_inst = modules[next_module].store.funcs[next_func_idx]
.try_into_local()
.unwrap();

let remaining_locals = local_func_inst.locals.iter().cloned();
let locals = Locals::new(params, remaining_locals);

stack.push_stackframe(
*current_module,
func_to_call_idx,
func_to_call_ty,
locals,
wasm.pc,
);

wasm = &mut modules[next_module].wasm_reader;
*current_module = next_module;

wasm.move_start_to(local_func_inst.code_expr)
.unwrap_validated();
}
}
}
LOCAL_GET => {
stack.get_local(wasm.read_var_u32().unwrap_validated() as LocalIdx);
Expand All @@ -127,21 +178,38 @@ pub(super) fn run<H: HookSet>(
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();
let global = modules[*current_module]
.store
.globals
.get(global_idx)
.unwrap_validated();

// TODO: imported global

stack.push_value(global.value);
}
GLOBAL_SET => {
let global_idx = wasm.read_var_u32().unwrap_validated() as GlobalIdx;
let global = store.globals.get_mut(global_idx).unwrap_validated();
let global = modules[*current_module]
.store
.globals
.get_mut(global_idx)
.unwrap_validated();

// TODO: imported global (?) ... can imported globals be set as mutable?

global.value = stack.pop_value(global.global.ty.ty)
}
I32_LOAD => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let memarg = MemArg::read_unvalidated(wasm);
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

let mem = store.mems.first().unwrap_validated(); // there is only one memory allowed as of now
// TODO: how does this interact with imports?
let mem = modules[*current_module]
.store
.mems
.first()
.unwrap_validated(); // there is only one memory allowed as of now

let data: u32 = {
// The spec states that this should be a 33 bit integer
Expand All @@ -164,10 +232,15 @@ pub(super) fn run<H: HookSet>(
trace!("Instruction: i32.load [{relative_address}] -> [{data}]");
}
F32_LOAD => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let memarg = MemArg::read_unvalidated(wasm);
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

let mem = store.mems.first().unwrap_validated(); // there is only one memory allowed as of now
// TODO: how does this interact with imports?
let mem = modules[*current_module]
.store
.mems
.first()
.unwrap_validated(); // there is only one memory allowed as of now

let data: f32 = {
// The spec states that this should be a 33 bit integer
Expand All @@ -190,12 +263,17 @@ pub(super) fn run<H: HookSet>(
trace!("Instruction: f32.load [{relative_address}] -> [{data}]");
}
I32_STORE => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let memarg = MemArg::read_unvalidated(wasm);

let data_to_store: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

let mem = store.mems.get_mut(0).unwrap_validated(); // there is only one memory allowed as of now
// TODO: How does this interact with imports?
let mem = modules[*current_module]
.store
.mems
.get_mut(0)
.unwrap_validated(); // there is only one memory allowed as of now

// The spec states that this should be a 33 bit integer
// See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions
Expand All @@ -211,12 +289,17 @@ pub(super) fn run<H: HookSet>(
trace!("Instruction: i32.store [{relative_address} {data_to_store}] -> []");
}
F32_STORE => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let memarg = MemArg::read_unvalidated(wasm);

let data_to_store: f32 = stack.pop_value(ValType::NumType(NumType::F32)).into();
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

let mem = store.mems.get_mut(0).unwrap_validated(); // there is only one memory allowed as of now
// TODO: how does this interact with imports?
let mem = modules[*current_module]
.store
.mems
.get_mut(0)
.unwrap_validated(); // there is only one memory allowed as of now

// The spec states that this should be a 33 bit integer
// See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions
Expand All @@ -232,12 +315,17 @@ pub(super) fn run<H: HookSet>(
trace!("Instruction: f32.store [{relative_address} {data_to_store}] -> []");
}
F64_STORE => {
let memarg = MemArg::read_unvalidated(&mut wasm);
let memarg = MemArg::read_unvalidated(wasm);

let data_to_store: f64 = stack.pop_value(ValType::NumType(NumType::F64)).into();
let relative_address: u32 = stack.pop_value(ValType::NumType(NumType::I32)).into();

let mem = store.mems.get_mut(0).unwrap_validated(); // there is only one memory allowed as of now
// TODO: how does this interact with imports?
let mem = modules[*current_module]
.store
.mems
.get_mut(0)
.unwrap_validated(); // there is only one memory allowed as of now

// The spec states that this should be a 33 bit integer
// See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions
Expand Down
Loading

0 comments on commit 9e29849

Please sign in to comment.