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

Compute delta_ip and delta_stp in side table #626

Merged
merged 15 commits into from
Oct 22, 2024
16 changes: 9 additions & 7 deletions crates/interpreter/src/bit_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,29 @@

use crate::error::*;

pub fn into_signed_field(mask: u32, value: i32) -> Result<u32, Error> {
into_field(mask, value.wrapping_add(offset(mask)) as u32)
pub fn into_signed_field(mask: u64, value: i32) -> Result<u64, Error> {
into_field(mask, value.wrapping_add(offset(mask)) as u64)
}

pub fn from_signed_field(mask: u32, field: u32) -> i32 {
pub fn from_signed_field(mask: u64, field: u64) -> i32 {
from_field(mask, field) as i32 - offset(mask)
}

fn offset(mask: u32) -> i32 {
fn offset(mask: u64) -> i32 {
1 << (mask.count_ones() - 1)
}

pub fn into_field(mask: u32, value: u32) -> Result<u32, Error> {
pub fn into_field(mask: u64, value: u64) -> Result<u64, Error> {
ia0 marked this conversation as resolved.
Show resolved Hide resolved
let field = (value << mask.trailing_zeros()) & mask;
if from_field(mask, field) != value {
#[cfg(feature = "debug")]
eprintln!("Bit field value {value:08x} doesn't fit in mask {mask:08x}.");
ia0 marked this conversation as resolved.
Show resolved Hide resolved
return Err(unsupported(if_debug!(Unsupported::SideTable)));
}
Ok(field)
}

pub fn from_field(mask: u32, field: u32) -> u32 {
pub fn from_field(mask: u64, field: u64) -> u64 {
ia0 marked this conversation as resolved.
Show resolved Hide resolved
(field & mask) >> mask.trailing_zeros()
}

Expand Down Expand Up @@ -80,7 +82,7 @@ mod tests {
#[test]
fn unsigned_field_error() {
let mask = 0b111000;
for value in [8, u32::MAX] {
for value in [8, u64::MAX] {
assert!(matches!(into_field(mask, value), Err(Error::Unsupported(_))));
}
}
Expand Down
16 changes: 8 additions & 8 deletions crates/interpreter/src/side_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ use crate::error::*;
#[allow(dead_code)] // TODO(dev/fast-interp)
#[derive(Default, Copy, Clone, Debug)]
#[repr(transparent)]
pub struct SideTableEntry(u32);
pub struct SideTableEntry(u64);

pub struct SideTableEntryView {
/// The amount to adjust the instruction pointer by if the branch is taken.
pub delta_ip: i32,
/// The amount to adjust the side-table pointer by if the branch is taken.
pub delta_stp: i32,
/// The number of values that will be copied if the branch is taken.
pub val_cnt: u32,
pub val_cnt: u64,
/// The number of values that will be popped if the branch is taken.
pub pop_cnt: u32,
pub pop_cnt: u64,
ia0 marked this conversation as resolved.
Show resolved Hide resolved
}

#[allow(dead_code)] // TODO(dev/fast-interp)
impl SideTableEntry {
const DELTA_IP_MASK: u32 = 0x0000ffff;
const DELTA_STP_MASK: u32 = 0x003f0000;
const VAL_CNT_MASK: u32 = 0x07c00000;
const POP_CNT_MASK: u32 = 0xf8000000;
const DELTA_IP_MASK: u64 = 0xffff;
const DELTA_STP_MASK: u64 = 0xffff << 16;
const VAL_CNT_MASK: u64 = 0xffff << 32;
const POP_CNT_MASK: u64 = 0xffff << 48;

fn new(view: SideTableEntryView) -> Result<Self, Error> {
pub fn new(view: SideTableEntryView) -> Result<Self, Error> {
let mut fields = 0;
fields |= into_signed_field(Self::DELTA_IP_MASK, view.delta_ip)?;
fields |= into_signed_field(Self::DELTA_STP_MASK, view.delta_stp)?;
Expand Down
127 changes: 92 additions & 35 deletions crates/interpreter/src/valid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,10 @@ pub fn validate(binary: &[u8]) -> Result<Vec<Vec<SideTableEntry>>, Error> {
type Parser<'m> = parser::Parser<'m, Check>;
type CheckResult = MResult<(), Check>;

struct FuncMetadata {
type_idx: TypeIdx,
#[allow(dead_code)]
// TODO(dev/fast-interp): Change to `&'m [SideTableEntry]` when making it persistent in flash.
side_table: Vec<SideTableEntry>,
}

#[derive(Default)]
struct Context<'m> {
types: Vec<FuncType<'m>>,
funcs: Vec<FuncMetadata>,
funcs: Vec<TypeIdx>,
tables: Vec<TableType>,
mems: Vec<MemType>,
globals: Vec<GlobalType>,
Expand Down Expand Up @@ -132,6 +125,7 @@ impl<'m> Context<'m> {
self.datas = Some(parser.parse_u32()? as usize);
check(parser.is_empty())?;
}
let mut side_tables = vec![];
if let Some(mut parser) = self.check_section(parser, SectionId::Code)? {
check(self.funcs.len() == imported_funcs + parser.parse_vec()?)?;
for x in imported_funcs .. self.funcs.len() {
Expand All @@ -140,7 +134,7 @@ impl<'m> Context<'m> {
let t = self.functype(x as FuncIdx).unwrap();
let mut locals = t.params.to_vec();
parser.parse_locals(&mut locals)?;
Expr::check_body(self, &mut parser, &refs, locals, t.results)?;
side_tables.push(Expr::check_body(self, &mut parser, &refs, locals, t.results)?);
check(parser.is_empty())?;
}
check(parser.is_empty())?;
Expand All @@ -157,7 +151,7 @@ impl<'m> Context<'m> {
}
self.check_section(parser, SectionId::Custom)?;
check(parser.is_empty())?;
Ok(vec![]) // TODO(dev/fast-interp): implement.
Ok(side_tables)
}

fn check_section(
Expand Down Expand Up @@ -200,7 +194,7 @@ impl<'m> Context<'m> {

fn add_functype(&mut self, x: TypeIdx) -> CheckResult {
check((x as usize) < self.types.len())?;
self.funcs.push(FuncMetadata { type_idx: x, side_table: vec![] });
self.funcs.push(x);
Ok(())
}

Expand Down Expand Up @@ -236,7 +230,7 @@ impl<'m> Context<'m> {
}

fn functype(&self, x: FuncIdx) -> Result<FuncType<'m>, Error> {
self.type_(self.funcs.get(x as usize).ok_or_else(invalid)?.type_idx)
self.type_(*self.funcs.get(x as usize).ok_or_else(invalid)?)
}

fn table(&self, x: TableIdx) -> Result<&TableType, Error> {
Expand Down Expand Up @@ -412,24 +406,43 @@ struct Expr<'a, 'm> {
is_body: bool,
locals: Vec<ValType>,
labels: Vec<Label<'m>>,
// TODO(dev/fast-interp): Remove `Option` after `side_table` is verified.
side_table: Vec<Option<SideTableEntryView>>,
}

#[derive(Debug, Copy, Clone)]
struct SideTableBranch<'m> {
parser: &'m [u8],
side_table: usize,
}

impl SideTableBranch<'_> {
fn delta_ip(&self, source: &[u8]) -> i32 {
(self.parser.as_ptr() as isize - source.as_ptr() as isize) as i32
}

fn delta_stp(&self, source: usize) -> i32 {
(self.side_table - source) as i32
}
ia0 marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Default)]
struct Label<'m> {
type_: FuncType<'m>,
/// Whether an `else` is possible before `end`.
kind: LabelKind,
kind: LabelKind<'m>,
/// Whether the bottom of the stack is polymorphic.
polymorphic: bool,
stack: Vec<OpdType>,
branches: Vec<SideTableBranch<'m>>,
}

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
enum LabelKind {
#[derive(Debug, Default, Clone)]
enum LabelKind<'m> {
#[default]
Block,
Loop,
If,
Loop(SideTableBranch<'m>),
If(SideTableBranch<'m>),
}

impl<'a, 'm> Expr<'a, 'm> {
Expand All @@ -445,6 +458,7 @@ impl<'a, 'm> Expr<'a, 'm> {
is_body: false,
locals: vec![],
labels: vec![Label::default()],
side_table: vec![],
}
}

Expand All @@ -461,15 +475,16 @@ impl<'a, 'm> Expr<'a, 'm> {
fn check_body(
context: &'a Context<'m>, parser: &'a mut Parser<'m>, refs: &'a [bool],
locals: Vec<ValType>, results: ResultType<'m>,
) -> CheckResult {
) -> MResult<Vec<SideTableEntry>, Check> {
let mut expr = Expr::new(context, parser, Err(refs));
expr.is_body = true;
expr.locals = locals;
expr.label().type_.results = results;
expr.check()
expr.check()?;
expr.side_table.into_iter().map(|entry| SideTableEntry::new(entry.unwrap())).collect()
}

fn check(mut self) -> CheckResult {
fn check(&mut self) -> CheckResult {
while !self.labels.is_empty() {
self.instr()?;
}
Expand Down Expand Up @@ -504,28 +519,34 @@ impl<'a, 'm> Expr<'a, 'm> {
Unreachable => self.stack_polymorphic(),
Nop => (),
Block(b) => self.push_label(self.blocktype(&b)?, LabelKind::Block)?,
Loop(b) => self.push_label(self.blocktype(&b)?, LabelKind::Loop)?,
Loop(b) => self.push_label(self.blocktype(&b)?, LabelKind::Loop(self.branch()))?,
If(b) => {
self.pop_check(ValType::I32)?;
self.push_label(self.blocktype(&b)?, LabelKind::If)?;
self.push_label(self.blocktype(&b)?, LabelKind::If(self.branch()))?;
self.side_table.push(None);
}
Else => {
let label = self.label();
check(core::mem::replace(&mut label.kind, LabelKind::Block) == LabelKind::If)?;
let FuncType { params, results } = label.type_;
match core::mem::replace(&mut self.label().kind, LabelKind::Block) {
LabelKind::If(source) => self.stitch(source, self.branch()),
_ => Err(invalid())?,
}
let FuncType { params, results } = self.label().type_;
self.pops(results)?;
check(self.stack().is_empty())?;
self.label().polymorphic = false;
self.pushs(params);
self.br_label(0)?;
}
End => unreachable!(),
Br(l) => {
self.pops(self.br_label(l)?)?;
let res = self.br_label(l)?;
self.pops(res)?;
ia0 marked this conversation as resolved.
Show resolved Hide resolved
self.stack_polymorphic();
}
BrIf(l) => {
self.pop_check(ValType::I32)?;
self.swaps(self.br_label(l)?)?;
let res = self.br_label(l)?;
self.swaps(res)?;
}
BrTable(ls, ln) => {
self.pop_check(ValType::I32)?;
Expand Down Expand Up @@ -742,20 +763,25 @@ impl<'a, 'm> Expr<'a, 'm> {
self.for_each(expected, |x, y| check(x.matches(y)))
}

fn push_label(&mut self, type_: FuncType<'m>, kind: LabelKind) -> CheckResult {
fn push_label(&mut self, type_: FuncType<'m>, kind: LabelKind<'m>) -> CheckResult {
self.pops(type_.params)?;
let stack = type_.params.iter().cloned().map(OpdType::from).collect();
let label = Label { type_, kind, polymorphic: false, stack };
let label = Label { type_, kind, polymorphic: false, stack, branches: vec![] };
self.labels.push(label);
Ok(())
}

fn end_label(&mut self) -> CheckResult {
let target = self.branch();
for source in core::mem::take(&mut self.label().branches) {
self.stitch(source, target);
}
let label = self.label();
if label.kind == LabelKind::If {
if let LabelKind::If(source) = label.kind {
check(label.type_.params == label.type_.results)?;
self.stitch(source, target);
}
let results = label.type_.results;
let results = self.label().type_.results;
self.pops(results)?;
check(self.labels.pop().unwrap().stack.is_empty())?;
if !self.labels.is_empty() {
Expand All @@ -764,17 +790,48 @@ impl<'a, 'm> Expr<'a, 'm> {
Ok(())
}

fn br_label(&self, l: LabelIdx) -> Result<ResultType<'m>, Error> {
fn stitch(&mut self, source: SideTableBranch, target: SideTableBranch) {
let SideTableBranch { parser, side_table } = source;
let entry = &mut self.side_table[side_table];
assert!(entry.is_none());
*entry = Some(SideTableEntryView {
delta_ip: target.delta_ip(parser),
delta_stp: target.delta_stp(side_table),
// TODO(dev/fast-interp): Compute the fields below.
val_cnt: 0,
pop_cnt: 0,
});
}

fn br_label(&mut self, l: LabelIdx) -> Result<ResultType<'m>, Error> {
let l = l as usize;
let n = self.labels.len();
check(l < n)?;
zhouwfang marked this conversation as resolved.
Show resolved Hide resolved
let label = &self.labels[n - l - 1];
let source = self.branch();
let label = &mut self.labels[n - l - 1];
Ok(match label.kind {
LabelKind::Block | LabelKind::If => label.type_.results,
LabelKind::Loop => label.type_.params,
LabelKind::Block | LabelKind::If(_) => {
label.branches.push(source);
self.side_table.push(None);
ia0 marked this conversation as resolved.
Show resolved Hide resolved
label.type_.results
}
LabelKind::Loop(SideTableBranch { parser, side_table }) => {
self.side_table.push(Some(SideTableEntryView {
delta_ip: source.delta_ip(parser),
delta_stp: source.delta_stp(side_table),
// TODO(dev/fast-interp): Compute the fields below.
val_cnt: 0,
pop_cnt: 0,
}));
label.type_.params
}
})
}

fn branch(&self) -> SideTableBranch<'m> {
SideTableBranch { parser: self.parser.save(), side_table: self.side_table.len() }
}

fn call(&mut self, t: FuncType) -> CheckResult {
self.pops(t.params)?;
self.pushs(t.results);
Expand Down
2 changes: 1 addition & 1 deletion crates/interpreter/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ensure_submodule third_party/WebAssembly/spec

test_helper

cargo test --lib --features=toctou
cargo test --lib --features=debug,toctou
cargo check --lib --target=thumbv7em-none-eabi
cargo check --lib --target=thumbv7em-none-eabi --features=cache
cargo check --lib --target=riscv32imc-unknown-none-elf \
Expand Down
Loading