Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

structured ctrl #101

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/core/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::core::indices::GlobalIdx;
use crate::validation_stack::LabelKind;
use core::fmt::{Display, Formatter};
use core::str::Utf8Error;

Expand Down Expand Up @@ -31,6 +30,7 @@ pub enum Error {
InvalidNumType,
InvalidVecType,
InvalidFuncType,
InvalidFuncTypeIdx,
InvalidRefType,
InvalidValType,
InvalidExportDesc(u8),
Expand All @@ -50,12 +50,15 @@ pub enum Error {
InvalidGlobalIdx(GlobalIdx),
GlobalIsConst,
RuntimeError(RuntimeError),
FoundLabel(LabelKind),
MemoryIsNotDefined(MemIdx),
// mem.align, wanted alignment
ErroneousAlignment(u32, u32),
NoDataSegments,
DataSegmentNotFound(DataIdx),
InvalidLabelIdx(usize),
ValidationCtrlStackEmpty,
ElseWithoutMatchingIf,
IfWithoutMatchingElse,
}

impl Display for Error {
Expand Down Expand Up @@ -86,6 +89,9 @@ impl Display for Error {
Error::InvalidFuncType => {
f.write_str("An invalid byte was read where a functype was expected")
}
Error::InvalidFuncTypeIdx => {
f.write_str("An invalid index to the fuctypes table was read")
}
Error::InvalidRefType => {
f.write_str("An invalid byte was read where a reftype was expected")
}
Expand Down Expand Up @@ -128,9 +134,6 @@ impl Display for Error {
)),
Error::GlobalIsConst => f.write_str("A const global cannot be written to"),
Error::RuntimeError(err) => err.fmt(f),
Error::FoundLabel(lk) => f.write_fmt(format_args!(
"Expecting a ValType, a Label was found: {lk:?}"
)),
Error::ExpectedAnOperand => f.write_str("Expected a ValType"), // Error => f.write_str("Expected an operand (ValType) on the stack")
Error::MemoryIsNotDefined(memidx) => f.write_fmt(format_args!(
"C.mems[{}] is NOT defined when it should be",
Expand All @@ -146,6 +149,18 @@ impl Display for Error {
Error::DataSegmentNotFound(data_idx) => {
f.write_fmt(format_args!("Data Segment {} not found", data_idx))
}
Error::InvalidLabelIdx(label_idx) => {
f.write_fmt(format_args!("invalid label index {}", label_idx))
}
Error::ValidationCtrlStackEmpty => {
f.write_str("cannot retrieve last ctrl block, validation ctrl stack is empty")
}
Error::ElseWithoutMatchingIf => {
f.write_str("read 'else' without a previous matching 'if' instruction")
}
Error::IfWithoutMatchingElse => {
f.write_str("read 'end' without matching 'else' instruction to 'if' instruction")
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod error;

pub mod indices;
pub mod reader;
pub mod sidetable;
5 changes: 5 additions & 0 deletions src/core/reader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ pub mod span {
pub const fn len(&self) -> usize {
self.len
}

// TODO is this ok?
pub const fn from(&self) -> usize {
self.from
}
cemonem marked this conversation as resolved.
Show resolved Hide resolved
}

impl<'a> Index<Span> for WasmReader<'a> {
Expand Down
1 change: 0 additions & 1 deletion src/core/reader/types/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ impl Debug for DataSegment {
/// ;; for hardcoded offsets, we'll usually use i32.const because of wasm being x86 arch
/// )
/// ```

///
/// Since the span has only the start and length and acts a reference, we print the start and end (both inclusive, notice the '..=')
/// We print it in both decimal and hexadecimal so it's easy to trace in something like <https://webassembly.github.io/wabt/demo/wat2wasm/>
Expand Down
79 changes: 79 additions & 0 deletions src/core/reader/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,85 @@ 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> {
if wasm.peek_u8()? as i8 == 0x40 {
// Empty block type
let _ = wasm.read_u8().unwrap_validated();
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)
}
cemonem marked this conversation as resolved.
Show resolved Hide resolved
}

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].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(Error::InvalidFuncTypeIdx)
}
}
}
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Limits {
pub min: u32,
Expand Down
11 changes: 11 additions & 0 deletions src/core/reader/types/opcode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
//! All opcodes, in alphanumerical order by their numeric (hex-)value
pub const UNREACHABLE: u8 = 0x00;
pub const NOP: u8 = 0x01;
pub const BLOCK: u8 = 0x02;
pub const LOOP: u8 = 0x03;
pub const IF: u8 = 0x04;
#[allow(unused)] // TODO remove this once sidetable implementation lands
pub const ELSE: u8 = 0x05;
pub const END: u8 = 0x0B;
pub const BR: u8 = 0x0C;
#[allow(unused)] // TODO remove this once sidetable implementation lands
pub const BR_IF: u8 = 0x0D;
#[allow(unused)] // TODO remove this once sidetable implementation lands
pub const BR_TABLE: u8 = 0x0E;
cemonem marked this conversation as resolved.
Show resolved Hide resolved
pub const RETURN: u8 = 0x0F;
pub const CALL: u8 = 0x10;
pub const DROP: u8 = 0x1A;
Expand Down
21 changes: 21 additions & 0 deletions src/core/reader/types/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ impl WasmReader<'_> {
Ok(result)
}

pub fn read_var_i33(&mut self) -> Result<i64> {
let mut result: i64 = 0;
let mut shift: u64 = 0;

let mut byte: i64;
loop {
byte = self.read_u8()? as i64;
result |= (byte & 0b0111_1111) << shift;
shift += 7;
if (byte & 0b1000_0000) == 0 {
break;
}
}

if shift < 33 && (byte & 0x40 != 0) {
result |= !0 << shift;
}

Ok(result)
}
cemonem marked this conversation as resolved.
Show resolved Hide resolved

pub fn read_var_f32(&mut self) -> Result<u32> {
if self.full_wasm_binary.len() - self.pc < 4 {
return Err(Error::Eof);
Expand Down
65 changes: 65 additions & 0 deletions src/core/sidetable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! This module contains a data structure to allow in-place interpretation
//!
//! Control-flow in WASM is denoted in labels. To avoid linear search through the WASM binary or
//! stack for the respective label of a branch, a sidetable is generated during validation, which
//! stores the offset on the current instruction pointer for the branch. A sidetable entry hence
//! allows to translate the implicit control flow information ("jump to the next `else`") to
//! explicit modifications of the instruction pointer (`instruction_pointer += 13`).
//!
//! Branches in WASM can only go outwards, they either `break` out of a block or `continue` to the
//! head of a loob block. Put differently, a label can only be referenced from within its
//! associated structured control instruction.
//!
//! Noteworthy, branching can also have side-effects on the operand stack:
//!
//! - Taking a branch unwinds the operand stack, down to where the targeted structured control flow
//! instruction was entered. [`SidetableEntry::popcnt`] holds information on how many values to
//! pop from the operand stack when a branch is taken.
//! - When a branch is taken, it may consume arguments from the operand stack. These are pushed
//! 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.
//!
//! # Reference
//!
//! - Core / Syntax / Instructions / Control Instructions, WASM Spec,
//! <https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions>
//! - "A fast in-place interpreter for WebAssembly", Ben L. Titzer,
//! <https://arxiv.org/abs/2205.01183>

use alloc::vec::Vec;

// A sidetable

pub type Sidetable = Vec<SidetableEntry>;

/// Entry to translate the current branches implicit target into an explicit offset to the instruction pointer, as well as the side table pointer
///
/// Each of the following constructs requires a [`SidetableEntry`]:
///
/// - br
/// - br_if
/// - br_table
/// - else
// TODO hide implementation
// TODO Remove Clone trait from sidetables
#[derive(Clone)]
pub struct SidetableEntry {
/// Δpc: the amount to adjust the instruction pointer by if the branch is taken
pub delta_pc: isize,

/// Δstp: the amount to adjust the side-table index by if the branch is taken
pub delta_stp: isize,

/// valcnt: the number of values that will be copied if the branch is taken
///
/// Branches may additionally consume operands themselves, which they push back on the operand
/// stack after unwinding.
pub valcnt: usize,

/// popcnt: the number of values that will be popped if the branch is taken
///
/// Taking a branch unwinds the operand stack down to the height where the targeted structured
/// control instruction was entered.
pub popcnt: usize,
}
Loading
Loading