Skip to main content

js_component_bindgen/
function_bindgen.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write;
3use std::mem;
4
5use heck::{ToLowerCamelCase, ToUpperCamelCase};
6use wasmtime_environ::component::{
7    InterfaceType, ResourceIndex, RuntimeCallbackIndex, RuntimeComponentInstanceIndex,
8    RuntimeMemoryIndex, RuntimeReallocIndex, TypeComponentLocalErrorContextTableIndex,
9    TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex,
10};
11use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
12use wit_component::StringEncoding;
13use wit_parser::abi::WasmType;
14use wit_parser::{
15    Alignment, ArchitectureSize, Handle, Resolve, SizeAlign, Type, TypeDef, TypeDefKind, TypeId,
16};
17
18use crate::intrinsics::Intrinsic;
19use crate::intrinsics::component::ComponentIntrinsic;
20use crate::intrinsics::conversion::ConversionIntrinsic;
21use crate::intrinsics::js_helper::JsHelperIntrinsic;
22use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
23use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
24use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
25use crate::intrinsics::resource::ResourceIntrinsic;
26use crate::intrinsics::string::StringIntrinsic;
27use crate::{ManagesIntrinsics, get_thrown_type, source};
28use crate::{uwrite, uwriteln};
29
30/// Method of error handling
31#[derive(Debug, Clone, PartialEq)]
32pub enum ErrHandling {
33    /// Do no special handling of errors, requiring users to return objects that represent
34    /// errors as represented in WIT
35    None,
36    /// Require throwing of result error objects
37    ThrowResultErr,
38    /// Catch thrown errors and convert them into result<t,e> error variants
39    ResultCatchHandler,
40}
41
42impl ErrHandling {
43    fn to_js_string(&self) -> String {
44        match self {
45            ErrHandling::None => "none".into(),
46            ErrHandling::ThrowResultErr => "throw-result-err".into(),
47            ErrHandling::ResultCatchHandler => "result-catch-handler".into(),
48        }
49    }
50}
51
52/// Data related to a given resource
53#[derive(Clone, Debug, PartialEq)]
54pub enum ResourceData {
55    Host {
56        tid: TypeResourceTableIndex,
57        rid: ResourceIndex,
58        local_name: String,
59        dtor_name: Option<String>,
60    },
61    Guest {
62        resource_name: String,
63        prefix: Option<String>,
64        extra: Option<ResourceExtraData>,
65    },
66}
67
68#[derive(Clone, Debug, PartialEq)]
69pub struct PayloadTypeMetadata {
70    pub(crate) ty: Type,
71    pub(crate) iface_ty: InterfaceType,
72    /// JS expression that serves as a function that lifts a given type
73    pub(crate) lift_js_expr: String,
74    /// JS expression that serves as a function that lowers a given type
75    pub(crate) lower_js_expr: String,
76    pub(crate) size32: u32,
77    pub(crate) align32: u32,
78    pub(crate) flat_count: Option<u8>,
79}
80
81/// Supplemental data kept along with [`ResourceData`]
82#[derive(Clone, Debug, PartialEq)]
83pub enum ResourceExtraData {
84    Stream {
85        table_idx: TypeStreamTableIndex,
86        elem_ty: Option<PayloadTypeMetadata>,
87    },
88    Future {
89        table_idx: TypeFutureTableIndex,
90        elem_ty: Option<PayloadTypeMetadata>,
91        nesting_level: u32,
92    },
93    ErrorContext {
94        table_idx: TypeComponentLocalErrorContextTableIndex,
95    },
96}
97
98/// Map used for resource function bindgen within a given component
99///
100/// Mapping from the instance + resource index in that component (internal or external)
101/// to the unique global resource id used to key the resource tables for this resource.
102///
103/// The id value uniquely identifies the resource table so that if a resource is used
104/// by n components, there should be n different indices and spaces in use. The map is
105/// therefore entirely unique and fully distinct for each instance's function bindgen.
106///
107/// The second bool is true if it is an imported resource.
108///
109/// For a given resource table id {x}, with resource index {y} the local variables are assumed:
110/// - handleTable{x}
111/// - captureTable{y} (rep to instance map for captured imported tables, only for JS import bindgen, not hybrid)
112/// - captureCnt{y} for assigning capture rep
113///
114/// For component-defined resources:
115/// - finalizationRegistry{x}
116///
117/// handleTable internally will be allocated with { rep: i32, own: bool } entries
118///
119/// In the case of an imported resource tables, in place of "rep" we just store
120/// the direct JS object being referenced, since in JS the object is its own handle.
121///
122#[derive(Clone, Debug, PartialEq)]
123pub struct ResourceTable {
124    /// Whether a resource was imported
125    ///
126    /// This should be tracked because imported types cannot be re-exported uniquely (?)
127    pub imported: bool,
128
129    /// Data related to the actual resource
130    pub data: ResourceData,
131}
132
133/// A mapping of type IDs to the resources that they represent
134pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
135
136#[derive(bon::Builder)]
137#[non_exhaustive]
138pub struct FunctionBindgen<'a> {
139    /// Mapping of resources for types that have corresponding definitions locally
140    pub resource_map: &'a ResourceMap,
141
142    /// Whether current resource borrows need to be deactivated
143    pub clear_resource_borrows: bool,
144
145    /// Set of intrinsics
146    pub intrinsics: &'a mut BTreeSet<Intrinsic>,
147
148    /// Whether to perform valid lifting optimization
149    pub valid_lifting_optimization: bool,
150    /// Whether WIT flags are represented as bigint values.
151    #[builder(default)]
152    pub flags_as_bigint: bool,
153
154    /// Sizes and alignments for sub elements
155    pub sizes: &'a SizeAlign,
156
157    /// Method of error handling
158    pub err: ErrHandling,
159
160    /// Temporary values
161    pub tmp: usize,
162
163    /// Source code of the function
164    pub src: source::Source,
165
166    /// Block storage
167    pub block_storage: Vec<source::Source>,
168
169    /// Blocks of the function
170    pub blocks: Vec<(String, Vec<String>)>,
171
172    /// Parameters of the function
173    pub params: Vec<String>,
174
175    /// Memory variable
176    pub memory: Option<&'a String>,
177
178    /// Realloc function name
179    pub realloc: Option<&'a String>,
180
181    /// Post return function name
182    pub post_return: Option<&'a String>,
183
184    /// Prefix to use when printing tracing information
185    pub tracing_prefix: &'a String,
186
187    /// Whether tracing is enabled
188    pub tracing_enabled: bool,
189
190    /// Whether top-level result errors should be thrown as their raw lifted payload.
191    pub no_component_error_wrapping: bool,
192
193    /// Method if string encoding
194    pub encoding: StringEncoding,
195
196    /// Callee of the function
197    pub callee: &'a str,
198
199    /// Whether the callee is dynamic (i.e. has multiple operands)
200    pub callee_resource_dynamic: bool,
201
202    /// The [`wit_bindgen::Resolve`] containing extracted WIT information
203    pub resolve: &'a Resolve,
204
205    /// Whether the function requires async porcelain
206    ///
207    /// In the case of an import this likely implies the use of JSPI
208    /// and in the case of an export this is simply code generation metadata.
209    pub requires_async_porcelain: bool,
210
211    /// Whether the function is guest async lifted (i.e. WASI P3)
212    pub is_async: bool,
213
214    /// Whether an async export returning a future needs a non-async outer
215    /// function to preserve the future as a distinct awaitable layer.
216    pub wrap_async_future_result: bool,
217
218    /// Interface name
219    pub iface_name: Option<&'a str>,
220
221    /// Whether the callee was transpiled from Wasm to JS (asm.js) and thus needs shimming for i64
222    pub asmjs: bool,
223
224    /// Component state generated from processing a component.
225    ///
226    /// This information is normally accessible via producing/having
227    /// access to a [`wasmtime_environ::component::Component`]), and
228    /// is required for *some* bindgen instructions.
229    ///
230    /// If you are performing bindgen aganist a dummy module, you may omit this field, but if
231    /// processing has been performed on the component, and this state is available at time of generation for
232    /// this function, provide this information (normally found in [`CanonicalOptions`]s)
233    ///
234    pub component_state: Option<FunctionBindgenComponentState>,
235
236    /// Whether the bindgen is being performed for an import
237    /// (false implies generation is being performed for an export)
238    pub(crate) for_import: Option<bool>,
239}
240
241/// Metadata that is derived from processing a component.
242///
243/// This information is often required to perform bindgen completely, *but*
244/// requires component processing which not all downstream bindgen consumers may
245/// perform.
246///
247/// For bindgen consumers that perform generation which requires this information,
248/// the information should be provided.
249///
250#[derive(bon::Builder)]
251#[non_exhaustive]
252pub struct FunctionBindgenComponentState {
253    pub(crate) component_idx: RuntimeComponentInstanceIndex,
254    pub(crate) realloc_fn_idx: Option<RuntimeReallocIndex>,
255    pub(crate) memory_idx: Option<RuntimeMemoryIndex>,
256    pub(crate) callback_fn_idx: Option<RuntimeCallbackIndex>,
257}
258
259/// JS expressions that resolve to or return component state
260#[derive(bon::Builder)]
261#[non_exhaustive]
262pub struct ComponentStateJsExprs {
263    /// JS expression that is a number, e.g. "0"
264    pub(crate) component_idx: String,
265    /// JS expression that is the generated name of the callback, e.g. "callback_1"
266    pub(crate) callback_fn_name: String,
267    /// JS function expression that returns the callback function for this function, e.g. "() => callback_1"
268    pub(crate) get_callback_fn: String,
269    /// JS expression that is the index of the top-level declared memory associated with this function, e.g. "1"
270    pub(crate) memory_idx: String,
271    /// JS function expression that returns the top-level declared memory associated with this function, e.g. "() => memory0"
272    pub(crate) get_memory_fn: String,
273    /// JS function expression that returns the realloc fn relevant to this function, e.g. "() => realloc0"
274    pub(crate) get_realloc_fn: String,
275}
276
277impl FunctionBindgenComponentState {
278    /// Get JS expressions that represent the bindgen state
279    ///
280    /// When certain values are missing either `null` or `() => null` JS expressions are returned
281    ///
282    fn get_js_exprs(&self) -> ComponentStateJsExprs {
283        ComponentStateJsExprs {
284            component_idx: self.component_idx.as_u32().to_string(),
285            callback_fn_name: match self.callback_fn_idx {
286                Some(idx) => format!("callback_{}", idx.as_u32()),
287                None => "null".into(),
288            },
289            get_callback_fn: match self.callback_fn_idx {
290                Some(idx) => format!("() => callback_{}", idx.as_u32()),
291                None => "() => null".into(),
292            },
293            memory_idx: match self.memory_idx {
294                Some(idx) => idx.as_u32().to_string(),
295                None => "null".into(),
296            },
297            get_memory_fn: match self.memory_idx {
298                Some(idx) => format!("() => memory{}", idx.as_u32()),
299                None => "() => null".into(),
300            },
301            get_realloc_fn: match self.realloc_fn_idx {
302                Some(idx) => format!("() => realloc{}", idx.as_u32()),
303                None => "undefined".into(),
304            },
305        }
306    }
307}
308
309/// Returns whether an `ok`/`err` tagged object can unambiguously be interpreted
310/// as an explicitly wrapped result returned by a host import.
311///
312/// Hosts have historically returned the raw success payload, so that
313/// interpretation takes precedence whenever the payload itself can have the
314/// corresponding tag.
315fn unambiguous_result_wrapper_tags(resolve: &Resolve, ty: Option<&Type>) -> (bool, bool) {
316    let Some(ty) = ty else {
317        return (true, true);
318    };
319    let Type::Id(id) = ty else {
320        return (true, true);
321    };
322
323    match &resolve.types[crate::dealias(resolve, *id)].kind {
324        TypeDefKind::Variant(variant) => (
325            !variant.cases.iter().any(|case| case.name == "ok"),
326            !variant.cases.iter().any(|case| case.name == "err"),
327        ),
328        TypeDefKind::Result(_) => (false, false),
329        TypeDefKind::Record(record) => {
330            let unambiguous = !record.fields.iter().any(|field| field.name == "tag");
331            (unambiguous, unambiguous)
332        }
333        TypeDefKind::Flags(flags) => {
334            let unambiguous = !flags.flags.iter().any(|flag| flag.name == "tag");
335            (unambiguous, unambiguous)
336        }
337        TypeDefKind::Option(payload) => unambiguous_result_wrapper_tags(resolve, Some(payload)),
338        _ => (true, true),
339    }
340}
341
342impl FunctionBindgen<'_> {
343    fn tmp(&mut self) -> usize {
344        let ret = self.tmp;
345        self.tmp += 1;
346        ret
347    }
348
349    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
350        self.intrinsics.insert(intrinsic);
351        intrinsic.name().to_string()
352    }
353
354    fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
355    where
356        T: std::fmt::Display,
357    {
358        let clamp = self.intrinsic(Intrinsic::ClampGuest);
359        results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
360    }
361
362    fn load(
363        &mut self,
364        method: &str,
365        offset: ArchitectureSize,
366        operands: &[String],
367        results: &mut Vec<String>,
368    ) {
369        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
370        let Some(memory) = self.memory.as_ref() else {
371            panic!(
372                "unexpectedly missing memory during bindgen for interface [{:?}] (callee {})",
373                self.iface_name, self.callee,
374            );
375        };
376        results.push(format!(
377            "{view}({memory}).{method}({} + {offset}, true)",
378            operands[0],
379            offset = offset.size_wasm32()
380        ));
381    }
382
383    fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
384        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
385        let memory = self.memory.as_ref().unwrap();
386        uwriteln!(
387            self.src,
388            "{view}({memory}).{method}({} + {offset}, {}, true);",
389            operands[1],
390            operands[0],
391            offset = offset.size_wasm32()
392        );
393    }
394
395    /// Write result assignment lines to output
396    ///
397    /// In general this either means writing preambles, for example that look like the following:
398    ///
399    /// ```js
400    /// let ret =
401    /// ```
402    ///
403    /// ```js
404    /// var [ ret0, ret1, ret2 ] =
405    /// ```
406    ///
407    /// ```js
408    /// let ret;
409    /// ```
410    ///
411    /// This function returns as a first tuple parameter a list of
412    /// variables that should be created via let statements beforehand.
413    ///
414    /// # Arguments
415    ///
416    /// * `amt` - number of results
417    /// * `results` - list of variables that will be returned
418    ///
419    fn generate_result_assignment_lhs(
420        &mut self,
421        amt: usize,
422        results: &mut Vec<String>,
423        is_async: bool,
424    ) -> (String, String) {
425        let mut s = String::new();
426        let mut vars_init = String::new();
427        match amt {
428            0 => {
429                // Async functions with no returns still return async code,
430                // which will be used as the initial callback result going into the async driver
431                if is_async {
432                    uwrite!(s, "ret = ")
433                }
434                uwriteln!(vars_init, "let ret;");
435            }
436            1 => {
437                uwrite!(s, "ret = ");
438                results.push("ret".to_string());
439                uwriteln!(vars_init, "let ret;");
440            }
441            n => {
442                uwrite!(s, "[");
443                for i in 0..n {
444                    if i > 0 {
445                        uwrite!(s, ", ");
446                    }
447                    uwrite!(s, "ret{i}");
448                    results.push(format!("ret{i}"));
449                    uwriteln!(vars_init, "let ret;");
450                }
451                uwrite!(s, "] = ");
452            }
453        }
454        (vars_init, s)
455    }
456
457    fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
458        match cast {
459            Bitcast::I32ToF32 => {
460                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
461                format!("{cvt}({op})")
462            }
463            Bitcast::F32ToI32 => {
464                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
465                format!("{cvt}({op})")
466            }
467            Bitcast::I64ToF64 => {
468                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
469                format!("{cvt}({op})")
470            }
471            Bitcast::F64ToI64 => {
472                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
473                format!("{cvt}({op})")
474            }
475            Bitcast::I32ToI64 => format!("BigInt({op})"),
476            Bitcast::I64ToI32 => format!("Number({op})"),
477            Bitcast::I64ToF32 => {
478                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
479                format!("{cvt}(Number({op}))")
480            }
481            Bitcast::F32ToI64 => {
482                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
483                format!("BigInt({cvt}({op}))")
484            }
485            Bitcast::None
486            | Bitcast::P64ToI64
487            | Bitcast::LToI32
488            | Bitcast::I32ToL
489            | Bitcast::LToP
490            | Bitcast::PToL
491            | Bitcast::PToI32
492            | Bitcast::I32ToP => op.to_string(),
493            Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
494            Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
495            Bitcast::Sequence(casts) => {
496                let mut statement = op.to_string();
497                for cast in casts.iter() {
498                    statement = self.bitcast(cast, &statement);
499                }
500                statement
501            }
502        }
503    }
504
505    /// Start the current task
506    ///
507    /// The code generated by this function *may* also start a subtask
508    /// where appropriate.
509    fn start_current_task(&mut self, instr: &Instruction) {
510        let is_async = self.is_async;
511        let is_manual_async = self.requires_async_porcelain;
512        let preserve_future_result = self.wrap_async_future_result;
513        let fn_name = self.callee;
514        let err_handling = self.err.to_js_string();
515
516        let (calling_wasm_export, prefix) = match instr {
517            Instruction::CallWasm { .. } => (true, "_wasm_call_"),
518            Instruction::CallInterface { .. } => (false, "_interface_call_"),
519            _ => unreachable!(
520                "unrecognized instruction triggering start of current task: [{instr:?}]"
521            ),
522        };
523        let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
524            AsyncTaskIntrinsic::CreateNewCurrentTask,
525        ));
526
527        let (component_idx_expr, get_callback_fn_expr, callback_fn_name) =
528            if let Some(state) = &self.component_state {
529                let ComponentStateJsExprs {
530                    component_idx,
531                    callback_fn_name,
532                    get_callback_fn,
533                    ..
534                } = state.get_js_exprs();
535                (component_idx, get_callback_fn, callback_fn_name)
536            } else {
537                ("1".into(), "() => null".into(), "null".into())
538            };
539
540        uwriteln!(
541            self.src,
542            r#"
543              const [task, {prefix}currentTaskID] = {start_current_task_fn}({{
544                  componentIdx: {component_idx_expr},
545                  isAsync: {is_async},
546                  isManualAsync: {is_manual_async},
547                  preserveFutureResult: {preserve_future_result},
548                  entryFnName: '{fn_name}',
549                  getCallbackFn: {get_callback_fn_expr},
550                  callbackFnName: {callback_fn_name},
551                  errHandling: '{err_handling}',
552                  callingWasmExport: {calling_wasm_export},
553              }});
554            "#,
555        );
556    }
557}
558
559impl ManagesIntrinsics for FunctionBindgen<'_> {
560    /// Add an intrinsic, supplying it's name afterwards
561    fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
562        self.intrinsic(intrinsic);
563    }
564}
565
566impl Bindgen for FunctionBindgen<'_> {
567    type Operand = String;
568
569    /// Get the sizes and alignment for a given structure
570    fn sizes(&self) -> &SizeAlign {
571        self.sizes
572    }
573
574    /// Push a new block of code
575    fn push_block(&mut self) {
576        let prev = mem::take(&mut self.src);
577        self.block_storage.push(prev);
578    }
579
580    /// Finish a block of code
581    fn finish_block(&mut self, operands: &mut Vec<String>) {
582        let to_restore = self.block_storage.pop().unwrap();
583        let src = mem::replace(&mut self.src, to_restore);
584        self.blocks.push((src.into(), mem::take(operands)));
585    }
586
587    /// Output the return pointer
588    fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
589        unimplemented!("determining the return pointer for this function is not implemented");
590    }
591
592    /// Check whether a list of the given element type can be represented as a builtni JS type
593    ///
594    /// # Arguments
595    ///
596    /// * `resolve` - the [`Resolve`] that might be used to resolve nested types (i.e. [`Type::TypeId`])
597    /// * `elem_ty` - the [`Type`] of the element stored in the list
598    ///
599    fn is_list_canonical(&self, resolve: &Resolve, elem_ty: &Type) -> bool {
600        js_array_ty(resolve, elem_ty).is_some()
601    }
602
603    fn emit(
604        &mut self,
605        resolve: &Resolve,
606        inst: &Instruction<'_>,
607        operands: &mut Vec<String>,
608        results: &mut Vec<String>,
609    ) {
610        match inst {
611            Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
612
613            Instruction::I32Const { val } => results.push(val.to_string()),
614
615            Instruction::ConstZero { tys } => {
616                for t in tys.iter() {
617                    match t {
618                        WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
619                        WasmType::I32
620                        | WasmType::F32
621                        | WasmType::F64
622                        | WasmType::Pointer
623                        | WasmType::Length => results.push("0".to_string()),
624                    }
625                }
626            }
627
628            Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
629
630            Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
631
632            Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
633
634            Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
635
636            Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
637
638            Instruction::U64FromI64 => {
639                results.push(format!("BigInt.asUintN(64, BigInt({}))", operands[0]))
640            }
641
642            Instruction::S32FromI32 | Instruction::S64FromI64 => {
643                results.push(operands.pop().unwrap())
644            }
645
646            Instruction::I32FromU8 => {
647                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
648                results.push(format!("{conv}({op})", op = operands[0]))
649            }
650
651            Instruction::I32FromS8 => {
652                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
653                results.push(format!("{conv}({op})", op = operands[0]))
654            }
655
656            Instruction::I32FromU16 => {
657                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
658                results.push(format!("{conv}({op})", op = operands[0]))
659            }
660
661            Instruction::I32FromS16 => {
662                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
663                results.push(format!("{conv}({op})", op = operands[0]))
664            }
665
666            Instruction::I32FromU32 => {
667                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
668                results.push(format!("{conv}({op})", op = operands[0]))
669            }
670
671            Instruction::I32FromS32 => {
672                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
673                results.push(format!("{conv}({op})", op = operands[0]))
674            }
675
676            Instruction::I64FromU64 => {
677                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
678                results.push(format!("{conv}({op})", op = operands[0]))
679            }
680
681            Instruction::I64FromS64 => {
682                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
683                results.push(format!("{conv}({op})", op = operands[0]))
684            }
685
686            Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
687                results.push(operands.pop().unwrap())
688            }
689
690            Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
691                results.push(format!("+{}", operands[0]))
692            }
693
694            Instruction::CharFromI32 => {
695                let validate =
696                    self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
697                results.push(format!("{}({})", validate, operands[0]));
698            }
699
700            Instruction::I32FromChar => {
701                let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
702                results.push(format!("{}({})", validate, operands[0]));
703            }
704
705            Instruction::Bitcasts { casts } => {
706                for (cast, op) in casts.iter().zip(operands) {
707                    results.push(self.bitcast(cast, op));
708                }
709            }
710
711            Instruction::BoolFromI32 => {
712                let tmp = self.tmp();
713                uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
714                if self.valid_lifting_optimization {
715                    results.push(format!("!!bool{tmp}"));
716                } else {
717                    let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
718                    results.push(format!(
719                        "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
720                    ));
721                }
722            }
723
724            Instruction::I32FromBool => {
725                results.push(format!("{} ? 1 : 0", operands[0]));
726            }
727
728            Instruction::RecordLower { record, .. } => {
729                // use destructuring field access to get each
730                // field individually.
731                let tmp = self.tmp();
732                let mut expr = "var {".to_string();
733                for (i, field) in record.fields.iter().enumerate() {
734                    if i > 0 {
735                        expr.push_str(", ");
736                    }
737                    let name = format!("v{tmp}_{i}");
738                    expr.push_str(&field.name.to_lower_camel_case());
739                    expr.push_str(": ");
740                    expr.push_str(&name);
741                    results.push(name);
742                }
743                uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
744            }
745
746            Instruction::RecordLift { record, .. } => {
747                // records are represented as plain objects, so we
748                // make a new object and set all the fields with an object
749                // literal.
750                let mut result = "{\n".to_string();
751                for (field, op) in record.fields.iter().zip(operands) {
752                    result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
753                }
754                result.push('}');
755                results.push(result);
756            }
757
758            Instruction::TupleLower { tuple, .. } => {
759                // Tuples are represented as an array, sowe can use
760                // destructuring assignment to lower the tuple into its
761                // components.
762                let tmp = self.tmp();
763                let mut expr = "var [".to_string();
764                for i in 0..tuple.types.len() {
765                    if i > 0 {
766                        expr.push_str(", ");
767                    }
768                    let name = format!("tuple{tmp}_{i}");
769                    expr.push_str(&name);
770                    results.push(name);
771                }
772                uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
773            }
774
775            Instruction::TupleLift { .. } => {
776                // Tuples are represented as an array, so we just shove all
777                // the operands into an array.
778                results.push(format!("[{}]", operands.join(", ")));
779            }
780
781            Instruction::FlagsLower { flags, .. } => {
782                let op0 = &operands[0];
783
784                if self.flags_as_bigint {
785                    let flag_count = flags.flags.len();
786                    uwriteln!(
787                        self.src,
788                        "if (typeof {op0} !== 'bigint') {{
789                            throw new TypeError('flags must be a bigint');
790                        }}
791                        if ({op0} < 0n || ({op0} >> {flag_count}n) !== 0n) {{
792                            throw new TypeError('flags have extraneous bits set');
793                        }}"
794                    );
795
796                    for i in 0..flags.repr().count() {
797                        let tmp = self.tmp();
798                        let name = format!("flags{tmp}");
799                        let shift = i * 32;
800                        uwriteln!(
801                            self.src,
802                            "const {name} = Number(({op0} >> {shift}n) & 0xffff_ffffn);"
803                        );
804                        results.push(name);
805                    }
806                    return;
807                }
808
809                // Generate the result names.
810                for _ in 0..flags.repr().count() {
811                    let tmp = self.tmp();
812                    let name = format!("flags{tmp}");
813                    // Default to 0 so that in the null/undefined case, everything is false by
814                    // default.
815                    uwrite!(self.src, "let {name} = 0;\n");
816                    results.push(name);
817                }
818
819                uwrite!(
820                    self.src,
821                    "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
822                );
823
824                for (i, chunk) in flags.flags.chunks(32).enumerate() {
825                    let result_name = &results[i];
826
827                    uwrite!(self.src, "{result_name} = ");
828                    for (i, flag) in chunk.iter().enumerate() {
829                        if i != 0 {
830                            uwrite!(self.src, " | ");
831                        }
832
833                        let flag = flag.name.to_lower_camel_case();
834                        uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
835                    }
836                    uwrite!(self.src, ";\n");
837                }
838
839                uwrite!(
840                            self.src,
841                            "\
842                    }} else if ({op0} !== null && {op0} !== undefined) {{
843                        throw new TypeError('only an object, undefined or null can be converted to flags');
844                    }}
845                ");
846
847                // We don't need to do anything else for the null/undefined
848                // case, since that's interpreted as everything false, and we
849                // already defaulted everyting to 0.
850            }
851
852            Instruction::FlagsLift { flags, .. } => {
853                let tmp = self.tmp();
854                results.push(format!("flags{tmp}"));
855
856                if let Some(op) = operands.last() {
857                    // We only need an extraneous bits check if the number of flags isn't a multiple
858                    // of 32, because if it is then all the bits are used and there are no
859                    // extraneous bits.
860                    if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
861                        let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
862                        uwriteln!(
863                            self.src,
864                            "if (({op} & {mask}) !== 0) {{
865                                throw new TypeError('flags have extraneous bits set');
866                            }}"
867                        );
868                    }
869                }
870
871                if self.flags_as_bigint {
872                    uwriteln!(self.src, "var flags{tmp} = 0n;");
873                    for (i, op) in operands.iter().enumerate() {
874                        let shift = i * 32;
875                        uwriteln!(self.src, "flags{tmp} |= BigInt({op} >>> 0) << {shift}n;");
876                    }
877                    return;
878                }
879
880                uwriteln!(self.src, "var flags{tmp} = {{");
881
882                for (i, flag) in flags.flags.iter().enumerate() {
883                    let flag = flag.name.to_lower_camel_case();
884                    let op = &operands[i / 32];
885                    let mask: u32 = 1 << (i % 32);
886                    uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
887                }
888
889                uwriteln!(self.src, "}};");
890            }
891
892            Instruction::VariantPayloadName => results.push("e".to_string()),
893
894            Instruction::VariantLower {
895                variant,
896                results: result_types,
897                name,
898                ..
899            } => {
900                let blocks = self
901                    .blocks
902                    .drain(self.blocks.len() - variant.cases.len()..)
903                    .collect::<Vec<_>>();
904                let tmp = self.tmp();
905                let op = &operands[0];
906                uwriteln!(self.src, "var variant{tmp} = {op};");
907
908                for i in 0..result_types.len() {
909                    uwriteln!(self.src, "let variant{tmp}_{i};");
910                    results.push(format!("variant{tmp}_{i}"));
911                }
912
913                let expr_to_match = format!("variant{tmp}.tag");
914
915                uwriteln!(self.src, "switch ({expr_to_match}) {{");
916                for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
917                    uwriteln!(self.src, "case '{}': {{", case.name.as_str());
918                    if case.ty.is_some() {
919                        uwriteln!(self.src, "const e = variant{tmp}.val;");
920                    }
921                    self.src.push_str(&block);
922
923                    for (i, result) in block_results.iter().enumerate() {
924                        uwriteln!(self.src, "variant{tmp}_{i} = {result};");
925                    }
926                    uwriteln!(
927                        self.src,
928                        "break;
929                        }}"
930                    );
931                }
932                let variant_name = name.to_upper_camel_case();
933                uwriteln!(
934                    self.src,
935                    r#"default: {{
936                        throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
937                    }}"#,
938                );
939                uwriteln!(self.src, "}}");
940            }
941
942            Instruction::VariantLift { variant, name, .. } => {
943                let blocks = self
944                    .blocks
945                    .drain(self.blocks.len() - variant.cases.len()..)
946                    .collect::<Vec<_>>();
947
948                let tmp = self.tmp();
949                let op = &operands[0];
950
951                uwriteln!(
952                    self.src,
953                    "let variant{tmp};
954                    switch ({op}) {{"
955                );
956
957                for (i, (case, (block, block_results))) in
958                    variant.cases.iter().zip(blocks).enumerate()
959                {
960                    let tag = case.name.as_str();
961                    uwriteln!(
962                        self.src,
963                        "case {i}: {{
964                            {block}\
965                            variant{tmp} = {{
966                                tag: '{tag}',"
967                    );
968                    if case.ty.is_some() {
969                        assert!(block_results.len() == 1);
970                        uwriteln!(self.src, "   val: {}", block_results[0]);
971                    } else {
972                        assert!(block_results.is_empty());
973                    }
974                    uwriteln!(
975                        self.src,
976                        "   }};
977                        break;
978                        }}"
979                    );
980                }
981                let variant_name = name.to_upper_camel_case();
982                if !self.valid_lifting_optimization {
983                    uwriteln!(
984                        self.src,
985                        "default: {{
986                            throw new TypeError('invalid variant discriminant for {variant_name}');
987                        }}",
988                    );
989                }
990                uwriteln!(self.src, "}}");
991                results.push(format!("variant{tmp}"));
992            }
993
994            Instruction::OptionLower {
995                payload,
996                results: result_types,
997                ..
998            } => {
999                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1000                let (mut some, some_results) = self.blocks.pop().unwrap();
1001                let (mut none, none_results) = self.blocks.pop().unwrap();
1002
1003                let tmp = self.tmp();
1004                let op = &operands[0];
1005                uwriteln!(self.src, "var variant{tmp} = {op};");
1006
1007                for i in 0..result_types.len() {
1008                    uwriteln!(self.src, "let variant{tmp}_{i};");
1009                    results.push(format!("variant{tmp}_{i}"));
1010
1011                    let some_result = &some_results[i];
1012                    let none_result = &none_results[i];
1013                    uwriteln!(some, "variant{tmp}_{i} = {some_result};");
1014                    uwriteln!(none, "variant{tmp}_{i} = {none_result};");
1015                }
1016
1017                if maybe_null(resolve, payload) {
1018                    uwriteln!(
1019                        self.src,
1020                        r#"switch (variant{tmp}.tag) {{
1021                            case 'none': {{
1022                                {none}
1023                                break;
1024                            }}
1025                            case 'some': {{
1026                                const e = variant{tmp}.val;
1027                                {some}
1028                                break;
1029                            }}
1030                            default: {{
1031                                {debug_log_fn}("ERROR: invalid value (expected option as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
1032                                throw new TypeError('invalid variant specified for option');
1033                            }}
1034                        }}"#,
1035                    );
1036                } else {
1037                    uwriteln!(
1038                        self.src,
1039                        "if (variant{tmp} === null || variant{tmp} === undefined) {{
1040                            {none}\
1041                        }} else {{
1042                            const e = variant{tmp};
1043                            {some}\
1044                        }}"
1045                    );
1046                }
1047            }
1048
1049            Instruction::OptionLift { payload, .. } => {
1050                let (some, some_results) = self.blocks.pop().unwrap();
1051                let (none, none_results) = self.blocks.pop().unwrap();
1052                assert!(none_results.is_empty());
1053                assert!(some_results.len() == 1);
1054                let some_result = &some_results[0];
1055
1056                let tmp = self.tmp();
1057                let op = &operands[0];
1058
1059                let (v_none, v_some) = if maybe_null(resolve, payload) {
1060                    (
1061                        "{ tag: 'none' }",
1062                        format!(
1063                            "{{
1064                                tag: 'some',
1065                                val: {some_result}
1066                            }}"
1067                        ),
1068                    )
1069                } else {
1070                    ("undefined", some_result.into())
1071                };
1072
1073                if !self.valid_lifting_optimization {
1074                    uwriteln!(
1075                        self.src,
1076                        "let variant{tmp};
1077                        switch ({op}) {{
1078                            case 0: {{
1079                                {none}\
1080                                variant{tmp} = {v_none};
1081                                break;
1082                            }}
1083                            case 1: {{
1084                                {some}\
1085                                variant{tmp} = {v_some};
1086                                break;
1087                            }}
1088                            default: {{
1089                                throw new TypeError('invalid variant discriminant for option');
1090                            }}
1091                        }}",
1092                    );
1093                } else {
1094                    uwriteln!(
1095                        self.src,
1096                        "let variant{tmp};
1097                        if ({op}) {{
1098                            {some}\
1099                            variant{tmp} = {v_some};
1100                        }} else {{
1101                            {none}\
1102                            variant{tmp} = {v_none};
1103                        }}"
1104                    );
1105                }
1106
1107                results.push(format!("variant{tmp}"));
1108            }
1109
1110            Instruction::ResultLower {
1111                results: result_types,
1112                ..
1113            } => {
1114                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1115                let (mut err, err_results) = self.blocks.pop().unwrap();
1116                let (mut ok, ok_results) = self.blocks.pop().unwrap();
1117
1118                let tmp = self.tmp();
1119                let op = &operands[0];
1120                uwriteln!(self.src, "var variant{tmp} = {op};");
1121
1122                for i in 0..result_types.len() {
1123                    uwriteln!(self.src, "let variant{tmp}_{i};");
1124                    results.push(format!("variant{tmp}_{i}"));
1125
1126                    let ok_result = &ok_results[i];
1127                    let err_result = &err_results[i];
1128                    uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
1129                    uwriteln!(err, "variant{tmp}_{i} = {err_result};");
1130                }
1131
1132                uwriteln!(
1133                    self.src,
1134                    r#"switch (variant{tmp}.tag) {{
1135                        case 'ok': {{
1136                            const e = variant{tmp}.val;
1137                            {ok}
1138                            break;
1139                        }}
1140                        case 'err': {{
1141                            const e = variant{tmp}.val;
1142                            {err}
1143                            break;
1144                        }}
1145                        default: {{
1146                            {debug_log_fn}("ERROR: invalid value (expected result as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
1147                            throw new TypeError('invalid variant specified for result');
1148                        }}
1149                    }}"#,
1150                );
1151            }
1152
1153            Instruction::ResultLift { result, .. } => {
1154                let (err, err_results) = self.blocks.pop().unwrap();
1155                let (ok, ok_results) = self.blocks.pop().unwrap();
1156                let ok_result = if result.ok.is_some() {
1157                    assert_eq!(ok_results.len(), 1);
1158                    ok_results[0].to_string()
1159                } else {
1160                    assert_eq!(ok_results.len(), 0);
1161                    String::from("undefined")
1162                };
1163                let err_result = if result.err.is_some() {
1164                    assert_eq!(err_results.len(), 1);
1165                    err_results[0].to_string()
1166                } else {
1167                    assert_eq!(err_results.len(), 0);
1168                    String::from("undefined")
1169                };
1170                let tmp = self.tmp();
1171                let op0 = &operands[0];
1172
1173                if !self.valid_lifting_optimization {
1174                    uwriteln!(
1175                        self.src,
1176                        "let variant{tmp};
1177                        switch ({op0}) {{
1178                            case 0: {{
1179                                {ok}\
1180                                variant{tmp} = {{
1181                                    tag: 'ok',
1182                                    val: {ok_result}
1183                                }};
1184                                break;
1185                            }}
1186                            case 1: {{
1187                                {err}\
1188                                variant{tmp} = {{
1189                                    tag: 'err',
1190                                    val: {err_result}
1191                                }};
1192                                break;
1193                            }}
1194                            default: {{
1195                                throw new TypeError('invalid variant discriminant for expected');
1196                            }}
1197                        }}",
1198                    );
1199                } else {
1200                    uwriteln!(
1201                        self.src,
1202                        "let variant{tmp};
1203                        if ({op0}) {{
1204                            {err}\
1205                            variant{tmp} = {{
1206                                tag: 'err',
1207                                val: {err_result}
1208                            }};
1209                        }} else {{
1210                            {ok}\
1211                            variant{tmp} = {{
1212                                tag: 'ok',
1213                                val: {ok_result}
1214                            }};
1215                        }}"
1216                    );
1217                }
1218                results.push(format!("variant{tmp}"));
1219            }
1220
1221            Instruction::EnumLower { name, enum_, .. } => {
1222                let tmp = self.tmp();
1223
1224                let op = &operands[0];
1225                uwriteln!(self.src, "var val{tmp} = {op};");
1226
1227                // Declare a variable to hold the result.
1228                uwriteln!(
1229                    self.src,
1230                    "let enum{tmp};
1231                    switch (val{tmp}) {{"
1232                );
1233                for (i, case) in enum_.cases.iter().enumerate() {
1234                    uwriteln!(
1235                        self.src,
1236                        "case '{case}': {{
1237                            enum{tmp} = {i};
1238                            break;
1239                        }}",
1240                        case = case.name
1241                    );
1242                }
1243                uwriteln!(self.src, "default: {{");
1244                if !self.valid_lifting_optimization {
1245                    uwriteln!(
1246                        self.src,
1247                        "if (({op}) instanceof Error) {{
1248                        console.error({op});
1249                    }}"
1250                    );
1251                }
1252                uwriteln!(
1253                            self.src,
1254                            "
1255                            throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
1256                        }}
1257                    }}",
1258                        );
1259
1260                results.push(format!("enum{tmp}"));
1261            }
1262
1263            Instruction::EnumLift { name, enum_, .. } => {
1264                let tmp = self.tmp();
1265
1266                uwriteln!(
1267                    self.src,
1268                    "let enum{tmp};
1269                    switch ({}) {{",
1270                    operands[0]
1271                );
1272                for (i, case) in enum_.cases.iter().enumerate() {
1273                    uwriteln!(
1274                        self.src,
1275                        "case {i}: {{
1276                            enum{tmp} = '{case}';
1277                            break;
1278                        }}",
1279                        case = case.name
1280                    );
1281                }
1282                if !self.valid_lifting_optimization {
1283                    let name = name.to_upper_camel_case();
1284                    uwriteln!(
1285                        self.src,
1286                        "default: {{
1287                            throw new TypeError('invalid discriminant specified for {name}');
1288                        }}",
1289                    );
1290                }
1291                uwriteln!(self.src, "}}");
1292
1293                results.push(format!("enum{tmp}"));
1294            }
1295
1296            // The ListCanonLower instruction is called on async function parameter lowers,
1297            // which are separated in memory by one pointer follow.
1298            //
1299            // We ignore `realloc` in the instruction because it's the name of the *import* from the
1300            // component's side (i.e. `"cabi_realloc"`). Bindings have already set up the appropriate
1301            // realloc for the current component (e.g. `realloc0`) and it is available in the bindgen
1302            // object @ `self.realloc`
1303            //
1304            // Note that this can be called *inside* a "regular" ListCanonLower, for example
1305            // when a list of lists or list of Uint8Arrays is sent.
1306            //
1307            Instruction::ListCanonLower { element, .. } => {
1308                self.intrinsic(Intrinsic::Conversion(
1309                    ConversionIntrinsic::RequireValidNumericPrimitive,
1310                ));
1311                let tmp = self.tmp();
1312                let memory = self.memory.as_ref().unwrap();
1313                let realloc = self.realloc.unwrap();
1314
1315                // Alias the list to a local variable
1316                uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1317                if matches!(element, Type::U8) {
1318                    uwriteln!(
1319                        self.src,
1320                        "var len{tmp} = Array.isArray(val{tmp}) ? val{tmp}.length : val{tmp}.byteLength;"
1321                    );
1322                } else {
1323                    uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1324                }
1325
1326                // Gather metadata about list element
1327                let size = self.sizes.size(element).size_wasm32();
1328                let align = self.sizes.align(element).align_wasm32();
1329
1330                // Allocate space for the type in question
1331                uwriteln!(
1332                    self.src,
1333                    "var ptr{tmp} = {realloc_call}(0, 0, {align}, len{tmp} * {size});",
1334                    realloc_call = if self.is_async {
1335                        format!("await {realloc}")
1336                    } else {
1337                        realloc.to_string()
1338                    },
1339                );
1340
1341                // Determine what methods to use with a DataView when setting the data
1342                let (dataview_set_method, check_fn_intrinsic) =
1343                    gen_dataview_set_and_check_fn_js_for_numeric_type(resolve, element);
1344
1345                // Detect whether we're dealing with a regular array
1346                uwriteln!(
1347                    self.src,
1348                    r#"
1349                        let valData{tmp};
1350                        const valLenBytes{tmp} = len{tmp} * {size};
1351                        if (Array.isArray(val{tmp})) {{
1352                            // Regular array likely containing numbers, write values to memory
1353                            let offset = 0;
1354                            const dv{tmp} = new DataView({memory}.buffer);
1355                            for (const v of val{tmp}) {{
1356                                {check_fn_intrinsic}(v);
1357                                dv{tmp}.{dataview_set_method}(ptr{tmp} + offset, v, true);
1358                                offset += {size};
1359                            }}
1360                        }} else {{
1361                            // TypedArray / ArrayBuffer-like, direct copy
1362                            valData{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, valLenBytes{tmp});
1363                            const out{tmp} = new Uint8Array({memory}.buffer, ptr{tmp}, valLenBytes{tmp});
1364                            out{tmp}.set(valData{tmp});
1365                        }}
1366                    "#,
1367                );
1368
1369                results.push(format!("ptr{tmp}"));
1370                results.push(format!("len{tmp}"));
1371            }
1372
1373            Instruction::ListCanonLift { element, .. } => {
1374                let tmp = self.tmp();
1375                let memory = self.memory.as_ref().unwrap();
1376                let align = self.sizes.align(element).align_wasm32();
1377                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1378                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1379                uwriteln!(
1380                    self.src,
1381                    "if (ptr{tmp} % {align} !== 0) throw new TypeError(`list pointer [${{ptr{tmp}}}] is not aligned to {align}`);
1382                    var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {elem_size}));",
1383                    elem_size = self.sizes.size(element).size_wasm32(),
1384                    array_ty = js_array_ty(resolve, element).unwrap(), // TODO: this is the wrong endianness
1385                );
1386                results.push(format!("result{tmp}"));
1387            }
1388
1389            Instruction::StringLower { .. } => {
1390                // Only Utf8 and Utf16 supported for now
1391                assert!(matches!(
1392                    self.encoding,
1393                    StringEncoding::UTF8 | StringEncoding::UTF16
1394                ));
1395
1396                let (call_prefix, encode_intrinsic) = match (self.encoding, self.is_async) {
1397                    (StringEncoding::UTF16, true) => (
1398                        "await ",
1399                        Intrinsic::String(StringIntrinsic::Utf16EncodeAsync),
1400                    ),
1401                    (StringEncoding::UTF16, false) => {
1402                        ("", Intrinsic::String(StringIntrinsic::Utf16Encode))
1403                    }
1404                    (StringEncoding::UTF8, true) => (
1405                        "await ",
1406                        Intrinsic::String(StringIntrinsic::Utf8EncodeAsync),
1407                    ),
1408                    (StringEncoding::UTF8, false) => {
1409                        ("", Intrinsic::String(StringIntrinsic::Utf8Encode))
1410                    }
1411                    _ => unreachable!("unsupported encoding {}", self.encoding),
1412                };
1413                let encode = self.intrinsic(encode_intrinsic);
1414
1415                let tmp = self.tmp();
1416                let memory = self.memory.as_ref().unwrap();
1417                let str = String::from("cabi_realloc");
1418                let realloc = self.realloc.unwrap_or(&str);
1419                let s = &operands[0];
1420                uwriteln!(
1421                    self.src,
1422                    r#"
1423                      var encodeRes = {call_prefix}{encode}({s}, {realloc}, {memory});
1424                      var ptr{tmp} = encodeRes.ptr;
1425                      var len{tmp} = {encoded_len};
1426                    "#,
1427                    encoded_len = match self.encoding {
1428                        StringEncoding::UTF8 => "encodeRes.len".into(),
1429                        _ => format!("{}.length", s),
1430                    }
1431                );
1432                results.push(format!("ptr{tmp}"));
1433                results.push(format!("len{tmp}"));
1434            }
1435
1436            Instruction::StringLift => {
1437                // Only Utf8 and Utf16 supported for now
1438                assert!(matches!(
1439                    self.encoding,
1440                    StringEncoding::UTF8 | StringEncoding::UTF16
1441                ));
1442                let decoder = self.intrinsic(match self.encoding {
1443                    StringEncoding::UTF16 => Intrinsic::String(StringIntrinsic::Utf16Decoder),
1444                    _ => Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8),
1445                });
1446                let tmp = self.tmp();
1447                let memory = self.memory.as_ref().unwrap();
1448                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1449                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1450                uwriteln!(
1451                    self.src,
1452                    "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
1453                    if self.encoding == StringEncoding::UTF16 {
1454                        "16"
1455                    } else {
1456                        "8"
1457                    }
1458                );
1459                results.push(format!("result{tmp}"));
1460            }
1461
1462            Instruction::ListLower { element, .. } => {
1463                let (body, body_results) = self.blocks.pop().unwrap();
1464                assert!(body_results.is_empty());
1465                let tmp = self.tmp();
1466                let vec = format!("vec{tmp}");
1467                let result = format!("result{tmp}");
1468                let len = format!("len{tmp}");
1469                let size = self.sizes.size(element).size_wasm32();
1470                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1471
1472                // first store our vec-to-lower in a temporary since we'll
1473                // reference it multiple times.
1474                uwriteln!(self.src, "var {vec} = {};", operands[0]);
1475                uwriteln!(self.src, "var {len} = {vec}.length;");
1476
1477                // ... then realloc space for the result in the guest module
1478                let realloc = self.realloc.as_ref().unwrap();
1479                uwriteln!(
1480                    self.src,
1481                    "var {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1482                    realloc_call = if self.is_async {
1483                        format!("await {realloc}")
1484                    } else {
1485                        realloc.to_string()
1486                    },
1487                );
1488
1489                // ... then consume the vector and use the block to lower the
1490                // result.
1491                uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1492                uwriteln!(self.src, "const e = {vec}[i];");
1493                uwrite!(self.src, "const base = {result} + i * {size};");
1494                self.src.push_str(&body);
1495                uwrite!(self.src, "}}\n");
1496
1497                results.push(result);
1498                results.push(len);
1499            }
1500
1501            Instruction::ListLift { element, .. } => {
1502                let (body, body_results) = self.blocks.pop().unwrap();
1503                let tmp = self.tmp();
1504                let size = self.sizes.size(element).size_wasm32();
1505                let align = self.sizes.align(element).align_wasm32();
1506                let len = format!("len{tmp}");
1507                uwriteln!(self.src, "var {len} = {};", operands[1]);
1508                let base = format!("base{tmp}");
1509                uwriteln!(self.src, "var {base} = {};", operands[0]);
1510                uwriteln!(
1511                    self.src,
1512                    "if ({base} % {align} !== 0) throw new TypeError(`list pointer [${{{base}}}] is not aligned to {align}`);"
1513                );
1514                let result = format!("result{tmp}");
1515                uwriteln!(self.src, "var {result} = [];");
1516                results.push(result.clone());
1517
1518                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1519                uwriteln!(self.src, "const base = {base} + i * {size};");
1520                self.src.push_str(&body);
1521                assert_eq!(body_results.len(), 1);
1522                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1523                uwrite!(self.src, "}}\n");
1524            }
1525
1526            Instruction::MapLower { key, value, .. } => {
1527                let (body, body_results) = self.blocks.pop().unwrap();
1528                assert!(body_results.is_empty());
1529
1530                let tmp = self.tmp();
1531                let map = format!("map{tmp}");
1532                let entries = format!("entries{tmp}");
1533                let result = format!("result{tmp}");
1534                let len = format!("len{tmp}");
1535                let entry = self.sizes.record([*key, *value]);
1536                let size = entry.size.size_wasm32();
1537                let align = ArchitectureSize::from(entry.align).size_wasm32();
1538
1539                uwriteln!(self.src, "const {map} = {};", operands[0]);
1540                uwriteln!(
1541                    self.src,
1542                    "if (!({map} instanceof Map)) throw new TypeError('expected a Map');"
1543                );
1544                uwriteln!(self.src, "const {entries} = {map}.entries();");
1545                uwriteln!(self.src, "const {len} = {map}.size;");
1546
1547                let realloc = self.realloc.as_ref().unwrap();
1548                uwriteln!(
1549                    self.src,
1550                    "const {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1551                    realloc_call = if self.is_async {
1552                        format!("await {realloc}")
1553                    } else {
1554                        realloc.to_string()
1555                    },
1556                );
1557
1558                uwriteln!(self.src, "let i = 0;");
1559                uwriteln!(self.src, "for (const [key, value] of {entries}) {{");
1560                uwriteln!(self.src, "const base = {result} + i * {size};");
1561                self.src.push_str(&body);
1562                uwriteln!(self.src, "i++;");
1563                uwrite!(self.src, "}}\n");
1564
1565                results.push(result);
1566                results.push(len);
1567            }
1568
1569            Instruction::MapLift { key, value, .. } => {
1570                let (body, body_results) = self.blocks.pop().unwrap();
1571                assert_eq!(body_results.len(), 2);
1572
1573                let tmp = self.tmp();
1574                let entry_size = self.sizes.record([*key, *value]).size.size_wasm32();
1575                let len = format!("len{tmp}");
1576                uwriteln!(self.src, "const {len} = {};", operands[1]);
1577                let base = format!("base{tmp}");
1578                uwriteln!(self.src, "const {base} = {};", operands[0]);
1579                let result = format!("result{tmp}");
1580                uwriteln!(self.src, "const {result} = new Map();");
1581                results.push(result.clone());
1582
1583                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1584                uwriteln!(self.src, "const base = {base} + i * {entry_size};");
1585                self.src.push_str(&body);
1586                uwriteln!(
1587                    self.src,
1588                    "{result}.set({}, {});",
1589                    body_results[0],
1590                    body_results[1]
1591                );
1592                uwrite!(self.src, "}}\n");
1593            }
1594
1595            Instruction::FixedLengthListLower { size, .. } => {
1596                let tmp = self.tmp();
1597                let array = format!("array{tmp}");
1598                uwriteln!(self.src, "const {array} = {};", operands[0]);
1599                for i in 0..*size {
1600                    results.push(format!("{array}[{i}]"));
1601                }
1602            }
1603
1604            Instruction::FixedLengthListLift { .. } => {
1605                let tmp = self.tmp();
1606                let result = format!("result{tmp}");
1607                uwriteln!(self.src, "const {result} = [{}];", operands.join(", "));
1608                results.push(result);
1609            }
1610
1611            Instruction::FixedLengthListLowerToMemory {
1612                element, size: len, ..
1613            } => {
1614                let (body, body_results) = self.blocks.pop().unwrap();
1615                assert!(body_results.is_empty());
1616
1617                let tmp = self.tmp();
1618                let array = format!("array{tmp}");
1619                uwriteln!(self.src, "const {array} = {};", operands[0]);
1620                let addr = format!("addr{tmp}");
1621                uwriteln!(self.src, "const {addr} = {};", operands[1]);
1622                let elem_size = self.sizes.size(element).size_wasm32();
1623
1624                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1625                uwriteln!(self.src, "const e = {array}[i];");
1626                uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1627                self.src.push_str(&body);
1628                uwrite!(self.src, "}}\n");
1629            }
1630
1631            Instruction::FixedLengthListLiftFromMemory {
1632                element, size: len, ..
1633            } => {
1634                let (body, body_results) = self.blocks.pop().unwrap();
1635                assert_eq!(body_results.len(), 1);
1636
1637                let tmp = self.tmp();
1638                let addr = format!("addr{tmp}");
1639                uwriteln!(self.src, "const {addr} = {};", operands[0]);
1640                let elem_size = self.sizes.size(element).size_wasm32();
1641                let result = format!("result{tmp}");
1642                uwriteln!(self.src, "const {result} = [];");
1643                results.push(result.clone());
1644
1645                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1646                uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1647                self.src.push_str(&body);
1648                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1649                uwrite!(self.src, "}}\n");
1650            }
1651
1652            Instruction::IterElem { .. } => results.push("e".to_string()),
1653
1654            Instruction::IterMapKey { .. } => results.push("key".to_string()),
1655
1656            Instruction::IterMapValue { .. } => results.push("value".to_string()),
1657
1658            Instruction::IterBasePointer => results.push("base".to_string()),
1659
1660            Instruction::CallWasm { name, sig } => {
1661                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1662                let get_component_state = self.intrinsic(Intrinsic::Component(
1663                    ComponentIntrinsic::GetOrCreateAsyncState,
1664                ));
1665                let component_idx_expr = self
1666                    .component_state
1667                    .as_ref()
1668                    .map(|state| state.get_js_exprs().component_idx)
1669                    .unwrap_or_else(|| "-1".into());
1670                let has_post_return = self.post_return.is_some();
1671                let is_async = self.is_async;
1672                uwriteln!(
1673                    self.src,
1674                    "{debug_log_fn}('{prefix} [Instruction::CallWasm] enter', {{
1675                         funcName: '{name}',
1676                         paramCount: {param_count},
1677                         async: {is_async},
1678                         postReturn: {has_post_return},
1679                      }});",
1680                    param_count = sig.params.len(),
1681                    prefix = self.tracing_prefix,
1682                );
1683
1684                // Write out whether the callee was host provided
1685                // (if we're calling into wasm then we know it was not)
1686                uwriteln!(self.src, "const hostProvided = false;");
1687
1688                // Argument validation and lowering happen before this instruction. Only
1689                // disable the instance once execution is about to enter the component.
1690                uwriteln!(
1691                    self.src,
1692                    "{get_component_state}({component_idx_expr}).throwIfTrapped();"
1693                );
1694
1695                // Inject machinery for starting a 'current' task
1696                // (this will define the 'task' variable)
1697                self.start_current_task(inst);
1698
1699                // TODO: trap if this component is already on the call stack (re-entrancy)
1700
1701                // TODO(threads): start a thread
1702                // TODO(threads): Task#enter needs to be called with the thread that is executing (inside thread_func)
1703                // TODO(threads): thread_func will contain the actual call rather than attempting to execute immediately
1704
1705                // If we're dealing with an async task, do explicit task enter
1706                if self.is_async || self.requires_async_porcelain {
1707                    uwriteln!(
1708                        self.src,
1709                        r#"
1710                        const started = await task.enter();
1711                        if (!started) {{
1712                            {debug_log_fn}('[Instruction::AsyncTaskReturn] failed to enter task', {{
1713                                taskID: task.id(),
1714                                subtaskID: task.currentSubtask()?.id(),
1715                            }});
1716                            throw new Error("failed to enter task");
1717                        }}
1718                        "#,
1719                    );
1720                } else {
1721                    uwriteln!(self.src, "const started = task.enterSync();",);
1722                }
1723
1724                // Set up resource scope tracking, if we're in a resource call
1725                if self.callee_resource_dynamic {
1726                    let resource_borrows =
1727                        self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceCallBorrows));
1728                    let handle_tables = self.intrinsic(Intrinsic::HandleTables);
1729                    let scope_id = self.intrinsic(Intrinsic::ScopeId);
1730                    uwriteln!(
1731                        self.src,
1732                        r#"
1733                          {scope_id}++;
1734                          task.registerOnResolveHandler(() => {{
1735                              {scope_id}--;
1736                              for (const {{ rid, handle }} of {resource_borrows}) {{
1737                                  const storedScopeId = {handle_tables}[rid][handle << 1]
1738                                  if (storedScopeId === {scope_id}) {{
1739                                      throw new TypeError('borrows not dropped for resource call');
1740                                  }}
1741                              }}
1742                              {resource_borrows} = [];
1743                          }}
1744
1745                          }});
1746                        "#
1747                    );
1748                    uwriteln!(self.src, "{scope_id}++;");
1749                }
1750
1751                // Set task memory index and memory object
1752                let (memory_idx_expr, get_memory_fn_expr) =
1753                    if let Some(state) = &self.component_state {
1754                        let ComponentStateJsExprs {
1755                            memory_idx,
1756                            get_memory_fn,
1757                            ..
1758                        } = state.get_js_exprs();
1759                        (memory_idx, get_memory_fn)
1760                    } else {
1761                        ("null".into(), "() => null".into())
1762                    };
1763                uwriteln!(
1764                    self.src,
1765                    r#"
1766                      if ({memory_idx_expr} !== null) {{
1767                          task.setReturnMemoryIdx({memory_idx_expr});
1768                          task.setReturnMemory({get_memory_fn_expr}());
1769                      }}
1770                    "#
1771                );
1772
1773                // Output result binding preamble (e.g. 'var ret =', 'var [ ret0, ret1] = exports...() ')
1774                // along with the code to perofrm the call
1775                let sig_results_length = sig.results.len();
1776                let (vars_init, assignment_lhs) =
1777                    self.generate_result_assignment_lhs(sig_results_length, results, is_async);
1778
1779                let (call_prefix, call_wrapper, call_err_cleanup) =
1780                    if self.requires_async_porcelain | self.is_async {
1781                        (
1782                            "await ",
1783                            self.intrinsic(Intrinsic::WithGlobalCurrentTaskMetaFnAsync),
1784                            format!(
1785                                r#"
1786                              {debug_log_fn}('[Instruction::CallWasm] error during async call', {{
1787                                  taskID: task.id(),
1788                                  err,
1789                              }});
1790                              {get_component_state}({component_idx_expr}).markTrapped(err);
1791                              task.setErrored(err);
1792                              task.reject(err);
1793                              task.exit();
1794                              return task.completionPromise();
1795                            "#
1796                            ),
1797                        )
1798                    } else {
1799                        (
1800                            "",
1801                            self.intrinsic(Intrinsic::WithGlobalCurrentTaskMetaFn),
1802                            format!(
1803                                r#"
1804                              {debug_log_fn}('[Instruction::CallWasm] error during sync call', {{
1805                                  taskID: task.id(),
1806                                  err,
1807                              }});
1808                              {get_component_state}({component_idx_expr}).markTrapped(err);
1809                              task.setErrored(err);
1810                              task.reject(err);
1811                              task.exit();
1812                              throw err;
1813                            "#
1814                            ),
1815                        )
1816                    };
1817
1818                let args = if self.asmjs {
1819                    let split_i64 =
1820                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::SplitBigInt64));
1821
1822                    let mut args = Vec::new();
1823                    for (i, op) in operands
1824                        .drain(operands.len() - sig.params.len()..)
1825                        .enumerate()
1826                    {
1827                        if matches!(sig.params[i], WasmType::I64) {
1828                            args.push(format!("...({split_i64}({op}))"));
1829                        } else {
1830                            args.push(op);
1831                        }
1832                    }
1833                    args
1834                } else {
1835                    mem::take(operands)
1836                };
1837
1838                let mut callee_invoke = format!(
1839                    "{callee}({args})",
1840                    callee = self.callee,
1841                    args = args.join(", ")
1842                );
1843
1844                if self.asmjs {
1845                    // wasm2js does not support multivalue return
1846                    // if/when it does, this will need changing.
1847                    assert!(sig.results.len() <= 1);
1848                    // same with async(?)
1849                    assert!(!self.requires_async_porcelain && !self.is_async);
1850
1851                    if sig.results.len() == 1 && matches!(sig.results[0], WasmType::I64) {
1852                        let merge_i64 = self
1853                            .intrinsic(Intrinsic::Conversion(ConversionIntrinsic::MergeBigInt64));
1854                        callee_invoke =
1855                            format!("{merge_i64}({callee_invoke}, task.tmpRetI64HighBits)");
1856                    }
1857                }
1858
1859                uwriteln!(
1860                    self.src,
1861                    r#"
1862                      {vars_init}
1863                      try {{
1864                           {assignment_lhs} {call_prefix} {call_wrapper}({{
1865                               taskID: task.id(),
1866                               componentIdx: task.componentIdx(),
1867                               fn: () => {callee_invoke},
1868                            }});
1869                      }} catch (err) {{
1870                          {call_err_cleanup}
1871                      }}
1872                    "#,
1873                );
1874
1875                if self.tracing_enabled {
1876                    let prefix = self.tracing_prefix;
1877                    let to_result_string =
1878                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1879                    uwriteln!(
1880                        self.src,
1881                        "console.error(`{prefix} return {}`);",
1882                        if sig_results_length > 0 || !results.is_empty() {
1883                            format!("result=${{{to_result_string}(ret)}}")
1884                        } else {
1885                            "".to_string()
1886                        }
1887                    );
1888                }
1889            }
1890
1891            // Call to an imported interface (normally provided by the host)
1892            Instruction::CallInterface { func, async_ } => {
1893                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1894                let get_component_state = self.intrinsic(Intrinsic::Component(
1895                    ComponentIntrinsic::GetOrCreateAsyncState,
1896                ));
1897                let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
1898                    AsyncTaskIntrinsic::CreateNewCurrentTask,
1899                ));
1900                let current_task_get_fn =
1901                    self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1902
1903                // At first, use the global current task metadata, in case we are executing from
1904                // inside a with-global-current-task wrapper
1905                let get_global_current_task_meta_fn =
1906                    self.intrinsic(Intrinsic::GetGlobalCurrentTaskMetaFn);
1907
1908                uwriteln!(
1909                    self.src,
1910                    "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ enter)');",
1911                    prefix = self.tracing_prefix,
1912                    async_ = async_.then_some("async").unwrap_or("sync"),
1913                );
1914
1915                // Determine the callee function and arguments
1916                let (callee_fn_js, callee_args_js) = if self.callee_resource_dynamic {
1917                    (
1918                        format!("{}.{}", operands[0], self.callee),
1919                        operands[1..].join(", "),
1920                    )
1921                } else {
1922                    (self.callee.into(), operands.join(", "))
1923                };
1924
1925                uwriteln!(self.src, "const hostProvided = true;");
1926
1927                // Set task memory index and memory object
1928                let (component_idx_expr, callback_fn_name_expr, get_callback_fn_expr) =
1929                    if let Some(state) = &self.component_state {
1930                        let ComponentStateJsExprs {
1931                            component_idx,
1932                            callback_fn_name,
1933                            get_callback_fn,
1934                            ..
1935                        } = state.get_js_exprs();
1936                        (component_idx, callback_fn_name, get_callback_fn)
1937                    } else {
1938                        ("-1".into(), "null".into(), "() => null".into())
1939                    };
1940
1941                // Start the necessary subtasks and/or host task
1942                //
1943                // We must create a subtask in the case of an async host import.
1944                //
1945                // If there's no parent task, we're not executing in a subtask situation,
1946                // so we can just create the new task and immediately continue execution.
1947                //
1948                // If there *is* a parent task, then we are likely about to create new task that
1949                // matches/belongs to an existing subtask in the parent task.
1950                //
1951                // If we're dealing with a function that has been marked as a host import, then
1952                // we expect that `Trampoline::LowerImport` and relevant intrinsics were called before
1953                // this, and a subtask has been set up.
1954                //
1955                uwriteln!(
1956                    self.src,
1957                    r#"
1958                    let parentTask;
1959                    let task;
1960                    let subtask;
1961
1962                    const createTask = () => {{
1963                        const results = {start_current_task_fn}({{
1964                            componentIdx: -1,
1965                            isAsync: {is_async},
1966                            entryFnName: '{fn_name}',
1967                            getCallbackFn: {get_callback_fn_expr},
1968                            callbackFnName: {callback_fn_name_expr},
1969                            errHandling: '{err_handling}',
1970                            callingWasmExport: false,
1971                        }});
1972                        task = results[0];
1973                    }};
1974
1975                    taskCreation: {{
1976                        parentTask = {current_task_get_fn}(
1977                            {component_idx_expr},
1978                            {get_global_current_task_meta_fn}({component_idx_expr})?.taskID,
1979                        )?.task;
1980
1981                        if (!parentTask) {{
1982                            createTask();
1983                            break taskCreation;
1984                        }}
1985
1986                        createTask();
1987
1988                        if (hostProvided) {{
1989                            subtask = parentTask.getLatestSubtask();
1990                            if (!subtask) {{
1991                                throw new Error(`Missing subtask (in parent task [${{parentTask.id()}}]) for host import, has the import been lowered? (ensure asyncImports are set properly)`);
1992                            }}
1993                            task.setParentSubtask(subtask);
1994                        }}
1995                    }}
1996                    "#,
1997                    is_async = self.is_async,
1998                    fn_name = self.callee,
1999                    err_handling = self.err.to_js_string(),
2000                );
2001
2002                let is_async = self.requires_async_porcelain || *async_;
2003
2004                // If we're async then we *know* that there is a result, even if the functoin doesn't have one
2005                // at the CM level -- async functions always return
2006                let fn_wasm_result_count = if func.result.is_none() { 0 } else { 1 };
2007
2008                // If the task is async, do an explicit wait for backpressure before the call execution
2009                if is_async {
2010                    uwriteln!(
2011                        self.src,
2012                        r#"
2013                        const started = await task.enter({{ isHost: hostProvided }});
2014                        if (!started) {{
2015                            {debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
2016                                taskID: task.id(),
2017                                subtaskID: task.getParentSubtask()?.id(),
2018                            }});
2019                            throw new Error("failed to enter task");
2020                        }}
2021                        "#,
2022                    );
2023                } else {
2024                    uwriteln!(self.src, "const started = task.enterSync();",);
2025                }
2026
2027                // Build the JS expression that calls the callee
2028                let (call_prefix, call_wrapper, call_err_cleanup) = if is_async
2029                    || self.requires_async_porcelain
2030                {
2031                    (
2032                        "await ",
2033                        self.intrinsic(Intrinsic::WithGlobalCurrentTaskMetaFnAsync),
2034                        format!(
2035                            r#"
2036                              {debug_log_fn}('[Instruction::CallInterface] error during async call', {{
2037                                  taskID: task.id(),
2038                                  subtaskID: task.getParentSubtask()?.id(),
2039                                  err,
2040                              }});
2041                              {get_component_state}({component_idx_expr}).markTrapped(err);
2042                              task.setErrored(err);
2043                              task.reject(err);
2044                              task.exit();
2045                              return task.completionPromise();
2046                            "#
2047                        ),
2048                    )
2049                } else {
2050                    (
2051                        "",
2052                        self.intrinsic(Intrinsic::WithGlobalCurrentTaskMetaFn),
2053                        format!(
2054                            r#"
2055                              {debug_log_fn}('[Instruction::CallInterface] error during sync call', {{
2056                                  taskID: task.id(),
2057                                  subtaskID: task.getParentSubtask()?.id(),
2058                                  err,
2059                              }});
2060                              {get_component_state}({component_idx_expr}).markTrapped(err);
2061                              task.setErrored(err);
2062                              task.reject(err);
2063                              task.exit();
2064                              throw err;
2065                            "#
2066                        ),
2067                    )
2068                };
2069
2070                let call = format!(
2071                    r#"{call_prefix} {call_wrapper}({{
2072                              componentIdx: task.componentIdx(),
2073                              taskID: task.id(),
2074                              fn: () => {callee_fn_js}({callee_args_js}),
2075                          }})
2076                        "#,
2077                );
2078
2079                match self.err {
2080                    // If configured to do *no* error handling at all or throw
2081                    // error objects directly, we can simply perform the call
2082                    ErrHandling::None | ErrHandling::ThrowResultErr => {
2083                        let (vars_init, assignment_lhs) = self.generate_result_assignment_lhs(
2084                            fn_wasm_result_count,
2085                            results,
2086                            is_async,
2087                        );
2088                        uwriteln!(
2089                            self.src,
2090                            r#"
2091                              {vars_init}
2092                              try {{
2093                                 {assignment_lhs}{call};
2094                              }} catch (err) {{
2095                                  {call_err_cleanup}
2096                              }}
2097                            "#
2098                        );
2099                    }
2100                    // If configured to force all thrown errors into result objects,
2101                    // then we add a try/catch around the call
2102                    ErrHandling::ResultCatchHandler => {
2103                        let (wrapped_ok, wrapped_err) = unambiguous_result_wrapper_tags(
2104                            self.resolve,
2105                            get_thrown_type(self.resolve, func.result).unwrap().0,
2106                        );
2107                        let host_ret = format!("hostRet{}", self.tmp());
2108                        let wrapper_test = match (wrapped_ok, wrapped_err) {
2109                            (true, true) => {
2110                                format!("({host_ret}.tag === 'ok' || {host_ret}.tag === 'err')")
2111                            }
2112                            (true, false) => format!("{host_ret}.tag === 'ok'"),
2113                            (false, true) => format!("{host_ret}.tag === 'err'"),
2114                            (false, false) => "false".to_string(),
2115                        };
2116                        // result<_, string> allows JS error coercion only, while
2117                        // any other result type will trap for arbitrary JS errors.
2118                        let err_payload = if let (_, Some(Type::Id(err_ty))) =
2119                            get_thrown_type(self.resolve, func.result).unwrap()
2120                        {
2121                            match &self.resolve.types[*err_ty].kind {
2122                                TypeDefKind::Type(Type::String) => {
2123                                    self.intrinsic(Intrinsic::GetErrorPayloadString)
2124                                }
2125                                _ => self.intrinsic(Intrinsic::GetErrorPayload),
2126                            }
2127                        } else {
2128                            self.intrinsic(Intrinsic::GetErrorPayload)
2129                        };
2130                        uwriteln!(
2131                            self.src,
2132                            r#"
2133                            let ret;
2134                            try {{
2135                                const {host_ret} = {call};
2136                                ret = {host_ret} !== null && typeof {host_ret} === 'object' && {wrapper_test}
2137                                    ? {host_ret}
2138                                    : {{ tag: 'ok', val: {host_ret} }};
2139                            }} catch (e) {{
2140                                if ({get_component_state}({component_idx_expr}).markTrapped(e)) {{ throw e; }}
2141                                ret = {{ tag: 'err', val: {err_payload}(e) }};
2142                            }}
2143                            "#,
2144                        );
2145                        results.push("ret".to_string());
2146                    }
2147                }
2148
2149                if self.tracing_enabled {
2150                    let prefix = self.tracing_prefix;
2151                    let to_result_string =
2152                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
2153                    uwriteln!(
2154                        self.src,
2155                        "console.error(`{prefix} return {}`);",
2156                        if fn_wasm_result_count > 0 || !results.is_empty() {
2157                            format!("result=${{{to_result_string}(ret)}}")
2158                        } else {
2159                            "".to_string()
2160                        }
2161                    );
2162                }
2163
2164                // TODO: if it was an async call, we may not be able to clear the borrows yet.
2165                // save them to the task/ensure they are added to the task's list of borrows?
2166                //
2167                // TODO: if there is a subtask, we must not clear borrows until subtask.deliverReturn
2168                // is called.
2169
2170                // After a high level call, we need to deactivate the component resource borrows.
2171                if self.clear_resource_borrows {
2172                    let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2173                    let cur_resource_borrows =
2174                        self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
2175                    uwriteln!(
2176                        self.src,
2177                        "for (const entry of {cur_resource_borrows}) {{
2178                            const rsc = entry.rsc ?? entry;
2179                            if (entry.drop) {{
2180                                if (rsc[{symbol_resource_handle}]) {{
2181                                    entry.drop(rsc[{symbol_resource_handle}]);
2182                                }}
2183                            }}
2184                            rsc[{symbol_resource_handle}] = undefined;
2185                        }}
2186                        {cur_resource_borrows} = [];"
2187                    );
2188                    self.clear_resource_borrows = false;
2189                }
2190            }
2191
2192            Instruction::Return {
2193                func,
2194                amt: stack_value_count,
2195            } => {
2196                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2197                uwriteln!(
2198                    self.src,
2199                    "{debug_log_fn}('{prefix} [Instruction::Return]', {{
2200                         funcName: '{func_name}',
2201                         paramCount: {stack_value_count},
2202                         async: {is_async},
2203                         postReturn: {post_return_present}
2204                      }});",
2205                    func_name = func.name,
2206                    post_return_present = self.post_return.is_some(),
2207                    is_async = self.is_async,
2208                    prefix = self.tracing_prefix,
2209                );
2210
2211                // Get the component idx expr
2212                let component_idx_expr = if let Some(state) = &self.component_state {
2213                    let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
2214                    component_idx
2215                } else {
2216                    "-1".into()
2217                };
2218
2219                // Build the post return functionality
2220                // to clean up tasks and possibly return values
2221                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2222                    ComponentIntrinsic::GetOrCreateAsyncState,
2223                ));
2224                let gen_post_return_js =
2225                    |(post_return_call, ret_stmt): (String, Option<String>)| {
2226                        format!(
2227                            r#"
2228                        let cstate = {get_or_create_async_state_fn}({component_idx_expr});
2229                        cstate.mayLeave = false;
2230                        {post_return_call}
2231                        cstate.mayLeave = true;
2232                        task.exit();
2233                        {ret_stmt}
2234                            "#,
2235                            ret_stmt = ret_stmt.unwrap_or_default(),
2236                        )
2237                    };
2238
2239                assert!(!self.is_async, "async functions should use AsyncTaskReturn");
2240
2241                // Depending how many values are on the stack after returning, we must execute differently.
2242                //
2243                // In particular, if this function is async (distinct from whether async porcelain was necessary or not),
2244                // rather than simply executing the function we must return (or block for) the promise that was created
2245                // for the task.
2246                match stack_value_count {
2247                    // (sync) Handle no result case
2248                    0 => {
2249                        uwriteln!(self.src, "task.resolve([ret]);");
2250                        if let Some(f) = &self.post_return {
2251                            uwriteln!(
2252                                self.src,
2253                                "{post_return_js}",
2254                                post_return_js = gen_post_return_js((format!("{f}();"), None)),
2255                            );
2256                        } else {
2257                            uwriteln!(self.src, "task.exit();");
2258                        }
2259                    }
2260
2261                    // (sync) Handle single `result<t>` case
2262                    1 if self.err == ErrHandling::ThrowResultErr => {
2263                        let op = &operands[0];
2264                        let throw_err = if self.no_component_error_wrapping {
2265                            "throw retCopy.val;".to_string()
2266                        } else {
2267                            let component_err = self.intrinsic(Intrinsic::ComponentError);
2268                            format!("throw new {component_err}(retCopy.val);")
2269                        };
2270
2271                        uwriteln!(self.src, "const retCopy = {op};");
2272                        uwriteln!(self.src, "task.resolve([retCopy.val]);");
2273
2274                        if let Some(f) = &self.post_return {
2275                            uwriteln!(
2276                                self.src,
2277                                "{}",
2278                                gen_post_return_js((format!("{f}(ret);"), None))
2279                            );
2280                        } else {
2281                            uwriteln!(self.src, "task.exit();");
2282                        }
2283
2284                        uwriteln!(
2285                            self.src,
2286                            r#"
2287                              if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
2288                                  {throw_err}
2289                              }}
2290                              return retCopy.val;
2291                            "#
2292                        );
2293                    }
2294
2295                    // (sync) Handle all other cases (including single parameter non-result<t>)
2296                    stack_value_count => {
2297                        let ret_val = match stack_value_count {
2298                            0 => unreachable!(
2299                                "unexpectedly zero return values for synchronous return"
2300                            ),
2301                            1 => operands[0].to_string(),
2302                            _ => format!("[{}]", operands.join(", ")),
2303                        };
2304                        // The surrounding FutureValue represents the future returned by the WIT
2305                        // function, so the manual async transport must consume the lifted future
2306                        // before boxing its payload for FutureValue's start operation.
2307                        let return_val = if self.wrap_async_future_result {
2308                            format!("{{ value: await {ret_val} }}")
2309                        } else {
2310                            ret_val.clone()
2311                        };
2312
2313                        uwriteln!(self.src, "task.resolve([{ret_val}]);");
2314
2315                        // Handle the post return if necessary
2316                        if let Some(post_return_fn) = self.post_return {
2317                            // In the case there is a post return function, we'll want to copy the value
2318                            // then perform the post return before leaving
2319
2320                            // Write out the assignment for the given return value
2321                            uwriteln!(self.src, "const retCopy = {ret_val};");
2322                            let post_return_val = if self.wrap_async_future_result {
2323                                "{ value: await retCopy }"
2324                            } else {
2325                                "retCopy"
2326                            };
2327
2328                            // Generate the JS that should perform the post return w/ the result
2329                            // and pass a copy fo the result to the actual caller
2330                            let post_return_js = gen_post_return_js((
2331                                format!("{post_return_fn}(ret);"),
2332                                Some([format!("return {post_return_val};")].join("\n")),
2333                            ));
2334                            uwriteln!(self.src, "{post_return_js}");
2335                        } else {
2336                            uwriteln!(self.src, "task.exit();");
2337                            uwriteln!(self.src, "return {return_val};")
2338                        }
2339                    }
2340                }
2341            }
2342
2343            Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
2344
2345            Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
2346
2347            Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
2348
2349            Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
2350
2351            Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
2352
2353            Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
2354
2355            Instruction::I32Load16U { offset } => {
2356                self.load("getUint16", *offset, operands, results)
2357            }
2358
2359            Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
2360
2361            Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
2362
2363            Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
2364
2365            Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
2366
2367            Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
2368
2369            Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
2370
2371            Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
2372
2373            Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
2374
2375            Instruction::LengthLoad { offset } => {
2376                self.load("getUint32", *offset, operands, results)
2377            }
2378
2379            Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
2380
2381            Instruction::PointerLoad { offset } => {
2382                self.load("getUint32", *offset, operands, results)
2383            }
2384
2385            Instruction::Malloc { size, align, .. } => {
2386                let tmp = self.tmp();
2387                let realloc = self.realloc.as_ref().unwrap();
2388                let ptr = format!("ptr{tmp}");
2389                uwriteln!(
2390                    self.src,
2391                    "var {ptr} = {realloc_call}(0, 0, {align}, {size});",
2392                    align = align.align_wasm32(),
2393                    realloc_call = if self.is_async {
2394                        format!("await {realloc}")
2395                    } else {
2396                        realloc.to_string()
2397                    },
2398                    size = size.size_wasm32()
2399                );
2400                results.push(ptr);
2401            }
2402
2403            Instruction::HandleLift { handle, .. } => {
2404                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2405                let resource_ty = &crate::dealias(self.resolve, *ty);
2406                let ResourceTable { imported, data } = &self.resource_map[resource_ty];
2407
2408                let is_own = matches!(handle, Handle::Own(_));
2409                let rsc = format!("rsc{}", self.tmp());
2410                let handle = format!("handle{}", self.tmp());
2411                uwriteln!(self.src, "var {handle} = {};", &operands[0]);
2412
2413                match data {
2414                    ResourceData::Host {
2415                        tid,
2416                        rid,
2417                        local_name,
2418                        dtor_name,
2419                    } => {
2420                        let tid = tid.as_u32();
2421                        let rid = rid.as_u32();
2422                        let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2423                        let rsc_table_remove = self
2424                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
2425                        let rsc_flag = self
2426                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
2427                        if !imported {
2428                            let symbol_resource_handle =
2429                                self.intrinsic(Intrinsic::SymbolResourceHandle);
2430
2431                            uwriteln!(
2432                                self.src,
2433                                "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);"
2434                            );
2435
2436                            if is_own {
2437                                // Sending an own handle out to JS as a return value - set up finalizer and disposal.
2438                                let empty_func = self
2439                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2440                                uwriteln!(self.src,
2441                                            "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2442                                    finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
2443                                let dtor_call = dtor_name
2444                                    .as_ref()
2445                                    .map(|dtor| format!("{dtor}(handleEntry.rep);"))
2446                                    .unwrap_or_default();
2447                                // Explicitly dropping an own handle must always release the host-side
2448                                // handle and finalizer registration. A component-defined destructor is
2449                                // an additional callback, not a prerequisite for resource cleanup.
2450                                //
2451                                // Disable Symbol.dispose and clear the handle before calling the
2452                                // component destructor so repeated or re-entrant disposal is a no-op.
2453                                uwriteln!(
2454                                            self.src,
2455                                            "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
2456                                        finalizationRegistry{tid}.unregister({rsc});
2457                                        const handleEntry = {rsc_table_remove}(handleTable{tid}, {handle});
2458                                        {rsc}[{symbol_dispose}] = {empty_func};
2459                                        {rsc}[{symbol_resource_handle}] = undefined;
2460                                        {dtor_call}
2461                                    }}}});"
2462                                        );
2463                            } else {
2464                                // Borrow handles of local resources have rep handles, which we carry through here.
2465                                uwriteln!(
2466                                    self.src,
2467                                    "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
2468                                );
2469                            }
2470                        } else {
2471                            let rep = format!("rep{}", self.tmp());
2472                            // Imported handles either lift as instance capture from a previous lowering,
2473                            // or we create a new JS class to represent it.
2474                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2475                            let symbol_resource_handle =
2476                                self.intrinsic(Intrinsic::SymbolResourceHandle);
2477
2478                            uwriteln!(
2479                                self.src,
2480                                r#"
2481                                  var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2482                                  var {rsc} = captureTable{rid}.get({rep});
2483                                  if (!{rsc}) {{
2484                                      {rsc} = Object.create({local_name}.prototype);
2485                                      Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2486                                      Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2487                                  }}
2488                                "#,
2489                            );
2490
2491                            if is_own {
2492                                // An own lifting is a transfer to JS, so existing own handle is implicitly dropped.
2493                                uwriteln!(
2494                                    self.src,
2495                                    "else {{
2496                                        captureTable{rid}.delete({rep});
2497                                    }}
2498                                    {rsc_table_remove}(handleTable{tid}, {handle});"
2499                                );
2500                            }
2501                        }
2502
2503                        // Borrow handles are tracked to release after the call by CallInterface.
2504                        if !is_own {
2505                            let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2506                                ResourceIntrinsic::CurResourceBorrows,
2507                            ));
2508                            uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2509                            self.clear_resource_borrows = true;
2510                        }
2511                    }
2512
2513                    ResourceData::Guest {
2514                        resource_name,
2515                        prefix,
2516                        extra,
2517                    } => {
2518                        assert!(
2519                            extra.is_none(),
2520                            "plain resource handles do not carry extra data"
2521                        );
2522
2523                        let symbol_resource_handle =
2524                            self.intrinsic(Intrinsic::SymbolResourceHandle);
2525                        let prefix = prefix.as_deref().unwrap_or("");
2526                        let lower_camel = resource_name.to_lower_camel_case();
2527
2528                        if !imported {
2529                            if is_own {
2530                                uwriteln!(
2531                                    self.src,
2532                                    "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;"
2533                                );
2534                                uwrite!(
2535                                    self.src,
2536                                    r#"
2537                                      repTable.delete({handle});
2538                                      delete {rsc}[{symbol_resource_handle}];
2539                                      finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2540                                    "#
2541                                );
2542                            } else {
2543                                uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2544                            }
2545                        } else {
2546                            let upper_camel = resource_name.to_upper_camel_case();
2547
2548                            uwrite!(
2549                                self.src,
2550                                r#"
2551                                  var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
2552                                   Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2553                                "#
2554                            );
2555
2556                            uwriteln!(
2557                                self.src,
2558                                "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
2559                            );
2560
2561                            if !is_own {
2562                                let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2563                                    ResourceIntrinsic::CurResourceBorrows,
2564                                ));
2565                                uwriteln!(
2566                                    self.src,
2567                                    "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2568                                );
2569                                self.clear_resource_borrows = true;
2570                            }
2571                        }
2572                    }
2573                }
2574                results.push(rsc);
2575            }
2576
2577            Instruction::HandleLower { handle, name, .. } => {
2578                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2579                let is_own = matches!(handle, Handle::Own(_));
2580                let ResourceTable { imported, data } =
2581                    &self.resource_map[&crate::dealias(self.resolve, *ty)];
2582
2583                let class_name = name.to_upper_camel_case();
2584                let handle = format!("handle{}", self.tmp());
2585                let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2586                let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2587                let op = &operands[0];
2588
2589                match data {
2590                    ResourceData::Host {
2591                        tid,
2592                        rid,
2593                        local_name,
2594                        ..
2595                    } => {
2596                        let tid = tid.as_u32();
2597                        let rid = rid.as_u32();
2598
2599                        match (imported, is_own) {
2600                            // Imported, owned host-provided resource
2601                            (_imported @ false, _owned @ true) => {
2602                                let empty_func = self
2603                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2604                                uwriteln!(
2605                                    self.src,
2606                                    r#"
2607                                      var {handle} = {op}[{symbol_resource_handle}];
2608                                      if (!{handle}) {{
2609                                          throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2610                                      }}
2611                                      finalizationRegistry{tid}.unregister({op});
2612                                      {op}[{symbol_dispose}] = {empty_func};
2613                                      {op}[{symbol_resource_handle}] = undefined;
2614                                    "#,
2615                                );
2616                            }
2617
2618                            // Imported, borrowed host-provdied resource
2619                            (_imported @ false, _owned @ false) => {
2620                                // When expecting a borrow, the JS resource provided will always be an own
2621                                // handle. This is because it is not possible for borrow handles to be passed
2622                                // back reentrantly.
2623                                // We then set the handle to the rep per the local borrow rule.
2624                                let rsc_flag = self.intrinsic(Intrinsic::Resource(
2625                                    ResourceIntrinsic::ResourceTableFlag,
2626                                ));
2627                                let own_handle = format!("handle{}", self.tmp());
2628                                uwriteln!(
2629                                    self.src,
2630                                    r#"
2631                                      var {own_handle} = {op}[{symbol_resource_handle}];
2632                                      if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
2633                                          throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2634                                      }}
2635                                      var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};
2636                                    "#,
2637                                );
2638                            }
2639
2640                            // Imported, owned guest-provided resource
2641                            (_imported @ true, _owned @ true) => {
2642                                // Imported resources may already have a handle if they were constructed
2643                                // by a component and then passed out.
2644                                //
2645                                // If the handle is not present, in hybrid bindgen we check for a Symbol.for('cabiRep')
2646                                // to get the resource rep.
2647                                //
2648                                // Fall back to assign a new rep in the capture table, when the imported
2649                                // resource was constructed externally.
2650                                let symbol_resource_rep =
2651                                    self.intrinsic(Intrinsic::SymbolResourceRep);
2652                                let create_own_fn = self.intrinsic(Intrinsic::Resource(
2653                                    ResourceIntrinsic::ResourceTableCreateOwn,
2654                                ));
2655
2656                                uwriteln!(
2657                                    self.src,
2658                                    r#"
2659                                      if (!({op} instanceof {local_name})) {{
2660                                          throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2661                                      }}
2662                                      var {handle} = {op}[{symbol_resource_handle}];
2663                                      if (!{handle}) {{
2664                                          const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2665                                          captureTable{rid}.set(rep, {op});
2666                                          {handle} = {create_own_fn}(handleTable{tid}, rep);
2667                                      }}
2668                                    "#
2669                                );
2670                            }
2671
2672                            // Imported, borrowed guest-provided resource
2673                            (_imported @ true, _owned @ false) => {
2674                                // Imported resources may already have a handle if they were constructed
2675                                // by a component and then passed out.
2676                                //
2677                                // Otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
2678                                // to get the resource rep.
2679                                // Fall back to assign a new rep in the capture table, when the imported
2680                                // resource was constructed externally.
2681
2682                                let symbol_resource_rep =
2683                                    self.intrinsic(Intrinsic::SymbolResourceRep);
2684                                let scope_id = self.intrinsic(Intrinsic::ScopeId);
2685                                let create_borrow_fn = self.intrinsic(Intrinsic::Resource(
2686                                    ResourceIntrinsic::ResourceTableCreateBorrow,
2687                                ));
2688
2689                                uwriteln!(
2690                                    self.src,
2691                                    r#"
2692                                      if (!({op} instanceof {local_name})) {{
2693                                          throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2694                                      }}
2695                                      var {handle} = {op}[{symbol_resource_handle}];
2696                                      if (!{handle}) {{
2697                                          const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2698                                          captureTable{rid}.set(rep, {op});
2699                                          {handle} = {create_borrow_fn}(handleTable{tid}, rep, {scope_id});
2700                                      }}
2701                                    "#
2702                                );
2703                            }
2704                        }
2705                    }
2706
2707                    ResourceData::Guest {
2708                        resource_name,
2709                        prefix,
2710                        extra,
2711                    } => {
2712                        assert!(
2713                            extra.is_none(),
2714                            "plain resource handles do not carry extra data"
2715                        );
2716
2717                        let upper_camel = resource_name.to_upper_camel_case();
2718                        let lower_camel = resource_name.to_lower_camel_case();
2719                        let prefix = prefix.as_deref().unwrap_or("");
2720
2721                        let symbol_resource_handle =
2722                            self.intrinsic(Intrinsic::SymbolResourceHandle);
2723
2724                        let tmp = self.tmp();
2725                        match (imported, is_own) {
2726                            // imported owned/borrowed guest resource
2727                            (_imported @ true, _owned) => {
2728                                uwrite!(
2729                                    self.src,
2730                                    r#"
2731                                      var {handle} = {op}[{symbol_resource_handle}];
2732                                      finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
2733                                    "#
2734                                );
2735                            }
2736
2737                            // Not-imported, borrowed guest resource
2738                            (_imported @ false, _owned @ false) => {
2739                                let local_rep = format!("localRep{tmp}");
2740                                uwriteln!(
2741                                    self.src,
2742                                    r#"
2743                                      if (!({op} instanceof {upper_camel})) {{
2744                                          throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2745                                      }}
2746                                      let {handle} = {op}[{symbol_resource_handle}];
2747                                      if ({handle} === undefined) {{
2748                                          var {local_rep} = repCnt++;
2749                                          repTable.set({local_rep}, {{ rep: {op}, own: false }});
2750                                          {op}[{symbol_resource_handle}] = {local_rep};
2751                                      }}
2752                                    "#
2753                                );
2754                            }
2755
2756                            // Not-imported, owned guest resource
2757                            (_imported @ false, _owned @ true) => {
2758                                let local_rep = format!("localRep{tmp}");
2759                                uwriteln!(
2760                                    self.src,
2761                                    r#"
2762                                      if (!({op} instanceof {upper_camel})) {{
2763                                          throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2764                                      }}
2765                                      let {handle} = {op}[{symbol_resource_handle}];
2766                                      if ({handle} === undefined) {{
2767                                          var {local_rep} = repCnt++;
2768                                          repTable.set({local_rep}, {{ rep: {op}, own: true }});
2769                                          {handle} = $resource_{prefix}new${lower_camel}({local_rep});
2770                                          {op}[{symbol_resource_handle}] = {handle};
2771                                          finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
2772                                      }}
2773                                    "#
2774                                );
2775                            }
2776                        }
2777                    }
2778                }
2779                results.push(handle);
2780            }
2781
2782            Instruction::DropHandle { ty } => {
2783                let _ = ty;
2784                todo!("[Instruction::DropHandle] not yet implemented")
2785            }
2786
2787            Instruction::Flush { amt } => {
2788                for item in operands.iter().take(*amt) {
2789                    results.push(item.clone());
2790                }
2791            }
2792
2793            Instruction::ErrorContextLift => {
2794                let item = operands
2795                    .first()
2796                    .expect("unexpectedly missing ErrorContextLift arg");
2797                results.push(item.clone());
2798            }
2799
2800            Instruction::ErrorContextLower => {
2801                let item = operands
2802                    .first()
2803                    .expect("unexpectedly missing ErrorContextLower arg");
2804                results.push(item.clone());
2805            }
2806
2807            Instruction::FutureLower { ty, .. } => {
2808                let future_arg = operands
2809                    .first()
2810                    .expect("unexpectedly missing FutureLower arg");
2811
2812                // Lowering is only performed inline for sync functions, and for async
2813                // functions when the operand is an incoming parameter (e.g. an async
2814                // export lowering a host-provided `Promise` param before `CallWasm`).
2815                //
2816                // For async host *imports* the operand is the host function's return
2817                // value (i.e. produced by `CallInterface`): lowering of async import
2818                // results is performed by the async return-handling machinery
2819                // (see `AsyncTaskIntrinsic::LowerImport` and `task.resolve`), so
2820                // lowering the value inline here as well would consume/settle the
2821                // `Promise` and double-lower it.
2822                if self.is_async && self.for_import.unwrap_or_default() {
2823                    results.push(future_arg.clone());
2824                    return;
2825                }
2826
2827                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2828                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2829                    ComponentIntrinsic::GetOrCreateAsyncState,
2830                ));
2831                let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture(
2832                    AsyncFutureIntrinsic::GenFutureHostInjectFn,
2833                ));
2834                let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture(
2835                    AsyncFutureIntrinsic::IsFutureLowerableObject,
2836                ));
2837                let nested_future_symbol = self.intrinsic(Intrinsic::AsyncFuture(
2838                    AsyncFutureIntrinsic::NestedFutureSymbol,
2839                ));
2840                let create_future_fn =
2841                    self.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::CreateFuture));
2842
2843                // Build the lowering function for the type produced by the future
2844                let type_id = &crate::dealias(self.resolve, *ty);
2845                let ResourceTable {
2846                    imported: true,
2847                    data:
2848                        ResourceData::Guest {
2849                            extra:
2850                                Some(ResourceExtraData::Future {
2851                                    table_idx: future_table_idx_ty,
2852                                    nesting_level,
2853                                    elem_ty,
2854                                }),
2855                            ..
2856                        },
2857                } = self
2858                    .resource_map
2859                    .get(type_id)
2860                    .expect("missing resource mapping for future lower")
2861                else {
2862                    unreachable!("invalid resource table observed during future lower");
2863                };
2864                let future_table_idx = future_table_idx_ty.as_u32();
2865
2866                // Generate payload metadata ('elemMeta')
2867                let (
2868                    payload_type_name_js,
2869                    lift_fn_js,
2870                    lower_fn_js,
2871                    payload_is_none,
2872                    payload_is_numeric,
2873                    payload_is_borrow,
2874                    payload_is_async_value,
2875                    payload_size32_js,
2876                    payload_align32_js,
2877                    payload_flat_count_js,
2878                ) = match elem_ty {
2879                    Some(PayloadTypeMetadata {
2880                        ty: _,
2881                        iface_ty,
2882                        lift_js_expr,
2883                        lower_js_expr,
2884                        size32,
2885                        align32,
2886                        flat_count,
2887                    }) => (
2888                        format!("'{iface_ty:?}'"),
2889                        lift_js_expr.as_str(),
2890                        lower_js_expr.as_str(),
2891                        "false",
2892                        format!(
2893                            "{}",
2894                            matches!(
2895                                iface_ty,
2896                                InterfaceType::U8
2897                                    | InterfaceType::U16
2898                                    | InterfaceType::U32
2899                                    | InterfaceType::U64
2900                                    | InterfaceType::S8
2901                                    | InterfaceType::S16
2902                                    | InterfaceType::S32
2903                                    | InterfaceType::S64
2904                                    | InterfaceType::Float32
2905                                    | InterfaceType::Float64
2906                            )
2907                        ),
2908                        format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
2909                        format!(
2910                            "{}",
2911                            matches!(
2912                                iface_ty,
2913                                InterfaceType::Stream(_) | InterfaceType::Future(_)
2914                            )
2915                        ),
2916                        size32.to_string(),
2917                        align32.to_string(),
2918                        flat_count.unwrap_or(0).to_string(),
2919                    ),
2920                    None => (
2921                        "null".into(),
2922                        "() => {{ throw new Error('no lift fn'); }}",
2923                        "() => {{ throw new Error('no lower fn'); }}",
2924                        "true",
2925                        "false".into(),
2926                        "false".into(),
2927                        "false".into(),
2928                        "null".into(),
2929                        "null".into(),
2930                        "0".into(),
2931                    ),
2932                };
2933
2934                let tmp = self.tmp();
2935                let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}");
2936
2937                let (component_idx_expr, get_realloc_fn_expr) =
2938                    if let Some(state) = &self.component_state {
2939                        let ComponentStateJsExprs {
2940                            component_idx,
2941                            get_realloc_fn,
2942                            ..
2943                        } = state.get_js_exprs();
2944                        (component_idx, get_realloc_fn)
2945                    } else {
2946                        ("-1".into(), "undefined".into())
2947                    };
2948
2949                uwriteln!(
2950                    self.src,
2951                    r#"
2952                        if (!{is_future_lowerable_object_fn}({future_arg})) {{
2953                            {debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }});
2954                            throw new Error('unrecognized future object (not Promise/Thenable)');
2955                        }}
2956
2957                        const cstate{tmp} = {get_or_create_async_state_fn}({component_idx_expr});
2958                        if (!cstate{tmp}) {{
2959                            throw new Error(`missing component state for component [{component_idx_expr}]`);
2960                        }}
2961
2962                        // TODO(feat): facilitate non utf8 string encoding for lowered futures
2963                        const stringEncoding = 'utf8';
2964
2965                        let outermostReadEnd{tmp};
2966                        let futuresList{tmp} = [];
2967                        let future{tmp} = {future_arg};
2968                        let nextFuture{tmp};
2969                        let openedCount = -1;
2970                        // Lower exactly this future layer. If its payload is another
2971                        // future, elemMeta.lowerFn recursively creates that endpoint.
2972                        let futureNestingLevel{tmp} = 0;
2973
2974                        while (futureNestingLevel{tmp} >= 0) {{
2975                            const {{
2976                                writeEnd,
2977                                writeEndWaitableIdx,
2978                                readEnd,
2979                                readEndWaitableIdx
2980                            }} = {create_future_fn}(cstate{tmp}, {{
2981                                tableIdx: {future_table_idx},
2982                                elemMeta: {{
2983                                    liftFn: {lift_fn_js},
2984                                    lowerFn: {lower_fn_js},
2985                                    payloadTypeName: {payload_type_name_js},
2986                                    isNone: {payload_is_none},
2987                                    isNumeric: {payload_is_numeric},
2988                                    isBorrowed: {payload_is_borrow},
2989                                    isAsyncValue: {payload_is_async_value},
2990                                    flatCount: {payload_flat_count_js},
2991                                    align32: {payload_align32_js},
2992                                    size32: {payload_size32_js},
2993                                    stringEncoding,
2994                                    getReallocFn: {get_realloc_fn_expr},
2995                                }}
2996                            }});
2997
2998                            const hostInjectFn = {gen_future_host_inject_fn}({{
2999                                promise: future{tmp},
3000                                stringEncoding,
3001                                hostWriteEnd: writeEnd,
3002                            }});
3003                            readEnd.setHostInjectFn(hostInjectFn);
3004
3005                            const meta{tmp} = {{
3006                                isInnermost: futureNestingLevel{tmp} === {nesting_level},
3007                                level: futureNestingLevel{tmp},
3008                            }};
3009
3010                            const innerFuture = future{tmp};
3011                            future{tmp} = {{ }};
3012                            future{tmp}[{nested_future_symbol}] = meta{tmp};
3013                            future{tmp}.readEndWaitableIdx = readEndWaitableIdx;
3014                            future{tmp}.writeEndWaitableIdx = writeEndWaitableIdx;
3015                            future{tmp}.futureTableIdx = {future_table_idx};
3016                            future{tmp}.componentIdx = {component_idx_expr};
3017                            future{tmp}.then = async (resolve, reject) => {{
3018                                let p;
3019                                if (openedCount === {nesting_level}) {{
3020                                    p = innerFuture;
3021                                }} else {{
3022                                    openedCount++;
3023                                    p = futuresList{tmp}[futuresList{tmp}.length - (openedCount + 1)];
3024                                }}
3025
3026                                try {{
3027                                    resolve(await p);
3028                                }} catch (err) {{
3029                                    reject(err);
3030                                }}
3031                            }};
3032
3033                            outermostReadEnd{tmp} = readEnd;
3034
3035                            futuresList{tmp}.push(future{tmp});
3036                            futureNestingLevel{tmp}--;
3037                        }}
3038
3039                        const readEnd{tmp} = outermostReadEnd{tmp};
3040
3041                        // TODO: need to *lower* the internal future???
3042
3043                        const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx();
3044                    "#
3045                );
3046
3047                results.push(lowered_future_waitable_idx);
3048            }
3049
3050            Instruction::FutureLift { payload, ty } => {
3051                let future_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncFuture(
3052                    AsyncFutureIntrinsic::FutureNewFromLift,
3053                ));
3054
3055                // We must look up the type idx to find the future
3056                let type_id = &crate::dealias(self.resolve, *ty);
3057                let ResourceTable {
3058                    imported: true,
3059                    data:
3060                        ResourceData::Guest {
3061                            extra:
3062                                Some(ResourceExtraData::Future {
3063                                    table_idx: future_table_idx_ty,
3064                                    elem_ty: future_element_ty,
3065                                    ..
3066                                }),
3067                            ..
3068                        },
3069                } = self
3070                    .resource_map
3071                    .get(type_id)
3072                    .expect("missing resource mapping for future lift")
3073                else {
3074                    unreachable!("invalid resource table observed during future lift");
3075                };
3076
3077                // if a future element is present, it should match the payload we're getting
3078                let (lift_fn_js, lower_fn_js) = match future_element_ty {
3079                    Some(PayloadTypeMetadata {
3080                        ty,
3081                        lift_js_expr,
3082                        lower_js_expr,
3083                        ..
3084                    }) => {
3085                        assert_eq!(Some(*ty), **payload, "future element type mismatch");
3086                        (lift_js_expr.to_string(), lower_js_expr.to_string())
3087                    }
3088                    None => (
3089                        "() => {{ throw new Error('no lift fn'); }}".into(),
3090                        "() => {{ throw new Error('no lower fn'); }}".into(),
3091                    ),
3092                };
3093                if let Some(PayloadTypeMetadata { ty, .. }) = future_element_ty {
3094                    assert_eq!(Some(*ty), **payload, "future element type mismatch");
3095                }
3096
3097                let tmp = self.tmp();
3098                let result_var = format!("futureResult{tmp}");
3099
3100                // Optionally preform the lift for the future in question
3101                match (self.is_async, self.for_import.unwrap_or_default()) {
3102                    // It is possible for lifting to be called both at the *start* and *end* of
3103                    // a given function depending on how it called:
3104                    //
3105                    // 1. lifting results to convert a component-produced result for use by the host *after* `CallWasm` returns
3106                    // 2. lifting parameters (`future` -> `Promise`), for use by the host, *before* `CallInterface`
3107                    //
3108                    // In (1), the function being generated must correspond to an export (from a component), and we only
3109                    // perform the lifting if we know the value is imminently ready (i.e. the sync case).
3110                    //
3111                    // In (2) the function must correspond to an import (from the host), and regardless of whether
3112                    // the function being generated is async or not, the host *must* deal in terms of lifted values
3113                    // (i.e. `Promise`, not index to a future)
3114                    //
3115                    (_is_async @ false, _for_import @ false) | (_is_async, _for_import @ true) => {
3116                        // If we're dealing with a sync function, we can use the return directly
3117                        let arg_future_end_idx = operands
3118                            .first()
3119                            .expect("unexpectedly missing future end return arg in FutureLift");
3120
3121                        let (payload_ty_size32_js, payload_ty_align32_js) =
3122                            if let Some(payload_ty) = payload {
3123                                (
3124                                    self.sizes.size(payload_ty).size_wasm32().to_string(),
3125                                    self.sizes.align(payload_ty).align_wasm32().to_string(),
3126                                )
3127                            } else {
3128                                ("null".into(), "null".into())
3129                            };
3130
3131                        let future_table_idx = future_table_idx_ty.as_u32();
3132
3133                        // Set task memory index and memory object
3134                        let component_idx_expr = if let Some(state) = &self.component_state {
3135                            let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3136                            component_idx
3137                        } else {
3138                            "-1".into()
3139                        };
3140
3141                        // We only need to write the result var for use *if* the
3142                        // function that is being executed is provided by the host (i.e. `CallInterface`)
3143                        //
3144                        // The future value in question is being lifted *from* the component,
3145                        // such that the host call can use it (as a `Promise`).
3146                        //
3147                        // `hostProvided` is set hoistably in `CallWasm`/`CallInterface`
3148                        uwriteln!(
3149                            self.src,
3150                            r#"
3151                              const {result_var} = {future_new_from_lift_fn}({{
3152                                  componentIdx: {component_idx_expr},
3153                                  futureTableIdx: {future_table_idx},
3154                                  futureEndWaitableIdx: {arg_future_end_idx},
3155                                  payloadLiftFn: {lift_fn_js},
3156                                  payloadLowerFn: {lower_fn_js},
3157                                  payloadTypeSize32: {payload_ty_size32_js},
3158                                  payloadTypeAlign32: {payload_ty_align32_js},
3159                              }});
3160                            "#,
3161                        );
3162                    }
3163
3164                    // For all other cases, we do not need to perform the lift
3165                    _ => {}
3166                }
3167
3168                results.push(result_var.clone());
3169            }
3170
3171            Instruction::StreamLower { ty, .. } => {
3172                let stream_arg = operands
3173                    .first()
3174                    .expect("unexpectedly missing StreamLower arg");
3175
3176                // Lowering is only performed inline for sync functions, and for async
3177                // functions when the operand is an incoming parameter (e.g. an async
3178                // export lowering a host-provided stream param before `CallWasm`).
3179                //
3180                // For async host *imports* the operand is the host function's return
3181                // value (i.e. produced by `CallInterface`): lowering of async import
3182                // results is performed by the async return-handling machinery
3183                // (see `AsyncTaskIntrinsic::LowerImport` and `task.resolve`), so
3184                // lowering the value inline here as well would consume/lock the
3185                // stream (e.g. a host `ReadableStream`) and double-lower it.
3186                if self.is_async && self.for_import.unwrap_or_default() {
3187                    results.push(stream_arg.clone());
3188                    return;
3189                }
3190
3191                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3192                let async_iterator_symbol = self.intrinsic(Intrinsic::SymbolAsyncIterator);
3193                let iterator_symbol = self.intrinsic(Intrinsic::SymbolIterator);
3194                let external_readable_stream_class =
3195                    self.intrinsic(Intrinsic::PlatformReadableStreamClass);
3196                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3197                    ComponentIntrinsic::GetOrCreateAsyncState,
3198                ));
3199                let gen_stream_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream(
3200                    AsyncStreamIntrinsic::GenStreamHostInjectFn,
3201                ));
3202                let gen_read_fn_from_lowerable_stream_fn = self.intrinsic(Intrinsic::AsyncStream(
3203                    AsyncStreamIntrinsic::GenReadFnFromLowerableStream,
3204                ));
3205                let create_stream_fn =
3206                    self.intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::CreateStream));
3207
3208                // TODO(???): A component could end up receiving a stream that it outputted,
3209                // and the below would fail (imported: false)?
3210
3211                // Build the lowering function for the type produced by the stream
3212                let type_id = &crate::dealias(self.resolve, *ty);
3213                let ResourceTable {
3214                    imported: true,
3215                    data:
3216                        ResourceData::Guest {
3217                            extra:
3218                                Some(ResourceExtraData::Stream {
3219                                    table_idx: stream_table_idx_ty,
3220                                    elem_ty,
3221                                }),
3222                            ..
3223                        },
3224                } = self
3225                    .resource_map
3226                    .get(type_id)
3227                    .expect("missing resource mapping for stream lower")
3228                else {
3229                    unreachable!("invalid resource table observed during stream lower");
3230                };
3231
3232                let stream_table_idx = stream_table_idx_ty.as_u32();
3233
3234                let (
3235                    payload_type_name_js,
3236                    lift_fn_js,
3237                    lower_fn_js,
3238                    payload_is_none,
3239                    payload_is_numeric,
3240                    payload_is_borrow,
3241                    payload_is_async_value,
3242                    payload_size32_js,
3243                    payload_align32_js,
3244                    payload_flat_count_js,
3245                ) = match elem_ty {
3246                    Some(PayloadTypeMetadata {
3247                        ty: _,
3248                        iface_ty,
3249                        lift_js_expr,
3250                        lower_js_expr,
3251                        size32,
3252                        align32,
3253                        flat_count,
3254                    }) => (
3255                        format!("'{iface_ty:?}'"),
3256                        lift_js_expr.as_str(),
3257                        lower_js_expr.as_str(),
3258                        "false",
3259                        format!(
3260                            "{}",
3261                            matches!(
3262                                iface_ty,
3263                                InterfaceType::U8
3264                                    | InterfaceType::U16
3265                                    | InterfaceType::U32
3266                                    | InterfaceType::U64
3267                                    | InterfaceType::S8
3268                                    | InterfaceType::S16
3269                                    | InterfaceType::S32
3270                                    | InterfaceType::S64
3271                                    | InterfaceType::Float32
3272                                    | InterfaceType::Float64
3273                            )
3274                        ),
3275                        format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
3276                        format!(
3277                            "{}",
3278                            matches!(
3279                                iface_ty,
3280                                InterfaceType::Stream(_) | InterfaceType::Future(_)
3281                            )
3282                        ),
3283                        size32.to_string(),
3284                        align32.to_string(),
3285                        flat_count.unwrap_or(0).to_string(),
3286                    ),
3287                    None => (
3288                        "null".into(),
3289                        "() => {{ throw new Error('no lift fn'); }}",
3290                        "() => {{ throw new Error('no lower fn'); }}",
3291                        "true",
3292                        "false".into(),
3293                        "false".into(),
3294                        "false".into(),
3295                        "null".into(),
3296                        "null".into(),
3297                        "0".into(),
3298                    ),
3299                };
3300
3301                // Set task memory index and memory object
3302                let (component_idx_expr, get_realloc_fn_expr) =
3303                    if let Some(state) = &self.component_state {
3304                        let ComponentStateJsExprs {
3305                            component_idx,
3306                            get_realloc_fn,
3307                            ..
3308                        } = state.get_js_exprs();
3309                        (component_idx, get_realloc_fn)
3310                    } else {
3311                        ("-1".into(), "undefined".into())
3312                    };
3313
3314                let tmp = self.tmp();
3315                let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}");
3316                uwriteln!(
3317                    self.src,
3318                    r#"
3319                        if (!({async_iterator_symbol} in {stream_arg})
3320                            && !({iterator_symbol} in {stream_arg})
3321                            && !({stream_arg} instanceof {external_readable_stream_class})) {{
3322                            {debug_log_fn}('[Instruction::StreamLower] object with no supported stream protocol', {{ {stream_arg} }});
3323                            throw new Error('unrecognized stream object (no supported stream protocol)');
3324                        }}
3325
3326                        const cstate{tmp} = {get_or_create_async_state_fn}({component_idx_expr});
3327                        if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx_expr}]`); }}
3328
3329                        const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = {create_stream_fn}(cstate{tmp}, {{
3330                            tableIdx: {stream_table_idx},
3331                            elemMeta: {{
3332                                liftFn: {lift_fn_js},
3333                                lowerFn: {lower_fn_js},
3334                                payloadTypeName: {payload_type_name_js},
3335                                isNone: {payload_is_none},
3336                                isNumeric: {payload_is_numeric},
3337                                isBorrowed: {payload_is_borrow},
3338                                isAsyncValue: {payload_is_async_value},
3339                                flatCount: {payload_flat_count_js},
3340                                align32: {payload_align32_js},
3341                                size32: {payload_size32_js},
3342                                // TODO(feat): facilitate non utf8 string encoding for lowered streams
3343                                stringEncoding: 'utf8',
3344                                getReallocFn: {get_realloc_fn_expr},
3345                            }},
3346                        }});
3347
3348                        const readFn{tmp} = {gen_read_fn_from_lowerable_stream_fn}({stream_arg});
3349
3350                        const hostInjectFn = {gen_stream_host_inject_fn}({{
3351                            readFn: readFn{tmp},
3352                            hostWriteEnd: hostWriteEnd{tmp},
3353                            readEnd: readEnd{tmp},
3354                        }});
3355                        readEnd{tmp}.setHostInjectFn(hostInjectFn);
3356                        readEnd{tmp}.setHostDropFn(readFn{tmp}.drop);
3357
3358                        const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx();
3359                    "#
3360                );
3361
3362                results.push(lowered_stream_waitable_idx);
3363            }
3364
3365            Instruction::StreamLift { payload, ty } => {
3366                let stream_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncStream(
3367                    AsyncStreamIntrinsic::StreamNewFromLift,
3368                ));
3369
3370                // We must look up the type idx to find the stream
3371                let type_id = &crate::dealias(self.resolve, *ty);
3372                let ResourceTable {
3373                    imported: true,
3374                    data:
3375                        ResourceData::Guest {
3376                            extra:
3377                                Some(ResourceExtraData::Stream {
3378                                    table_idx: stream_table_idx_ty,
3379                                    elem_ty: stream_element_ty,
3380                                }),
3381                            ..
3382                        },
3383                } = self
3384                    .resource_map
3385                    .get(type_id)
3386                    .expect("missing resource mapping for stream lift")
3387                else {
3388                    unreachable!("invalid resource table observed during stream lift");
3389                };
3390
3391                // if a stream element is present, it should match the payload we're getting
3392                let (lift_fn_js, lower_fn_js) = match stream_element_ty {
3393                    Some(PayloadTypeMetadata {
3394                        ty,
3395                        lift_js_expr,
3396                        lower_js_expr,
3397                        ..
3398                    }) => {
3399                        assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3400                        (lift_js_expr.to_string(), lower_js_expr.to_string())
3401                    }
3402                    None => (
3403                        "() => {{ throw new Error('no lift fn'); }}".into(),
3404                        "() => {{ throw new Error('no lower fn'); }}".into(),
3405                    ),
3406                };
3407                if let Some(PayloadTypeMetadata { ty, .. }) = stream_element_ty {
3408                    assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3409                }
3410
3411                let tmp = self.tmp();
3412                let result_var = format!("streamResult{tmp}");
3413
3414                // Optionally preform the lift for the stream in question
3415                match (self.is_async, self.for_import.unwrap_or_default()) {
3416                    // It is possible for lifting to be called both at the *start* and *end* of
3417                    // a given function depending on how it called:
3418                    //
3419                    // 1. lifting results to convert a component-produced result for use by the host *after* `CallWasm` returns
3420                    // 2. lifting parameters (`stream` -> `AsyncIterator`), for use by the host, *before* `CallInterface`
3421                    //
3422                    // In (1), the function being generated must correspond to an export (from a component), and we only
3423                    // perform the lifting if we know the value is imminently ready (i.e. the sync case).
3424                    //
3425                    // In (2) the function must correspond to an import (from the host), and regardless of whether
3426                    // the function being generated is async or not, the host *must* deal in terms of lifted values
3427                    // (i.e. `AsyncIterator`, not index to a stream)
3428                    //
3429                    (_is_async @ false, _for_import @ false) | (_is_async, _for_import @ true) => {
3430                        let arg_stream_end_idx = operands
3431                            .first()
3432                            .expect("unexpectedly missing stream end return arg in StreamLift");
3433
3434                        let (payload_ty_size32_js, payload_ty_align32_js) =
3435                            if let Some(payload_ty) = payload {
3436                                (
3437                                    self.sizes.size(payload_ty).size_wasm32().to_string(),
3438                                    self.sizes.align(payload_ty).align_wasm32().to_string(),
3439                                )
3440                            } else {
3441                                ("null".into(), "null".into())
3442                            };
3443
3444                        let stream_table_idx = stream_table_idx_ty.as_u32();
3445
3446                        // Set task memory index and memory object
3447                        let component_idx_expr = if let Some(state) = &self.component_state {
3448                            let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3449                            component_idx
3450                        } else {
3451                            "-1".into()
3452                        };
3453
3454                        uwriteln!(
3455                            self.src,
3456                            r#"
3457                              const {result_var} = {stream_new_from_lift_fn}({{
3458                                  componentIdx: {component_idx_expr},
3459                                  streamTableIdx: {stream_table_idx},
3460                                  streamEndWaitableIdx: {arg_stream_end_idx},
3461                                  payloadLiftFn: {lift_fn_js},
3462                                  payloadLowerFn: {lower_fn_js},
3463                                  payloadTypeSize32: {payload_ty_size32_js},
3464                                  payloadTypeAlign32: {payload_ty_align32_js},
3465                              }});
3466                            "#,
3467                        );
3468                    }
3469
3470                    // For other cases, we can do nothing as the future idx passes right through
3471                    _ => {}
3472                };
3473
3474                // TODO(fix): in the async case we return an uninitialized var, which should not be necessary
3475                results.push(result_var.clone());
3476            }
3477
3478            // Instruction::AsyncTaskReturn does *not* correspond to an canonical `task.return`,
3479            // but rather to a "return"/exit from an a lifted async function (e.g. pre-callback)
3480            //
3481            // To modify behavior of the `task.return` intrinsic, see:
3482            //   - `Trampoline::TaskReturn`
3483            //   - `AsyncTaskIntrinsic::TaskReturn`
3484            //
3485            // This is simply the end of the async function definition (e.g. `CallWasm`) that has been
3486            // lifted, which contains information about the async state.
3487            //
3488            // For an async function 'some-func', this instruction is triggered w/ the following `name`s:
3489            // - '[task-return]some-func'
3490            //
3491            // At this point in code generation, the following things have already been set:
3492            // - `parentTask`: A parent task, if one was executing before
3493            // - `subtask`: A subtask, if the current task is a subtask of a parent task
3494            // - `task`: the currently executing task
3495            // - `ret`: the original function return value, via (i.e. via `CallWasm`/`CallInterface`)
3496            // - `hostProvided`: whether the original function was a host-provided (i.e. host provided import)
3497            //
3498            Instruction::AsyncTaskReturn { name, params } => {
3499                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3500                let is_async_js = self.requires_async_porcelain | self.is_async;
3501                let async_driver_loop_fn =
3502                    self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::DriverLoop));
3503                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3504                    ComponentIntrinsic::GetOrCreateAsyncState,
3505                ));
3506
3507                // Set task memory index and memory object
3508                let component_idx_expr = if let Some(state) = &self.component_state {
3509                    let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3510                    component_idx
3511                } else {
3512                    "-1".into()
3513                };
3514
3515                let throw_result_err = if self.no_component_error_wrapping {
3516                    "throw taskRes.val;".to_string()
3517                } else {
3518                    let component_err = self.intrinsic(Intrinsic::ComponentError);
3519                    format!("throw new {component_err}(taskRes.val);")
3520                };
3521
3522                uwriteln!(
3523                    self.src,
3524                    "{debug_log_fn}('{prefix}  [Instruction::AsyncTaskReturn]', {{
3525                         funcName: '{name}',
3526                         paramCount: {param_count},
3527                         componentIdx: {component_idx_expr},
3528                         postReturn: {post_return_present},
3529                         hostProvided,
3530                      }});",
3531                    param_count = params.len(),
3532                    post_return_present = self.post_return.is_some(),
3533                    prefix = self.tracing_prefix,
3534                );
3535
3536                assert!(
3537                    self.is_async,
3538                    "non-async functions should not be performing async returns (func {name})",
3539                );
3540
3541                // If we're dealing with an async call, then `ret` is actually the
3542                // state of async behavior.
3543                //
3544                // The result *should* be a Promise that resolves to whatever the current task
3545                // will eventually resolve to.
3546                //
3547                // NOTE: Regardless of whether async porcelain is required here, we want to return the result
3548                // of the computation as a whole, not the current async state (which is what `ret` currently is).
3549                //
3550                // `ret` is only a Promise if we have async-lowered the function in question (e.g. via JSPI)
3551                //
3552                // ```ts
3553                // type ret = number | Promise<number>;
3554                // ```ts
3555                //
3556                // If the import was host provided we *already* have the result via
3557                // JSPI and simply calling the host provided JS function -- there is no need
3558                // to drive the async loop as with an async import that came from a component.
3559                //
3560                // If a subtask is defined, then we're in the case of a lowered async import,
3561                // which means that the first async call (to the callee fn) has occurred,
3562                // and a subtask has been created, but has not been triggered as started.
3563                //
3564                // NOTE: for host provided functions, we know that the resolution fo the
3565                // function itself are the lifted (component model -- i.e. a string not a pointer + len)
3566                // results. In those cases, we can simply return the result that was provided by the host.
3567                //
3568                // Alternatively, if we have entered an async return, and are part of a subtask
3569                // then we should start it, given that the task we have recently created (however we got to
3570                // the async return) is going to continue to be polled soon (via the driver loop).
3571                //
3572                uwriteln!(
3573                    self.src,
3574                    r#"
3575                      if (hostProvided) {{
3576                          {debug_log_fn}('[Instruction::AsyncTaskReturn] signaling host-provided async return completion', {{
3577                              task: task.id(),
3578                              subtask: subtask?.id(),
3579                              result: ret,
3580                          }})
3581                          task.resolve([ret]);
3582                          task.exit();
3583                          return {return_awaited_completion_promise};
3584                      }}
3585
3586                      const componentState = {get_or_create_async_state_fn}({component_idx_expr});
3587                      if (!componentState) {{ throw new Error('failed to lookup current component state'); }}
3588
3589                      queueMicrotask(async (resolve, reject) => {{
3590                          try {{
3591                              {debug_log_fn}("[Instruction::AsyncTaskReturn] starting driver loop", {{
3592                                  fnName: '{name}',
3593                                  componentInstanceIdx: {component_idx_expr},
3594                                  taskID: task.id(),
3595                              }});
3596                              await {async_driver_loop_fn}({{
3597                                  componentInstanceIdx: {component_idx_expr},
3598                                  componentState,
3599                                  task,
3600                                  fnName: '{name}',
3601                                  isAsync: {is_async_js},
3602                                  callbackResult: ret,
3603                              }});
3604                          }} catch (err) {{
3605                              {debug_log_fn}("[Instruction::AsyncTaskReturn] driver loop call failure", {{ err }});
3606                          }}
3607                      }});
3608
3609                      let taskRes = await task.completionPromise();
3610                      if (task.getErrHandling() === 'throw-result-err') {{
3611                          if (typeof taskRes !== 'object') {{
3612                              return {return_task_res};
3613                          }}
3614                          if (taskRes.tag === 'err') {{ {throw_result_err} }}
3615                          if (taskRes.tag === 'ok') {{ taskRes = taskRes.val; }}
3616                      }}
3617
3618                      return {return_task_res};
3619                      "#,
3620                    // If we are returning the awaited task completion promise directly
3621                    // that contains a future<t>, we must wrap the result so we can deal
3622                    // with nesting if present
3623                    return_awaited_completion_promise = if self.wrap_async_future_result {
3624                        "{ value: await task.completionPromise() }"
3625                    } else {
3626                        "await task.completionPromise()"
3627                    },
3628                    // If we are returning the task result post-resolution, and it contains a future<t>,
3629                    // we must wrap the result so we can deal with nesting if present
3630                    return_task_res = if self.wrap_async_future_result {
3631                        "{ value: taskRes }"
3632                    } else {
3633                        "taskRes"
3634                    }
3635                );
3636            }
3637
3638            Instruction::GuestDeallocate { .. }
3639            | Instruction::GuestDeallocateString
3640            | Instruction::GuestDeallocateList { .. }
3641            | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
3642
3643            Instruction::GuestDeallocateMap { .. } => unimplemented!("map deallocation support"),
3644        }
3645    }
3646}
3647
3648/// Tests whether `ty` can be represented with `null`, and if it can then
3649/// the "other type" is returned. If `Some` is returned that means that `ty`
3650/// is `null | <return>`. If `None` is returned that means that `null` can't
3651/// be used to represent `ty`.
3652pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
3653    let id = match ty {
3654        Type::Id(id) => *id,
3655        _ => return None,
3656    };
3657    match &resolve.types[id].kind {
3658        // If `ty` points to an `option<T>`, then `ty` can be represented
3659        // with `null` if `t` itself can't be represented with null. For
3660        // example `option<option<u32>>` can't be represented with `null`
3661        // since that's ambiguous if it's `none` or `some(none)`.
3662        //
3663        // Note, oddly enough, that `option<option<option<u32>>>` can be
3664        // represented as `null` since:
3665        //
3666        // * `null` => `none`
3667        // * `{ tag: "none" }` => `some(none)`
3668        // * `{ tag: "some", val: null }` => `some(some(none))`
3669        // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
3670        //
3671        // It's doubtful anyone would actually rely on that though due to
3672        // how confusing it is.
3673        TypeDefKind::Option(t) => {
3674            if !maybe_null(resolve, t) {
3675                Some(t)
3676            } else {
3677                None
3678            }
3679        }
3680        TypeDefKind::Type(t) => as_nullable(resolve, t),
3681        _ => None,
3682    }
3683}
3684
3685pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
3686    as_nullable(resolve, ty).is_some()
3687}
3688
3689/// Retrieve the specialized JS array type that would contain a given element type,
3690/// if one exists.
3691///
3692/// e.g. a Wasm [`Type::U8`] would be represetned by a JS `Uint8Array`
3693///
3694/// # Arguments
3695///
3696/// * `resolve` - The [`Resolve`] used to look up nested type IDs if necessary
3697/// * `element_ty` - The [`Type`] that represents elements of the array
3698pub fn js_array_ty(resolve: &Resolve, element_ty: &Type) -> Option<&'static str> {
3699    match element_ty {
3700        Type::Bool => None,
3701        Type::U8 => Some("Uint8Array"),
3702        Type::S8 => Some("Int8Array"),
3703        Type::U16 => Some("Uint16Array"),
3704        Type::S16 => Some("Int16Array"),
3705        Type::U32 => Some("Uint32Array"),
3706        Type::S32 => Some("Int32Array"),
3707        Type::U64 => Some("BigUint64Array"),
3708        Type::S64 => Some("BigInt64Array"),
3709        Type::F32 => Some("Float32Array"),
3710        Type::F64 => Some("Float64Array"),
3711        Type::Char => None,
3712        Type::String => None,
3713        Type::ErrorContext => None,
3714        Type::Id(id) => match &resolve.types[*id].kind {
3715            // Recur to resolve type aliases, etc.
3716            TypeDefKind::Type(t) => js_array_ty(resolve, t),
3717            _ => None,
3718        },
3719    }
3720}
3721
3722/// Generate the JS `DataView` set and numeric checks for a given numeric type
3723///
3724/// # Arguments
3725///
3726/// * `ty` - the [`Type`] to check
3727///
3728fn gen_dataview_set_and_check_fn_js_for_numeric_type(
3729    resolve: &Resolve,
3730    ty: &Type,
3731) -> (&'static str, String) {
3732    let check_fn = Intrinsic::Conversion(ConversionIntrinsic::RequireValidNumericPrimitive).name();
3733    match ty {
3734        // Unsigned Integers
3735        Type::Bool => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3736        Type::U8 => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3737        Type::U16 => ("setUint16", format!("{check_fn}.bind(null, 'u16')",)),
3738        Type::U32 => ("setUint32", format!("{check_fn}.bind(null, 'u32')",)),
3739        Type::U64 => ("setBigUint64", format!("{check_fn}.bind(null, 'u64')",)),
3740        // Signed integers
3741        Type::S8 => ("setInt8", format!("{check_fn}.bind(null, 's8')",)),
3742        Type::S16 => ("setInt16", format!("{check_fn}.bind(null, 's16')",)),
3743        Type::S32 => ("setInt32", format!("{check_fn}.bind(null, 's32')",)),
3744        Type::S64 => ("setBigInt64", format!("{check_fn}.bind(null, 's64')",)),
3745        // Floating point
3746        Type::F32 => ("setFloat32", format!("{check_fn}.bind(null, 'f32')",)),
3747        Type::F64 => ("setFloat64", format!("{check_fn}.bind(null, 'f64')",)),
3748        Type::Id(id) => match resolve.types.get(*id) {
3749            // Type aliases should resolve to types that have the kind `TypeDefKind::Type`
3750            Some(TypeDef {
3751                kind: TypeDefKind::Type(inner_ty),
3752                ..
3753            }) => gen_dataview_set_and_check_fn_js_for_numeric_type(resolve, inner_ty),
3754            // We do not expect to resolve to types that *do not* have the `TypeDefKind::Type(...)`
3755            Some(inner_ty) => {
3756                unreachable!(
3757                    "unexpected non-type-kind typedef [{inner_ty:?}] (as type {ty:?}) for canonical list lower [{ty:?}]",
3758                )
3759            }
3760            // All type ids should resolve via the passed in `Resolve`
3761            None => unreachable!("missing/unresolvable type [{ty:?}]"),
3762        },
3763        _ => unreachable!("unsupported type [{ty:?}] for canonical list lower"),
3764    }
3765}
3766
3767#[cfg(test)]
3768mod tests {
3769    use super::*;
3770
3771    #[test]
3772    fn result_wrapper_detection_preserves_ambiguous_success_payloads() {
3773        let mut resolve = Resolve::new();
3774        let package = resolve
3775            .push_str(
3776                std::path::Path::new("result-wrappers.wit"),
3777                r#"
3778                    package test:result-wrappers;
3779
3780                    interface imports {
3781                        variant ordinary-variant { a(u32), b }
3782                        variant ok-variant { ok(u32), other }
3783                        record tagged-record { tag: string, val: u32 }
3784
3785                        primitive: func() -> result<u32, string>;
3786                        ordinary: func() -> result<ordinary-variant, string>;
3787                        ok-case: func() -> result<ok-variant, string>;
3788                        nested: func() -> result<result<u32, string>, string>;
3789                        tagged: func() -> result<tagged-record, string>;
3790                    }
3791                "#,
3792            )
3793            .unwrap();
3794        let interface = resolve.packages[package].interfaces["imports"];
3795
3796        let tags_for = |name: &str| {
3797            let ok = get_thrown_type(
3798                &resolve,
3799                resolve.interfaces[interface].functions[name].result,
3800            )
3801            .unwrap()
3802            .0;
3803            unambiguous_result_wrapper_tags(&resolve, ok)
3804        };
3805
3806        assert_eq!(tags_for("primitive"), (true, true));
3807        assert_eq!(tags_for("ordinary"), (true, true));
3808        assert_eq!(tags_for("ok-case"), (false, true));
3809        assert_eq!(tags_for("nested"), (false, false));
3810        assert_eq!(tags_for("tagged"), (false, false));
3811    }
3812
3813    #[test]
3814    fn test_alias_type_gen_dataview_set_and_check_fn_js_for_numeric_type() {
3815        let mut resolve = Resolve::new();
3816
3817        let owner = wit_parser::TypeOwner::Interface(resolve.interfaces.next_id());
3818
3819        let ty_id = resolve.types.alloc(wit_parser::TypeDef {
3820            name: None,
3821            kind: TypeDefKind::Type(Type::U64),
3822            docs: Default::default(),
3823            stability: Default::default(),
3824            owner,
3825            span: Default::default(),
3826            external_id: None,
3827        });
3828
3829        let ty_ = Type::Id(ty_id);
3830
3831        let (dataview_set_method, check_fn_intrinsic) =
3832            gen_dataview_set_and_check_fn_js_for_numeric_type(&resolve, &ty_);
3833
3834        assert_eq!(dataview_set_method, "setBigUint64");
3835
3836        let check_fn =
3837            Intrinsic::Conversion(ConversionIntrinsic::RequireValidNumericPrimitive).name();
3838
3839        assert_eq!(check_fn_intrinsic, format!("{check_fn}.bind(null, 'u64')",));
3840    }
3841}