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