Skip to content

Commit

Permalink
wip!: replace validation stack with validation ctxt
Browse files Browse the repository at this point in the history
  • Loading branch information
Cem Onem committed Nov 22, 2024
1 parent f7ba965 commit cb1ded4
Show file tree
Hide file tree
Showing 7 changed files with 515 additions and 716 deletions.
2 changes: 1 addition & 1 deletion src/core/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::core::indices::GlobalIdx;
use crate::validation_stack::LabelKind;
use crate::validation_context::LabelKind;
use core::fmt::{Display, Formatter};
use core::str::Utf8Error;

Expand Down
359 changes: 182 additions & 177 deletions src/validation/code.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{Error, Result};
pub(crate) mod code;
pub(crate) mod globals;
pub(crate) mod read_constant_expression;
pub(crate) mod validation_stack;
pub(crate) mod validation_context;

/// Information collected from validating a module.
/// This can be used to create a [crate::RuntimeInstance].
Expand Down
12 changes: 6 additions & 6 deletions src/validation/read_constant_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::core::reader::types::global::GlobalType;
use crate::core::reader::WasmReader;
use crate::{Error, NumType, Result, ValType};

use super::validation_stack::ValidationStack;
use super::validation_context::ValidationContext;

/// Read and validate constant expressions.
///
Expand Down Expand Up @@ -89,7 +89,7 @@ pub fn read_constant_instructions(
let start_pc = wasm.pc;

// Compared to the code validation, we create the validation stack here as opposed to taking it as an argument.
let mut stack = ValidationStack::new();
let mut stack = ValidationContext::new();

loop {
let Ok(first_instr_byte) = wasm.read_u8() else {
Expand All @@ -109,23 +109,23 @@ pub fn read_constant_instructions(
}
I32_CONST => {
let _num = wasm.read_var_i32()?;
stack.push_valtype(ValType::NumType(NumType::I32));
stack.push_val_type(ValType::NumType(NumType::I32));
}
I64_CONST => {
let _num = wasm.read_var_i64()?;
stack.push_valtype(ValType::NumType(NumType::I64));
stack.push_val_type(ValType::NumType(NumType::I64));
}
I32_ADD | I32_SUB | I32_MUL => {
stack.assert_pop_val_type(ValType::NumType(NumType::I32))?;
stack.assert_pop_val_type(ValType::NumType(NumType::I32))?;

stack.push_valtype(ValType::NumType(NumType::I32));
stack.push_val_type(ValType::NumType(NumType::I32));
}
I64_ADD | I64_SUB | I64_MUL => {
stack.assert_pop_val_type(ValType::NumType(NumType::I64))?;
stack.assert_pop_val_type(ValType::NumType(NumType::I64))?;

stack.push_valtype(ValType::NumType(NumType::I64));
stack.push_val_type(ValType::NumType(NumType::I64));
}
REF_NULL => {
todo!();
Expand Down
321 changes: 321 additions & 0 deletions src/validation/validation_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
#![allow(unused)] // TODO remove this once sidetable implementation lands
use super::Result;
use alloc::vec;
use alloc::vec::Vec;
use libm::exp;

use crate::core::reader::types::{FuncType, ResultType};

use crate::{Error, ValType};

#[derive(Debug, PartialEq, Eq)]
pub(super) struct ValidationContext {
val_stack: Vec<ValType>,
// TODO hide implementation
pub ctrl_stack: Vec<CtrlStackEntry>,
// TODO sidetable ref?
//
}

// TODO hide implementation
impl ValidationContext {
/// Initialize a new ValidationStack
pub(super) fn new() -> Self {
Self {
val_stack: Vec::new(),

//dummy label to account for the function itself,
//in case for return instructions that make the rest unreachable
ctrl_stack: vec![CtrlStackEntry {
label_info: LabelInfo::Func,
// TODO dummy, replace with the types of this function later
block_ty: FuncType {
params: ResultType {
valtypes: Vec::new(),
},
returns: ResultType {
valtypes: Vec::new(),
},
},
// TODO dummy, replace with the arity of this function later
height: 0,
unreachable: false,
}],
}
}

pub(super) fn val_stack_len(&self) -> usize {
self.val_stack.len()
}

pub fn ctrl_stack_len(&self) -> usize {
self.ctrl_stack.len()
}

pub(super) fn push_val_type(&mut self, valtype: ValType) {
self.val_stack.push(valtype);
}

pub fn push_ctrl(&mut self, ctrl_stack_entry: CtrlStackEntry) {
// TODO order?
// TODO include this or not?
// for &v in ctrl_stack_entry.block_ty.params.valtypes.iter().rev() {
// self.push_val_type(v);
// }
self.ctrl_stack.push(ctrl_stack_entry);
}

pub fn pop_ctrl(&mut self) -> Result<CtrlStackEntry> {
if self.ctrl_stack_len() == 1 {
return Err(Error::InvalidCtrlStackType(None));
}
let last_ctrl_stack_entry = self.ctrl_stack.pop().unwrap();
Ok(last_ctrl_stack_entry)

// TODO include this or not?
// for &v in last_ctrl_stack_entry.block_ty.params.valtypes.iter() {
// self.assert_pop_val_type(v);
// }

// // TODO is this necessary?
// if self.val_stack_len() == last_ctrl_stack_entry.height {
// Ok(last_ctrl_stack_entry)
// }
// else {
// Err(Error::EndInvalidValueStack)
// }
}

pub fn make_unspecified(&mut self) {
let last_ctrl_stack_entry = self.ctrl_stack.last_mut().unwrap();
last_ctrl_stack_entry.unreachable = true;
self.val_stack.truncate(last_ctrl_stack_entry.height);
}

pub fn is_unspecified(&self) -> bool {
let last_ctrl_stack_entry = self.ctrl_stack.last().unwrap();
self.val_stack_len() == last_ctrl_stack_entry.height && last_ctrl_stack_entry.unreachable
}

/// Similar to [`ValidationStack::pop`], because it pops a value from the stack,
/// but more public and doesn't actually return the popped value.
pub(super) fn drop_val_type(&mut self) -> Result<()> {
self.pop_val_type()
.map_err(|e| Error::EndInvalidValueStack)
.map(|v| ())
}

pub fn pop_val_type(&mut self) -> Result<Option<ValType>> {
if self.is_unspecified() {
return Ok(None);
}
let last_ctrl_stack_entry = self.ctrl_stack.last().unwrap();
if self.val_stack.len() == last_ctrl_stack_entry.height {
Err(Error::EndInvalidValueStack)
} else {
Ok(Some(self.val_stack.pop().unwrap()))
}
}

pub fn assert_nth_top_val_type(&self, n: usize, expected_ty: ValType) -> Result<()> {
let last_ctrl_stack_entry = self.ctrl_stack.last().unwrap();
if self.val_stack_len() - n <= last_ctrl_stack_entry.height {
if last_ctrl_stack_entry.unreachable {
return Ok(());
} else {
return Err((Error::EndInvalidValueStack));
}
}

if self.val_stack[self.val_stack_len() - n - 1] == expected_ty {
Ok(())
} else {
Err(Error::InvalidValidationStackValType(None))
}
}

// TODO should this even exist?
//fn raw_pop_val(&mut self) -> Result<ValidationStackEntry> {
// ???
//}

pub(super) fn assert_pop_val_type(&mut self, expected_ty: ValType) -> Result<ValType> {
let actual_ty = self.pop_val_type()?;
// TODO we hope to never assert an Unknown type?
match actual_ty {
None => Ok(expected_ty),
Some(v) => {
if v == expected_ty {
Ok(expected_ty)
} else {
Err(Error::InvalidValType)
}
}
}
}

pub(super) fn assert_pop_val_types(&mut self, expected_val_types: &[ValType]) -> Result<()> {
self.assert_val_types(expected_val_types);
for i in 0..expected_val_types.len() {
self.pop_val_type();
}
Ok(())
}

pub(super) fn assert_val_types(&self, expected_val_types: &[ValType]) -> Result<()> {
let last_ctrl_stack_entry = self.ctrl_stack.last().unwrap();
for (i, expected_ty) in expected_val_types.iter().rev().enumerate() {
if self.val_stack_len() - i <= last_ctrl_stack_entry.height {
if last_ctrl_stack_entry.unreachable {
return Ok(());
} else {
return Err(Error::EndInvalidValueStack);
}
}

if self.val_stack[self.val_stack_len() - i - 1] != *expected_ty {
return Err(Error::InvalidValidationStackValType(None));
}
}
Ok(())
}
}

// TODO replace LabelInfo with this later
// TODO fix what is public or not
// TODO innards are coupled with sidetable
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LabelInfo {
Block {
stps_to_backpatch: Vec<usize>,
},
If {
stps_to_backpatch: Vec<usize>,
stp: usize,
},
Loop {
ip: usize,
stp: usize,
},
Func,
}

impl LabelInfo {
//TODO ugly but I am not sure if there is a better rustier way
pub fn get_labelkind(&self) -> LabelKind {
match self {
LabelInfo::Block { .. } => LabelKind::Block,
LabelInfo::Loop { .. } => LabelKind::Loop,
LabelInfo::If { .. } => LabelKind::If,
LabelInfo::Func => LabelKind::Func,
}
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CtrlStackEntry {
pub label_info: LabelInfo,
pub block_ty: FuncType,
pub height: usize,
pub unreachable: bool,
}

impl CtrlStackEntry {
pub fn label_types(&self) -> &ResultType {
match &self.label_info {
LabelInfo::Loop { .. } => &self.block_ty.params,
_ => &self.block_ty.returns,
}
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LabelKind {
Func,
Block,
Loop,
If,
}

#[cfg(test)]
mod tests {
use crate::{NumType, RefType, ValType};

use super::{LabelInfo, LabelKind, ValidationContext};

#[test]
fn push_then_pop() {
let mut ctxt = ValidationContext::new();

ctxt.push_val_type(ValType::NumType(NumType::F64));
ctxt.push_val_type(ValType::NumType(NumType::I32));
ctxt.push_val_type(ValType::VecType);
ctxt.push_val_type(ValType::RefType(RefType::ExternRef));

ctxt.assert_pop_val_type(ValType::RefType(RefType::ExternRef))
.unwrap();
ctxt.assert_pop_val_type(ValType::VecType).unwrap();
ctxt.assert_pop_val_type(ValType::NumType(NumType::I32))
.unwrap();
ctxt.assert_pop_val_type(ValType::NumType(NumType::F64))
.unwrap();
}

#[test]
fn assert_valtypes() {
let mut ctxt = ValidationContext::new();

ctxt.push_val_type(ValType::NumType(NumType::F64));
ctxt.push_val_type(ValType::NumType(NumType::I32));
ctxt.push_val_type(ValType::NumType(NumType::F32));

ctxt.assert_val_types(&[
ValType::NumType(NumType::F64),
ValType::NumType(NumType::I32),
ValType::NumType(NumType::F32),
])
.unwrap();

ctxt.push_val_type(ValType::NumType(NumType::I32));

ctxt.assert_val_types(&[ValType::NumType(NumType::I32)])
.unwrap();
}

#[test]
fn assert_emtpy_valtypes() {
let mut ctxt = ValidationContext::new();

ctxt.assert_val_types(&[]).unwrap();
}

#[test]
fn assert_valtypes2() {
let mut ctxt = ValidationContext::new();

ctxt.assert_val_types(&[]).unwrap();

ctxt.push_val_type(ValType::NumType(NumType::I32));
ctxt.push_val_type(ValType::NumType(NumType::F32));
ctxt.push_val_type(ValType::NumType(NumType::I64));

// There are always zero valtypes on top of the stack
ctxt.assert_val_types(&[]).unwrap();

ctxt.assert_val_types(&[ValType::NumType(NumType::I64)])
.unwrap();

ctxt.assert_val_types(&[
ValType::NumType(NumType::F32),
ValType::NumType(NumType::I64),
])
.unwrap();

ctxt.assert_val_types(&[
ValType::NumType(NumType::I32),
ValType::NumType(NumType::F32),
ValType::NumType(NumType::I64),
])
.unwrap();
}
}
Loading

0 comments on commit cb1ded4

Please sign in to comment.