Skip to content

Commit 8e3d6fe

Browse files
authored
Update the walrus dependency (#2125)
This commit updates the `walrus` crate used in `wasm-bindgen`. The major change here is how `walrus` handles element segments, exposing segments rather than trying to keep a contiugous array of all the elements and doing the splitting itself. That means that we need to do mroe logic here in `wasm-bindgen` to juggle indices, segments, etc.
1 parent dc54c0f commit 8e3d6fe

File tree

20 files changed

+242
-120
lines changed

20 files changed

+242
-120
lines changed

crates/anyref-xform/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ edition = '2018'
1313

1414
[dependencies]
1515
anyhow = "1.0"
16-
walrus = "0.14.0"
16+
walrus = "0.16.0"
1717

1818
[dev-dependencies]
1919
rayon = "1.0"

crates/anyref-xform/src/lib.rs

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
use anyhow::{anyhow, bail, Error};
1919
use std::cmp;
2020
use std::collections::{BTreeMap, HashMap, HashSet};
21+
use std::mem;
2122
use walrus::ir::*;
22-
use walrus::{ExportId, ImportId, InstrLocId, TypeId};
23+
use walrus::{ElementId, ExportId, ImportId, InstrLocId, TypeId};
2324
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
2425

2526
// must be kept in sync with src/lib.rs and ANYREF_HEAP_START
@@ -34,11 +35,19 @@ pub struct Context {
3435
// values in the function signature should turn into anyref.
3536
imports: HashMap<ImportId, Function>,
3637
exports: HashMap<ExportId, Function>,
37-
elements: BTreeMap<u32, (u32, Function)>,
38+
39+
// List of functions we're transforming that are present in the function
40+
// table. Each index here is an index into the function table, and the
41+
// `Function` describes how we're transforming it.
42+
new_elements: Vec<(u32, Function)>,
3843

3944
// When wrapping closures with new shims, this is the index of the next
4045
// table entry that we'll be handing out.
41-
next_element: u32,
46+
new_element_offset: u32,
47+
48+
// Map of the existing function table, keyed by offset and contains the
49+
// final offset plus the element segment used to initialized that range.
50+
elements: BTreeMap<u32, ElementId>,
4251

4352
// The anyref table we'll be using, injected after construction
4453
table: Option<TableId>,
@@ -93,22 +102,27 @@ impl Context {
93102
// Figure out what the maximum index of functions pointers are. We'll
94103
// be adding new entries to the function table later (maybe) so
95104
// precalculate this ahead of time.
96-
let mut tables = module.tables.iter().filter_map(|t| match &t.kind {
97-
walrus::TableKind::Function(f) => Some(f),
98-
_ => None,
99-
});
100-
if let Some(t) = tables.next() {
101-
if tables.next().is_some() {
102-
bail!("more than one function table present")
105+
if let Some(t) = module.tables.main_function_table()? {
106+
let t = module.tables.get(t);
107+
for id in t.elem_segments.iter() {
108+
let elem = module.elements.get(*id);
109+
let offset = match &elem.kind {
110+
walrus::ElementKind::Active { offset, .. } => offset,
111+
_ => continue,
112+
};
113+
let offset = match offset {
114+
walrus::InitExpr::Value(Value::I32(n)) => *n as u32,
115+
other => bail!("invalid offset for segment of function table {:?}", other),
116+
};
117+
let max = offset + elem.members.len() as u32;
118+
self.new_element_offset = cmp::max(self.new_element_offset, max);
119+
self.elements.insert(offset, *id);
103120
}
104-
self.next_element = t.elements.len() as u32;
105121
}
106-
drop(tables);
107122

108123
// Add in an anyref table to the module, which we'll be using for
109124
// our transform below.
110-
let kind = walrus::TableKind::Anyref(Default::default());
111-
self.table = Some(module.tables.add_local(DEFAULT_MIN, None, kind));
125+
self.table = Some(module.tables.add_local(DEFAULT_MIN, None, ValType::Anyref));
112126

113127
Ok(())
114128
}
@@ -151,10 +165,8 @@ impl Context {
151165
ret_anyref: bool,
152166
) -> Option<u32> {
153167
self.function(anyref, ret_anyref).map(|f| {
154-
let ret = self.next_element;
155-
self.next_element += 1;
156-
self.elements.insert(ret, (idx, f));
157-
ret
168+
self.new_elements.push((idx, f));
169+
self.new_elements.len() as u32 + self.new_element_offset - 1
158170
})
159171
}
160172

@@ -265,7 +277,7 @@ impl Transform<'_> {
265277
self.process_exports(module)?;
266278
assert!(self.cx.exports.is_empty());
267279
self.process_elements(module)?;
268-
assert!(self.cx.elements.is_empty());
280+
assert!(self.cx.new_elements.is_empty());
269281

270282
// If we didn't actually transform anything, no need to inject or
271283
// rewrite anything from below.
@@ -394,18 +406,20 @@ impl Transform<'_> {
394406
None => return Ok(()),
395407
};
396408
let table = module.tables.get_mut(table);
397-
let kind = match &mut table.kind {
398-
walrus::TableKind::Function(f) => f,
399-
_ => unreachable!(),
400-
};
401-
if kind.relative_elements.len() > 0 {
402-
bail!("not compatible with relative element initializers yet");
403-
}
404409

405410
// Create shims for all our functions and append them all to the segment
406411
// which places elements at the end.
407-
while let Some((idx, function)) = self.cx.elements.remove(&(kind.elements.len() as u32)) {
408-
let target = kind.elements[idx as usize].unwrap();
412+
let mut new_segment = Vec::new();
413+
for (idx, function) in mem::replace(&mut self.cx.new_elements, Vec::new()) {
414+
let (&offset, &orig_element) = self
415+
.cx
416+
.elements
417+
.range(..=idx)
418+
.next_back()
419+
.ok_or(anyhow!("failed to find segment defining index {}", idx))?;
420+
let target = module.elements.get(orig_element).members[(idx - offset) as usize].ok_or(
421+
anyhow!("function index {} not present in element segment", idx),
422+
)?;
409423
let (shim, _anyref_ty) = self.append_shim(
410424
target,
411425
&format!("closure{}", idx),
@@ -414,14 +428,21 @@ impl Transform<'_> {
414428
&mut module.funcs,
415429
&mut module.locals,
416430
)?;
417-
kind.elements.push(Some(shim));
431+
new_segment.push(Some(shim));
418432
}
419433

420434
// ... and next update the limits of the table in case any are listed.
421-
table.initial = cmp::max(table.initial, kind.elements.len() as u32);
435+
let new_max = self.cx.new_element_offset + new_segment.len() as u32;
436+
table.initial = cmp::max(table.initial, new_max);
422437
if let Some(max) = table.maximum {
423-
table.maximum = Some(cmp::max(max, kind.elements.len() as u32));
438+
table.maximum = Some(cmp::max(max, new_max));
424439
}
440+
let kind = walrus::ElementKind::Active {
441+
table: table.id(),
442+
offset: InitExpr::Value(Value::I32(self.cx.new_element_offset as i32)),
443+
};
444+
let segment = module.elements.add(kind, ValType::Funcref, new_segment);
445+
table.elem_segments.insert(segment);
425446

426447
Ok(())
427448
}

crates/anyref-xform/tests/table.wat

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
(table (;0;) 2 funcref)
2929
(table (;1;) 32 anyref)
3030
(export "func" (table 0))
31-
(elem (;0;) (i32.const 0) func $foo $closure0 anyref shim))
31+
(elem (;0;) (i32.const 0) func $foo)
32+
(elem (;1;) (i32.const 1) func $closure0 anyref shim))
3233
;)

