Skip to content

Commit fa821b4

Browse files
author
Arnaud Riess
committed
feat(parser): refactored parser
1 parent dcd0f5d commit fa821b4

2 files changed

Lines changed: 128 additions & 89 deletions

File tree

crates/herkos/src/parser/mod.rs

Lines changed: 112 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,111 @@ fn eval_const_expr(const_expr: wasmparser::ConstExpr) -> Result<InitValue> {
185185
}
186186
}
187187

188+
/// Parse an active element segment, or return None for passive/declared segments.
189+
fn parse_element_segment(element: wasmparser::Element) -> Result<Option<ElementSegment>> {
190+
match element.kind {
191+
wasmparser::ElementKind::Active {
192+
table_index,
193+
offset_expr,
194+
} => {
195+
// table_index is Option<u32>; None means table 0 (MVP default)
196+
let tidx = table_index.unwrap_or(0);
197+
if tidx != 0 {
198+
anyhow::bail!(
199+
"Multi-table element segments not supported (table_index={})",
200+
tidx
201+
);
202+
}
203+
204+
let offset = match eval_const_expr(offset_expr)? {
205+
InitValue::I32(v) => v as u32,
206+
_ => anyhow::bail!("Element segment offset must be i32"),
207+
};
208+
209+
// Collect function indices from element items
210+
let mut func_indices = Vec::new();
211+
match element.items {
212+
wasmparser::ElementItems::Functions(funcs) => {
213+
for func_idx in funcs {
214+
let idx = func_idx.context("reading element func index")?;
215+
func_indices.push(idx);
216+
}
217+
}
218+
wasmparser::ElementItems::Expressions(..) => {
219+
anyhow::bail!("Expression-based element segments not supported");
220+
}
221+
}
222+
223+
Ok(Some(ElementSegment {
224+
offset,
225+
func_indices,
226+
}))
227+
}
228+
wasmparser::ElementKind::Passive | wasmparser::ElementKind::Declared => {
229+
// Skip passive/declared element segments
230+
Ok(None)
231+
}
232+
}
233+
}
234+
235+
/// Parse an active data segment, or return None for passive segments.
236+
fn parse_data_segment(data: wasmparser::Data) -> Result<Option<DataSegment>> {
237+
match data.kind {
238+
wasmparser::DataKind::Active {
239+
memory_index: 0,
240+
offset_expr,
241+
} => {
242+
let offset = match eval_const_expr(offset_expr)? {
243+
InitValue::I32(v) => v as u32,
244+
_ => anyhow::bail!("Data segment offset must be i32"),
245+
};
246+
Ok(Some(DataSegment {
247+
offset,
248+
data: data.data.to_vec(),
249+
}))
250+
}
251+
wasmparser::DataKind::Passive => {
252+
// Skip passive data segments (used with memory.init)
253+
Ok(None)
254+
}
255+
wasmparser::DataKind::Active { memory_index, .. } => {
256+
anyhow::bail!(
257+
"Multi-memory data segments not supported (memory_index={})",
258+
memory_index
259+
);
260+
}
261+
}
262+
}
263+
264+
/// Parse a function code section entry, extracting locals and bytecode.
265+
fn parse_code_entry(body: wasmparser::FunctionBody, type_idx: u32) -> Result<ParsedFunction> {
266+
// Extract locals
267+
let mut locals = Vec::new();
268+
let locals_reader = body.get_locals_reader().context("getting locals reader")?;
269+
for local in locals_reader {
270+
let (count, val_type) = local.context("reading local")?;
271+
for _ in 0..count {
272+
locals.push(val_type);
273+
}
274+
}
275+
276+
// Extract operators as raw bytes (parsed later in the IR builder)
277+
let operators_reader = body
278+
.get_operators_reader()
279+
.context("getting operators reader")?;
280+
let mut binary_reader = operators_reader.get_binary_reader();
281+
let remaining = binary_reader.bytes_remaining();
282+
let body_bytes = binary_reader
283+
.read_bytes(remaining)
284+
.context("reading body bytes")?;
285+
286+
Ok(ParsedFunction {
287+
type_idx,
288+
locals,
289+
body: body_bytes.to_vec(),
290+
})
291+
}
292+
188293
/// Parse a WebAssembly binary into a structured module.
189294
pub fn parse_wasm(wasm_bytes: &[u8]) -> Result<ParsedModule> {
190295
let parser = Parser::new(0);
@@ -268,32 +373,9 @@ pub fn parse_wasm(wasm_bytes: &[u8]) -> Result<ParsedModule> {
268373
}
269374

270375
Payload::CodeSectionEntry(body) => {
271-
// Extract locals
272-
let mut locals = Vec::new();
273-
let locals_reader = body.get_locals_reader().context("getting locals reader")?;
274-
for local in locals_reader {
275-
let (count, val_type) = local.context("reading local")?;
276-
for _ in 0..count {
277-
locals.push(val_type);
278-
}
279-
}
280-
281-
// Extract operators as raw bytes
282-
// (We'll parse them later in the IR builder)
283-
let operators_reader = body
284-
.get_operators_reader()
285-
.context("getting operators reader")?;
286-
let mut binary_reader = operators_reader.get_binary_reader();
287-
let remaining = binary_reader.bytes_remaining();
288-
let body_bytes = binary_reader
289-
.read_bytes(remaining)
290-
.context("reading body bytes")?;
291-
292-
functions.push(ParsedFunction {
293-
type_idx: function_types[functions.len()], // Match with function section
294-
locals,
295-
body: body_bytes.to_vec(),
296-
});
376+
let type_idx = function_types[functions.len()]; // Match with function section
377+
let parsed_func = parse_code_entry(body, type_idx)?;
378+
functions.push(parsed_func);
297379
}
298380

299381
Payload::MemorySection(reader) => {
@@ -321,46 +403,8 @@ pub fn parse_wasm(wasm_bytes: &[u8]) -> Result<ParsedModule> {
321403
Payload::ElementSection(reader) => {
322404
for element in reader {
323405
let element = element.context("reading element segment")?;
324-
match element.kind {
325-
wasmparser::ElementKind::Active {
326-
table_index,
327-
offset_expr,
328-
} => {
329-
// table_index is Option<u32>; None means table 0 (MVP default)
330-
let tidx = table_index.unwrap_or(0);
331-
if tidx != 0 {
332-
anyhow::bail!(
333-
"Multi-table element segments not supported (table_index={})",
334-
tidx
335-
);
336-
}
337-
let offset = match eval_const_expr(offset_expr)? {
338-
InitValue::I32(v) => v as u32,
339-
_ => anyhow::bail!("Element segment offset must be i32"),
340-
};
341-
// Collect function indices from element items
342-
let mut func_indices = Vec::new();
343-
match element.items {
344-
wasmparser::ElementItems::Functions(funcs) => {
345-
for func_idx in funcs {
346-
let idx = func_idx.context("reading element func index")?;
347-
func_indices.push(idx);
348-
}
349-
}
350-
wasmparser::ElementItems::Expressions(..) => {
351-
anyhow::bail!(
352-
"Expression-based element segments not supported"
353-
);
354-
}
355-
}
356-
element_segments.push(ElementSegment {
357-
offset,
358-
func_indices,
359-
});
360-
}
361-
wasmparser::ElementKind::Passive | wasmparser::ElementKind::Declared => {
362-
// Skip passive/declared element segments
363-
}
406+
if let Some(segment) = parse_element_segment(element)? {
407+
element_segments.push(segment);
364408
}
365409
}
366410
}
@@ -398,29 +442,8 @@ pub fn parse_wasm(wasm_bytes: &[u8]) -> Result<ParsedModule> {
398442
Payload::DataSection(reader) => {
399443
for data in reader {
400444
let data = data.context("reading data segment")?;
401-
match data.kind {
402-
wasmparser::DataKind::Active {
403-
memory_index: 0,
404-
offset_expr,
405-
} => {
406-
let offset = match eval_const_expr(offset_expr)? {
407-
InitValue::I32(v) => v as u32,
408-
_ => anyhow::bail!("Data segment offset must be i32"),
409-
};
410-
data_segments.push(DataSegment {
411-
offset,
412-
data: data.data.to_vec(),
413-
});
414-
}
415-
wasmparser::DataKind::Passive => {
416-
// Skip passive data segments (used with memory.init)
417-
}
418-
wasmparser::DataKind::Active { memory_index, .. } => {
419-
anyhow::bail!(
420-
"Multi-memory data segments not supported (memory_index={})",
421-
memory_index
422-
);
423-
}
445+
if let Some(segment) = parse_data_segment(data)? {
446+
data_segments.push(segment);
424447
}
425448
}
426449
}

sparkfile.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- benchmark embedded (code size + runtime performance)
2+
- benchmark transpiler (runtime performance)
3+
- for testing: wat generator, for transpiler testing purpose, deterministic, input: function size, # of function, # of imports, # of globals, memory size, ...
4+
- transpiler can generate mocks for making 1st time integration easier.
5+
- code coverage tooling + runtime.
6+
- test on hardware.
7+
- import embedded hal.
8+
- host side helpers for manipulating memory, taking endianess into account.
9+
- determinism
10+
- provide simpl example project
11+
- think of a more complex project with embedded. E.g. embedded, i2c, accelration sensor, send data over mqtt.
12+
- verifier in a seprated repo
13+
- make all crate names consistent
14+
- make the transpiler's lib side work like the cc crate in a build.rs
15+
- --max-pages has no effect!
16+
- Getting started section is too big.

0 commit comments

Comments
 (0)