Skip to content

Commit 2fbb835

Browse files
committed
Create the wasm-bindgen-wasm-conventions crate
This tiny crate provides utilities for working with Wasm codegen conventions (typically established by LLVM or lld) such as getting the shadow stack pointer. It also de-duplicates all the places in the codebase where we were implementing these conventions in one-off ways.
1 parent 68af85d commit 2fbb835

File tree

7 files changed

+174
-122
lines changed

7 files changed

+174
-122
lines changed

crates/cli-support/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.50' }
2323
wasm-bindgen-shared = { path = "../shared", version = '=0.2.50' }
2424
wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.50' }
2525
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.50' }
26+
wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.50' }
2627
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.50' }
2728
wasm-webidl-bindings = "0.5.0"

crates/cli-support/src/lib.rs

+19-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::mem;
88
use std::path::{Path, PathBuf};
99
use std::str;
1010
use walrus::Module;
11+
use wasm_bindgen_wasm_conventions as wasm_conventions;
1112

1213
mod anyref;
1314
mod decode;
@@ -278,18 +279,13 @@ impl Bindgen {
278279
}
279280
};
280281

281-
// Our multi-value xform relies on the presence of the stack pointer, so
282-
// temporarily export it so that our many GC's don't remove it before
283-
// the xform runs.
284-
if self.multi_value {
285-
// Assume that the first global is the shadow stack pointer, since that is
286-
// what LLVM codegens.
287-
match module.globals.iter().next() {
288-
Some(g) if g.ty == walrus::ValType::I32 => {
289-
module.exports.add("__shadow_stack_pointer", g.id());
290-
}
291-
_ => {}
292-
}
282+
// Our threads and multi-value xforms rely on the presence of the stack
283+
// pointer, so temporarily export it so that our many GC's don't remove
284+
// it before the xform runs.
285+
let mut exported_shadow_stack_pointer = false;
286+
if self.multi_value || self.threads.is_enabled() {
287+
wasm_conventions::export_shadow_stack_pointer(&mut module)?;
288+
exported_shadow_stack_pointer = true;
293289
}
294290

295291
// This isn't the hardest thing in the world too support but we
@@ -387,6 +383,17 @@ impl Bindgen {
387383
}
388384
}
389385

