Skip to content

Serialize side table and integrate verify #758

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

Merged
merged 33 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
eda11c8
feat!: add merge() and serialize()
zhouwfang Feb 17, 2025
fb458d9
fix: review
zhouwfang Feb 18, 2025
264540f
refactor: try_from
zhouwfang Feb 19, 2025
f4f6766
refactor: store num_functions as u16
zhouwfang Feb 19, 2025
48f2613
refactor: remove Parser::parse_u16()
zhouwfang Feb 19, 2025
9ee3f2a
refactor: merge()
zhouwfang Feb 19, 2025
b28956c
refactor: add section()
zhouwfang Feb 19, 2025
25193e5
refactor!: merge->prepare; Module stores STView
zhouwfang Feb 23, 2025
8b366cd
style: minor
zhouwfang Feb 23, 2025
395540b
docs: remove
zhouwfang Feb 23, 2025
b127178
fix: name serialization
zhouwfang Feb 23, 2025
3f3b07c
fix: assert_{malformed,invalid} call prepare
zhouwfang Feb 24, 2025
12d6c10
fix: Module::binary should ignore side table
zhouwfang Feb 24, 2025
961e676
fix: first validate in prepare
zhouwfang Feb 24, 2025
0e701b9
fix: branch_table()
zhouwfang Feb 27, 2025
cf8c31c
refactor!: add parse_side_table() in parser.rs
zhouwfang Feb 28, 2025
b7def87
fix: import
zhouwfang Feb 28, 2025
59b4cf5
fix: parse_side_table()
zhouwfang Feb 28, 2025
bc7d576
fix: side_table_result() in valid.rs
zhouwfang Feb 28, 2025
ffe3ad8
review
ia0 Feb 28, 2025
3b73282
debug
ia0 Feb 28, 2025
5f5d9c3
fix: patch_branch() off by 1
zhouwfang Feb 28, 2025
b7e3b3a
fix: remove assignment in patch_branch()
zhouwfang Mar 1, 2025
a6ca8a7
refactor
zhouwfang Mar 1, 2025
14b1300
fix!: verify()
zhouwfang Mar 2, 2025
09579e4
fix!: call parse_side_table() in func()
zhouwfang Mar 2, 2025
c37e31b
fix: add with overflow
zhouwfang Mar 2, 2025
b1e4cb9
fix: weaken the stack check in stitch_branch()
zhouwfang Mar 2, 2025
583c65f
fix: remove check on stack in push_branch()
zhouwfang Mar 2, 2025
40cd8b3
Merge branch 'dev/fast-interp' into serialize-side-table
ia0 Mar 3, 2025
0fbe6f3
optimize Module::func()
ia0 Mar 3, 2025
293ea6a
review
ia0 Mar 3, 2025
911b5ae
disable hw-host in dev/fast-interp
ia0 Mar 3, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
x tests-1 pull_request push schedule
x tests-2 pull_request push schedule
x tests-3 pull_request push schedule
[[ $GITHUB_BASE_REF == dev/fast-interp ]] || \
x hw-host pull_request push schedule
x book pull_request push schedule
x footprint pull_request push
Expand Down
33 changes: 33 additions & 0 deletions crates/interpreter/src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use alloc::vec::Vec;

pub fn leb128(wasm: &mut Vec<u8>, mut x: usize) {
assert!(x <= u32::MAX as usize);
while x > 127 {
wasm.push(0x80 | (x & 0x7f) as u8);
x >>= 7;
}
wasm.push(x as u8);
}

pub fn custom_section(wasm: &mut Vec<u8>, name: &str, content: &[u8]) {
assert!(name.len() < 128);
wasm.push(0);
leb128(wasm, 1 + name.len() + content.len());
wasm.push(name.len() as u8);
wasm.extend_from_slice(name.as_bytes());
wasm.extend_from_slice(content);
}
1 change: 1 addition & 0 deletions crates/interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ macro_rules! support_if {

mod error;
mod exec;
mod format;
mod id;
mod module;
mod parser;
Expand Down
31 changes: 11 additions & 20 deletions crates/interpreter/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use alloc::boxed::Box;
use alloc::vec::Vec;
use core::cmp::Ordering;

use crate::parser::{SkipData, SkipElem};
use crate::side_table::*;
use crate::syntax::*;
use crate::toctou::*;
use crate::valid::prepare;
use crate::*;

/// Valid module.
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct Module<'m> {
binary: &'m [u8],
types: Vec<FuncType<'m>>,
// TODO(dev/fast-interp): Change the type to `SideTableView` which will be parsed by
// `new_unchecked()`.
side_table: &'m [MetadataEntry],
side_table: SideTableView<'m>,
}

