Skip to content

Add reference output tests for JS operations #1894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 4, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions crates/anyref-xform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct Meta {
pub table: TableId,
pub alloc: Option<FunctionId>,
pub drop_slice: Option<FunctionId>,
pub live_count: Option<FunctionId>,
}

struct Transform<'a> {
Expand Down Expand Up @@ -178,20 +179,27 @@ impl Context {
let mut heap_alloc = None;
let mut heap_dealloc = None;
let mut drop_slice = None;
let mut live_count = None;

// Find exports of some intrinsics which we only need for a runtime
// implementation.
let mut to_delete = Vec::new();
for export in module.exports.iter() {
let f = match export.item {
walrus::ExportItem::Function(f) => f,
_ => continue,
};
match export.name.as_str() {
"__wbindgen_anyref_table_alloc" => heap_alloc = Some(f),
"__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f),
"__wbindgen_drop_anyref_slice" => drop_slice = Some(f),
"__anyref_table_alloc" => heap_alloc = Some(f),
"__anyref_table_dealloc" => heap_dealloc = Some(f),
"__anyref_drop_slice" => drop_slice = Some(f),
"__anyref_heap_live_count" => live_count = Some(f),
_ => continue,
}
to_delete.push(export.id());
}
for id in to_delete {
module.exports.delete(id);
}
let mut clone_ref = None;
if let Some(heap_alloc) = heap_alloc {
Expand Down Expand Up @@ -240,6 +248,7 @@ impl Context {
table,
alloc: heap_alloc,
drop_slice,
live_count,
})
}
}
Expand Down
52 changes: 46 additions & 6 deletions crates/cli-support/src/anyref.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::wit::{AdapterKind, Instruction, NonstandardWitSection};
use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux};
use crate::wit::AuxImport;
use anyhow::Error;
use std::collections::HashMap;
use walrus::Module;
use crate::intrinsic::Intrinsic;
use wasm_bindgen_anyref_xform::Context;