386+
// If we exported the shadow stack pointer earlier, remove it from the
387+
// export set now.
388+
if exported_shadow_stack_pointer {
389+
wasm_conventions::unexport_shadow_stack_pointer(&mut module)?;
390+
// The shadow stack pointer is potentially unused now, but since it
391+
// most likely _is_ in use, we don't pay the cost of a full GC here
392+
// just to remove one potentially unnecessary global.
393+
//
394+
// walrus::passes::gc::run(&mut module);
395+
}
396+
390397
Ok(Output {
391398
module,
392399
stem: stem.to_string(),

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

+5-41
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ use crate::descriptor::VectorKind;
5353
use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName};
5454
use crate::webidl::{NonstandardIncoming, NonstandardOutgoing};
5555
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
56-
use failure::{bail, format_err, Error, ResultExt};
57-
use walrus::{GlobalId, MemoryId, Module};
56+
use failure::{bail, Error, ResultExt};
57+
use walrus::Module;
5858
use wasm_bindgen_multi_value_xform as multi_value_xform;
59+
use wasm_bindgen_wasm_conventions as wasm_conventions;
5960
use wasm_webidl_bindings::ast;
6061

6162
pub fn add_multi_value(
@@ -79,8 +80,8 @@ pub fn add_multi_value(
7980
return Ok(());
8081
}
8182

82-
let memory = get_memory(module)?;
83-
let shadow_stack_pointer = get_shadow_stack_pointer(module)?;
83+
let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?;
84+
let memory = wasm_conventions::get_memory(module)?;
8485
multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?;
8586

8687
// Finally, unset `return_via_outptr`, fix up its incoming bindings'
@@ -163,43 +164,6 @@ fn fixup_binding_argument_gets(incoming: &mut [NonstandardIncoming]) -> Result<(
163164
}
164165
}
165166

166-
fn get_memory(module: &Module) -> Result<MemoryId, Error> {
167-
let mut memories = module.memories.iter().map(|m| m.id());
168-
let memory = memories.next();
169-
if memories.next().is_some() {
170-
bail!(
171-
"expected a single memory, found multiple; multiple memories \
172-
currently not supported"
173-
);
174-
}
175-
memory.ok_or_else(|| {
176-
format_err!(
177-
"module does not have a memory; must have a memory \
178-
to transform return pointers into Wasm multi-value"
179-
)
180-
})
181-
}
182-
183-
// Get the `__shadow_stack_pointer` global that we stashed in an export early on
184-
// in the pipeline.
185-
fn get_shadow_stack_pointer(module: &mut Module) -> Result<GlobalId, Error> {
186-
let (g, e) = module
187-
.exports
188-
.iter()
189-
.find(|e| e.name == "__shadow_stack_pointer")
190-
.map(|e| {
191-
let g = match e.item {
192-
walrus::ExportItem::Global(g) => g,
193-
_ => unreachable!(),
194-
};
195-
(g, e.id())
196-
})
197-
.ok_or_else(|| format_err!("module does not have a shadow stack pointer"))?;
198-
199-
module.exports.delete(e);
200-
Ok(g)
201-
}
202-
203167
pub fn add_section(
204168
module: &mut Module,
205169
aux: &WasmBindgenAux,

crates/threads-xform/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ edition = "2018"
1414
[dependencies]
1515
failure = "0.1"
1616
walrus = "0.12.0"
17+
wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.50" }

crates/threads-xform/src/lib.rs

+37-69
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use failure::{bail, format_err, Error};
77
use walrus::ir::Value;
88
use walrus::{DataId, FunctionId, InitExpr, ValType};
99
use walrus::{ExportItem, GlobalId, GlobalKind, ImportKind, MemoryId, Module};
10+
use wasm_bindgen_wasm_conventions as wasm_conventions;
1011

1112
const PAGE_SIZE: u32 = 1 << 16;
1213

@@ -16,6 +17,7 @@ const PAGE_SIZE: u32 = 1 << 16;
1617
pub struct Config {
1718
maximum_memory: u32,
1819
thread_stack_size: u32,
20+
enabled: bool,
1921
}
2022

2123
impl Config {
@@ -24,9 +26,15 @@ impl Config {
2426
Config {
2527
maximum_memory: 1 << 30, // 1GB
2628
thread_stack_size: 1 << 20, // 1MB
29+
enabled: env::var("WASM_BINDGEN_THREADS").is_ok(),
2730
}
2831
}
2932

33+
/// Is threaded Wasm enabled?
34+
pub fn is_enabled(&self) -> bool {
35+
self.enabled
36+
}
37+
3038
/// Specify the maximum amount of memory the wasm module can ever have.
3139
///
3240
/// We'll be specifying that the memory for this wasm module is shared, and
@@ -79,18 +87,22 @@ impl Config {
7987
///
8088
/// More and/or less may happen here over time, stay tuned!
8189
pub fn run(&self, module: &mut Module) -> Result<(), Error> {
90+
if !self.enabled {
91+
return Ok(());
92+
}
93+
8294
// Compatibility with older LLVM outputs. Newer LLVM outputs, when
8395
// atomics are enabled, emit a shared memory. That's a good indicator
8496
// that we have work to do. If shared memory isn't enabled, though then
8597
// this isn't an atomic module so there's nothing to do. We still allow,
8698
// though, an environment variable to force us to go down this path to
8799
// remain compatibile with older LLVM outputs.
88-
let memory = find_memory(module)?;
89-
if !module.memories.get(memory).shared && env::var("WASM_BINDGEN_THREADS").is_err() {
100+
let memory = wasm_conventions::get_memory(module)?;
101+
if !module.memories.get(memory).shared {
90102
return Ok(());
91103
}
92104

93-
let stack_pointer = find_stack_pointer(module)?;
105+
let stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?;
94106
let addr = allocate_static_data(module, memory, 4, 4)?;
95107
let zero = InitExpr::Value(Value::I32(0));
96108
let globals = Globals {
@@ -207,17 +219,6 @@ fn switch_data_segments_to_passive(
207219
Ok(ret)
208220
}
209221

210-
fn find_memory(module: &mut Module) -> Result<MemoryId, Error> {
211-
let mut memories = module.memories.iter();
212-
let memory = memories
213-
.next()
214-
.ok_or_else(|| format_err!("currently incompatible with no memory modules"))?;
215-
if memories.next().is_some() {
216-
bail!("only one memory is currently supported");
217-
}
218-
Ok(memory.id())
219-
}
220-
221222
fn update_memory(module: &mut Module, memory: MemoryId, max: u32) -> Result<MemoryId, Error> {
222223
assert!(max % PAGE_SIZE == 0);
223224
let memory = module.memories.get_mut(memory);
@@ -313,37 +314,6 @@ fn allocate_static_data(
313314
Ok(address)
314315
}
315316

316-
fn find_stack_pointer(module: &mut Module) -> Result<Option<GlobalId>, Error> {
317-
let candidates = module
318-
.globals
319-
.iter()
320-
.filter(|g| g.ty == ValType::I32)
321-
.filter(|g| g.mutable)
322-
.filter(|g| match g.kind {
323-
GlobalKind::Local(_) => true,
324-
GlobalKind::Import(_) => false,
325-
})
326-
.collect::<Vec<_>>();
327-
328-
if candidates.len() == 0 {
329-
return Ok(None);
330-
}
331-
if candidates.len() > 2 {
332-
bail!("too many mutable globals to infer the stack pointer");
333-
}
334-
if candidates.len() == 1 {
335-
return Ok(Some(candidates[0].id()));
336-
}
337-
338-
// If we've got two mutable globals then we're in a pretty standard
339-
// situation for threaded code where one is the stack pointer and one is the
340-
// TLS base offset. We need to figure out which is which, and we basically
341-
// assume LLVM's current codegen where the first is the stack pointer.
342-
//
343-
// TODO: have an actual check here.
344-
Ok(Some(candidates[0].id()))
345-
}
346-
347317
enum InitMemory {
348318
Segments(Vec<PassiveSegment>),
349319
Call {
@@ -358,7 +328,7 @@ fn inject_start(
358328
memory_init: InitMemory,
359329
globals: &Globals,
360330
addr: u32,
361-
stack_pointer: Option<GlobalId>,
331+
stack_pointer: GlobalId,
362332
stack_size: u32,
363333
memory: MemoryId,
364334
) -> Result<(), Error> {
@@ -393,30 +363,28 @@ fn inject_start(
393363
// we give ourselves a stack via memory.grow and we update our stack
394364
// pointer as the default stack pointer is surely wrong for us.
395365
|body| {
396-
if let Some(stack_pointer) = stack_pointer {
397-
// local0 = grow_memory(stack_size);
398-
body.i32_const((stack_size / PAGE_SIZE) as i32)
399-
.memory_grow(memory)
400-
.local_set(local);
401-
402-
// if local0 == -1 then trap
403-
body.block(None, |body| {
404-
let target = body.id();
405-
body.local_get(local)
406-
.i32_const(-1)
407-
.binop(BinaryOp::I32Ne)
408-
.br_if(target)
409-
.unreachable();
410-
});
411-
412-
// stack_pointer = local0 + stack_size
366+
// local0 = grow_memory(stack_size);
367+
body.i32_const((stack_size / PAGE_SIZE) as i32)
368+
.memory_grow(memory)
369+
.local_set(local);
370+
371+
// if local0 == -1 then trap
372+
body.block(None, |body| {
373+
let target = body.id();
413374
body.local_get(local)
414-
.i32_const(PAGE_SIZE as i32)
415-
.binop(BinaryOp::I32Mul)
416-
.i32_const(stack_size as i32)
417-
.binop(BinaryOp::I32Add)
418-
.global_set(stack_pointer);
419-
}
375+
.i32_const(-1)
376+
.binop(BinaryOp::I32Ne)
377+
.br_if(target)
378+
.unreachable();
379+
});
380+
381+
// stack_pointer = local0 + stack_size
382+
body.local_get(local)
383+
.i32_const(PAGE_SIZE as i32)
384+
.binop(BinaryOp::I32Mul)
385+
.i32_const(stack_size as i32)
386+
.binop(BinaryOp::I32Add)
387+
.global_set(stack_pointer);
420388
},
421389
// If the thread ID is zero then we can skip the update of the stack
422390
// pointer as we know our stack pointer is valid. We need to initialize

crates/wasm-conventions/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "wasm-bindgen-wasm-conventions"
3+
version = "0.2.50"
4+
authors = ["The wasm-bindgen developers"]
5+
license = "MIT/Apache-2.0"
6+
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-conventions"
7+
homepage = "https://rustwasm.github.io/wasm-bindgen/"
8+
documentation = "https://docs.rs/wasm-bindgen-wasm-conventions"
9+
description = "Utilities for working with Wasm codegen conventions (usually established by LLVM/lld)"
10+
edition = "2018"
11+
12+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
14+
[dependencies]
15+
walrus = "0.12.0"
16+
failure = "0.1.5"

0 commit comments

Comments
 (0)