use heck::*;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt::Write;
use std::mem;
use wai_bindgen_gen_core::wai_parser::abi::{
AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType,
};
use wai_bindgen_gen_core::{wai_parser::*, Direction, Files, Generator};
#[derive(Default)]
pub struct Js {
src: Source,
in_import: bool,
opts: Opts,
guest_imports: HashMap<String, Imports>,
guest_exports: HashMap<String, Exports>,
sizes: SizeAlign,
intrinsics: BTreeMap<Intrinsic, String>,
all_intrinsics: BTreeSet<Intrinsic>,
needs_get_export: bool,
imported_resources: BTreeSet<ResourceId>,
exported_resources: BTreeSet<ResourceId>,
needs_ty_option: bool,
needs_ty_result: bool,
}
#[derive(Default)]
struct Imports {
freestanding_funcs: Vec<(String, Source)>,
resource_funcs: BTreeMap<ResourceId, Vec<(String, Source)>>,
}
#[derive(Default)]
struct Exports {
freestanding_funcs: Vec<Source>,
resource_funcs: BTreeMap<ResourceId, Vec<Source>>,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
pub struct Opts {
#[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))]
pub no_typescript: bool,
}
impl Opts {
pub fn build(self) -> Js {
let mut r = Js::new();
r.opts = self;
r
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
enum Intrinsic {
ClampGuest,
ClampHost,
ClampHost64,
DataView,
ValidateGuestChar,
ValidateHostChar,
ValidateFlags,
ValidateFlags64,
ToString,
I32ToF32,
F32ToI32,
I64ToF64,
F64ToI64,
Utf8Decoder,
Utf8Encode,
Utf8EncodedLen,
Slab,
Promises,
WithCurrentPromise,
ThrowInvalidBool,
}
impl Intrinsic {
fn name(&self) -> &'static str {
match self {
Intrinsic::ClampGuest => "clamp_guest",
Intrinsic::ClampHost => "clamp_host",
Intrinsic::ClampHost64 => "clamp_host64",
Intrinsic::DataView => "data_view",
Intrinsic::ValidateGuestChar => "validate_guest_char",
Intrinsic::ValidateHostChar => "validate_host_char",
Intrinsic::ValidateFlags => "validate_flags",
Intrinsic::ValidateFlags64 => "validate_flags64",
Intrinsic::ToString => "to_string",
Intrinsic::F32ToI32 => "f32ToI32",
Intrinsic::I32ToF32 => "i32ToF32",
Intrinsic::F64ToI64 => "f64ToI64",
Intrinsic::I64ToF64 => "i64ToF64",
Intrinsic::Utf8Decoder => "UTF8_DECODER",
Intrinsic::Utf8Encode => "utf8_encode",
Intrinsic::Utf8EncodedLen => "utf8_encoded_len",
Intrinsic::Slab => "Slab",
Intrinsic::Promises => "PROMISES",
Intrinsic::WithCurrentPromise => "with_current_promise",
Intrinsic::ThrowInvalidBool => "throw_invalid_bool",
}
}
}
impl Js {
pub fn new() -> Js {
Js::default()
}
fn abi_variant(dir: Direction) -> AbiVariant {
match dir {
Direction::Import => AbiVariant::GuestExport,
Direction::Export => AbiVariant::GuestImport,
}
}
fn array_ty(&self, iface: &Interface, ty: &Type) -> Option<&'static str> {
match ty {
Type::Unit | Type::Bool => None,
Type::U8 => Some("Uint8Array"),
Type::S8 => Some("Int8Array"),
Type::U16 => Some("Uint16Array"),
Type::S16 => Some("Int16Array"),
Type::U32 => Some("Uint32Array"),
Type::S32 => Some("Int32Array"),
Type::U64 => Some("BigUint64Array"),
Type::S64 => Some("BigInt64Array"),
Type::Float32 => Some("Float32Array"),
Type::Float64 => Some("Float64Array"),
Type::Char => None,
Type::Handle(_) => None,
Type::String => None,
Type::Id(id) => match &iface.types[*id].kind {
TypeDefKind::Type(t) => self.array_ty(iface, t),
_ => None,
},
}
}
fn print_ty(&mut self, iface: &Interface, ty: &Type) {
match ty {
Type::Unit => self.src.ts("void"),
Type::Bool => self.src.ts("boolean"),
Type::U8
| Type::S8
| Type::U16
| Type::S16
| Type::U32
| Type::S32
| Type::Float32
| Type::Float64 => self.src.ts("number"),
Type::U64 | Type::S64 => self.src.ts("bigint"),
Type::Char => self.src.ts("string"),
Type::Handle(id) => self.src.ts(&iface.resources[*id].name.to_camel_case()),
Type::String => self.src.ts("string"),
Type::Id(id) => {
let ty = &iface.types[*id];
if let Some(name) = &ty.name {
return self.src.ts(&name.to_camel_case());
}
match &ty.kind {
TypeDefKind::Type(t) => self.print_ty(iface, t),
TypeDefKind::Tuple(t) => self.print_tuple(iface, t),
TypeDefKind::Record(_) => panic!("anonymous record"),
TypeDefKind::Flags(_) => panic!("anonymous flags"),
TypeDefKind::Enum(_) => panic!("anonymous enum"),
TypeDefKind::Union(_) => panic!("anonymous union"),
TypeDefKind::Option(t) => {
if self.maybe_null(iface, t) {
self.needs_ty_option = true;
self.src.ts("Option<");
self.print_ty(iface, t);
self.src.ts(">");
} else {
self.print_ty(iface, t);
self.src.ts(" | null");
}
}
TypeDefKind::Expected(e) => {
self.needs_ty_result = true;
self.src.ts("Result<");
self.print_ty(iface, &e.ok);
self.src.ts(", ");
self.print_ty(iface, &e.err);
self.src.ts(">");
}
TypeDefKind::Variant(_) => panic!("anonymous variant"),
TypeDefKind::List(v) => self.print_list(iface, v),
TypeDefKind::Future(_) => todo!("anonymous future"),
TypeDefKind::Stream(_) => todo!("anonymous stream"),
}
}
}
}
fn print_list(&mut self, iface: &Interface, ty: &Type) {
match self.array_ty(iface, ty) {
Some(ty) => self.src.ts(ty),
None => {
self.print_ty(iface, ty);
self.src.ts("[]");
}
}
}
fn print_tuple(&mut self, iface: &Interface, tuple: &Tuple) {
self.src.ts("[");
for (i, ty) in tuple.types.iter().enumerate() {
if i > 0 {
self.src.ts(", ");
}
self.print_ty(iface, ty);
}
self.src.ts("]");
}
fn docs_raw(&mut self, docs: &str) {
self.src.ts("/**\n");
for line in docs.lines() {
self.src.ts(&format!(" * {}\n", line));
}
self.src.ts(" */\n");
}
fn docs(&mut self, docs: &Docs) {
match &docs.contents {
Some(docs) => self.docs_raw(docs),
None => (),
}
}
fn ts_func(&mut self, iface: &Interface, func: &Function) {
self.docs(&func.docs);
let mut name_printed = false;
if let FunctionKind::Static { .. } = &func.kind {
if self.in_import {
name_printed = true;
self.src.ts(&func.name.to_mixed_case());
} else {
self.src.ts("static ");
}
}
if !name_printed {
self.src.ts(&func.item_name().to_mixed_case());
}
self.src.ts("(");
let param_start = match &func.kind {
FunctionKind::Freestanding => 0,
FunctionKind::Static { .. } if self.in_import => 0,
FunctionKind::Static { .. } => {
self.src.ts(&iface.name.to_mixed_case());
self.src.ts(": ");
self.src.ts(&iface.name.to_camel_case());
if !func.params.is_empty() {
self.src.ts(", ");
}
0
}
FunctionKind::Method { .. } => 1,
};
for (i, (name, ty)) in func.params[param_start..].iter().enumerate() {
if i > 0 {
self.src.ts(", ");
}
self.src.ts(to_js_ident(&name.to_mixed_case()));
self.src.ts(": ");
self.print_ty(iface, ty);
}
self.src.ts("): ");
if func.is_async {
self.src.ts("Promise<");
}
self.print_ty(iface, &func.result);
if func.is_async {
self.src.ts(">");
}
self.src.ts(";\n");
}
fn intrinsic(&mut self, i: Intrinsic) -> String {
if let Some(name) = self.intrinsics.get(&i) {
return name.clone();
}
self.intrinsics.insert(i, i.name().to_string());
return i.name().to_string();
}
fn maybe_null(&self, iface: &Interface, ty: &Type) -> bool {
self.as_nullable(iface, ty).is_some()
}
fn as_nullable<'a>(&self, iface: &'a Interface, ty: &'a Type) -> Option<&'a Type> {
let id = match ty {
Type::Id(id) => *id,
_ => return None,
};
match &iface.types[id].kind {
TypeDefKind::Option(t) => {
if !self.maybe_null(iface, t) {
Some(t)
} else {
None
}
}
TypeDefKind::Type(t) => self.as_nullable(iface, t),
_ => None,
}
}
}
impl Generator for Js {
fn preprocess_one(&mut self, iface: &Interface, dir: Direction) {
let variant = Self::abi_variant(dir);
self.sizes.fill(iface);
self.in_import = variant == AbiVariant::GuestImport;
}
fn type_record(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
record: &Record,
docs: &Docs,
) {
self.docs(docs);
self.src
.ts(&format!("export interface {} {{\n", name.to_camel_case()));
for field in record.fields.iter() {
self.docs(&field.docs);
let (option_str, ty) = self
.as_nullable(iface, &field.ty)
.map_or(("", &field.ty), |ty| ("?", ty));
self.src
.ts(&format!("{}{}: ", field.name.to_mixed_case(), option_str));
self.print_ty(iface, ty);
self.src.ts(",\n");
}
self.src.ts("}\n");
}
fn type_tuple(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
tuple: &Tuple,
docs: &Docs,
) {
self.docs(docs);
self.src
.ts(&format!("export type {} = ", name.to_camel_case()));
self.print_tuple(iface, tuple);
self.src.ts(";\n");
}
fn type_flags(
&mut self,
_iface: &Interface,
_id: TypeId,
name: &str,
flags: &Flags,
docs: &Docs,
) {
self.docs(docs);
let repr = js_flags_repr(flags);
let ty = repr.ty();
let suffix = repr.suffix();
self.src
.ts(&format!("export type {} = {ty};\n", name.to_camel_case()));
let name = name.to_shouty_snake_case();
for (i, flag) in flags.flags.iter().enumerate() {
let flag = flag.name.to_shouty_snake_case();
let ident = format!("{name}_{flag}");
self.src
.js(&format!("const {ident} = {}{suffix};\n", 1u128 << i));
self.src
.ts(&format!("export const {ident} = {}{suffix};\n", 1u128 << i));
self.src.export(ident);
}
}
fn type_variant(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
variant: &Variant,
docs: &Docs,
) {
self.docs(docs);
self.src
.ts(&format!("export type {} = ", name.to_camel_case()));
for (i, case) in variant.cases.iter().enumerate() {
if i > 0 {
self.src.ts(" | ");
}
self.src
.ts(&format!("{}_{}", name, case.name).to_camel_case());
}
self.src.ts(";\n");
for case in variant.cases.iter() {
self.docs(&case.docs);
self.src.ts(&format!(
"export interface {} {{\n",
format!("{}_{}", name, case.name).to_camel_case()
));
self.src.ts("tag: \"");
self.src.ts(&case.name);
self.src.ts("\",\n");
if case.ty != Type::Unit {
self.src.ts("val: ");
self.print_ty(iface, &case.ty);
self.src.ts(",\n");
}
self.src.ts("}\n");
}
}
fn type_union(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
union: &Union,
docs: &Docs,
) {
self.docs(docs);
let name = name.to_camel_case();
self.src.ts(&format!("export type {name} = "));
for i in 0..union.cases.len() {
if i > 0 {
self.src.ts(" | ");
}
self.src.ts(&format!("{name}{i}"));
}
self.src.ts(";\n");
for (i, case) in union.cases.iter().enumerate() {
self.docs(&case.docs);
self.src.ts(&format!("export interface {name}{i} {{\n"));
self.src.ts(&format!("tag: {i},\n"));
self.src.ts("val: ");
self.print_ty(iface, &case.ty);
self.src.ts(",\n");
self.src.ts("}\n");
}
}
fn type_option(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
payload: &Type,
docs: &Docs,
) {
self.docs(docs);
let name = name.to_camel_case();
self.src.ts(&format!("export type {name} = "));
if self.maybe_null(iface, payload) {
self.needs_ty_option = true;
self.src.ts("Option<");
self.print_ty(iface, payload);
self.src.ts(">");
} else {
self.print_ty(iface, payload);
self.src.ts(" | null");
}
self.src.ts(";\n");
}
fn type_expected(
&mut self,
iface: &Interface,
_id: TypeId,
name: &str,
expected: &Expected,
docs: &Docs,
) {
self.docs(docs);
let name = name.to_camel_case();
self.needs_ty_result = true;
self.src.ts(&format!("export type {name} = Result<"));
self.print_ty(iface, &expected.ok);
self.src.ts(", ");
self.print_ty(iface, &expected.err);
self.src.ts(">;\n");
}
fn type_enum(
&mut self,
_iface: &Interface,
_id: TypeId,
name: &str,
enum_: &Enum,
docs: &Docs,
) {
let mut complete_docs = String::new();
if let Some(docs) = &docs.contents {
complete_docs.push_str(docs);
complete_docs.push('\n');
}
writeln!(complete_docs, "# Variants").unwrap();
for case in enum_.cases.iter() {
writeln!(complete_docs).unwrap();
writeln!(complete_docs, "## `\"{}\"`", case.name).unwrap();
if let Some(docs) = &case.docs.contents {
writeln!(complete_docs).unwrap();
complete_docs.push_str(docs);
}
}
self.docs_raw(&complete_docs);
self.src
.ts(&format!("export type {} = ", name.to_camel_case()));
for (i, case) in enum_.cases.iter().enumerate() {
if i != 0 {
self.src.ts(" | ");
}
self.src.ts(&format!("\"{}\"", case.name));
}
self.src.ts(";\n");
}
fn type_resource(&mut self, _iface: &Interface, ty: ResourceId) {
if !self.in_import {
self.exported_resources.insert(ty);
}
}
fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
self.docs(docs);
self.src
.ts(&format!("export type {} = ", name.to_camel_case()));
self.print_ty(iface, ty);
self.src.ts(";\n");
}
fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
self.docs(docs);
self.src
.ts(&format!("export type {} = ", name.to_camel_case()));
self.print_list(iface, ty);
self.src.ts(";\n");
}
fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
drop((iface, _id, name, ty, docs));
}
fn export(&mut self, iface: &Interface, func: &Function) {
let prev = mem::take(&mut self.src);
let sig = iface.wasm_signature(AbiVariant::GuestImport, func);
let params = (0..sig.params.len())
.map(|i| format!("arg{}", i))
.collect::<Vec<_>>();
self.src
.js(&format!("function({}) {{\n", params.join(", ")));
self.ts_func(iface, func);
let mut f = FunctionBindgen::new(self, false, params);
iface.call(
AbiVariant::GuestImport,
LiftLower::LiftArgsLowerResults,
func,
&mut f,
);
let FunctionBindgen {
src,
needs_memory,
needs_realloc,
needs_free,
..
} = f;
if needs_memory {
self.needs_get_export = true;
self.src.js("const memory = get_export(\"memory\");\n");
}
if let Some(name) = needs_realloc {
self.needs_get_export = true;
self.src
.js(&format!("const realloc = get_export(\"{}\");\n", name));
}
if let Some(name) = needs_free {
self.needs_get_export = true;
self.src
.js(&format!("const free = get_export(\"{}\");\n", name));
}
self.src.js(&src.js);
if func.is_async {
self.src.js("}, catch_closure);\n"); self.src.js("});\n"); }
self.src.js("}");
let src = mem::replace(&mut self.src, prev);
let imports = self
.guest_imports
.entry(iface.name.to_string())
.or_default();
let dst = match &func.kind {
FunctionKind::Freestanding | FunctionKind::Static { .. } => {
&mut imports.freestanding_funcs
}
FunctionKind::Method { resource, .. } => {
imports.resource_funcs.entry(*resource).or_default()
}
};
dst.push((func.name.to_string(), src));
}
fn import(&mut self, iface: &Interface, func: &Function) {
let prev = mem::take(&mut self.src);
let mut params = func
.params
.iter()
.enumerate()
.map(|(i, _)| format!("arg{}", i))
.collect::<Vec<_>>();
let mut sig_start = 0;
let mut first_is_operand = true;
let src_object = match &func.kind {
FunctionKind::Freestanding => "this".to_string(),
FunctionKind::Static { .. } => {
self.src.js("static ");
params.insert(0, iface.name.to_mixed_case());
first_is_operand = false;
iface.name.to_mixed_case()
}
FunctionKind::Method { .. } => {
params[0] = "this".to_string();
sig_start = 1;
"this._obj".to_string()
}
};
if func.is_async {
self.src.js("async ");
}
self.src.js(&format!(
"{}({}) {{\n",
func.item_name().to_mixed_case(),
params[sig_start..].join(", ")
));
self.ts_func(iface, func);
if !first_is_operand {
params.remove(0);
}
let mut f = FunctionBindgen::new(self, false, params);
f.src_object = src_object;
iface.call(
AbiVariant::GuestExport,
LiftLower::LowerArgsLiftResults,
func,
&mut f,
);
let FunctionBindgen {
src,
needs_memory,
needs_realloc,
needs_free,
src_object,
..
} = f;
if needs_memory {
self.src
.js(&format!("const memory = {}._exports.memory;\n", src_object));
}
if let Some(name) = needs_realloc {
self.src.js(&format!(
"const realloc = {}._exports[\"{}\"];\n",
src_object, name
));
}
if let Some(name) = needs_free {
self.src.js(&format!(
"const free = {}._exports[\"{}\"];\n",
src_object, name
));
}
self.src.js(&src.js);
self.src.js("}\n");
let exports = self
.guest_exports
.entry(iface.name.to_string())
.or_insert_with(Exports::default);
let func_body = mem::replace(&mut self.src, prev);
match &func.kind {
FunctionKind::Freestanding => {
exports.freestanding_funcs.push(func_body);
}
FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => {
exports
.resource_funcs
.entry(*resource)
.or_default()
.push(func_body);
}
}
}
fn finish_one(&mut self, iface: &Interface, files: &mut Files) {
for (module, funcs) in mem::take(&mut self.guest_imports) {
let function_name = format!("add{}ToImports", module.to_camel_case());
self.src.js(&format!(
"function {function_name}(imports, obj{}) {{\n",
if self.needs_get_export {
", get_export"
} else {
""
},
));
self.src.ts(&format!(
"export function add{}ToImports(imports: any, obj: {0}{}): void;\n",
module.to_camel_case(),
if self.needs_get_export {
", get_export: (name: string) => WebAssembly.ExportValue"
} else {
""
},
));
self.src.export(function_name);
self.src.js(&format!(
"if (!(\"{0}\" in imports)) imports[\"{0}\"] = {{}};\n",
module,
));
self.src
.ts(&format!("export interface {} {{\n", module.to_camel_case()));
for (name, src) in funcs
.freestanding_funcs
.iter()
.chain(funcs.resource_funcs.values().flatten())
{
self.src.js(&format!(
"imports[\"{}\"][\"{}\"] = {};\n",
module,
name,
src.js.trim(),
));
}
for (_, src) in funcs.freestanding_funcs.iter() {
self.src.ts(&src.ts);
}
if !self.imported_resources.is_empty() {
self.src
.js("if (!(\"canonical_abi\" in imports)) imports[\"canonical_abi\"] = {};\n");
}
for resource in self.imported_resources.clone() {
let slab = self.intrinsic(Intrinsic::Slab);
self.src.js(&format!(
"
const resources{idx} = new {slab}();
imports.canonical_abi[\"resource_drop_{name}\"] = (i) => {{
const val = resources{idx}.remove(i);
if (obj.drop{camel})
obj.drop{camel}(val);
}};
",
name = iface.resources[resource].name,
camel = iface.resources[resource].name.to_camel_case(),
idx = resource.index(),
slab = slab,
));
self.src.ts(&format!(
"drop{}?: (val: {0}) => void;\n",
iface.resources[resource].name.to_camel_case()
));
}
self.src.js("}");
self.src.ts("}\n");
for (resource, _) in iface.resources.iter() {
self.src.ts(&format!(
"export interface {} {{\n",
iface.resources[resource].name.to_camel_case()
));
if let Some(funcs) = funcs.resource_funcs.get(&resource) {
for (_, src) in funcs {
self.src.ts(&src.ts);
}
}
self.src.ts("}\n");
}
}
let imports = mem::take(&mut self.src);
for (module, exports) in mem::take(&mut self.guest_exports) {
let module = module.to_camel_case();
self.src.ts(&format!("export class {} {{\n", module));
self.src.js(&format!("class {} {{\n", module));
self.src.export(module);
self.src.ts("
/**
* The WebAssembly instance that this class is operating with.
* This is only available after the `instantiate` method has
* been called.
*/
instance: WebAssembly.Instance;
");
self.src.ts("
/**
* Constructs a new instance with internal state necessary to
* manage a wasm instance.
*
* Note that this does not actually instantiate the WebAssembly
* instance or module, you'll need to call the `instantiate`
* method below to \"activate\" this class.
*/
constructor();
");
if !self.exported_resources.is_empty() {
self.src.js("constructor() {\n");
let slab = self.intrinsic(Intrinsic::Slab);
for r in self.exported_resources.iter() {
self.src.js(&format!(
"this._resource{}_slab = new {}();\n",
r.index(),
slab
));
}
self.src.js("}\n");
}
self.src.ts("
/**
* This is a low-level method which can be used to add any
* intrinsics necessary for this instance to operate to an
* import object.
*
* The `import` object given here is expected to be used later
* to actually instantiate the module this class corresponds to.
* If the `instantiate` method below actually does the
* instantiation then there's no need to call this method, but
* if you're instantiating manually elsewhere then this can be
* used to prepare the import object for external instantiation.
*/
addToImports(imports: any): void;
");
self.src.js("addToImports(imports) {\n");
let any_async = iface.functions.iter().any(|f| f.is_async);
if !self.exported_resources.is_empty() || any_async {
self.src
.js("if (!(\"canonical_abi\" in imports)) imports[\"canonical_abi\"] = {};\n");
}
for r in self.exported_resources.iter() {
self.src.js(&format!(
"
imports.canonical_abi['resource_drop_{name}'] = i => {{
this._resource{idx}_slab.remove(i).drop();
}};
imports.canonical_abi['resource_clone_{name}'] = i => {{
const obj = this._resource{idx}_slab.get(i);
return this._resource{idx}_slab.insert(obj.clone())
}};
imports.canonical_abi['resource_get_{name}'] = i => {{
return this._resource{idx}_slab.get(i)._wasm_val;
}};
imports.canonical_abi['resource_new_{name}'] = i => {{
const registry = this._registry{idx};
return this._resource{idx}_slab.insert(new {class}(i, this));
}};
",
name = iface.resources[*r].name,
idx = r.index(),
class = iface.resources[*r].name.to_camel_case(),
));
}
if any_async {
let promises = self.intrinsic(Intrinsic::Promises);
self.src.js(&format!(
"
imports.canonical_abi['async_export_done'] = (ctx, ptr) => {{
{}.remove(ctx)(ptr >>> 0)
}};
",
promises
));
}
self.src.js("}\n");
self.src.ts("
/**
* Initializes this object with the provided WebAssembly
* module/instance.
*
* This is intended to be a flexible method of instantiating
* and completion of the initialization of this class. This
* method must be called before interacting with the
* WebAssembly object.
*
* The first argument to this method is where to get the
* wasm from. This can be a whole bunch of different types,
* for example:
*
* * A precompiled `WebAssembly.Module`
* * A typed array buffer containing the wasm bytecode.
* * A `Promise` of a `Response` which is used with
* `instantiateStreaming`
* * A `Response` itself used with `instantiateStreaming`.
* * An already instantiated `WebAssembly.Instance`
*
* If necessary the module is compiled, and if necessary the
* module is instantiated. Whether or not it's necessary
* depends on the type of argument provided to
* instantiation.
*
* If instantiation is performed then the `imports` object
* passed here is the list of imports used to instantiate
* the instance. This method may add its own intrinsics to
* this `imports` object too.
*/
instantiate(
module: WebAssembly.Module | BufferSource | Promise<Response> | Response | WebAssembly.Instance,
imports?: any,
): Promise<void>;
");
self.src.js("
async instantiate(module, imports) {
imports = imports || {};
this.addToImports(imports);
");
self.src.js("
if (module instanceof WebAssembly.Instance) {
this.instance = module;
} else if (module instanceof WebAssembly.Module) {
this.instance = await WebAssembly.instantiate(module, imports);
} else if (module instanceof ArrayBuffer || module instanceof Uint8Array) {
const { instance } = await WebAssembly.instantiate(module, imports);
this.instance = instance;
} else {
const { instance } = await WebAssembly.instantiateStreaming(module, imports);
this.instance = instance;
}
this._exports = this.instance.exports;
");
for r in self.exported_resources.iter() {
self.src.js(&format!(
"this._registry{} = new FinalizationRegistry(this._exports['canonical_abi_drop_{}']);\n",
r.index(),
iface.resources[*r].name,
));
}
self.src.js("}\n");
for func in exports.freestanding_funcs.iter() {
self.src.js(&func.js);
self.src.ts(&func.ts);
}
self.src.ts("}\n");
self.src.js("}\n");
for &ty in self.exported_resources.iter() {
let class_name = iface.resources[ty].name.to_camel_case();
self.src.js(&format!(
"
class {class_name} {{
constructor(wasm_val, obj) {{
this._wasm_val = wasm_val;
this._obj = obj;
this._refcnt = 1;
obj._registry{idx}.register(this, wasm_val, this);
}}
clone() {{
this._refcnt += 1;
return this;
}}
drop() {{
this._refcnt -= 1;
if (this._refcnt !== 0)
return;
this._obj._registry{idx}.unregister(this);
const dtor = this._obj._exports['canonical_abi_drop_{}'];
const wasm_val = this._wasm_val;
delete this._obj;
delete this._refcnt;
delete this._wasm_val;
dtor(wasm_val);
}}
",
iface.resources[ty].name,
idx = ty.index(),
));
self.src.ts(&format!(
"
export class {class_name} {{
// Creates a new strong reference count as a new
// object. This is only required if you're also
// calling `drop` below and want to manually manage
// the reference count from JS.
//
// If you don't call `drop`, you don't need to call
// this and can simply use the object from JS.
clone(): {class_name};
// Explicitly indicate that this JS object will no
// longer be used. If the internal reference count
// reaches zero then this will deterministically
// destroy the underlying wasm object.
//
// This is not required to be called from JS. Wasm
// destructors will be automatically called for you
// if this is not called using the JS
// `FinalizationRegistry`.
//
// Calling this method does not guarantee that the
// underlying wasm object is deallocated. Something
// else (including wasm) may be holding onto a
// strong reference count.
drop(): void;
",
));
self.src.export(class_name);
if let Some(funcs) = exports.resource_funcs.get(&ty) {
for func in funcs {
self.src.js(&func.js);
self.src.ts(&func.ts);
}
}
self.src.ts("}\n");
self.src.js("}\n");
}
}
let exports = mem::take(&mut self.src);
if mem::take(&mut self.needs_ty_option) {
self.src
.ts("export type Option<T> = { tag: \"none\" } | { tag: \"some\", val; T };\n");
}
if mem::take(&mut self.needs_ty_result) {
self.src.ts(
"export type Result<T, E> = { tag: \"ok\", val: T } | { tag: \"err\", val: E };\n",
);
}
if !self.intrinsics.is_empty() {
self.src.js("const { ");
for (i, (intrinsic, name)) in mem::take(&mut self.intrinsics).into_iter().enumerate() {
if i > 0 {
self.src.js(", ");
}
self.src.js(intrinsic.name());
if intrinsic.name() != name {
self.src.js(" as ");
self.src.js(&name);
}
self.all_intrinsics.insert(intrinsic);
}
self.src.js(" } = require('./intrinsics.js');\n");
}
self.src.js(&imports.js);
self.src.ts(&imports.ts);
self.src.js(&exports.js);
self.src.ts(&exports.ts);
let mut exported_items = Vec::new();
exported_items.extend(exports.exported_items);
exported_items.extend(imports.exported_items);
self.src.js(&format!(
"\nmodule.exports = {{ {} }};\n",
exported_items.join(", ")
));
let src = mem::take(&mut self.src);
let name = iface.name.to_kebab_case();
files.push(&format!("{}.js", name), src.js.as_bytes());
if !self.opts.no_typescript {
files.push(&format!("{}.d.ts", name), src.ts.as_bytes());
}
}
fn finish_all(&mut self, files: &mut Files) {
assert!(self.src.ts.is_empty());
assert!(self.src.js.is_empty());
self.print_intrinsics();
assert!(self.src.ts.is_empty());
files.push("intrinsics.js", self.src.js.as_bytes());
}
}
struct FunctionBindgen<'a> {
gen: &'a mut Js,
tmp: usize,
src: Source,
block_storage: Vec<wai_bindgen_gen_core::Source>,
blocks: Vec<(String, Vec<String>)>,
in_import: bool,
needs_memory: bool,
needs_realloc: Option<String>,
needs_free: Option<String>,
params: Vec<String>,
src_object: String,
}
impl FunctionBindgen<'_> {
fn new(gen: &mut Js, in_import: bool, params: Vec<String>) -> FunctionBindgen<'_> {
FunctionBindgen {
gen,
tmp: 0,
src: Source::default(),
block_storage: Vec::new(),
blocks: Vec::new(),
in_import,
needs_memory: false,
needs_realloc: None,
needs_free: None,
params,
src_object: "this".to_string(),
}
}
fn tmp(&mut self) -> usize {
let ret = self.tmp;
self.tmp += 1;
ret
}
fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
where
T: std::fmt::Display,
{
let clamp = self.gen.intrinsic(Intrinsic::ClampGuest);
results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
}
fn clamp_host<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
where
T: std::fmt::Display,
{
let clamp = self.gen.intrinsic(Intrinsic::ClampHost);
results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
}
fn clamp_host64<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
where
T: std::fmt::Display,
{
let clamp = self.gen.intrinsic(Intrinsic::ClampHost64);
results.push(format!("{}({}, {}n, {}n)", clamp, operands[0], min, max));
}
fn load(&mut self, method: &str, offset: i32, operands: &[String], results: &mut Vec<String>) {
self.needs_memory = true;
let view = self.gen.intrinsic(Intrinsic::DataView);
results.push(format!(
"{}(memory).{}({} + {}, true)",
view, method, operands[0], offset,
));
}
fn store(&mut self, method: &str, offset: i32, operands: &[String]) {
self.needs_memory = true;
let view = self.gen.intrinsic(Intrinsic::DataView);
self.src.js(&format!(
"{}(memory).{}({} + {}, {}, true);\n",
view, method, operands[1], offset, operands[0]
));
}
fn bind_results(&mut self, amt: usize, results: &mut Vec<String>) {
match amt {
0 => {}
1 => {
self.src.js("const ret = ");
results.push("ret".to_string());
}
n => {
self.src.js("const [");
for i in 0..n {
if i > 0 {
self.src.js(", ");
}
self.src.js(&format!("ret{}", i));
results.push(format!("ret{}", i));
}
self.src.js("] = ");
}
}
}
}
impl Bindgen for FunctionBindgen<'_> {
type Operand = String;
fn sizes(&self) -> &SizeAlign {
&self.gen.sizes
}
fn push_block(&mut self) {
let prev = mem::take(&mut self.src.js);
self.block_storage.push(prev);
}
fn finish_block(&mut self, operands: &mut Vec<String>) {
let to_restore = self.block_storage.pop().unwrap();
let src = mem::replace(&mut self.src.js, to_restore);
self.blocks.push((src.into(), mem::take(operands)));
}
fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String {
unimplemented!()
}
fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool {
self.gen.array_ty(iface, ty).is_some()
}
fn emit(
&mut self,
iface: &Interface,
inst: &Instruction<'_>,
operands: &mut Vec<String>,
results: &mut Vec<String>,
) {
match inst {
Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
Instruction::I32Const { val } => results.push(val.to_string()),
Instruction::ConstZero { tys } => {
for t in tys.iter() {
match t {
WasmType::I64 => results.push("0n".to_string()),
WasmType::I32 | WasmType::F32 | WasmType::F64 => {
results.push("0".to_string());
}
}
}
}
Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
Instruction::U32FromI32 => {
results.push(format!("{} >>> 0", operands[0]));
}
Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])),
Instruction::S32FromI32 | Instruction::S64FromI64 => {
results.push(operands.pop().unwrap())
}
Instruction::I32FromU8 => self.clamp_host(results, operands, u8::MIN, u8::MAX),
Instruction::I32FromS8 => self.clamp_host(results, operands, i8::MIN, i8::MAX),
Instruction::I32FromU16 => self.clamp_host(results, operands, u16::MIN, u16::MAX),
Instruction::I32FromS16 => self.clamp_host(results, operands, i16::MIN, i16::MAX),
Instruction::I32FromU32 => {
self.clamp_host(results, operands, u32::MIN, u32::MAX);
}
Instruction::I32FromS32 => self.clamp_host(results, operands, i32::MIN, i32::MAX),
Instruction::I64FromU64 => self.clamp_host64(results, operands, u64::MIN, u64::MAX),
Instruction::I64FromS64 => self.clamp_host64(results, operands, i64::MIN, i64::MAX),
Instruction::Float32FromF32 | Instruction::Float64FromF64 => {
results.push(operands.pop().unwrap())
}
Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => {
results.push(format!("+{}", operands[0]));
}
Instruction::CharFromI32 => {
let validate = self.gen.intrinsic(Intrinsic::ValidateGuestChar);
results.push(format!("{}({})", validate, operands[0]));
}
Instruction::I32FromChar => {
let validate = self.gen.intrinsic(Intrinsic::ValidateHostChar);
results.push(format!("{}({})", validate, operands[0]));
}
Instruction::Bitcasts { casts } => {
for (cast, op) in casts.iter().zip(operands) {
match cast {
Bitcast::I32ToF32 => {
let cvt = self.gen.intrinsic(Intrinsic::I32ToF32);
results.push(format!("{}({})", cvt, op));
}
Bitcast::F32ToI32 => {
let cvt = self.gen.intrinsic(Intrinsic::F32ToI32);
results.push(format!("{}({})", cvt, op));
}
Bitcast::I64ToF64 => {
let cvt = self.gen.intrinsic(Intrinsic::I64ToF64);
results.push(format!("{}({})", cvt, op));
}
Bitcast::F64ToI64 => {
let cvt = self.gen.intrinsic(Intrinsic::F64ToI64);
results.push(format!("{}({})", cvt, op));
}
Bitcast::I32ToI64 => results.push(format!("BigInt({})", op)),
Bitcast::I64ToI32 => results.push(format!("Number({})", op)),
Bitcast::I64ToF32 => {
let cvt = self.gen.intrinsic(Intrinsic::I32ToF32);
results.push(format!("{}(Number({}))", cvt, op));
}
Bitcast::F32ToI64 => {
let cvt = self.gen.intrinsic(Intrinsic::F32ToI32);
results.push(format!("BigInt({}({}))", cvt, op));
}
Bitcast::None => results.push(op.clone()),
}
}
}
Instruction::UnitLower => {}
Instruction::UnitLift => {
results.push("undefined".to_string());
}
Instruction::BoolFromI32 => {
let tmp = self.tmp();
self.src
.js(&format!("const bool{} = {};\n", tmp, operands[0]));
let throw = self.gen.intrinsic(Intrinsic::ThrowInvalidBool);
results.push(format!(
"bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
));
}
Instruction::I32FromBool => {
results.push(format!("{} ? 1 : 0", operands[0]));
}
Instruction::I32FromOwnedHandle { ty } => {
self.gen.imported_resources.insert(*ty);
results.push(format!("resources{}.insert({})", ty.index(), operands[0]));
}
Instruction::HandleBorrowedFromI32 { ty } => {
self.gen.imported_resources.insert(*ty);
results.push(format!("resources{}.get({})", ty.index(), operands[0]));
}
Instruction::I32FromBorrowedHandle { ty } => {
let tmp = self.tmp();
self.src
.js(&format!("const obj{} = {};\n", tmp, operands[0]));
if operands[0] != "this" {
self.src.js(&format!(
"if (!(obj{} instanceof {})) ",
tmp,
iface.resources[*ty].name.to_camel_case()
));
self.src.js(&format!(
"throw new TypeError('expected instance of {}');\n",
iface.resources[*ty].name.to_camel_case()
));
}
results.push(format!(
"{}._resource{}_slab.insert(obj{}.clone())",
self.src_object,
ty.index(),
tmp,
));
}
Instruction::HandleOwnedFromI32 { ty } => {
results.push(format!(
"{}._resource{}_slab.remove({})",
self.src_object,
ty.index(),
operands[0],
));
}
Instruction::RecordLower { record, .. } => {
let tmp = self.tmp();
let mut expr = "const {".to_string();
for (i, field) in record.fields.iter().enumerate() {
if i > 0 {
expr.push_str(", ");
}
let name = format!("v{}_{}", tmp, i);
expr.push_str(&field.name.to_mixed_case());
expr.push_str(": ");
expr.push_str(&name);
results.push(name);
}
self.src.js(&format!("{} }} = {};\n", expr, operands[0]));
}
Instruction::RecordLift { record, .. } => {
let mut result = "{\n".to_string();
for (field, op) in record.fields.iter().zip(operands) {
result.push_str(&format!("{}: {},\n", field.name.to_mixed_case(), op));
}
result.push('}');
results.push(result);
}
Instruction::TupleLower { tuple, .. } => {
let tmp = self.tmp();
let mut expr = "const [".to_string();
for i in 0..tuple.types.len() {
if i > 0 {
expr.push_str(", ");
}
let name = format!("tuple{}_{}", tmp, i);
expr.push_str(&name);
results.push(name);
}
self.src.js(&format!("{}] = {};\n", expr, operands[0]));
}
Instruction::TupleLift { .. } => {
results.push(format!("[{}]", operands.join(", ")));
}
Instruction::FlagsLower { flags, .. } => {
let repr = js_flags_repr(flags);
let validate = match repr {
JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags),
JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64),
};
let op0 = &operands[0];
let len = flags.flags.len();
let n = repr.suffix();
let tmp = self.tmp();
let mask = (1u128 << len) - 1;
self.src.js(&format!(
"const flags{tmp} = {validate}({op0}, {mask}{n});\n"
));
match repr {
JsFlagsRepr::Number => {
results.push(format!("flags{}", tmp));
}
JsFlagsRepr::Bigint => {
for i in 0..flags.repr().count() {
let i = 32 * i;
results.push(format!("Number((flags{tmp} >> {i}n) & 0xffffffffn)",));
}
}
}
}
Instruction::FlagsLift { flags, .. } => {
let repr = js_flags_repr(flags);
let n = repr.suffix();
let tmp = self.tmp();
let operand = match repr {
JsFlagsRepr::Number => operands[0].clone(),
JsFlagsRepr::Bigint => {
self.src.js(&format!("let flags{tmp} = 0n;\n"));
for (i, op) in operands.iter().enumerate() {
let i = 32 * i;
self.src
.js(&format!("flags{tmp} |= BigInt({op}) << {i}n;\n",));
}
format!("flags{tmp}")
}
};
let validate = match repr {
JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags),
JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64),
};
let len = flags.flags.len();
let mask = (1u128 << len) - 1;
results.push(format!("{validate}({operand}, {mask}{n})"));
}
Instruction::VariantPayloadName => results.push("e".to_string()),
Instruction::VariantLower {
variant,
results: result_types,
name,
..
} => {
let blocks = self
.blocks
.drain(self.blocks.len() - variant.cases.len()..)
.collect::<Vec<_>>();
let tmp = self.tmp();
self.src
.js(&format!("const variant{} = {};\n", tmp, operands[0]));
for i in 0..result_types.len() {
self.src.js(&format!("let variant{}_{};\n", tmp, i));
results.push(format!("variant{}_{}", tmp, i));
}
let expr_to_match = format!("variant{}.tag", tmp);
self.src.js(&format!("switch ({}) {{\n", expr_to_match));
for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
self.src
.js(&format!("case \"{}\": {{\n", case.name.as_str()));
if case.ty != Type::Unit {
self.src.js(&format!("const e = variant{}.val;\n", tmp));
}
self.src.js(&block);
for (i, result) in block_results.iter().enumerate() {
self.src
.js(&format!("variant{}_{} = {};\n", tmp, i, result));
}
self.src.js("break;\n}\n");
}
let variant_name = name.to_camel_case();
self.src.js("default:\n");
self.src.js(&format!(
"throw new RangeError(\"invalid variant specified for {}\");\n",
variant_name
));
self.src.js("}\n");
}
Instruction::VariantLift { variant, name, .. } => {
let blocks = self
.blocks
.drain(self.blocks.len() - variant.cases.len()..)
.collect::<Vec<_>>();
let tmp = self.tmp();
self.src.js(&format!("let variant{};\n", tmp));
self.src.js(&format!("switch ({}) {{\n", operands[0]));
for (i, (case, (block, block_results))) in
variant.cases.iter().zip(blocks).enumerate()
{
self.src.js(&format!("case {}: {{\n", i));
self.src.js(&block);
self.src.js(&format!("variant{} = {{\n", tmp));
self.src.js(&format!("tag: \"{}\",\n", case.name.as_str()));
assert!(block_results.len() == 1);
if case.ty != Type::Unit {
self.src.js(&format!("val: {},\n", block_results[0]));
} else {
assert_eq!(block_results[0], "undefined");
}
self.src.js("};\n");
self.src.js("break;\n}\n");
}
let variant_name = name.to_camel_case();
self.src.js("default:\n");
self.src.js(&format!(
"throw new RangeError(\"invalid variant discriminant for {}\");\n",
variant_name
));
self.src.js("}\n");
results.push(format!("variant{}", tmp));
}
Instruction::UnionLower {
union,
results: result_types,
name,
..
} => {
let blocks = self
.blocks
.drain(self.blocks.len() - union.cases.len()..)
.collect::<Vec<_>>();
let tmp = self.tmp();
let op0 = &operands[0];
self.src.js(&format!("const union{tmp} = {op0};\n"));
for i in 0..result_types.len() {
self.src.js(&format!("let union{tmp}_{i};\n"));
results.push(format!("union{tmp}_{i}"));
}
self.src.js(&format!("switch (union{tmp}.tag) {{\n"));
for (i, (_case, (block, block_results))) in
union.cases.iter().zip(blocks).enumerate()
{
self.src.js(&format!("case {i}: {{\n"));
self.src.js(&format!("const e = union{tmp}.val;\n"));
self.src.js(&block);
for (i, result) in block_results.iter().enumerate() {
self.src.js(&format!("union{tmp}_{i} = {result};\n"));
}
self.src.js("break;\n}\n");
}
let name = name.to_camel_case();
self.src.js("default:\n");
self.src.js(&format!(
"throw new RangeError(\"invalid union specified for {name}\");\n",
));
self.src.js("}\n");
}
Instruction::UnionLift { union, name, .. } => {
let blocks = self
.blocks
.drain(self.blocks.len() - union.cases.len()..)
.collect::<Vec<_>>();
let tmp = self.tmp();
self.src.js(&format!("let union{tmp};\n"));
self.src.js(&format!("switch ({}) {{\n", operands[0]));
for (i, (_case, (block, block_results))) in
union.cases.iter().zip(blocks).enumerate()
{
assert!(block_results.len() == 1);
let block_result = &block_results[0];
self.src.js(&format!(
"case {i}: {{
{block}
union{tmp} = {{
tag: {i},
val: {block_result},
}};
break;
}}\n"
));
}
let name = name.to_camel_case();
self.src.js("default:\n");
self.src.js(&format!(
"throw new RangeError(\"invalid union discriminant for {name}\");\n",
));
self.src.js("}\n");
results.push(format!("union{tmp}"));
}
Instruction::OptionLower {
payload,
results: result_types,
..
} => {
let (mut some, some_results) = self.blocks.pop().unwrap();
let (mut none, none_results) = self.blocks.pop().unwrap();
let tmp = self.tmp();
self.src
.js(&format!("const variant{tmp} = {};\n", operands[0]));
for i in 0..result_types.len() {
self.src.js(&format!("let variant{tmp}_{i};\n"));
results.push(format!("variant{tmp}_{i}"));
let some_result = &some_results[i];
let none_result = &none_results[i];
some.push_str(&format!("variant{tmp}_{i} = {some_result};\n"));
none.push_str(&format!("variant{tmp}_{i} = {none_result};\n"));
}
if self.gen.maybe_null(iface, payload) {
self.src.js(&format!(
"
switch (variant{tmp}.tag) {{
case \"none\": {{
{none}
break;
}}
case \"some\": {{
const e = variant{tmp}.val;
{some}
break;
}}
default: {{
throw new RangeError(\"invalid variant specified for option\");
}}
}}
"
));
} else {
self.src.js(&format!(
"
switch (variant{tmp}) {{
case null: {{
{none}
break;
}}
default: {{
const e = variant{tmp};
{some}
break;
}}
}}
"
));
}
}
Instruction::OptionLift { payload, .. } => {
let (some, some_results) = self.blocks.pop().unwrap();
let (none, none_results) = self.blocks.pop().unwrap();
assert!(none_results.len() == 1);
assert!(some_results.len() == 1);
let some_result = &some_results[0];
assert_eq!(none_results[0], "undefined");
let tmp = self.tmp();
self.src.js(&format!("let variant{tmp};\n"));
self.src.js(&format!("switch ({}) {{\n", operands[0]));
if self.gen.maybe_null(iface, payload) {
self.src.js(&format!(
"
case 0: {{
{none}
variant{tmp} = {{ tag: \"none\" }};
break;
}}
case 1: {{
{some}
variant{tmp} = {{ tag: \"some\", val: {some_result} }};
break;
}}
",
));
} else {
self.src.js(&format!(
"
case 0: {{
{none}
variant{tmp} = null;
break;
}}
case 1: {{
{some}
variant{tmp} = {some_result};
break;
}}
",
));
}
self.src.js("
default:
throw new RangeError(\"invalid variant discriminant for option\");
");
self.src.js("}\n");
results.push(format!("variant{tmp}"));
}
Instruction::ExpectedLower {
results: result_types,
..
} => {
let (mut err, err_results) = self.blocks.pop().unwrap();
let (mut ok, ok_results) = self.blocks.pop().unwrap();
let tmp = self.tmp();
self.src
.js(&format!("const variant{tmp} = {};\n", operands[0]));
for i in 0..result_types.len() {
self.src.js(&format!("let variant{tmp}_{i};\n"));
results.push(format!("variant{tmp}_{i}"));
let ok_result = &ok_results[i];
let err_result = &err_results[i];
ok.push_str(&format!("variant{tmp}_{i} = {ok_result};\n"));
err.push_str(&format!("variant{tmp}_{i} = {err_result};\n"));
}
self.src.js(&format!(
"
switch (variant{tmp}.tag) {{
case \"ok\": {{
const e = variant{tmp}.val;
{ok}
break;
}}
case \"err\": {{
const e = variant{tmp}.val;
{err}
break;
}}
default: {{
throw new RangeError(\"invalid variant specified for expected\");
}}
}}
"
));
}
Instruction::ExpectedLift { .. } => {
let (err, err_results) = self.blocks.pop().unwrap();
let (ok, ok_results) = self.blocks.pop().unwrap();
let err_result = &err_results[0];
let ok_result = &ok_results[0];
let tmp = self.tmp();
let op0 = &operands[0];
self.src.js(&format!(
"
let variant{tmp};
switch ({op0}) {{
case 0: {{
{ok}
variant{tmp} = {{ tag: \"ok\", val: {ok_result} }};
break;
}}
case 1: {{
{err}
variant{tmp} = {{ tag: \"err\", val: {err_result} }};
break;
}}
default: {{
throw new RangeError(\"invalid variant discriminant for expected\");
}}
}}
",
));
results.push(format!("variant{tmp}"));
}
Instruction::EnumLower { name, enum_, .. } => {
let tmp = self.tmp();
let to_string = self.gen.intrinsic(Intrinsic::ToString);
self.src
.js(&format!("const val{tmp} = {to_string}({});\n", operands[0]));
self.src.js(&format!("let enum{tmp};\n"));
self.src.js(&format!("switch (val{tmp}) {{\n"));
for (i, case) in enum_.cases.iter().enumerate() {
self.src.js(&format!(
"\
case \"{case}\": {{
enum{tmp} = {i};
break;
}}
",
case = case.name
));
}
self.src.js(&format!("\
default: {{
throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
}}
}}
"));
results.push(format!("enum{tmp}"));
}
Instruction::EnumLift { name, enum_, .. } => {
let tmp = self.tmp();
self.src.js(&format!("let enum{tmp};\n"));
self.src.js(&format!("switch ({}) {{\n", operands[0]));
for (i, case) in enum_.cases.iter().enumerate() {
self.src.js(&format!(
"\
case {i}: {{
enum{tmp} = \"{case}\";
break;
}}
",
case = case.name
));
}
self.src.js(&format!(
"\
default: {{
throw new RangeError(\"invalid discriminant specified for {name}\");
}}
}}
",
name = name.to_camel_case()
));
results.push(format!("enum{tmp}"));
}
Instruction::ListCanonLower { element, realloc } => {
let realloc = realloc.unwrap();
self.gen.needs_get_export = true;
self.needs_memory = true;
self.needs_realloc = Some(realloc.to_string());
let tmp = self.tmp();
let size = self.gen.sizes.size(element);
let align = self.gen.sizes.align(element);
self.src
.js(&format!("const val{} = {};\n", tmp, operands[0]));
self.src.js(&format!("const len{} = val{0}.length;\n", tmp));
self.src.js(&format!(
"const ptr{} = realloc(0, 0, {}, len{0} * {});\n",
tmp, align, size,
));
self.src.js(&format!(
"(new Uint8Array(memory.buffer, ptr{0}, len{0} * {1})).set(new Uint8Array(val{0}.buffer, val{0}.byteOffset, len{0} * {1}));\n",
tmp, size,
));
results.push(format!("ptr{}", tmp));
results.push(format!("len{}", tmp));
}
Instruction::ListCanonLift { element, free, .. } => {
self.needs_memory = true;
let tmp = self.tmp();
self.src
.js(&format!("const ptr{} = {};\n", tmp, operands[0]));
self.src
.js(&format!("const len{} = {};\n", tmp, operands[1]));
let array_ty = self.gen.array_ty(iface, element).unwrap();
let result = format!(
"new {}(memory.buffer.slice(ptr{}, ptr{1} + len{1} * {}))",
array_ty,
tmp,
self.gen.sizes.size(element),
);
let align = self.gen.sizes.align(element);
match free {
Some(free) => {
self.needs_free = Some(free.to_string());
self.src.js(&format!("const list{} = {};\n", tmp, result));
self.src
.js(&format!("free(ptr{}, len{0}, {});\n", tmp, align));
results.push(format!("list{}", tmp));
}
None => results.push(result),
}
}
Instruction::StringLower { realloc } => {
let realloc = realloc.unwrap();
self.gen.needs_get_export = true;
self.needs_memory = true;
self.needs_realloc = Some(realloc.to_string());
let tmp = self.tmp();
let encode = self.gen.intrinsic(Intrinsic::Utf8Encode);
self.src.js(&format!(
"const ptr{} = {}({}, realloc, memory);\n",
tmp, encode, operands[0],
));
let encoded_len = self.gen.intrinsic(Intrinsic::Utf8EncodedLen);
self.src.js(&format!("const len{tmp} = {encoded_len}();\n"));
results.push(format!("ptr{}", tmp));
results.push(format!("len{}", tmp));
}
Instruction::StringLift { free } => {
self.needs_memory = true;
let tmp = self.tmp();
self.src
.js(&format!("const ptr{} = {};\n", tmp, operands[0]));
self.src
.js(&format!("const len{} = {};\n", tmp, operands[1]));
let decoder = self.gen.intrinsic(Intrinsic::Utf8Decoder);
let result = format!(
"{}.decode(new Uint8Array(memory.buffer, ptr{}, len{1}))",
decoder, tmp,
);
match free {
Some(free) => {
self.needs_free = Some(free.to_string());
self.src.js(&format!("const list{} = {};\n", tmp, result));
self.src.js(&format!("free(ptr{}, len{0}, 1);\n", tmp));
results.push(format!("list{}", tmp));
}
None => results.push(result),
}
}
Instruction::ListLower { element, realloc } => {
let realloc = realloc.unwrap();
let (body, body_results) = self.blocks.pop().unwrap();
assert!(body_results.is_empty());
let tmp = self.tmp();
let vec = format!("vec{}", tmp);
let result = format!("result{}", tmp);
let len = format!("len{}", tmp);
self.needs_realloc = Some(realloc.to_string());
let size = self.gen.sizes.size(element);
let align = self.gen.sizes.align(element);
self.src.js(&format!("const {} = {};\n", vec, operands[0]));
self.src.js(&format!("const {} = {}.length;\n", len, vec));
self.src.js(&format!(
"const {} = realloc(0, 0, {}, {} * {});\n",
result, align, len, size,
));
self.src
.js(&format!("for (let i = 0; i < {}.length; i++) {{\n", vec));
self.src.js(&format!("const e = {}[i];\n", vec));
self.src
.js(&format!("const base = {} + i * {};\n", result, size));
self.src.js(&body);
self.src.js("}\n");
results.push(result);
results.push(len);
}
Instruction::ListLift { element, free, .. } => {
let (body, body_results) = self.blocks.pop().unwrap();
let tmp = self.tmp();
let size = self.gen.sizes.size(element);
let align = self.gen.sizes.align(element);
let len = format!("len{}", tmp);
self.src.js(&format!("const {} = {};\n", len, operands[1]));
let base = format!("base{}", tmp);
self.src.js(&format!("const {} = {};\n", base, operands[0]));
let result = format!("result{}", tmp);
self.src.js(&format!("const {} = [];\n", result));
results.push(result.clone());
self.src
.js(&format!("for (let i = 0; i < {}; i++) {{\n", len));
self.src
.js(&format!("const base = {} + i * {};\n", base, size));
self.src.js(&body);
assert_eq!(body_results.len(), 1);
self.src
.js(&format!("{}.push({});\n", result, body_results[0]));
self.src.js("}\n");
if let Some(free) = free {
self.needs_free = Some(free.to_string());
self.src
.js(&format!("free({}, {} * {}, {});\n", base, len, size, align,));
}
}
Instruction::IterElem { .. } => results.push("e".to_string()),
Instruction::IterBasePointer => results.push("base".to_string()),
Instruction::CallWasm {
iface: _,
name,
sig,
} => {
self.bind_results(sig.results.len(), results);
self.src.js(&self.src_object);
self.src.js("._exports['");
self.src.js(name);
self.src.js("'](");
self.src.js(&operands.join(", "));
self.src.js(");\n");
}
Instruction::CallWasmAsyncExport {
module: _,
name,
params: _,
results: wasm_results,
} => {
self.bind_results(wasm_results.len(), results);
let promises = self.gen.intrinsic(Intrinsic::Promises);
self.src.js(&format!(
"\
await new Promise((resolve, reject) => {{
const promise_ctx = {promises}.insert(val => {{
if (typeof val !== 'number')
return reject(val);
resolve(\
",
promises = promises
));
if !wasm_results.is_empty() {
self.src.js("[");
let operands = &["val".to_string()];
let mut results = Vec::new();
for (i, result) in wasm_results.iter().enumerate() {
if i > 0 {
self.src.js(", ");
}
let method = match result {
WasmType::I32 => "getInt32",
WasmType::I64 => "getBigInt64",
WasmType::F32 => "getFloat32",
WasmType::F64 => "getFloat64",
};
self.load(method, (i * 8) as i32, operands, &mut results);
self.src.js(&results.pop().unwrap());
}
self.src.js("]");
}
self.src.js(");\n"); self.src.js("});\n"); let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise);
self.src.js(&with);
self.src.js("(promise_ctx, _prev => {\n");
self.src.js(&self.src_object);
self.src.js("._exports['");
self.src.js(name);
self.src.js("'](");
for op in operands {
self.src.js(op);
self.src.js(", ");
}
self.src.js("promise_ctx);\n");
self.src.js("});\n"); self.src.js("});\n"); }
Instruction::CallInterface { module: _, func } => {
let call = |me: &mut FunctionBindgen<'_>| match &func.kind {
FunctionKind::Freestanding | FunctionKind::Static { .. } => {
me.src.js(&format!(
"obj.{}({})",
func.name.to_mixed_case(),
operands.join(", "),
));
}
FunctionKind::Method { name, .. } => {
me.src.js(&format!(
"{}.{}({})",
operands[0],
name.to_mixed_case(),
operands[1..].join(", "),
));
}
};
let mut bind_results = |me: &mut FunctionBindgen<'_>| match &func.result {
Type::Unit => {
results.push("".to_string());
}
_ => {
me.src.js("const ret = ");
results.push("ret".to_string());
}
};
if func.is_async {
let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise);
let promises = self.gen.intrinsic(Intrinsic::Promises);
self.src.js(&with);
self.src.js("(null, cur_promise => {\n");
self.src.js(&format!(
"const catch_closure = e => {}.remove(cur_promise)(e);\n",
promises
));
call(self);
self.src.js(".then(e => {\n");
match &func.result {
Type::Unit => {
results.push("".to_string());
}
_ => {
bind_results(self);
self.src.js("e;\n");
}
}
} else {
bind_results(self);
call(self);
self.src.js(";\n");
}
}
Instruction::Return { amt, func: _ } => match amt {
0 => {}
1 => self.src.js(&format!("return {};\n", operands[0])),
_ => {
assert!(self.in_import);
self.src.js(&format!("return [{}];\n", operands.join(", ")));
}
},
Instruction::ReturnAsyncImport { .. } => {
self.gen.needs_get_export = true;
let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise);
self.src.js(&format!(
"\
{with}(cur_promise, _prev => {{
get_export(\"__indirect_function_table\").get({})({});
}});
",
operands[0],
operands[1..].join(", "),
with = with,
));
}
Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
Instruction::I32Load16U { offset } => {
self.load("getUint16", *offset, operands, results)
}
Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
Instruction::Malloc {
realloc,
size,
align,
} => {
self.needs_realloc = Some(realloc.to_string());
let tmp = self.tmp();
let ptr = format!("ptr{}", tmp);
self.src.js(&format!(
"const {} = realloc(0, 0, {}, {});\n",
ptr, align, size
));
results.push(ptr);
}
i => unimplemented!("{:?}", i),
}
}
}
impl Js {
fn print_intrinsics(&mut self) {
if self.all_intrinsics.contains(&Intrinsic::I32ToF32)
|| self.all_intrinsics.contains(&Intrinsic::F32ToI32)
{
self.src.js("
const I32_TO_F32_I = new Int32Array(1);
const I32_TO_F32_F = new Float32Array(I32_TO_F32_I.buffer);
");
}
if self.all_intrinsics.contains(&Intrinsic::I64ToF64)
|| self.all_intrinsics.contains(&Intrinsic::F64ToI64)
{
self.src.js("
const I64_TO_F64_I = new BigInt64Array(1);
const I64_TO_F64_F = new Float64Array(I64_TO_F64_I.buffer);
");
}
if self.all_intrinsics.contains(&Intrinsic::Promises) {
self.all_intrinsics.insert(Intrinsic::Slab);
}
for i in mem::take(&mut self.all_intrinsics) {
self.print_intrinsic(i);
}
self.src.js(&format!(
"\nmodule.exports = {{ {} }};\n",
self.src.exported_items.join(", ")
));
}
fn print_intrinsic(&mut self, i: Intrinsic) {
self.src.export(i.name());
match i {
Intrinsic::ClampGuest => self.src.js("
function clamp_guest(i, min, max) {
if (i < min || i > max) \
throw new RangeError(`must be between ${min} and ${max}`);
return i;
}
"),
Intrinsic::ClampHost => self.src.js("
function clamp_host(i, min, max) {
if (!Number.isInteger(i)) \
throw new TypeError(`must be an integer`);
if (i < min || i > max) \
throw new RangeError(`must be between ${min} and ${max}`);
return i;
}
"),
Intrinsic::DataView => self.src.js("
let DATA_VIEW = new DataView(new ArrayBuffer());
function data_view(mem) {
if (DATA_VIEW.buffer !== mem.buffer) \
DATA_VIEW = new DataView(mem.buffer);
return DATA_VIEW;
}
"),
Intrinsic::ClampHost64 => self.src.js("
function clamp_host64(i, min, max) {
if (typeof i !== 'bigint') \
throw new TypeError(`must be a bigint`);
if (i < min || i > max) \
throw new RangeError(`must be between ${min} and ${max}`);
return i;
}
"),
Intrinsic::ValidateGuestChar => self.src.js("
function validate_guest_char(i) {
if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) \
throw new RangeError(`not a valid char`);
return String.fromCodePoint(i);
}
"),
Intrinsic::ValidateHostChar => self.src.js("
function validate_host_char(s) {
if (typeof s !== 'string') \
throw new TypeError(`must be a string`);
return s.codePointAt(0);
}
"),
Intrinsic::ValidateFlags => self.src.js("
function validate_flags(flags, mask) {
if (!Number.isInteger(flags)) \
throw new TypeError('flags were not an integer');
if ((flags & ~mask) != 0)
throw new TypeError('flags have extraneous bits set');
return flags;
}
"),
Intrinsic::ValidateFlags64 => self.src.js("
function validate_flags64(flags, mask) {
if (typeof flags !== 'bigint')
throw new TypeError('flags were not a bigint');
if ((flags & ~mask) != 0n)
throw new TypeError('flags have extraneous bits set');
return flags;
}
"),
Intrinsic::ToString => self.src.js("
function to_string(val) {
if (typeof val === 'symbol') {
throw new TypeError('symbols cannot be converted to strings');
} else {
// Calling `String` almost directly calls `ToString`, except that it also allows symbols,
// which is why we have the symbol-rejecting branch above.
//
// Definition of `String`: https://tc39.es/ecma262/#sec-string-constructor-string-value
return String(val);
}
}
"),
Intrinsic::I32ToF32 => self.src.js("
function i32ToF32(i) {
I32_TO_F32_I[0] = i;
return I32_TO_F32_F[0];
}
"),
Intrinsic::F32ToI32 => self.src.js("
function f32ToI32(f) {
I32_TO_F32_F[0] = f;
return I32_TO_F32_I[0];
}
"),
Intrinsic::I64ToF64 => self.src.js("
function i64ToF64(i) {
I64_TO_F64_I[0] = i;
return I64_TO_F64_F[0];
}
"),
Intrinsic::F64ToI64 => self.src.js("
function f64ToI64(f) {
I64_TO_F64_F[0] = f;
return I64_TO_F64_I[0];
}
"),
Intrinsic::Utf8Decoder => self
.src
.js("const UTF8_DECODER = new TextDecoder('utf-8');\n"),
Intrinsic::Utf8EncodedLen => self.src.js("
let UTF8_ENCODED_LEN = 0;
function utf8_encoded_len() {
return UTF8_ENCODED_LEN;
}
"),
Intrinsic::Utf8Encode => self.src.js("
const UTF8_ENCODER = new TextEncoder('utf-8');
function utf8_encode(s, realloc, memory) {
if (typeof s !== 'string') \
throw new TypeError('expected a string');
if (s.length === 0) {
UTF8_ENCODED_LEN = 0;
return 1;
}
let alloc_len = 0;
let ptr = 0;
let writtenTotal = 0;
while (s.length > 0) {
ptr = realloc(ptr, alloc_len, 1, alloc_len + s.length);
alloc_len += s.length;
const { read, written } = UTF8_ENCODER.encodeInto(
s,
new Uint8Array(memory.buffer, ptr + writtenTotal, alloc_len - writtenTotal),
);
writtenTotal += written;
s = s.slice(read);
}
if (alloc_len > writtenTotal)
ptr = realloc(ptr, alloc_len, 1, writtenTotal);
UTF8_ENCODED_LEN = writtenTotal;
return ptr;
}
"),
Intrinsic::Slab => self.src.js("
class Slab {
constructor() {
this.list = [];
this.head = 0;
}
insert(val) {
if (this.head >= this.list.length) {
this.list.push({
next: this.list.length + 1,
val: undefined,
});
}
const ret = this.head;
const slot = this.list[ret];
this.head = slot.next;
slot.next = -1;
slot.val = val;
return ret;
}
get(idx) {
if (idx >= this.list.length)
throw new RangeError('handle index not valid');
const slot = this.list[idx];
if (slot.next === -1)
return slot.val;
throw new RangeError('handle index not valid');
}
remove(idx) {
const ret = this.get(idx); // validate the slot
const slot = this.list[idx];
slot.val = undefined;
slot.next = this.head;
this.head = idx;
return ret;
}
}
"),
Intrinsic::Promises => self.src.js("const PROMISES = new Slab();\n"),
Intrinsic::WithCurrentPromise => self.src.js("
let CUR_PROMISE = null;
function with_current_promise(val, closure) {
const prev = CUR_PROMISE;
CUR_PROMISE = val;
try {
closure(prev);
} finally {
CUR_PROMISE = prev;
}
}
"),
Intrinsic::ThrowInvalidBool => self.src.js("
function throw_invalid_bool() {
throw new RangeError(\"invalid variant discriminant for bool\");
}
"),
}
}
}
pub fn to_js_ident(name: &str) -> &str {
match name {
"in" => "in_",
"import" => "import_",
"export" => "export_",
"module" => "module_",
s => s,
}
}
#[derive(Default)]
struct Source {
js: wai_bindgen_gen_core::Source,
ts: wai_bindgen_gen_core::Source,
exported_items: Vec<String>,
}
impl Source {
fn js(&mut self, s: &str) {
self.js.push_str(s);
}
fn ts(&mut self, s: &str) {
self.ts.push_str(s);
}
fn export(&mut self, name: impl Into<String>) {
self.exported_items.push(name.into());
}
}
enum JsFlagsRepr {
Number,
Bigint,
}
impl JsFlagsRepr {
fn ty(&self) -> &'static str {
match self {
JsFlagsRepr::Number => "number",
JsFlagsRepr::Bigint => "bigint",
}
}
fn suffix(&self) -> &'static str {
match self {
JsFlagsRepr::Number => "",
JsFlagsRepr::Bigint => "n",
}
}
}
fn js_flags_repr(f: &Flags) -> JsFlagsRepr {
match f.repr() {
FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => JsFlagsRepr::Number,
FlagsRepr::U32(_) => JsFlagsRepr::Bigint,
}
}