//! Interface types bindings generator support for `spidermonkey.wasm`. #![deny(missing_docs)] mod data_segments; use data_segments::DataSegments; use heck::SnakeCase; use lazy_static::lazy_static; use std::borrow::Cow; use std::convert::TryFrom; use std::ops::Range; use std::path::PathBuf; use std::{collections::HashMap, mem}; use wasm_encoder::Instruction; use wit_bindgen_gen_core::{ wit_parser::{ abi::{self, AbiVariant, WasmSignature, WasmType}, Docs, Enum, Expected, Flags, Function, Interface, Record, ResourceId, SizeAlign, Tuple, Type, TypeId, Union, Variant, }, Direction, Files, Generator, }; #[allow(missing_docs)] #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] pub struct Opts { /// The path to the JavaScript module. pub js: PathBuf, #[cfg_attr(feature = "structopt", structopt(long))] pub import_spidermonkey: bool, } #[allow(missing_docs)] impl Opts { pub fn build<'a>(self, js_source: impl Into>) -> SpiderMonkeyWasm<'a> { let mut builder = SpiderMonkeyWasm::new(self.js, js_source); builder.import_spidermonkey(self.import_spidermonkey); builder } } lazy_static! { /// Functions exported from `spidermonkey.wasm` static ref SMW_EXPORTS: Vec<(&'static str, WasmSignature)> = vec![ ( "_initialize", WasmSignature { params: vec![], results: vec![], retptr: false, indirect_params: false, }, ), ( "canonical_abi_free", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "canonical_abi_realloc", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ( "SMW_initialize_engine", WasmSignature { params: vec![], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_new_module_builder", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ( "SMW_module_builder_add_export", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_finish_module_builder", WasmSignature { params: vec![WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_eval_module", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_malloc", WasmSignature { params: vec![WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ( "SMW_fill_operands", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, } ), ( "SMW_clear_operands", WasmSignature { params: vec![], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_push_arg", WasmSignature { params: vec![WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_call", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_push_return_value", WasmSignature { params: vec![WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_finish_returns", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_i32_from_u32", WasmSignature { params: vec![WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ( "SMW_u32_from_i32", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_string_canon_lower", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_string_canon_lift", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_spread_into_array", WasmSignature { params: vec![WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ( "SMW_get_array_element", WasmSignature { params: vec![WasmType::I32, WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_array_push", WasmSignature { params: vec![WasmType::I32, WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "SMW_new_array", WasmSignature { params: vec![WasmType::I32], results: vec![], retptr: false, indirect_params: false, }, ), ( "dump_i32", WasmSignature { params: vec![WasmType::I32], results: vec![WasmType::I32], retptr: false, indirect_params: false, }, ), ]; } /// The `spidermonkey.wasm` bindings generator. /// /// ## Code Shape /// /// The output is a single Wasm file that imports and exports the functions /// defined in the given WIT files and additionally /// /// * embeds or imports (configurable) a `spidermonkey.wasm` instance, and /// /// * exports a `wizer.initialize` function that initializes SpiderMonkey and /// evaluates the top level of the JavaScript. /// /// ### Initialization /// /// As an API contract, the `wizer.initialize` function must be invoked before /// any other function. It must only be invoked once. /// /// The initialization function performs the following tasks: /// /// * Calls `spidermonkey.wasm`'s `_initialize` function, which runs C++ global /// contructors. /// /// * `malloc`s space in `spidermonkey.wasm`'s linear memory and copies the /// JavaScript source code from its linear memory into the malloc'd space. /// /// * Evaluates the JavaScript source, compiling it to bytecode and initializing /// globals and defining top-level functions in the process. /// /// ### Imports /// /// By the time an imported WIT function is called, we have the following /// layers of code on the stack, listed from older to younger frames: /// /// * User JS code (inside `spidermonkey.wasm`'s internal JS stack) /// /// This is the user's JavaScript code that is running inside of /// `spidermonkey.wasm` and which wants to call an external, imported function /// that is described with WIT. /// /// * Import glue Wasm code (on the Wasm stack) /// /// This is a synthesized Wasm function that understands both the canonical /// ABI and the SpiderMonkey API. It translates outgoing arguments from /// SpiderMonkey values into the canonical ABI representation, calls the /// actual imported Wasm function, and then translates the incoming results /// from the canonical ABI representation into SpiderMonkey values. /// /// * Imported function (on the Wasm Stack) /// /// This is the actual Wasm function whose signature is described in WIT and /// uses the canonical ABI. /// /// ### Exports /// /// By the time an exported JS function that implements a WIT signature is /// called, we have the following frames on the stack, listed form older to /// younger frames: /// /// * External caller (on the Wasm or native stack) /// /// This is whoever is calling our JS-implemented WIT export, using the /// canonical ABI. This might be another Wasm module or it might be some /// native code in the host. /// /// * Export glue Wasm code (on the Wasm stack) /// /// This is a synthesized function that understands both the canonical ABI and /// the SpiderMonkey API. It translates incoming arguments from the canonical /// ABI representation into SpiderMonkey values, calls the JS function that /// implements this export with those values, and then translates the /// function's outgoing results from SpiderMonkey values into the canonical /// ABI representation. /// /// * JavaScript function implementing the WIT signature (inside /// `spidermonkey.wasm`'s internal stack) /// /// This is the user-written JavaScript function that is being exported. It /// accepts and returns the JavaScript values that correspond to the interface /// types used in the WIT signature. pub struct SpiderMonkeyWasm<'a> { /// The filename to use for the JS. js_name: PathBuf, /// The JS source code. js: Cow<'a, str>, return_pointer_area_size: usize, return_pointer_area_align: usize, num_import_functions: Option, num_export_functions: Option, import_spidermonkey: bool, /// Function types that we use in this Wasm module. types: wasm_encoder::TypeSection, /// A map from wasm signature to its index in the `self.types` types /// section. We use this to reuse earlier type definitions when possible. wasm_sig_to_index: HashMap, /// The imports section containing the raw canonical ABI function imports /// for each imported function we are wrapping in glue. imports: wasm_encoder::ImportSection, /// The glue functions we've generated for imported canonical ABI functions /// thus far. import_glue_fns: Vec, /// A map from `module_name -> func_name -> (index, num_args)`. import_fn_name_to_index: HashMap>, exports: wasm_encoder::ExportSection, /// The glue functions we've generated for exported canonical ABI functions /// thus far, and their type index. export_glue_fns: Vec<(wasm_encoder::Function, u32)>, data_segments: DataSegments, sizes: SizeAlign, function_names: Vec<(u32, String)>, local_names: Vec<(u32, wasm_encoder::NameMap)>, } impl<'a> SpiderMonkeyWasm<'a> { /// Construct a new `SpiderMonkeyWasm` bindings generator using the given /// JavaScript module. pub fn new(js_name: impl Into, js: impl Into>) -> Self { let js_name = js_name.into(); let js = js.into(); SpiderMonkeyWasm { js_name, js, return_pointer_area_size: 0, return_pointer_area_align: 0, num_import_functions: None, num_export_functions: None, import_spidermonkey: false, types: wasm_encoder::TypeSection::new(), wasm_sig_to_index: Default::default(), imports: wasm_encoder::ImportSection::new(), import_glue_fns: Default::default(), import_fn_name_to_index: Default::default(), exports: wasm_encoder::ExportSection::new(), export_glue_fns: Default::default(), data_segments: DataSegments::new(1), sizes: Default::default(), function_names: Vec::new(), local_names: Vec::new(), } } /// Configure how `spidermonkey.wasm` is linked. /// /// By default, the whole `spidermonkey.wasm` module is embedded inside our /// generated glue module's `module` section, and then instantiated in the /// `instance` section. /// /// If `import` is `true`, then `spidermonkey.wasm` is not embedded into the /// generated glue module. Instead, the glue module imports a /// `spidermonkey.wasm` instance. pub fn import_spidermonkey(&mut self, import: bool) { self.import_spidermonkey = import; } fn intern_type(&mut self, wasm_sig: WasmSignature) -> u32 { if let Some(idx) = self.wasm_sig_to_index.get(&&wasm_sig) { return *idx; } let idx = self.types.len(); self.types.function( wasm_sig.params.iter().copied().map(convert_ty), wasm_sig.results.iter().copied().map(convert_ty), ); self.wasm_sig_to_index.insert(wasm_sig, idx); idx } fn link_spidermonkey_wasm( &mut self, modules: &mut wasm_encoder::ModuleSection, instances: &mut wasm_encoder::InstanceSection, aliases: &mut wasm_encoder::AliasSection, ) { if self.import_spidermonkey { // Import an instance that exports all the expected // `spidermonkey.wasm` things. let exports: Vec<_> = SMW_EXPORTS .iter() .map(|(name, sig)| { let idx = self.intern_type(sig.clone()); (*name, wasm_encoder::EntityType::Function(idx)) }) .chain(Some(( "memory", wasm_encoder::EntityType::Memory(wasm_encoder::MemoryType { minimum: 0, maximum: None, memory64: false, }), ))) .chain(Some(( "__indirect_function_table", wasm_encoder::EntityType::Table(wasm_encoder::TableType { element_type: wasm_encoder::ValType::FuncRef, minimum: 0, maximum: None, }), ))) .collect(); let instance_type_index = self.types.len(); self.types.instance(exports); self.imports.import( "spidermonkey", None, wasm_encoder::EntityType::Instance(instance_type_index), ); } else { // Embded `spidermonkey.wasm` in the modules section and then // instantiate it. This will involve adding its imports to our // import section and fowarding them along. let _ = (modules, instances); todo!() } // Regardless whether we imported an instance or instantiated an embedded // module, we now have an instance of `spidermonkey.wasm`. Alias its // exported functions and exported memory into this module's index // spaces. let instance_index = u32::try_from(self.import_fn_name_to_index.len()).unwrap(); aliases.instance_export(instance_index, wasm_encoder::ItemKind::Memory, "memory"); aliases.instance_export( instance_index, wasm_encoder::ItemKind::Table, "__indirect_function_table", ); for (name, _) in &*SMW_EXPORTS { aliases.instance_export(instance_index, wasm_encoder::ItemKind::Function, name); let idx = self.spidermonkey_import(name); self.function_names.push((idx, name.to_string())); } } /// Malloc `size` bytes and save the result to `local`. /// /// Note that `SMW_malloc` will never return `nullptr`. /// /// ```wat /// (local.set ${local} (call $SMW_malloc (i32.const ${size}))) /// ``` fn malloc_static_size<'b, F>(&mut self, func: &mut F, size: u32, result_local: u32) where F: InstructionSink<'b>, { // [] func.instruction(Instruction::I32Const(size as _)); // [i32] func.instruction(Instruction::Call(self.spidermonkey_import("SMW_malloc"))); // [i32] func.instruction(Instruction::LocalSet(result_local)); // [] } /// Malloc `size` bytes and save the result to `local`. Trap if `malloc` /// returned `nullptr`. /// /// Note that `SMW_malloc` will never return `nullptr`. /// /// ```wat /// (local.set ${result_local} (call $malloc (local.get ${size_local}))) /// ``` fn malloc_dynamic_size<'b, F>(&mut self, func: &mut F, size_local: u32, result_local: u32) where F: InstructionSink<'b>, { // [] func.instruction(Instruction::LocalGet(size_local)); // [i32] func.instruction(Instruction::Call(self.spidermonkey_import("SMW_malloc"))); // [i32] func.instruction(Instruction::LocalSet(result_local)); // [] } /// Copy data from the root glue module's linear memory into /// `spidermonkey.wasm`'s linear memory: /// /// ```wat /// (memory.copy 0 1 (local.get ${to_local}) /// (i32.const ${from_offset}) /// (i32.const ${len})) /// ``` fn copy_to_smw<'b, F>(&self, func: &mut F, from_offset: u32, to_local: u32, len: u32) where F: InstructionSink<'b>, { // [] func.instruction(Instruction::LocalGet(to_local)); // [i32] func.instruction(Instruction::I32Const(from_offset as _)); // [i32 i32] func.instruction(Instruction::I32Const(len as _)); // [i32 i32 i32] func.instruction(Instruction::MemoryCopy { src: GLUE_MEMORY, dst: SM_MEMORY, }); // [] } fn clear_js_operands<'b, F>(&self, func: &mut F) where F: InstructionSink<'b>, { // [] func.instruction(Instruction::Call( self.spidermonkey_import("SMW_clear_operands"), )); // [] } fn define_wizer_initialize( &mut self, funcs: &mut wasm_encoder::FunctionSection, code: &mut wasm_encoder::CodeSection, js_name_offset: u32, js_name_len: u32, js_offset: u32, js_len: u32, ) { assert_eq!(funcs.len(), code.len()); let wizer_init_index = self.wit_import_functions_len() + u32::try_from(SMW_EXPORTS.len()).unwrap() + funcs.len(); self.function_names .push((wizer_init_index, format!("wizer.initialize"))); let ty_index = self.intern_type(WasmSignature { params: vec![], results: vec![], retptr: false, indirect_params: false, }); funcs.function(ty_index); let locals = vec![(7, wasm_encoder::ValType::I32)]; let js_name_local = 0; let js_local = 1; let module_name_local = 2; let module_builder_local = 3; let table_size_local = 4; let func_name_local = 5; let ret_ptr_local = 6; let mut local_names = wasm_encoder::NameMap::new(); local_names.append(js_name_local, "js_name"); local_names.append(js_local, "js"); local_names.append(module_name_local, "module_name"); local_names.append(module_builder_local, "module_builder"); local_names.append(table_size_local, "table_size"); local_names.append(func_name_local, "func_name"); local_names.append(ret_ptr_local, "ret_ptr"); self.local_names.push((wizer_init_index, local_names)); let mut wizer_init = wasm_encoder::Function::new(locals); // Call `_initialize` because that must be called before any other // exports per the WASI reactor ABI. let init_index = self.spidermonkey_import("_initialize"); wizer_init.instruction(&Instruction::Call(init_index)); // Malloc space in `spidermonkey.wasm`'s linear memory for the JS file // name and the JS source. self.malloc_static_size(&mut wizer_init, js_name_len, js_name_local); self.malloc_static_size(&mut wizer_init, js_len, js_local); // Copy the data into the freshly allocated regions. self.copy_to_smw(&mut wizer_init, js_name_offset, js_name_local, js_name_len); self.copy_to_smw(&mut wizer_init, js_offset, js_local, js_len); // Allocate space in the `spidermonkey.wasm` memory for the return // pointer area and save it to the return pointer global. // // TODO: handle `self.return_pointer_area_align` here if self.return_pointer_area_size > 0 { self.malloc_static_size( &mut wizer_init, u32::try_from(self.return_pointer_area_size).unwrap(), ret_ptr_local, ); // [] wizer_init.instruction(&Instruction::LocalGet(ret_ptr_local)); // [i32] wizer_init.instruction(&Instruction::GlobalSet(RET_PTR_GLOBAL)); // [] } // Call `SMW_initialize_engine`: // // (call $SMW_initialize_engine) let smw_initialize_engine = self.spidermonkey_import("SMW_initialize_engine"); wizer_init.instruction(&Instruction::Call(smw_initialize_engine)); // Define a JS module for each WIT module that is imported. This JS // module will export each of our generated glue functions for that WIT // module. let smw_new_module_builder = self.spidermonkey_import("SMW_new_module_builder"); let import_fn_name_to_index = std::mem::replace(&mut self.import_fn_name_to_index, Default::default()); for (module, funcs) in &import_fn_name_to_index { // Malloc space for the module name. self.malloc_static_size( &mut wizer_init, u32::try_from(module.len()).unwrap(), module_name_local, ); // Copy the module name into the malloc'd space. let module_offset = self.data_segments.add(module.as_bytes().iter().copied()); self.copy_to_smw( &mut wizer_init, module_offset, module_name_local, u32::try_from(module.len()).unwrap(), ); // Call `SMW_new_module_builder`, passing it the module name: // // (call $SMW_new_module_builder (local.get ${module_name}) // (i32.const ${module.len()})) // local.set ${module_builder} wizer_init // [] .instruction(&Instruction::LocalGet(module_name_local)) // [i32] .instruction(&Instruction::I32Const(i32::try_from(module.len()).unwrap())) // [i32 i32] .instruction(&Instruction::Call(smw_new_module_builder)) // [i32] .instruction(&Instruction::LocalSet(module_builder_local)); // [] // Grow enough space in the function table for the functions we will // add to it. Check for failure to allocate and trap if so. // // (table.grow (ref.null) (i32.const ${funcs.len()})) // local.tee ${table_size} // i32.const -1 // i32.eq // if // unreachable // end wizer_init // [] .instruction(&Instruction::RefNull(wasm_encoder::ValType::FuncRef)) // [funcref] .instruction(&Instruction::I32Const(i32::try_from(funcs.len()).unwrap())) // [funcref i32] .instruction(&Instruction::TableGrow { table: 0 }) // [i32] .instruction(&Instruction::LocalTee(table_size_local)) // [i32] .instruction(&Instruction::I32Const(-1)) // [i32 i32] .instruction(&Instruction::I32Eq) // [i32] .instruction(&Instruction::If(wasm_encoder::BlockType::Empty)) // [] .instruction(&Instruction::Unreachable) // [] .instruction(&Instruction::End); // [] for (i, (func, (func_index, num_args))) in funcs.iter().enumerate() { // Malloc space for the function's name. self.malloc_static_size( &mut wizer_init, u32::try_from(func.len()).unwrap(), func_name_local, ); // Copy the function's name into the malloc'd space. let func_name_offset = self.data_segments.add(func.as_bytes().iter().copied()); self.copy_to_smw( &mut wizer_init, func_name_offset, func_name_local, u32::try_from(func.len()).unwrap(), ); // Set `table[orig_size + i]` to our synthesized import glue // function: // // (table.set (i32.add (i32.const ${i}) (local.get ${table_size})) // (ref.func ${func_index})) let glue_func_index = self.wit_import_glue_fn(*func_index); wizer_init // [] .instruction(&Instruction::I32Const(i32::try_from(i).unwrap())) // [i32] .instruction(&Instruction::LocalGet(table_size_local)) // [i32 i32] .instruction(&Instruction::I32Add) // [i32] .instruction(&Instruction::RefFunc(glue_func_index)) // [i32 funcref] .instruction(&Instruction::TableSet { table: 0 }); // [] // Call `SMW_module_builder_add_export` passing the index of the // function that we just inserted into the table: // // (call $SMW_module_builder_add_export (local.get ${module_builder}) // (local.get ${func_name}) // (i32.const ${func.len()}) // (i32.add (i32.const ${i}) (local.get ${table_size})) // (i32.const ${num_args})) let smw_module_builder_add_export = self.spidermonkey_import("SMW_module_builder_add_export"); wizer_init // [] .instruction(&Instruction::LocalGet(module_builder_local)) // [i32] .instruction(&Instruction::LocalGet(func_name_local)) // [i32 i32] .instruction(&Instruction::I32Const(i32::try_from(func.len()).unwrap())) // [i32 i32 i32] .instruction(&Instruction::I32Const(i32::try_from(i).unwrap())) // [i32 i32 i32 i32] .instruction(&Instruction::LocalGet(table_size_local)) // [i32 i32 i32 i32 i32] .instruction(&Instruction::I32Add) // [i32 i32 i32 i32] .instruction(&Instruction::I32Const(i32::try_from(*num_args).unwrap())) // [i32 i32 i32 i32 i32] .instruction(&Instruction::Call(smw_module_builder_add_export)); // [] } // Call `SMW_finish_module_builder` to register the module: // // (call $SMW_finish_module_builder (local.get ${module_builder})) let smw_finish_module_builder = self.spidermonkey_import("SMW_finish_module_builder"); wizer_init // [] .instruction(&Instruction::LocalGet(module_builder_local)) // [i32] .instruction(&Instruction::Call(smw_finish_module_builder)); // [] } // Call `SMW_eval_module`, passing it the pointers to the JS file name // and JS source: // // (call $SMW_eval_module (local.get 0) (local.get 1) (i32.const ${js_len})) let smw_eval_module = self.spidermonkey_import("SMW_eval_module"); wizer_init // [] .instruction(&Instruction::LocalGet(js_name_local)) // [i32] .instruction(&Instruction::LocalGet(js_local)) // [i32 i32] .instruction(&Instruction::I32Const(js_len as i32)) // [i32 i32 i32] .instruction(&Instruction::Call(smw_eval_module)); // [] wizer_init.instruction(&Instruction::End); code.function(&wizer_init); self.exports.export( "wizer.initialize", wasm_encoder::Export::Function(wizer_init_index), ); } } // ### Function Index Space // // The generated glue module's function index space is laid out as follows: // // ```text // |wit imports...|spidermonkey.wasm imports...|import glue...|export glue...|wizer.initialize| // ``` impl SpiderMonkeyWasm<'_> { /// Get the number of imported WIT functions. fn wit_import_functions_len(&self) -> u32 { self.num_import_functions .expect("must call `preprocess_all` before generating bindings") } /// Get the function index for the i^th WIT import. fn wit_import(&self, i: u32) -> u32 { i } /// Get the function index for the given spidermonkey function. fn spidermonkey_import(&self, name: &str) -> u32 { self.wit_import_functions_len() + u32::try_from( SMW_EXPORTS .iter() .position(|(n, _)| *n == name) .unwrap_or_else(|| panic!("unknown `spidermonkey.wasm` export: {}", name)), ) .unwrap() } /// Get the function index where WIT import glue functions start. fn wit_import_glue_fns_start(&self) -> u32 { self.wit_import_functions_len() + u32::try_from(SMW_EXPORTS.len()).unwrap() } /// Get the range of indices for our synthesized glue functions for WIT /// imports. fn wit_import_glue_fn_range(&self) -> Range { let start = self.wit_import_glue_fns_start(); let end = self.wit_export_start(); start..end } /// Get the function index for the i^th synthesized glue function for a WIT /// import. fn wit_import_glue_fn(&self, i: u32) -> u32 { assert!( i < self.wit_import_functions_len(), "{} < {}", i, self.wit_import_functions_len() ); let start = self.wit_import_glue_fns_start(); start + i } /// Get the function index where WIT export glue functions start. fn wit_export_start(&self) -> u32 { self.wit_import_glue_fns_start() + self.wit_import_functions_len() } fn wit_exports_len(&self) -> u32 { self.num_export_functions .expect("must call `preprocess_all` before generating bindings") } /// Get the function index for the i^th WIT export. fn wit_export(&self, i: u32) -> u32 { assert!(i < self.wit_exports_len()); self.wit_export_start() + i } } impl Generator for SpiderMonkeyWasm<'_> { fn preprocess_all(&mut self, imports: &[Interface], exports: &[Interface]) { assert!( self.num_import_functions.is_none() && self.num_export_functions.is_none(), "must call `preprocess_all` exactly once" ); assert!( exports.len() <= 1, "only one exported interface is currently supported" ); self.num_import_functions = Some(u32::try_from(imports.iter().map(|i| i.functions.len()).sum::()).unwrap()); self.num_export_functions = Some(u32::try_from(exports.iter().map(|i| i.functions.len()).sum::()).unwrap()); } fn preprocess_one(&mut self, iface: &Interface, _dir: Direction) { self.sizes.fill(iface); } fn type_record( &mut self, iface: &Interface, id: TypeId, name: &str, record: &Record, docs: &Docs, ) { let _ = (iface, id, name, record, docs); todo!() } fn type_tuple( &mut self, iface: &Interface, id: TypeId, name: &str, tuple: &Tuple, docs: &Docs, ) { let _ = (iface, id, name, tuple, docs); todo!() } fn type_flags( &mut self, iface: &Interface, id: TypeId, name: &str, flags: &Flags, docs: &Docs, ) { let _ = (iface, id, name, flags, docs); todo!() } fn type_variant( &mut self, iface: &Interface, id: TypeId, name: &str, variant: &Variant, docs: &Docs, ) { let _ = (iface, id, name, variant, docs); todo!() } fn type_union( &mut self, iface: &Interface, id: TypeId, name: &str, union: &Union, docs: &Docs, ) { let _ = (iface, id, name, union, docs); todo!() } fn type_option( &mut self, iface: &Interface, id: TypeId, name: &str, payload: &Type, docs: &Docs, ) { let _ = (iface, id, name, payload, docs); todo!() } fn type_expected( &mut self, iface: &Interface, id: TypeId, name: &str, expected: &Expected, docs: &Docs, ) { let _ = (iface, id, name, expected, docs); todo!() } fn type_enum(&mut self, iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { let _ = (iface, id, name, enum_, docs); todo!() } fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { let _ = (iface, ty); todo!() } fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { let _ = (iface, id, name, ty, docs); todo!() } fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { let _ = (iface, id, name, ty, docs); todo!() } fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { let _ = (iface, id, name, name, ty, docs); todo!() } fn import(&mut self, iface: &Interface, func: &Function) { assert!(!func.is_async, "async not supported yet"); // Add the raw Wasm import. let wasm_sig = iface.wasm_signature(AbiVariant::GuestImport, func); let type_index = self.intern_type(wasm_sig.clone()); let import_fn_index = self.wit_import(self.imports.len()); self.imports.import( &iface.name, Some(&func.name), wasm_encoder::EntityType::Function(type_index), ); let existing = self .import_fn_name_to_index .entry(iface.name.clone()) .or_default() .insert( func.name.clone(), (import_fn_index, u32::try_from(func.params.len()).unwrap()), ); assert!(existing.is_none()); self.function_names .push((import_fn_index, format!("{}.{}", iface.name, func.name))); let mut bindgen = Bindgen::new(self, &wasm_sig, func, abi::LiftLower::LowerArgsLiftResults); iface.call( AbiVariant::GuestImport, abi::LiftLower::LowerArgsLiftResults, func, &mut bindgen, ); let func_encoder = bindgen.finish(); self.import_glue_fns.push(func_encoder); } fn export(&mut self, iface: &Interface, func: &Function) { assert!(!func.is_async, "async not supported yet"); let wasm_sig = iface.wasm_signature(AbiVariant::GuestExport, func); let type_index = self.intern_type(wasm_sig.clone()); let export_fn_index = self.wit_export(self.exports.len()); self.exports .export(&func.name, wasm_encoder::Export::Function(export_fn_index)); self.function_names .push((export_fn_index, format!("{}.{}", iface.name, func.name))); let mut bindgen = Bindgen::new(self, &wasm_sig, func, abi::LiftLower::LiftArgsLowerResults); iface.call( AbiVariant::GuestExport, abi::LiftLower::LiftArgsLowerResults, func, &mut bindgen, ); let func_encoder = bindgen.finish(); self.export_glue_fns.push((func_encoder, type_index)); } fn finish_one(&mut self, _iface: &Interface, _files: &mut Files) { // Nothing to do until wil finish all interfaces and generate our Wasm // glue code. } fn finish_all(&mut self, files: &mut Files) { let mut module = wasm_encoder::Module::new(); let mut modules = wasm_encoder::ModuleSection::new(); let mut instances = wasm_encoder::InstanceSection::new(); let mut aliases = wasm_encoder::AliasSection::new(); let mut mems = wasm_encoder::MemorySection::new(); let mut funcs = wasm_encoder::FunctionSection::new(); let mut globals = wasm_encoder::GlobalSection::new(); let mut elems = wasm_encoder::ElementSection::new(); let mut code = wasm_encoder::CodeSection::new(); self.link_spidermonkey_wasm(&mut modules, &mut instances, &mut aliases); // Define the return pointer global. globals.global( wasm_encoder::GlobalType { val_type: wasm_encoder::ValType::I32, mutable: true, }, &Instruction::I32Const(0), ); // Re-export `spidermonkey.wasm`'s memory and canonical ABI functions. self.exports .export("memory", wasm_encoder::Export::Memory(SM_MEMORY)); self.exports.export( "canonical_abi_free", wasm_encoder::Export::Function(self.spidermonkey_import("canonical_abi_free")), ); self.exports.export( "canonical_abi_realloc", wasm_encoder::Export::Function(self.spidermonkey_import("canonical_abi_realloc")), ); // Add the WIT function imports (add their import glue functions) to // the module. // // Each of these functions has the Wasm equivalent of this function // signature: // // using JSNative = bool (JSContext* cx, unsigned argc, JSValue *vp); let js_native_type_index = self.intern_type(WasmSignature { params: vec![ // JSContext *cx WasmType::I32, // unsigned argc WasmType::I32, // JSValue *vp WasmType::I32, ], results: vec![ // bool WasmType::I32, ], retptr: false, indirect_params: false, }); for f in &self.import_glue_fns { funcs.function(js_native_type_index); code.function(f); } for (f, ty_idx) in &self.export_glue_fns { funcs.function(*ty_idx); code.function(f); } // We will use `ref.func` to get a reference to each of our synthesized // import glue functions, so we need to declare them as reference-able. let func_indices: Vec = self.wit_import_glue_fn_range().collect(); if !func_indices.is_empty() { elems.declared( wasm_encoder::ValType::FuncRef, wasm_encoder::Elements::Functions(&func_indices), ); } let js_name = self.js_name.display().to_string(); let js_name_offset = self.data_segments.add(js_name.as_bytes().iter().copied()); let js_offset = self.data_segments.add(self.js.as_bytes().iter().copied()); self.define_wizer_initialize( &mut funcs, &mut code, js_name_offset, u32::try_from(js_name.len()).unwrap(), js_offset, u32::try_from(self.js.len()).unwrap(), ); module.section(&self.types).section(&self.imports); if !self.import_spidermonkey { module.section(&modules).section(&instances); } mems.memory(self.data_segments.memory_type()); let data = self.data_segments.take_data(); // Fill out the `names` section to assist in debugging the generated // wasm. let mut names = wasm_encoder::NameSection::new(); self.function_names.sort_by_key(|a| a.0); let mut function_names = wasm_encoder::NameMap::new(); for (i, name) in self.function_names.iter() { function_names.append(*i, name); } names.functions(&function_names); self.local_names.sort_by_key(|a| a.0); let mut local_names = wasm_encoder::IndirectNameMap::new(); for (i, names) in self.local_names.iter() { local_names.append(*i, names); } names.locals(&local_names); let mut table_names = wasm_encoder::NameMap::new(); table_names.append(0, "sm_function_table"); names.tables(&table_names); let mut memory_names = wasm_encoder::NameMap::new(); memory_names.append(SM_MEMORY, "sm_mem"); memory_names.append(GLUE_MEMORY, "glue_mem"); names.memories(&memory_names); module .section(&aliases) .section(&funcs) .section(&mems) .section(&globals) .section(&self.exports) .section(&elems) .section(&code) .section(&data) .section(&names); let wasm = module.finish(); let js_file_stem = self.js_name.file_stem().unwrap_or_else(|| { panic!( "input JavaScript file path does not have a file stem: {}", self.js_name.display() ) }); let js_file_stem = js_file_stem.to_str().unwrap_or_else(|| { panic!( "input JavaScript file path is not UTF-8 representable: {}", self.js_name.display() ) }); let wasm_name = format!("{}.wasm", js_file_stem); files.push(&wasm_name, &wasm); } } trait InstructionSink<'a> { fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>); } impl<'a> InstructionSink<'a> for wasm_encoder::Function { fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>) { wasm_encoder::Function::instruction(self, &inst); } } impl<'a> InstructionSink<'a> for Vec> { fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>) { self.push(inst); } } const RET_PTR_GLOBAL: u32 = 0; const SM_MEMORY: u32 = 0; const GLUE_MEMORY: u32 = 1; fn convert_ty(ty: WasmType) -> wasm_encoder::ValType { match ty { WasmType::I32 => wasm_encoder::ValType::I32, WasmType::I64 => wasm_encoder::ValType::I64, WasmType::F32 => wasm_encoder::ValType::F32, WasmType::F64 => wasm_encoder::ValType::F64, } } struct Bindgen<'a, 'b> { gen: &'a mut SpiderMonkeyWasm<'b>, sig: &'a WasmSignature, lift_lower: abi::LiftLower, locals: Vec, js_count: u32, blocks: Vec>>, block_results: Vec>, /// The `i`th JS operand that is our current iteration element, if any. iter_elem: Vec, /// The Wasm local for our current iteration's base pointer, if any. iter_base_pointer: Vec, /// Allocations to free after the call. /// /// `(local holding pointer, local holding length, alignment)` to_free: Vec<(u32, u32, u32)>, } impl<'a, 'b> Bindgen<'a, 'b> { fn new( gen: &'a mut SpiderMonkeyWasm<'b>, sig: &'a WasmSignature, func: &'a Function, lift_lower: abi::LiftLower, ) -> Self { let js_count = match lift_lower { abi::LiftLower::LiftArgsLowerResults => 0, abi::LiftLower::LowerArgsLiftResults => u32::try_from(func.params.len()).unwrap(), }; let mut insts = vec![]; if lift_lower == abi::LiftLower::LowerArgsLiftResults && !func.params.is_empty() { // Initialize `bindgen.cpp`'s JS value operands vector with the // arguments given to this function // // [] insts.push(Instruction::LocalGet(1)); // [i32] insts.push(Instruction::LocalGet(2)); // [i32 i32] insts.push(Instruction::Call( gen.spidermonkey_import("SMW_fill_operands"), )); // [] } Bindgen { gen, sig, lift_lower, locals: vec![], js_count, blocks: vec![insts], block_results: vec![], iter_elem: vec![], iter_base_pointer: vec![], to_free: vec![], } } fn inst(&mut self, inst: Instruction<'a>) { self.current_block().push(inst); } fn current_block(&mut self) -> &mut Vec> { self.blocks.last_mut().unwrap() } fn pop_block(&mut self) -> (Vec>, Vec) { ( self.blocks.pop().unwrap(), self.block_results.pop().unwrap(), ) } /// Create a new Wasm local for this function and return its index. fn new_local(&mut self, ty: wasm_encoder::ValType) -> u32 { let offset = match self.lift_lower { abi::LiftLower::LiftArgsLowerResults => self.sig.params.len(), // `JSNative` functions take three `i32` arguments: cx, argc, and // vp. abi::LiftLower::LowerArgsLiftResults => 3, }; let idx = u32::try_from(self.locals.len() + offset).unwrap(); self.locals.push(ty); idx } /// Get the next JS value operand. fn next_js(&mut self) -> Operand { let js = self.js_count; self.js_count += 1; Operand::Js(js) } /// Finish generating these bindings and return the encoded Wasm function. fn finish(self) -> wasm_encoder::Function { // TODO: Coalesce contiguous locals of the same type here into the // compact encoding, like `[(i32, 3)]` rather than `[(i32, 1), (i32, 1), // (i32, 1)]`. let mut f = wasm_encoder::Function::new(self.locals.into_iter().map(|l| (1, l))); // By the time we get here, we should have finished all nested blocks. assert_eq!(self.blocks.len(), 1); for inst in &self.blocks[0] { f.instruction(inst); } f.instruction(&Instruction::End); f } } /// Operands are locals that either hold the value directly or refer to an index /// in `bindgen.cpp`'s JS operand vector depending on if we're dealing with a JS /// or Wasm value: /// /// * When we are _importing_ a function, we are lifting arguments and lowering /// results, so `operands` always refer to the `n`th local and `results` refer /// to the `n`th value in `bindgen.cpp`'s JS value vector. /// /// * When we are _exporting_ a function, we are lowering arguments and lifting /// results, so `operands` always refer to the `n`th value in `bindgen.cpp`'s /// JS value vector and `results` always refer to the `n`th local. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Operand { /// The `n`th JS value in `bindgen.cpp`'s operand vector. Js(u32), /// The `n`th Wasm local. Wasm(u32), /// A "unit" void type Unit, } impl Operand { fn unwrap_js(&self) -> u32 { match *self { Operand::Js(js) => js, Operand::Wasm(_) => panic!("Operand::unwrap_js on a Wasm operand"), Operand::Unit => panic!("Operand::unwrap_js on a Unit operand"), } } fn unwrap_wasm(&self) -> u32 { match *self { Operand::Wasm(w) => w, Operand::Js(_) => panic!("Operand::unwrap_wasm on a JS operand"), Operand::Unit => panic!("Operand::unwrap_wasm on a Unit operand"), } } fn unwrap_unit(&self) { match *self { Operand::Unit => {} Operand::Wasm(_) => panic!("Operand::unwrap_unit on a Wasm operand"), Operand::Js(_) => panic!("Operand::unwrap_unit on a JS operand"), } } } fn pop_wasm(operands: &mut Vec) -> u32 { match operands.pop() { Some(op) => op.unwrap_wasm(), None => panic!("`pop_wasm` with an empty stack"), } } fn pop_js(operands: &mut Vec) -> u32 { match operands.pop() { Some(op) => op.unwrap_js(), None => panic!("`pop_js` with an empty stack"), } } fn sm_mem_arg(offset: u32) -> wasm_encoder::MemArg { wasm_encoder::MemArg { offset: offset as u64, align: 0, memory_index: SM_MEMORY, } } impl abi::Bindgen for Bindgen<'_, '_> { type Operand = Operand; fn emit( &mut self, _iface: &Interface, inst: &abi::Instruction<'_>, operands: &mut Vec, results: &mut Vec, ) { match inst { abi::Instruction::GetArg { nth } => { let nth = u32::try_from(*nth).unwrap(); results.push(match self.lift_lower { abi::LiftLower::LiftArgsLowerResults => Operand::Wasm(nth), abi::LiftLower::LowerArgsLiftResults => Operand::Js(nth), }); } abi::Instruction::I32Const { val: _ } => todo!(), abi::Instruction::Bitcasts { casts: _ } => todo!(), abi::Instruction::ConstZero { tys: _ } => todo!(), abi::Instruction::I32Load { offset } => { let addr = pop_wasm(operands); let local = self.new_local(wasm_encoder::ValType::I32); // [] self.inst(Instruction::LocalGet(addr)); // [i32] self.inst(Instruction::I32Load(sm_mem_arg((*offset as u32).into()))); // [i32] self.inst(Instruction::LocalSet(local)); // [] results.push(Operand::Wasm(local)); } abi::Instruction::I32Load8U { offset: _ } => todo!(), abi::Instruction::I32Load8S { offset: _ } => todo!(), abi::Instruction::I32Load16U { offset: _ } => todo!(), abi::Instruction::I32Load16S { offset: _ } => todo!(), abi::Instruction::I64Load { offset: _ } => todo!(), abi::Instruction::F32Load { offset: _ } => todo!(), abi::Instruction::F64Load { offset: _ } => todo!(), abi::Instruction::I32Store { offset } => { let addr = pop_wasm(operands); let val = pop_wasm(operands); // [] self.inst(Instruction::LocalGet(addr)); // [i32] self.inst(Instruction::LocalGet(val)); // [i32 i32] self.inst(Instruction::I32Store(sm_mem_arg((*offset as u32).into()))); // [] } abi::Instruction::I32Store8 { offset: _ } => todo!(), abi::Instruction::I32Store16 { offset: _ } => todo!(), abi::Instruction::I64Store { offset: _ } => todo!(), abi::Instruction::F32Store { offset: _ } => todo!(), abi::Instruction::F64Store { offset: _ } => todo!(), abi::Instruction::I32FromChar => todo!(), abi::Instruction::I64FromU64 => todo!(), abi::Instruction::I64FromS64 => todo!(), abi::Instruction::I32FromU32 => { let js = pop_js(operands); let local = self.new_local(wasm_encoder::ValType::I32); // [] self.inst(Instruction::I32Const(js as i32)); // [i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_i32_from_u32"), )); // [i32] self.inst(Instruction::LocalSet(local)); // [] results.push(Operand::Wasm(local)); } abi::Instruction::I32FromS32 => todo!(), abi::Instruction::I32FromU16 => todo!(), abi::Instruction::I32FromS16 => todo!(), abi::Instruction::I32FromU8 => todo!(), abi::Instruction::I32FromS8 => todo!(), abi::Instruction::F32FromFloat32 => todo!(), abi::Instruction::F64FromFloat64 => todo!(), abi::Instruction::S8FromI32 => todo!(), abi::Instruction::U8FromI32 => todo!(), abi::Instruction::S16FromI32 => todo!(), abi::Instruction::U16FromI32 => todo!(), abi::Instruction::S32FromI32 => todo!(), abi::Instruction::U32FromI32 => { let local = pop_wasm(operands); let result = self.next_js(); // [] self.inst(Instruction::LocalGet(local)); // [i32] self.inst(Instruction::I32Const(result.unwrap_js() as i32)); // [i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_u32_from_i32"), )); // [] results.push(result); } abi::Instruction::S64FromI64 => todo!(), abi::Instruction::U64FromI64 => todo!(), abi::Instruction::CharFromI32 => todo!(), abi::Instruction::Float32FromF32 => todo!(), abi::Instruction::Float64FromF64 => todo!(), abi::Instruction::I32FromBorrowedHandle { ty: _ } => todo!(), abi::Instruction::I32FromOwnedHandle { ty: _ } => todo!(), abi::Instruction::HandleOwnedFromI32 { ty: _ } => todo!(), abi::Instruction::HandleBorrowedFromI32 { ty: _ } => todo!(), abi::Instruction::ListCanonLower { .. } => todo!(), abi::Instruction::StringLower { realloc } => { let js = pop_js(operands); let ptr = self.new_local(wasm_encoder::ValType::I32); let len = self.new_local(wasm_encoder::ValType::I32); // Make sure our return pointer area can hold at least two // `u32`s, since we will use it that way with // `SMW_{list,string}_canon_lower`. self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(1); self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(4); // [] self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); // [i32] self.inst(Instruction::I32Const(js as _)); // [i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_string_canon_lower"), )); // [] // Read the pointer from the return pointer area. // // [] self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); // [i32] self.inst(Instruction::I32Load(sm_mem_arg(0))); // [i32] self.inst(Instruction::LocalSet(ptr)); // [] // Read the length from the return pointer area. // // [] self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); // [i32] self.inst(Instruction::I32Load(sm_mem_arg(4))); // [i32] self.inst(Instruction::LocalSet(len)); // [] // If `realloc` is `None`, then we are responsible for freeing // this pointer after the call. if realloc.is_none() { self.to_free.push((ptr, len, 1)); } results.push(Operand::Wasm(ptr)); results.push(Operand::Wasm(len)); } abi::Instruction::ListLower { element, realloc } => { let iterable = pop_js(operands); let (block, block_results) = self.pop_block(); assert!(block_results.is_empty()); let iter_elem = self.iter_elem.pop().unwrap(); let iter_base_pointer = self.iter_base_pointer.pop().unwrap(); let length = self.new_local(wasm_encoder::ValType::I32); let index = self.new_local(wasm_encoder::ValType::I32); let ptr = self.new_local(wasm_encoder::ValType::I32); let size = self.gen.sizes.size(element); let align = self.gen.sizes.align(element); // [] self.inst(Instruction::I32Const(iterable as i32)); // [i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_spread_into_array"), )); // [i32] self.inst(Instruction::LocalSet(length)); // [] // `malloc` space for the result. self.gen .malloc_dynamic_size(self.blocks.last_mut().unwrap(), length, ptr); // Create a new block and loop. The block is so we can branch to // it to exit out of the loop. // // Also re-zero the index since the current block itself might // be reused multiple times if it is part of a loop body. // // [] self.inst(Instruction::I32Const(0)); // [i32] self.inst(Instruction::LocalSet(index)); // [] self.inst(Instruction::Block(wasm_encoder::BlockType::Empty)); // [] self.inst(Instruction::Loop(wasm_encoder::BlockType::Empty)); // [] // Check the loop's exit condition: `index >= length`. // // [] self.inst(Instruction::LocalGet(index)); // [i32] self.inst(Instruction::LocalGet(length)); // [i32 i32] self.inst(Instruction::I32GeU); // [i32] self.inst(Instruction::BrIf(1)); // [] // Update the element for this iteration. // // [] self.inst(Instruction::I32Const(iterable as i32)); // [i32] self.inst(Instruction::LocalGet(index)); // [i32 32] self.inst(Instruction::I32Const(iter_elem as i32)); // [i32 i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_get_array_element"), )); // [] // Update the base pointer for this iteration. // // [] self.inst(Instruction::LocalGet(index)); // [i32] self.inst(Instruction::I32Const(u32::try_from(size).unwrap() as _)); // [i32 i32] self.inst(Instruction::I32Mul); // [i32] self.inst(Instruction::LocalGet(ptr)); // [i32 i32] self.inst(Instruction::I32Add); // [i32] self.inst(Instruction::LocalSet(iter_base_pointer)); // [] // Now do include the snippet that lowers a single list element! self.current_block().extend(block); // Increment our index counter. // // [] self.inst(Instruction::LocalGet(index)); // [i32] self.inst(Instruction::I32Const(1)); // [i32 i32] self.inst(Instruction::I32Add); // [i32] self.inst(Instruction::LocalSet(index)); // [] // Unconditionally jump back to the loop head, and close out our blocks. // // [] self.inst(Instruction::Br(0)); // [] self.inst(Instruction::End); // [] self.inst(Instruction::End); // [] // If `realloc` is `None`, then we are responsible for freeing // this pointer after the call. if realloc.is_none() { self.to_free .push((ptr, length, u32::try_from(align).unwrap())); } results.push(Operand::Wasm(ptr)); results.push(Operand::Wasm(length)); } abi::Instruction::ListCanonLift { .. } => todo!(), abi::Instruction::StringLift { free } => { let len = pop_wasm(operands); let ptr = pop_wasm(operands); let result = self.next_js(); // [] self.inst(Instruction::LocalGet(ptr)); // [i32] self.inst(Instruction::LocalGet(len)); // [i32 i32] self.inst(Instruction::I32Const(result.unwrap_js() as i32)); // [i32 i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_string_canon_lift"), )); // [] if let Some(free) = free { // [] self.inst(Instruction::LocalGet(ptr)); // [i32] self.inst(Instruction::LocalGet(len)); // [i32 i32] self.inst(Instruction::I32Const(1)); // [i32 i32 i32] self.inst(Instruction::Call(self.gen.spidermonkey_import(free))); // [] } results.push(result); } abi::Instruction::ListLift { element, free, ty: _, } => { let len = pop_wasm(operands); let ptr = pop_wasm(operands); let (block, block_results) = self.pop_block(); assert_eq!(block_results.len(), 1); let iter_base_pointer = self.iter_base_pointer.pop().unwrap(); let index = self.new_local(wasm_encoder::ValType::I32); let size = self.gen.sizes.size(element); let align = self.gen.sizes.align(element); let result = self.next_js(); // Create a new JS array object that will be the result of this // lifting. // // [] self.inst(Instruction::I32Const(result.unwrap_js() as i32)); // [i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_new_array"), )); // [] // Create a block and a loop. The block is for branching to when // we need to exit the loop. // // Also re-zero the loop index because it might be reused across // multiple loops if the current block itself is also a loop // body. // // [] self.inst(Instruction::Block(wasm_encoder::BlockType::Empty)); // [] self.inst(Instruction::I32Const(0)); // [i32] self.inst(Instruction::LocalSet(index)); // [] self.inst(Instruction::Loop(wasm_encoder::BlockType::Empty)); // [] // Check for our loop's exit condition: `index >= len`. // // [] self.inst(Instruction::LocalGet(index)); // [i32] self.inst(Instruction::LocalGet(len)); // [i32 i32] self.inst(Instruction::I32GeU); // [i32] self.inst(Instruction::BrIf(1)); // [] // Update the base pointer for this iteration. // // [] self.inst(Instruction::LocalGet(index)); // [i32] self.inst(Instruction::I32Const(u32::try_from(size).unwrap() as _)); // [i32 i32] self.inst(Instruction::I32Mul); // [i32] self.inst(Instruction::LocalGet(ptr)); // [i32 i32] self.inst(Instruction::I32Add); // [i32] self.inst(Instruction::LocalSet(iter_base_pointer)); // [] self.current_block().extend(block); // Append the result of this iteration's lifting to our JS array. // // [] self.inst(Instruction::I32Const(result.unwrap_js() as i32)); // [i32] self.inst(Instruction::I32Const(block_results[0].unwrap_js() as i32)); // [i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_array_push"), )); // [] // Increment the index variable, unconditionally jump back to // the head of the loop, and close out our blocks. // // [] self.inst(Instruction::I32Const(1)); // [i32] self.inst(Instruction::LocalGet(index)); // [i32 i32] self.inst(Instruction::I32Add); // [i32] self.inst(Instruction::LocalSet(index)); // [] self.inst(Instruction::Br(0)); // [] self.inst(Instruction::End); // [] self.inst(Instruction::End); // [] if let Some(free) = free { // [] self.inst(Instruction::LocalGet(ptr)); // [i32] self.inst(Instruction::LocalGet(len)); // [i32 i32] self.inst(Instruction::I32Const(u32::try_from(align).unwrap() as _)); // [i32 i32 i32] self.inst(Instruction::Call(self.gen.spidermonkey_import(free))); // [] } results.push(result); } abi::Instruction::IterElem { element: _ } => { let iter_elem = self.next_js(); self.iter_elem.push(iter_elem.unwrap_js()); results.push(iter_elem); } abi::Instruction::IterBasePointer => { let iter_base_pointer = self.new_local(wasm_encoder::ValType::I32); self.iter_base_pointer.push(iter_base_pointer); results.push(Operand::Wasm(iter_base_pointer)); } abi::Instruction::RecordLower { record: _, name: _, ty: _, } => todo!(), abi::Instruction::RecordLift { record: _, name: _, ty: _, } => todo!(), abi::Instruction::TupleLower { tuple: _, ty: _ } => todo!(), abi::Instruction::TupleLift { tuple: _, ty: _ } => todo!(), abi::Instruction::FlagsLower { flags: _, name: _, ty: _, } => todo!(), abi::Instruction::FlagsLift { flags: _, name: _, ty: _, } => todo!(), abi::Instruction::VariantPayloadName => todo!(), abi::Instruction::VariantLower { variant: _, name: _, ty: _, results: _, } => todo!(), abi::Instruction::VariantLift { variant: _, name: _, ty: _, } => todo!(), abi::Instruction::OptionLower { .. } => todo!(), abi::Instruction::OptionLift { .. } => todo!(), abi::Instruction::ExpectedLower { .. } => todo!(), abi::Instruction::ExpectedLift { .. } => todo!(), abi::Instruction::UnionLower { .. } => todo!(), abi::Instruction::UnionLift { .. } => todo!(), abi::Instruction::EnumLower { enum_: _, name: _, ty: _, } => todo!(), abi::Instruction::EnumLift { enum_: _, name: _, ty: _, } => todo!(), abi::Instruction::CallWasm { iface, name, sig } => { // Push the Wasm arguments. // // [] let locals: Vec<_> = sig.params.iter().map(|_| pop_wasm(operands)).collect(); for local in locals.into_iter().rev() { self.inst(Instruction::LocalGet(local)); } // [A...] let func_index = self .gen .import_fn_name_to_index .get(&iface.name) .unwrap() .get(*name) .unwrap() .0; self.inst(Instruction::Call(func_index)); // [R...] // Allocate locals for the results and pop the return values off // the Wasm stack, saving each of them to the associated local. let locals: Vec<_> = sig .results .iter() .map(|ty| self.new_local(convert_ty(*ty))) .collect(); // [R...] for l in locals.iter().rev() { self.inst(Instruction::LocalSet(*l)); } // [] results.extend(locals.into_iter().map(Operand::Wasm)); for (ptr, len, alignment) in mem::replace(&mut self.to_free, vec![]) { // [] self.inst(Instruction::LocalGet(ptr)); // [i32] self.inst(Instruction::LocalGet(len)); // [i32 i32] self.inst(Instruction::I32Const(alignment as _)); // [i32 i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("canonical_abi_free"), )); // [] } } abi::Instruction::CallInterface { module: _, func } => { // TODO: Rather than always dynamically pushing all of our JS // arguments, make `SMW_call_{0,1,...,n}` up to the largest // common `n` so we can directly pass the arguments for most // function calls. // Push the JS arguments. let js_args: Vec<_> = func.params.iter().map(|_| pop_js(operands)).collect(); for js in js_args.into_iter().rev() { // [] self.inst(Instruction::I32Const(js as _)); // [i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_push_arg"), )); // [] } // TODO: Rather than `malloc`ing the name for each call, we // should pre-`malloc` them in the `wizer.initialize` function, // add a global to the glue module that points to the // pre-`malloc`ed name for each exported function, and then // reuse those that pre-`malloc`ed name on each call. // Malloc space for the function name. let func_name_local = self.new_local(wasm_encoder::ValType::I32); self.gen.malloc_static_size( self.blocks.last_mut().unwrap(), u32::try_from(func.name.len()).unwrap() + 1, func_name_local, ); // Copy the function name from the glue Wasm module's memory // into the `malloc`ed space in the `spidermonkey.wasm`'s // memory. let func_name_offset = self .gen .data_segments .add(func.name.to_snake_case().as_bytes().iter().copied()); self.gen.copy_to_smw( self.blocks.last_mut().unwrap(), func_name_offset, func_name_local, u32::try_from(func.name.len()).unwrap(), ); let (first_result, num_results) = match &func.result { // If there aren't any function results, then this argument // to `SMW_call` is going to be ignored. Use a highly // visible placeholder so that if this is ever accidentally // used it is easier to debug. Type::Unit => { results.push(Operand::Unit); (0xffffffff, 0) } _ => { let js = self.next_js(); results.push(js); (js.unwrap_js(), 1) } }; // Make the call. // // [] self.inst(Instruction::LocalGet(func_name_local)); // [i32] self.inst(Instruction::I32Const( i32::try_from(func.name.len()).unwrap(), )); // [i32 i32] self.inst(Instruction::I32Const(num_results)); // [i32 i32 i32] self.inst(Instruction::I32Const(first_result as i32)); // [i32 i32 i32 i32] self.inst(Instruction::Call(self.gen.spidermonkey_import("SMW_call"))); // [] } abi::Instruction::CallWasmAsyncExport { .. } => todo!(), abi::Instruction::CallWasmAsyncImport { .. } => todo!(), abi::Instruction::Return { func, amt } => { match self.lift_lower { abi::LiftLower::LowerArgsLiftResults => { match &func.result { Type::Unit => { operands[0].unwrap_unit(); } _ => { // Attach the return values to the `JS::CallArgs`: // build up the return values via a series of // `SMW_push_return_value` calls, followed by a // single `SMW_finish_returns` call. // // TODO: introduce fast path intrinsics for common // small numbers of return values so that we don't // have to do multiple intrinsic calls here, and can // instead do a single `SMW_return_{1,2,...,n}` // call. let val = pop_js(operands); // [] self.inst(Instruction::I32Const(val as _)); // [i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_push_return_value"), )); // [] self.inst(Instruction::LocalGet(1)); // [i32] self.inst(Instruction::LocalGet(2)); // [i32 i32] self.inst(Instruction::Call( self.gen.spidermonkey_import("SMW_finish_returns"), )); // [] } } // NB: only clear the JS operands after we've attached // the return value(s) to the `JS::CallArgs`. self.gen.clear_js_operands(self.blocks.last_mut().unwrap()); // Return `true`, meaning that a JS exception was not thrown. // // [] self.inst(Instruction::I32Const(1)); // [i32] self.inst(Instruction::Return); // [] } abi::LiftLower::LiftArgsLowerResults => { self.gen.clear_js_operands(self.blocks.last_mut().unwrap()); // Get the return values out of their locals and push // them onto the Wasm stack. // // [] for _ in 0..*amt { let local = pop_wasm(operands); self.inst(Instruction::LocalGet(local)); } // [R...] self.inst(Instruction::Return); // [] } } } abi::Instruction::UnitLower { .. } => { operands[0].unwrap_unit(); } abi::Instruction::UnitLift { .. } => { results.push(Operand::Unit); } abi::Instruction::I32FromBool { .. } => todo!(), abi::Instruction::BoolFromI32 { .. } => todo!(), abi::Instruction::ReturnAsyncExport { .. } => todo!(), abi::Instruction::ReturnAsyncImport { .. } => todo!(), abi::Instruction::Malloc { .. } => todo!(), abi::Instruction::Free { .. } => todo!(), } } fn return_pointer(&mut self, _iface: &Interface, size: usize, align: usize) -> Self::Operand { self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); let local = self.new_local(wasm_encoder::ValType::I32); // [] self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); // [i32] self.inst(Instruction::LocalSet(local)); // [] Operand::Wasm(local) } fn push_block(&mut self) { self.blocks.push(vec![]); } fn finish_block(&mut self, results: &mut Vec) { self.block_results.push(results.to_vec()); } fn sizes(&self) -> &SizeAlign { &self.gen.sizes } fn is_list_canonical(&self, _iface: &Interface, _ty: &Type) -> bool { // TODO: we will want to support canonical lists for the various typed // arrays. false } }