crates/cli-support/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ log = "0.4"
1818
rustc-demangle = "0.1.13"
1919
serde_json = "1.0"
2020
tempfile = "3.0"
21-
walrus = "0.14.0"
21+
walrus = "0.16.1"
2222
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.62' }
2323
wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.62' }
2424
wasm-bindgen-shared = { path = "../shared", version = '=0.2.62' }
2525
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.62' }
2626
wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.62' }
2727
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.62' }
2828
wit-text = "0.1.1"
29-
wit-walrus = "0.1.0"
29+
wit-walrus = "0.2.0"
3030
wit-validator = "0.1.0"

crates/cli-support/src/anyref.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use crate::intrinsic::Intrinsic;
33
use crate::wit::AuxImport;
44
use crate::wit::{AdapterKind, Instruction, NonstandardWitSection};
55
use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux};
6-
use anyhow::Error;
6+
use anyhow::Result;
77
use std::collections::HashMap;
8-
use walrus::Module;
8+
use walrus::{ir::Value, ElementKind, InitExpr, Module};
99
use wasm_bindgen_anyref_xform::Context;
1010

11-
pub fn process(module: &mut Module) -> Result<(), Error> {
11+
pub fn process(module: &mut Module) -> Result<()> {
1212
let mut cfg = Context::default();
1313
cfg.prepare(module)?;
1414
let section = module
@@ -382,3 +382,86 @@ fn module_needs_anyref_metadata(aux: &WasmBindgenAux, section: &NonstandardWitSe
382382
})
383383
})
384384
}
385+
386+
/// In MVP wasm all element segments must be contiguous lists of function
387+
/// indices. Post-MVP with reference types element segments can have holes.
388+
/// While `walrus` will select the encoding that fits, this function forces the
389+
/// listing of segments to be MVP-compatible.
390+
pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
391+
// List of new element segments we're going to be adding.
392+
let mut new_segments = Vec::new();
393+
394+
// Here we take a look at all element segments in the module to see if we
395+
// need to split them.
396+
for segment in module.elements.iter_mut() {
397+
// If this segment has all-`Some` members then it's alrady contiguous
398+
// and we can skip it.
399+
if segment.members.iter().all(|m| m.is_some()) {
400+
continue;
401+
}
402+
403+
// For now active segments are all we're interested in since
404+
// passive/declared have no hope of being MVP-compatible anyway.
405+
// Additionally we only handle active segments with i32 offsets, since
406+
// global offsets get funky since we'd need to add an offset.
407+
let (table, offset) = match &segment.kind {
408+
ElementKind::Active {
409+
table,
410+
offset: InitExpr::Value(Value::I32(n)),
411+
} => (*table, *n),
412+
_ => continue,
413+
};
414+
415+
// `block` keeps track of a block of contiguous segment of functions
416+
let mut block = None;
417+
// This keeps track of where we're going to truncate the current segment
418+
// after we split out all the blocks.
419+
let mut truncate = 0;
420+
// This commits a block of contiguous functions into the `new_segments`
421+
// list, accounting for the new offset which is relative to the old
422+
// offset.
423+
let mut commit = |last_idx: usize, block: Vec<_>| {
424+
let new_offset = offset + (last_idx - block.len()) as i32;
425+
let new_offset = InitExpr::Value(Value::I32(new_offset));
426+
new_segments.push((table, new_offset, segment.ty, block));
427+
};
428+
for (i, id) in segment.members.iter().enumerate() {
429+
match id {
430+
// If we find a function, then we either start a new block or
431+
// push it onto the existing block.
432+
Some(id) => block.get_or_insert(Vec::new()).push(Some(*id)),
433+
None => {
434+
let block = match block.take() {
435+
Some(b) => b,
436+
None => continue,
437+
};
438+
// If this is the first block (truncate isn't set and the
439+
// length of the block means it starts from the beginning),
440+
// then we leave it in the original list and don't commit
441+
// anything, we'll just edit where to truncate later.
442+
// Otherwise we commit this block to the new segment list.
443+
if truncate == 0 && block.len() == i {
444+
truncate = i;
445+
} else {
446+
commit(i, block);
447+
}
448+
}
449+
}
450+
}
451+
452+
// If there's no trailing empty slots then we commit the last block onto
453+
// the new segment list.
454+
if let Some(block) = block {
455+
commit(segment.members.len(), block);
456+
}
457+
segment.members.truncate(truncate);
458+
}
459+
460+
for (table, offset, ty, members) in new_segments {
461+
let id = module
462+
.elements
463+
.add(ElementKind::Active { table, offset }, ty, members);
464+
module.tables.get_mut(table).elem_segments.insert(id);
465+
}
466+
Ok(())
467+
}

