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