use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt::Write;
use std::mem;
use std::ops::Index;
use base64::Engine as _;
use base64::engine::general_purpose;
use heck::{ToKebabCase, ToLowerCamelCase, ToUpperCamelCase};
use semver::Version;
use wasmtime_environ::component::{
CanonicalOptions, CanonicalOptionsDataModel, Component, ComponentTranslation, ComponentTypes,
CoreDef, CoreExport, Export, ExportItem, FixedEncoding, GlobalInitializer, InstantiateModule,
InterfaceType, LinearMemoryOptions, LoweredIndex, ResourceIndex, RuntimeComponentInstanceIndex,
RuntimeImportIndex, RuntimeInstanceIndex, StaticModuleIndex, Trampoline, TrampolineIndex,
TypeDef, TypeFuncIndex, TypeResourceTableIndex, TypeStreamTableIndex,
};
use wasmtime_environ::component::{
ExportIndex, ExtractCallback, NameMap, NameMapNoIntern, Transcode,
TypeComponentLocalErrorContextTableIndex,
};
use wasmtime_environ::{EntityIndex, PrimaryMap};
use wit_bindgen_core::abi::{self, LiftLower};
use wit_component::StringEncoding;
use wit_parser::abi::AbiVariant;
use wit_parser::{
Function, FunctionKind, Handle, Resolve, Result_, SizeAlign, Type, TypeDefKind, TypeId,
WorldId, WorldItem, WorldKey,
};
use crate::esm_bindgen::EsmBindgen;
use crate::files::Files;
use crate::function_bindgen::{
ErrHandling, FunctionBindgen, ResourceData, ResourceExtraData, ResourceMap, ResourceTable,
};
use crate::intrinsics::component::ComponentIntrinsic;
use crate::intrinsics::js_helper::JsHelperIntrinsic;
use crate::intrinsics::lift::LiftIntrinsic;
use crate::intrinsics::lower::LowerIntrinsic;
use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
use crate::intrinsics::p3::error_context::ErrCtxIntrinsic;
use crate::intrinsics::p3::host::HostIntrinsic;
use crate::intrinsics::p3::waitable::WaitableIntrinsic;
use crate::intrinsics::resource::ResourceIntrinsic;
use crate::intrinsics::string::StringIntrinsic;
use crate::intrinsics::webidl::WebIdlIntrinsic;
use crate::intrinsics::{
AsyncDeterminismProfile, Intrinsic, RenderIntrinsicsArgs, render_intrinsics,
};
use crate::names::{LocalNames, is_js_reserved_word, maybe_quote_id, maybe_quote_member};
use crate::{
FunctionIdentifier, ManagesIntrinsics, core, get_thrown_type, is_async_fn,
requires_async_porcelain, source, uwrite, uwriteln,
};
const MAX_ASYNC_FLAT_PARAMS: usize = 4;
#[derive(Debug, Default, Clone)]
pub struct TranspileOpts {
pub name: String,
pub no_typescript: bool,
pub instantiation: Option<InstantiationMode>,
pub import_bindings: Option<BindingsMode>,
pub map: Option<HashMap<String, String>>,
pub no_nodejs_compat: bool,
pub base64_cutoff: usize,
pub tla_compat: bool,
pub valid_lifting_optimization: bool,
pub tracing: bool,
pub no_namespaced_exports: bool,
pub multi_memory: bool,
pub guest: bool,
pub async_mode: Option<AsyncMode>,
}
#[derive(Default, Clone, Debug)]
pub enum AsyncMode {
#[default]
Sync,
JavaScriptPromiseIntegration {
imports: Vec<String>,
exports: Vec<String>,
},
}
#[derive(Default, Clone, Debug)]
pub enum InstantiationMode {
#[default]
Async,
Sync,
}
enum CallType {
Standard,
AsyncStandard,
FirstArgIsThis,
AsyncFirstArgIsThis,
CalleeResourceDispatch,
AsyncCalleeResourceDispatch,
}
#[derive(Default, Clone, Debug)]
pub enum BindingsMode {
Hybrid,
#[default]
Js,
Optimized,
DirectOptimized,
}
struct JsBindgen<'a> {
local_names: LocalNames,
esm_bindgen: EsmBindgen,
src: Source,
core_module_cnt: usize,
opts: &'a TranspileOpts,
all_intrinsics: BTreeSet<Intrinsic>,
all_core_exported_funcs: Vec<(String, bool)>,
}
struct JsFunctionBindgenArgs<'a> {
nparams: usize,
call_type: CallType,
iface_name: Option<&'a str>,
callee: &'a str,
opts: &'a CanonicalOptions,
func: &'a Function,
resource_map: &'a ResourceMap,
abi: AbiVariant,
requires_async_porcelain: bool,
is_async: bool,
}
impl<'a> ManagesIntrinsics for JsBindgen<'a> {
fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
self.intrinsic(intrinsic);
}
}
#[allow(clippy::too_many_arguments)]
pub fn transpile_bindgen(
name: &str,
component: &ComponentTranslation,
modules: &PrimaryMap<StaticModuleIndex, core::Translation<'_>>,
types: &ComponentTypes,
resolve: &Resolve,
id: WorldId,
opts: TranspileOpts,
files: &mut Files,
) -> (Vec<String>, Vec<(String, Export)>) {
let (async_imports, async_exports) = match opts.async_mode.clone() {
None | Some(AsyncMode::Sync) => (Default::default(), Default::default()),
Some(AsyncMode::JavaScriptPromiseIntegration { imports, exports }) => {
(imports.into_iter().collect(), exports.into_iter().collect())
}
};
let mut bindgen = JsBindgen {
local_names: LocalNames::default(),
src: Source::default(),
esm_bindgen: EsmBindgen::default(),
core_module_cnt: 0,
opts: &opts,
all_intrinsics: BTreeSet::new(),
all_core_exported_funcs: Vec::new(),
};
bindgen.local_names.exclude_globals(
&Intrinsic::get_global_names()
.into_iter()
.collect::<Vec<_>>(),
);
bindgen.core_module_cnt = modules.len();
let mut stream_tables = BTreeMap::new();
for idx in 0..component.component.num_stream_tables {
let stream_table_idx = TypeStreamTableIndex::from_u32(idx as u32);
let stream_table_ty = &types[stream_table_idx];
stream_tables.insert(stream_table_idx, stream_table_ty.instance);
}
let mut err_ctx_tables = BTreeMap::new();
for idx in 0..component.component.num_error_context_tables {
let err_ctx_table_idx = TypeComponentLocalErrorContextTableIndex::from_u32(idx as u32);
let err_ctx_table_ty = &types[err_ctx_table_idx];
err_ctx_tables.insert(err_ctx_table_idx, err_ctx_table_ty.instance);
}
let mut instantiator = Instantiator {
src: Source::default(),
sizes: SizeAlign::default(),
bindgen: &mut bindgen,
modules,
instances: Default::default(),
error_context_component_initialized: (0..component
.component
.num_runtime_component_instances)
.map(|_| false)
.collect(),
error_context_component_table_initialized: (0..component
.component
.num_error_context_tables)
.map(|_| false)
.collect(),
resolve,
world: id,
translation: component,
component: &component.component,
types,
async_imports,
async_exports,
imports: Default::default(),
exports: Default::default(),
lowering_options: Default::default(),
used_instance_flags: Default::default(),
defined_resource_classes: Default::default(),
imports_resource_types: Default::default(),
imports_resource_index_types: Default::default(),
exports_resource_types: Default::default(),
exports_resource_index_types: Default::default(),
resource_exports: Default::default(),
resource_imports: Default::default(),
resources_initialized: BTreeMap::new(),
resource_tables_initialized: BTreeMap::new(),
stream_tables,
err_ctx_tables,
};
instantiator.sizes.fill(resolve);
instantiator.initialize();
instantiator.instantiate();
let mut intrinsic_definitions = source::Source::default();
instantiator.resource_definitions(&mut intrinsic_definitions);
instantiator.instance_flags();
instantiator.bindgen.src.js(&instantiator.src.js);
instantiator.bindgen.src.js_init(&instantiator.src.js_init);
instantiator
.bindgen
.finish_component(name, files, &opts, intrinsic_definitions);
let exports = instantiator
.bindgen
.esm_bindgen
.exports()
.iter()
.map(|(export_name, canon_export_name)| {
let expected_export_name =
if canon_export_name.contains(':') || canon_export_name.starts_with("[async]") {
canon_export_name.to_string()
} else {
canon_export_name.to_kebab_case()
};
let export = instantiator
.component
.exports
.get(&expected_export_name, &NameMapNoIntern)
.unwrap_or_else(|| panic!("failed to find component export [{expected_export_name}] (original '{canon_export_name}')"));
(
export_name.to_string(),
instantiator.component.export_items[*export].clone(),
)
})
.collect();
(bindgen.esm_bindgen.import_specifiers(), exports)
}
impl JsBindgen<'_> {
fn finish_component(
&mut self,
name: &str,
files: &mut Files,
opts: &TranspileOpts,
intrinsic_definitions: source::Source,
) {
let mut output = source::Source::default();
let mut compilation_promises = source::Source::default();
let mut core_exported_funcs = source::Source::default();
for (core_export_fn, is_async) in self.all_core_exported_funcs.iter() {
let local_name = self.local_names.get(core_export_fn);
if *is_async {
uwriteln!(
core_exported_funcs,
"{local_name} = WebAssembly.promising({core_export_fn});",
);
} else {
uwriteln!(core_exported_funcs, "{local_name} = {core_export_fn};",);
}
}
if matches!(self.opts.instantiation, Some(InstantiationMode::Async)) {
uwriteln!(
compilation_promises,
"if (!getCoreModule) getCoreModule = (name) => {}(new URL(`./${{name}}`, import.meta.url));",
self.intrinsic(Intrinsic::FetchCompile)
);
}
let mut removed = BTreeSet::new();
for i in 0..self.core_module_cnt {
let local_name = format!("module{i}");
let mut name_idx = core_file_name(name, i as u32);
if self.opts.instantiation.is_some() {
uwriteln!(
compilation_promises,
"const {local_name} = getCoreModule('{name_idx}');"
);
} else if files.get_size(&name_idx).unwrap() < self.opts.base64_cutoff {
assert!(removed.insert(i));
let data = files.remove(&name_idx).unwrap();
uwriteln!(
compilation_promises,
"const {local_name} = {}('{}');",
self.intrinsic(Intrinsic::Base64Compile),
general_purpose::STANDARD_NO_PAD.encode(&data),
);
} else {
if let Some(&replacement) = removed.iter().next() {
assert!(removed.remove(&replacement) && removed.insert(i));
let data = files.remove(&name_idx).unwrap();
name_idx = core_file_name(name, replacement as u32);
files.push(&name_idx, &data);
}
uwriteln!(
compilation_promises,
"const {local_name} = {}(new URL('./{name_idx}', import.meta.url));",
self.intrinsic(Intrinsic::FetchCompile)
);
}
}
uwriteln!(output, r#""use jco";"#);
let js_intrinsics = render_intrinsics(RenderIntrinsicsArgs {
intrinsics: &mut self.all_intrinsics,
no_nodejs_compat: self.opts.no_nodejs_compat,
instantiation: self.opts.instantiation.is_some(),
determinism: AsyncDeterminismProfile::default(),
});
if let Some(instantiation) = &self.opts.instantiation {
uwrite!(
output,
"\
export function instantiate(getCoreModule, imports, instantiateCore = {}) {{
{}
{}
{}
",
match instantiation {
InstantiationMode::Async => "WebAssembly.instantiate",
InstantiationMode::Sync =>
"(module, importObject) => new WebAssembly.Instance(module, importObject)",
},
&js_intrinsics as &str,
&intrinsic_definitions as &str,
&compilation_promises as &str,
);
}
let imports_object = if self.opts.instantiation.is_some() {
Some("imports")
} else {
None
};
self.esm_bindgen
.render_imports(&mut output, imports_object, &mut self.local_names);
if self.opts.instantiation.is_some() {
uwrite!(&mut self.src.js, "{}", &core_exported_funcs as &str);
self.esm_bindgen.render_exports(
&mut self.src.js,
self.opts.instantiation.is_some(),
&mut self.local_names,
opts,
);
uwrite!(
output,
"\
let gen = (function* _initGenerator () {{
{}\
{};
}})();
let promise, resolve, reject;
function runNext (value) {{
try {{
let done;
do {{
({{ value, done }} = gen.next(value));
}} while (!(value instanceof Promise) && !done);
if (done) {{
if (resolve) return resolve(value);
else return value;
}}
if (!promise) promise = new Promise((_resolve, _reject) => (resolve = _resolve, reject = _reject));
value.then(nextVal => done ? resolve() : runNext(nextVal), reject);
}}
catch (e) {{
if (reject) reject(e);
else throw e;
}}
}}
const maybeSyncReturn = runNext(null);
return promise || maybeSyncReturn;
}};
",
&self.src.js_init as &str,
&self.src.js as &str,
);
} else {
let (maybe_init_export, maybe_init) =
if self.opts.tla_compat && opts.instantiation.is_none() {
uwriteln!(self.src.js_init, "_initialized = true;");
(
"\
let _initialized = false;
export ",
"",
)
} else {
(
"",
"
await $init;
",
)
};
uwrite!(
output,
"\
{}
{}
{}
{maybe_init_export}const $init = (() => {{
let gen = (function* _initGenerator () {{
{}\
{}\
{}\
}})();
let promise, resolve, reject;
function runNext (value) {{
try {{
let done;
do {{
({{ value, done }} = gen.next(value));
}} while (!(value instanceof Promise) && !done);
if (done) {{
if (resolve) resolve(value);
else return value;
}}
if (!promise) promise = new Promise((_resolve, _reject) => (resolve = _resolve, reject = _reject));
value.then(runNext, reject);
}}
catch (e) {{
if (reject) reject(e);
else throw e;
}}
}}
const maybeSyncReturn = runNext(null);
return promise || maybeSyncReturn;
}})();
{maybe_init}\
",
&js_intrinsics as &str,
&intrinsic_definitions as &str,
&self.src.js as &str,
&compilation_promises as &str,
&self.src.js_init as &str,
&core_exported_funcs as &str,
);
self.esm_bindgen.render_exports(
&mut output,
self.opts.instantiation.is_some(),
&mut self.local_names,
opts,
);
}
let mut bytes = output.as_bytes();
if bytes[0] == b'\n' {
bytes = &bytes[1..];
}
files.push(&format!("{name}.js"), bytes);
}
fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
self.all_intrinsics.insert(intrinsic);
intrinsic.name().to_string()
}
}
pub(crate) struct Instantiator<'a, 'b> {
src: Source,
bindgen: &'a mut JsBindgen<'b>,
modules: &'a PrimaryMap<StaticModuleIndex, core::Translation<'a>>,
instances: PrimaryMap<RuntimeInstanceIndex, StaticModuleIndex>,
types: &'a ComponentTypes,
resolve: &'a Resolve,
world: WorldId,
sizes: SizeAlign,
component: &'a Component,
error_context_component_initialized: PrimaryMap<RuntimeComponentInstanceIndex, bool>,
error_context_component_table_initialized:
PrimaryMap<TypeComponentLocalErrorContextTableIndex, bool>,
translation: &'a ComponentTranslation,
exports_resource_types: BTreeMap<TypeId, ResourceIndex>,
exports_resource_index_types: BTreeMap<ResourceIndex, TypeId>,
imports_resource_types: BTreeMap<TypeId, ResourceIndex>,
#[allow(unused)]
imports_resource_index_types: BTreeMap<ResourceIndex, TypeId>,
resources_initialized: BTreeMap<ResourceIndex, bool>,
resource_tables_initialized: BTreeMap<TypeResourceTableIndex, bool>,
exports: BTreeMap<String, WorldKey>,
imports: BTreeMap<String, WorldKey>,
used_instance_flags: RefCell<BTreeSet<RuntimeComponentInstanceIndex>>,
defined_resource_classes: BTreeSet<String>,
async_imports: HashSet<String>,
async_exports: HashSet<String>,
lowering_options:
PrimaryMap<LoweredIndex, (&'a CanonicalOptions, TrampolineIndex, TypeFuncIndex)>,
stream_tables: BTreeMap<TypeStreamTableIndex, RuntimeComponentInstanceIndex>,
err_ctx_tables:
BTreeMap<TypeComponentLocalErrorContextTableIndex, RuntimeComponentInstanceIndex>,
resource_exports: ResourceMap,
resource_imports: ResourceMap,
}
impl<'a> ManagesIntrinsics for Instantiator<'a, '_> {
fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
self.bindgen.intrinsic(intrinsic);
}
}
impl<'a> Instantiator<'a, '_> {
fn initialize(&mut self) {
for (key, _) in &self.resolve.worlds[self.world].imports {
let name = &self.resolve.name_world_key(key);
self.imports.insert(name.to_string(), key.clone());
}
for (key, _) in &self.resolve.worlds[self.world].exports {
let name = &self.resolve.name_world_key(key);
self.exports.insert(name.to_string(), key.clone());
}
for (key, item) in &self.resolve.worlds[self.world].imports {
let name = &self.resolve.name_world_key(key);
let Some((_, (_, import))) = self
.component
.import_types
.iter()
.find(|(_, (impt_name, _))| impt_name == name)
else {
match item {
WorldItem::Interface { .. } => {
unreachable!("unexpected interface in import types during initialization")
}
WorldItem::Function(_) => {
unreachable!("unexpected function in import types during initialization")
}
WorldItem::Type { id, .. } => {
assert!(!matches!(
self.resolve.types[*id].kind,
TypeDefKind::Resource
))
}
}
continue;
};
match item {
WorldItem::Interface { id, .. } => {
let TypeDef::ComponentInstance(instance) = import else {
unreachable!("unexpectedly non-component instance import in interface")
};
let import_ty = &self.types[*instance];
let iface = &self.resolve.interfaces[*id];
for (ty_name, ty) in &iface.types {
match &import_ty.exports.get(ty_name) {
Some(TypeDef::Resource(resource_table_idx)) => {
let ty = crate::dealias(self.resolve, *ty);
let resource_table_ty = &self.types[*resource_table_idx];
self.imports_resource_types
.insert(ty, resource_table_ty.unwrap_concrete_ty());
}
Some(TypeDef::Interface(_)) | None => {}
Some(_) => unreachable!("unexpected type in interface"),
}
}
}
WorldItem::Function(_) => {}
WorldItem::Type { id, .. } => match import {
TypeDef::Resource(resource) => {
let ty = crate::dealias(self.resolve, *id);
let resource_table_ty = &self.types[*resource];
self.imports_resource_types
.insert(ty, resource_table_ty.unwrap_concrete_ty());
}
TypeDef::Interface(_) => {}
_ => unreachable!("unexpected type in import world item"),
},
}
}
self.exports_resource_types = self.imports_resource_types.clone();
for (key, item) in &self.resolve.worlds[self.world].exports {
let name = &self.resolve.name_world_key(key);
let (_, export_idx) = self
.component
.exports
.raw_iter()
.find(|(expt_name, _)| *expt_name == name)
.unwrap();
let export = &self.component.export_items[*export_idx];
match item {
WorldItem::Interface { id, .. } => {
let iface = &self.resolve.interfaces[*id];
let Export::Instance { exports, .. } = &export else {
unreachable!("unexpectedly non export instance item")
};
for (ty_name, ty) in &iface.types {
match self.component.export_items
[*exports.get(ty_name, &NameMapNoIntern).unwrap()]
{
Export::Type(TypeDef::Resource(resource)) => {
let ty = crate::dealias(self.resolve, *ty);
let resource_table_ty = &self.types[resource];
let concrete_ty = resource_table_ty.unwrap_concrete_ty();
self.exports_resource_types.insert(ty, concrete_ty);
self.exports_resource_index_types.insert(concrete_ty, ty);
}
Export::Type(_) => {}
_ => unreachable!(
"unexpected type in component export items on iface [{iface_name}]",
iface_name = iface.name.as_deref().unwrap_or("<unknown>"),
),
}
}
}
WorldItem::Function(_) => {}
WorldItem::Type { .. } => unreachable!("unexpected exported world item type"),
}
}
}
fn instantiate(&mut self) {
for (i, trampoline) in self.translation.trampolines.iter() {
let Trampoline::LowerImport {
index,
lower_ty,
options,
} = trampoline
else {
continue;
};
let options = self
.component
.options
.get(*options)
.expect("failed to find canon options");
let i = self.lowering_options.push((options, i, *lower_ty));
assert_eq!(i, *index);
}
if let Some(InstantiationMode::Async) = self.bindgen.opts.instantiation {
if self.modules.len() > 1 {
self.src.js_init.push_str("Promise.all([");
for i in 0..self.modules.len() {
if i > 0 {
self.src.js_init.push_str(", ");
}
self.src.js_init.push_str(&format!("module{i}"));
}
uwriteln!(self.src.js_init, "]).catch(() => {{}});");
}
}
let global_stream_table_map =
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GlobalStreamTableMap).name();
let rep_table_class = Intrinsic::RepTableClass.name();
for (table_idx, component_idx) in self.stream_tables.iter() {
self.src.js.push_str(&format!(
"{global_stream_table_map}[{}] = {{ componentIdx: {}, table: new {rep_table_class}() }};\n",
table_idx.as_u32(),
component_idx.as_u32(),
));
}
let global_err_ctx_table_map =
Intrinsic::ErrCtx(ErrCtxIntrinsic::GlobalErrCtxTableMap).name();
let rep_table_class = Intrinsic::RepTableClass.name();
for (table_idx, component_idx) in self.err_ctx_tables.iter() {
self.src.js.push_str(&format!(
"{global_err_ctx_table_map}[{}] = {{ componentIdx: {}, table: new {rep_table_class}() }};\n",
table_idx.as_u32(),
component_idx.as_u32(),
));
}
let mut lower_import_initializers = Vec::new();
for init in self.component.initializers.iter() {
match init {
GlobalInitializer::InstantiateModule(_m, _maybe_idx) => {
for lower_import_init in lower_import_initializers.drain(..) {
self.instantiation_global_initializer(lower_import_init);
}
}
GlobalInitializer::LowerImport { .. } => {
lower_import_initializers.push(init);
continue;
}
_ => {}
}
self.instantiation_global_initializer(init);
}
for init in lower_import_initializers.drain(..) {
self.instantiation_global_initializer(init);
}
self.exports(&self.component.exports);
for (i, trampoline) in self
.translation
.trampolines
.iter()
.filter(|(_, t)| Instantiator::is_early_trampoline(t))
{
self.trampoline(i, trampoline);
}
if self.bindgen.opts.instantiation.is_some() {
let js_init = mem::take(&mut self.src.js_init);
self.src.js.push_str(&js_init);
}
for (i, trampoline) in self
.translation
.trampolines
.iter()
.filter(|(_, t)| !Instantiator::is_early_trampoline(t))
{
self.trampoline(i, trampoline);
}
}
fn ensure_local_resource_class(&mut self, local_name: String) {
if !self.defined_resource_classes.contains(&local_name) {
uwriteln!(
self.src.js,
"\nclass {local_name} {{
constructor () {{
throw new Error('\"{local_name}\" resource does not define a constructor');
}}
}}"
);
self.defined_resource_classes.insert(local_name.to_string());
}
}
fn resource_definitions(&mut self, definitions: &mut source::Source) {
for resource in 0..self.component.num_resources {
let resource = ResourceIndex::from_u32(resource);
let is_imported = self.component.defined_resource_index(resource).is_none();
if is_imported {
continue;
}
if let Some(local_name) = self.bindgen.local_names.try_get(resource) {
self.ensure_local_resource_class(local_name.to_string());
}
}
if self.bindgen.all_intrinsics.contains(&Intrinsic::Resource(
ResourceIntrinsic::ResourceTransferBorrow,
)) || self.bindgen.all_intrinsics.contains(&Intrinsic::Resource(
ResourceIntrinsic::ResourceTransferBorrowValidLifting,
)) {
let defined_resource_tables = Intrinsic::DefinedResourceTables.name();
uwrite!(definitions, "const {defined_resource_tables} = [");
for tidx in 0..self.component.num_resources {
let tid = TypeResourceTableIndex::from_u32(tidx);
let resource_table_ty = &self.types[tid];
let rid = resource_table_ty.unwrap_concrete_ty();
if let Some(defined_index) = self.component.defined_resource_index(rid) {
let instance_idx = resource_table_ty.unwrap_concrete_instance();
if instance_idx == self.component.defined_resource_instances[defined_index] {
uwrite!(definitions, "true,");
}
} else {
uwrite!(definitions, ",");
};
}
uwrite!(definitions, "];\n");
}
}
fn ensure_error_context_local_table(
&mut self,
component_idx: RuntimeComponentInstanceIndex,
err_ctx_tbl_idx: TypeComponentLocalErrorContextTableIndex,
) {
if self.error_context_component_initialized[component_idx]
&& self.error_context_component_table_initialized[err_ctx_tbl_idx]
{
return;
}
let err_ctx_local_tables = self
.bindgen
.intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ComponentLocalTable));
let rep_table_class = Intrinsic::RepTableClass.name();
let c = component_idx.as_u32();
if !self.error_context_component_initialized[component_idx] {
uwriteln!(self.src.js, "{err_ctx_local_tables}.set({c}, new Map());");
self.error_context_component_initialized[component_idx] = true;
}
if !self.error_context_component_table_initialized[err_ctx_tbl_idx] {
let t = err_ctx_tbl_idx.as_u32();
uwriteln!(
self.src.js,
"{err_ctx_local_tables}.get({c}).set({t}, new {rep_table_class}({{ target: `component [{c}] local error ctx table [{t}]` }}));"
);
self.error_context_component_table_initialized[err_ctx_tbl_idx] = true;
}
}
fn ensure_resource_table(&mut self, resource_table_idx: TypeResourceTableIndex) {
if self
.resource_tables_initialized
.contains_key(&resource_table_idx)
{
return;
}
let resource_table_ty = &self.types[resource_table_idx];
let resource_idx = resource_table_ty.unwrap_concrete_ty();
let (is_imported, maybe_dtor) =
if let Some(resource_idx) = self.component.defined_resource_index(resource_idx) {
let resource_def = self
.component
.initializers
.iter()
.find_map(|i| match i {
GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
_ => None,
})
.unwrap();
if let Some(dtor) = &resource_def.dtor {
(false, format!("\n{}(rep);", self.core_def(dtor)))
} else {
(false, "".into())
}
} else {
(true, "".into())
};
let handle_tables = self.bindgen.intrinsic(Intrinsic::HandleTables);
let rsc_table_flag = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
let rsc_table_remove = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
let rtid = resource_table_idx.as_u32();
if is_imported {
uwriteln!(
self.src.js,
"const handleTable{rtid} = [{rsc_table_flag}, 0];",
);
if !self.resources_initialized.contains_key(&resource_idx) {
let ridx = resource_idx.as_u32();
uwriteln!(
self.src.js,
"const captureTable{ridx} = new Map();
let captureCnt{ridx} = 0;"
);
self.resources_initialized.insert(resource_idx, true);
}
} else {
let finalization_registry_create = self
.bindgen
.intrinsic(Intrinsic::FinalizationRegistryCreate);
uwriteln!(
self.src.js,
"const handleTable{rtid} = [{rsc_table_flag}, 0];
const finalizationRegistry{rtid} = {finalization_registry_create}((handle) => {{
const {{ rep }} = {rsc_table_remove}(handleTable{rtid}, handle);{maybe_dtor}
}});
",
);
}
uwriteln!(self.src.js, "{handle_tables}[{rtid}] = handleTable{rtid};");
self.resource_tables_initialized
.insert(resource_table_idx, true);
}
fn instance_flags(&mut self) {
let mut instance_flag_defs = String::new();
for used in self.used_instance_flags.borrow().iter() {
let i = used.as_u32();
uwriteln!(
&mut instance_flag_defs,
"const instanceFlags{i} = new WebAssembly.Global({{ value: \"i32\", mutable: true }}, {});",
wasmtime_environ::component::FLAG_MAY_LEAVE
);
}
self.src.js_init.prepend_str(&instance_flag_defs);
}
fn is_early_trampoline(trampoline: &Trampoline) -> bool {
matches!(
trampoline,
Trampoline::AsyncStartCall { .. }
| Trampoline::BackpressureDec { .. }
| Trampoline::BackpressureInc { .. }
| Trampoline::ContextGet { .. }
| Trampoline::ContextSet { .. }
| Trampoline::EnterSyncCall
| Trampoline::ErrorContextDebugMessage { .. }
| Trampoline::ErrorContextDrop { .. }
| Trampoline::ErrorContextNew { .. }
| Trampoline::ErrorContextTransfer
| Trampoline::ExitSyncCall
| Trampoline::FutureCancelRead { .. }
| Trampoline::FutureCancelWrite { .. }
| Trampoline::FutureDropReadable { .. }
| Trampoline::FutureDropWritable { .. }
| Trampoline::FutureRead { .. }
| Trampoline::FutureWrite { .. }
| Trampoline::LowerImport { .. }
| Trampoline::PrepareCall { .. }
| Trampoline::ResourceDrop { .. }
| Trampoline::ResourceNew { .. }
| Trampoline::ResourceRep { .. }
| Trampoline::ResourceTransferBorrow
| Trampoline::ResourceTransferOwn
| Trampoline::StreamCancelRead { .. }
| Trampoline::StreamCancelWrite { .. }
| Trampoline::StreamDropReadable { .. }
| Trampoline::StreamDropWritable { .. }
| Trampoline::StreamNew { .. }
| Trampoline::StreamRead { .. }
| Trampoline::StreamTransfer
| Trampoline::StreamWrite { .. }
| Trampoline::SubtaskCancel { .. }
| Trampoline::SubtaskDrop { .. }
| Trampoline::SyncStartCall { .. }
| Trampoline::TaskCancel { .. }
| Trampoline::TaskReturn { .. }
| Trampoline::WaitableJoin { .. }
| Trampoline::WaitableSetDrop { .. }
| Trampoline::WaitableSetNew { .. }
| Trampoline::WaitableSetPoll { .. }
| Trampoline::WaitableSetWait { .. }
)
}
fn trampoline(&mut self, i: TrampolineIndex, trampoline: &'a Trampoline) {
let i = i.as_u32();
match trampoline {
Trampoline::TaskCancel { instance } => {
let task_cancel_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::TaskCancel));
uwriteln!(
self.src.js,
"const trampoline{i} = {task_cancel_fn}.bind(null, {instance_idx});\n",
instance_idx = instance.as_u32(),
);
}
Trampoline::SubtaskCancel { instance, async_ } => {
let task_cancel_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::SubtaskCancel));
uwriteln!(
self.src.js,
"const trampoline{i} = {task_cancel_fn}.bind(null, {instance_idx}, {async_});\n",
instance_idx = instance.as_u32(),
);
}
Trampoline::SubtaskDrop { instance } => {
let component_idx = instance.as_u32();
let subtask_drop_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::SubtaskDrop));
uwriteln!(
self.src.js,
"const trampoline{i} = {subtask_drop_fn}.bind(
null,
{component_idx},
);"
);
}
Trampoline::WaitableSetNew { instance } => {
let waitable_set_new_fn = self
.bindgen
.intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetNew));
uwriteln!(
self.src.js,
"const trampoline{i} = {waitable_set_new_fn}.bind(null, {});\n",
instance.as_u32(),
);
}
Trampoline::WaitableSetWait { instance, options } => {
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
assert_eq!(
instance.as_u32(),
options.instance.as_u32(),
"options index instance must match trampoline"
);
let CanonicalOptions {
instance,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
..
} = options
else {
panic!("unexpected/missing memory data model during waitable-set.wait");
};
let instance_idx = instance.as_u32();
let memory_idx = memory
.expect("missing memory idx for waitable-set.wait")
.as_u32();
let waitable_set_wait_fn = self
.bindgen
.intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetWait));
uwriteln!(
self.src.js,
r#"
const trampoline{i} = new WebAssembly.Suspending({waitable_set_wait_fn}.bind(null, {{
componentIdx: {instance_idx},
isAsync: {async_},
memoryIdx: {memory_idx},
getMemoryFn: () => memory{memory_idx},
}}));
"#,
);
}
Trampoline::WaitableSetPoll { options, .. } => {
let CanonicalOptions {
instance,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
cancellable,
..
} = self
.component
.options
.get(*options)
.expect("failed to find options")
else {
panic!("unexpected memory data model during waitable-set.poll");
};
let instance_idx = instance.as_u32();
let memory_idx = memory
.expect("missing memory idx for waitable-set.poll")
.as_u32();
let waitable_set_poll_fn = self
.bindgen
.intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetPoll));
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {waitable_set_poll_fn}.bind(
null,
{{
componentIdx: {instance_idx},
isAsync: {async_},
isCancellable: {cancellable},
memoryIdx: {memory_idx},
getMemoryFn: () => memory{memory_idx},
}}
);
"#,
);
}
Trampoline::WaitableSetDrop { instance } => {
let waitable_set_drop_fn = self
.bindgen
.intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetDrop));
uwriteln!(
self.src.js,
"const trampoline{i} = {waitable_set_drop_fn}.bind(null, {instance_idx});\n",
instance_idx = instance.as_u32(),
);
}
Trampoline::WaitableJoin { instance } => {
let waitable_join_fn = self
.bindgen
.intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableJoin));
uwriteln!(
self.src.js,
"const trampoline{i} = {waitable_join_fn}.bind(null, {instance_idx});\n",
instance_idx = instance.as_u32(),
);
}
Trampoline::StreamNew { ty, instance } => {
let stream_new_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamNew));
let instance_idx = instance.as_u32();
let stream_table_idx = ty.as_u32();
let table_ty = &self.types[*ty];
let stream_ty_idx = table_ty.ty;
let stream_ty_idx_js = stream_ty_idx.as_u32();
let stream_ty = &self.types[stream_ty_idx];
let (
align_32_js,
size_32_js,
flat_count_js,
lift_fn_js,
lower_fn_js,
is_none_js,
is_numeric_type_js,
is_borrow_js,
is_async_value_js,
) = match stream_ty.payload {
None => (
"0".into(),
"0".into(),
"0".into(),
"null".into(),
"null".into(),
"true",
"false".into(),
"false".into(),
"false".into(),
),
Some(ty) => (
self.types.canonical_abi(&ty).align32.to_string(),
self.types.canonical_abi(&ty).size32.to_string(),
self.types
.canonical_abi(&ty)
.flat_count
.map(|v| v.to_string())
.unwrap_or_else(|| "null".into()),
gen_flat_lift_fn_js_expr(
self,
&ty,
&wasmtime_environ::component::StringEncoding::Utf8,
),
gen_flat_lower_fn_js_expr(
self,
self.types,
&ty,
&wasmtime_environ::component::StringEncoding::Utf8,
),
"false",
format!(
"{}",
matches!(
ty,
InterfaceType::U8
| InterfaceType::U16
| InterfaceType::U32
| InterfaceType::U64
| InterfaceType::S8
| InterfaceType::S16
| InterfaceType::S32
| InterfaceType::S64
| InterfaceType::Float32
| InterfaceType::Float64
)
),
format!("{}", matches!(ty, InterfaceType::Borrow(_))),
format!(
"{}",
matches!(ty, InterfaceType::Stream(_) | InterfaceType::Future(_))
),
),
};
uwriteln!(
self.src.js,
"const trampoline{i} = {stream_new_fn}.bind(null, {{
streamTableIdx: {stream_table_idx},
callerComponentIdx: {instance_idx},
elemMeta: {{
liftFn: {lift_fn_js},
lowerFn: {lower_fn_js},
typeIdx: {stream_ty_idx_js},
isNone: {is_none_js},
isNumeric: {is_numeric_type_js},
isBorrowed: {is_borrow_js},
isAsyncValue: {is_async_value_js},
flatCount: {flat_count_js},
align32: {align_32_js},
size32: {size_32_js},
}},
}});\n",
);
}
Trampoline::StreamRead {
instance,
ty,
options,
} => {
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
assert_eq!(
instance.as_u32(),
options.instance.as_u32(),
"options index instance must match trampoline"
);
let CanonicalOptions {
instance,
string_encoding,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
..
} = options
else {
unreachable!("missing/invalid data model for options during stream.read")
};
let memory_idx = memory.expect("missing memory idx for stream.read").as_u32();
let (realloc_idx, get_realloc_fn_js) = match realloc {
Some(v) => {
let v = v.as_u32().to_string();
(v.to_string(), format!("() => realloc{v}"))
}
None => ("null".into(), "null".into()),
};
let component_instance_id = instance.as_u32();
let string_encoding = string_encoding_js_literal(string_encoding);
let stream_table_idx = ty.as_u32();
let stream_read_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamRead));
let register_global_memory_for_component_fn =
Intrinsic::RegisterGlobalMemoryForComponent.name();
uwriteln!(
self.src.js_init,
r#"{register_global_memory_for_component_fn}({{
componentIdx: {component_instance_id},
memoryIdx: {memory_idx},
memory: memory{memory_idx},
}});"#
);
uwriteln!(
self.src.js,
r#"const trampoline{i} = new WebAssembly.Suspending({stream_read_fn}.bind(
null,
{{
componentIdx: {component_instance_id},
memoryIdx: {memory_idx},
getMemoryFn: () => memory{memory_idx},
reallocIdx: {realloc_idx},
getReallocFn: {get_realloc_fn_js},
stringEncoding: {string_encoding},
isAsync: {async_},
streamTableIdx: {stream_table_idx},
}}
));
"#,
);
}
Trampoline::StreamWrite {
instance,
ty,
options,
} => {
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
assert_eq!(
instance.as_u32(),
options.instance.as_u32(),
"options index instance must match trampoline"
);
let CanonicalOptions {
instance,
string_encoding,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
..
} = options
else {
unreachable!("unexpected memory data model during stream.write");
};
let component_instance_id = instance.as_u32();
let memory_idx = memory
.expect("missing memory idx for stream.write")
.as_u32();
let (realloc_idx, get_realloc_fn_js) = match realloc {
Some(v) => {
let v = v.as_u32().to_string();
(v.to_string(), format!("() => realloc{v}"))
}
None => ("null".into(), "() => null".into()),
};
let string_encoding = string_encoding_js_literal(string_encoding);
let stream_table_idx = ty.as_u32();
let stream_write_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamWrite));
let register_global_memory_for_component_fn =
Intrinsic::RegisterGlobalMemoryForComponent.name();
uwriteln!(
self.src.js_init,
r#"{register_global_memory_for_component_fn}({{
componentIdx: {component_instance_id},
memoryIdx: {memory_idx},
memory: memory{memory_idx},
}});"#
);
uwriteln!(
self.src.js,
r#"
const trampoline{i} = new WebAssembly.Suspending({stream_write_fn}.bind(
null,
{{
componentIdx: {component_instance_id},
memoryIdx: {memory_idx},
getMemoryFn: () => memory{memory_idx},
reallocIdx: {realloc_idx},
getReallocFn: {get_realloc_fn_js},
stringEncoding: {string_encoding},
isAsync: {async_},
streamTableIdx: {stream_table_idx},
}}
));
"#,
);
}
Trampoline::StreamCancelRead {
instance,
ty,
async_,
}
| Trampoline::StreamCancelWrite {
instance,
ty,
async_,
} => {
let stream_cancel_fn = match trampoline {
Trampoline::StreamCancelRead { .. } => self.bindgen.intrinsic(
Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamCancelRead),
),
Trampoline::StreamCancelWrite { .. } => self.bindgen.intrinsic(
Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamCancelWrite),
),
_ => unreachable!("unexpected trampoline"),
};
let stream_table_idx = ty.as_u32();
let component_idx = instance.as_u32();
uwriteln!(
self.src.js,
r#"
const trampoline{i} = new WebAssembly.Suspending({stream_cancel_fn}.bind(null, {{
streamTableIdx: {stream_table_idx},
isAsync: {async_},
componentIdx: {component_idx},
}}));
"#,
);
}
Trampoline::StreamDropReadable { ty, instance }
| Trampoline::StreamDropWritable { ty, instance } => {
let intrinsic_fn = match trampoline {
Trampoline::StreamDropReadable { .. } => self.bindgen.intrinsic(
Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamDropReadable),
),
Trampoline::StreamDropWritable { .. } => self.bindgen.intrinsic(
Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamDropWritable),
),
_ => unreachable!("unexpected trampoline"),
};
let stream_idx = ty.as_u32();
let instance_idx = instance.as_u32();
uwriteln!(
self.src.js,
"const trampoline{i} = {intrinsic_fn}.bind(null, {{
streamTableIdx: {stream_idx},
componentIdx: {instance_idx},
}});\n",
);
}
Trampoline::StreamTransfer => {
let stream_transfer_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamTransfer));
uwriteln!(self.src.js, "const trampoline{i} = {stream_transfer_fn};\n",);
}
Trampoline::FutureNew { ty, .. } => {
let future_new_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureNew));
uwriteln!(
self.src.js,
"const trampoline{i} = {future_new_fn}.bind(null, {});\n",
ty.as_u32(),
);
}
Trampoline::FutureRead { ty, options, .. } => {
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
let future_idx = ty.as_u32();
let CanonicalOptions {
instance,
string_encoding,
callback,
post_return,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
..
} = options
else {
unreachable!("unexpected memory data model during future.read");
};
let component_instance_id = instance.as_u32();
let memory_idx = memory.expect("missing memory idx for future.read").as_u32();
let realloc_idx = realloc
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into());
let string_encoding = string_encoding_js_literal(string_encoding);
assert!(
callback.is_none(),
"callback should not be present for future read"
);
assert!(
post_return.is_none(),
"post_return should not be present for future read"
);
let future_read_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureRead));
uwriteln!(
self.src.js,
r#"const trampoline{i} = {future_read_fn}.bind(
null,
{component_instance_id},
{memory_idx},
{realloc_idx},
{string_encoding},
{async_},
{future_idx},
);
"#,
);
}
Trampoline::FutureWrite { ty, options, .. } => {
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
let future_idx = ty.as_u32();
let CanonicalOptions {
instance,
string_encoding,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
..
} = options
else {
unreachable!("unexpected memory data model during future.write");
};
let component_instance_id = instance.as_u32();
let memory_idx = memory
.expect("missing memory idx for future.write")
.as_u32();
let realloc_idx = realloc
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into());
let string_encoding = string_encoding_js_literal(string_encoding);
let future_write_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureWrite));
uwriteln!(
self.src.js,
r#"const trampoline{i} = {future_write_fn}.bind(
null,
{component_instance_id},
{memory_idx},
{realloc_idx},
{string_encoding},
{async_},
{future_idx},
);
"#,
);
}
Trampoline::FutureCancelRead { ty, async_, .. } => {
let future_cancel_read_fn = self.bindgen.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::FutureCancelRead,
));
uwriteln!(
self.src.js,
"const trampoline{i} = {future_cancel_read_fn}.bind(null, {future_idx}, {async_});\n",
future_idx = ty.as_u32(),
);
}
Trampoline::FutureCancelWrite { ty, async_, .. } => {
let future_cancel_write_fn = self.bindgen.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::FutureCancelWrite,
));
uwriteln!(
self.src.js,
"const trampoline{i} = {future_cancel_write_fn}.bind(null, {future_idx}, {async_});\n",
future_idx = ty.as_u32(),
);
}
Trampoline::FutureDropReadable { ty, .. } => {
let future_drop_readable_fn = self.bindgen.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::FutureDropReadable,
));
uwriteln!(
self.src.js,
"const trampoline{i} = {future_drop_readable_fn}.bind(null, {future_idx});\n",
future_idx = ty.as_u32(),
);
}
Trampoline::FutureDropWritable { ty, .. } => {
let future_drop_writable_fn = self.bindgen.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::FutureDropWritable,
));
uwriteln!(
self.src.js,
"const trampoline{i} = {future_drop_writable_fn}.bind(null, {future_idx});\n",
future_idx = ty.as_u32(),
);
}
Trampoline::FutureTransfer => todo!("Trampoline::FutureTransfer"),
Trampoline::ErrorContextNew { ty, options, .. } => {
let CanonicalOptions {
instance,
string_encoding,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
..
} = self
.component
.options
.get(*options)
.expect("failed to find options")
else {
panic!("unexpected memory data model during error-context.new");
};
self.ensure_error_context_local_table(*instance, *ty);
let local_err_tbl_idx = ty.as_u32();
let component_idx = instance.as_u32();
let memory_idx = memory
.expect("missing realloc fn idx for error-context.debug-message")
.as_u32();
let decoder = match string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => self
.bindgen
.intrinsic(Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8)),
wasmtime_environ::component::StringEncoding::Utf16 => self
.bindgen
.intrinsic(Intrinsic::String(StringIntrinsic::Utf16Decoder)),
enc => panic!(
"unsupported string encoding [{enc:?}] for error-context.debug-message"
),
};
uwriteln!(
self.src.js,
"function trampoline{i}InputStr(ptr, len) {{
return {decoder}.decode(new DataView(memory{memory_idx}.buffer, ptr, len));
}}"
);
let err_ctx_new_fn = self
.bindgen
.intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextNew));
uwriteln!(
self.src.js,
"const trampoline{i} = {err_ctx_new_fn}.bind(
null,
{{
componentIdx: {component_idx},
localTableIdx: {local_err_tbl_idx},
readStrFn: trampoline{i}InputStr,
}}
);
"
);
}
Trampoline::ErrorContextDebugMessage {
instance, options, ..
} => {
let CanonicalOptions {
async_,
callback,
post_return,
string_encoding,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
..
} = self
.component
.options
.get(*options)
.expect("failed to find options")
else {
panic!("unexpected memory data model during error-context.debug-message");
};
let debug_message_fn = self
.bindgen
.intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextDebugMessage));
let realloc_fn_idx = realloc
.expect("missing realloc fn idx for error-context.debug-message")
.as_u32();
let memory_idx = memory
.expect("missing realloc fn idx for error-context.debug-message")
.as_u32();
match string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => {
let encode_fn = self
.bindgen
.intrinsic(Intrinsic::String(StringIntrinsic::Utf8Encode));
uwriteln!(
self.src.js,
"function trampoline{i}OutputStr(s, outputPtr) {{
const memory = memory{memory_idx};
const reallocFn = realloc{realloc_fn_idx};
let {{ ptr, len }} = {encode_fn}(s, reallocFn, memory);
new DataView(memory.buffer).setUint32(outputPtr, ptr, true)
new DataView(memory.buffer).setUint32(outputPtr + 4, len, true)
}}"
);
}
wasmtime_environ::component::StringEncoding::Utf16 => {
let encode_fn = self
.bindgen
.intrinsic(Intrinsic::String(StringIntrinsic::Utf16Encode));
uwriteln!(
self.src.js,
"function trampoline{i}OutputStr(s, outputPtr) {{
const memory = memory{memory_idx};
const reallocFn = realloc{realloc_fn_idx};
let ptr = {encode_fn}(s, reallocFn, memory);
let len = s.length;
new DataView(memory.buffer).setUint32(outputPtr, ptr, true)
new DataView(memory.buffer).setUint32(outputPtr + 4, len, true)
}}"
);
}
enc => panic!(
"unsupported string encoding [{enc:?}] for error-context.debug-message"
),
};
let options_obj = format!(
"{{callback:{callback}, postReturn: {post_return}, async: {async_}}}",
callback = callback
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into()),
post_return = post_return
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into()),
);
let component_idx = instance.as_u32();
uwriteln!(
self.src.js,
"const trampoline{i} = {debug_message_fn}.bind(
null,
{{
componentIdx: {component_idx},
options: {options_obj},
writeStrFn: trampoline{i}OutputStr,
}}
);"
);
}
Trampoline::ErrorContextDrop { instance, ty } => {
let drop_fn = self
.bindgen
.intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextDrop));
let local_err_tbl_idx = ty.as_u32();
let component_idx = instance.as_u32();
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {drop_fn}.bind(
null,
{{ componentIdx: {component_idx}, localTableIdx: {local_err_tbl_idx} }},
);
"#
);
}
Trampoline::ErrorContextTransfer => {
let transfer_fn = self
.bindgen
.intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextTransfer));
uwriteln!(self.src.js, "const trampoline{i} = {transfer_fn};");
}
Trampoline::PrepareCall { memory } => {
let prepare_call_fn = self
.bindgen
.intrinsic(Intrinsic::Host(HostIntrinsic::PrepareCall));
let (memory_idx_js, memory_fn_js) = memory
.map(|v| {
(
v.as_u32().to_string(),
format!("() => memory{}", v.as_u32()),
)
})
.unwrap_or_else(|| ("null".into(), "() => null".into()));
uwriteln!(
self.src.js,
"const trampoline{i} = {prepare_call_fn}.bind(null, {memory_idx_js}, {memory_fn_js});",
)
}
Trampoline::SyncStartCall { callback } => {
let sync_start_call_fn = self
.bindgen
.intrinsic(Intrinsic::Host(HostIntrinsic::SyncStartCall));
uwriteln!(
self.src.js,
"const trampoline{i} = {sync_start_call_fn}.bind(null, {});",
callback
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into()),
);
}
Trampoline::AsyncStartCall {
callback,
post_return,
} => {
let async_start_call_fn = self
.bindgen
.intrinsic(Intrinsic::Host(HostIntrinsic::AsyncStartCall));
let (callback_idx, callback_fn) = callback
.map(|v| (v.as_u32().to_string(), format!("callback_{}", v.as_u32())))
.unwrap_or_else(|| ("null".into(), "null".into()));
let (post_return_idx, post_return_fn) = post_return
.map(|v| (v.as_u32().to_string(), format!("postReturn{}", v.as_u32())))
.unwrap_or_else(|| ("null".into(), "null".into()));
uwriteln!(
self.src.js,
"const trampoline{i} = {async_start_call_fn}.bind(
null,
{{
postReturnIdx: {post_return_idx},
getPostReturnFn: () => {post_return_fn},
callbackIdx: {callback_idx},
getCallbackFn: () => {callback_fn},
}},
);",
);
}
Trampoline::LowerImport {
index: _,
lower_ty,
options,
} => {
let canon_opts = self
.component
.options
.get(*options)
.expect("failed to find options");
let component_idx = canon_opts.instance.as_u32();
let is_async = canon_opts.async_;
let cancellable = canon_opts.cancellable;
let func_ty = self.types.index(*lower_ty);
let param_types = &self.types.index(func_ty.params).types;
let param_lift_fns_js =
gen_flat_lift_fn_list_js_expr(self, param_types.iter().as_slice(), canon_opts);
let result_types = &self.types.index(func_ty.results).types;
let result_lower_fns_js = gen_flat_lower_fn_list_js_expr(
self,
self.types,
result_types.iter().as_slice(),
&canon_opts.string_encoding,
);
let get_callback_fn_js = canon_opts
.callback
.map(|idx| format!("() => callback_{}", idx.as_u32()))
.unwrap_or_else(|| "() => null".into());
let get_post_return_fn_js = canon_opts
.post_return
.map(|idx| format!("() => postReturn{}", idx.as_u32()))
.unwrap_or_else(|| "() => null".into());
let (memory_exprs, realloc_expr_js) =
if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
memory,
realloc,
}) = canon_opts.data_model
{
(
memory.map(|idx| {
(
idx.as_u32().to_string(),
format!("() => memory{}", idx.as_u32()),
)
}),
realloc.map(|idx| format!("() => realloc{}", idx.as_u32())),
)
} else {
(None, None)
};
let (memory_idx_js, memory_expr_js) =
memory_exprs.unwrap_or_else(|| ("null".into(), "() => null".into()));
let realloc_expr_js = realloc_expr_js.unwrap_or_else(|| "() => null".into());
let func_ty_async = func_ty.async_;
let call = format!(
r#"{lower_import_intrinsic}.bind(
null,
{{
trampolineIdx: {i},
componentIdx: {component_idx},
isAsync: {is_async},
isManualAsync: _trampoline{i}.manuallyAsync,
paramLiftFns: {param_lift_fns_js},
resultLowerFns: {result_lower_fns_js},
funcTypeIsAsync: {func_ty_async},
getCallbackFn: {get_callback_fn_js},
getPostReturnFn: {get_post_return_fn_js},
isCancellable: {cancellable},
memoryIdx: {memory_idx_js},
getMemoryFn: {memory_expr_js},
getReallocFn: {realloc_expr_js},
importFn: _trampoline{i},
}},
)"#,
lower_import_intrinsic = if is_async || func_ty_async {
self.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::LowerImport))
} else {
self.bindgen.intrinsic(Intrinsic::AsyncTask(
AsyncTaskIntrinsic::LowerImportBackwardsCompat,
))
}
);
if is_async || func_ty_async {
uwriteln!(
self.src.js,
"let trampoline{i} = new WebAssembly.Suspending({call});"
);
} else {
uwriteln!(
self.src.js,
"let trampoline{i} = _trampoline{i}.manuallyAsync ? new WebAssembly.Suspending({call}) : {call};"
);
}
}
Trampoline::Transcoder {
op,
from,
from64,
to,
to64,
} => {
if *from64 || *to64 {
unimplemented!("memory 64 transcoder");
}
let from = from.as_u32();
let to = to.as_u32();
match op {
Transcode::Copy(FixedEncoding::Utf8) => {
uwriteln!(
self.src.js,
r#"
function trampoline{i} (from_ptr, len, to_ptr) {{
new Uint8Array(memory{to}.buffer, to_ptr, len).set(new Uint8Array(memory{from}.buffer, from_ptr, len));
}}
"#
);
}
Transcode::Copy(FixedEncoding::Utf16) => unimplemented!("utf16 copier"),
Transcode::Copy(FixedEncoding::Latin1) => unimplemented!("latin1 copier"),
Transcode::Latin1ToUtf16 => unimplemented!("latin to utf16 transcoder"),
Transcode::Latin1ToUtf8 => unimplemented!("latin to utf8 transcoder"),
Transcode::Utf16ToCompactProbablyUtf16 => {
unimplemented!("utf16 to compact wtf16 transcoder")
}
Transcode::Utf16ToCompactUtf16 => {
unimplemented!("utf16 to compact utf16 transcoder")
}
Transcode::Utf16ToLatin1 => unimplemented!("utf16 to latin1 transcoder"),
Transcode::Utf16ToUtf8 => {
uwriteln!(
self.src.js,
r#"
function trampoline{i} (src, src_len, dst, dst_len) {{
const encoder = new TextEncoder();
const {{ read, written }} = encoder.encodeInto(String.fromCharCode.apply(null, new Uint16Array(memory{from}.buffer, src, src_len)), new Uint8Array(memory{to}.buffer, dst, dst_len));
return [read, written];
}}
"#,
);
}
Transcode::Utf8ToCompactUtf16 => {
unimplemented!("utf8 to compact utf16 transcoder")
}
Transcode::Utf8ToLatin1 => unimplemented!("utf8 to latin1 transcoder"),
Transcode::Utf8ToUtf16 => {
uwriteln!(
self.src.js,
r#"
function trampoline{i} (from_ptr, len, to_ptr) {{
const decoder = new TextDecoder();
const content = decoder.decode(new Uint8Array(memory{from}.buffer, from_ptr, len));
const strlen = content.length
const view = new Uint16Array(memory{to}.buffer, to_ptr, strlen * 2)
for (var i = 0; i < strlen; i++) {{
view[i] = content.charCodeAt(i);
}}
return strlen;
}}
"#,
);
}
};
}
Trampoline::ResourceNew {
ty: resource_ty_idx,
..
} => {
self.ensure_resource_table(*resource_ty_idx);
let rid = resource_ty_idx.as_u32();
let rsc_table_create_own = self.bindgen.intrinsic(Intrinsic::Resource(
ResourceIntrinsic::ResourceTableCreateOwn,
));
uwriteln!(
self.src.js,
"const trampoline{i} = {rsc_table_create_own}.bind(null, handleTable{rid});"
);
}
Trampoline::ResourceRep {
ty: resource_ty_idx,
..
} => {
self.ensure_resource_table(*resource_ty_idx);
let rid = resource_ty_idx.as_u32();
let rsc_flag = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
uwriteln!(
self.src.js,
"function trampoline{i} (handle) {{
return handleTable{rid}[(handle << 1) + 1] & ~{rsc_flag};
}}"
);
}
Trampoline::ResourceDrop {
ty: resource_table_ty_idx,
..
} => {
self.ensure_resource_table(*resource_table_ty_idx);
let tid = resource_table_ty_idx.as_u32();
let resource_table_ty = &self.types[*resource_table_ty_idx];
let resource_ty = resource_table_ty.unwrap_concrete_ty();
let rid = resource_ty.as_u32();
let dtor = if let Some(resource_idx) =
self.component.defined_resource_index(resource_ty)
{
let resource_def = self
.component
.initializers
.iter()
.find_map(|i| match i {
GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
_ => None,
})
.unwrap();
if let Some(dtor) = &resource_def.dtor {
format!(
"
{}(handleEntry.rep);",
self.core_def(dtor)
)
} else {
"".into()
}
} else {
let symbol_dispose = self.bindgen.intrinsic(Intrinsic::SymbolDispose);
let symbol_cabi_dispose = self.bindgen.intrinsic(Intrinsic::SymbolCabiDispose);
if let Some(imported_resource_local_name) =
self.bindgen.local_names.try_get(resource_ty)
{
format!(
"
const rsc = captureTable{rid}.get(handleEntry.rep);
if (rsc) {{
if (rsc[{symbol_dispose}]) rsc[{symbol_dispose}]();
captureTable{rid}.delete(handleEntry.rep);
}} else if ({imported_resource_local_name}[{symbol_cabi_dispose}]) {{
{imported_resource_local_name}[{symbol_cabi_dispose}](handleEntry.rep);
}}"
)
} else {
format!(
"throw new TypeError('unreachable trampoline for resource [{:?}]')",
resource_ty
)
}
};
let rsc_table_remove = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
uwrite!(
self.src.js,
"function trampoline{i}(handle) {{
const handleEntry = {rsc_table_remove}(handleTable{tid}, handle);
if (handleEntry.own) {{
{dtor}
}}
}}
",
);
}
Trampoline::ResourceTransferOwn => {
let resource_transfer = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTransferOwn));
uwriteln!(self.src.js, "const trampoline{i} = {resource_transfer};");
}
Trampoline::ResourceTransferBorrow => {
let resource_transfer =
self.bindgen
.intrinsic(if self.bindgen.opts.valid_lifting_optimization {
Intrinsic::Resource(
ResourceIntrinsic::ResourceTransferBorrowValidLifting,
)
} else {
Intrinsic::Resource(ResourceIntrinsic::ResourceTransferBorrow)
});
uwriteln!(self.src.js, "const trampoline{i} = {resource_transfer};");
}
Trampoline::ResourceEnterCall => {
let scope_id = self.bindgen.intrinsic(Intrinsic::ScopeId);
uwrite!(self.src.js, "function trampoline{i}() {{ {scope_id}++; }}");
}
Trampoline::ResourceExitCall => {
let scope_id = self.bindgen.intrinsic(Intrinsic::ScopeId);
let resource_borrows = self
.bindgen
.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceCallBorrows));
let handle_tables = self.bindgen.intrinsic(Intrinsic::HandleTables);
uwrite!(
self.src.js,
r#"function trampoline{i}() {{
{scope_id}--;
for (const {{ rid, handle }} of {resource_borrows}) {{
const storedScopeId = {handle_tables}[rid][handle << 1]
if (storedScopeId === {scope_id}) {{
throw new TypeError('borrows not dropped for resource call');
}}
}}
{resource_borrows} = [];
}}
"#,
);
}
Trampoline::ContextSet { instance, slot, .. } => {
let context_set_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextSet));
let component_idx = instance.as_u32();
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {context_set_fn}.bind(null, {{
componentIdx: {component_idx},
slot: {slot},
}});
"#
);
}
Trampoline::ContextGet { instance, slot } => {
let context_get_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextGet));
let component_idx = instance.as_u32();
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {context_get_fn}.bind(null, {{
componentIdx: {component_idx},
slot: {slot},
}});
"#
);
}
Trampoline::TaskReturn {
results, options, ..
} => {
let canon_opts = self
.component
.options
.get(*options)
.expect("failed to find options");
let CanonicalOptions {
instance,
async_,
data_model:
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
callback,
post_return,
..
} = canon_opts
else {
unreachable!("unexpected memory data model during task.return");
};
if realloc.is_some() && memory.is_none() {
panic!("memory must be present if realloc is");
}
if *async_ && post_return.is_some() {
panic!("async and post return must not be specified together");
}
if *async_ && callback.is_none() {
panic!("callback must be specified for async");
}
if let Some(cb_idx) = callback {
let cb_fn = &self.types[TypeFuncIndex::from_u32(cb_idx.as_u32())];
match self.types[cb_fn.params].types[..] {
[InterfaceType::S32, InterfaceType::S32, InterfaceType::S32] => {}
_ => panic!("unexpected params for async callback fn"),
}
match self.types[cb_fn.results].types[..] {
[InterfaceType::S32] => {}
_ => panic!("unexpected results for async callback fn"),
}
}
let result_types = &self.types[*results].types;
let result_flat_param_total: usize = result_types
.iter()
.map(|t| {
self.types
.canonical_abi(t)
.flat_count
.map(usize::from)
.unwrap_or(0)
})
.sum();
let use_direct_params = result_flat_param_total < MAX_ASYNC_FLAT_PARAMS;
let mut lift_fns: Vec<String> = Vec::with_capacity(result_types.len());
for result_ty in result_types {
lift_fns.push(gen_flat_lift_fn_js_expr(
self,
result_ty,
&canon_opts.string_encoding,
));
}
let lift_fns_js = format!("[{}]", lift_fns.join(","));
let mut lower_fns: Vec<String> = Vec::with_capacity(result_types.len());
for result_ty in result_types {
lower_fns.push(gen_flat_lower_fn_js_expr(
self.bindgen,
self.types,
result_ty,
&canon_opts.string_encoding,
));
}
let lower_fns_js = format!("[{}]", lower_fns.join(","));
let get_memory_fn_js = memory
.map(|idx| format!("() => memory{}", idx.as_u32()))
.unwrap_or_else(|| "() => null".into());
let memory_idx_js = memory
.map(|idx| idx.as_u32().to_string())
.unwrap_or_else(|| "null".into());
let component_idx = instance.as_u32();
let task_return_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::TaskReturn));
let callback_fn_idx = callback
.map(|v| v.as_u32().to_string())
.unwrap_or_else(|| "null".into());
uwriteln!(
self.src.js,
"const trampoline{i} = {task_return_fn}.bind(
null,
{{
componentIdx: {component_idx},
useDirectParams: {use_direct_params},
getMemoryFn: {get_memory_fn_js},
memoryIdx: {memory_idx_js},
callbackFnIdx: {callback_fn_idx},
liftFns: {lift_fns_js},
lowerFns: {lower_fns_js},
}},
);",
);
}
Trampoline::BackpressureInc { instance } => {
let backpressure_inc_fn = self
.bindgen
.intrinsic(Intrinsic::Component(ComponentIntrinsic::BackpressureInc));
uwriteln!(
self.src.js,
"const trampoline{i} = {backpressure_inc_fn}.bind(null, {instance});\n",
instance = instance.as_u32(),
);
}
Trampoline::BackpressureDec { instance } => {
let backpressure_dec_fn = self
.bindgen
.intrinsic(Intrinsic::Component(ComponentIntrinsic::BackpressureDec));
uwriteln!(
self.src.js,
"const trampoline{i} = {backpressure_dec_fn}.bind(null, {instance});\n",
instance = instance.as_u32(),
);
}
Trampoline::ThreadYield {
cancellable,
instance,
} => {
let yield_fn = self
.bindgen
.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::Yield));
let component_instance_idx = instance.as_u32();
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {yield_fn}.bind(null, {{
isCancellable: {cancellable},
componentIdx: {component_instance_idx},
}});
"#,
);
}
Trampoline::ThreadIndex => todo!("Trampoline::ThreadIndex"),
Trampoline::ThreadNewIndirect { .. } => todo!("Trampoline::ThreadNewIndirect"),
Trampoline::ThreadSwitchTo { .. } => todo!("Trampoline::ThreadSwitchTo"),
Trampoline::ThreadSuspend { .. } => todo!("Trampoline::ThreadSuspend"),
Trampoline::ThreadResumeLater { .. } => todo!("Trampoline::ThreadResumeLater"),
Trampoline::ThreadYieldTo { .. } => todo!("Trampoline::ThreadYieldTo"),
Trampoline::Trap => {
uwriteln!(
self.src.js,
"function trampoline{i}(rep) {{ throw new TypeError('Trap'); }}"
);
}
Trampoline::EnterSyncCall => {
let enter_symmetric_sync_guest_call_fn = self.bindgen.intrinsic(
Intrinsic::AsyncTask(AsyncTaskIntrinsic::EnterSymmetricSyncGuestCall),
);
uwriteln!(
self.src.js,
r#"
const trampoline{i} = {enter_symmetric_sync_guest_call_fn};
"#,
);
}
Trampoline::ExitSyncCall => {
let exit_symmetric_sync_guest_call_fn = self.bindgen.intrinsic(
Intrinsic::AsyncTask(AsyncTaskIntrinsic::ExitSymmetricSyncGuestCall),
);
uwriteln!(
self.src.js,
"const trampoline{i} = {exit_symmetric_sync_guest_call_fn};\n",
);
}
}
}
fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) {
match init {
GlobalInitializer::ExtractCallback(ExtractCallback { index, def }) => {
let callback_idx = index.as_u32();
let core_def = self.core_def(def);
uwriteln!(self.src.js, "let callback_{callback_idx};",);
uwriteln!(
self.src.js_init,
r#"
callback_{callback_idx} = WebAssembly.promising({core_def});
callback_{callback_idx}.fnName = "{core_def}";
"#
);
}
GlobalInitializer::InstantiateModule(m, _) => match m {
InstantiateModule::Static(idx, args) => {
self.instantiate_static_module(*idx, args);
}
InstantiateModule::Import(..) => unimplemented!(),
},
GlobalInitializer::LowerImport { index, import } => {
self.lower_import(*index, *import);
}
GlobalInitializer::ExtractMemory(m) => {
let def = self.core_export_var_name(&m.export);
let idx = m.index.as_u32();
uwriteln!(self.src.js, "let memory{idx};");
uwriteln!(self.src.js_init, "memory{idx} = {def};");
}
GlobalInitializer::ExtractRealloc(r) => {
let def = self.core_def(&r.def);
let idx = r.index.as_u32();
uwriteln!(self.src.js, "let realloc{idx};");
uwriteln!(self.src.js, "let realloc{idx}Async;");
uwriteln!(self.src.js_init, "realloc{idx} = {def};",);
uwriteln!(
self.src.js_init,
r#"
try {{
realloc{idx}Async = WebAssembly.promising({def});
}} catch(err) {{
realloc{idx}Async = {def};
}}
"#
);
}
GlobalInitializer::ExtractPostReturn(p) => {
let def = self.core_def(&p.def);
let idx = p.index.as_u32();
uwriteln!(self.src.js, "let postReturn{idx};");
uwriteln!(self.src.js, "let postReturn{idx}Async;");
uwriteln!(self.src.js_init, "postReturn{idx} = {def};");
uwriteln!(
self.src.js_init,
r#"
try {{
postReturn{idx}Async = WebAssembly.promising({def});
}} catch(err) {{
postReturn{idx}Async = {def};
}}
"#
);
}
GlobalInitializer::Resource(_) => {}
GlobalInitializer::ExtractTable(_) => {}
}
}
fn instantiate_static_module(&mut self, module_idx: StaticModuleIndex, args: &[CoreDef]) {
let mut import_obj = BTreeMap::new();
for (module, name, arg) in self.modules[module_idx].imports(args) {
let def = self.augmented_import_def(&arg);
let dst = import_obj.entry(module).or_insert(BTreeMap::new());
let prev = dst.insert(name, def);
assert!(
prev.is_none(),
"unsupported duplicate import of `{module}::{name}`"
);
assert!(prev.is_none());
}
let mut imports = String::new();
if !import_obj.is_empty() {
imports.push_str(", {\n");
for (module, names) in import_obj {
imports.push_str(&maybe_quote_id(module));
imports.push_str(": {\n");
for (name, val) in names {
imports.push_str(&maybe_quote_id(name));
uwriteln!(imports, ": {val},");
}
imports.push_str("},\n");
}
imports.push('}');
}
let i = self.instances.push(module_idx);
let iu32 = i.as_u32();
let instantiate = self.bindgen.intrinsic(Intrinsic::InstantiateCore);
uwriteln!(self.src.js, "let exports{iu32};");
match self.bindgen.opts.instantiation {
Some(InstantiationMode::Async) | None => {
uwriteln!(
self.src.js_init,
"({{ exports: exports{iu32} }} = yield {instantiate}(yield module{}{imports}));",
module_idx.as_u32(),
)
}
Some(InstantiationMode::Sync) => {
uwriteln!(
self.src.js_init,
"({{ exports: exports{iu32} }} = {instantiate}(module{}{imports}));",
module_idx.as_u32(),
);
}
}
}
fn create_resource_fn_map(
&mut self,
func: &Function,
ty_func_idx: TypeFuncIndex,
resource_map: &mut ResourceMap,
) {
let params_ty = &self.types[self.types[ty_func_idx].params];
for (p, iface_ty) in func.params.iter().zip(params_ty.types.iter()) {
if let Type::Id(id) = p.ty {
self.connect_resource_types(id, iface_ty, resource_map);
}
}
let results_ty = &self.types[self.types[ty_func_idx].results];
if let (Some(Type::Id(id)), Some(iface_ty)) = (func.result, results_ty.types.first()) {
self.connect_resource_types(id, iface_ty, resource_map);
}
}
fn resource_name(
resolve: &Resolve,
local_names: &'a mut LocalNames,
resource: TypeId,
resource_map: &BTreeMap<TypeId, ResourceIndex>,
) -> &'a str {
let resource = crate::dealias(resolve, resource);
local_names
.get_or_create(
resource_map[&resource],
&resolve.types[resource]
.name
.as_ref()
.unwrap()
.to_upper_camel_case(),
)
.0
}
fn lower_import(&mut self, index: LoweredIndex, import: RuntimeImportIndex) {
let (options, trampoline, func_ty) = self.lowering_options[index];
let (import_index, path) = &self.component.imports[import];
let (import_name, _) = &self.component.import_types[*import_index];
let world_key = &self.imports[import_name];
let (func, func_name, iface_name) =
match &self.resolve.worlds[self.world].imports[world_key] {
WorldItem::Function(func) => {
assert_eq!(path.len(), 0);
(func, import_name, None)
}
WorldItem::Interface { id, .. } => {
assert_eq!(path.len(), 1);
let iface = &self.resolve.interfaces[*id];
let func = &iface.functions[&path[0]];
(
func,
&path[0],
Some(iface.name.as_deref().unwrap_or_else(|| import_name)),
)
}
WorldItem::Type { .. } => unreachable!("unexpected imported world item type"),
};
let is_async = is_async_fn(func, options);
if options.async_ {
assert!(
options.post_return.is_none(),
"async function {func_name} (import {import_name}) can't have post return",
);
}
let requires_async_porcelain = requires_async_porcelain(
FunctionIdentifier::Fn(func),
import_name,
&self.async_imports,
);
let (import_specifier, maybe_iface_member) = map_import(
&self.bindgen.opts.map,
if iface_name.is_some() {
import_name
} else {
match func.kind {
FunctionKind::Method(_) => {
let stripped = import_name.strip_prefix("[method]").unwrap();
&stripped[0..stripped.find(".").unwrap()]
}
FunctionKind::AsyncMethod(_) => {
let stripped = import_name.strip_prefix("[async method]").unwrap();
&stripped[0..stripped.find(".").unwrap()]
}
FunctionKind::Static(_) => {
let stripped = import_name.strip_prefix("[static]").unwrap();
&stripped[0..stripped.find(".").unwrap()]
}
FunctionKind::AsyncStatic(_) => {
let stripped = import_name.strip_prefix("[async static]").unwrap();
&stripped[0..stripped.find(".").unwrap()]
}
FunctionKind::Constructor(_) => {
import_name.strip_prefix("[constructor]").unwrap()
}
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => import_name,
}
},
);
let mut import_resource_map = ResourceMap::new();
self.create_resource_fn_map(func, func_ty, &mut import_resource_map);
let (callee_name, call_type) = match func.kind {
FunctionKind::Freestanding => (
self.bindgen
.local_names
.get_or_create(
format!(
"import:{import}-{maybe_iface_member}-{func_name}",
import = import_specifier,
maybe_iface_member = maybe_iface_member.as_deref().unwrap_or(""),
func_name = &func.name
),
&func.name,
)
.0
.to_string(),
CallType::Standard,
),
FunctionKind::AsyncFreestanding => (
self.bindgen
.local_names
.get_or_create(
format!(
"import:async-{import}-{maybe_iface_member}-{func_name}",
import = import_specifier,
maybe_iface_member = maybe_iface_member.as_deref().unwrap_or(""),
func_name = &func.name
),
&func.name,
)
.0
.to_string(),
CallType::AsyncStandard,
),
FunctionKind::Method(_) => (
func.item_name().to_lower_camel_case(),
CallType::CalleeResourceDispatch,
),
FunctionKind::AsyncMethod(_) => (
func.item_name().to_lower_camel_case(),
CallType::AsyncCalleeResourceDispatch,
),
FunctionKind::Static(resource_id) => (
format!(
"{}.{}",
Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.imports_resource_types
),
func.item_name().to_lower_camel_case()
),
CallType::Standard,
),
FunctionKind::AsyncStatic(resource_id) => (
format!(
"{}.{}",
Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.imports_resource_types
),
func.item_name().to_lower_camel_case()
),
CallType::AsyncStandard,
),
FunctionKind::Constructor(resource_id) => (
format!(
"new {}",
Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.imports_resource_types
)
),
CallType::Standard,
),
};
let nparams = self
.resolve
.wasm_signature(AbiVariant::GuestImport, func)
.params
.len();
let trampoline_idx = trampoline.as_u32();
match self.bindgen.opts.import_bindings {
None | Some(BindingsMode::Js) | Some(BindingsMode::Hybrid) => {
if is_async | requires_async_porcelain {
uwrite!(
self.src.js,
"\nconst _trampoline{trampoline_idx} = async function"
);
} else {
uwrite!(
self.src.js,
"\nconst _trampoline{trampoline_idx} = function"
);
}
let iface_name = if import_name.is_empty() {
None
} else {
Some(import_name.to_string())
};
self.bindgen(JsFunctionBindgenArgs {
nparams,
call_type,
iface_name: iface_name.as_deref(),
callee: &callee_name,
opts: options,
func,
resource_map: &import_resource_map,
abi: AbiVariant::GuestImport,
requires_async_porcelain,
is_async,
});
uwriteln!(self.src.js, "");
uwriteln!(
self.src.js,
"_trampoline{trampoline_idx}.fnName = '{}#{callee_name}';",
iface_name.unwrap_or_default(),
);
if requires_async_porcelain {
uwriteln!(
self.src.js,
"_trampoline{trampoline_idx}.manuallyAsync = true;"
);
}
}
Some(BindingsMode::Optimized) | Some(BindingsMode::DirectOptimized) => {
uwriteln!(self.src.js, "let trampoline{trampoline_idx};");
}
};
if !matches!(
self.bindgen.opts.import_bindings,
None | Some(BindingsMode::Js)
) {
let (memory, realloc) =
if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
memory,
realloc,
}) = options.data_model
{
(
memory.map(|idx| format!(" memory: memory{},", idx.as_u32())),
realloc.map(|idx| format!(" realloc: realloc{},", idx.as_u32())),
)
} else {
(None, None)
};
let memory = memory.unwrap_or_default();
let realloc = realloc.unwrap_or_default();
let post_return = options
.post_return
.map(|idx| format!(" postReturn: postReturn{},", idx.as_u32()))
.unwrap_or("".into());
let string_encoding = match options.string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => "",
wasmtime_environ::component::StringEncoding::Utf16 => " stringEncoding: 'utf16',",
wasmtime_environ::component::StringEncoding::CompactUtf16 => {
" stringEncoding: 'compact-utf16',"
}
};
let callee_name = match func.kind {
FunctionKind::Constructor(_) => callee_name[4..].to_string(),
FunctionKind::Static(_)
| FunctionKind::AsyncStatic(_)
| FunctionKind::Freestanding
| FunctionKind::AsyncFreestanding => callee_name.to_string(),
FunctionKind::Method(resource_id) | FunctionKind::AsyncMethod(resource_id) => {
format!(
"{}.prototype.{callee_name}",
Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.imports_resource_types
)
)
}
};
self.resource_imports.extend(import_resource_map.clone());
let resource_tables = {
let mut resource_tables: Vec<TypeResourceTableIndex> = Vec::new();
for (_, data) in import_resource_map {
let ResourceTable {
data: ResourceData::Host { tid, .. },
..
} = &data
else {
unreachable!("unexpected non-host resource table");
};
resource_tables.push(*tid);
}
if resource_tables.is_empty() {
"".to_string()
} else {
format!(
" resourceTables: [{}],",
resource_tables
.iter()
.map(|x| format!("handleTable{}", x.as_u32()))
.collect::<Vec<String>>()
.join(", ")
)
}
};
match self.bindgen.opts.import_bindings {
Some(BindingsMode::Hybrid) => {
let symbol_cabi_lower = self.bindgen.intrinsic(Intrinsic::SymbolCabiLower);
uwriteln!(self.src.js_init, "if ({callee_name}[{symbol_cabi_lower}]) {{
trampoline{} = {callee_name}[{symbol_cabi_lower}]({{{memory}{realloc}{post_return}{string_encoding}{resource_tables}}});
}}", trampoline.as_u32());
}
Some(BindingsMode::Optimized) => {
let symbol_cabi_lower = self.bindgen.intrinsic(Intrinsic::SymbolCabiLower);
if !self.bindgen.opts.valid_lifting_optimization {
uwriteln!(self.src.js_init, "if (!{callee_name}[{symbol_cabi_lower}]) {{
throw new TypeError('import for \"{import_name}\" does not define a Symbol.for(\"cabiLower\") optimized binding');
}}");
}
uwriteln!(
self.src.js_init,
"trampoline{} = {callee_name}[{symbol_cabi_lower}]({{{memory}{realloc}{post_return}{string_encoding}{resource_tables}}});",
trampoline.as_u32()
);
}
Some(BindingsMode::DirectOptimized) => {
uwriteln!(
self.src.js_init,
"trampoline{} = {callee_name}({{{memory}{realloc}{post_return}{string_encoding}}});",
trampoline.as_u32()
);
}
None | Some(BindingsMode::Js) => unreachable!("invalid bindings mode"),
};
}
let (import_name, binding_name) = match func.kind {
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
(func_name.to_lower_camel_case(), callee_name)
}
FunctionKind::Method(tid)
| FunctionKind::AsyncMethod(tid)
| FunctionKind::Static(tid)
| FunctionKind::AsyncStatic(tid)
| FunctionKind::Constructor(tid) => {
let ty = &self.resolve.types[tid];
let class_name = ty.name.as_ref().unwrap().to_upper_camel_case();
let resource_name = Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
tid,
&self.imports_resource_types,
)
.to_string();
(class_name, resource_name)
}
};
self.ensure_import(
import_specifier,
iface_name,
maybe_iface_member.as_deref(),
if iface_name.is_some() {
Some(import_name.to_string())
} else {
None
},
binding_name,
);
}
fn ensure_import(
&mut self,
import_specifier: String,
iface_name: Option<&str>,
iface_member: Option<&str>,
import_binding: Option<String>,
local_name: String,
) {
if import_specifier.starts_with("webidl:") {
self.bindgen
.intrinsic(Intrinsic::WebIdl(WebIdlIntrinsic::GlobalThisIdlProxy));
}
let mut import_path = Vec::with_capacity(2);
import_path.push(import_specifier);
if let Some(_iface_name) = iface_name {
if let Some(iface_member) = iface_member {
import_path.push(iface_member.to_lower_camel_case());
}
import_path.push(import_binding.clone().unwrap());
} else if let Some(iface_member) = iface_member {
import_path.push(iface_member.into());
} else if let Some(import_binding) = &import_binding {
import_path.push(import_binding.into());
}
self.bindgen
.esm_bindgen
.add_import_binding(&import_path, local_name);
}
fn connect_p3_resources(
&mut self,
id: &TypeId,
maybe_elem_ty: &Option<Type>,
iface_ty: &InterfaceType,
resource_map: &mut ResourceMap,
) {
let remote_resource = match iface_ty {
InterfaceType::Future(table_idx) => ResourceTable {
imported: true,
data: ResourceData::Guest {
resource_name: "Future".into(),
prefix: Some(format!("${}", table_idx.as_u32())),
extra: Some(ResourceExtraData::Future {
table_idx: *table_idx,
elem_ty: *maybe_elem_ty,
}),
},
},
InterfaceType::Stream(table_idx) => ResourceTable {
imported: true,
data: ResourceData::Guest {
resource_name: "Stream".into(),
prefix: Some(format!("${}", table_idx.as_u32())),
extra: Some(ResourceExtraData::Stream {
table_idx: *table_idx,
elem_ty: *maybe_elem_ty,
}),
},
},
InterfaceType::ErrorContext(table_idx) => ResourceTable {
imported: true,
data: ResourceData::Guest {
resource_name: "ErrorContext".into(),
prefix: Some(format!("${}", table_idx.as_u32())),
extra: Some(ResourceExtraData::ErrorContext {
table_idx: *table_idx,
}),
},
},
_ => unreachable!("unexpected interface type [{iface_ty:?}] with no type"),
};
resource_map.insert(*id, remote_resource);
}
fn connect_host_resource(
&mut self,
t: TypeId,
resource_table_ty_idx: TypeResourceTableIndex,
resource_map: &mut ResourceMap,
) {
self.ensure_resource_table(resource_table_ty_idx);
let resource_table_ty = &self.types[resource_table_ty_idx];
let resource_idx = resource_table_ty.unwrap_concrete_ty();
let imported = self
.component
.defined_resource_index(resource_idx)
.is_none();
let resource_id = crate::dealias(self.resolve, t);
let ty = &self.resolve.types[resource_id];
let mut dtor_str = None;
if let Some(resource_idx) = self.component.defined_resource_index(resource_idx) {
assert!(!imported);
let resource_def = self
.component
.initializers
.iter()
.find_map(|i| match i {
GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
_ => None,
})
.unwrap();
if let Some(dtor) = &resource_def.dtor {
dtor_str = Some(self.core_def(dtor));
}
}
let resource_name = ty.name.as_ref().unwrap().to_upper_camel_case();
let local_name = if imported {
let (world_key, iface_name) = match ty.owner {
wit_parser::TypeOwner::World(world) => (
self.resolve.worlds[world]
.imports
.iter()
.find(|&(_, item)| matches!(*item, WorldItem::Type { id, .. } if id == t))
.unwrap()
.0
.clone(),
None,
),
wit_parser::TypeOwner::Interface(iface) => {
match &self.resolve.interfaces[iface].name {
Some(name) => (WorldKey::Interface(iface), Some(name.as_str())),
None => {
let key = self.resolve.worlds[self.world]
.imports
.iter()
.find(|&(_, item)| match item {
WorldItem::Interface { id, .. } => *id == iface,
_ => false,
})
.unwrap()
.0;
(
key.clone(),
match key {
WorldKey::Name(name) => Some(name.as_str()),
WorldKey::Interface(_) => None,
},
)
}
}
}
wit_parser::TypeOwner::None => unimplemented!(),
};
let import_name = self.resolve.name_world_key(&world_key);
let (local_name, _) = self
.bindgen
.local_names
.get_or_create(resource_idx, &resource_name);
let local_name_str = local_name.to_string();
let (import_specifier, maybe_iface_member) =
map_import(&self.bindgen.opts.map, &import_name);
self.ensure_import(
import_specifier,
iface_name,
maybe_iface_member.as_deref(),
iface_name.map(|_| resource_name),
local_name_str.to_string(),
);
local_name_str
} else {
let (local_name, _) = self
.bindgen
.local_names
.get_or_create(resource_idx, &resource_name);
local_name.to_string()
};
let entry = ResourceTable {
imported,
data: ResourceData::Host {
tid: resource_table_ty_idx,
rid: resource_idx,
local_name,
dtor_name: dtor_str,
},
};
if let Some(existing) = resource_map.get(&resource_id) {
assert_eq!(*existing, entry);
return;
}
resource_map.insert(resource_id, entry);
}
fn connect_resource_types(
&mut self,
id: TypeId,
iface_ty: &InterfaceType,
resource_map: &mut ResourceMap,
) {
let kind = &self.resolve.types[id].kind;
match (kind, iface_ty) {
(TypeDefKind::Flags(_), InterfaceType::Flags(_))
| (TypeDefKind::Enum(_), InterfaceType::Enum(_)) => {}
(TypeDefKind::Record(t1), InterfaceType::Record(t2)) => {
let t2 = &self.types[*t2];
for (f1, f2) in t1.fields.iter().zip(t2.fields.iter()) {
if let Type::Id(id) = f1.ty {
self.connect_resource_types(id, &f2.ty, resource_map);
}
}
}
(
TypeDefKind::Handle(Handle::Own(t1) | Handle::Borrow(t1)),
InterfaceType::Own(t2) | InterfaceType::Borrow(t2),
) => {
self.connect_host_resource(*t1, *t2, resource_map);
}
(TypeDefKind::Tuple(t1), InterfaceType::Tuple(t2)) => {
let t2 = &self.types[*t2];
for (f1, f2) in t1.types.iter().zip(t2.types.iter()) {
if let Type::Id(id) = f1 {
self.connect_resource_types(*id, f2, resource_map);
}
}
}
(TypeDefKind::Variant(t1), InterfaceType::Variant(t2)) => {
let t2 = &self.types[*t2];
for (f1, f2) in t1.cases.iter().zip(t2.cases.iter()) {
if let Some(Type::Id(id)) = &f1.ty {
self.connect_resource_types(*id, f2.1.as_ref().unwrap(), resource_map);
}
}
}
(TypeDefKind::Option(t1), InterfaceType::Option(t2)) => {
let t2 = &self.types[*t2];
if let Type::Id(id) = t1 {
self.connect_resource_types(*id, &t2.ty, resource_map);
}
}
(TypeDefKind::Result(t1), InterfaceType::Result(t2)) => {
let t2 = &self.types[*t2];
if let Some(Type::Id(id)) = &t1.ok {
self.connect_resource_types(*id, &t2.ok.unwrap(), resource_map);
}
if let Some(Type::Id(id)) = &t1.err {
self.connect_resource_types(*id, &t2.err.unwrap(), resource_map);
}
}
(TypeDefKind::List(t1), InterfaceType::List(t2)) => {
let t2 = &self.types[*t2];
if let Type::Id(id) = t1 {
self.connect_resource_types(*id, &t2.element, resource_map);
}
}
(TypeDefKind::FixedLengthList(t1, _len), InterfaceType::FixedLengthList(t2)) => {
let t2 = &self.types[*t2];
if let Type::Id(id) = t1 {
self.connect_resource_types(*id, &t2.element, resource_map);
}
}
(TypeDefKind::Type(ty), _) => {
if let Type::Id(id) = ty {
self.connect_resource_types(*id, iface_ty, resource_map);
}
}
(TypeDefKind::Future(maybe_elem_ty), _iface_ty)
| (TypeDefKind::Stream(maybe_elem_ty), _iface_ty) => {
match maybe_elem_ty {
None => {
self.connect_p3_resources(&id, maybe_elem_ty, iface_ty, resource_map);
}
Some(elem_ty @ Type::Id(_t)) => {
self.connect_p3_resources(&id, &Some(*elem_ty), iface_ty, resource_map);
}
Some(_) => {
self.connect_p3_resources(&id, maybe_elem_ty, iface_ty, resource_map);
}
}
}
(
TypeDefKind::Result(Result_ { ok, err }),
tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
) => {
if let Some(Type::Id(ok_t)) = ok {
self.connect_resource_types(*ok_t, tk2, resource_map)
}
if let Some(Type::Id(err_t)) = err {
self.connect_resource_types(*err_t, tk2, resource_map)
}
}
(
TypeDefKind::Option(ty),
tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
) => {
if let Type::Id(some_t) = ty {
self.connect_resource_types(*some_t, tk2, resource_map)
}
}
(
TypeDefKind::Handle(Handle::Own(t1) | Handle::Borrow(t1)),
tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
) => self.connect_resource_types(*t1, tk2, resource_map),
(TypeDefKind::Resource, InterfaceType::Future(_) | InterfaceType::Stream(_)) => {}
(
TypeDefKind::Variant(variant),
tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
) => {
for f1 in variant.cases.iter() {
if let Some(Type::Id(id)) = &f1.ty {
self.connect_resource_types(*id, tk2, resource_map);
}
}
}
(
TypeDefKind::Record(record),
tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
) => {
for f1 in record.fields.iter() {
if let Type::Id(id) = f1.ty {
self.connect_resource_types(id, tk2, resource_map);
}
}
}
(
TypeDefKind::Enum(_) | TypeDefKind::Flags(_),
InterfaceType::Future(_) | InterfaceType::Stream(_),
) => {}
(TypeDefKind::Resource, tk2) => {
unreachable!(
"resource types do not need to be connected (in this case, to [{tk2:?}])"
)
}
(TypeDefKind::Unknown, tk2) => {
unreachable!("unknown types cannot be connected (in this case to [{tk2:?}])")
}
(tk1, tk2) => unreachable!("invalid typedef kind combination [{tk1:?}] [{tk2:?}]",),
}
}
fn bindgen(&mut self, args: JsFunctionBindgenArgs) {
let JsFunctionBindgenArgs {
nparams,
call_type,
iface_name,
callee,
opts,
func,
resource_map,
abi,
requires_async_porcelain,
is_async,
} = args;
let (memory, realloc) =
if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
memory,
realloc,
}) = opts.data_model
{
(
memory.map(|idx| format!("memory{}", idx.as_u32())),
realloc.map(|idx| {
format!(
"realloc{}{}",
idx.as_u32(),
if is_async {
"Async"
} else {
Default::default()
}
)
}),
)
} else {
(None, None)
};
let post_return = opts.post_return.map(|idx| {
format!(
"postReturn{}{}",
idx.as_u32(),
if is_async {
"Async"
} else {
Default::default()
}
)
});
let tracing_prefix = format!(
"[iface=\"{}\", function=\"{}\"]",
iface_name.unwrap_or("<no iface>"),
func.name
);
self.src.js("(");
let mut params = Vec::new();
let mut first = true;
for i in 0..nparams {
if i == 0
&& matches!(
call_type,
CallType::FirstArgIsThis | CallType::AsyncFirstArgIsThis
)
{
params.push("this".into());
continue;
}
if !first {
self.src.js(", ");
} else {
first = false;
}
let param = format!("arg{i}");
self.src.js(¶m);
params.push(param);
}
uwriteln!(self.src.js, ") {{");
if self.bindgen.opts.tracing {
let event_fields = func
.params
.iter()
.enumerate()
.map(|(i, p)| format!("{}=${{arguments[{i}]}}", p.name))
.collect::<Vec<String>>();
uwriteln!(
self.src.js,
"console.error(`{tracing_prefix} call {}`);",
event_fields.join(", ")
);
}
if self.bindgen.opts.tla_compat
&& matches!(abi, AbiVariant::GuestExport)
&& self.bindgen.opts.instantiation.is_none()
{
let throw_uninitialized = self.bindgen.intrinsic(Intrinsic::ThrowUninitialized);
uwrite!(
self.src.js,
"\
if (!_initialized) {throw_uninitialized}();
"
);
}
let mut f = FunctionBindgen {
resource_map,
clear_resource_borrows: false,
intrinsics: &mut self.bindgen.all_intrinsics,
valid_lifting_optimization: self.bindgen.opts.valid_lifting_optimization,
sizes: &self.sizes,
err: if get_thrown_type(self.resolve, func.result).is_some() {
match abi {
AbiVariant::GuestExport
| AbiVariant::GuestExportAsync
| AbiVariant::GuestExportAsyncStackful => ErrHandling::ThrowResultErr,
AbiVariant::GuestImport | AbiVariant::GuestImportAsync => {
ErrHandling::ResultCatchHandler
}
}
} else {
ErrHandling::None
},
block_storage: Vec::new(),
blocks: Vec::new(),
callee,
callee_resource_dynamic: matches!(call_type, CallType::CalleeResourceDispatch),
memory: memory.as_ref(),
realloc: realloc.as_ref(),
tmp: 0,
params,
post_return: post_return.as_ref(),
tracing_prefix: &tracing_prefix,
tracing_enabled: self.bindgen.opts.tracing,
encoding: match opts.string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => StringEncoding::UTF8,
wasmtime_environ::component::StringEncoding::Utf16 => StringEncoding::UTF16,
wasmtime_environ::component::StringEncoding::CompactUtf16 => {
StringEncoding::CompactUTF16
}
},
src: source::Source::default(),
resolve: self.resolve,
requires_async_porcelain,
is_async,
canon_opts: opts,
iface_name,
};
abi::call(
self.resolve,
abi,
match abi {
AbiVariant::GuestImport | AbiVariant::GuestImportAsync => {
LiftLower::LiftArgsLowerResults
}
AbiVariant::GuestExport
| AbiVariant::GuestExportAsync
| AbiVariant::GuestExportAsyncStackful => LiftLower::LowerArgsLiftResults,
},
func,
&mut f,
is_async,
);
self.src.js(&f.src);
self.src.js("}");
}
fn augmented_import_def(&self, def: &core::AugmentedImport<'_>) -> String {
match def {
core::AugmentedImport::CoreDef(def) => self.core_def(def),
core::AugmentedImport::Memory { mem, op } => {
let mem = self.core_def(mem);
match op {
core::AugmentedOp::I32Load => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getInt32(ptr + off, true)"
)
}
core::AugmentedOp::I32Load8U => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getUint8(ptr + off, true)"
)
}
core::AugmentedOp::I32Load8S => {
format!("(ptr, off) => new DataView({mem}.buffer).getInt8(ptr + off, true)")
}
core::AugmentedOp::I32Load16U => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getUint16(ptr + off, true)"
)
}
core::AugmentedOp::I32Load16S => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getInt16(ptr + off, true)"
)
}
core::AugmentedOp::I64Load => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getBigInt64(ptr + off, true)"
)
}
core::AugmentedOp::F32Load => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getFloat32(ptr + off, true)"
)
}
core::AugmentedOp::F64Load => {
format!(
"(ptr, off) => new DataView({mem}.buffer).getFloat64(ptr + off, true)"
)
}
core::AugmentedOp::I32Store8 => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setInt8(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::I32Store16 => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setInt16(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::I32Store => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setInt32(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::I64Store => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setBigInt64(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::F32Store => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setFloat32(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::F64Store => {
format!(
"(ptr, val, offset) => {{
new DataView({mem}.buffer).setFloat64(ptr + offset, val, true);
}}"
)
}
core::AugmentedOp::MemorySize => {
format!("ptr => {mem}.buffer.byteLength / 65536")
}
}
}
}
}
fn core_def(&self, def: &CoreDef) -> String {
match def {
CoreDef::Export(e) => self.core_export_var_name(e),
CoreDef::TaskMayBlock => AsyncTaskIntrinsic::CurrentTaskMayBlock.name().into(),
CoreDef::Trampoline(i) => format!("trampoline{}", i.as_u32()),
CoreDef::InstanceFlags(i) => {
self.used_instance_flags.borrow_mut().insert(*i);
format!("instanceFlags{}", i.as_u32())
}
CoreDef::UnsafeIntrinsic(ui) => {
let idx = ui.index();
format!("unsafeIntrinsic{idx}")
}
}
}
fn core_export_var_name<T>(&self, export: &CoreExport<T>) -> String
where
T: Into<EntityIndex> + Copy,
{
let name = match &export.item {
ExportItem::Index(idx) => {
let module_idx = self
.instances
.get(export.instance)
.expect("unexpectedly missing export instance");
let module = &self
.modules
.get(*module_idx)
.expect("unexpectedly missing module by idx");
let idx = (*idx).into();
module
.exports()
.iter()
.find_map(|(name, i)| if *i == idx { Some(name) } else { None })
.unwrap()
}
ExportItem::Name(s) => s,
};
let i = export.instance.as_u32() as usize;
let quoted = maybe_quote_member(name);
format!("exports{i}{quoted}")
}
fn exports(&mut self, exports: &NameMap<String, ExportIndex>) {
for (export_name, export_idx) in exports.raw_iter() {
let export = &self.component.export_items[*export_idx];
let world_key = &self.exports[export_name];
let item = &self.resolve.worlds[self.world].exports[world_key];
let mut export_resource_map = ResourceMap::new();
match export {
Export::LiftedFunction {
func: def,
options,
ty: func_ty,
} => {
let func = match item {
WorldItem::Function(f) => f,
WorldItem::Interface { .. } | WorldItem::Type { .. } => {
unreachable!("unexpectedly non-function lifted function export")
}
};
self.create_resource_fn_map(func, *func_ty, &mut export_resource_map);
let local_name = String::from(match func.kind {
FunctionKind::Constructor(resource_id)
| FunctionKind::Method(resource_id)
| FunctionKind::AsyncMethod(resource_id)
| FunctionKind::Static(resource_id)
| FunctionKind::AsyncStatic(resource_id) => Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.exports_resource_types,
),
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
self.bindgen.local_names.create_once(export_name)
}
});
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
self.export_bindgen(
&local_name,
def,
options,
func,
func_ty,
export_name,
&export_resource_map,
);
let js_binding_name = match func.kind {
FunctionKind::Constructor(ty)
| FunctionKind::Method(ty)
| FunctionKind::AsyncMethod(ty)
| FunctionKind::Static(ty)
| FunctionKind::AsyncStatic(ty) => self.resolve.types[ty]
.name
.as_ref()
.unwrap()
.to_upper_camel_case(),
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
export_name.to_lower_camel_case()
}
};
self.bindgen.esm_bindgen.add_export_binding(
None,
local_name,
js_binding_name,
func,
);
}
Export::Instance { exports, .. } => {
let iface_id = match item {
WorldItem::Interface { id, .. } => *id,
WorldItem::Function(_) | WorldItem::Type { .. } => {
unreachable!("unexpectedly non-interface export instance")
}
};
for (func_name, export_idx) in exports.raw_iter() {
let export = &self.component.export_items[*export_idx];
let (def, options, func_ty) = match export {
Export::LiftedFunction { func, options, ty } => (func, options, ty),
Export::Type(_) => continue, _ => unreachable!("unexpected non-lifted function export"),
};
let func = &self.resolve.interfaces[iface_id].functions[func_name];
self.create_resource_fn_map(func, *func_ty, &mut export_resource_map);
let local_name = String::from(match func.kind {
FunctionKind::Constructor(resource_id)
| FunctionKind::Method(resource_id)
| FunctionKind::AsyncMethod(resource_id)
| FunctionKind::Static(resource_id)
| FunctionKind::AsyncStatic(resource_id) => {
Instantiator::resource_name(
self.resolve,
&mut self.bindgen.local_names,
resource_id,
&self.exports_resource_types,
)
}
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
self.bindgen.local_names.create_once(func_name)
}
});
let options = self
.component
.options
.get(*options)
.expect("failed to find options");
self.export_bindgen(
&local_name,
def,
options,
func,
func_ty,
export_name,
&export_resource_map,
);
let export_binding_name = match func.kind {
FunctionKind::Constructor(ty)
| FunctionKind::Method(ty)
| FunctionKind::AsyncMethod(ty)
| FunctionKind::Static(ty)
| FunctionKind::AsyncStatic(ty) => self.resolve.types[ty]
.name
.as_ref()
.unwrap()
.to_upper_camel_case(),
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
func_name.to_lower_camel_case()
}
};
self.bindgen.esm_bindgen.add_export_binding(
Some(export_name),
local_name,
export_binding_name,
func,
);
}
}
Export::Type(_) => {}
Export::ModuleStatic { .. } | Export::ModuleImport { .. } => unimplemented!(),
}
self.resource_exports.extend(export_resource_map);
}
self.bindgen.esm_bindgen.populate_export_aliases();
}
#[allow(clippy::too_many_arguments)]
fn export_bindgen(
&mut self,
local_name: &str,
def: &CoreDef,
options: &CanonicalOptions,
func: &Function,
_func_ty_idx: &TypeFuncIndex,
export_name: &String,
export_resource_map: &ResourceMap,
) {
let requires_async_porcelain = requires_async_porcelain(
FunctionIdentifier::Fn(func),
export_name,
&self.async_exports,
);
if options.async_ {
assert!(
options.post_return.is_none(),
"async function {local_name} (export {export_name}) can't have post return"
);
}
let is_async = is_async_fn(func, options);
let maybe_async = if requires_async_porcelain || is_async {
"async "
} else {
""
};
let core_export_fn = self.core_def(def);
let callee = match self
.bindgen
.local_names
.get_or_create(&core_export_fn, &core_export_fn)
{
(local_name, true) => local_name.to_string(),
(local_name, false) => {
let local_name = local_name.to_string();
uwriteln!(self.src.js, "let {local_name};");
self.bindgen
.all_core_exported_funcs
.push((core_export_fn.clone(), is_async | requires_async_porcelain));
local_name
}
};
let iface_name = if export_name.is_empty() {
None
} else {
Some(export_name)
};
match func.kind {
FunctionKind::Freestanding => {
uwrite!(self.src.js, "\n{maybe_async}function {local_name}")
}
FunctionKind::Method(_) => {
self.ensure_local_resource_class(local_name.to_string());
let method_name = func.item_name().to_lower_camel_case();
uwrite!(
self.src.js,
"\n{local_name}.prototype.{method_name} = {maybe_async}function {}",
if !is_js_reserved_word(&method_name) {
method_name.to_string()
} else {
format!("${method_name}")
}
);
}
FunctionKind::Static(_) => {
self.ensure_local_resource_class(local_name.to_string());
let method_name = func.item_name().to_lower_camel_case();
uwrite!(
self.src.js,
"\n{local_name}.{method_name} = function {}",
if !is_js_reserved_word(&method_name) {
method_name.to_string()
} else {
format!("${method_name}")
}
);
}
FunctionKind::Constructor(_) => {
if self.defined_resource_classes.contains(local_name) {
panic!(
"Internal error: Resource constructor must be defined before other methods and statics"
);
}
uwrite!(
self.src.js,
"
class {local_name} {{
constructor"
);
self.defined_resource_classes.insert(local_name.to_string());
}
FunctionKind::AsyncFreestanding => {
uwrite!(self.src.js, "\nasync function {local_name}")
}
FunctionKind::AsyncMethod(_) => {
self.ensure_local_resource_class(local_name.to_string());
let method_name = func.item_name().to_lower_camel_case();
let fn_name = if !is_js_reserved_word(&method_name) {
method_name.to_string()
} else {
format!("${method_name}")
};
uwrite!(
self.src.js,
"\n{local_name}.prototype.{method_name} = async function {fn_name}",
);
}
FunctionKind::AsyncStatic(_) => {
self.ensure_local_resource_class(local_name.to_string());
let method_name = func.item_name().to_lower_camel_case();
let fn_name = if !is_js_reserved_word(&method_name) {
method_name.to_string()
} else {
format!("${method_name}")
};
uwrite!(
self.src.js,
"\n{local_name}.{method_name} = async function {fn_name}",
);
}
};
self.bindgen(JsFunctionBindgenArgs {
nparams: func.params.len(),
call_type: match func.kind {
FunctionKind::Method(_) => CallType::FirstArgIsThis,
FunctionKind::AsyncMethod(_) => CallType::AsyncFirstArgIsThis,
FunctionKind::Freestanding
| FunctionKind::Static(_)
| FunctionKind::Constructor(_) => CallType::Standard,
FunctionKind::AsyncFreestanding | FunctionKind::AsyncStatic(_) => {
CallType::AsyncStandard
}
},
iface_name: iface_name.map(|v| v.as_str()),
callee: &callee,
opts: options,
func,
resource_map: export_resource_map,
abi: AbiVariant::GuestExport,
requires_async_porcelain,
is_async,
});
match func.kind {
FunctionKind::AsyncFreestanding | FunctionKind::Freestanding => self.src.js("\n"),
FunctionKind::AsyncMethod(_)
| FunctionKind::AsyncStatic(_)
| FunctionKind::Method(_)
| FunctionKind::Static(_) => self.src.js(";\n"),
FunctionKind::Constructor(_) => self.src.js("\n}\n"),
}
}
}
#[derive(Default)]
pub struct Source {
pub js: source::Source,
pub js_init: source::Source,
}
impl Source {
pub fn js(&mut self, s: &str) {
self.js.push_str(s);
}
pub fn js_init(&mut self, s: &str) {
self.js_init.push_str(s);
}
}
fn semver_compat_key(version_str: &str) -> Option<(String, Version)> {
let version = Version::parse(version_str).ok()?;
if !version.pre.is_empty() {
None
} else if version.major != 0 {
Some((format!("{}", version.major), version))
} else if version.minor != 0 {
Some((format!("0.{}", version.minor), version))
} else {
None
}
}
fn parse_mapping(mapping: &str) -> (String, Option<String>) {
if mapping.len() > 1
&& let Some(hash_idx) = mapping[1..].find('#')
{
return (
mapping[0..hash_idx + 1].to_string(),
Some(mapping[hash_idx + 2..].into()),
);
}
(mapping.into(), None)
}
fn map_import(map: &Option<HashMap<String, String>>, impt: &str) -> (String, Option<String>) {
let impt_sans_version = match impt.find('@') {
Some(version_idx) => &impt[0..version_idx],
None => impt,
};
if let Some(map) = map.as_ref() {
if let Some(mapping) = map.get(impt) {
return parse_mapping(mapping);
}
if let Some(mapping) = map.get(impt_sans_version) {
return parse_mapping(mapping);
}
for (key, mapping) in map {
if let Some(wildcard_idx) = key.find('*') {
let lhs = &key[0..wildcard_idx];
let rhs = &key[wildcard_idx + 1..];
if impt_sans_version.starts_with(lhs) && impt_sans_version.ends_with(rhs) {
let matched = &impt_sans_version[wildcard_idx
..wildcard_idx + impt_sans_version.len() - lhs.len() - rhs.len()];
let mapping = mapping.replace('*', matched);
return parse_mapping(&mapping);
}
if impt.starts_with(lhs) && impt.ends_with(rhs) {
let matched =
&impt[wildcard_idx..wildcard_idx + impt.len() - lhs.len() - rhs.len()];
let mapping = mapping.replace('*', matched);
return parse_mapping(&mapping);
}
}
}
if let Some(at) = impt.find('@') {
let impt_ver_str = &impt[at + 1..];
if let Some((impt_compat, _)) = semver_compat_key(impt_ver_str) {
let mut best_match: Option<(String, Version)> = None;
for (key, mapping) in map {
let key_at = match key.find('@') {
Some(at) => at,
None => continue,
};
let key_base = &key[..key_at];
let key_ver_str = &key[key_at + 1..];
let (key_compat, key_ver) = match semver_compat_key(key_ver_str) {
Some(k) => k,
None => continue,
};
if impt_compat != key_compat {
continue;
}
let resolved = if let Some(wildcard_idx) = key_base.find('*') {
let lhs = &key_base[..wildcard_idx];
let rhs = &key_base[wildcard_idx + 1..];
if impt_sans_version.starts_with(lhs) && impt_sans_version.ends_with(rhs) {
let matched = &impt_sans_version[wildcard_idx
..wildcard_idx + impt_sans_version.len() - lhs.len() - rhs.len()];
Some(mapping.replace('*', matched))
} else {
None
}
} else if key_base == impt_sans_version {
Some(mapping.clone())
} else {
None
};
if let Some(resolved_mapping) = resolved {
match &best_match {
Some((_, prev_ver)) if key_ver <= *prev_ver => {}
_ => {
best_match = Some((resolved_mapping, key_ver));
}
}
}
}
if let Some((mapping, _)) = best_match {
return parse_mapping(&mapping);
}
}
}
}
(impt_sans_version.to_string(), None)
}
pub fn parse_world_key(name: &str) -> Option<(&str, &str, &str)> {
let registry_idx = name.find(':')?;
let ns = &name[0..registry_idx];
match name.rfind('/') {
Some(sep_idx) => {
let end = if let Some(version_idx) = name.rfind('@') {
version_idx
} else {
name.len()
};
Some((
ns,
&name[registry_idx + 1..sep_idx],
&name[sep_idx + 1..end],
))
}
None => Some((ns, &name[registry_idx + 1..], "")),
}
}
fn core_file_name(name: &str, idx: u32) -> String {
let i_str = if idx == 0 {
String::from("")
} else {
(idx + 1).to_string()
};
format!("{name}.core{i_str}.wasm")
}
fn string_encoding_js_literal(val: &wasmtime_environ::component::StringEncoding) -> &'static str {
match val {
wasmtime_environ::component::StringEncoding::Utf8 => "'utf8'",
wasmtime_environ::component::StringEncoding::Utf16 => "'utf16'",
wasmtime_environ::component::StringEncoding::CompactUtf16 => "'compact-utf16'",
}
}
pub fn gen_flat_lift_fn_list_js_expr(
intrinsic_mgr: &mut Instantiator,
types: &[InterfaceType],
canon_opts: &CanonicalOptions,
) -> String {
let mut lift_fns: Vec<String> = Vec::with_capacity(types.len());
for ty in types.iter() {
lift_fns.push(gen_flat_lift_fn_js_expr(
intrinsic_mgr,
ty,
&canon_opts.string_encoding,
));
}
format!("[{}]", lift_fns.join(","))
}
pub fn gen_flat_lift_fn_js_expr(
intrinsic_mgr: &mut Instantiator,
ty: &InterfaceType,
string_encoding: &wasmtime_environ::component::StringEncoding,
) -> String {
let component_types = intrinsic_mgr.types;
match ty {
InterfaceType::Bool => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatBool));
Intrinsic::Lift(LiftIntrinsic::LiftFlatBool).name().into()
}
InterfaceType::S8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS8));
Intrinsic::Lift(LiftIntrinsic::LiftFlatS8).name().into()
}
InterfaceType::U8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU8));
Intrinsic::Lift(LiftIntrinsic::LiftFlatU8).name().into()
}
InterfaceType::S16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS16));
Intrinsic::Lift(LiftIntrinsic::LiftFlatS16).name().into()
}
InterfaceType::U16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU16));
Intrinsic::Lift(LiftIntrinsic::LiftFlatU16).name().into()
}
InterfaceType::S32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS32));
Intrinsic::Lift(LiftIntrinsic::LiftFlatS32).name().into()
}
InterfaceType::U32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU32));
Intrinsic::Lift(LiftIntrinsic::LiftFlatU32).name().into()
}
InterfaceType::S64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS64));
Intrinsic::Lift(LiftIntrinsic::LiftFlatS64).name().into()
}
InterfaceType::U64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU64));
Intrinsic::Lift(LiftIntrinsic::LiftFlatU64).name().into()
}
InterfaceType::Float32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat32));
Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat32)
.name()
.into()
}
InterfaceType::Float64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat64));
Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat64)
.name()
.into()
}
InterfaceType::Char => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatChar));
Intrinsic::Lift(LiftIntrinsic::LiftFlatChar).name().into()
}
InterfaceType::String => match string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatStringUtf8));
Intrinsic::Lift(LiftIntrinsic::LiftFlatStringUtf8)
.name()
.into()
}
wasmtime_environ::component::StringEncoding::Utf16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatStringUtf16));
Intrinsic::Lift(LiftIntrinsic::LiftFlatStringUtf16)
.name()
.into()
}
wasmtime_environ::component::StringEncoding::CompactUtf16 => {
todo!("latin1+utf8 not supported")
}
},
InterfaceType::Record(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord));
let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord).name();
let record_ty = &component_types[*ty_idx];
let mut keys_and_lifts_expr = String::from("[");
for f in &record_ty.fields {
keys_and_lifts_expr.push_str(&format!(
"['{}', {}, {}, {}],",
f.name.to_lower_camel_case(),
gen_flat_lift_fn_js_expr(intrinsic_mgr, &f.ty, string_encoding),
component_types.canonical_abi(ty).size32,
component_types.canonical_abi(ty).align32,
));
}
keys_and_lifts_expr.push(']');
format!("{lift_fn}({keys_and_lifts_expr})")
}
InterfaceType::Variant(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant));
let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant).name();
let variant_ty = &component_types[*ty_idx];
let mut cases_and_lifts_expr = String::from("[");
for (name, maybe_ty) in &variant_ty.cases {
let lift_args = match maybe_ty {
None => format!("['{}', null, 0, 0, 0],", name),
Some(ty) => {
format!(
"['{name}', {}, {}, {}, {}],",
gen_flat_lift_fn_js_expr(intrinsic_mgr, ty, string_encoding),
variant_ty.abi.size32,
variant_ty.abi.align32,
variant_ty.info.payload_offset32,
)
}
};
cases_and_lifts_expr.push_str(&lift_args);
}
cases_and_lifts_expr.push(']');
format!("{lift_fn}({cases_and_lifts_expr})")
}
InterfaceType::List(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatList));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatList).name();
let list_ty = &component_types[*ty_idx];
let lift_fn_expr =
gen_flat_lift_fn_js_expr(intrinsic_mgr, &list_ty.element, string_encoding);
let elem_cabi = component_types.canonical_abi(&list_ty.element);
let align_32 = elem_cabi.align32;
let size_32 = elem_cabi.size32;
format!("{f}({{ elemLiftFn: {lift_fn_expr}, align32: {align_32}, size32: {size_32} }})")
}
InterfaceType::FixedLengthList(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatList));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatList).name();
let list_ty = &component_types[*ty_idx];
let lift_fn_expr =
gen_flat_lift_fn_js_expr(intrinsic_mgr, &list_ty.element, string_encoding);
let list_len = list_ty.size;
let elem_cabi = component_types.canonical_abi(&list_ty.element);
let align_32 = elem_cabi.align32;
let size_32 = elem_cabi.size32;
format!(
"{f}({{ elemLiftFn: {lift_fn_expr}, align32: {align_32}, size32: {size_32}, knownLen: {list_len} }})"
)
}
InterfaceType::Tuple(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatTuple));
let tuple_ty = &component_types[*ty_idx];
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatTuple).name();
let size_u32 = tuple_ty.abi.size32;
let align_u32 = tuple_ty.abi.align32;
let mut elem_lifts_expr = String::from("[");
for ty in &tuple_ty.types {
let lift_fn_js = gen_flat_lift_fn_js_expr(intrinsic_mgr, ty, string_encoding);
elem_lifts_expr.push_str(&format!("[{lift_fn_js}, {size_u32}, {align_u32}],"));
}
elem_lifts_expr.push(']');
format!("{f}({elem_lifts_expr})")
}
InterfaceType::Flags(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags).name();
let flags_ty = &component_types[*ty_idx];
let size_u32 = flags_ty.abi.size32;
let align_u32 = flags_ty.abi.align32;
let names_expr = format!(
"[{}]",
flags_ty
.names
.iter()
.map(|s| format!("'{s}'"))
.collect::<Vec<_>>()
.join(",")
);
let num_flags = flags_ty.names.len();
let elem_size = if num_flags <= 8 {
1
} else if num_flags <= 16 {
2
} else {
4
};
format!(
"{f}({{ names: {names_expr}, size32: {size_u32}, align32: {align_u32}, intSize: {elem_size} }})"
)
}
InterfaceType::Enum(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatEnum));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatEnum).name();
let enum_ty = &component_types[*ty_idx];
let size_32 = enum_ty.abi.size32;
let align_32 = enum_ty.abi.align32;
let payload_offset_32 = enum_ty.info.payload_offset32;
let mut elem_lifts_expr = String::from("[");
for name in &enum_ty.names {
elem_lifts_expr.push_str(&format!(
"['{name}', null, {size_32}, {align_32}, {payload_offset_32}],"
));
}
elem_lifts_expr.push(']');
format!("{f}({elem_lifts_expr})")
}
InterfaceType::Option(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatOption));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatOption).name();
let option_ty = &component_types[*ty_idx];
let payload_offset_32 = option_ty.info.payload_offset32;
let align_32 = option_ty.abi.align32;
let size_32 = option_ty.abi.size32;
let lift_fn_js =
gen_flat_lift_fn_js_expr(intrinsic_mgr, &option_ty.ty, string_encoding);
format!(
"{f}([
['none', null, {size_32}, {align_32}, {payload_offset_32} ],
['some', {lift_fn_js}, {size_32}, {align_32}, {payload_offset_32} ],
])"
)
}
InterfaceType::Result(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatResult));
let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatResult).name();
let result_ty = &component_types[*ty_idx];
let mut cases_and_lifts_expr = String::from("[");
if let Some(ok_ty) = result_ty.ok {
cases_and_lifts_expr.push_str(&format!(
"['ok', {}, {}, {}, {}],",
gen_flat_lift_fn_js_expr(intrinsic_mgr, &ok_ty, string_encoding),
result_ty.abi.size32,
result_ty.abi.align32,
result_ty.info.payload_offset32,
))
} else {
cases_and_lifts_expr.push_str("['ok', null, 0, 0, 0],");
}
if let Some(err_ty) = &result_ty.err {
cases_and_lifts_expr.push_str(&format!(
"['err', {}, {}, {}, {}],",
gen_flat_lift_fn_js_expr(intrinsic_mgr, err_ty, string_encoding),
result_ty.abi.size32,
result_ty.abi.align32,
result_ty.info.payload_offset32,
))
} else {
cases_and_lifts_expr.push_str("['err', null, 0, 0, 0],");
}
cases_and_lifts_expr.push(']');
format!("{lift_fn}({cases_and_lifts_expr})")
}
InterfaceType::Own(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatOwn));
intrinsic_mgr.add_intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
intrinsic_mgr.add_intrinsic(Intrinsic::SymbolResourceHandle);
intrinsic_mgr.add_intrinsic(Intrinsic::SymbolDispose);
intrinsic_mgr
.add_intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
intrinsic_mgr.add_intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatOwn).name();
let table_ty = &component_types[*ty_idx];
let component_idx = table_ty.unwrap_concrete_instance().as_u32();
let resource_idx = table_ty.unwrap_concrete_ty();
if let Some(resource_typedef) = intrinsic_mgr
.exports_resource_index_types
.get(&resource_idx)
&& let Some(ResourceTable { data, .. }) =
intrinsic_mgr.resource_exports.get(resource_typedef)
{
let (resource_class_name, create_resource_fn_js) = match data {
ResourceData::Guest { .. } => {
unimplemented!(
"owned resources created by guests should must have host-side data"
)
}
ResourceData::Host {
tid,
local_name,
dtor_name,
..
} => {
let empty_func = JsHelperIntrinsic::EmptyFunc.name();
let symbol_resource_handle = Intrinsic::SymbolResourceHandle.name();
let symbol_dispose = Intrinsic::SymbolDispose.name();
let rsc_table_remove = ResourceIntrinsic::ResourceTableRemove.name();
let tid = tid.as_u32();
let rsc_flag = ResourceIntrinsic::ResourceTableFlag.name();
let dtor_setup_js = dtor_name.as_ref().map(|dtor|
format!(
r#"
Object.defineProperty(
resourceObj,
{symbol_dispose},
{{
writable: true,
value: function() {{
finalizationRegistry{tid}.unregister(resourceObj);
{rsc_table_remove}(handleTable{tid}, handle);
resourceObj[{symbol_dispose}] = {empty_func};
resourceObj[{symbol_resource_handle}] = undefined;
{dtor}(handleTable{tid}[(handle << 1) + 1] & ~{rsc_flag});
}}
}}
);
"#
)
).unwrap_or_default();
let create_resource_fn_js = format!(
r#"
(handle) => {{
const resourceObj = Object.create({local_name}.prototype);
Object.defineProperty(resourceObj, {symbol_resource_handle}, {{
writable: true,
value: handle,
}});
finalizationRegistry{tid}.register(resourceObj, handle, resourceObj);
{dtor_setup_js}
return resourceObj;
}}
"#
);
(local_name.to_string(), create_resource_fn_js)
}
};
format!(
r#"{f}({{
componentIdx: {component_idx},
className: {resource_class_name},
createResourceFn: {create_resource_fn_js},
}})
"#,
)
} else {
format!(
r#"{f}({{
componentIdx: {component_idx},
className: null,
createResourceFn: () => {{ throw new Error('invalid/missing resource type data'); }},
}})
"#,
)
}
}
InterfaceType::Borrow(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatBorrow));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatBorrow).name();
format!("{f}.bind(null, {table_idx})")
}
InterfaceType::Future(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture).name();
format!("{f}.bind(null, {table_idx})")
}
InterfaceType::Stream(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatStream));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatStream).name();
let table_ty = &component_types[*ty_idx];
let component_idx = table_ty.instance.as_u32();
let stream_ty_idx = table_ty.ty;
let stream_ty = &component_types[stream_ty_idx];
let payload = stream_ty.payload;
let (is_borrowed, is_none_type_js, is_numeric_type_js) = match payload {
None => (false, true, false),
Some(t) => (
matches!(t, InterfaceType::Borrow(_)),
false,
matches!(
ty,
InterfaceType::U8
| InterfaceType::U16
| InterfaceType::U32
| InterfaceType::U64
| InterfaceType::S8
| InterfaceType::S16
| InterfaceType::S32
| InterfaceType::S64
| InterfaceType::Float32
| InterfaceType::Float64
),
),
};
format!(
r#"{f}({{
streamTableIdx: {table_idx},
componentIdx: {component_idx},
isBorrowedType: {is_borrowed},
isNoneType: {is_none_type_js},
isNumericTypeJs: {is_numeric_type_js},
}})"#
)
}
InterfaceType::ErrorContext(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatErrorContext));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatErrorContext).name();
format!("{f}.bind(null, {table_idx})")
}
}
}
pub fn gen_flat_lower_fn_list_js_expr(
intrinsic_mgr: &mut impl ManagesIntrinsics,
component_types: &ComponentTypes,
types: &[InterfaceType],
string_encoding: &wasmtime_environ::component::StringEncoding,
) -> String {
let mut lower_fns: Vec<String> = Vec::with_capacity(types.len());
for ty in types.iter() {
lower_fns.push(gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
ty,
string_encoding,
));
}
format!("[{}]", lower_fns.join(","))
}
pub fn gen_flat_lower_fn_js_expr(
intrinsic_mgr: &mut impl ManagesIntrinsics,
component_types: &ComponentTypes,
ty: &InterfaceType,
string_encoding: &wasmtime_environ::component::StringEncoding,
) -> String {
match ty {
InterfaceType::Bool => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatBool));
Intrinsic::Lower(LowerIntrinsic::LowerFlatBool)
.name()
.into()
}
InterfaceType::S8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS8));
Intrinsic::Lower(LowerIntrinsic::LowerFlatS8).name().into()
}
InterfaceType::U8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU8));
Intrinsic::Lower(LowerIntrinsic::LowerFlatU8).name().into()
}
InterfaceType::S16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS16));
Intrinsic::Lower(LowerIntrinsic::LowerFlatS16).name().into()
}
InterfaceType::U16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU16));
Intrinsic::Lower(LowerIntrinsic::LowerFlatU16).name().into()
}
InterfaceType::S32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS32));
Intrinsic::Lower(LowerIntrinsic::LowerFlatS32).name().into()
}
InterfaceType::U32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU32));
Intrinsic::Lower(LowerIntrinsic::LowerFlatU32).name().into()
}
InterfaceType::S64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS64));
Intrinsic::Lower(LowerIntrinsic::LowerFlatS64).name().into()
}
InterfaceType::U64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU64));
Intrinsic::Lower(LowerIntrinsic::LowerFlatU64).name().into()
}
InterfaceType::Float32 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat32));
Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat32)
.name()
.into()
}
InterfaceType::Float64 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat64));
Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat64)
.name()
.into()
}
InterfaceType::Char => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatChar));
Intrinsic::Lower(LowerIntrinsic::LowerFlatChar)
.name()
.into()
}
InterfaceType::String => match string_encoding {
wasmtime_environ::component::StringEncoding::Utf8 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatStringUtf8));
Intrinsic::Lower(LowerIntrinsic::LowerFlatStringUtf8)
.name()
.into()
}
wasmtime_environ::component::StringEncoding::Utf16 => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatStringUtf16));
Intrinsic::Lower(LowerIntrinsic::LowerFlatStringUtf16)
.name()
.into()
}
wasmtime_environ::component::StringEncoding::CompactUtf16 => {
todo!("latin1+utf8 not supported")
}
},
InterfaceType::Record(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord));
let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord).name();
let record_ty = &component_types[*ty_idx];
let mut keys_and_lowers_expr = String::from("[");
for f in &record_ty.fields {
keys_and_lowers_expr.push_str(&format!(
"['{}', {}, {}, {} ],",
f.name.to_lower_camel_case(),
gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&f.ty,
string_encoding
),
component_types.canonical_abi(ty).size32,
component_types.canonical_abi(ty).align32,
));
}
keys_and_lowers_expr.push(']');
format!("{lower_fn}.bind(null, {keys_and_lowers_expr})")
}
InterfaceType::Variant(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatVariant));
let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatVariant).name();
let variant_ty = &component_types[*ty_idx];
let size32 = variant_ty.abi.size32;
let align32 = variant_ty.abi.align32;
let payload_offset32 = variant_ty.info.payload_offset32;
let mut lower_metas_expr = String::from("[");
for (name, maybe_ty) in variant_ty.cases.iter() {
lower_metas_expr.push_str(&format!(
"[ '{name}', {}, {size32}, {align32}, {payload_offset32} ],",
maybe_ty
.map(|ty| gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&ty,
string_encoding,
))
.unwrap_or_else(|| "null".into()),
));
}
lower_metas_expr.push(']');
format!("{lower_fn}({lower_metas_expr})")
}
InterfaceType::List(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatList));
let list_ty = &component_types[*ty_idx];
let elem_ty_lower_expr = gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&list_ty.element,
string_encoding,
);
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatList).name();
let ty_idx = ty_idx.as_u32();
format!("{f}({{ elemLowerFn: {elem_ty_lower_expr}, typeIdx: {ty_idx} }})")
}
InterfaceType::FixedLengthList(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatList));
let list_ty = &component_types[*ty_idx];
let elem_ty_lower_expr = gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&list_ty.element,
string_encoding,
);
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatList).name();
let ty_idx = ty_idx.as_u32();
format!("{f}({{ elemLowerFn: {elem_ty_lower_expr}, typeIdx: {ty_idx} }})")
}
InterfaceType::Tuple(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatTuple));
let ty_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatTuple).name();
format!("{f}.bind(null, {ty_idx})")
}
InterfaceType::Flags(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags));
let ty_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags).name();
format!("{f}.bind(null, {ty_idx})")
}
InterfaceType::Enum(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatEnum));
let ty_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatEnum).name();
format!("{f}.bind(null, {ty_idx})")
}
InterfaceType::Option(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatOption));
let option_ty = &component_types[*ty_idx];
let size32 = option_ty.abi.size32;
let align32 = option_ty.abi.align32;
let payload_offset32 = option_ty.info.payload_offset32;
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatOption).name();
let mut cases_and_lowers_expr = String::from("[");
cases_and_lowers_expr.push_str(&format!(
"[ 'some', {}, {size32}, {align32}, {payload_offset32} ],",
gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&option_ty.ty,
string_encoding,
)
));
cases_and_lowers_expr.push_str(&format!(
"[ 'none', null, {size32}, {align32}, {payload_offset32} ],",
));
cases_and_lowers_expr.push(']');
format!("{f}({cases_and_lowers_expr})")
}
InterfaceType::Result(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatResult));
let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatResult).name();
let result_ty = &component_types[*ty_idx];
let size32 = result_ty.abi.size32;
let align32 = result_ty.abi.align32;
let payload_offset32 = result_ty.info.payload_offset32;
let mut cases_and_lowers_expr = String::from("[");
cases_and_lowers_expr.push_str(&format!(
"[ 'ok', {}, {size32}, {align32}, {payload_offset32} ],",
result_ty
.ok
.map(|ty| gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&ty,
string_encoding,
))
.unwrap_or_else(|| "null".into())
));
cases_and_lowers_expr.push_str(&format!(
"[ 'err', {}, {size32}, {align32}, {payload_offset32} ],",
result_ty
.err
.map(|ty| gen_flat_lower_fn_js_expr(
intrinsic_mgr,
component_types,
&ty,
string_encoding,
))
.unwrap_or_else(|| "null".into())
));
cases_and_lowers_expr.push(']');
format!("{lower_fn}({cases_and_lowers_expr})")
}
InterfaceType::Own(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatOwn));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatOwn).name();
format!("{f}.bind(null, {table_idx})")
}
InterfaceType::Borrow(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatBorrow));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatBorrow).name();
format!("{f}.bind(null, {table_idx})")
}
InterfaceType::Future(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture));
let table_idx = ty_idx.as_u32();
let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture).name();
format!("{f}.bind(null, {table_idx})")
}
InterfaceType::Stream(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatStream));
let table_idx = ty_idx.as_u32();
let lower_flat_stream_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatStream).name();
format!("{lower_flat_stream_fn}.bind(null, {table_idx})")
}
InterfaceType::ErrorContext(ty_idx) => {
intrinsic_mgr.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatErrorContext));
let table_idx = ty_idx.as_u32();
let lower_flat_err_ctx_fn =
Intrinsic::Lower(LowerIntrinsic::LowerFlatErrorContext).name();
format!("{lower_flat_err_ctx_fn}.bind(null, {table_idx})")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compat_key(version_str: &str) -> Option<String> {
semver_compat_key(version_str).map(|(key, _)| key)
}
#[test]
fn test_semver_compat_key() {
assert_eq!(compat_key("1.0.0"), Some("1".into()));
assert_eq!(compat_key("1.2.3"), Some("1".into()));
assert_eq!(compat_key("2.0.0"), Some("2".into()));
assert_eq!(compat_key("0.2.0"), Some("0.2".into()));
assert_eq!(compat_key("0.2.10"), Some("0.2".into()));
assert_eq!(compat_key("0.1.0"), Some("0.1".into()));
assert_eq!(compat_key("0.0.1"), None);
assert_eq!(compat_key("1.0.0-rc.1"), None);
assert_eq!(compat_key("0.2.0-pre"), None);
assert_eq!(compat_key("not-a-version"), None);
}
#[test]
fn test_semver_compat_key_returns_parsed_version() {
let (key, ver) = semver_compat_key("1.2.3").unwrap();
assert_eq!(key, "1");
assert_eq!(ver, Version::new(1, 2, 3));
}
#[test]
fn test_map_import_exact_match() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.2.0".into(), "./http.js#types".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.0"),
("./http.js".into(), Some("types".into()))
);
}
#[test]
fn test_map_import_sans_version_match() {
let mut map = HashMap::new();
map.insert("wasi:http/types".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("./http.js".into(), None)
);
}
#[test]
fn test_map_import_wildcard_sans_version() {
let mut map = HashMap::new();
map.insert("wasi:http/*".into(), "./http.js#*".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("./http.js".into(), Some("types".into()))
);
}
#[test]
fn test_map_import_semver_exact_key() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.2.0".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("./http.js".into(), None)
);
}
#[test]
fn test_map_import_semver_wildcard_key() {
let mut map = HashMap::new();
map.insert("wasi:http/*@0.2.1".into(), "./http.js#*".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("./http.js".into(), Some("types".into()))
);
}
#[test]
fn test_map_import_semver_lower_import_version() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.2.10".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.1"),
("./http.js".into(), None)
);
}
#[test]
fn test_map_import_semver_no_cross_minor() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.3.0".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_map_import_semver_prefers_highest() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.2.1".into(), "./http-old.js".into());
map.insert("wasi:http/types@0.2.5".into(), "./http-new.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.10"),
("./http-new.js".into(), None)
);
}
#[test]
fn test_map_import_no_match_prerelease() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.2.0-rc.1".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.2.0"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_map_import_no_match_zero_zero() {
let mut map = HashMap::new();
map.insert("wasi:http/types@0.0.1".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@0.0.2"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_map_import_semver_major_version() {
let mut map = HashMap::new();
map.insert("wasi:http/types@1.0.0".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@1.2.3"),
("./http.js".into(), None)
);
}
#[test]
fn test_map_import_semver_no_cross_major() {
let mut map = HashMap::new();
map.insert("wasi:http/types@1.0.0".into(), "./http.js".into());
let map = Some(map);
assert_eq!(
map_import(&map, "wasi:http/types@2.0.0"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_map_import_no_map() {
assert_eq!(
map_import(&None, "wasi:http/types@0.2.0"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_map_import_no_map_unversioned() {
assert_eq!(
map_import(&None, "wasi:http/types"),
("wasi:http/types".into(), None)
);
}
#[test]
fn test_parse_mapping_with_hash() {
assert_eq!(
parse_mapping("./http.js#types"),
("./http.js".into(), Some("types".into()))
);
}
#[test]
fn test_parse_mapping_without_hash() {
assert_eq!(parse_mapping("./http.js"), ("./http.js".into(), None));
}
#[test]
fn test_parse_mapping_leading_hash() {
assert_eq!(parse_mapping("#foo"), ("#foo".into(), None));
}
#[test]
fn test_parse_mapping_empty() {
assert_eq!(parse_mapping(""), ("".into(), None));
}
}