crates/cli-support/src/descriptors.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,9 @@ impl WasmBindgenDescriptorsSection {
107107
// For all indirect functions that were closure descriptors, delete them
108108
// from the function table since we've executed them and they're not
109109
// necessary in the final binary.
110-
let table_id = match interpreter.function_table_id() {
111-
Some(id) => id,
112-
None => return Ok(()),
113-
};
114-
let table = module.tables.get_mut(table_id);
115-
let table = match &mut table.kind {
116-
walrus::TableKind::Function(f) => f,
117-
_ => unreachable!(),
118-
};
119-
for idx in element_removal_list {
110+
for (segment, idx) in element_removal_list {
120111
log::trace!("delete element {}", idx);
121-
assert!(table.elements[idx].is_some());
122-
table.elements[idx] = None;
112+
module.elements.get_mut(segment).members[idx] = None;
123113
}
124114

125115
// And finally replace all calls of `wbindgen_describe_closure` with a

crates/cli-support/src/js/binding.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,18 +1145,9 @@ impl Invocation {
11451145
// The function table never changes right now, so we can statically
11461146
// look up the desired function.
11471147
CallTableElement(idx) => {
1148-
let table = module
1149-
.tables
1150-
.main_function_table()?
1151-
.ok_or_else(|| anyhow!("no function table found"))?;
1152-
let functions = match &module.tables.get(table).kind {
1153-
walrus::TableKind::Function(f) => f,
1154-
_ => bail!("should have found a function table"),
1155-
};
1156-
let id = functions
1157-
.elements
1158-
.get(*idx as usize)
1159-
.and_then(|id| *id)
1148+
let entry = wasm_bindgen_wasm_conventions::get_function_table_entry(module, *idx)?;
1149+
let id = entry
1150+
.func
11601151
.ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?;
11611152
Invocation::Core { id, defer: false }
11621153
}

crates/cli-support/src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ impl Bindgen {
373373
for id in ids {
374374
module.exports.delete(id);
375375
}
376+
// Clean up element segments as well if they have holes in them
377+
// after some of our transformations, because non-anyref engines
378+
// only support contiguous arrays of function references in element
379+
// segments.
380+
anyref::force_contiguous_elements(&mut module)?;
376381
}
377382

378383
// If wasm interface types are enabled then the `__wbindgen_throw`
@@ -564,13 +569,6 @@ impl OutputMode {
564569
}
565570
}
566571

567-
fn bundler(&self) -> bool {
568-
match self {
569-
OutputMode::Bundler { .. } => true,
570-
_ => false,
571-
}
572-
}
573-
574572
fn esm_integration(&self) -> bool {
575573
match self {
576574
OutputMode::Bundler { .. }

crates/cli-support/src/wit/section.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,18 +220,11 @@ fn translate_instruction(
220220
_ => bail!("can only call exported functions"),
221221
},
222222
CallTableElement(e) => {
223-
let table = module
224-
.tables
225-
.main_function_table()?
226-
.ok_or_else(|| anyhow!("no function table found in module"))?;
227-
let functions = match &module.tables.get(table).kind {
228-
walrus::TableKind::Function(f) => f,
229-
_ => unreachable!(),
230-
};
231-
match functions.elements.get(*e as usize) {
232-
Some(Some(f)) => Ok(wit_walrus::Instruction::CallCore(*f)),
233-
_ => bail!("expected to find an element of the function table"),
234-
}
223+
let entry = wasm_bindgen_wasm_conventions::get_function_table_entry(module, *e)?;
224+
let id = entry
225+
.func
226+
.ok_or_else(|| anyhow!("function table wasn't filled in a {}", e))?;
227+
Ok(wit_walrus::Instruction::CallCore(id))
235228
}
236229
StringToMemory {
237230
mem,

crates/cli-support/src/wit/standard.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,9 @@ impl AdapterType {
313313
walrus::ValType::F32 => AdapterType::F32,
314314
walrus::ValType::F64 => AdapterType::F64,
315315
walrus::ValType::Anyref => AdapterType::Anyref,
316-
walrus::ValType::V128 => return None,
316+
walrus::ValType::Funcref | walrus::ValType::Nullref | walrus::ValType::V128 => {
317+
return None
318+
}
317319
})
318320
}
319321

0 commit comments

Comments
 (0)