use heck::*; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Write; use std::mem; use wit_bindgen_gen_core::wit_parser::abi::{ AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, }; use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator}; #[derive(Default)] pub struct Js { src: Source, in_import: bool, opts: Opts, guest_imports: HashMap, guest_exports: HashMap, sizes: SizeAlign, intrinsics: BTreeMap, all_intrinsics: BTreeSet, needs_get_export: bool, imported_resources: BTreeSet, exported_resources: BTreeSet, needs_ty_option: bool, needs_ty_result: bool, } #[derive(Default)] struct Imports { freestanding_funcs: Vec<(String, Source)>, resource_funcs: BTreeMap>, } #[derive(Default)] struct Exports { freestanding_funcs: Vec, resource_funcs: BTreeMap>, } #[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, /// Implementation of https://tc39.es/ecma262/#sec-tostring. 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 { // This generator uses a reversed mapping! In the JS host-side // bindings, we don't use any extra adapter layer between guest wasm // modules and the host. When the guest imports functions using the // `GuestImport` ABI, the host directly implements the `GuestImport` // ABI, even though the host is *exporting* functions. Similarly, when // the guest exports functions using the `GuestExport` ABI, the host // directly imports them with the `GuestExport` ABI, even though the // host is *importing* functions. 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 => return, } } fn ts_func(&mut self, iface: &Interface, func: &Function) { self.docs(&func.docs); let mut name_printed = false; if let FunctionKind::Static { .. } = &func.kind { // static methods in imports are still wired up to an imported host // object, but static methods on exports are actually static // methods on the resource object. 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 { .. } => { // the 0th argument for exported static methods will be the // instantiated interface self.src.ts(&iface.name.to_mixed_case()); self.src.ts(": "); self.src.ts(&iface.name.to_camel_case()); if func.params.len() > 0 { self.src.ts(", "); } 0 } // skip the first parameter on methods which is `this` 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(); } // TODO: should select a name that automatically doesn't conflict with // anything else being generated. self.intrinsics.insert(i, i.name().to_string()); return i.name().to_string(); } /// Returns whether `null` is a valid value of type `ty` fn maybe_null(&self, iface: &Interface, ty: &Type) -> bool { self.as_nullable(iface, ty).is_some() } /// Tests whether `ty` can be represented with `null`, and if it can then /// the "other type" is returned. If `Some` is returned that means that `ty` /// is `null | `. If `None` is returned that means that `null` can't /// be used to represent `ty`. 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 { // If `ty` points to an `option`, then `ty` can be represented // with `null` if `t` itself can't be represented with null. For // example `option>` can't be represented with `null` // since that's ambiguous if it's `none` or `some(none)`. // // Note, oddly enough, that `option>>` can be // represented as `null` since: // // * `null` => `none` // * `{ tag: "none" }` => `some(none)` // * `{ tag: "some", val: null }` => `some(some(none))` // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` // // It's doubtful anyone would actually rely on that though due to // how confusing it is. 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(); self.src.js(&format!( "export const {name}_{flag} = {}{suffix};\n", 1u128 << i, )); self.src.ts(&format!( "export const {name}_{flag} = {}{suffix};\n", 1u128 << i, )); } } 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, ) { // The complete documentation for this enum, including documentation for variants. let mut complete_docs = String::new(); if let Some(docs) = &docs.contents { complete_docs.push_str(docs); // Add a gap before the `# Variants` section. 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)); } // As with `abi_variant` above, we're generating host-side bindings here // so a user "export" uses the "guest import" ABI variant on the inside of // this `Generator` implementation. 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::>(); 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; // TODO: hardcoding "memory" 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 { // Note that `catch_closure` here is defined by the `CallInterface` // instruction. self.src.js("}, catch_closure);\n"); // `.then` block self.src.js("});\n"); // `with_current_promise` block. } self.src.js("}"); let src = mem::replace(&mut self.src, prev); let imports = self .guest_imports .entry(iface.name.to_string()) .or_insert(Imports::default()); let dst = match &func.kind { FunctionKind::Freestanding | FunctionKind::Static { .. } => { &mut imports.freestanding_funcs } FunctionKind::Method { resource, .. } => imports .resource_funcs .entry(*resource) .or_insert(Vec::new()), }; dst.push((func.name.to_string(), src)); } // As with `abi_variant` above, we're generating host-side bindings here // so a user "import" uses the "export" ABI variant on the inside of // this `Generator` implementation. 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::>(); 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 { // TODO: hardcoding "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_insert(Vec::new()) .push(func_body); } } } fn finish_one(&mut self, iface: &Interface, files: &mut Files) { for (module, funcs) in mem::take(&mut self.guest_imports) { // TODO: `module.exports` vs `export function` self.src.js(&format!( "export function add{}ToImports(imports, obj{}) {{\n", module.to_camel_case(), 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.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().flat_map(|v| v)) { 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.len() > 0 { 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!("export class {} {{\n", 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.len() > 0 { 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.len() > 0 || 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(&format!( " /** * 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 | WebAssembly.Instance, imports?: any, ): Promise; ", )); self.src.js(" async instantiate(module, imports) { imports = imports || {}; this.addToImports(imports); "); // With intrinsics prep'd we can now instantiate the module. JS has // a ... variety of methods of instantiation, so we basically just // try to be flexible here. 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; "); // Exported resources all get a finalization registry, and we // created them after instantiation so we can pass the raw wasm // export as the destructor callback. 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() { self.src.js(&format!( " export class {} {{ 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.to_camel_case(), iface.resources[ty].name, idx = ty.index(), )); self.src.ts(&format!( " export class {} {{ // 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(): {0}; // 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; ", iface.resources[ty].name.to_camel_case(), )); 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 = { tag: \"none\" } | { tag: \"some\", val; T };\n"); } if mem::take(&mut self.needs_ty_result) { self.src.ts( "export type Result = { tag: \"ok\", val: T } | { tag: \"err\", val: E };\n", ); } if self.intrinsics.len() > 0 { self.src.js("import { "); 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(" } from './intrinsics.js';\n"); } self.src.js(&imports.js); self.src.ts(&imports.ts); self.src.js(&exports.js); self.src.ts(&exports.ts); 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, blocks: Vec<(String, Vec)>, in_import: bool, needs_memory: bool, needs_realloc: Option, needs_free: Option, params: Vec, src_object: String, } impl FunctionBindgen<'_> { fn new(gen: &mut Js, in_import: bool, params: Vec) -> 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(&mut self, results: &mut Vec, 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(&mut self, results: &mut Vec, 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(&mut self, results: &mut Vec, 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) { 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) { 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) { 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, results: &mut Vec, ) { 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()); } } } } // The representation of i32 in JS is a number, so 8/16-bit values // get further clamped to ensure that the upper bits aren't set when // we pass the value, ensuring that only the right number of bits // are transferred. 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), // Use `>>>0` to ensure the bits of the number are treated as // unsigned. Instruction::U32FromI32 => { results.push(format!("{} >>> 0", operands[0])); } // All bigints coming from wasm are treated as signed, so convert // it to ensure it's treated as unsigned. Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])), // Nothing to do signed->signed where the representations are the // same. Instruction::S32FromI32 | Instruction::S64FromI64 => { results.push(operands.pop().unwrap()) } // All values coming from the host and going to wasm need to have // their ranges validated, since the host could give us any value. 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), // The native representation in JS of f32 and f64 is just a number, // so there's nothing to do here. Everything wasm gives us is // representable in JS. Instruction::Float32FromF32 | Instruction::Float64FromF64 => { results.push(operands.pop().unwrap()) } Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => { // Use a unary `+` to cast to a float. results.push(format!("+{}", operands[0])); } // Validate that i32 values coming from wasm are indeed valid code // points. Instruction::CharFromI32 => { let validate = self.gen.intrinsic(Intrinsic::ValidateGuestChar); results.push(format!("{}({})", validate, operands[0])); } // Validate that strings are indeed 1 character long and valid // unicode. 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])); } // These instructions are used with handles when we're implementing // imports. This means we interact with the `resources` slabs to // translate the wasm-provided index into a JS value. 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])); } // These instructions are used for handles to objects owned in wasm. // This means that they're interacting with a wrapper class defined // in JS. Instruction::I32FromBorrowedHandle { ty } => { let tmp = self.tmp(); self.src .js(&format!("const obj{} = {};\n", tmp, operands[0])); // If this is the `this` argument then it's implicitly already valid 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, .. } => { // use destructuring field access to get each // field individually. 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, .. } => { // records are represented as plain objects, so we // make a new object and set all the fields with an object // literal. 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_str("}"); results.push(result); } Instruction::TupleLower { tuple, .. } => { // Tuples are represented as an array, sowe can use // destructuring assignment to lower the tuple into its // components. 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 { .. } => { // Tuples are represented as an array, so we just shove all // the operands into an array. 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::>(); 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::>(); 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::>(); 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::>(); 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}")); } // Lowers an enum in accordance with https://webidl.spec.whatwg.org/#es-enumeration. 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])); // Declare a variable to hold the result. 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 } => { // Lowering only happens when we're passing lists into wasm, // which forces us to always allocate, so this should always be // `Some`. 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, )); // TODO: this is the wrong endianness 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])); // TODO: this is the wrong endianness 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 } => { // Lowering only happens when we're passing strings into wasm, // which forces us to always allocate, so this should always be // `Some`. 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{} = {};\n", tmp, encoded_len)); 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); // first store our vec-to-lower in a temporary since we'll // reference it multiple times. self.src.js(&format!("const {} = {};\n", vec, operands[0])); self.src.js(&format!("const {} = {}.length;\n", len, vec)); // ... then realloc space for the result in the guest module self.src.js(&format!( "const {} = realloc(0, 0, {}, {} * {});\n", result, align, len, size, )); // ... then consume the vector and use the block to lower the // result. 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.len() > 0 { 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("]"); } // Finish the blocks from above self.src.js(");\n"); // `resolve(...)` self.src.js("});\n"); // `promises.insert(...)` 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"); // call to `with` self.src.js("});\n"); // `await new Promise(...)` } 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 { .. } => { // When we reenter webassembly successfully that means that the // host's promise resolved without exception. Take the current // promise index saved as part of `CallInterface` and update the // `CUR_PROMISE` global with what's currently being executed. // This'll get reset once the wasm returns again. // // Note that the name `cur_promise` used here is introduced in // the `CallInterface` codegen above in the closure for // `with_current_promise` which we're using here. // // TODO: hardcoding `__indirect_function_table` and no help if // it's not actually defined. 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); } } fn print_intrinsic(&mut self, i: Intrinsic) { match i { Intrinsic::ClampGuest => self.src.js(" export 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(" export 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()); export function data_view(mem) { if (DATA_VIEW.buffer !== mem.buffer) \ DATA_VIEW = new DataView(mem.buffer); return DATA_VIEW; } "), Intrinsic::ClampHost64 => self.src.js(" export 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(" export function validate_guest_char(i) { if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) \ throw new RangeError(`not a valid char`); return String.fromCodePoint(i); } "), // TODO: this is incorrect. It at least allows strings of length > 0 // but it probably doesn't do the right thing for unicode or invalid // utf16 strings either. Intrinsic::ValidateHostChar => self.src.js(" export 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(" export 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(" export 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(" export 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(" export function i32ToF32(i) { I32_TO_F32_I[0] = i; return I32_TO_F32_F[0]; } "), Intrinsic::F32ToI32 => self.src.js(" export function f32ToI32(f) { I32_TO_F32_F[0] = f; return I32_TO_F32_I[0]; } "), Intrinsic::I64ToF64 => self.src.js(" export function i64ToF64(i) { I64_TO_F64_I[0] = i; return I64_TO_F64_F[0]; } "), Intrinsic::F64ToI64 => self.src.js(" export function f64ToI64(f) { I64_TO_F64_F[0] = f; return I64_TO_F64_I[0]; } "), Intrinsic::Utf8Decoder => self .src .js("export const UTF8_DECODER = new TextDecoder('utf-8');\n"), Intrinsic::Utf8EncodedLen => self.src.js("export let UTF8_ENCODED_LEN = 0;\n"), Intrinsic::Utf8Encode => self.src.js(" const UTF8_ENCODER = new TextEncoder('utf-8'); export 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(" export 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("export const PROMISES = new Slab();\n"), Intrinsic::WithCurrentPromise => self.src.js(" let CUR_PROMISE = null; export 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(" export 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_", s => s, } } #[derive(Default)] struct Source { js: wit_bindgen_gen_core::Source, ts: wit_bindgen_gen_core::Source, } impl Source { fn js(&mut self, s: &str) { self.js.push_str(s); } fn ts(&mut self, s: &str) { self.ts.push_str(s); } } 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, } }