Skip to content

Commit 5c76900

Browse files
authored
Fix MVP element-segment encoding for table64 active segments (#317)
* Fix MVP element-segment encoding for table64 active segments When walrus emits an active element segment whose table is table 0, it tells `wasm-encoder` to use the MVP encoding (variant `0x00`) which carries an implicit table index of 0 and requires the offset constant to be `i32`. If table 0 is a `table64` table the offset must be `i64`, so the MVP encoding is not applicable; the resulting binary is rejected by compliant engines (V8 reports `invalid table elements limits flags` because it tries to decode the i64 offset bytes as an i32 const expression and then reads the following byte as element-section limits). Use `wasm-encoder`'s explicit-table-index form (variant `0x02`) unconditionally for `table64` tables. Non-`table64` table-0 active segments continue to use the backwards-compatible MVP encoding. Test: `crates/tests/tests/table64_active_segment.rs` asserts at the byte level that the emitted segment kind tag is not `0x00` for a table64 active segment. The test also round-trips through walrus. * fixup workflow
1 parent 8b17b8b commit 5c76900

4 files changed

Lines changed: 100 additions & 6 deletions

File tree

.github/workflows/main.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: CI
2-
on: [push, pull_request]
2+
on:
3+
pull_request:
4+
push:
5+
branches: [main]
36

47
jobs:
58
test:

crates/tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ serde_json = { version = "1.0.40", features = ['preserve_order'] }
1616
tempfile = "3.1.0"
1717
walrus = { path = "../.." }
1818
walrus-tests-utils = { path = "../tests-utils" }
19+
wasmparser = "0.245.1"
1920
wasmprinter = "0.245"
2021
wat = "1.0.85"
2122

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! Repro for an emit bug: walrus picks the MVP element-segment form
2+
//! (variant `0x00`, implicit table 0, i32 offset) for active segments
3+
//! targeting a `table64` table, which V8 rejects with `invalid table
4+
//! elements limits flags`. `wasmparser` accepts the malformed binary,
5+
//! so this test asserts at the byte level on the segment-kind tag.
6+
7+
use walrus::{
8+
ConstExpr, ElementItems, ElementKind, FunctionBuilder, Module, ModuleConfig, RefType,
9+
};
10+
11+
#[test]
12+
fn table64_active_segment_round_trips() {
13+
let mut config = ModuleConfig::new();
14+
config.generate_producers_section(false);
15+
let mut module = Module::with_config(config.clone());
16+
17+
let func_id =
18+
FunctionBuilder::new(&mut module.types, &[], &[]).finish(vec![], &mut module.funcs);
19+
module.exports.add("f", func_id);
20+
21+
let table_id = module
22+
.tables
23+
.add_local(/* table64 */ true, 1, Some(1), RefType::FUNCREF);
24+
25+
let elem_id = module.elements.add(
26+
ElementKind::Active {
27+
table: table_id,
28+
offset: ConstExpr::Value(walrus::ir::Value::I64(0)),
29+
},
30+
ElementItems::Functions(vec![func_id]),
31+
);
32+
module
33+
.tables
34+
.get_mut(table_id)
35+
.elem_segments
36+
.insert(elem_id);
37+
38+
let wasm = module.emit_wasm();
39+
40+
let kind = first_element_segment_kind(&wasm).expect("module must have an element section");
41+
assert_ne!(
42+
kind, 0x00,
43+
"walrus emitted MVP element-segment form for a table64 segment; \
44+
engines like V8 reject this. Use the explicit-table-index form."
45+
);
46+
47+
let module2 = config.parse(&wasm).expect("must round-trip");
48+
let table = module2.tables.iter().next().expect("should have a table");
49+
assert!(table.table64, "table64 flag must survive round-trip");
50+
}
51+
52+
fn first_element_segment_kind(wasm: &[u8]) -> Option<u8> {
53+
let mut parser = wasmparser::Parser::new(0);
54+
let mut cur = wasm;
55+
loop {
56+
match parser.parse(cur, true).ok()? {
57+
wasmparser::Chunk::Parsed { consumed, payload } => {
58+
if let wasmparser::Payload::ElementSection(reader) = &payload {
59+
let range = reader.range();
60+
let bytes = &wasm[range.start..range.end];
61+
let (_count, n) = leb128_u32(bytes);
62+
return Some(bytes[n]);
63+
}
64+
if matches!(payload, wasmparser::Payload::End(_)) {
65+
return None;
66+
}
67+
cur = &cur[consumed..];
68+
}
69+
_ => return None,
70+
}
71+
}
72+
}
73+
74+
fn leb128_u32(bytes: &[u8]) -> (u32, usize) {
75+
let mut result = 0u32;
76+
let mut shift = 0;
77+
let mut i = 0;
78+
loop {
79+
let b = bytes[i];
80+
result |= ((b & 0x7f) as u32) << shift;
81+
i += 1;
82+
if b & 0x80 == 0 {
83+
return (result, i);
84+
}
85+
shift += 7;
86+
}
87+
}

src/module/elements.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,15 @@ impl Emit for ModuleElements {
254254
) {
255255
match kind {
256256
ElementKind::Active { table, offset } => {
257-
// When the table index is 0, set this to `None` to tell `wasm-encoder` to use
258-
// the backwards-compatible MVP encoding.
259-
let table_index =
260-
Some(cx.indices.get_table_index(*table)).filter(|&index| index != 0);
257+
// Use the MVP encoding (implicit table 0) only when the
258+
// table is index 0 and not a table64, since MVP requires
259+
// an i32 offset.
260+
let table_index = cx.indices.get_table_index(*table);
261+
let is_table64 = cx.module.tables.get(*table).table64;
262+
let encoded_table_index =
263+
Some(table_index).filter(|&idx| idx != 0 || is_table64);
261264
wasm_element_section.active(
262-
table_index,
265+
encoded_table_index,
263266
&offset.to_wasmencoder_type(cx),
264267
els,
265268
);

0 commit comments

Comments
 (0)