pub fn process(module: &mut Module) -> Result<(), Error> {
Expand All @@ -17,7 +19,7 @@ pub fn process(module: &mut Module) -> Result<(), Error> {
.implements
.iter()
.cloned()
.map(|(core, adapter)| (adapter, core))
.map(|(core, _, adapter)| (adapter, core))
.collect::<HashMap<_, _>>();

// Transform all exported functions in the module, using the bindings listed
Expand Down Expand Up @@ -45,13 +47,51 @@ pub fn process(module: &mut Module) -> Result<(), Error> {

let meta = cfg.run(module)?;

let section = module
// Store functions found during the anyref transformation onto the auxiliary
// section. These will end up getting used for the full support in the JS
// polyfill, so we'll want the functions to stick around.
let mut aux = module
.customs
.get_typed_mut::<WasmBindgenAux>()
.delete_typed::<WasmBindgenAux>()
.expect("wit custom section should exist");
section.anyref_table = Some(meta.table);
section.anyref_alloc = meta.alloc;
section.anyref_drop_slice = meta.drop_slice;
aux.anyref_table = Some(meta.table);
aux.anyref_alloc = meta.alloc;
aux.anyref_drop_slice = meta.drop_slice;

// Additionally if the heap live count intrinsic was found, if it was
// actually called from any adapter function we want to switch to calling
// the wasm core module intrinsic directly. This'll avoid generating
// incorrect JS glue.
if let Some(id) = meta.live_count {
let section = module
.customs
.get_typed_mut::<NonstandardWitSection>()
.expect("wit custom section should exist");
for (_, adapter) in section.adapters.iter_mut() {
let instrs = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
};
for instr in instrs {
let adapter = match instr.instr {
Instruction::CallAdapter(id) => id,
_ => continue,
};
let import = match aux.import_map.get(&adapter) {
Some(import) => import,
None => continue,
};
match import {
AuxImport::Intrinsic(Intrinsic::AnyrefHeapLiveCount) => {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These nested loops are really calling for some kind of traversal / walker or something with filtering support for adapter instructions...

_ => continue,
}
instr.instr = Instruction::Standard(wit_walrus::Instruction::CallCore(id));
}
}
}

module.customs.add(*aux);

Ok(())
}

Expand Down
21 changes: 0 additions & 21 deletions crates/cli-support/src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,6 @@ pub fn execute(module: &mut Module) -> Result<WasmBindgenDescriptorsSectionId, E
section.execute_exports(module, &mut interpreter)?;
section.execute_closures(module, &mut interpreter)?;

// Delete all descriptor functions and imports from the module now that
// we've executed all of them.
//
// Note though that during this GC pass it's a bit aggressive in that it can
// delete the function table entirely. We don't actually know at this point
// whether we need the function table or not. The bindings generation may
// need to export the table so the JS glue can call functions in it, and
// that's only discovered during binding selection. For now we just add
// synthetic root exports for all tables in the module, and then we delete
// the exports just after GC. This should keep tables like the function
// table alive during GC all the way through to the bindings generation
// where we can either actually export it or gc it out since it's not used.
let mut exported_tables = Vec::new();
for table in module.tables.iter() {
exported_tables.push(module.exports.add("foo", table.id()));
}
walrus::passes::gc::run(module);
for export in exported_tables {
module.exports.delete(export);
}

Ok(module.customs.add(section))
}

Expand Down
137 changes: 49 additions & 88 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub struct Context<'a> {
imports_post: String,
typescript: String,
exposed_globals: Option<HashSet<Cow<'static, str>>>,
required_internal_exports: HashSet<Cow<'static, str>>,
next_export_idx: usize,
config: &'a Bindgen,
pub module: &'a mut Module,
Expand Down Expand Up @@ -87,7 +86,6 @@ impl<'a> Context<'a> {
imports_post: String::new(),
typescript: "/* tslint:disable */\n".to_string(),
exposed_globals: Some(Default::default()),
required_internal_exports: Default::default(),
imported_names: Default::default(),
js_imports: Default::default(),
defined_identifiers: Default::default(),
Expand Down Expand Up @@ -170,10 +168,6 @@ impl<'a> Context<'a> {
}

fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
if !self.required_internal_exports.insert(name.into()) {
return Ok(());
}

if self.module.exports.iter().any(|e| e.name == name) {
return Ok(());
}
Expand All @@ -192,33 +186,13 @@ impl<'a> Context<'a> {
// `__wrap` and such.
self.write_classes()?;

// We're almost done here, so we can delete any internal exports (like
// `__wbindgen_malloc`) if none of our JS glue actually needed it.
self.unexport_unused_internal_exports();

// Initialization is just flat out tricky and not something we
// understand super well. To try to handle various issues that have come
// up we always remove the `start` function if one is present. The JS
// bindings glue then manually calls the start function (if it was
// previously present).
let needs_manual_start = self.unstart_start_function();

// After all we've done, especially
// `unexport_unused_internal_exports()`, we probably have a bunch of
// garbage in the module that's no longer necessary, so delete
// everything that we don't actually need. Afterwards make sure we don't
// try to emit bindings for now-nonexistent imports by pruning our
// `wasm_import_definitions` set.
walrus::passes::gc::run(self.module);
let remaining_imports = self
.module
.imports
.iter()
.map(|i| i.id())
.collect::<HashSet<_>>();
self.wasm_import_definitions
.retain(|id, _| remaining_imports.contains(id));

// Cause any future calls to `should_write_global` to panic, making sure
// we don't ask for items which we can no longer emit.
drop(self.exposed_globals.take().unwrap());
Expand Down Expand Up @@ -739,25 +713,6 @@ impl<'a> Context<'a> {
Ok(())
}

fn unexport_unused_internal_exports(&mut self) {
let mut to_remove = Vec::new();
for export in self.module.exports.iter() {
match export.name.as_str() {
// Otherwise only consider our special exports, which all start
// with the same prefix which hopefully only we're using.
n if n.starts_with("__wbindgen") => {
if !self.required_internal_exports.contains(n) {
to_remove.push(export.id());
}
}
_ => {}
}
}
for id in to_remove {
self.module.exports.delete(id);
}
}

fn expose_drop_ref(&mut self) {
if !self.should_write_global("drop_ref") {
return;
Expand Down Expand Up @@ -1585,29 +1540,34 @@ impl<'a> Context<'a> {
if !self.should_write_global("handle_error") {
return Ok(());
}
self.require_internal_export("__wbindgen_exn_store")?;
let store = self
.aux
.exn_store
.ok_or_else(|| anyhow!("failed to find `__wbindgen_exn_store` intrinsic"))?;
let store = self.export_name_of(store);
match (self.aux.anyref_table, self.aux.anyref_alloc) {
(Some(table), Some(alloc)) => {
let add = self.expose_add_to_anyref_table(table, alloc)?;
self.global(&format!(
"
function handleError(e) {{
const idx = {}(e);
wasm.__wbindgen_exn_store(idx);
wasm.{}(idx);
}}
",
add,
add, store,
));
}
_ => {
self.expose_add_heap_object();
self.global(
self.global(&format!(
"
function handleError(e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
function handleError(e) {{
wasm.{}(addHeapObject(e));
}}
",
);
store,
));
}
}
Ok(())
Expand Down Expand Up @@ -1943,9 +1903,9 @@ impl<'a> Context<'a> {
let kind = match self.aux.export_map.get(&id) {
Some(export) => Kind::Export(export),
None => {
let core = self.wit.implements.iter().find(|pair| pair.1 == id);
let core = self.wit.implements.iter().find(|pair| pair.2 == id);
match core {
Some((core, _)) => Kind::Import(*core),
Some((core, _, _)) => Kind::Import(*core),
None => Kind::Adapter,
}
}
Expand Down Expand Up @@ -2708,35 +2668,22 @@ impl<'a> Context<'a> {

Intrinsic::AnyrefHeapLiveCount => {
assert_eq!(args.len(), 0);
if self.config.anyref {
// Eventually we should add support to the anyref-xform to
// re-write calls to the imported
// `__wbindgen_anyref_heap_live_count` function into calls to
// the exported `__wbindgen_anyref_heap_live_count_impl`
// function, and to un-export that function.
//
// But for now, we just bounce wasm -> js -> wasm because it is
// easy.
self.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?;
"wasm.__wbindgen_anyref_heap_live_count_impl()".into()
} else {
self.expose_global_heap();
prelude.push_str(
"
let free_count = 0;
let next = heap_next;
while (next < heap.length) {
free_count += 1;
next = heap[next];
}
",
);
format!(
"heap.length - free_count - {} - {}",
INITIAL_HEAP_OFFSET,
INITIAL_HEAP_VALUES.len(),
)
}
self.expose_global_heap();
prelude.push_str(
"
let free_count = 0;
let next = heap_next;
while (next < heap.length) {
free_count += 1;
next = heap[next];
}
",
);
format!(
"heap.length - free_count - {} - {}",
INITIAL_HEAP_OFFSET,
INITIAL_HEAP_VALUES.len(),
)
}

Intrinsic::InitAnyrefTable => {
Expand Down Expand Up @@ -2953,15 +2900,29 @@ impl<'a> Context<'a> {
}
});
if let Some(export) = export {
self.required_internal_exports
.insert(export.name.clone().into());
return export.name.clone();
}
let name = format!("__wbindgen_export_{}", self.next_export_idx);
let default_name = format!("__wbindgen_export_{}", self.next_export_idx);
self.next_export_idx += 1;
let name = match id {
walrus::ExportItem::Function(f) => match &self.module.funcs.get(f).name {
Some(s) => to_js_identifier(s),
None => default_name,
},
_ => default_name,
};
self.module.exports.add(&name, id);
self.required_internal_exports.insert(name.clone().into());
return name;

// Not really an exhaustive list, but works for our purposes.
fn to_js_identifier(name: &str) -> String {
name.chars()
.map(|c| match c {
' ' | ':' => '_',
c => c,
})
.collect()
}
}

fn adapter_name(&self, id: AdapterId) -> String {
Expand Down
Loading