Skip to main content

js_component_bindgen/
lib.rs

1use std::collections::HashSet;
2
3use anyhow::{Context as _, Result, anyhow, bail, ensure};
4use ts_bindgen::ts_bindgen;
5use wasmtime_environ::component::{
6    CanonicalOptions, ComponentTypesBuilder, Export, StaticModuleIndex,
7};
8use wasmtime_environ::wasmparser::WasmFeatures;
9use wasmtime_environ::{PrimaryMap, ScopeVec, Tunables};
10use wit_bindgen_core::wit_parser::Function;
11use wit_component::DecodedWasm;
12use wit_parser::{Package, Resolve, Stability, Type, TypeDefKind, TypeId, WorldId};
13
14mod core;
15mod files;
16mod transpile_bindgen;
17mod ts_bindgen;
18
19pub mod esm_bindgen;
20pub mod function_bindgen;
21pub mod names;
22pub mod source;
23
24pub mod intrinsics;
25use intrinsics::Intrinsic;
26
27use transpile_bindgen::transpile_bindgen;
28pub use transpile_bindgen::{AsyncMode, BindingsMode, InstantiationMode, TranspileOpts};
29
30/// Calls [`write!`] with the passed arguments and unwraps the result.
31///
32/// Useful for writing to things with infallible `Write` implementations like
33/// `Source` and `String`.
34///
35/// [`write!`]: std::write
36#[macro_export]
37macro_rules! uwrite {
38    ($dst:expr, $($arg:tt)*) => {
39        write!($dst, $($arg)*).unwrap()
40    };
41}
42
43/// Calls [`writeln!`] with the passed arguments and unwraps the result.
44///
45/// Useful for writing to things with infallible `Write` implementations like
46/// `Source` and `String`.
47///
48/// [`writeln!`]: std::writeln
49#[macro_export]
50macro_rules! uwriteln {
51    ($dst:expr, $($arg:tt)*) => {
52        writeln!($dst, $($arg)*).unwrap()
53    };
54}
55
56pub struct Transpiled {
57    pub files: Vec<(String, Vec<u8>)>,
58    pub imports: Vec<String>,
59    pub exports: Vec<(String, Export)>,
60}
61
62pub struct ComponentInfo {
63    pub imports: Vec<String>,
64    pub exports: Vec<(String, wasmtime_environ::component::Export)>,
65}
66
67pub fn generate_types(
68    name: &str,
69    resolve: Resolve,
70    world_id: WorldId,
71    opts: TranspileOpts,
72) -> Result<Vec<(String, Vec<u8>)>> {
73    let mut files = files::Files::default();
74
75    ts_bindgen(name, &resolve, world_id, &opts, &mut files)
76        .context("failed to generate Typescript bindings")?;
77
78    let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
79    for (name, source) in files.iter() {
80        files_out.push((name.to_string(), source.to_vec()));
81    }
82    Ok(files_out)
83}
84
85/// Generate the JS transpilation bindgen for a given Wasm component binary
86/// Outputs the file map and import and export metadata for the Transpilation
87#[cfg(feature = "transpile-bindgen")]
88pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result<Transpiled> {
89    use wasmtime_environ::component::{Component, Translator};
90
91    let name = opts.name.clone();
92    let mut files = files::Files::default();
93
94    // Use the `wit-component` crate here to parse `binary` and discover
95    // the type-level descriptions and `Resolve` corresponding to the
96    // component binary. This will synthesize a `Resolve` which has a top-level
97    // package which has a single document and `world` within it which describes
98    // the state of the component. This is then further used afterwards for
99    // bindings Transpilation as-if a `*.wit` file was input.
100    let decoded = wit_component::decode(component)
101        .context("failed to extract interface information from component")?;
102
103    let (resolve, world_id) = match decoded {
104        DecodedWasm::WitPackage(_, _) => bail!("unexpected wit package as input"),
105        DecodedWasm::Component(resolve, world_id) => (resolve, world_id),
106    };
107
108    // Components are complicated, there's no real way around that. To
109    // handle all the work of parsing a component and figuring out how to
110    // instantiate core wasm modules and such all the work is offloaded to
111    // Wasmtime itself. This crate generator is based on Wasmtime's
112    // low-level `wasmtime-environ` crate which is technically not a public
113    // dependency but the same author who worked on that in Wasmtime wrote
114    // this as well so... "seems fine".
115    //
116    // Note that we're not pulling in the entire Wasmtime engine here,
117    // moreso just the "spine" of validating a component. This enables using
118    // Wasmtime's internal `Component` representation as a much easier to
119    // process version of a component that has decompiled everything
120    // internal to a component to a straight linear list of initializers
121    // that need to be executed to instantiate a component.
122    let scope = ScopeVec::new();
123    let tunables = Tunables::default_u32();
124
125    // The validator that will be used on the component must enable support for all
126    // CM features we expect components to use.
127    //
128    // This does not require the correct execution of the related features post-transpilation,
129    // but without the right features specified, components won't load at all.
130    let mut validator = wasmtime_environ::wasmparser::Validator::new_with_features(
131        WasmFeatures::WASM3
132            | WasmFeatures::WIDE_ARITHMETIC
133            | WasmFeatures::COMPONENT_MODEL
134            | WasmFeatures::CM_ASYNC
135            | WasmFeatures::CM_ASYNC_BUILTINS
136            | WasmFeatures::CM_ASYNC_STACKFUL
137            | WasmFeatures::CM_ERROR_CONTEXT
138            | WasmFeatures::CM_FIXED_SIZE_LIST,
139    );
140
141    let mut types = ComponentTypesBuilder::new(&validator);
142
143    let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope)
144        .translate(component)
145        .map_err(|e| anyhow!(e).context("failed to translate component"))?;
146
147    let modules: PrimaryMap<StaticModuleIndex, core::Translation<'_>> = modules
148        .into_iter()
149        .map(|(_i, module)| core::Translation::new(module, opts.multi_memory))
150        .collect::<Result<_>>()?;
151
152    let wasmtime_component = Component::default();
153    let types = types.finish(&wasmtime_component);
154
155    // Insert all core wasm modules into the generated `Files` which will
156    // end up getting used in the `generate_instantiate` method.
157    for (i, module) in modules.iter() {
158        files.push(&core_file_name(&name, i.as_u32()), module.wasm());
159    }
160
161    if !opts.no_typescript {
162        ts_bindgen(&name, &resolve, world_id, &opts, &mut files)
163            .context("failed to generate Typescript bindings")?;
164    }
165
166    let (imports, exports) = transpile_bindgen(
167        &name, &component, &modules, &types.0, &resolve, world_id, opts, &mut files,
168    );
169
170    let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
171    for (name, source) in files.iter() {
172        files_out.push((name.to_string(), source.to_vec()));
173    }
174    Ok(Transpiled {
175        files: files_out,
176        imports,
177        exports,
178    })
179}
180
181fn core_file_name(name: &str, idx: u32) -> String {
182    let i_str = if idx == 0 {
183        String::from("")
184    } else {
185        (idx + 1).to_string()
186    };
187    format!("{name}.core{i_str}.wasm")
188}
189
190pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
191    loop {
192        match &resolve.types[id].kind {
193            TypeDefKind::Type(Type::Id(that_id)) => id = *that_id,
194            _ => break id,
195        }
196    }
197}
198
199/// Check if an item (usually some form of [`WorldItem`]) should be allowed through the feature gate
200/// of a given package.
201fn feature_gate_allowed(
202    resolve: &Resolve,
203    package: &Package,
204    stability: &Stability,
205    item_name: &str,
206) -> Result<bool> {
207    Ok(match stability {
208        Stability::Unknown => true,
209        Stability::Stable { since, .. } => {
210            let Some(package_version) = package.name.version.as_ref() else {
211                // If the package version is missing (we're likely dealing with an unresolved package)
212                // and we can't really check much.
213                return Ok(true);
214            };
215
216            ensure!(
217                package_version >= since,
218                "feature gate on [{item_name}] refers to an unreleased (future) package version [{since}] (current package version is [{package_version}])"
219            );
220
221            // Stabilization (@since annotation) overrides features and deprecation
222            true
223        }
224        Stability::Unstable {
225            feature,
226            deprecated: _,
227        } => {
228            // If a @unstable feature is present but the related feature was not enabled
229            // or all features was not selected, exclude
230            resolve.all_features || resolve.features.contains(feature)
231        }
232    })
233}
234
235/// Utility function for deducing whether a type can throw
236pub fn get_thrown_type(
237    resolve: &Resolve,
238    return_type: Option<Type>,
239) -> Option<(Option<&Type>, Option<&Type>)> {
240    match return_type {
241        None => None,
242        Some(Type::Id(id)) => match &resolve.types[id].kind {
243            TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())),
244            _ => None,
245        },
246        _ => None,
247    }
248}
249
250/// Check whether a given function is an async fn
251///
252/// Functions that are designated as guest async represent use of
253/// the WASI p3 async feature.
254///
255/// These functions must be called from transpiled javsacript much differently
256/// than they would otherwise be, i.e. in accordance to the Component Model
257/// async feature.
258pub(crate) fn is_async_fn(func: &Function, canon_opts: &CanonicalOptions) -> bool {
259    if canon_opts.async_ {
260        return true;
261    }
262    func.kind.is_async()
263}
264
265/// Identifier for a function used
266enum FunctionIdentifier<'a> {
267    Fn(&'a Function),
268    CanonFnName(&'a str),
269}
270
271/// Check whether a function has been marked or async binding generation
272///
273/// When dealing with imports, functions that are designated to require async porcelain
274/// are usually asynchronous host functions -- they will have code generated
275/// that enables use of techniques like JSPI for exposing asynchronous host/platform
276/// imports to WebAssembly guests.
277///
278/// When dealing with an export, functions that require async porcelain simply provide
279/// an interface in the transpiled codebase that produces a `Promise`, i.e. one that can
280/// be called in an *already* asynchronous context (JS `async` function) or resolved with a`.then()`.
281///
282/// Exports do not indicate a use of JSPI, as JSPI is only for bridging asynchronous *host* behavior
283/// to synchronous WebAssembly modules
284///
285/// This function is *not* for detecting WASI P3 asynchronous behavior -- see [`is_guest_async_lifted_fn`].
286pub(crate) fn requires_async_porcelain(
287    func: FunctionIdentifier<'_>,
288    id: &str,
289    async_funcs: &HashSet<String>,
290) -> bool {
291    let name = match func {
292        FunctionIdentifier::Fn(func) => func.name.as_str(),
293        FunctionIdentifier::CanonFnName(name) => name,
294    }
295    .trim_start_matches("[async]");
296
297    if async_funcs.contains(name) {
298        return true;
299    }
300
301    let qualified_name = format!("{id}#{name}");
302    if async_funcs.contains(&qualified_name) {
303        return true;
304    }
305
306    if let Some(pos) = id.find('@') {
307        let namespace = &id[..pos];
308        let namespaced_name = format!("{namespace}#{name}");
309
310        if async_funcs.contains(&namespaced_name) {
311            return true;
312        }
313    }
314    false
315}
316
317/// Objects that can control the printing/setup of intrinsics (normally in some final codegen output)
318trait ManagesIntrinsics {
319    /// Add an intrinsic, supplying it's name afterwards
320    fn add_intrinsic(&mut self, intrinsic: Intrinsic);
321}