impl<'m> Import<'m> {
Expand All @@ -53,11 +49,8 @@ impl ImportDesc {
impl<'m> Module<'m> {
/// Validates a WASM module in binary format.
pub fn new(binary: &'m [u8]) -> Result<Self, Error> {
let side_table = prepare(binary)?;
let mut module = unsafe { Self::new_unchecked(binary) };
// TODO(dev/fast-interp): We should take a buffer as argument to write to.
module.side_table = Box::leak(Box::new(side_table));
Ok(module)
crate::valid::verify(binary)?;
Ok(unsafe { Self::new_unchecked(binary) })
}

/// Creates a valid module from binary format.
Expand All @@ -66,12 +59,10 @@ impl<'m> Module<'m> {
///
/// The module must be valid.
pub unsafe fn new_unchecked(binary: &'m [u8]) -> Self {
let mut module = Module {
// Only keep the sections (i.e. skip the header).
binary: &binary[8 ..],
types: Vec::new(),
side_table: &[], // TODO(dev/fast-interp): Parse from binary.
};
// Only keep the sections (i.e. skip the header).
let mut parser = unsafe { Parser::new(&binary[8 ..]) };
let side_table = parser.parse_side_table().into_ok();
let mut module = Module { binary: parser.save(), types: Vec::new(), side_table };
if let Some(mut parser) = module.section(SectionId::Type) {
for _ in 0 .. parser.parse_vec().into_ok() {
module.types.push(parser.parse_functype().into_ok());
Expand Down Expand Up @@ -121,7 +112,7 @@ impl<'m> Module<'m> {
}

pub(crate) fn func_type(&self, x: FuncIdx) -> FuncType<'m> {
self.types[self.side_table[x as usize].type_idx]
self.types[self.side_table.metadata(x as usize).type_idx()]
}

pub(crate) fn table_type(&self, x: TableIdx) -> TableType {
Expand Down Expand Up @@ -182,8 +173,8 @@ impl<'m> Module<'m> {
}

pub(crate) fn func(&self, x: FuncIdx) -> (Parser<'m>, &'m [BranchTableEntry]) {
let MetadataEntry { parser_range, branch_table, .. } = &self.side_table[x as usize];
(unsafe { Parser::new(&self.binary[parser_range.clone()]) }, branch_table)
let metadata = self.side_table.metadata(x as usize);
(unsafe { Parser::new(&self.binary[metadata.parser_range()]) }, metadata.branch_table())
}

pub(crate) fn data(&self, x: DataIdx) -> Parser<'m> {
Expand Down
14 changes: 14 additions & 0 deletions crates/interpreter/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use core::marker::PhantomData;

#[cfg(feature = "debug")]
use crate::error::*;
use crate::side_table::*;
use crate::syntax::*;
use crate::toctou::*;

Expand Down Expand Up @@ -551,6 +552,19 @@ impl<'m, M: Mode> Parser<'m, M> {
user.init(self.parse_bytes(len)?)
}

pub fn parse_side_table(&mut self) -> MResult<SideTableView<'m>, M> {
let id = self.parse_section_id()?;
M::check(|| id == SectionId::Custom)?;
let mut parser = self.split_section()?;
let name = parser.parse_name()?;
M::check(|| name == SECTION_NAME)?;
let num_funcs = try_into::<M, [u8; 2], _>(parser.parse_bytes(2)?)?;
let num_funcs = u16::from_le_bytes(num_funcs) as usize;
let indices = parser.parse_bytes((num_funcs + 1) * 2)?;
let metadata = parser.save();
Ok(SideTableView { func_idx: 0, indices, metadata })
}

pub fn skip_to_end(&mut self, l: LabelIdx) -> MResult<(), M> {
let mut depth = l as usize + 1;
while depth > 0 {
Expand Down
66 changes: 46 additions & 20 deletions crates/interpreter/src/side_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,16 @@ use core::ops::Range;
use crate::error::*;
use crate::module::Parser;

pub const SECTION_NAME: &str = "wasefire-sidetable";

#[derive(Debug)]
pub struct SideTableView<'m> {
pub func_idx: usize,
pub indices: &'m [u8], // including 0 and the length of metadata_array
pub metadata: &'m [u8],
}

impl<'m> SideTableView<'m> {
pub fn new(binary: &'m [u8]) -> Result<Self, Error> {
let num_functions = parse_u16(binary, 0) as usize;
let indices_end = 2 + (num_functions + 1) * 2;
Ok(SideTableView {
func_idx: 0,
indices: &binary[2 .. indices_end],
metadata: &binary[indices_end ..],
})
}

// TODO(dev/fast-interp): Make it generic since it will be used in both `Check` and `Use` modes.
// (Returns `MResult<Metadata<'m>, M>` instead.)
pub fn metadata(&self, func_idx: usize) -> Metadata<'m> {
Expand All @@ -45,7 +38,7 @@ impl<'m> SideTableView<'m> {
}
}

#[derive(Default, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone)]
pub struct Metadata<'m>(&'m [u8]);

impl<'m> Metadata<'m> {
Expand All @@ -58,18 +51,14 @@ impl<'m> Metadata<'m> {
unsafe { Parser::new(&module[self.parser_range()]) }
}

pub fn branch_table(&self) -> &[BranchTableEntry] {
pub fn branch_table(&self) -> &'m [BranchTableEntry] {
let entry_size = size_of::<BranchTableEntry>();
assert_eq!(
(self.0.len() - 10) % entry_size,
0,
"Metadata length for branch table must be divisible by {} bytes",
entry_size
);
let branch_table = &self.0[10 ..];
assert_eq!(branch_table.len() % entry_size, 0);
unsafe {
core::slice::from_raw_parts(
self.0[10 ..].as_ptr() as *const BranchTableEntry,
self.0.len() / entry_size,
branch_table.as_ptr().cast(),
branch_table.len() / entry_size,
)
}
}
Expand All @@ -86,6 +75,34 @@ pub struct MetadataEntry {
pub branch_table: Vec<BranchTableEntry>,
}

pub fn serialize(side_table: &[MetadataEntry]) -> Result<Vec<u8>, Error> {
let mut res = Vec::new();
let num_funcs = try_from::<u16>("length of MetadataEntry", side_table.len())?;
res.extend_from_slice(&num_funcs.to_le_bytes());
let mut index = 0u16;
res.extend_from_slice(&index.to_le_bytes());
for entry in side_table {
index = try_from::<u16>(
"index of MetadataEntry",
index as usize + 10 + 6 * entry.branch_table.len(),
)?;
res.extend_from_slice(&index.to_le_bytes());
}
for entry in side_table {
let type_idx = try_from::<u16>("MetadataEntry::type_idx", entry.type_idx)?;
res.extend_from_slice(&type_idx.to_le_bytes());
let range_start =
try_from::<u32>("MetadataEntry::parser_range start", entry.parser_range.start)?;
res.extend_from_slice(&range_start.to_le_bytes());
let range_end = try_from::<u32>("MetadataEntry::parser_range end", entry.parser_range.end)?;
res.extend_from_slice(&range_end.to_le_bytes());
for branch in &entry.branch_table {
res.extend_from_slice(&branch.0);
}
}
Ok(res)
}

#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct BranchTableEntry([u8; 6]);
Expand Down Expand Up @@ -146,3 +163,12 @@ fn parse_u16(data: &[u8], offset: usize) -> u16 {
fn parse_u32(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(data[offset ..][.. 4].try_into().unwrap())
}

#[allow(unused_variables)]
fn try_from<T: TryFrom<usize>>(msg: &str, val: usize) -> Result<T, Error> {
T::try_from(val).map_err(|_| {
#[cfg(feature = "debug")]
eprintln!("{msg} overflow");
unsupported(if_debug!(Unsupported::SideTable))
})
}
5 changes: 5 additions & 0 deletions crates/interpreter/src/toctou.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ pub fn byte_enum<M: Mode, T: Copy + TryFromByte + UnsafeFromByte>(x: u8) -> MRes
M::choose(|| T::try_from_byte(x), || unsafe { T::from_byte_unchecked(x) })
}

pub fn try_into<M: Mode, T: Copy, S: TryInto<T>>(x: S) -> MResult<T, M> {
let y = S::try_into(x).ok();
M::choose(|| y, || unsafe { y.unwrap_unchecked() })
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading