From 3e69b108eaec7fbefe294d3199bc630c6f1ba2c2 Mon Sep 17 00:00:00 2001 From: Pauan Date: Tue, 21 Apr 2020 23:40:11 +0200 Subject: [PATCH 1/5] Improving the code generation for catch --- crates/cli-support/src/js/binding.rs | 2492 +++++++++++++------------- crates/cli-support/src/js/mod.rs | 75 +- 2 files changed, 1304 insertions(+), 1263 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 9af2a469d3d..34dd9adcb5b 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -1,1245 +1,1247 @@ -//! Support for actually generating a JS function shim. -//! -//! This `Builder` type is used to generate JS function shims which sit between -//! exported functions, table elements, imports, etc. All function shims -//! generated by `wasm-bindgen` run through this type. - -use crate::js::Context; -use crate::wit::InstructionData; -use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; -use anyhow::{anyhow, bail, Error}; -use walrus::Module; - -/// A one-size-fits-all builder for processing WebIDL bindings and generating -/// JS. -pub struct Builder<'a, 'b> { - /// Parent context used to expose helper functions and such. - pub cx: &'a mut Context<'b>, - /// Whether or not this is building a constructor for a Rust class, and if - /// so what class it's constructing. - constructor: Option, - /// Whether or not this is building a method of a Rust class instance, and - /// whether or not the method consumes `self` or not. - method: Option, - /// Whether or not we're catching exceptions from the main function - /// invocation. Currently only used for imports. - catch: bool, - /// Whether or not we're logging the error coming out of this intrinsic - log_error: bool, -} - -/// Helper struct used to create JS to process all instructions in an adapter -/// function. -pub struct JsBuilder<'a, 'b> { - /// General context for building JS, used to manage intrinsic names, exposed - /// JS functions, etc. - cx: &'a mut Context<'b>, - - /// The "prelude" of the function, or largely just the JS function we've - /// built so far. - prelude: String, - - /// JS code to execute in a `finally` block in case any exceptions happen. - finally: String, - - /// An index used to manage allocation of temporary indices, used to name - /// variables to ensure nothing clashes with anything else. - tmp: usize, - - /// Names or expressions representing the arguments to the adapter. This is - /// use to translate the `arg.get` instruction. - args: Vec, - - /// The wasm interface types "stack". The expressions pushed onto this stack - /// are intended to be *pure*, and if they're not, they should be pushed - /// into the `prelude`, assigned to a variable, and the variable should be - /// pushed to the stack. We're not super principled about this though, so - /// improvements will likely happen here over time. - stack: Vec, -} - -pub struct JsFunction { - pub code: String, - pub ts_sig: String, - pub js_doc: String, - pub ts_arg_tys: Vec, - pub ts_ret_ty: Option, - pub might_be_optional_field: bool, -} - -impl<'a, 'b> Builder<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { - Builder { - log_error: false, - cx, - constructor: None, - method: None, - catch: false, - } - } - - pub fn method(&mut self, consumed: bool) { - self.method = Some(consumed); - } - - pub fn constructor(&mut self, class: &str) { - self.constructor = Some(class.to_string()); - } - - pub fn catch(&mut self, catch: bool) { - self.catch = catch; - } - - pub fn log_error(&mut self, log: bool) { - self.log_error = log; - } - - pub fn process( - &mut self, - adapter: &Adapter, - instructions: &[InstructionData], - explicit_arg_names: &Option>, - ) -> Result { - if self - .cx - .aux - .imports_with_assert_no_shim - .contains(&adapter.id) - { - bail!("generating a shim for something asserted to have no shim"); - } - - let mut params = adapter.params.iter(); - let mut function_args = Vec::new(); - let mut arg_tys = Vec::new(); - - // If this is a method then we're generating this as part of a class - // method, so the leading parameter is the this pointer stored on - // the JS object, so synthesize that here. - let mut js = JsBuilder::new(self.cx); - match self.method { - Some(consumes_self) => { - drop(params.next()); - if js.cx.config.debug { - js.prelude( - "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", - ); - } - if consumes_self { - js.prelude("var ptr = this.ptr;"); - js.prelude("this.ptr = 0;"); - js.args.push("ptr".to_string()); - } else { - js.args.push("this.ptr".to_string()); - } - } - None => {} - } - for (i, param) in params.enumerate() { - let arg = match explicit_arg_names { - Some(list) => list[i].clone(), - None => format!("arg{}", i), - }; - js.args.push(arg.clone()); - function_args.push(arg); - arg_tys.push(param); - } - - // Translate all instructions, the fun loop! - // - // This loop will process all instructions for this adapter function. - // Each instruction will push/pop from the `js.stack` variable, and will - // eventually build up the entire `js.prelude` variable with all the JS - // code that we're going to be adding. Note that the stack at the end - // represents all returned values. - // - // We don't actually manage a literal stack at runtime, but instead we - // act as more of a compiler to generate straight-line code to make it - // more JIT-friendly. The generated code should be equivalent to the - // wasm interface types stack machine, however. - for instr in instructions { - instruction(&mut js, &instr.instr, &mut self.log_error)?; - } - - assert_eq!(js.stack.len(), adapter.results.len()); - match js.stack.len() { - 0 => {} - 1 => { - let val = js.pop(); - js.prelude(&format!("return {};", val)); - } - - // TODO: this should be pretty trivial to support (commented out - // code below), but we should be sure to have a test for this - // somewhere. Currently I don't think it's possible to actually - // exercise this with just Rust code, so let's wait until we get - // some tests to enable this path. - _ => bail!("multi-value returns from adapters not supported yet"), - // _ => { - // let expr = js.stack.join(", "); - // js.stack.truncate(0); - // js.prelude(&format!("return [{}];", expr)); - // } - } - assert!(js.stack.is_empty()); - - // // Remove extraneous typescript args which were synthesized and aren't - // // part of our function shim. - // while self.ts_args.len() > function_args.len() { - // self.ts_args.remove(0); - // } - - let mut code = String::new(); - code.push_str("("); - code.push_str(&function_args.join(", ")); - code.push_str(") {\n"); - - let mut call = js.prelude; - if js.finally.len() != 0 { - call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); - } - - if self.catch { - js.cx.expose_handle_error()?; - call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call); - } - - // Generate a try/catch block in debug mode which handles unexpected and - // unhandled exceptions, typically used on imports. This currently just - // logs what happened, but keeps the exception being thrown to propagate - // elsewhere. - if self.log_error { - js.cx.expose_log_error(); - call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call); - } - - code.push_str(&call); - code.push_str("}"); - - // Rust Structs' fields converted into Getter and Setter functions before - // we decode them from webassembly, finding if a function is a field - // should start from here. Struct fields(Getter) only have one arg, and - // this is the clue we can infer if a function might be a field. - let mut might_be_optional_field = false; - let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature( - &function_args, - &arg_tys, - &adapter.results, - &mut might_be_optional_field, - ); - let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty); - Ok(JsFunction { - code, - ts_sig, - js_doc, - ts_arg_tys, - ts_ret_ty, - might_be_optional_field, - }) - } - - /// Returns the typescript signature of the binding that this has described. - /// This is used to generate all the TypeScript definitions later on. - /// - /// Note that the TypeScript returned here is just the argument list and the - /// return value, it doesn't include the function name in any way. - fn typescript_signature( - &self, - arg_names: &[String], - arg_tys: &[&AdapterType], - result_tys: &[AdapterType], - might_be_optional_field: &mut bool, - ) -> (String, Vec, Option) { - // Build up the typescript signature as well - let mut omittable = true; - let mut ts_args = Vec::new(); - let mut ts_arg_tys = Vec::new(); - for (name, ty) in arg_names.iter().zip(arg_tys).rev() { - // In TypeScript, we can mark optional parameters as omittable - // using the `?` suffix, but only if they're not followed by - // non-omittable parameters. Therefore iterate the parameter list - // in reverse and stop using the `?` suffix for optional params as - // soon as a non-optional parameter is encountered. - let mut arg = name.to_string(); - let mut ts = String::new(); - match ty { - AdapterType::Option(ty) if omittable => { - arg.push_str("?: "); - adapter2ts(ty, &mut ts); - } - ty => { - omittable = false; - arg.push_str(": "); - adapter2ts(ty, &mut ts); - } - } - arg.push_str(&ts); - ts_arg_tys.push(ts); - ts_args.push(arg); - } - ts_args.reverse(); - ts_arg_tys.reverse(); - let mut ts = format!("({})", ts_args.join(", ")); - - // If this function is an optional field's setter, it should have only - // one arg, and omittable should be `true`. - if ts_args.len() == 1 && omittable { - *might_be_optional_field = true; - } - - // Constructors have no listed return type in typescript - let mut ts_ret = None; - if self.constructor.is_none() { - ts.push_str(": "); - let mut ret = String::new(); - match result_tys.len() { - 0 => ret.push_str("void"), - 1 => adapter2ts(&result_tys[0], &mut ret), - _ => ret.push_str("[any]"), - } - ts.push_str(&ret); - ts_ret = Some(ret); - } - return (ts, ts_arg_tys, ts_ret); - } - - /// Returns a helpful JS doc comment which lists types for all parameters - /// and the return value. - fn js_doc_comments( - &self, - arg_names: &[String], - arg_tys: &[&AdapterType], - ts_ret: &Option, - ) -> String { - let mut ret = String::new(); - for (name, ty) in arg_names.iter().zip(arg_tys) { - ret.push_str("@param {"); - adapter2ts(ty, &mut ret); - ret.push_str("} "); - ret.push_str(name); - ret.push_str("\n"); - } - if let Some(ts) = ts_ret { - if ts != "void" { - ret.push_str(&format!("@returns {{{}}}", ts)); - } - } - ret - } -} - -impl<'a, 'b> JsBuilder<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { - JsBuilder { - cx, - args: Vec::new(), - tmp: 0, - finally: String::new(), - prelude: String::new(), - stack: Vec::new(), - } - } - - pub fn arg(&self, idx: u32) -> &str { - &self.args[idx as usize] - } - - pub fn prelude(&mut self, prelude: &str) { - for line in prelude.trim().lines().map(|l| l.trim()) { - if !line.is_empty() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); - } - } - } - - pub fn finally(&mut self, finally: &str) { - for line in finally.trim().lines().map(|l| l.trim()) { - if !line.is_empty() { - self.finally.push_str(line); - self.finally.push_str("\n"); - } - } - } - - pub fn tmp(&mut self) -> usize { - let ret = self.tmp; - self.tmp += 1; - return ret; - } - - fn pop(&mut self) -> String { - self.stack.pop().unwrap() - } - - fn push(&mut self, arg: String) { - self.stack.push(arg); - } - - fn assert_class(&mut self, arg: &str, class: &str) { - self.cx.expose_assert_class(); - self.prelude(&format!("_assertClass({}, {});", arg, class)); - } - - fn assert_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_num(); - self.prelude(&format!("_assertNum({});", arg)); - } - - fn assert_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_bool(); - self.prelude(&format!("_assertBoolean({});", arg)); - } - - fn assert_optional_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_number(arg); - self.prelude("}"); - } - - fn assert_optional_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bool(arg); - self.prelude("}"); - } - - fn assert_not_moved(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.prelude(&format!( - "\ - if ({0}.ptr === 0) {{ - throw new Error('Attempt to use a moved value'); - }} - ", - arg, - )); - } - - fn string_to_memory( - &mut self, - mem: walrus::MemoryId, - malloc: walrus::FunctionId, - realloc: Option, - ) -> Result<(), Error> { - let pass = self.cx.expose_pass_string_to_wasm(mem)?; - let val = self.pop(); - let malloc = self.cx.export_name_of(malloc); - let i = self.tmp(); - let realloc = match realloc { - Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)), - None => String::new(), - }; - self.prelude(&format!( - "var ptr{i} = {f}({0}, wasm.{malloc}{realloc});", - val, - i = i, - f = pass, - malloc = malloc, - realloc = realloc, - )); - self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - self.push(format!("ptr{}", i)); - self.push(format!("len{}", i)); - Ok(()) - } -} - -fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { - // Here first properly aligned nonzero address is chosen to be the - // out-pointer. We use the address for a BigInt64Array sometimes which - // means it needs to be 8-byte aligned. Otherwise valid code is - // unlikely to ever be working around address 8, so this should be a - // safe address to use for returning data through. - let retptr_val = 8; - - match instr { - Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { - let arg = js.arg(*n).to_string(); - js.push(arg); - } - - Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { - panic!("standard call adapter functions should be mapped to our adapters"); - } - - Instruction::Standard(wit_walrus::Instruction::CallCore(_)) - | Instruction::CallExport(_) - | Instruction::CallAdapter(_) - | Instruction::CallTableElement(_) - | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { - let invoc = Invocation::from(instr, js.cx.module)?; - let (params, results) = invoc.params_results(js.cx); - - // Pop off the number of parameters for the function we're calling - let mut args = Vec::new(); - for _ in 0..params { - args.push(js.pop()); - } - args.reverse(); - - // Call the function through an export of the underlying module. - let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; - - // And then figure out how to actually handle where the call - // happens. This is pretty conditional depending on the number of - // return values of the function. - match (invoc.defer(), results) { - (true, 0) => { - js.finally(&format!("{};", call)); - js.stack.extend(args); - } - (true, _) => panic!("deferred calls must have no results"), - (false, 0) => js.prelude(&format!("{};", call)), - (false, n) => { - js.prelude(&format!("var ret = {};", call)); - if n == 1 { - js.push("ret".to_string()); - } else { - for i in 0..n { - js.push(format!("ret[{}]", i)); - } - } - } - } - } - - Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { - let val = js.pop(); - js.assert_number(&val); - js.push(val); - } - - // When converting to a JS number we need to specially handle the `u32` - // case because if the high bit is set then it comes out as a negative - // number, but we want to switch that to an unsigned representation. - Instruction::Standard(wit_walrus::Instruction::WasmToInt { - trap: false, - output, - .. - }) => { - let val = js.pop(); - match output { - wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), - _ => js.push(val), - } - } - - Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) - | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { - bail!("trapping wasm-to-int and int-to-wasm instructions not supported") - } - - Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { - let len = js.pop(); - let ptr = js.pop(); - let get = js.cx.expose_get_string_from_wasm(*mem)?; - js.push(format!("{}({}, {})", get, ptr, len)); - } - - Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { - js.string_to_memory(*mem, *malloc, None)?; - } - - Instruction::StringToMemory { - mem, - malloc, - realloc, - } => { - js.string_to_memory(*mem, *malloc, *realloc)?; - } - - Instruction::Retptr => js.stack.push(retptr_val.to_string()), - - Instruction::StoreRetptr { ty, offset, mem } => { - let (mem, size) = match ty { - AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), - AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), - AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), - other => bail!("invalid aggregate return type {:?}", other), - }; - // Note that we always assume the return pointer is argument 0, - // which is currently the case for LLVM. - let val = js.pop(); - let expr = format!( - "{}()[{} / {} + {}] = {};", - mem, - js.arg(0), - size, - offset, - val, - ); - js.prelude(&expr); - } - - Instruction::LoadRetptr { ty, offset, mem } => { - let (mem, size) = match ty { - AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), - AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), - AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), - other => bail!("invalid aggregate return type {:?}", other), - }; - // If we're loading from the return pointer then we must have pushed - // it earlier, and we always push the same value, so load that value - // here - let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); - js.prelude(&format!("var r{} = {};", offset, expr)); - js.push(format!("r{}", offset)); - } - - Instruction::I32FromBool => { - let val = js.pop(); - js.assert_bool(&val); - // JS will already coerce booleans into numbers for us - js.push(val); - } - - Instruction::I32FromStringFirstChar => { - let val = js.pop(); - js.push(format!("{}.codePointAt(0)", val)); - } - - Instruction::I32FromAnyrefOwned => { - js.cx.expose_add_heap_object(); - let val = js.pop(); - js.push(format!("addHeapObject({})", val)); - } - - Instruction::I32FromAnyrefBorrow => { - js.cx.expose_borrowed_objects(); - js.cx.expose_global_stack_pointer(); - let val = js.pop(); - js.push(format!("addBorrowedObject({})", val)); - js.finally("heap[stack_pointer++] = undefined;"); - } - - Instruction::I32FromAnyrefRustOwned { class } => { - let val = js.pop(); - js.assert_class(&val, &class); - js.assert_not_moved(&val); - let i = js.tmp(); - js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); - js.prelude(&format!("{}.ptr = 0;", val)); - js.push(format!("ptr{}", i)); - } - - Instruction::I32FromAnyrefRustBorrow { class } => { - let val = js.pop(); - js.assert_class(&val, &class); - js.assert_not_moved(&val); - js.push(format!("{}.ptr", val)); - } - - Instruction::I32FromOptionRust { class } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - let i = js.tmp(); - js.prelude(&format!("let ptr{} = 0;", i)); - js.prelude(&format!("if (!isLikeNone({0})) {{", val)); - js.assert_class(&val, class); - js.assert_not_moved(&val); - js.prelude(&format!("ptr{} = {}.ptr;", i, val)); - js.prelude(&format!("{}.ptr = 0;", val)); - js.prelude("}"); - js.push(format!("ptr{}", i)); - } - - Instruction::I32Split64 { signed } => { - let val = js.pop(); - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - js.prelude(&format!( - " - {f}[0] = {val}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - val = val, - )); - js.push(format!("low{}", i)); - js.push(format!("high{}", i)); - } - - Instruction::I32SplitOption64 { signed } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - js.prelude(&format!( - "\ - {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - val = val, - )); - js.push(format!("!isLikeNone({0})", val)); - js.push(format!("low{}", i)); - js.push(format!("high{}", i)); - } - - Instruction::I32FromOptionAnyref { table_and_alloc } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - match table_and_alloc { - Some((table, alloc)) => { - let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?; - js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); - } - None => { - js.cx.expose_add_heap_object(); - js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); - } - } - } - - Instruction::I32FromOptionU32Sentinel => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); - } - - Instruction::I32FromOptionBool => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_bool(&val); - js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); - } - - Instruction::I32FromOptionChar => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.push(format!( - "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", - val - )); - } - - Instruction::I32FromOptionEnum { hole } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); - } - - Instruction::FromOptionNative { .. } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("!isLikeNone({0})", val)); - js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); - } - - Instruction::VectorToMemory { kind, malloc, mem } => { - let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - let malloc = js.cx.export_name_of(*malloc); - let i = js.tmp(); - js.prelude(&format!( - "var ptr{i} = {f}({0}, wasm.{malloc});", - val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::OptionString { - mem, - malloc, - realloc, - } => { - let func = js.cx.expose_pass_string_to_wasm(*mem)?; - js.cx.expose_is_like_none(); - let i = js.tmp(); - let malloc = js.cx.export_name_of(*malloc); - let val = js.pop(); - let realloc = match realloc { - Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)), - None => String::new(), - }; - js.prelude(&format!( - "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});", - val, - i = i, - f = func, - malloc = malloc, - realloc = realloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::OptionVector { kind, mem, malloc } => { - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - js.cx.expose_is_like_none(); - let i = js.tmp(); - let malloc = js.cx.export_name_of(*malloc); - let val = js.pop(); - js.prelude(&format!( - "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", - val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::MutableSliceToMemory { - kind, - malloc, - mem, - free, - } => { - // First up, pass the JS value into wasm, getting out a pointer and - // a length. These two pointer/length values get pushed onto the - // value stack. - let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - let malloc = js.cx.export_name_of(*malloc); - let i = js.tmp(); - js.prelude(&format!( - "var ptr{i} = {f}({val}, wasm.{malloc});", - val = val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - - // Next we set up a `finally` clause which will both update the - // original mutable slice with any modifications, and then free the - // Rust-backed memory. - let free = js.cx.export_name_of(*free); - let get = js.cx.memview_function(*kind, *mem); - js.finally(&format!( - " - {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); - wasm.{free}(ptr{i}, len{i} * {size}); - ", - val = val, - get = get, - free = free, - size = kind.size(), - i = i, - )); - } - - Instruction::BoolFromI32 => { - let val = js.pop(); - js.push(format!("{} !== 0", val)); - } - - Instruction::AnyrefLoadOwned => { - js.cx.expose_take_object(); - let val = js.pop(); - js.push(format!("takeObject({})", val)); - } - - Instruction::StringFromChar => { - let val = js.pop(); - js.push(format!("String.fromCodePoint({})", val)); - } - - Instruction::I64FromLoHi { signed } => { - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - let high = js.pop(); - let low = js.pop(); - js.prelude(&format!( - "\ - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {f}[0]; - ", - low = low, - high = high, - f = f, - i = i, - )); - js.push(format!("n{}", i)) - } - - Instruction::RustFromI32 { class } => { - js.cx.require_class_wrap(class); - let val = js.pop(); - js.push(format!("{}.__wrap({})", class, val)); - } - - Instruction::OptionRustFromI32 { class } => { - js.cx.require_class_wrap(class); - let val = js.pop(); - js.push(format!( - "{0} === 0 ? undefined : {1}.__wrap({0})", - val, class, - )) - } - - Instruction::CachedStringLoad { - owned, - optional: _, - mem, - free, - } => { - let len = js.pop(); - let ptr = js.pop(); - let tmp = js.tmp(); - - let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; - - js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); - - if *owned { - let free = js.cx.export_name_of(*free); - js.prelude(&format!( - "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", - free, - ptr = ptr, - len = len, - )); - } - - js.push(format!("v{}", tmp)); - } - - Instruction::TableGet => { - let val = js.pop(); - js.cx.expose_get_object(); - js.push(format!("getObject({})", val)); - } - - Instruction::StackClosure { - adapter, - nargs, - mutable, - } => { - let i = js.tmp(); - let b = js.pop(); - let a = js.pop(); - js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); - let args = (0..*nargs) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", "); - let wrapper = js.cx.adapter_name(*adapter); - if *mutable { - // Mutable closures need protection against being called - // recursively, so ensure that we clear out one of the - // internal pointers while it's being invoked. - js.prelude(&format!( - "var cb{i} = ({args}) => {{ - const a = state{i}.a; - state{i}.a = 0; - try {{ - return {name}(a, state{i}.b, {args}); - }} finally {{ - state{i}.a = a; - }} - }};", - i = i, - args = args, - name = wrapper, - )); - } else { - js.prelude(&format!( - "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", - i = i, - args = args, - wrapper = wrapper, - )); - } - - // Make sure to null out our internal pointers when we return - // back to Rust to ensure that any lingering references to the - // closure will fail immediately due to null pointers passed in - // to Rust. - js.finally(&format!("state{}.a = state{0}.b = 0;", i)); - js.push(format!("cb{}", i)); - } - - Instruction::VectorLoad { kind, mem, free } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - let i = js.tmp(); - let free = js.cx.export_name_of(*free); - js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!( - "wasm.{}({}, {} * {});", - free, - ptr, - len, - kind.size() - )); - js.push(format!("v{}", i)) - } - - Instruction::OptionVectorLoad { kind, mem, free } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - let i = js.tmp(); - let free = js.cx.export_name_of(*free); - js.prelude(&format!("let v{};", i)); - js.prelude(&format!("if ({} !== 0) {{", ptr)); - js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!( - "wasm.{}({}, {} * {});", - free, - ptr, - len, - kind.size() - )); - js.prelude("}"); - js.push(format!("v{}", i)); - } - - Instruction::View { kind, mem } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); - } - - Instruction::OptionView { kind, mem } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - js.push(format!( - "{ptr} === 0 ? undefined : {f}({ptr}, {len})", - ptr = ptr, - len = len, - f = f - )); - } - - Instruction::OptionU32Sentinel => { - let val = js.pop(); - js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); - } - - Instruction::ToOptionNative { ty: _, signed } => { - let val = js.pop(); - let present = js.pop(); - js.push(format!( - "{} === 0 ? undefined : {}{}", - present, - val, - if *signed { "" } else { " >>> 0" }, - )); - } - - Instruction::OptionBoolFromI32 => { - let val = js.pop(); - js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); - } - - Instruction::OptionCharFromI32 => { - let val = js.pop(); - js.push(format!( - "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", - val, - )); - } - - Instruction::OptionEnumFromI32 { hole } => { - let val = js.pop(); - js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); - } - - Instruction::Option64FromI32 { signed } => { - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - let high = js.pop(); - let low = js.pop(); - let present = js.pop(); - js.prelude(&format!( - " - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {present} === 0 ? undefined : {f}[0]; - ", - present = present, - low = low, - high = high, - f = f, - i = i, - )); - js.push(format!("n{}", i)); - } - } - Ok(()) -} - -enum Invocation { - Core { id: walrus::FunctionId, defer: bool }, - Adapter(AdapterId), -} - -impl Invocation { - fn from(instr: &Instruction, module: &Module) -> Result { - use Instruction::*; - Ok(match instr { - Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { - id: *f, - defer: false, - }, - - Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { - id: *f, - defer: true, - }, - - CallExport(e) => match module.exports.get(*e).item { - walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, - _ => panic!("can only call exported function"), - }, - - // The function table never changes right now, so we can statically - // look up the desired function. - CallTableElement(idx) => { - let table = module - .tables - .main_function_table()? - .ok_or_else(|| anyhow!("no function table found"))?; - let functions = match &module.tables.get(table).kind { - walrus::TableKind::Function(f) => f, - _ => bail!("should have found a function table"), - }; - let id = functions - .elements - .get(*idx as usize) - .and_then(|id| *id) - .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; - Invocation::Core { id, defer: false } - } - - CallAdapter(id) => Invocation::Adapter(*id), - - // this function is only called for the above instructions - _ => unreachable!(), - }) - } - - fn params_results(&self, cx: &Context) -> (usize, usize) { - match self { - Invocation::Core { id, .. } => { - let ty = cx.module.funcs.get(*id).ty(); - let ty = cx.module.types.get(ty); - (ty.params().len(), ty.results().len()) - } - Invocation::Adapter(id) => { - let adapter = &cx.wit.adapters[id]; - (adapter.params.len(), adapter.results.len()) - } - } - } - - fn invoke( - &self, - cx: &mut Context, - args: &[String], - prelude: &mut String, - log_error: &mut bool, - ) -> Result { - match self { - Invocation::Core { id, .. } => { - let name = cx.export_name_of(*id); - Ok(format!("wasm.{}({})", name, args.join(", "))) - } - Invocation::Adapter(id) => { - let adapter = &cx.wit.adapters[id]; - let kind = match adapter.kind { - AdapterKind::Import { kind, .. } => kind, - AdapterKind::Local { .. } => { - bail!("adapter-to-adapter calls not supported yet"); - } - }; - let import = &cx.aux.import_map[id]; - let variadic = cx.aux.imports_with_variadic.contains(id); - if cx.import_never_log_error(import) { - *log_error = false; - } - cx.invoke_import(import, kind, args, variadic, prelude) - } - } - } - - fn defer(&self) -> bool { - match self { - Invocation::Core { defer, .. } => *defer, - _ => false, - } - } -} - -fn adapter2ts(ty: &AdapterType, dst: &mut String) { - match ty { - AdapterType::I32 - | AdapterType::S8 - | AdapterType::S16 - | AdapterType::S32 - | AdapterType::U8 - | AdapterType::U16 - | AdapterType::U32 - | AdapterType::F32 - | AdapterType::F64 => dst.push_str("number"), - AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"), - AdapterType::String => dst.push_str("string"), - AdapterType::Anyref => dst.push_str("any"), - AdapterType::Bool => dst.push_str("boolean"), - AdapterType::Vector(kind) => dst.push_str(kind.js_ty()), - AdapterType::Option(ty) => { - adapter2ts(ty, dst); - dst.push_str(" | undefined"); - } - AdapterType::NamedAnyref(name) => dst.push_str(name), - AdapterType::Struct(name) => dst.push_str(name), - AdapterType::Function => dst.push_str("any"), - } -} +//! Support for actually generating a JS function shim. +//! +//! This `Builder` type is used to generate JS function shims which sit between +//! exported functions, table elements, imports, etc. All function shims +//! generated by `wasm-bindgen` run through this type. + +use crate::js::Context; +use crate::wit::InstructionData; +use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use anyhow::{anyhow, bail, Error}; +use walrus::Module; + +/// A one-size-fits-all builder for processing WebIDL bindings and generating +/// JS. +pub struct Builder<'a, 'b> { + /// Parent context used to expose helper functions and such. + pub cx: &'a mut Context<'b>, + /// Whether or not this is building a constructor for a Rust class, and if + /// so what class it's constructing. + constructor: Option, + /// Whether or not this is building a method of a Rust class instance, and + /// whether or not the method consumes `self` or not. + method: Option, + /// Whether or not we're catching exceptions from the main function + /// invocation. Currently only used for imports. + catch: bool, + /// Whether or not we're logging the error coming out of this intrinsic + log_error: bool, +} + +/// Helper struct used to create JS to process all instructions in an adapter +/// function. +pub struct JsBuilder<'a, 'b> { + /// General context for building JS, used to manage intrinsic names, exposed + /// JS functions, etc. + cx: &'a mut Context<'b>, + + /// The "prelude" of the function, or largely just the JS function we've + /// built so far. + prelude: String, + + /// JS code to execute in a `finally` block in case any exceptions happen. + finally: String, + + /// An index used to manage allocation of temporary indices, used to name + /// variables to ensure nothing clashes with anything else. + tmp: usize, + + /// Names or expressions representing the arguments to the adapter. This is + /// use to translate the `arg.get` instruction. + args: Vec, + + /// The wasm interface types "stack". The expressions pushed onto this stack + /// are intended to be *pure*, and if they're not, they should be pushed + /// into the `prelude`, assigned to a variable, and the variable should be + /// pushed to the stack. We're not super principled about this though, so + /// improvements will likely happen here over time. + stack: Vec, +} + +pub struct JsFunction { + pub code: String, + pub ts_sig: String, + pub js_doc: String, + pub ts_arg_tys: Vec, + pub ts_ret_ty: Option, + pub might_be_optional_field: bool, + pub catch: bool, + pub log_error: bool, +} + +impl<'a, 'b> Builder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { + Builder { + log_error: false, + cx, + constructor: None, + method: None, + catch: false, + } + } + + pub fn method(&mut self, consumed: bool) { + self.method = Some(consumed); + } + + pub fn constructor(&mut self, class: &str) { + self.constructor = Some(class.to_string()); + } + + pub fn catch(&mut self, catch: bool) { + self.catch = catch; + } + + pub fn log_error(&mut self, log: bool) { + self.log_error = log; + } + + pub fn process( + &mut self, + adapter: &Adapter, + instructions: &[InstructionData], + explicit_arg_names: &Option>, + ) -> Result { + if self + .cx + .aux + .imports_with_assert_no_shim + .contains(&adapter.id) + { + bail!("generating a shim for something asserted to have no shim"); + } + + let mut params = adapter.params.iter(); + let mut function_args = Vec::new(); + let mut arg_tys = Vec::new(); + + // If this is a method then we're generating this as part of a class + // method, so the leading parameter is the this pointer stored on + // the JS object, so synthesize that here. + let mut js = JsBuilder::new(self.cx); + match self.method { + Some(consumes_self) => { + drop(params.next()); + if js.cx.config.debug { + js.prelude( + "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", + ); + } + if consumes_self { + js.prelude("var ptr = this.ptr;"); + js.prelude("this.ptr = 0;"); + js.args.push("ptr".to_string()); + } else { + js.args.push("this.ptr".to_string()); + } + } + None => {} + } + for (i, param) in params.enumerate() { + let arg = match explicit_arg_names { + Some(list) => list[i].clone(), + None => format!("arg{}", i), + }; + js.args.push(arg.clone()); + function_args.push(arg); + arg_tys.push(param); + } + + // Translate all instructions, the fun loop! + // + // This loop will process all instructions for this adapter function. + // Each instruction will push/pop from the `js.stack` variable, and will + // eventually build up the entire `js.prelude` variable with all the JS + // code that we're going to be adding. Note that the stack at the end + // represents all returned values. + // + // We don't actually manage a literal stack at runtime, but instead we + // act as more of a compiler to generate straight-line code to make it + // more JIT-friendly. The generated code should be equivalent to the + // wasm interface types stack machine, however. + for instr in instructions { + instruction(&mut js, &instr.instr, &mut self.log_error)?; + } + + assert_eq!(js.stack.len(), adapter.results.len()); + match js.stack.len() { + 0 => {} + 1 => { + let val = js.pop(); + js.prelude(&format!("return {};", val)); + } + + // TODO: this should be pretty trivial to support (commented out + // code below), but we should be sure to have a test for this + // somewhere. Currently I don't think it's possible to actually + // exercise this with just Rust code, so let's wait until we get + // some tests to enable this path. + _ => bail!("multi-value returns from adapters not supported yet"), + // _ => { + // let expr = js.stack.join(", "); + // js.stack.truncate(0); + // js.prelude(&format!("return [{}];", expr)); + // } + } + assert!(js.stack.is_empty()); + + // // Remove extraneous typescript args which were synthesized and aren't + // // part of our function shim. + // while self.ts_args.len() > function_args.len() { + // self.ts_args.remove(0); + // } + + let mut code = String::new(); + code.push_str("("); + code.push_str(&function_args.join(", ")); + code.push_str(") {\n"); + + let mut call = js.prelude; + if js.finally.len() != 0 { + call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); + } + + if self.catch { + js.cx.expose_handle_error()?; + } + + // Generate a try/catch block in debug mode which handles unexpected and + // unhandled exceptions, typically used on imports. This currently just + // logs what happened, but keeps the exception being thrown to propagate + // elsewhere. + if self.log_error { + js.cx.expose_log_error(); + } + + code.push_str(&call); + code.push_str("}"); + + // Rust Structs' fields converted into Getter and Setter functions before + // we decode them from webassembly, finding if a function is a field + // should start from here. Struct fields(Getter) only have one arg, and + // this is the clue we can infer if a function might be a field. + let mut might_be_optional_field = false; + let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature( + &function_args, + &arg_tys, + &adapter.results, + &mut might_be_optional_field, + ); + let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty); + Ok(JsFunction { + code, + ts_sig, + js_doc, + ts_arg_tys, + ts_ret_ty, + might_be_optional_field, + catch: self.catch, + log_error: self.log_error, + }) + } + + /// Returns the typescript signature of the binding that this has described. + /// This is used to generate all the TypeScript definitions later on. + /// + /// Note that the TypeScript returned here is just the argument list and the + /// return value, it doesn't include the function name in any way. + fn typescript_signature( + &self, + arg_names: &[String], + arg_tys: &[&AdapterType], + result_tys: &[AdapterType], + might_be_optional_field: &mut bool, + ) -> (String, Vec, Option) { + // Build up the typescript signature as well + let mut omittable = true; + let mut ts_args = Vec::new(); + let mut ts_arg_tys = Vec::new(); + for (name, ty) in arg_names.iter().zip(arg_tys).rev() { + // In TypeScript, we can mark optional parameters as omittable + // using the `?` suffix, but only if they're not followed by + // non-omittable parameters. Therefore iterate the parameter list + // in reverse and stop using the `?` suffix for optional params as + // soon as a non-optional parameter is encountered. + let mut arg = name.to_string(); + let mut ts = String::new(); + match ty { + AdapterType::Option(ty) if omittable => { + arg.push_str("?: "); + adapter2ts(ty, &mut ts); + } + ty => { + omittable = false; + arg.push_str(": "); + adapter2ts(ty, &mut ts); + } + } + arg.push_str(&ts); + ts_arg_tys.push(ts); + ts_args.push(arg); + } + ts_args.reverse(); + ts_arg_tys.reverse(); + let mut ts = format!("({})", ts_args.join(", ")); + + // If this function is an optional field's setter, it should have only + // one arg, and omittable should be `true`. + if ts_args.len() == 1 && omittable { + *might_be_optional_field = true; + } + + // Constructors have no listed return type in typescript + let mut ts_ret = None; + if self.constructor.is_none() { + ts.push_str(": "); + let mut ret = String::new(); + match result_tys.len() { + 0 => ret.push_str("void"), + 1 => adapter2ts(&result_tys[0], &mut ret), + _ => ret.push_str("[any]"), + } + ts.push_str(&ret); + ts_ret = Some(ret); + } + return (ts, ts_arg_tys, ts_ret); + } + + /// Returns a helpful JS doc comment which lists types for all parameters + /// and the return value. + fn js_doc_comments( + &self, + arg_names: &[String], + arg_tys: &[&AdapterType], + ts_ret: &Option, + ) -> String { + let mut ret = String::new(); + for (name, ty) in arg_names.iter().zip(arg_tys) { + ret.push_str("@param {"); + adapter2ts(ty, &mut ret); + ret.push_str("} "); + ret.push_str(name); + ret.push_str("\n"); + } + if let Some(ts) = ts_ret { + if ts != "void" { + ret.push_str(&format!("@returns {{{}}}", ts)); + } + } + ret + } +} + +impl<'a, 'b> JsBuilder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { + JsBuilder { + cx, + args: Vec::new(), + tmp: 0, + finally: String::new(), + prelude: String::new(), + stack: Vec::new(), + } + } + + pub fn arg(&self, idx: u32) -> &str { + &self.args[idx as usize] + } + + pub fn prelude(&mut self, prelude: &str) { + for line in prelude.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.prelude.push_str(line); + self.prelude.push_str("\n"); + } + } + } + + pub fn finally(&mut self, finally: &str) { + for line in finally.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.finally.push_str(line); + self.finally.push_str("\n"); + } + } + } + + pub fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + return ret; + } + + fn pop(&mut self) -> String { + self.stack.pop().unwrap() + } + + fn push(&mut self, arg: String) { + self.stack.push(arg); + } + + fn assert_class(&mut self, arg: &str, class: &str) { + self.cx.expose_assert_class(); + self.prelude(&format!("_assertClass({}, {});", arg, class)); + } + + fn assert_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_num(); + self.prelude(&format!("_assertNum({});", arg)); + } + + fn assert_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_bool(); + self.prelude(&format!("_assertBoolean({});", arg)); + } + + fn assert_optional_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_number(arg); + self.prelude("}"); + } + + fn assert_optional_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_bool(arg); + self.prelude("}"); + } + + fn assert_not_moved(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.prelude(&format!( + "\ + if ({0}.ptr === 0) {{ + throw new Error('Attempt to use a moved value'); + }} + ", + arg, + )); + } + + fn string_to_memory( + &mut self, + mem: walrus::MemoryId, + malloc: walrus::FunctionId, + realloc: Option, + ) -> Result<(), Error> { + let pass = self.cx.expose_pass_string_to_wasm(mem)?; + let val = self.pop(); + let malloc = self.cx.export_name_of(malloc); + let i = self.tmp(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)), + None => String::new(), + }; + self.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = pass, + malloc = malloc, + realloc = realloc, + )); + self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + self.push(format!("ptr{}", i)); + self.push(format!("len{}", i)); + Ok(()) + } +} + +fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { + // Here first properly aligned nonzero address is chosen to be the + // out-pointer. We use the address for a BigInt64Array sometimes which + // means it needs to be 8-byte aligned. Otherwise valid code is + // unlikely to ever be working around address 8, so this should be a + // safe address to use for returning data through. + let retptr_val = 8; + + match instr { + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + let arg = js.arg(*n).to_string(); + js.push(arg); + } + + Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + panic!("standard call adapter functions should be mapped to our adapters"); + } + + Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::CallExport(_) + | Instruction::CallAdapter(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { + let invoc = Invocation::from(instr, js.cx.module)?; + let (params, results) = invoc.params_results(js.cx); + + // Pop off the number of parameters for the function we're calling + let mut args = Vec::new(); + for _ in 0..params { + args.push(js.pop()); + } + args.reverse(); + + // Call the function through an export of the underlying module. + let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; + + // And then figure out how to actually handle where the call + // happens. This is pretty conditional depending on the number of + // return values of the function. + match (invoc.defer(), results) { + (true, 0) => { + js.finally(&format!("{};", call)); + js.stack.extend(args); + } + (true, _) => panic!("deferred calls must have no results"), + (false, 0) => js.prelude(&format!("{};", call)), + (false, n) => { + js.prelude(&format!("var ret = {};", call)); + if n == 1 { + js.push("ret".to_string()); + } else { + for i in 0..n { + js.push(format!("ret[{}]", i)); + } + } + } + } + } + + Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + let val = js.pop(); + js.assert_number(&val); + js.push(val); + } + + // When converting to a JS number we need to specially handle the `u32` + // case because if the high bit is set then it comes out as a negative + // number, but we want to switch that to an unsigned representation. + Instruction::Standard(wit_walrus::Instruction::WasmToInt { + trap: false, + output, + .. + }) => { + let val = js.pop(); + match output { + wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), + _ => js.push(val), + } + } + + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) + | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { + bail!("trapping wasm-to-int and int-to-wasm instructions not supported") + } + + Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { + let len = js.pop(); + let ptr = js.pop(); + let get = js.cx.expose_get_string_from_wasm(*mem)?; + js.push(format!("{}({}, {})", get, ptr, len)); + } + + Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { + js.string_to_memory(*mem, *malloc, None)?; + } + + Instruction::StringToMemory { + mem, + malloc, + realloc, + } => { + js.string_to_memory(*mem, *malloc, *realloc)?; + } + + Instruction::Retptr => js.stack.push(retptr_val.to_string()), + + Instruction::StoreRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // Note that we always assume the return pointer is argument 0, + // which is currently the case for LLVM. + let val = js.pop(); + let expr = format!( + "{}()[{} / {} + {}] = {};", + mem, + js.arg(0), + size, + offset, + val, + ); + js.prelude(&expr); + } + + Instruction::LoadRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // If we're loading from the return pointer then we must have pushed + // it earlier, and we always push the same value, so load that value + // here + let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); + js.prelude(&format!("var r{} = {};", offset, expr)); + js.push(format!("r{}", offset)); + } + + Instruction::I32FromBool => { + let val = js.pop(); + js.assert_bool(&val); + // JS will already coerce booleans into numbers for us + js.push(val); + } + + Instruction::I32FromStringFirstChar => { + let val = js.pop(); + js.push(format!("{}.codePointAt(0)", val)); + } + + Instruction::I32FromAnyrefOwned => { + js.cx.expose_add_heap_object(); + let val = js.pop(); + js.push(format!("addHeapObject({})", val)); + } + + Instruction::I32FromAnyrefBorrow => { + js.cx.expose_borrowed_objects(); + js.cx.expose_global_stack_pointer(); + let val = js.pop(); + js.push(format!("addBorrowedObject({})", val)); + js.finally("heap[stack_pointer++] = undefined;"); + } + + Instruction::I32FromAnyrefRustOwned { class } => { + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + let i = js.tmp(); + js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.push(format!("ptr{}", i)); + } + + Instruction::I32FromAnyrefRustBorrow { class } => { + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + js.push(format!("{}.ptr", val)); + } + + Instruction::I32FromOptionRust { class } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + let i = js.tmp(); + js.prelude(&format!("let ptr{} = 0;", i)); + js.prelude(&format!("if (!isLikeNone({0})) {{", val)); + js.assert_class(&val, class); + js.assert_not_moved(&val); + js.prelude(&format!("ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.prelude("}"); + js.push(format!("ptr{}", i)); + } + + Instruction::I32Split64 { signed } => { + let val = js.pop(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + " + {f}[0] = {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32SplitOption64 { signed } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + "\ + {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32FromOptionAnyref { table_and_alloc } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + match table_and_alloc { + Some((table, alloc)) => { + let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?; + js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); + } + None => { + js.cx.expose_add_heap_object(); + js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + } + } + } + + Instruction::I32FromOptionU32Sentinel => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); + } + + Instruction::I32FromOptionBool => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_bool(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); + } + + Instruction::I32FromOptionChar => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.push(format!( + "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", + val + )); + } + + Instruction::I32FromOptionEnum { hole } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); + } + + Instruction::FromOptionNative { .. } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); + } + + Instruction::VectorToMemory { kind, malloc, mem } => { + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionString { + mem, + malloc, + realloc, + } => { + let func = js.cx.expose_pass_string_to_wasm(*mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)), + None => String::new(), + }; + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = func, + malloc = malloc, + realloc = realloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionVector { kind, mem, malloc } => { + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::MutableSliceToMemory { + kind, + malloc, + mem, + free, + } => { + // First up, pass the JS value into wasm, getting out a pointer and + // a length. These two pointer/length values get pushed onto the + // value stack. + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({val}, wasm.{malloc});", + val = val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + + // Next we set up a `finally` clause which will both update the + // original mutable slice with any modifications, and then free the + // Rust-backed memory. + let free = js.cx.export_name_of(*free); + let get = js.cx.memview_function(*kind, *mem); + js.finally(&format!( + " + {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); + wasm.{free}(ptr{i}, len{i} * {size}); + ", + val = val, + get = get, + free = free, + size = kind.size(), + i = i, + )); + } + + Instruction::BoolFromI32 => { + let val = js.pop(); + js.push(format!("{} !== 0", val)); + } + + Instruction::AnyrefLoadOwned => { + js.cx.expose_take_object(); + let val = js.pop(); + js.push(format!("takeObject({})", val)); + } + + Instruction::StringFromChar => { + let val = js.pop(); + js.push(format!("String.fromCodePoint({})", val)); + } + + Instruction::I64FromLoHi { signed } => { + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + js.prelude(&format!( + "\ + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {f}[0]; + ", + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)) + } + + Instruction::RustFromI32 { class } => { + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!("{}.__wrap({})", class, val)); + } + + Instruction::OptionRustFromI32 { class } => { + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!( + "{0} === 0 ? undefined : {1}.__wrap({0})", + val, class, + )) + } + + Instruction::CachedStringLoad { + owned, + optional: _, + mem, + free, + } => { + let len = js.pop(); + let ptr = js.pop(); + let tmp = js.tmp(); + + let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; + + js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); + + if *owned { + let free = js.cx.export_name_of(*free); + js.prelude(&format!( + "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", + free, + ptr = ptr, + len = len, + )); + } + + js.push(format!("v{}", tmp)); + } + + Instruction::TableGet => { + let val = js.pop(); + js.cx.expose_get_object(); + js.push(format!("getObject({})", val)); + } + + Instruction::StackClosure { + adapter, + nargs, + mutable, + } => { + let i = js.tmp(); + let b = js.pop(); + let a = js.pop(); + js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); + let args = (0..*nargs) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + let wrapper = js.cx.adapter_name(*adapter); + if *mutable { + // Mutable closures need protection against being called + // recursively, so ensure that we clear out one of the + // internal pointers while it's being invoked. + js.prelude(&format!( + "var cb{i} = ({args}) => {{ + const a = state{i}.a; + state{i}.a = 0; + try {{ + return {name}(a, state{i}.b, {args}); + }} finally {{ + state{i}.a = a; + }} + }};", + i = i, + args = args, + name = wrapper, + )); + } else { + js.prelude(&format!( + "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", + i = i, + args = args, + wrapper = wrapper, + )); + } + + // Make sure to null out our internal pointers when we return + // back to Rust to ensure that any lingering references to the + // closure will fail immediately due to null pointers passed in + // to Rust. + js.finally(&format!("state{}.a = state{0}.b = 0;", i)); + js.push(format!("cb{}", i)); + } + + Instruction::VectorLoad { kind, mem, free } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.push(format!("v{}", i)) + } + + Instruction::OptionVectorLoad { kind, mem, free } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("let v{};", i)); + js.prelude(&format!("if ({} !== 0) {{", ptr)); + js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.prelude("}"); + js.push(format!("v{}", i)); + } + + Instruction::View { kind, mem } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); + } + + Instruction::OptionView { kind, mem } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!( + "{ptr} === 0 ? undefined : {f}({ptr}, {len})", + ptr = ptr, + len = len, + f = f + )); + } + + Instruction::OptionU32Sentinel => { + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); + } + + Instruction::ToOptionNative { ty: _, signed } => { + let val = js.pop(); + let present = js.pop(); + js.push(format!( + "{} === 0 ? undefined : {}{}", + present, + val, + if *signed { "" } else { " >>> 0" }, + )); + } + + Instruction::OptionBoolFromI32 => { + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); + } + + Instruction::OptionCharFromI32 => { + let val = js.pop(); + js.push(format!( + "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", + val, + )); + } + + Instruction::OptionEnumFromI32 { hole } => { + let val = js.pop(); + js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); + } + + Instruction::Option64FromI32 { signed } => { + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + let present = js.pop(); + js.prelude(&format!( + " + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {present} === 0 ? undefined : {f}[0]; + ", + present = present, + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)); + } + } + Ok(()) +} + +enum Invocation { + Core { id: walrus::FunctionId, defer: bool }, + Adapter(AdapterId), +} + +impl Invocation { + fn from(instr: &Instruction, module: &Module) -> Result { + use Instruction::*; + Ok(match instr { + Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { + id: *f, + defer: false, + }, + + Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { + id: *f, + defer: true, + }, + + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, + _ => panic!("can only call exported function"), + }, + + // The function table never changes right now, so we can statically + // look up the desired function. + CallTableElement(idx) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => bail!("should have found a function table"), + }; + let id = functions + .elements + .get(*idx as usize) + .and_then(|id| *id) + .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; + Invocation::Core { id, defer: false } + } + + CallAdapter(id) => Invocation::Adapter(*id), + + // this function is only called for the above instructions + _ => unreachable!(), + }) + } + + fn params_results(&self, cx: &Context) -> (usize, usize) { + match self { + Invocation::Core { id, .. } => { + let ty = cx.module.funcs.get(*id).ty(); + let ty = cx.module.types.get(ty); + (ty.params().len(), ty.results().len()) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + (adapter.params.len(), adapter.results.len()) + } + } + } + + fn invoke( + &self, + cx: &mut Context, + args: &[String], + prelude: &mut String, + log_error: &mut bool, + ) -> Result { + match self { + Invocation::Core { id, .. } => { + let name = cx.export_name_of(*id); + Ok(format!("wasm.{}({})", name, args.join(", "))) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + let kind = match adapter.kind { + AdapterKind::Import { kind, .. } => kind, + AdapterKind::Local { .. } => { + bail!("adapter-to-adapter calls not supported yet"); + } + }; + let import = &cx.aux.import_map[id]; + let variadic = cx.aux.imports_with_variadic.contains(id); + if cx.import_never_log_error(import) { + *log_error = false; + } + cx.invoke_import(import, kind, args, variadic, prelude) + } + } + } + + fn defer(&self) -> bool { + match self { + Invocation::Core { defer, .. } => *defer, + _ => false, + } + } +} + +fn adapter2ts(ty: &AdapterType, dst: &mut String) { + match ty { + AdapterType::I32 + | AdapterType::S8 + | AdapterType::S16 + | AdapterType::S32 + | AdapterType::U8 + | AdapterType::U16 + | AdapterType::U32 + | AdapterType::F32 + | AdapterType::F64 => dst.push_str("number"), + AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"), + AdapterType::String => dst.push_str("string"), + AdapterType::Anyref => dst.push_str("any"), + AdapterType::Bool => dst.push_str("boolean"), + AdapterType::Vector(kind) => dst.push_str(kind.js_ty()), + AdapterType::Option(ty) => { + adapter2ts(ty, dst); + dst.push_str(" | undefined"); + } + AdapterType::NamedAnyref(name) => dst.push_str(name), + AdapterType::Struct(name) => dst.push_str(name), + AdapterType::Function => dst.push_str("any"), + } +} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index fd36ff510ac..6d619661fbe 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1666,9 +1666,16 @@ impl<'a> Context<'a> { let add = self.expose_add_to_anyref_table(table, alloc)?; self.global(&format!( " - function handleError(e) {{ - const idx = {}(e); - wasm.{}(idx); + function handleError(f) {{ + return function () {{ + try {{ + return f.apply(this, arguments); + + }} catch (e) {{ + const idx = {}(e); + wasm.{}(idx); + }} + }}; }} ", add, store, @@ -1678,8 +1685,15 @@ impl<'a> Context<'a> { self.expose_add_heap_object(); self.global(&format!( " - function handleError(e) {{ - wasm.{}(addHeapObject(e)); + function handleError(f) {{ + return function () {{ + try {{ + return f.apply(this, arguments); + + }} catch (e) {{ + wasm.{}(addHeapObject(e)); + }} + }}; }} ", store, @@ -1695,20 +1709,27 @@ impl<'a> Context<'a> { } self.global( "\ - function logError(e) { - let error = (function () { + function logError(f) { + return function () { try { - return e instanceof Error \ - ? `${e.message}\\n\\nStack:\\n${e.stack}` \ - : e.toString(); - } catch(_) { - return \"\"; + return f.apply(this, arguments); + + } catch (e) { + let error = (function () { + try { + return e instanceof Error \ + ? `${e.message}\\n\\nStack:\\n${e.stack}` \ + : e.toString(); + } catch(_) { + return \"\"; + } + }()); + console.error(\"wasm-bindgen: imported JS function that \ + was not marked as `catch` threw an error:\", \ + error); + throw e; } - }()); - console.error(\"wasm-bindgen: imported JS function that \ - was not marked as `catch` threw an error:\", \ - error); - throw e; + }; } ", ); @@ -2183,6 +2204,8 @@ impl<'a> Context<'a> { js_doc, code, might_be_optional_field, + catch, + log_error, } = builder .process(&adapter, instrs, arg_names) .with_context(|| match kind { @@ -2201,6 +2224,9 @@ impl<'a> Context<'a> { // on what's being exported. match kind { Kind::Export(export) => { + assert_eq!(catch, false); + assert_eq!(log_error, false); + let ts_sig = match export.generate_typescript { true => Some(ts_sig.as_str()), false => None, @@ -2257,10 +2283,23 @@ impl<'a> Context<'a> { } } Kind::Import(core) => { + let code = if catch { + format!("handleError(function{})", code) + + } else if log_error { + format!("logError(function{})", code) + + } else { + format!("function{}", code) + }; + self.wasm_import_definitions - .insert(core, format!("function{}", code)); + .insert(core, code); } Kind::Adapter => { + assert_eq!(catch, false); + assert_eq!(log_error, false); + self.globals.push_str("function "); self.globals.push_str(&self.adapter_name(id)); self.globals.push_str(&code); From f719f0861a6c7c79c032526c334795b759c3e958 Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 22 Apr 2020 00:07:02 +0200 Subject: [PATCH 2/5] Fixing newlines --- crates/cli-support/src/js/binding.rs | 2494 +++++++++++++------------- 1 file changed, 1247 insertions(+), 1247 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 34dd9adcb5b..8f807805193 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -1,1247 +1,1247 @@ -//! Support for actually generating a JS function shim. -//! -//! This `Builder` type is used to generate JS function shims which sit between -//! exported functions, table elements, imports, etc. All function shims -//! generated by `wasm-bindgen` run through this type. - -use crate::js::Context; -use crate::wit::InstructionData; -use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; -use anyhow::{anyhow, bail, Error}; -use walrus::Module; - -/// A one-size-fits-all builder for processing WebIDL bindings and generating -/// JS. -pub struct Builder<'a, 'b> { - /// Parent context used to expose helper functions and such. - pub cx: &'a mut Context<'b>, - /// Whether or not this is building a constructor for a Rust class, and if - /// so what class it's constructing. - constructor: Option, - /// Whether or not this is building a method of a Rust class instance, and - /// whether or not the method consumes `self` or not. - method: Option, - /// Whether or not we're catching exceptions from the main function - /// invocation. Currently only used for imports. - catch: bool, - /// Whether or not we're logging the error coming out of this intrinsic - log_error: bool, -} - -/// Helper struct used to create JS to process all instructions in an adapter -/// function. -pub struct JsBuilder<'a, 'b> { - /// General context for building JS, used to manage intrinsic names, exposed - /// JS functions, etc. - cx: &'a mut Context<'b>, - - /// The "prelude" of the function, or largely just the JS function we've - /// built so far. - prelude: String, - - /// JS code to execute in a `finally` block in case any exceptions happen. - finally: String, - - /// An index used to manage allocation of temporary indices, used to name - /// variables to ensure nothing clashes with anything else. - tmp: usize, - - /// Names or expressions representing the arguments to the adapter. This is - /// use to translate the `arg.get` instruction. - args: Vec, - - /// The wasm interface types "stack". The expressions pushed onto this stack - /// are intended to be *pure*, and if they're not, they should be pushed - /// into the `prelude`, assigned to a variable, and the variable should be - /// pushed to the stack. We're not super principled about this though, so - /// improvements will likely happen here over time. - stack: Vec, -} - -pub struct JsFunction { - pub code: String, - pub ts_sig: String, - pub js_doc: String, - pub ts_arg_tys: Vec, - pub ts_ret_ty: Option, - pub might_be_optional_field: bool, - pub catch: bool, - pub log_error: bool, -} - -impl<'a, 'b> Builder<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { - Builder { - log_error: false, - cx, - constructor: None, - method: None, - catch: false, - } - } - - pub fn method(&mut self, consumed: bool) { - self.method = Some(consumed); - } - - pub fn constructor(&mut self, class: &str) { - self.constructor = Some(class.to_string()); - } - - pub fn catch(&mut self, catch: bool) { - self.catch = catch; - } - - pub fn log_error(&mut self, log: bool) { - self.log_error = log; - } - - pub fn process( - &mut self, - adapter: &Adapter, - instructions: &[InstructionData], - explicit_arg_names: &Option>, - ) -> Result { - if self - .cx - .aux - .imports_with_assert_no_shim - .contains(&adapter.id) - { - bail!("generating a shim for something asserted to have no shim"); - } - - let mut params = adapter.params.iter(); - let mut function_args = Vec::new(); - let mut arg_tys = Vec::new(); - - // If this is a method then we're generating this as part of a class - // method, so the leading parameter is the this pointer stored on - // the JS object, so synthesize that here. - let mut js = JsBuilder::new(self.cx); - match self.method { - Some(consumes_self) => { - drop(params.next()); - if js.cx.config.debug { - js.prelude( - "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", - ); - } - if consumes_self { - js.prelude("var ptr = this.ptr;"); - js.prelude("this.ptr = 0;"); - js.args.push("ptr".to_string()); - } else { - js.args.push("this.ptr".to_string()); - } - } - None => {} - } - for (i, param) in params.enumerate() { - let arg = match explicit_arg_names { - Some(list) => list[i].clone(), - None => format!("arg{}", i), - }; - js.args.push(arg.clone()); - function_args.push(arg); - arg_tys.push(param); - } - - // Translate all instructions, the fun loop! - // - // This loop will process all instructions for this adapter function. - // Each instruction will push/pop from the `js.stack` variable, and will - // eventually build up the entire `js.prelude` variable with all the JS - // code that we're going to be adding. Note that the stack at the end - // represents all returned values. - // - // We don't actually manage a literal stack at runtime, but instead we - // act as more of a compiler to generate straight-line code to make it - // more JIT-friendly. The generated code should be equivalent to the - // wasm interface types stack machine, however. - for instr in instructions { - instruction(&mut js, &instr.instr, &mut self.log_error)?; - } - - assert_eq!(js.stack.len(), adapter.results.len()); - match js.stack.len() { - 0 => {} - 1 => { - let val = js.pop(); - js.prelude(&format!("return {};", val)); - } - - // TODO: this should be pretty trivial to support (commented out - // code below), but we should be sure to have a test for this - // somewhere. Currently I don't think it's possible to actually - // exercise this with just Rust code, so let's wait until we get - // some tests to enable this path. - _ => bail!("multi-value returns from adapters not supported yet"), - // _ => { - // let expr = js.stack.join(", "); - // js.stack.truncate(0); - // js.prelude(&format!("return [{}];", expr)); - // } - } - assert!(js.stack.is_empty()); - - // // Remove extraneous typescript args which were synthesized and aren't - // // part of our function shim. - // while self.ts_args.len() > function_args.len() { - // self.ts_args.remove(0); - // } - - let mut code = String::new(); - code.push_str("("); - code.push_str(&function_args.join(", ")); - code.push_str(") {\n"); - - let mut call = js.prelude; - if js.finally.len() != 0 { - call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); - } - - if self.catch { - js.cx.expose_handle_error()?; - } - - // Generate a try/catch block in debug mode which handles unexpected and - // unhandled exceptions, typically used on imports. This currently just - // logs what happened, but keeps the exception being thrown to propagate - // elsewhere. - if self.log_error { - js.cx.expose_log_error(); - } - - code.push_str(&call); - code.push_str("}"); - - // Rust Structs' fields converted into Getter and Setter functions before - // we decode them from webassembly, finding if a function is a field - // should start from here. Struct fields(Getter) only have one arg, and - // this is the clue we can infer if a function might be a field. - let mut might_be_optional_field = false; - let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature( - &function_args, - &arg_tys, - &adapter.results, - &mut might_be_optional_field, - ); - let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty); - Ok(JsFunction { - code, - ts_sig, - js_doc, - ts_arg_tys, - ts_ret_ty, - might_be_optional_field, - catch: self.catch, - log_error: self.log_error, - }) - } - - /// Returns the typescript signature of the binding that this has described. - /// This is used to generate all the TypeScript definitions later on. - /// - /// Note that the TypeScript returned here is just the argument list and the - /// return value, it doesn't include the function name in any way. - fn typescript_signature( - &self, - arg_names: &[String], - arg_tys: &[&AdapterType], - result_tys: &[AdapterType], - might_be_optional_field: &mut bool, - ) -> (String, Vec, Option) { - // Build up the typescript signature as well - let mut omittable = true; - let mut ts_args = Vec::new(); - let mut ts_arg_tys = Vec::new(); - for (name, ty) in arg_names.iter().zip(arg_tys).rev() { - // In TypeScript, we can mark optional parameters as omittable - // using the `?` suffix, but only if they're not followed by - // non-omittable parameters. Therefore iterate the parameter list - // in reverse and stop using the `?` suffix for optional params as - // soon as a non-optional parameter is encountered. - let mut arg = name.to_string(); - let mut ts = String::new(); - match ty { - AdapterType::Option(ty) if omittable => { - arg.push_str("?: "); - adapter2ts(ty, &mut ts); - } - ty => { - omittable = false; - arg.push_str(": "); - adapter2ts(ty, &mut ts); - } - } - arg.push_str(&ts); - ts_arg_tys.push(ts); - ts_args.push(arg); - } - ts_args.reverse(); - ts_arg_tys.reverse(); - let mut ts = format!("({})", ts_args.join(", ")); - - // If this function is an optional field's setter, it should have only - // one arg, and omittable should be `true`. - if ts_args.len() == 1 && omittable { - *might_be_optional_field = true; - } - - // Constructors have no listed return type in typescript - let mut ts_ret = None; - if self.constructor.is_none() { - ts.push_str(": "); - let mut ret = String::new(); - match result_tys.len() { - 0 => ret.push_str("void"), - 1 => adapter2ts(&result_tys[0], &mut ret), - _ => ret.push_str("[any]"), - } - ts.push_str(&ret); - ts_ret = Some(ret); - } - return (ts, ts_arg_tys, ts_ret); - } - - /// Returns a helpful JS doc comment which lists types for all parameters - /// and the return value. - fn js_doc_comments( - &self, - arg_names: &[String], - arg_tys: &[&AdapterType], - ts_ret: &Option, - ) -> String { - let mut ret = String::new(); - for (name, ty) in arg_names.iter().zip(arg_tys) { - ret.push_str("@param {"); - adapter2ts(ty, &mut ret); - ret.push_str("} "); - ret.push_str(name); - ret.push_str("\n"); - } - if let Some(ts) = ts_ret { - if ts != "void" { - ret.push_str(&format!("@returns {{{}}}", ts)); - } - } - ret - } -} - -impl<'a, 'b> JsBuilder<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { - JsBuilder { - cx, - args: Vec::new(), - tmp: 0, - finally: String::new(), - prelude: String::new(), - stack: Vec::new(), - } - } - - pub fn arg(&self, idx: u32) -> &str { - &self.args[idx as usize] - } - - pub fn prelude(&mut self, prelude: &str) { - for line in prelude.trim().lines().map(|l| l.trim()) { - if !line.is_empty() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); - } - } - } - - pub fn finally(&mut self, finally: &str) { - for line in finally.trim().lines().map(|l| l.trim()) { - if !line.is_empty() { - self.finally.push_str(line); - self.finally.push_str("\n"); - } - } - } - - pub fn tmp(&mut self) -> usize { - let ret = self.tmp; - self.tmp += 1; - return ret; - } - - fn pop(&mut self) -> String { - self.stack.pop().unwrap() - } - - fn push(&mut self, arg: String) { - self.stack.push(arg); - } - - fn assert_class(&mut self, arg: &str, class: &str) { - self.cx.expose_assert_class(); - self.prelude(&format!("_assertClass({}, {});", arg, class)); - } - - fn assert_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_num(); - self.prelude(&format!("_assertNum({});", arg)); - } - - fn assert_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_bool(); - self.prelude(&format!("_assertBoolean({});", arg)); - } - - fn assert_optional_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_number(arg); - self.prelude("}"); - } - - fn assert_optional_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bool(arg); - self.prelude("}"); - } - - fn assert_not_moved(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.prelude(&format!( - "\ - if ({0}.ptr === 0) {{ - throw new Error('Attempt to use a moved value'); - }} - ", - arg, - )); - } - - fn string_to_memory( - &mut self, - mem: walrus::MemoryId, - malloc: walrus::FunctionId, - realloc: Option, - ) -> Result<(), Error> { - let pass = self.cx.expose_pass_string_to_wasm(mem)?; - let val = self.pop(); - let malloc = self.cx.export_name_of(malloc); - let i = self.tmp(); - let realloc = match realloc { - Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)), - None => String::new(), - }; - self.prelude(&format!( - "var ptr{i} = {f}({0}, wasm.{malloc}{realloc});", - val, - i = i, - f = pass, - malloc = malloc, - realloc = realloc, - )); - self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - self.push(format!("ptr{}", i)); - self.push(format!("len{}", i)); - Ok(()) - } -} - -fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { - // Here first properly aligned nonzero address is chosen to be the - // out-pointer. We use the address for a BigInt64Array sometimes which - // means it needs to be 8-byte aligned. Otherwise valid code is - // unlikely to ever be working around address 8, so this should be a - // safe address to use for returning data through. - let retptr_val = 8; - - match instr { - Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { - let arg = js.arg(*n).to_string(); - js.push(arg); - } - - Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { - panic!("standard call adapter functions should be mapped to our adapters"); - } - - Instruction::Standard(wit_walrus::Instruction::CallCore(_)) - | Instruction::CallExport(_) - | Instruction::CallAdapter(_) - | Instruction::CallTableElement(_) - | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { - let invoc = Invocation::from(instr, js.cx.module)?; - let (params, results) = invoc.params_results(js.cx); - - // Pop off the number of parameters for the function we're calling - let mut args = Vec::new(); - for _ in 0..params { - args.push(js.pop()); - } - args.reverse(); - - // Call the function through an export of the underlying module. - let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; - - // And then figure out how to actually handle where the call - // happens. This is pretty conditional depending on the number of - // return values of the function. - match (invoc.defer(), results) { - (true, 0) => { - js.finally(&format!("{};", call)); - js.stack.extend(args); - } - (true, _) => panic!("deferred calls must have no results"), - (false, 0) => js.prelude(&format!("{};", call)), - (false, n) => { - js.prelude(&format!("var ret = {};", call)); - if n == 1 { - js.push("ret".to_string()); - } else { - for i in 0..n { - js.push(format!("ret[{}]", i)); - } - } - } - } - } - - Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { - let val = js.pop(); - js.assert_number(&val); - js.push(val); - } - - // When converting to a JS number we need to specially handle the `u32` - // case because if the high bit is set then it comes out as a negative - // number, but we want to switch that to an unsigned representation. - Instruction::Standard(wit_walrus::Instruction::WasmToInt { - trap: false, - output, - .. - }) => { - let val = js.pop(); - match output { - wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), - _ => js.push(val), - } - } - - Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) - | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { - bail!("trapping wasm-to-int and int-to-wasm instructions not supported") - } - - Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { - let len = js.pop(); - let ptr = js.pop(); - let get = js.cx.expose_get_string_from_wasm(*mem)?; - js.push(format!("{}({}, {})", get, ptr, len)); - } - - Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { - js.string_to_memory(*mem, *malloc, None)?; - } - - Instruction::StringToMemory { - mem, - malloc, - realloc, - } => { - js.string_to_memory(*mem, *malloc, *realloc)?; - } - - Instruction::Retptr => js.stack.push(retptr_val.to_string()), - - Instruction::StoreRetptr { ty, offset, mem } => { - let (mem, size) = match ty { - AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), - AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), - AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), - other => bail!("invalid aggregate return type {:?}", other), - }; - // Note that we always assume the return pointer is argument 0, - // which is currently the case for LLVM. - let val = js.pop(); - let expr = format!( - "{}()[{} / {} + {}] = {};", - mem, - js.arg(0), - size, - offset, - val, - ); - js.prelude(&expr); - } - - Instruction::LoadRetptr { ty, offset, mem } => { - let (mem, size) = match ty { - AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), - AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), - AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), - other => bail!("invalid aggregate return type {:?}", other), - }; - // If we're loading from the return pointer then we must have pushed - // it earlier, and we always push the same value, so load that value - // here - let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); - js.prelude(&format!("var r{} = {};", offset, expr)); - js.push(format!("r{}", offset)); - } - - Instruction::I32FromBool => { - let val = js.pop(); - js.assert_bool(&val); - // JS will already coerce booleans into numbers for us - js.push(val); - } - - Instruction::I32FromStringFirstChar => { - let val = js.pop(); - js.push(format!("{}.codePointAt(0)", val)); - } - - Instruction::I32FromAnyrefOwned => { - js.cx.expose_add_heap_object(); - let val = js.pop(); - js.push(format!("addHeapObject({})", val)); - } - - Instruction::I32FromAnyrefBorrow => { - js.cx.expose_borrowed_objects(); - js.cx.expose_global_stack_pointer(); - let val = js.pop(); - js.push(format!("addBorrowedObject({})", val)); - js.finally("heap[stack_pointer++] = undefined;"); - } - - Instruction::I32FromAnyrefRustOwned { class } => { - let val = js.pop(); - js.assert_class(&val, &class); - js.assert_not_moved(&val); - let i = js.tmp(); - js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); - js.prelude(&format!("{}.ptr = 0;", val)); - js.push(format!("ptr{}", i)); - } - - Instruction::I32FromAnyrefRustBorrow { class } => { - let val = js.pop(); - js.assert_class(&val, &class); - js.assert_not_moved(&val); - js.push(format!("{}.ptr", val)); - } - - Instruction::I32FromOptionRust { class } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - let i = js.tmp(); - js.prelude(&format!("let ptr{} = 0;", i)); - js.prelude(&format!("if (!isLikeNone({0})) {{", val)); - js.assert_class(&val, class); - js.assert_not_moved(&val); - js.prelude(&format!("ptr{} = {}.ptr;", i, val)); - js.prelude(&format!("{}.ptr = 0;", val)); - js.prelude("}"); - js.push(format!("ptr{}", i)); - } - - Instruction::I32Split64 { signed } => { - let val = js.pop(); - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - js.prelude(&format!( - " - {f}[0] = {val}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - val = val, - )); - js.push(format!("low{}", i)); - js.push(format!("high{}", i)); - } - - Instruction::I32SplitOption64 { signed } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - js.prelude(&format!( - "\ - {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - val = val, - )); - js.push(format!("!isLikeNone({0})", val)); - js.push(format!("low{}", i)); - js.push(format!("high{}", i)); - } - - Instruction::I32FromOptionAnyref { table_and_alloc } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - match table_and_alloc { - Some((table, alloc)) => { - let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?; - js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); - } - None => { - js.cx.expose_add_heap_object(); - js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); - } - } - } - - Instruction::I32FromOptionU32Sentinel => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); - } - - Instruction::I32FromOptionBool => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_bool(&val); - js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); - } - - Instruction::I32FromOptionChar => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.push(format!( - "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", - val - )); - } - - Instruction::I32FromOptionEnum { hole } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); - } - - Instruction::FromOptionNative { .. } => { - let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); - js.push(format!("!isLikeNone({0})", val)); - js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); - } - - Instruction::VectorToMemory { kind, malloc, mem } => { - let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - let malloc = js.cx.export_name_of(*malloc); - let i = js.tmp(); - js.prelude(&format!( - "var ptr{i} = {f}({0}, wasm.{malloc});", - val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::OptionString { - mem, - malloc, - realloc, - } => { - let func = js.cx.expose_pass_string_to_wasm(*mem)?; - js.cx.expose_is_like_none(); - let i = js.tmp(); - let malloc = js.cx.export_name_of(*malloc); - let val = js.pop(); - let realloc = match realloc { - Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)), - None => String::new(), - }; - js.prelude(&format!( - "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});", - val, - i = i, - f = func, - malloc = malloc, - realloc = realloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::OptionVector { kind, mem, malloc } => { - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - js.cx.expose_is_like_none(); - let i = js.tmp(); - let malloc = js.cx.export_name_of(*malloc); - let val = js.pop(); - js.prelude(&format!( - "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", - val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - } - - Instruction::MutableSliceToMemory { - kind, - malloc, - mem, - free, - } => { - // First up, pass the JS value into wasm, getting out a pointer and - // a length. These two pointer/length values get pushed onto the - // value stack. - let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - let malloc = js.cx.export_name_of(*malloc); - let i = js.tmp(); - js.prelude(&format!( - "var ptr{i} = {f}({val}, wasm.{malloc});", - val = val, - i = i, - f = func, - malloc = malloc, - )); - js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); - js.push(format!("ptr{}", i)); - js.push(format!("len{}", i)); - - // Next we set up a `finally` clause which will both update the - // original mutable slice with any modifications, and then free the - // Rust-backed memory. - let free = js.cx.export_name_of(*free); - let get = js.cx.memview_function(*kind, *mem); - js.finally(&format!( - " - {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); - wasm.{free}(ptr{i}, len{i} * {size}); - ", - val = val, - get = get, - free = free, - size = kind.size(), - i = i, - )); - } - - Instruction::BoolFromI32 => { - let val = js.pop(); - js.push(format!("{} !== 0", val)); - } - - Instruction::AnyrefLoadOwned => { - js.cx.expose_take_object(); - let val = js.pop(); - js.push(format!("takeObject({})", val)); - } - - Instruction::StringFromChar => { - let val = js.pop(); - js.push(format!("String.fromCodePoint({})", val)); - } - - Instruction::I64FromLoHi { signed } => { - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - let high = js.pop(); - let low = js.pop(); - js.prelude(&format!( - "\ - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {f}[0]; - ", - low = low, - high = high, - f = f, - i = i, - )); - js.push(format!("n{}", i)) - } - - Instruction::RustFromI32 { class } => { - js.cx.require_class_wrap(class); - let val = js.pop(); - js.push(format!("{}.__wrap({})", class, val)); - } - - Instruction::OptionRustFromI32 { class } => { - js.cx.require_class_wrap(class); - let val = js.pop(); - js.push(format!( - "{0} === 0 ? undefined : {1}.__wrap({0})", - val, class, - )) - } - - Instruction::CachedStringLoad { - owned, - optional: _, - mem, - free, - } => { - let len = js.pop(); - let ptr = js.pop(); - let tmp = js.tmp(); - - let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; - - js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); - - if *owned { - let free = js.cx.export_name_of(*free); - js.prelude(&format!( - "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", - free, - ptr = ptr, - len = len, - )); - } - - js.push(format!("v{}", tmp)); - } - - Instruction::TableGet => { - let val = js.pop(); - js.cx.expose_get_object(); - js.push(format!("getObject({})", val)); - } - - Instruction::StackClosure { - adapter, - nargs, - mutable, - } => { - let i = js.tmp(); - let b = js.pop(); - let a = js.pop(); - js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); - let args = (0..*nargs) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", "); - let wrapper = js.cx.adapter_name(*adapter); - if *mutable { - // Mutable closures need protection against being called - // recursively, so ensure that we clear out one of the - // internal pointers while it's being invoked. - js.prelude(&format!( - "var cb{i} = ({args}) => {{ - const a = state{i}.a; - state{i}.a = 0; - try {{ - return {name}(a, state{i}.b, {args}); - }} finally {{ - state{i}.a = a; - }} - }};", - i = i, - args = args, - name = wrapper, - )); - } else { - js.prelude(&format!( - "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", - i = i, - args = args, - wrapper = wrapper, - )); - } - - // Make sure to null out our internal pointers when we return - // back to Rust to ensure that any lingering references to the - // closure will fail immediately due to null pointers passed in - // to Rust. - js.finally(&format!("state{}.a = state{0}.b = 0;", i)); - js.push(format!("cb{}", i)); - } - - Instruction::VectorLoad { kind, mem, free } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - let i = js.tmp(); - let free = js.cx.export_name_of(*free); - js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!( - "wasm.{}({}, {} * {});", - free, - ptr, - len, - kind.size() - )); - js.push(format!("v{}", i)) - } - - Instruction::OptionVectorLoad { kind, mem, free } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - let i = js.tmp(); - let free = js.cx.export_name_of(*free); - js.prelude(&format!("let v{};", i)); - js.prelude(&format!("if ({} !== 0) {{", ptr)); - js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!( - "wasm.{}({}, {} * {});", - free, - ptr, - len, - kind.size() - )); - js.prelude("}"); - js.push(format!("v{}", i)); - } - - Instruction::View { kind, mem } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); - } - - Instruction::OptionView { kind, mem } => { - let len = js.pop(); - let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - js.push(format!( - "{ptr} === 0 ? undefined : {f}({ptr}, {len})", - ptr = ptr, - len = len, - f = f - )); - } - - Instruction::OptionU32Sentinel => { - let val = js.pop(); - js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); - } - - Instruction::ToOptionNative { ty: _, signed } => { - let val = js.pop(); - let present = js.pop(); - js.push(format!( - "{} === 0 ? undefined : {}{}", - present, - val, - if *signed { "" } else { " >>> 0" }, - )); - } - - Instruction::OptionBoolFromI32 => { - let val = js.pop(); - js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); - } - - Instruction::OptionCharFromI32 => { - let val = js.pop(); - js.push(format!( - "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", - val, - )); - } - - Instruction::OptionEnumFromI32 { hole } => { - let val = js.pop(); - js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); - } - - Instruction::Option64FromI32 { signed } => { - let f = if *signed { - js.cx.expose_int64_cvt_shim() - } else { - js.cx.expose_uint64_cvt_shim() - }; - let i = js.tmp(); - let high = js.pop(); - let low = js.pop(); - let present = js.pop(); - js.prelude(&format!( - " - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {present} === 0 ? undefined : {f}[0]; - ", - present = present, - low = low, - high = high, - f = f, - i = i, - )); - js.push(format!("n{}", i)); - } - } - Ok(()) -} - -enum Invocation { - Core { id: walrus::FunctionId, defer: bool }, - Adapter(AdapterId), -} - -impl Invocation { - fn from(instr: &Instruction, module: &Module) -> Result { - use Instruction::*; - Ok(match instr { - Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { - id: *f, - defer: false, - }, - - Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { - id: *f, - defer: true, - }, - - CallExport(e) => match module.exports.get(*e).item { - walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, - _ => panic!("can only call exported function"), - }, - - // The function table never changes right now, so we can statically - // look up the desired function. - CallTableElement(idx) => { - let table = module - .tables - .main_function_table()? - .ok_or_else(|| anyhow!("no function table found"))?; - let functions = match &module.tables.get(table).kind { - walrus::TableKind::Function(f) => f, - _ => bail!("should have found a function table"), - }; - let id = functions - .elements - .get(*idx as usize) - .and_then(|id| *id) - .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; - Invocation::Core { id, defer: false } - } - - CallAdapter(id) => Invocation::Adapter(*id), - - // this function is only called for the above instructions - _ => unreachable!(), - }) - } - - fn params_results(&self, cx: &Context) -> (usize, usize) { - match self { - Invocation::Core { id, .. } => { - let ty = cx.module.funcs.get(*id).ty(); - let ty = cx.module.types.get(ty); - (ty.params().len(), ty.results().len()) - } - Invocation::Adapter(id) => { - let adapter = &cx.wit.adapters[id]; - (adapter.params.len(), adapter.results.len()) - } - } - } - - fn invoke( - &self, - cx: &mut Context, - args: &[String], - prelude: &mut String, - log_error: &mut bool, - ) -> Result { - match self { - Invocation::Core { id, .. } => { - let name = cx.export_name_of(*id); - Ok(format!("wasm.{}({})", name, args.join(", "))) - } - Invocation::Adapter(id) => { - let adapter = &cx.wit.adapters[id]; - let kind = match adapter.kind { - AdapterKind::Import { kind, .. } => kind, - AdapterKind::Local { .. } => { - bail!("adapter-to-adapter calls not supported yet"); - } - }; - let import = &cx.aux.import_map[id]; - let variadic = cx.aux.imports_with_variadic.contains(id); - if cx.import_never_log_error(import) { - *log_error = false; - } - cx.invoke_import(import, kind, args, variadic, prelude) - } - } - } - - fn defer(&self) -> bool { - match self { - Invocation::Core { defer, .. } => *defer, - _ => false, - } - } -} - -fn adapter2ts(ty: &AdapterType, dst: &mut String) { - match ty { - AdapterType::I32 - | AdapterType::S8 - | AdapterType::S16 - | AdapterType::S32 - | AdapterType::U8 - | AdapterType::U16 - | AdapterType::U32 - | AdapterType::F32 - | AdapterType::F64 => dst.push_str("number"), - AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"), - AdapterType::String => dst.push_str("string"), - AdapterType::Anyref => dst.push_str("any"), - AdapterType::Bool => dst.push_str("boolean"), - AdapterType::Vector(kind) => dst.push_str(kind.js_ty()), - AdapterType::Option(ty) => { - adapter2ts(ty, dst); - dst.push_str(" | undefined"); - } - AdapterType::NamedAnyref(name) => dst.push_str(name), - AdapterType::Struct(name) => dst.push_str(name), - AdapterType::Function => dst.push_str("any"), - } -} +//! Support for actually generating a JS function shim. +//! +//! This `Builder` type is used to generate JS function shims which sit between +//! exported functions, table elements, imports, etc. All function shims +//! generated by `wasm-bindgen` run through this type. + +use crate::js::Context; +use crate::wit::InstructionData; +use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use anyhow::{anyhow, bail, Error}; +use walrus::Module; + +/// A one-size-fits-all builder for processing WebIDL bindings and generating +/// JS. +pub struct Builder<'a, 'b> { + /// Parent context used to expose helper functions and such. + pub cx: &'a mut Context<'b>, + /// Whether or not this is building a constructor for a Rust class, and if + /// so what class it's constructing. + constructor: Option, + /// Whether or not this is building a method of a Rust class instance, and + /// whether or not the method consumes `self` or not. + method: Option, + /// Whether or not we're catching exceptions from the main function + /// invocation. Currently only used for imports. + catch: bool, + /// Whether or not we're logging the error coming out of this intrinsic + log_error: bool, +} + +/// Helper struct used to create JS to process all instructions in an adapter +/// function. +pub struct JsBuilder<'a, 'b> { + /// General context for building JS, used to manage intrinsic names, exposed + /// JS functions, etc. + cx: &'a mut Context<'b>, + + /// The "prelude" of the function, or largely just the JS function we've + /// built so far. + prelude: String, + + /// JS code to execute in a `finally` block in case any exceptions happen. + finally: String, + + /// An index used to manage allocation of temporary indices, used to name + /// variables to ensure nothing clashes with anything else. + tmp: usize, + + /// Names or expressions representing the arguments to the adapter. This is + /// use to translate the `arg.get` instruction. + args: Vec, + + /// The wasm interface types "stack". The expressions pushed onto this stack + /// are intended to be *pure*, and if they're not, they should be pushed + /// into the `prelude`, assigned to a variable, and the variable should be + /// pushed to the stack. We're not super principled about this though, so + /// improvements will likely happen here over time. + stack: Vec, +} + +pub struct JsFunction { + pub code: String, + pub ts_sig: String, + pub js_doc: String, + pub ts_arg_tys: Vec, + pub ts_ret_ty: Option, + pub might_be_optional_field: bool, + pub catch: bool, + pub log_error: bool, +} + +impl<'a, 'b> Builder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { + Builder { + log_error: false, + cx, + constructor: None, + method: None, + catch: false, + } + } + + pub fn method(&mut self, consumed: bool) { + self.method = Some(consumed); + } + + pub fn constructor(&mut self, class: &str) { + self.constructor = Some(class.to_string()); + } + + pub fn catch(&mut self, catch: bool) { + self.catch = catch; + } + + pub fn log_error(&mut self, log: bool) { + self.log_error = log; + } + + pub fn process( + &mut self, + adapter: &Adapter, + instructions: &[InstructionData], + explicit_arg_names: &Option>, + ) -> Result { + if self + .cx + .aux + .imports_with_assert_no_shim + .contains(&adapter.id) + { + bail!("generating a shim for something asserted to have no shim"); + } + + let mut params = adapter.params.iter(); + let mut function_args = Vec::new(); + let mut arg_tys = Vec::new(); + + // If this is a method then we're generating this as part of a class + // method, so the leading parameter is the this pointer stored on + // the JS object, so synthesize that here. + let mut js = JsBuilder::new(self.cx); + match self.method { + Some(consumes_self) => { + drop(params.next()); + if js.cx.config.debug { + js.prelude( + "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", + ); + } + if consumes_self { + js.prelude("var ptr = this.ptr;"); + js.prelude("this.ptr = 0;"); + js.args.push("ptr".to_string()); + } else { + js.args.push("this.ptr".to_string()); + } + } + None => {} + } + for (i, param) in params.enumerate() { + let arg = match explicit_arg_names { + Some(list) => list[i].clone(), + None => format!("arg{}", i), + }; + js.args.push(arg.clone()); + function_args.push(arg); + arg_tys.push(param); + } + + // Translate all instructions, the fun loop! + // + // This loop will process all instructions for this adapter function. + // Each instruction will push/pop from the `js.stack` variable, and will + // eventually build up the entire `js.prelude` variable with all the JS + // code that we're going to be adding. Note that the stack at the end + // represents all returned values. + // + // We don't actually manage a literal stack at runtime, but instead we + // act as more of a compiler to generate straight-line code to make it + // more JIT-friendly. The generated code should be equivalent to the + // wasm interface types stack machine, however. + for instr in instructions { + instruction(&mut js, &instr.instr, &mut self.log_error)?; + } + + assert_eq!(js.stack.len(), adapter.results.len()); + match js.stack.len() { + 0 => {} + 1 => { + let val = js.pop(); + js.prelude(&format!("return {};", val)); + } + + // TODO: this should be pretty trivial to support (commented out + // code below), but we should be sure to have a test for this + // somewhere. Currently I don't think it's possible to actually + // exercise this with just Rust code, so let's wait until we get + // some tests to enable this path. + _ => bail!("multi-value returns from adapters not supported yet"), + // _ => { + // let expr = js.stack.join(", "); + // js.stack.truncate(0); + // js.prelude(&format!("return [{}];", expr)); + // } + } + assert!(js.stack.is_empty()); + + // // Remove extraneous typescript args which were synthesized and aren't + // // part of our function shim. + // while self.ts_args.len() > function_args.len() { + // self.ts_args.remove(0); + // } + + let mut code = String::new(); + code.push_str("("); + code.push_str(&function_args.join(", ")); + code.push_str(") {\n"); + + let mut call = js.prelude; + if js.finally.len() != 0 { + call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); + } + + if self.catch { + js.cx.expose_handle_error()?; + } + + // Generate a try/catch block in debug mode which handles unexpected and + // unhandled exceptions, typically used on imports. This currently just + // logs what happened, but keeps the exception being thrown to propagate + // elsewhere. + if self.log_error { + js.cx.expose_log_error(); + } + + code.push_str(&call); + code.push_str("}"); + + // Rust Structs' fields converted into Getter and Setter functions before + // we decode them from webassembly, finding if a function is a field + // should start from here. Struct fields(Getter) only have one arg, and + // this is the clue we can infer if a function might be a field. + let mut might_be_optional_field = false; + let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature( + &function_args, + &arg_tys, + &adapter.results, + &mut might_be_optional_field, + ); + let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty); + Ok(JsFunction { + code, + ts_sig, + js_doc, + ts_arg_tys, + ts_ret_ty, + might_be_optional_field, + catch: self.catch, + log_error: self.log_error, + }) + } + + /// Returns the typescript signature of the binding that this has described. + /// This is used to generate all the TypeScript definitions later on. + /// + /// Note that the TypeScript returned here is just the argument list and the + /// return value, it doesn't include the function name in any way. + fn typescript_signature( + &self, + arg_names: &[String], + arg_tys: &[&AdapterType], + result_tys: &[AdapterType], + might_be_optional_field: &mut bool, + ) -> (String, Vec, Option) { + // Build up the typescript signature as well + let mut omittable = true; + let mut ts_args = Vec::new(); + let mut ts_arg_tys = Vec::new(); + for (name, ty) in arg_names.iter().zip(arg_tys).rev() { + // In TypeScript, we can mark optional parameters as omittable + // using the `?` suffix, but only if they're not followed by + // non-omittable parameters. Therefore iterate the parameter list + // in reverse and stop using the `?` suffix for optional params as + // soon as a non-optional parameter is encountered. + let mut arg = name.to_string(); + let mut ts = String::new(); + match ty { + AdapterType::Option(ty) if omittable => { + arg.push_str("?: "); + adapter2ts(ty, &mut ts); + } + ty => { + omittable = false; + arg.push_str(": "); + adapter2ts(ty, &mut ts); + } + } + arg.push_str(&ts); + ts_arg_tys.push(ts); + ts_args.push(arg); + } + ts_args.reverse(); + ts_arg_tys.reverse(); + let mut ts = format!("({})", ts_args.join(", ")); + + // If this function is an optional field's setter, it should have only + // one arg, and omittable should be `true`. + if ts_args.len() == 1 && omittable { + *might_be_optional_field = true; + } + + // Constructors have no listed return type in typescript + let mut ts_ret = None; + if self.constructor.is_none() { + ts.push_str(": "); + let mut ret = String::new(); + match result_tys.len() { + 0 => ret.push_str("void"), + 1 => adapter2ts(&result_tys[0], &mut ret), + _ => ret.push_str("[any]"), + } + ts.push_str(&ret); + ts_ret = Some(ret); + } + return (ts, ts_arg_tys, ts_ret); + } + + /// Returns a helpful JS doc comment which lists types for all parameters + /// and the return value. + fn js_doc_comments( + &self, + arg_names: &[String], + arg_tys: &[&AdapterType], + ts_ret: &Option, + ) -> String { + let mut ret = String::new(); + for (name, ty) in arg_names.iter().zip(arg_tys) { + ret.push_str("@param {"); + adapter2ts(ty, &mut ret); + ret.push_str("} "); + ret.push_str(name); + ret.push_str("\n"); + } + if let Some(ts) = ts_ret { + if ts != "void" { + ret.push_str(&format!("@returns {{{}}}", ts)); + } + } + ret + } +} + +impl<'a, 'b> JsBuilder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { + JsBuilder { + cx, + args: Vec::new(), + tmp: 0, + finally: String::new(), + prelude: String::new(), + stack: Vec::new(), + } + } + + pub fn arg(&self, idx: u32) -> &str { + &self.args[idx as usize] + } + + pub fn prelude(&mut self, prelude: &str) { + for line in prelude.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.prelude.push_str(line); + self.prelude.push_str("\n"); + } + } + } + + pub fn finally(&mut self, finally: &str) { + for line in finally.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.finally.push_str(line); + self.finally.push_str("\n"); + } + } + } + + pub fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + return ret; + } + + fn pop(&mut self) -> String { + self.stack.pop().unwrap() + } + + fn push(&mut self, arg: String) { + self.stack.push(arg); + } + + fn assert_class(&mut self, arg: &str, class: &str) { + self.cx.expose_assert_class(); + self.prelude(&format!("_assertClass({}, {});", arg, class)); + } + + fn assert_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_num(); + self.prelude(&format!("_assertNum({});", arg)); + } + + fn assert_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_bool(); + self.prelude(&format!("_assertBoolean({});", arg)); + } + + fn assert_optional_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_number(arg); + self.prelude("}"); + } + + fn assert_optional_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_bool(arg); + self.prelude("}"); + } + + fn assert_not_moved(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.prelude(&format!( + "\ + if ({0}.ptr === 0) {{ + throw new Error('Attempt to use a moved value'); + }} + ", + arg, + )); + } + + fn string_to_memory( + &mut self, + mem: walrus::MemoryId, + malloc: walrus::FunctionId, + realloc: Option, + ) -> Result<(), Error> { + let pass = self.cx.expose_pass_string_to_wasm(mem)?; + let val = self.pop(); + let malloc = self.cx.export_name_of(malloc); + let i = self.tmp(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)), + None => String::new(), + }; + self.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = pass, + malloc = malloc, + realloc = realloc, + )); + self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + self.push(format!("ptr{}", i)); + self.push(format!("len{}", i)); + Ok(()) + } +} + +fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { + // Here first properly aligned nonzero address is chosen to be the + // out-pointer. We use the address for a BigInt64Array sometimes which + // means it needs to be 8-byte aligned. Otherwise valid code is + // unlikely to ever be working around address 8, so this should be a + // safe address to use for returning data through. + let retptr_val = 8; + + match instr { + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + let arg = js.arg(*n).to_string(); + js.push(arg); + } + + Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + panic!("standard call adapter functions should be mapped to our adapters"); + } + + Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::CallExport(_) + | Instruction::CallAdapter(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { + let invoc = Invocation::from(instr, js.cx.module)?; + let (params, results) = invoc.params_results(js.cx); + + // Pop off the number of parameters for the function we're calling + let mut args = Vec::new(); + for _ in 0..params { + args.push(js.pop()); + } + args.reverse(); + + // Call the function through an export of the underlying module. + let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; + + // And then figure out how to actually handle where the call + // happens. This is pretty conditional depending on the number of + // return values of the function. + match (invoc.defer(), results) { + (true, 0) => { + js.finally(&format!("{};", call)); + js.stack.extend(args); + } + (true, _) => panic!("deferred calls must have no results"), + (false, 0) => js.prelude(&format!("{};", call)), + (false, n) => { + js.prelude(&format!("var ret = {};", call)); + if n == 1 { + js.push("ret".to_string()); + } else { + for i in 0..n { + js.push(format!("ret[{}]", i)); + } + } + } + } + } + + Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + let val = js.pop(); + js.assert_number(&val); + js.push(val); + } + + // When converting to a JS number we need to specially handle the `u32` + // case because if the high bit is set then it comes out as a negative + // number, but we want to switch that to an unsigned representation. + Instruction::Standard(wit_walrus::Instruction::WasmToInt { + trap: false, + output, + .. + }) => { + let val = js.pop(); + match output { + wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), + _ => js.push(val), + } + } + + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) + | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { + bail!("trapping wasm-to-int and int-to-wasm instructions not supported") + } + + Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { + let len = js.pop(); + let ptr = js.pop(); + let get = js.cx.expose_get_string_from_wasm(*mem)?; + js.push(format!("{}({}, {})", get, ptr, len)); + } + + Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { + js.string_to_memory(*mem, *malloc, None)?; + } + + Instruction::StringToMemory { + mem, + malloc, + realloc, + } => { + js.string_to_memory(*mem, *malloc, *realloc)?; + } + + Instruction::Retptr => js.stack.push(retptr_val.to_string()), + + Instruction::StoreRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // Note that we always assume the return pointer is argument 0, + // which is currently the case for LLVM. + let val = js.pop(); + let expr = format!( + "{}()[{} / {} + {}] = {};", + mem, + js.arg(0), + size, + offset, + val, + ); + js.prelude(&expr); + } + + Instruction::LoadRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // If we're loading from the return pointer then we must have pushed + // it earlier, and we always push the same value, so load that value + // here + let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); + js.prelude(&format!("var r{} = {};", offset, expr)); + js.push(format!("r{}", offset)); + } + + Instruction::I32FromBool => { + let val = js.pop(); + js.assert_bool(&val); + // JS will already coerce booleans into numbers for us + js.push(val); + } + + Instruction::I32FromStringFirstChar => { + let val = js.pop(); + js.push(format!("{}.codePointAt(0)", val)); + } + + Instruction::I32FromAnyrefOwned => { + js.cx.expose_add_heap_object(); + let val = js.pop(); + js.push(format!("addHeapObject({})", val)); + } + + Instruction::I32FromAnyrefBorrow => { + js.cx.expose_borrowed_objects(); + js.cx.expose_global_stack_pointer(); + let val = js.pop(); + js.push(format!("addBorrowedObject({})", val)); + js.finally("heap[stack_pointer++] = undefined;"); + } + + Instruction::I32FromAnyrefRustOwned { class } => { + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + let i = js.tmp(); + js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.push(format!("ptr{}", i)); + } + + Instruction::I32FromAnyrefRustBorrow { class } => { + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + js.push(format!("{}.ptr", val)); + } + + Instruction::I32FromOptionRust { class } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + let i = js.tmp(); + js.prelude(&format!("let ptr{} = 0;", i)); + js.prelude(&format!("if (!isLikeNone({0})) {{", val)); + js.assert_class(&val, class); + js.assert_not_moved(&val); + js.prelude(&format!("ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.prelude("}"); + js.push(format!("ptr{}", i)); + } + + Instruction::I32Split64 { signed } => { + let val = js.pop(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + " + {f}[0] = {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32SplitOption64 { signed } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + "\ + {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32FromOptionAnyref { table_and_alloc } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + match table_and_alloc { + Some((table, alloc)) => { + let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?; + js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); + } + None => { + js.cx.expose_add_heap_object(); + js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + } + } + } + + Instruction::I32FromOptionU32Sentinel => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); + } + + Instruction::I32FromOptionBool => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_bool(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); + } + + Instruction::I32FromOptionChar => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.push(format!( + "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", + val + )); + } + + Instruction::I32FromOptionEnum { hole } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); + } + + Instruction::FromOptionNative { .. } => { + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); + } + + Instruction::VectorToMemory { kind, malloc, mem } => { + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionString { + mem, + malloc, + realloc, + } => { + let func = js.cx.expose_pass_string_to_wasm(*mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)), + None => String::new(), + }; + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = func, + malloc = malloc, + realloc = realloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionVector { kind, mem, malloc } => { + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::MutableSliceToMemory { + kind, + malloc, + mem, + free, + } => { + // First up, pass the JS value into wasm, getting out a pointer and + // a length. These two pointer/length values get pushed onto the + // value stack. + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({val}, wasm.{malloc});", + val = val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + + // Next we set up a `finally` clause which will both update the + // original mutable slice with any modifications, and then free the + // Rust-backed memory. + let free = js.cx.export_name_of(*free); + let get = js.cx.memview_function(*kind, *mem); + js.finally(&format!( + " + {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); + wasm.{free}(ptr{i}, len{i} * {size}); + ", + val = val, + get = get, + free = free, + size = kind.size(), + i = i, + )); + } + + Instruction::BoolFromI32 => { + let val = js.pop(); + js.push(format!("{} !== 0", val)); + } + + Instruction::AnyrefLoadOwned => { + js.cx.expose_take_object(); + let val = js.pop(); + js.push(format!("takeObject({})", val)); + } + + Instruction::StringFromChar => { + let val = js.pop(); + js.push(format!("String.fromCodePoint({})", val)); + } + + Instruction::I64FromLoHi { signed } => { + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + js.prelude(&format!( + "\ + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {f}[0]; + ", + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)) + } + + Instruction::RustFromI32 { class } => { + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!("{}.__wrap({})", class, val)); + } + + Instruction::OptionRustFromI32 { class } => { + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!( + "{0} === 0 ? undefined : {1}.__wrap({0})", + val, class, + )) + } + + Instruction::CachedStringLoad { + owned, + optional: _, + mem, + free, + } => { + let len = js.pop(); + let ptr = js.pop(); + let tmp = js.tmp(); + + let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; + + js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); + + if *owned { + let free = js.cx.export_name_of(*free); + js.prelude(&format!( + "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", + free, + ptr = ptr, + len = len, + )); + } + + js.push(format!("v{}", tmp)); + } + + Instruction::TableGet => { + let val = js.pop(); + js.cx.expose_get_object(); + js.push(format!("getObject({})", val)); + } + + Instruction::StackClosure { + adapter, + nargs, + mutable, + } => { + let i = js.tmp(); + let b = js.pop(); + let a = js.pop(); + js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); + let args = (0..*nargs) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + let wrapper = js.cx.adapter_name(*adapter); + if *mutable { + // Mutable closures need protection against being called + // recursively, so ensure that we clear out one of the + // internal pointers while it's being invoked. + js.prelude(&format!( + "var cb{i} = ({args}) => {{ + const a = state{i}.a; + state{i}.a = 0; + try {{ + return {name}(a, state{i}.b, {args}); + }} finally {{ + state{i}.a = a; + }} + }};", + i = i, + args = args, + name = wrapper, + )); + } else { + js.prelude(&format!( + "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", + i = i, + args = args, + wrapper = wrapper, + )); + } + + // Make sure to null out our internal pointers when we return + // back to Rust to ensure that any lingering references to the + // closure will fail immediately due to null pointers passed in + // to Rust. + js.finally(&format!("state{}.a = state{0}.b = 0;", i)); + js.push(format!("cb{}", i)); + } + + Instruction::VectorLoad { kind, mem, free } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.push(format!("v{}", i)) + } + + Instruction::OptionVectorLoad { kind, mem, free } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("let v{};", i)); + js.prelude(&format!("if ({} !== 0) {{", ptr)); + js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.prelude("}"); + js.push(format!("v{}", i)); + } + + Instruction::View { kind, mem } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); + } + + Instruction::OptionView { kind, mem } => { + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!( + "{ptr} === 0 ? undefined : {f}({ptr}, {len})", + ptr = ptr, + len = len, + f = f + )); + } + + Instruction::OptionU32Sentinel => { + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); + } + + Instruction::ToOptionNative { ty: _, signed } => { + let val = js.pop(); + let present = js.pop(); + js.push(format!( + "{} === 0 ? undefined : {}{}", + present, + val, + if *signed { "" } else { " >>> 0" }, + )); + } + + Instruction::OptionBoolFromI32 => { + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); + } + + Instruction::OptionCharFromI32 => { + let val = js.pop(); + js.push(format!( + "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", + val, + )); + } + + Instruction::OptionEnumFromI32 { hole } => { + let val = js.pop(); + js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); + } + + Instruction::Option64FromI32 { signed } => { + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + let present = js.pop(); + js.prelude(&format!( + " + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {present} === 0 ? undefined : {f}[0]; + ", + present = present, + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)); + } + } + Ok(()) +} + +enum Invocation { + Core { id: walrus::FunctionId, defer: bool }, + Adapter(AdapterId), +} + +impl Invocation { + fn from(instr: &Instruction, module: &Module) -> Result { + use Instruction::*; + Ok(match instr { + Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { + id: *f, + defer: false, + }, + + Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { + id: *f, + defer: true, + }, + + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, + _ => panic!("can only call exported function"), + }, + + // The function table never changes right now, so we can statically + // look up the desired function. + CallTableElement(idx) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => bail!("should have found a function table"), + }; + let id = functions + .elements + .get(*idx as usize) + .and_then(|id| *id) + .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; + Invocation::Core { id, defer: false } + } + + CallAdapter(id) => Invocation::Adapter(*id), + + // this function is only called for the above instructions + _ => unreachable!(), + }) + } + + fn params_results(&self, cx: &Context) -> (usize, usize) { + match self { + Invocation::Core { id, .. } => { + let ty = cx.module.funcs.get(*id).ty(); + let ty = cx.module.types.get(ty); + (ty.params().len(), ty.results().len()) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + (adapter.params.len(), adapter.results.len()) + } + } + } + + fn invoke( + &self, + cx: &mut Context, + args: &[String], + prelude: &mut String, + log_error: &mut bool, + ) -> Result { + match self { + Invocation::Core { id, .. } => { + let name = cx.export_name_of(*id); + Ok(format!("wasm.{}({})", name, args.join(", "))) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + let kind = match adapter.kind { + AdapterKind::Import { kind, .. } => kind, + AdapterKind::Local { .. } => { + bail!("adapter-to-adapter calls not supported yet"); + } + }; + let import = &cx.aux.import_map[id]; + let variadic = cx.aux.imports_with_variadic.contains(id); + if cx.import_never_log_error(import) { + *log_error = false; + } + cx.invoke_import(import, kind, args, variadic, prelude) + } + } + } + + fn defer(&self) -> bool { + match self { + Invocation::Core { defer, .. } => *defer, + _ => false, + } + } +} + +fn adapter2ts(ty: &AdapterType, dst: &mut String) { + match ty { + AdapterType::I32 + | AdapterType::S8 + | AdapterType::S16 + | AdapterType::S32 + | AdapterType::U8 + | AdapterType::U16 + | AdapterType::U32 + | AdapterType::F32 + | AdapterType::F64 => dst.push_str("number"), + AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"), + AdapterType::String => dst.push_str("string"), + AdapterType::Anyref => dst.push_str("any"), + AdapterType::Bool => dst.push_str("boolean"), + AdapterType::Vector(kind) => dst.push_str(kind.js_ty()), + AdapterType::Option(ty) => { + adapter2ts(ty, dst); + dst.push_str(" | undefined"); + } + AdapterType::NamedAnyref(name) => dst.push_str(name), + AdapterType::Struct(name) => dst.push_str(name), + AdapterType::Function => dst.push_str("any"), + } +} From 4ee67f2d300b1b610258f1b1a2dc12e13c64a8f7 Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 22 Apr 2020 00:51:29 +0200 Subject: [PATCH 3/5] Running rustfmt --- crates/cli-support/src/js/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 6d619661fbe..653bea21fa1 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2285,16 +2285,13 @@ impl<'a> Context<'a> { Kind::Import(core) => { let code = if catch { format!("handleError(function{})", code) - } else if log_error { format!("logError(function{})", code) - } else { format!("function{}", code) }; - self.wasm_import_definitions - .insert(core, code); + self.wasm_import_definitions.insert(core, code); } Kind::Adapter => { assert_eq!(catch, false); From de0e941e10dd36acfa370e35b0cbb87809b28fce Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 22 Apr 2020 01:08:22 +0200 Subject: [PATCH 4/5] Updating unit tests --- .../tests/reference/anyref-import-catch.js | 23 +++++++++++-------- crates/cli/tests/reference/import-catch.js | 23 +++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/cli/tests/reference/anyref-import-catch.js b/crates/cli/tests/reference/anyref-import-catch.js index 5e7c481b570..4f5f48aa60a 100644 --- a/crates/cli/tests/reference/anyref-import-catch.js +++ b/crates/cli/tests/reference/anyref-import-catch.js @@ -24,9 +24,16 @@ function addToAnyrefTable0(obj) { return idx; } -function handleError(e) { - const idx = addToAnyrefTable0(e); - wasm.__wbindgen_exn_store(idx); +function handleError(f) { + return function () { + try { + return f.apply(this, arguments); + + } catch (e) { + const idx = addToAnyrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } + }; } /** */ @@ -34,13 +41,9 @@ export function exported() { wasm.exported(); } -export const __wbg_foo_8d66ddef0ff279d6 = function() { - try { - foo(); - } catch (e) { - handleError(e) - } -}; +export const __wbg_foo_8d66ddef0ff279d6 = handleError(function() { + foo(); +}); export const __wbindgen_throw = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); diff --git a/crates/cli/tests/reference/import-catch.js b/crates/cli/tests/reference/import-catch.js index b4b61630809..4df614ee8e2 100644 --- a/crates/cli/tests/reference/import-catch.js +++ b/crates/cli/tests/reference/import-catch.js @@ -29,8 +29,15 @@ function addHeapObject(obj) { return idx; } -function handleError(e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); +function handleError(f) { + return function () { + try { + return f.apply(this, arguments); + + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } + }; } /** */ @@ -38,18 +45,14 @@ export function exported() { wasm.exported(); } +export const __wbg_foo_8d66ddef0ff279d6 = handleError(function() { + foo(); +}); + export const __wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; -export const __wbg_foo_8d66ddef0ff279d6 = function() { - try { - foo(); - } catch (e) { - handleError(e) - } -}; - export const __wbindgen_rethrow = function(arg0) { throw takeObject(arg0); }; From 850c863f18892298e8a50e7e05d42b384048ec75 Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 22 Apr 2020 02:04:57 +0200 Subject: [PATCH 5/5] Fixing build error --- crates/cli/tests/reference/import-catch.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cli/tests/reference/import-catch.js b/crates/cli/tests/reference/import-catch.js index 4df614ee8e2..6b0f9fb225e 100644 --- a/crates/cli/tests/reference/import-catch.js +++ b/crates/cli/tests/reference/import-catch.js @@ -45,14 +45,14 @@ export function exported() { wasm.exported(); } -export const __wbg_foo_8d66ddef0ff279d6 = handleError(function() { - foo(); -}); - export const __wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; +export const __wbg_foo_8d66ddef0ff279d6 = handleError(function() { + foo(); +}); + export const __wbindgen_rethrow = function(arg0) { throw takeObject(arg0); };