Files
simple-rust-tests/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/lib.rs

2235 lines
80 KiB
Rust

//! 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<Cow<'a, str>>) -> 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<u32>,
num_export_functions: Option<u32>,
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<WasmSignature, u32>,
/// 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<wasm_encoder::Function>,
/// A map from `module_name -> func_name -> (index, num_args)`.
import_fn_name_to_index: HashMap<String, HashMap<String, (u32, u32)>>,
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<PathBuf>, js: impl Into<Cow<'a, str>>) -> 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<u32> {
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::<usize>()).unwrap());
self.num_export_functions =
Some(u32::try_from(exports.iter().map(|i| i.functions.len()).sum::<usize>()).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<u32> = 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<wasm_encoder::Instruction<'a>> {
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<wasm_encoder::ValType>,
js_count: u32,
blocks: Vec<Vec<Instruction<'a>>>,
block_results: Vec<Vec<Operand>>,
/// The `i`th JS operand that is our current iteration element, if any.
iter_elem: Vec<u32>,
/// The Wasm local for our current iteration's base pointer, if any.
iter_base_pointer: Vec<u32>,
/// 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<Instruction<'a>> {
self.blocks.last_mut().unwrap()
}
fn pop_block(&mut self) -> (Vec<Instruction<'a>>, Vec<Operand>) {
(
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<Operand>) -> u32 {
match operands.pop() {
Some(op) => op.unwrap_wasm(),
None => panic!("`pop_wasm` with an empty stack"),
}
}
fn pop_js(operands: &mut Vec<Operand>) -> 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<Self::Operand>,
results: &mut Vec<Self::Operand>,
) {
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::Operand>) {
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
}
}