Skip to content

Commit 188766a

Browse files
zhouwfangia0
andauthored
Serialize side table and integrate verify (#758)
#46 --------- Co-authored-by: Zhou Fang <[email protected]> Co-authored-by: Julien Cretin <[email protected]>
1 parent a3c0b6d commit 188766a

File tree

10 files changed

+172
-70
lines changed

10 files changed

+172
-70
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
x tests-1 pull_request push schedule
4545
x tests-2 pull_request push schedule
4646
x tests-3 pull_request push schedule
47+
[[ $GITHUB_BASE_REF == dev/fast-interp ]] || \
4748
x hw-host pull_request push schedule
4849
x book pull_request push schedule
4950
x footprint pull_request push

crates/interpreter/src/format.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use alloc::vec::Vec;
16+
17+
pub fn leb128(wasm: &mut Vec<u8>, mut x: usize) {
18+
assert!(x <= u32::MAX as usize);
19+
while x > 127 {
20+
wasm.push(0x80 | (x & 0x7f) as u8);
21+
x >>= 7;
22+
}
23+
wasm.push(x as u8);
24+
}
25+
26+
pub fn custom_section(wasm: &mut Vec<u8>, name: &str, content: &[u8]) {
27+
assert!(name.len() < 128);
28+
wasm.push(0);
29+
leb128(wasm, 1 + name.len() + content.len());
30+
wasm.push(name.len() as u8);
31+
wasm.extend_from_slice(name.as_bytes());
32+
wasm.extend_from_slice(content);
33+
}

crates/interpreter/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ macro_rules! support_if {
141141

142142
mod error;
143143
mod exec;
144+
mod format;
144145
mod id;
145146
mod module;
146147
mod parser;

crates/interpreter/src/module.rs

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,21 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use alloc::boxed::Box;
1615
use alloc::vec::Vec;
1716
use core::cmp::Ordering;
1817

1918
use crate::parser::{SkipData, SkipElem};
2019
use crate::side_table::*;
2120
use crate::syntax::*;
2221
use crate::toctou::*;
23-
use crate::valid::prepare;
2422
use crate::*;
2523

2624
/// Valid module.
27-
#[derive(Debug, Default)]
25+
#[derive(Debug)]
2826
pub struct Module<'m> {
2927
binary: &'m [u8],
3028
types: Vec<FuncType<'m>>,
31-
// TODO(dev/fast-interp): Change the type to `SideTableView` which will be parsed by
32-
// `new_unchecked()`.
33-
side_table: &'m [MetadataEntry],
29+
side_table: SideTableView<'m>,
3430
}
3531

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

6356
/// Creates a valid module from binary format.
@@ -66,12 +59,10 @@ impl<'m> Module<'m> {
6659
///
6760
/// The module must be valid.
6861
pub unsafe fn new_unchecked(binary: &'m [u8]) -> Self {
69-
let mut module = Module {
70-
// Only keep the sections (i.e. skip the header).
71-
binary: &binary[8 ..],
72-
types: Vec::new(),
73-
side_table: &[], // TODO(dev/fast-interp): Parse from binary.
74-
};
62+
// Only keep the sections (i.e. skip the header).
63+
let mut parser = unsafe { Parser::new(&binary[8 ..]) };
64+
let side_table = parser.parse_side_table().into_ok();
65+
let mut module = Module { binary: parser.save(), types: Vec::new(), side_table };
7566
if let Some(mut parser) = module.section(SectionId::Type) {
7667
for _ in 0 .. parser.parse_vec().into_ok() {
7768
module.types.push(parser.parse_functype().into_ok());
@@ -121,7 +112,7 @@ impl<'m> Module<'m> {
121112
}
122113

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

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

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

189180
pub(crate) fn data(&self, x: DataIdx) -> Parser<'m> {

crates/interpreter/src/parser.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use core::marker::PhantomData;
1717

1818
#[cfg(feature = "debug")]
1919
use crate::error::*;
20+
use crate::side_table::*;
2021
use crate::syntax::*;
2122
use crate::toctou::*;
2223

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

555+
pub fn parse_side_table(&mut self) -> MResult<SideTableView<'m>, M> {
556+
let id = self.parse_section_id()?;
557+
M::check(|| id == SectionId::Custom)?;
558+
let mut parser = self.split_section()?;
559+
let name = parser.parse_name()?;
560+
M::check(|| name == SECTION_NAME)?;
561+
let num_funcs = try_into::<M, [u8; 2], _>(parser.parse_bytes(2)?)?;
562+
let num_funcs = u16::from_le_bytes(num_funcs) as usize;
563+
let indices = parser.parse_bytes((num_funcs + 1) * 2)?;
564+
let metadata = parser.save();
565+
Ok(SideTableView { func_idx: 0, indices, metadata })
566+
}
567+
554568
pub fn skip_to_end(&mut self, l: LabelIdx) -> MResult<(), M> {
555569
let mut depth = l as usize + 1;
556570
while depth > 0 {

crates/interpreter/src/side_table.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,16 @@ use core::ops::Range;
1818
use crate::error::*;
1919
use crate::module::Parser;
2020

21+
pub const SECTION_NAME: &str = "wasefire-sidetable";
22+
23+
#[derive(Debug)]
2124
pub struct SideTableView<'m> {
2225
pub func_idx: usize,
2326
pub indices: &'m [u8], // including 0 and the length of metadata_array
2427
pub metadata: &'m [u8],
2528
}
2629

2730
impl<'m> SideTableView<'m> {
28-
pub fn new(binary: &'m [u8]) -> Result<Self, Error> {
29-
let num_functions = parse_u16(binary, 0) as usize;
30-
let indices_end = 2 + (num_functions + 1) * 2;
31-
Ok(SideTableView {
32-
func_idx: 0,
33-
indices: &binary[2 .. indices_end],
34-
metadata: &binary[indices_end ..],
35-
})
36-
}
37-
3831
// TODO(dev/fast-interp): Make it generic since it will be used in both `Check` and `Use` modes.
3932
// (Returns `MResult<Metadata<'m>, M>` instead.)
4033
pub fn metadata(&self, func_idx: usize) -> Metadata<'m> {
@@ -45,7 +38,7 @@ impl<'m> SideTableView<'m> {
4538
}
4639
}
4740

48-
#[derive(Default, Copy, Clone)]
41+
#[derive(Debug, Default, Copy, Clone)]
4942
pub struct Metadata<'m>(&'m [u8]);
5043

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

61-
pub fn branch_table(&self) -> &[BranchTableEntry] {
54+
pub fn branch_table(&self) -> &'m [BranchTableEntry] {
6255
let entry_size = size_of::<BranchTableEntry>();
63-
assert_eq!(
64-
(self.0.len() - 10) % entry_size,
65-
0,
66-
"Metadata length for branch table must be divisible by {} bytes",
67-
entry_size
68-
);
56+
let branch_table = &self.0[10 ..];
57+
assert_eq!(branch_table.len() % entry_size, 0);
6958
unsafe {
7059
core::slice::from_raw_parts(
71-
self.0[10 ..].as_ptr() as *const BranchTableEntry,
72-
self.0.len() / entry_size,
60+
branch_table.as_ptr().cast(),
61+
branch_table.len() / entry_size,
7362
)
7463
}
7564
}
@@ -86,6 +75,34 @@ pub struct MetadataEntry {
8675
pub branch_table: Vec<BranchTableEntry>,
8776
}
8877

78+
pub fn serialize(side_table: &[MetadataEntry]) -> Result<Vec<u8>, Error> {
79+
let mut res = Vec::new();
80+
let num_funcs = try_from::<u16>("length of MetadataEntry", side_table.len())?;
81+
res.extend_from_slice(&num_funcs.to_le_bytes());
82+
let mut index = 0u16;
83+
res.extend_from_slice(&index.to_le_bytes());
84+
for entry in side_table {
85+
index = try_from::<u16>(
86+
"index of MetadataEntry",
87+
index as usize + 10 + 6 * entry.branch_table.len(),
88+
)?;
89+
res.extend_from_slice(&index.to_le_bytes());
90+
}
91+
for entry in side_table {
92+
let type_idx = try_from::<u16>("MetadataEntry::type_idx", entry.type_idx)?;
93+
res.extend_from_slice(&type_idx.to_le_bytes());
94+
let range_start =
95+
try_from::<u32>("MetadataEntry::parser_range start", entry.parser_range.start)?;
96+
res.extend_from_slice(&range_start.to_le_bytes());
97+
let range_end = try_from::<u32>("MetadataEntry::parser_range end", entry.parser_range.end)?;
98+
res.extend_from_slice(&range_end.to_le_bytes());
99+
for branch in &entry.branch_table {
100+
res.extend_from_slice(&branch.0);
101+
}
102+
}
103+
Ok(res)
104+
}
105+
89106
#[derive(Copy, Clone, Debug)]
90107
#[repr(transparent)]
91108
pub struct BranchTableEntry([u8; 6]);
@@ -146,3 +163,12 @@ fn parse_u16(data: &[u8], offset: usize) -> u16 {
146163
fn parse_u32(data: &[u8], offset: usize) -> u32 {
147164
u32::from_le_bytes(data[offset ..][.. 4].try_into().unwrap())
148165
}
166+
167+
#[allow(unused_variables)]
168+
fn try_from<T: TryFrom<usize>>(msg: &str, val: usize) -> Result<T, Error> {
169+
T::try_from(val).map_err(|_| {
170+
#[cfg(feature = "debug")]
171+
eprintln!("{msg} overflow");
172+
unsupported(if_debug!(Unsupported::SideTable))
173+
})
174+
}

crates/interpreter/src/toctou.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ pub fn byte_enum<M: Mode, T: Copy + TryFromByte + UnsafeFromByte>(x: u8) -> MRes
126126
M::choose(|| T::try_from_byte(x), || unsafe { T::from_byte_unchecked(x) })
127127
}
128128

129+
pub fn try_into<M: Mode, T: Copy, S: TryInto<T>>(x: S) -> MResult<T, M> {
130+
let y = S::try_into(x).ok();
131+
M::choose(|| y, || unsafe { y.unwrap_unchecked() })
132+
}
133+
129134
#[cfg(test)]
130135
mod tests {
131136
use super::*;

0 commit comments

Comments
 (0)