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