use crate::js::Context;
use crate::wit::InstructionData;
use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction};
use anyhow::{anyhow, bail, Error};
use walrus::Module;
pub struct Builder<'a, 'b> {
pub cx: &'a mut Context<'b>,
constructor: Option<String>,
method: Option<bool>,
catch: bool,
log_error: bool,
}
pub struct JsBuilder<'a, 'b> {
cx: &'a mut Context<'b>,
prelude: String,
finally: String,
tmp: usize,
args: Vec<String>,
stack: Vec<String>,
}
pub struct JsFunction {
pub code: String,
pub ts_sig: String,
pub js_doc: String,
pub ts_arg_tys: Vec<String>,
pub ts_ret_ty: Option<String>,
}
impl<'a, 'b> Builder<'a, 'b> {
pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> {
Builder {
log_error: false,
cx,
constructor: None,
method: None,
catch: false,
}
}
pub fn method(&mut self, consumed: bool) {
self.method = Some(consumed);
}
pub fn constructor(&mut self, class: &str) {
self.constructor = Some(class.to_string());
}
pub fn catch(&mut self, catch: bool) {
self.catch = catch;
}
pub fn log_error(&mut self, log: bool) {
self.log_error = log;
}
pub fn process(
&mut self,
adapter: &Adapter,
instructions: &[InstructionData],
explicit_arg_names: &Option<Vec<String>>,
) -> Result<JsFunction, Error> {
if self
.cx
.aux
.imports_with_assert_no_shim
.contains(&adapter.id)
{
bail!("generating a shim for something asserted to have no shim");
}
let mut params = adapter.params.iter();
let mut function_args = Vec::new();
let mut arg_tys = Vec::new();
let mut js = JsBuilder::new(self.cx);
match self.method {
Some(consumes_self) => {
drop(params.next());
if js.cx.config.debug {
js.prelude(
"if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n",
);
}
if consumes_self {
js.prelude("var ptr = this.ptr;");
js.prelude("this.ptr = 0;");
js.args.push("ptr".to_string());
} else {
js.args.push("this.ptr".to_string());
}
}
None => {}
}
for (i, param) in params.enumerate() {
let arg = match explicit_arg_names {
Some(list) => list[i].clone(),
None => format!("arg{}", i),
};
js.args.push(arg.clone());
function_args.push(arg);
arg_tys.push(param);
}
for instr in instructions {
instruction(&mut js, &instr.instr, &mut self.log_error)?;
}
assert_eq!(js.stack.len(), adapter.results.len());
match js.stack.len() {
0 => {}
1 => {
let val = js.pop();
js.prelude(&format!("return {};", val));
}
_ => bail!("multi-value returns from adapters not supported yet"),
}
assert!(js.stack.is_empty());
let mut code = String::new();
code.push_str("(");
code.push_str(&function_args.join(", "));
code.push_str(") {\n");
let mut call = js.prelude;
if js.finally.len() != 0 {
call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally);
}
if self.catch {
js.cx.expose_handle_error()?;
call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call);
}
if self.log_error {
js.cx.expose_log_error();
call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call);
}
code.push_str(&call);
code.push_str("}");
let (ts_sig, ts_arg_tys, ts_ret_ty) =
self.typescript_signature(&function_args, &arg_tys, &adapter.results);
let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
Ok(JsFunction {
code,
ts_sig,
js_doc,
ts_arg_tys,
ts_ret_ty,
})
}
fn typescript_signature(
&self,
arg_names: &[String],
arg_tys: &[&AdapterType],
result_tys: &[AdapterType],
) -> (String, Vec<String>, Option<String>) {
let mut omittable = true;
let mut ts_args = Vec::new();
let mut ts_arg_tys = Vec::new();
for (name, ty) in arg_names.iter().zip(arg_tys).rev() {
let mut arg = name.to_string();
let mut ts = String::new();
match ty {
AdapterType::Option(ty) if omittable => {
arg.push_str("?: ");
adapter2ts(ty, &mut ts);
}
ty => {
omittable = false;
arg.push_str(": ");
adapter2ts(ty, &mut ts);
}
}
arg.push_str(&ts);
ts_arg_tys.push(ts);
ts_args.push(arg);
}
ts_args.reverse();
ts_arg_tys.reverse();
let mut ts = format!("({})", ts_args.join(", "));
let mut ts_ret = None;
if self.constructor.is_none() {
ts.push_str(": ");
let mut ret = String::new();
match result_tys.len() {
0 => ret.push_str("void"),
1 => adapter2ts(&result_tys[0], &mut ret),
_ => ret.push_str("[any]"),
}
ts.push_str(&ret);
ts_ret = Some(ret);
}
return (ts, ts_arg_tys, ts_ret);
}
fn js_doc_comments(
&self,
arg_names: &[String],
arg_tys: &[&AdapterType],
ts_ret: &Option<String>,
) -> String {
let mut ret = String::new();
for (name, ty) in arg_names.iter().zip(arg_tys) {
ret.push_str("@param {");
adapter2ts(ty, &mut ret);
ret.push_str("} ");
ret.push_str(name);
ret.push_str("\n");
}
if let Some(ts) = ts_ret {
if ts != "void" {
ret.push_str(&format!("@returns {{{}}}", ts));
}
}
ret
}
}
impl<'a, 'b> JsBuilder<'a, 'b> {
pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> {
JsBuilder {
cx,
args: Vec::new(),
tmp: 0,
finally: String::new(),
prelude: String::new(),
stack: Vec::new(),
}
}
pub fn arg(&self, idx: u32) -> &str {
&self.args[idx as usize]
}
pub fn prelude(&mut self, prelude: &str) {
for line in prelude.trim().lines().map(|l| l.trim()) {
if !line.is_empty() {
self.prelude.push_str(line);
self.prelude.push_str("\n");
}
}
}
pub fn finally(&mut self, finally: &str) {
for line in finally.trim().lines().map(|l| l.trim()) {
if !line.is_empty() {
self.finally.push_str(line);
self.finally.push_str("\n");
}
}
}
pub fn tmp(&mut self) -> usize {
let ret = self.tmp;
self.tmp += 1;
return ret;
}
fn pop(&mut self) -> String {
self.stack.pop().unwrap()
}
fn push(&mut self, arg: String) {
self.stack.push(arg);
}
fn assert_class(&mut self, arg: &str, class: &str) {
self.cx.expose_assert_class();
self.prelude(&format!("_assertClass({}, {});", arg, class));
}
fn assert_number(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
}
self.cx.expose_assert_num();
self.prelude(&format!("_assertNum({});", arg));
}
fn assert_bool(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
}
self.cx.expose_assert_bool();
self.prelude(&format!("_assertBoolean({});", arg));
}
fn assert_optional_number(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
}
self.cx.expose_is_like_none();
self.prelude(&format!("if (!isLikeNone({})) {{", arg));
self.assert_number(arg);
self.prelude("}");
}
fn assert_optional_bool(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
}
self.cx.expose_is_like_none();
self.prelude(&format!("if (!isLikeNone({})) {{", arg));
self.assert_bool(arg);
self.prelude("}");
}
fn assert_not_moved(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
}
self.prelude(&format!(
"\
if ({0}.ptr === 0) {{
throw new Error('Attempt to use a moved value');
}}
",
arg,
));
}
fn string_to_memory(
&mut self,
mem: walrus::MemoryId,
malloc: walrus::FunctionId,
realloc: Option<walrus::FunctionId>,
) -> Result<(), Error> {
let pass = self.cx.expose_pass_string_to_wasm(mem)?;
let val = self.pop();
let malloc = self.cx.export_name_of(malloc);
let i = self.tmp();
let realloc = match realloc {
Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)),
None => String::new(),
};
self.prelude(&format!(
"var ptr{i} = {f}({0}, wasm.{malloc}{realloc});",
val,
i = i,
f = pass,
malloc = malloc,
realloc = realloc,
));
self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
self.push(format!("ptr{}", i));
self.push(format!("len{}", i));
Ok(())
}
}
fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> {
let retptr_val = 8;
match instr {
Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => {
let arg = js.arg(*n).to_string();
js.push(arg);
}
Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => {
panic!("standard call adapter functions should be mapped to our adapters");
}
Instruction::Standard(wit_walrus::Instruction::CallCore(_))
| Instruction::CallExport(_)
| Instruction::CallAdapter(_)
| Instruction::CallTableElement(_)
| Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => {
let invoc = Invocation::from(instr, js.cx.module)?;
let (params, results) = invoc.params_results(js.cx);
let mut args = Vec::new();
for _ in 0..params {
args.push(js.pop());
}
args.reverse();
let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?;
match (invoc.defer(), results) {
(true, 0) => {
js.finally(&format!("{};", call));
js.stack.extend(args);
}
(true, _) => panic!("deferred calls must have no results"),
(false, 0) => js.prelude(&format!("{};", call)),
(false, n) => {
js.prelude(&format!("var ret = {};", call));
if n == 1 {
js.push("ret".to_string());
} else {
for i in 0..n {
js.push(format!("ret[{}]", i));
}
}
}
}
}
Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => {
let val = js.pop();
js.assert_number(&val);
js.push(val);
}
Instruction::Standard(wit_walrus::Instruction::WasmToInt {
trap: false,
output,
..
}) => {
let val = js.pop();
match output {
wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)),
_ => js.push(val),
}
}
Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. })
| Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => {
bail!("trapping wasm-to-int and int-to-wasm instructions not supported")
}
Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => {
let len = js.pop();
let ptr = js.pop();
let get = js.cx.expose_get_string_from_wasm(*mem)?;
js.push(format!("{}({}, {})", get, ptr, len));
}
Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => {
js.string_to_memory(*mem, *malloc, None)?;
}
Instruction::StringToMemory {
mem,
malloc,
realloc,
} => {
js.string_to_memory(*mem, *malloc, *realloc)?;
}
Instruction::Retptr => js.stack.push(retptr_val.to_string()),
Instruction::StoreRetptr { ty, offset, mem } => {
let (mem, size) = match ty {
AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4),
AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4),
AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8),
other => bail!("invalid aggregate return type {:?}", other),
};
let val = js.pop();
let expr = format!(
"{}()[{} / {} + {}] = {};",
mem,
js.arg(0),
size,
offset,
val,
);
js.prelude(&expr);
}
Instruction::LoadRetptr { ty, offset, mem } => {
let (mem, size) = match ty {
AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4),
AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4),
AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8),
other => bail!("invalid aggregate return type {:?}", other),
};
let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset);
js.prelude(&format!("var r{} = {};", offset, expr));
js.push(format!("r{}", offset));
}
Instruction::I32FromBool => {
let val = js.pop();
js.assert_bool(&val);
js.push(val);
}
Instruction::I32FromStringFirstChar => {
let val = js.pop();
js.push(format!("{}.codePointAt(0)", val));
}
Instruction::I32FromAnyrefOwned => {
js.cx.expose_add_heap_object();
let val = js.pop();
js.push(format!("addHeapObject({})", val));
}
Instruction::I32FromAnyrefBorrow => {
js.cx.expose_borrowed_objects();
js.cx.expose_global_stack_pointer();
let val = js.pop();
js.push(format!("addBorrowedObject({})", val));
js.finally("heap[stack_pointer++] = undefined;");
}
Instruction::I32FromAnyrefRustOwned { class } => {
let val = js.pop();
js.assert_class(&val, &class);
js.assert_not_moved(&val);
let i = js.tmp();
js.prelude(&format!("var ptr{} = {}.ptr;", i, val));
js.prelude(&format!("{}.ptr = 0;", val));
js.push(format!("ptr{}", i));
}
Instruction::I32FromAnyrefRustBorrow { class } => {
let val = js.pop();
js.assert_class(&val, &class);
js.assert_not_moved(&val);
js.push(format!("{}.ptr", val));
}
Instruction::I32FromOptionRust { class } => {
let val = js.pop();
js.cx.expose_is_like_none();
let i = js.tmp();
js.prelude(&format!("let ptr{} = 0;", i));
js.prelude(&format!("if (!isLikeNone({0})) {{", val));
js.assert_class(&val, class);
js.assert_not_moved(&val);
js.prelude(&format!("ptr{} = {}.ptr;", i, val));
js.prelude(&format!("{}.ptr = 0;", val));
js.prelude("}");
js.push(format!("ptr{}", i));
}
Instruction::I32Split64 { signed } => {
let val = js.pop();
let f = if *signed {
js.cx.expose_int64_cvt_shim()
} else {
js.cx.expose_uint64_cvt_shim()
};
let i = js.tmp();
js.prelude(&format!(
"
{f}[0] = {val};
const low{i} = u32CvtShim[0];
const high{i} = u32CvtShim[1];
",
i = i,
f = f,
val = val,
));
js.push(format!("low{}", i));
js.push(format!("high{}", i));
}
Instruction::I32SplitOption64 { signed } => {
let val = js.pop();
js.cx.expose_is_like_none();
let f = if *signed {
js.cx.expose_int64_cvt_shim()
} else {
js.cx.expose_uint64_cvt_shim()
};
let i = js.tmp();
js.prelude(&format!(
"\
{f}[0] = isLikeNone({val}) ? BigInt(0) : {val};
const low{i} = u32CvtShim[0];
const high{i} = u32CvtShim[1];
",
i = i,
f = f,
val = val,
));
js.push(format!("!isLikeNone({0})", val));
js.push(format!("low{}", i));
js.push(format!("high{}", i));
}
Instruction::I32FromOptionAnyref { table_and_alloc } => {
let val = js.pop();
js.cx.expose_is_like_none();
match table_and_alloc {
Some((table, alloc)) => {
let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?;
js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc));
}
None => {
js.cx.expose_add_heap_object();
js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val));
}
}
}
Instruction::I32FromOptionU32Sentinel => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_number(&val);
js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val));
}
Instruction::I32FromOptionBool => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_bool(&val);
js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val));
}
Instruction::I32FromOptionChar => {
let val = js.pop();
js.cx.expose_is_like_none();
js.push(format!(
"isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
val
));
}
Instruction::I32FromOptionEnum { hole } => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_number(&val);
js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole));
}
Instruction::FromOptionNative { .. } => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_number(&val);
js.push(format!("!isLikeNone({0})", val));
js.push(format!("isLikeNone({0}) ? 0 : {0}", val));
}
Instruction::VectorToMemory { kind, malloc, mem } => {
let val = js.pop();
let func = js.cx.pass_to_wasm_function(*kind, *mem)?;
let malloc = js.cx.export_name_of(*malloc);
let i = js.tmp();
js.prelude(&format!(
"var ptr{i} = {f}({0}, wasm.{malloc});",
val,
i = i,
f = func,
malloc = malloc,
));
js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
js.push(format!("ptr{}", i));
js.push(format!("len{}", i));
}
Instruction::OptionString {
mem,
malloc,
realloc,
} => {
let func = js.cx.expose_pass_string_to_wasm(*mem)?;
js.cx.expose_is_like_none();
let i = js.tmp();
let malloc = js.cx.export_name_of(*malloc);
let val = js.pop();
let realloc = match realloc {
Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)),
None => String::new(),
};
js.prelude(&format!(
"var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});",
val,
i = i,
f = func,
malloc = malloc,
realloc = realloc,
));
js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
js.push(format!("ptr{}", i));
js.push(format!("len{}", i));
}
Instruction::OptionVector { kind, mem, malloc } => {
let func = js.cx.pass_to_wasm_function(*kind, *mem)?;
js.cx.expose_is_like_none();
let i = js.tmp();
let malloc = js.cx.export_name_of(*malloc);
let val = js.pop();
js.prelude(&format!(
"var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});",
val,
i = i,
f = func,
malloc = malloc,
));
js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
js.push(format!("ptr{}", i));
js.push(format!("len{}", i));
}
Instruction::MutableSliceToMemory {
kind,
malloc,
mem,
free,
} => {
let val = js.pop();
let func = js.cx.pass_to_wasm_function(*kind, *mem)?;
let malloc = js.cx.export_name_of(*malloc);
let i = js.tmp();
js.prelude(&format!(
"var ptr{i} = {f}({val}, wasm.{malloc});",
val = val,
i = i,
f = func,
malloc = malloc,
));
js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
js.push(format!("ptr{}", i));
js.push(format!("len{}", i));
let free = js.cx.export_name_of(*free);
let get = js.cx.memview_function(*kind, *mem);
js.finally(&format!(
"
{val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i}));
wasm.{free}(ptr{i}, len{i} * {size});
",
val = val,
get = get,
free = free,
size = kind.size(),
i = i,
));
}
Instruction::BoolFromI32 => {
let val = js.pop();
js.push(format!("{} !== 0", val));
}
Instruction::AnyrefLoadOwned => {
js.cx.expose_take_object();
let val = js.pop();
js.push(format!("takeObject({})", val));
}
Instruction::StringFromChar => {
let val = js.pop();
js.push(format!("String.fromCodePoint({})", val));
}
Instruction::I64FromLoHi { signed } => {
let f = if *signed {
js.cx.expose_int64_cvt_shim()
} else {
js.cx.expose_uint64_cvt_shim()
};
let i = js.tmp();
let high = js.pop();
let low = js.pop();
js.prelude(&format!(
"\
u32CvtShim[0] = {low};
u32CvtShim[1] = {high};
const n{i} = {f}[0];
",
low = low,
high = high,
f = f,
i = i,
));
js.push(format!("n{}", i))
}
Instruction::RustFromI32 { class } => {
js.cx.require_class_wrap(class);
let val = js.pop();
js.push(format!("{}.__wrap({})", class, val));
}
Instruction::OptionRustFromI32 { class } => {
js.cx.require_class_wrap(class);
let val = js.pop();
js.push(format!(
"{0} === 0 ? undefined : {1}.__wrap({0})",
val, class,
))
}
Instruction::CachedStringLoad {
owned,
optional: _,
mem,
free,
} => {
let len = js.pop();
let ptr = js.pop();
let tmp = js.tmp();
let get = js.cx.expose_get_cached_string_from_wasm(*mem)?;
js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len));
if *owned {
let free = js.cx.export_name_of(*free);
js.prelude(&format!(
"if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}",
free,
ptr = ptr,
len = len,
));
}
js.push(format!("v{}", tmp));
}
Instruction::TableGet => {
let val = js.pop();
js.cx.expose_get_object();
js.push(format!("getObject({})", val));
}
Instruction::StackClosure {
adapter,
nargs,
mutable,
} => {
let i = js.tmp();
let b = js.pop();
let a = js.pop();
js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b));
let args = (0..*nargs)
.map(|i| format!("arg{}", i))
.collect::<Vec<_>>()
.join(", ");
let wrapper = js.cx.adapter_name(*adapter);
if *mutable {
js.prelude(&format!(
"var cb{i} = ({args}) => {{
const a = state{i}.a;
state{i}.a = 0;
try {{
return {name}(a, state{i}.b, {args});
}} finally {{
state{i}.a = a;
}}
}};",
i = i,
args = args,
name = wrapper,
));
} else {
js.prelude(&format!(
"var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});",
i = i,
args = args,
wrapper = wrapper,
));
}
js.finally(&format!("state{}.a = state{0}.b = 0;", i));
js.push(format!("cb{}", i));
}
Instruction::VectorLoad { kind, mem, free } => {
let len = js.pop();
let ptr = js.pop();
let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?;
let i = js.tmp();
let free = js.cx.export_name_of(*free);
js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len));
js.prelude(&format!(
"wasm.{}({}, {} * {});",
free,
ptr,
len,
kind.size()
));
js.push(format!("v{}", i))
}
Instruction::OptionVectorLoad { kind, mem, free } => {
let len = js.pop();
let ptr = js.pop();
let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?;
let i = js.tmp();
let free = js.cx.export_name_of(*free);
js.prelude(&format!("let v{};", i));
js.prelude(&format!("if ({} !== 0) {{", ptr));
js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len));
js.prelude(&format!(
"wasm.{}({}, {} * {});",
free,
ptr,
len,
kind.size()
));
js.prelude("}");
js.push(format!("v{}", i));
}
Instruction::View { kind, mem } => {
let len = js.pop();
let ptr = js.pop();
let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?;
js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f));
}
Instruction::OptionView { kind, mem } => {
let len = js.pop();
let ptr = js.pop();
let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?;
js.push(format!(
"{ptr} === 0 ? undefined : {f}({ptr}, {len})",
ptr = ptr,
len = len,
f = f
));
}
Instruction::OptionU32Sentinel => {
let val = js.pop();
js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val));
}
Instruction::ToOptionNative { ty: _, signed } => {
let val = js.pop();
let present = js.pop();
js.push(format!(
"{} === 0 ? undefined : {}{}",
present,
val,
if *signed { "" } else { " >>> 0" },
));
}
Instruction::OptionBoolFromI32 => {
let val = js.pop();
js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val));
}
Instruction::OptionCharFromI32 => {
let val = js.pop();
js.push(format!(
"{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})",
val,
));
}
Instruction::OptionEnumFromI32 { hole } => {
let val = js.pop();
js.push(format!("{0} === {1} ? undefined : {0}", val, hole));
}
Instruction::Option64FromI32 { signed } => {
let f = if *signed {
js.cx.expose_int64_cvt_shim()
} else {
js.cx.expose_uint64_cvt_shim()
};
let i = js.tmp();
let high = js.pop();
let low = js.pop();
let present = js.pop();
js.prelude(&format!(
"
u32CvtShim[0] = {low};
u32CvtShim[1] = {high};
const n{i} = {present} === 0 ? undefined : {f}[0];
",
present = present,
low = low,
high = high,
f = f,
i = i,
));
js.push(format!("n{}", i));
}
}
Ok(())
}
enum Invocation {
Core { id: walrus::FunctionId, defer: bool },
Adapter(AdapterId),
}
impl Invocation {
fn from(instr: &Instruction, module: &Module) -> Result<Invocation, Error> {
use Instruction::*;
Ok(match instr {
Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core {
id: *f,
defer: false,
},
Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core {
id: *f,
defer: true,
},
CallExport(e) => match module.exports.get(*e).item {
walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false },
_ => panic!("can only call exported function"),
},
CallTableElement(idx) => {
let table = module
.tables
.main_function_table()?
.ok_or_else(|| anyhow!("no function table found"))?;
let functions = match &module.tables.get(table).kind {
walrus::TableKind::Function(f) => f,
_ => bail!("should have found a function table"),
};
let id = functions
.elements
.get(*idx as usize)
.and_then(|id| *id)
.ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?;
Invocation::Core { id, defer: false }
}
CallAdapter(id) => Invocation::Adapter(*id),
_ => unreachable!(),
})
}
fn params_results(&self, cx: &Context) -> (usize, usize) {
match self {
Invocation::Core { id, .. } => {
let ty = cx.module.funcs.get(*id).ty();
let ty = cx.module.types.get(ty);
(ty.params().len(), ty.results().len())
}
Invocation::Adapter(id) => {
let adapter = &cx.wit.adapters[id];
(adapter.params.len(), adapter.results.len())
}
}
}
fn invoke(
&self,
cx: &mut Context,
args: &[String],
prelude: &mut String,
log_error: &mut bool,
) -> Result<String, Error> {
match self {
Invocation::Core { id, .. } => {
let name = cx.export_name_of(*id);
Ok(format!("wasm.{}({})", name, args.join(", ")))
}
Invocation::Adapter(id) => {
let adapter = &cx.wit.adapters[id];
let kind = match adapter.kind {
AdapterKind::Import { kind, .. } => kind,
AdapterKind::Local { .. } => {
bail!("adapter-to-adapter calls not supported yet");
}
};
let import = &cx.aux.import_map[id];
let variadic = cx.aux.imports_with_variadic.contains(id);
if cx.import_never_log_error(import) {
*log_error = false;
}
cx.invoke_import(import, kind, args, variadic, prelude)
}
}
}
fn defer(&self) -> bool {
match self {
Invocation::Core { defer, .. } => *defer,
_ => false,
}
}
}
fn adapter2ts(ty: &AdapterType, dst: &mut String) {
match ty {
AdapterType::I32
| AdapterType::S8
| AdapterType::S16
| AdapterType::S32
| AdapterType::U8
| AdapterType::U16
| AdapterType::U32
| AdapterType::F32
| AdapterType::F64 => dst.push_str("number"),
AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"),
AdapterType::String => dst.push_str("string"),
AdapterType::Anyref => dst.push_str("any"),
AdapterType::Bool => dst.push_str("boolean"),
AdapterType::Vector(kind) => dst.push_str(kind.js_ty()),
AdapterType::Option(ty) => {
adapter2ts(ty, dst);
dst.push_str(" | undefined");
}
AdapterType::Struct(name) => dst.push_str(name),
AdapterType::Function => dst.push_str("any"),
}
}