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    CanonicalOptions, ResourceIndex, TypeComponentLocalErrorContextTableIndex,
8    TypeFutureTableIndex, 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/// Supplemental data kept along with [`ResourceData`]
68#[derive(Clone, Debug, PartialEq)]
69pub enum ResourceExtraData {
70    Stream {
71        table_idx: TypeStreamTableIndex,
72        elem_ty: Option<Type>,
73    },
74    Future {
75        table_idx: TypeFutureTableIndex,
76        elem_ty: Option<Type>,
77    },
78    ErrorContext {
79        table_idx: TypeComponentLocalErrorContextTableIndex,
80    },
81}
82
83/// Map used for resource function bindgen within a given component
84///
85/// Mapping from the instance + resource index in that component (internal or external)
86/// to the unique global resource id used to key the resource tables for this resource.
87///
88/// The id value uniquely identifies the resource table so that if a resource is used
89/// by n components, there should be n different indices and spaces in use. The map is
90/// therefore entirely unique and fully distinct for each instance's function bindgen.
91///
92/// The second bool is true if it is an imported resource.
93///
94/// For a given resource table id {x}, with resource index {y} the local variables are assumed:
95/// - handleTable{x}
96/// - captureTable{y} (rep to instance map for captured imported tables, only for JS import bindgen, not hybrid)
97/// - captureCnt{y} for assigning capture rep
98///
99/// For component-defined resources:
100/// - finalizationRegistry{x}
101///
102/// handleTable internally will be allocated with { rep: i32, own: bool } entries
103///
104/// In the case of an imported resource tables, in place of "rep" we just store
105/// the direct JS object being referenced, since in JS the object is its own handle.
106///
107#[derive(Clone, Debug, PartialEq)]
108pub struct ResourceTable {
109    /// Whether a resource was imported
110    ///
111    /// This should be tracked because imported types cannot be re-exported uniquely (?)
112    pub imported: bool,
113
114    /// Data related to the actual resource
115    pub data: ResourceData,
116}
117
118/// A mapping of type IDs to the resources that they represent
119pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
120
121pub struct FunctionBindgen<'a> {
122    /// Mapping of resources for types that have corresponding definitions locally
123    pub resource_map: &'a ResourceMap,
124
125    /// Whether current resource borrows need to be deactivated
126    pub clear_resource_borrows: bool,
127
128    /// Set of intrinsics
129    pub intrinsics: &'a mut BTreeSet<Intrinsic>,
130
131    /// Whether to perform valid lifting optimization
132    pub valid_lifting_optimization: bool,
133
134    /// Sizes and alignments for sub elements
135    pub sizes: &'a SizeAlign,
136
137    /// Method of error handling
138    pub err: ErrHandling,
139
140    /// Temporary values
141    pub tmp: usize,
142
143    /// Source code of the function
144    pub src: source::Source,
145
146    /// Block storage
147    pub block_storage: Vec<source::Source>,
148
149    /// Blocks of the function
150    pub blocks: Vec<(String, Vec<String>)>,
151
152    /// Parameters of the function
153    pub params: Vec<String>,
154
155    /// Memory variable
156    pub memory: Option<&'a String>,
157
158    /// Realloc function name
159    pub realloc: Option<&'a String>,
160
161    /// Post return function name
162    pub post_return: Option<&'a String>,
163
164    /// Prefix to use when printing tracing information
165    pub tracing_prefix: &'a String,
166
167    /// Whether tracing is enabled
168    pub tracing_enabled: bool,
169
170    /// Method if string encoding
171    pub encoding: StringEncoding,
172
173    /// Callee of the function
174    pub callee: &'a str,
175
176    /// Whether the callee is dynamic (i.e. has multiple operands)
177    pub callee_resource_dynamic: bool,
178
179    /// The [`wit_bindgen::Resolve`] containing extracted WIT information
180    pub resolve: &'a Resolve,
181
182    /// Whether the function requires async porcelain
183    ///
184    /// In the case of an import this likely implies the use of JSPI
185    /// and in the case of an export this is simply code generation metadata.
186    pub requires_async_porcelain: bool,
187
188    /// Whether the function is guest async lifted (i.e. WASI P3)
189    pub is_async: bool,
190
191    /// Canon opts
192    pub canon_opts: &'a CanonicalOptions,
193
194    /// Interface name
195    pub iface_name: Option<&'a str>,
196}
197
198impl FunctionBindgen<'_> {
199    fn tmp(&mut self) -> usize {
200        let ret = self.tmp;
201        self.tmp += 1;
202        ret
203    }
204
205    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
206        self.intrinsics.insert(intrinsic);
207        intrinsic.name().to_string()
208    }
209
210    fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
211    where
212        T: std::fmt::Display,
213    {
214        let clamp = self.intrinsic(Intrinsic::ClampGuest);
215        results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
216    }
217
218    fn load(
219        &mut self,
220        method: &str,
221        offset: ArchitectureSize,
222        operands: &[String],
223        results: &mut Vec<String>,
224    ) {
225        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
226        let Some(memory) = self.memory.as_ref() else {
227            panic!(
228                "unexpectedly missing memory during bindgen for interface [{:?}] (callee {})",
229                self.iface_name, self.callee,
230            );
231        };
232        results.push(format!(
233            "{view}({memory}).{method}({} + {offset}, true)",
234            operands[0],
235            offset = offset.size_wasm32()
236        ));
237    }
238
239    fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
240        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
241        let memory = self.memory.as_ref().unwrap();
242        uwriteln!(
243            self.src,
244            "{view}({memory}).{method}({} + {offset}, {}, true);",
245            operands[1],
246            operands[0],
247            offset = offset.size_wasm32()
248        );
249    }
250
251    /// Write result assignment lines to output
252    ///
253    /// In general this either means writing preambles, for example that look like the following:
254    ///
255    /// ```js
256    /// let ret =
257    /// ```
258    ///
259    /// ```
260    /// var [ ret0, ret1, ret2 ] =
261    /// ```
262    ///
263    /// ```js
264    /// let ret;
265    /// ```
266    ///
267    /// # Arguments
268    ///
269    /// * `amt` - number of results
270    /// * `results` - list of variables that will be returned
271    ///
272    fn generate_result_assignment_lhs(
273        &mut self,
274        amt: usize,
275        results: &mut Vec<String>,
276        is_async: bool,
277    ) -> String {
278        let mut s = String::new();
279        match amt {
280            0 => {
281                // Async functions with no returns still return async code,
282                // which will be used as the initial callback result going into the async driver
283                if is_async {
284                    uwrite!(s, "let ret = ")
285                } else {
286                    uwrite!(s, "let ret;")
287                }
288            }
289            1 => {
290                uwrite!(s, "let ret = ");
291                results.push("ret".to_string());
292            }
293            n => {
294                uwrite!(s, "var [");
295                for i in 0..n {
296                    if i > 0 {
297                        uwrite!(s, ", ");
298                    }
299                    uwrite!(s, "ret{}", i);
300                    results.push(format!("ret{i}"));
301                }
302                uwrite!(s, "] = ");
303            }
304        }
305        s
306    }
307
308    fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
309        match cast {
310            Bitcast::I32ToF32 => {
311                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
312                format!("{cvt}({op})")
313            }
314            Bitcast::F32ToI32 => {
315                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
316                format!("{cvt}({op})")
317            }
318            Bitcast::I64ToF64 => {
319                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
320                format!("{cvt}({op})")
321            }
322            Bitcast::F64ToI64 => {
323                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
324                format!("{cvt}({op})")
325            }
326            Bitcast::I32ToI64 => format!("BigInt({op})"),
327            Bitcast::I64ToI32 => format!("Number({op})"),
328            Bitcast::I64ToF32 => {
329                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
330                format!("{cvt}(Number({op}))")
331            }
332            Bitcast::F32ToI64 => {
333                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
334                format!("BigInt({cvt}({op}))")
335            }
336            Bitcast::None
337            | Bitcast::P64ToI64
338            | Bitcast::LToI32
339            | Bitcast::I32ToL
340            | Bitcast::LToP
341            | Bitcast::PToL
342            | Bitcast::PToI32
343            | Bitcast::I32ToP => op.to_string(),
344            Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
345            Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
346            Bitcast::Sequence(casts) => {
347                let mut statement = op.to_string();
348                for cast in casts.iter() {
349                    statement = self.bitcast(cast, &statement);
350                }
351                statement
352            }
353        }
354    }
355
356    /// Start the current task
357    ///
358    /// The code generated by this function *may* also start a subtask
359    /// where appropriate.
360    fn start_current_task(&mut self, instr: &Instruction) {
361        let is_async = self.is_async;
362        let is_manual_async = self.requires_async_porcelain;
363        let fn_name = self.callee;
364        let err_handling = self.err.to_js_string();
365        let callback_fn_js = self
366            .canon_opts
367            .callback
368            .as_ref()
369            .map(|v| format!("callback_{}", v.as_u32()))
370            .unwrap_or_else(|| "null".into());
371        let (calling_wasm_export, prefix) = match instr {
372            Instruction::CallWasm { .. } => (true, "_wasm_call_"),
373            Instruction::CallInterface { .. } => (false, "_interface_call_"),
374            _ => unreachable!(
375                "unrecognized instruction triggering start of current task: [{instr:?}]"
376            ),
377        };
378        let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
379            AsyncTaskIntrinsic::CreateNewCurrentTask,
380        ));
381        let global_task_map = self.intrinsic(Intrinsic::AsyncTask(
382            AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap,
383        ));
384        let component_instance_idx = self.canon_opts.instance.as_u32();
385
386        // If we're within an async function, wait for all top level previous tasks to finish before running
387        // to ensure that guests do not try to run two tasks at the same time.
388        if is_async && self.requires_async_porcelain {
389            uwriteln!(
390                self.src,
391                r#"
392                // All other tasks must finish before we can start this one
393                const taskMetas = {global_task_map}.get({component_instance_idx});
394                if (taskMetas) {{
395                    const taskPromises = taskMetas
396                        .filter(mt => mt.componentIdx === {component_instance_idx})
397                        .map(mt => mt.task)
398                        .filter(t => !t.getParentSubtask())
399                        .map(t => t.exitPromise());
400                    await Promise.allSettled(taskPromises);
401                }}
402                "#,
403            );
404        }
405
406        uwriteln!(
407            self.src,
408            r#"
409              const [task, {prefix}currentTaskID] = {start_current_task_fn}({{
410                  componentIdx: {component_instance_idx},
411                  isAsync: {is_async},
412                  isManualAsync: {is_manual_async},
413                  entryFnName: '{fn_name}',
414                  getCallbackFn: () => {callback_fn_js},
415                  callbackFnName: '{callback_fn_js}',
416                  errHandling: '{err_handling}',
417                  callingWasmExport: {calling_wasm_export},
418              }});
419            "#,
420        );
421    }
422}
423
424impl ManagesIntrinsics for FunctionBindgen<'_> {
425    /// Add an intrinsic, supplying it's name afterwards
426    fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
427        self.intrinsic(intrinsic);
428    }
429}
430
431impl Bindgen for FunctionBindgen<'_> {
432    type Operand = String;
433
434    /// Get the sizes and alignment for a given structure
435    fn sizes(&self) -> &SizeAlign {
436        self.sizes
437    }
438
439    /// Push a new block of code
440    fn push_block(&mut self) {
441        let prev = mem::take(&mut self.src);
442        self.block_storage.push(prev);
443    }
444
445    /// Finish a block of code
446    fn finish_block(&mut self, operands: &mut Vec<String>) {
447        let to_restore = self.block_storage.pop().unwrap();
448        let src = mem::replace(&mut self.src, to_restore);
449        self.blocks.push((src.into(), mem::take(operands)));
450    }
451
452    /// Output the return pointer
453    fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
454        unimplemented!("determining the return pointer for this function is not implemented");
455    }
456
457    /// Check whether a list of the given element type can be represented as a builtni JS type
458    ///
459    /// # Arguments
460    ///
461    /// * `resolve` - the [`Resolve`] that might be used to resolve nested types (i.e. [`Type::TypeId`])
462    /// * `elem_ty` - the [`Type`] of the element stored in the list
463    ///
464    fn is_list_canonical(&self, resolve: &Resolve, elem_ty: &Type) -> bool {
465        js_array_ty(resolve, elem_ty).is_some()
466    }
467
468    fn emit(
469        &mut self,
470        resolve: &Resolve,
471        inst: &Instruction<'_>,
472        operands: &mut Vec<String>,
473        results: &mut Vec<String>,
474    ) {
475        match inst {
476            Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
477
478            Instruction::I32Const { val } => results.push(val.to_string()),
479
480            Instruction::ConstZero { tys } => {
481                for t in tys.iter() {
482                    match t {
483                        WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
484                        WasmType::I32
485                        | WasmType::F32
486                        | WasmType::F64
487                        | WasmType::Pointer
488                        | WasmType::Length => results.push("0".to_string()),
489                    }
490                }
491            }
492
493            Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
494
495            Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
496
497            Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
498
499            Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
500
501            Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
502
503            Instruction::U64FromI64 => {
504                results.push(format!("BigInt.asUintN(64, BigInt({}))", operands[0]))
505            }
506
507            Instruction::S32FromI32 | Instruction::S64FromI64 => {
508                results.push(operands.pop().unwrap())
509            }
510
511            Instruction::I32FromU8 => {
512                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
513                results.push(format!("{conv}({op})", op = operands[0]))
514            }
515
516            Instruction::I32FromS8 => {
517                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
518                results.push(format!("{conv}({op})", op = operands[0]))
519            }
520
521            Instruction::I32FromU16 => {
522                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
523                results.push(format!("{conv}({op})", op = operands[0]))
524            }
525
526            Instruction::I32FromS16 => {
527                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
528                results.push(format!("{conv}({op})", op = operands[0]))
529            }
530
531            Instruction::I32FromU32 => {
532                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
533                results.push(format!("{conv}({op})", op = operands[0]))
534            }
535
536            Instruction::I32FromS32 => {
537                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
538                results.push(format!("{conv}({op})", op = operands[0]))
539            }
540
541            Instruction::I64FromU64 => {
542                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
543                results.push(format!("{conv}({op})", op = operands[0]))
544            }
545
546            Instruction::I64FromS64 => {
547                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
548                results.push(format!("{conv}({op})", op = operands[0]))
549            }
550
551            Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
552                results.push(operands.pop().unwrap())
553            }
554
555            Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
556                results.push(format!("+{}", operands[0]))
557            }
558
559            Instruction::CharFromI32 => {
560                let validate =
561                    self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
562                results.push(format!("{}({})", validate, operands[0]));
563            }
564
565            Instruction::I32FromChar => {
566                let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
567                results.push(format!("{}({})", validate, operands[0]));
568            }
569
570            Instruction::Bitcasts { casts } => {
571                for (cast, op) in casts.iter().zip(operands) {
572                    results.push(self.bitcast(cast, op));
573                }
574            }
575
576            Instruction::BoolFromI32 => {
577                let tmp = self.tmp();
578                uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
579                if self.valid_lifting_optimization {
580                    results.push(format!("!!bool{tmp}"));
581                } else {
582                    let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
583                    results.push(format!(
584                        "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
585                    ));
586                }
587            }
588
589            Instruction::I32FromBool => {
590                results.push(format!("{} ? 1 : 0", operands[0]));
591            }
592
593            Instruction::RecordLower { record, .. } => {
594                // use destructuring field access to get each
595                // field individually.
596                let tmp = self.tmp();
597                let mut expr = "var {".to_string();
598                for (i, field) in record.fields.iter().enumerate() {
599                    if i > 0 {
600                        expr.push_str(", ");
601                    }
602                    let name = format!("v{tmp}_{i}");
603                    expr.push_str(&field.name.to_lower_camel_case());
604                    expr.push_str(": ");
605                    expr.push_str(&name);
606                    results.push(name);
607                }
608                uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
609            }
610
611            Instruction::RecordLift { record, .. } => {
612                // records are represented as plain objects, so we
613                // make a new object and set all the fields with an object
614                // literal.
615                let mut result = "{\n".to_string();
616                for (field, op) in record.fields.iter().zip(operands) {
617                    result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
618                }
619                result.push('}');
620                results.push(result);
621            }
622
623            Instruction::TupleLower { tuple, .. } => {
624                // Tuples are represented as an array, sowe can use
625                // destructuring assignment to lower the tuple into its
626                // components.
627                let tmp = self.tmp();
628                let mut expr = "var [".to_string();
629                for i in 0..tuple.types.len() {
630                    if i > 0 {
631                        expr.push_str(", ");
632                    }
633                    let name = format!("tuple{tmp}_{i}");
634                    expr.push_str(&name);
635                    results.push(name);
636                }
637                uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
638            }
639
640            Instruction::TupleLift { .. } => {
641                // Tuples are represented as an array, so we just shove all
642                // the operands into an array.
643                results.push(format!("[{}]", operands.join(", ")));
644            }
645
646            Instruction::FlagsLower { flags, .. } => {
647                let op0 = &operands[0];
648
649                // Generate the result names.
650                for _ in 0..flags.repr().count() {
651                    let tmp = self.tmp();
652                    let name = format!("flags{tmp}");
653                    // Default to 0 so that in the null/undefined case, everything is false by
654                    // default.
655                    uwrite!(self.src, "let {name} = 0;\n");
656                    results.push(name);
657                }
658
659                uwrite!(
660                    self.src,
661                    "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
662                );
663
664                for (i, chunk) in flags.flags.chunks(32).enumerate() {
665                    let result_name = &results[i];
666
667                    uwrite!(self.src, "{result_name} = ");
668                    for (i, flag) in chunk.iter().enumerate() {
669                        if i != 0 {
670                            uwrite!(self.src, " | ");
671                        }
672
673                        let flag = flag.name.to_lower_camel_case();
674                        uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
675                    }
676                    uwrite!(self.src, ";\n");
677                }
678
679                uwrite!(
680                            self.src,
681                            "\
682                    }} else if ({op0} !== null && {op0} !== undefined) {{
683                        throw new TypeError('only an object, undefined or null can be converted to flags');
684                    }}
685                ");
686
687                // We don't need to do anything else for the null/undefined
688                // case, since that's interpreted as everything false, and we
689                // already defaulted everyting to 0.
690            }
691
692            Instruction::FlagsLift { flags, .. } => {
693                let tmp = self.tmp();
694                results.push(format!("flags{tmp}"));
695
696                if let Some(op) = operands.last() {
697                    // We only need an extraneous bits check if the number of flags isn't a multiple
698                    // of 32, because if it is then all the bits are used and there are no
699                    // extraneous bits.
700                    if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
701                        let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
702                        uwriteln!(
703                            self.src,
704                            "if (({op} & {mask}) !== 0) {{
705                                throw new TypeError('flags have extraneous bits set');
706                            }}"
707                        );
708                    }
709                }
710
711                uwriteln!(self.src, "var flags{tmp} = {{");
712
713                for (i, flag) in flags.flags.iter().enumerate() {
714                    let flag = flag.name.to_lower_camel_case();
715                    let op = &operands[i / 32];
716                    let mask: u32 = 1 << (i % 32);
717                    uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
718                }
719
720                uwriteln!(self.src, "}};");
721            }
722
723            Instruction::VariantPayloadName => results.push("e".to_string()),
724
725            Instruction::VariantLower {
726                variant,
727                results: result_types,
728                name,
729                ..
730            } => {
731                let blocks = self
732                    .blocks
733                    .drain(self.blocks.len() - variant.cases.len()..)
734                    .collect::<Vec<_>>();
735                let tmp = self.tmp();
736                let op = &operands[0];
737                uwriteln!(self.src, "var variant{tmp} = {op};");
738
739                for i in 0..result_types.len() {
740                    uwriteln!(self.src, "let variant{tmp}_{i};");
741                    results.push(format!("variant{tmp}_{i}"));
742                }
743
744                let expr_to_match = format!("variant{tmp}.tag");
745
746                uwriteln!(self.src, "switch ({expr_to_match}) {{");
747                for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
748                    uwriteln!(self.src, "case '{}': {{", case.name.as_str());
749                    if case.ty.is_some() {
750                        uwriteln!(self.src, "const e = variant{tmp}.val;");
751                    }
752                    self.src.push_str(&block);
753
754                    for (i, result) in block_results.iter().enumerate() {
755                        uwriteln!(self.src, "variant{tmp}_{i} = {result};");
756                    }
757                    uwriteln!(
758                        self.src,
759                        "break;
760                        }}"
761                    );
762                }
763                let variant_name = name.to_upper_camel_case();
764                uwriteln!(
765                    self.src,
766                    r#"default: {{
767                        throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
768                    }}"#,
769                );
770                uwriteln!(self.src, "}}");
771            }
772
773            Instruction::VariantLift { variant, name, .. } => {
774                let blocks = self
775                    .blocks
776                    .drain(self.blocks.len() - variant.cases.len()..)
777                    .collect::<Vec<_>>();
778
779                let tmp = self.tmp();
780                let op = &operands[0];
781
782                uwriteln!(
783                    self.src,
784                    "let variant{tmp};
785                    switch ({op}) {{"
786                );
787
788                for (i, (case, (block, block_results))) in
789                    variant.cases.iter().zip(blocks).enumerate()
790                {
791                    let tag = case.name.as_str();
792                    uwriteln!(
793                        self.src,
794                        "case {i}: {{
795                            {block}\
796                            variant{tmp} = {{
797                                tag: '{tag}',"
798                    );
799                    if case.ty.is_some() {
800                        assert!(block_results.len() == 1);
801                        uwriteln!(self.src, "   val: {}", block_results[0]);
802                    } else {
803                        assert!(block_results.is_empty());
804                    }
805                    uwriteln!(
806                        self.src,
807                        "   }};
808                        break;
809                        }}"
810                    );
811                }
812                let variant_name = name.to_upper_camel_case();
813                if !self.valid_lifting_optimization {
814                    uwriteln!(
815                        self.src,
816                        "default: {{
817                            throw new TypeError('invalid variant discriminant for {variant_name}');
818                        }}",
819                    );
820                }
821                uwriteln!(self.src, "}}");
822                results.push(format!("variant{tmp}"));
823            }
824
825            Instruction::OptionLower {
826                payload,
827                results: result_types,
828                ..
829            } => {
830                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
831                let (mut some, some_results) = self.blocks.pop().unwrap();
832                let (mut none, none_results) = self.blocks.pop().unwrap();
833
834                let tmp = self.tmp();
835                let op = &operands[0];
836                uwriteln!(self.src, "var variant{tmp} = {op};");
837
838                for i in 0..result_types.len() {
839                    uwriteln!(self.src, "let variant{tmp}_{i};");
840                    results.push(format!("variant{tmp}_{i}"));
841
842                    let some_result = &some_results[i];
843                    let none_result = &none_results[i];
844                    uwriteln!(some, "variant{tmp}_{i} = {some_result};");
845                    uwriteln!(none, "variant{tmp}_{i} = {none_result};");
846                }
847
848                if maybe_null(resolve, payload) {
849                    uwriteln!(
850                        self.src,
851                        r#"switch (variant{tmp}.tag) {{
852                            case 'none': {{
853                                {none}
854                                break;
855                            }}
856                            case 'some': {{
857                                const e = variant{tmp}.val;
858                                {some}
859                                break;
860                            }}
861                            default: {{
862                                {debug_log_fn}("ERROR: invalid value (expected option as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
863                                throw new TypeError('invalid variant specified for option');
864                            }}
865                        }}"#,
866                    );
867                } else {
868                    uwriteln!(
869                        self.src,
870                        "if (variant{tmp} === null || variant{tmp} === undefined) {{
871                            {none}\
872                        }} else {{
873                            const e = variant{tmp};
874                            {some}\
875                        }}"
876                    );
877                }
878            }
879
880            Instruction::OptionLift { payload, .. } => {
881                let (some, some_results) = self.blocks.pop().unwrap();
882                let (none, none_results) = self.blocks.pop().unwrap();
883                assert!(none_results.is_empty());
884                assert!(some_results.len() == 1);
885                let some_result = &some_results[0];
886
887                let tmp = self.tmp();
888                let op = &operands[0];
889
890                let (v_none, v_some) = if maybe_null(resolve, payload) {
891                    (
892                        "{ tag: 'none' }",
893                        format!(
894                            "{{
895                                tag: 'some',
896                                val: {some_result}
897                            }}"
898                        ),
899                    )
900                } else {
901                    ("undefined", some_result.into())
902                };
903
904                if !self.valid_lifting_optimization {
905                    uwriteln!(
906                        self.src,
907                        "let variant{tmp};
908                        switch ({op}) {{
909                            case 0: {{
910                                {none}\
911                                variant{tmp} = {v_none};
912                                break;
913                            }}
914                            case 1: {{
915                                {some}\
916                                variant{tmp} = {v_some};
917                                break;
918                            }}
919                            default: {{
920                                throw new TypeError('invalid variant discriminant for option');
921                            }}
922                        }}",
923                    );
924                } else {
925                    uwriteln!(
926                        self.src,
927                        "let variant{tmp};
928                        if ({op}) {{
929                            {some}\
930                            variant{tmp} = {v_some};
931                        }} else {{
932                            {none}\
933                            variant{tmp} = {v_none};
934                        }}"
935                    );
936                }
937
938                results.push(format!("variant{tmp}"));
939            }
940
941            Instruction::ResultLower {
942                results: result_types,
943                ..
944            } => {
945                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
946                let (mut err, err_results) = self.blocks.pop().unwrap();
947                let (mut ok, ok_results) = self.blocks.pop().unwrap();
948
949                let tmp = self.tmp();
950                let op = &operands[0];
951                uwriteln!(self.src, "var variant{tmp} = {op};");
952
953                for i in 0..result_types.len() {
954                    uwriteln!(self.src, "let variant{tmp}_{i};");
955                    results.push(format!("variant{tmp}_{i}"));
956
957                    let ok_result = &ok_results[i];
958                    let err_result = &err_results[i];
959                    uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
960                    uwriteln!(err, "variant{tmp}_{i} = {err_result};");
961                }
962
963                uwriteln!(
964                    self.src,
965                    r#"switch (variant{tmp}.tag) {{
966                        case 'ok': {{
967                            const e = variant{tmp}.val;
968                            {ok}
969                            break;
970                        }}
971                        case 'err': {{
972                            const e = variant{tmp}.val;
973                            {err}
974                            break;
975                        }}
976                        default: {{
977                            {debug_log_fn}("ERROR: invalid value (expected result as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
978                            throw new TypeError('invalid variant specified for result');
979                        }}
980                    }}"#,
981                );
982            }
983
984            Instruction::ResultLift { result, .. } => {
985                let (err, err_results) = self.blocks.pop().unwrap();
986                let (ok, ok_results) = self.blocks.pop().unwrap();
987                let ok_result = if result.ok.is_some() {
988                    assert_eq!(ok_results.len(), 1);
989                    ok_results[0].to_string()
990                } else {
991                    assert_eq!(ok_results.len(), 0);
992                    String::from("undefined")
993                };
994                let err_result = if result.err.is_some() {
995                    assert_eq!(err_results.len(), 1);
996                    err_results[0].to_string()
997                } else {
998                    assert_eq!(err_results.len(), 0);
999                    String::from("undefined")
1000                };
1001                let tmp = self.tmp();
1002                let op0 = &operands[0];
1003
1004                if !self.valid_lifting_optimization {
1005                    uwriteln!(
1006                        self.src,
1007                        "let variant{tmp};
1008                        switch ({op0}) {{
1009                            case 0: {{
1010                                {ok}\
1011                                variant{tmp} = {{
1012                                    tag: 'ok',
1013                                    val: {ok_result}
1014                                }};
1015                                break;
1016                            }}
1017                            case 1: {{
1018                                {err}\
1019                                variant{tmp} = {{
1020                                    tag: 'err',
1021                                    val: {err_result}
1022                                }};
1023                                break;
1024                            }}
1025                            default: {{
1026                                throw new TypeError('invalid variant discriminant for expected');
1027                            }}
1028                        }}",
1029                    );
1030                } else {
1031                    uwriteln!(
1032                        self.src,
1033                        "let variant{tmp};
1034                        if ({op0}) {{
1035                            {err}\
1036                            variant{tmp} = {{
1037                                tag: 'err',
1038                                val: {err_result}
1039                            }};
1040                        }} else {{
1041                            {ok}\
1042                            variant{tmp} = {{
1043                                tag: 'ok',
1044                                val: {ok_result}
1045                            }};
1046                        }}"
1047                    );
1048                }
1049                results.push(format!("variant{tmp}"));
1050            }
1051
1052            Instruction::EnumLower { name, enum_, .. } => {
1053                let tmp = self.tmp();
1054
1055                let op = &operands[0];
1056                uwriteln!(self.src, "var val{tmp} = {op};");
1057
1058                // Declare a variable to hold the result.
1059                uwriteln!(
1060                    self.src,
1061                    "let enum{tmp};
1062                    switch (val{tmp}) {{"
1063                );
1064                for (i, case) in enum_.cases.iter().enumerate() {
1065                    uwriteln!(
1066                        self.src,
1067                        "case '{case}': {{
1068                            enum{tmp} = {i};
1069                            break;
1070                        }}",
1071                        case = case.name
1072                    );
1073                }
1074                uwriteln!(self.src, "default: {{");
1075                if !self.valid_lifting_optimization {
1076                    uwriteln!(
1077                        self.src,
1078                        "if (({op}) instanceof Error) {{
1079                        console.error({op});
1080                    }}"
1081                    );
1082                }
1083                uwriteln!(
1084                            self.src,
1085                            "
1086                            throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
1087                        }}
1088                    }}",
1089                        );
1090
1091                results.push(format!("enum{tmp}"));
1092            }
1093
1094            Instruction::EnumLift { name, enum_, .. } => {
1095                let tmp = self.tmp();
1096
1097                uwriteln!(
1098                    self.src,
1099                    "let enum{tmp};
1100                    switch ({}) {{",
1101                    operands[0]
1102                );
1103                for (i, case) in enum_.cases.iter().enumerate() {
1104                    uwriteln!(
1105                        self.src,
1106                        "case {i}: {{
1107                            enum{tmp} = '{case}';
1108                            break;
1109                        }}",
1110                        case = case.name
1111                    );
1112                }
1113                if !self.valid_lifting_optimization {
1114                    let name = name.to_upper_camel_case();
1115                    uwriteln!(
1116                        self.src,
1117                        "default: {{
1118                            throw new TypeError('invalid discriminant specified for {name}');
1119                        }}",
1120                    );
1121                }
1122                uwriteln!(self.src, "}}");
1123
1124                results.push(format!("enum{tmp}"));
1125            }
1126
1127            // The ListCanonLower instruction is called on async function parameter lowers,
1128            // which are separated in memory by one pointer follow.
1129            //
1130            // We ignore `realloc` in the instruction because it's the name of the *import* from the
1131            // component's side (i.e. `"cabi_realloc"`). Bindings have already set up the appropriate
1132            // realloc for the current component (e.g. `realloc0`) and it is available in the bindgen
1133            // object @ `self.realloc`
1134            //
1135            // Note that this can be called *inside* a "regular" ListCanonLower, for example
1136            // when a list of lists or list of Uint8Arrays is sent.
1137            //
1138            Instruction::ListCanonLower { element, .. } => {
1139                let tmp = self.tmp();
1140                let memory = self.memory.as_ref().unwrap();
1141                let realloc = self.realloc.unwrap();
1142
1143                // Gather metadata about list element
1144                let size = self.sizes.size(element).size_wasm32();
1145                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1146
1147                // Alias the list to a local variable
1148                uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1149                if matches!(element, Type::U8) {
1150                    uwriteln!(
1151                        self.src,
1152                        "var len{tmp} = Array.isArray(val{tmp}) ? val{tmp}.length : val{tmp}.byteLength;"
1153                    );
1154                } else {
1155                    uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1156                }
1157
1158                // Allocate space for the type in question
1159                uwriteln!(
1160                    self.src,
1161                    "var ptr{tmp} = {realloc_call}(0, 0, {align}, len{tmp} * {size});",
1162                    realloc_call = if self.is_async {
1163                        format!("await {realloc}")
1164                    } else {
1165                        realloc.to_string()
1166                    },
1167                );
1168
1169                // We may or may not be dealing with a buffer like object or a regular JS array,
1170                // in which case we can detect and use the right value
1171
1172                // Determine what methods to use with a DataView when setting the data
1173                let dataview_set_method = match element {
1174                    Type::Bool | Type::U8 => "setUint8",
1175                    Type::U16 => "setUint16",
1176                    Type::U32 => "setUint32",
1177                    Type::U64 => "setBigUint64",
1178                    Type::S8 => "setInt8",
1179                    Type::S16 => "setInt16",
1180                    Type::S32 => "setInt32",
1181                    Type::S64 => "setBigInt64",
1182                    Type::F32 => "setFloat32",
1183                    Type::F64 => "setFloat64",
1184                    _ => unreachable!("unsupported type [{element:?}] for canonical list lower"),
1185                };
1186
1187                // Detect whether we're dealing with a regular array
1188                uwriteln!(
1189                    self.src,
1190                    r#"
1191                        let valData{tmp};
1192                        const valLenBytes{tmp} = len{tmp} * {size};
1193                        if (Array.isArray(val{tmp})) {{
1194                            // Regular array likely containing numbers, write values to memory
1195                            let offset = 0;
1196                            const dv{tmp} = new DataView({memory}.buffer);
1197                            for (const v of val{tmp}) {{
1198                                dv{tmp}.{dataview_set_method}(ptr{tmp} + offset, v, true);
1199                                offset += {size};
1200                            }}
1201                        }} else {{
1202                            // TypedArray / ArrayBuffer-like, direct copy
1203                            valData{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, valLenBytes{tmp});
1204                            const out{tmp} = new Uint8Array({memory}.buffer, ptr{tmp},valLenBytes{tmp});
1205                            out{tmp}.set(valData{tmp});
1206                        }}
1207                    "#,
1208                );
1209
1210                results.push(format!("ptr{tmp}"));
1211                results.push(format!("len{tmp}"));
1212            }
1213
1214            Instruction::ListCanonLift { element, .. } => {
1215                let tmp = self.tmp();
1216                let memory = self.memory.as_ref().unwrap();
1217                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1218                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1219                uwriteln!(
1220                    self.src,
1221                    "var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {elem_size}));",
1222                    elem_size = self.sizes.size(element).size_wasm32(),
1223                    array_ty = js_array_ty(resolve, element).unwrap(), // TODO: this is the wrong endianness
1224                );
1225                results.push(format!("result{tmp}"));
1226            }
1227
1228            Instruction::StringLower { .. } => {
1229                // Only Utf8 and Utf16 supported for now
1230                assert!(matches!(
1231                    self.encoding,
1232                    StringEncoding::UTF8 | StringEncoding::UTF16
1233                ));
1234
1235                let (call_prefix, encode_intrinsic) = match (self.encoding, self.is_async) {
1236                    (StringEncoding::UTF16, true) => (
1237                        "await ",
1238                        Intrinsic::String(StringIntrinsic::Utf16EncodeAsync),
1239                    ),
1240                    (StringEncoding::UTF16, false) => {
1241                        ("", Intrinsic::String(StringIntrinsic::Utf16Encode))
1242                    }
1243                    (StringEncoding::UTF8, true) => (
1244                        "await ",
1245                        Intrinsic::String(StringIntrinsic::Utf8EncodeAsync),
1246                    ),
1247                    (StringEncoding::UTF8, false) => {
1248                        ("", Intrinsic::String(StringIntrinsic::Utf8Encode))
1249                    }
1250                    _ => unreachable!("unsupported encoding {}", self.encoding),
1251                };
1252                let encode = self.intrinsic(encode_intrinsic);
1253
1254                let tmp = self.tmp();
1255                let memory = self.memory.as_ref().unwrap();
1256                let str = String::from("cabi_realloc");
1257                let realloc = self.realloc.unwrap_or(&str);
1258                let s = &operands[0];
1259                uwriteln!(
1260                    self.src,
1261                    r#"
1262                      var encodeRes = {call_prefix}{encode}({s}, {realloc}, {memory});
1263                      var ptr{tmp} = encodeRes.ptr;
1264                      var len{tmp} = {encoded_len};
1265                    "#,
1266                    encoded_len = match self.encoding {
1267                        StringEncoding::UTF8 => "encodeRes.len".into(),
1268                        _ => format!("{}.length", s),
1269                    }
1270                );
1271                results.push(format!("ptr{tmp}"));
1272                results.push(format!("len{tmp}"));
1273            }
1274
1275            Instruction::StringLift => {
1276                // Only Utf8 and Utf16 supported for now
1277                assert!(matches!(
1278                    self.encoding,
1279                    StringEncoding::UTF8 | StringEncoding::UTF16
1280                ));
1281                let decoder = self.intrinsic(match self.encoding {
1282                    StringEncoding::UTF16 => Intrinsic::String(StringIntrinsic::Utf16Decoder),
1283                    _ => Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8),
1284                });
1285                let tmp = self.tmp();
1286                let memory = self.memory.as_ref().unwrap();
1287                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1288                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1289                uwriteln!(
1290                    self.src,
1291                    "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
1292                    if self.encoding == StringEncoding::UTF16 {
1293                        "16"
1294                    } else {
1295                        "8"
1296                    }
1297                );
1298                results.push(format!("result{tmp}"));
1299            }
1300
1301            Instruction::ListLower { element, .. } => {
1302                let (body, body_results) = self.blocks.pop().unwrap();
1303                assert!(body_results.is_empty());
1304                let tmp = self.tmp();
1305                let vec = format!("vec{tmp}");
1306                let result = format!("result{tmp}");
1307                let len = format!("len{tmp}");
1308                let size = self.sizes.size(element).size_wasm32();
1309                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1310
1311                // first store our vec-to-lower in a temporary since we'll
1312                // reference it multiple times.
1313                uwriteln!(self.src, "var {vec} = {};", operands[0]);
1314                uwriteln!(self.src, "var {len} = {vec}.length;");
1315
1316                // ... then realloc space for the result in the guest module
1317                let realloc = self.realloc.as_ref().unwrap();
1318                uwriteln!(
1319                    self.src,
1320                    "var {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1321                    realloc_call = if self.is_async {
1322                        format!("await {realloc}")
1323                    } else {
1324                        realloc.to_string()
1325                    },
1326                );
1327
1328                // ... then consume the vector and use the block to lower the
1329                // result.
1330                uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1331                uwriteln!(self.src, "const e = {vec}[i];");
1332                uwrite!(self.src, "const base = {result} + i * {size};");
1333                self.src.push_str(&body);
1334                uwrite!(self.src, "}}\n");
1335
1336                results.push(result);
1337                results.push(len);
1338            }
1339
1340            Instruction::ListLift { element, .. } => {
1341                let (body, body_results) = self.blocks.pop().unwrap();
1342                let tmp = self.tmp();
1343                let size = self.sizes.size(element).size_wasm32();
1344                let len = format!("len{tmp}");
1345                uwriteln!(self.src, "var {len} = {};", operands[1]);
1346                let base = format!("base{tmp}");
1347                uwriteln!(self.src, "var {base} = {};", operands[0]);
1348                let result = format!("result{tmp}");
1349                uwriteln!(self.src, "var {result} = [];");
1350                results.push(result.clone());
1351
1352                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1353                uwriteln!(self.src, "const base = {base} + i * {size};");
1354                self.src.push_str(&body);
1355                assert_eq!(body_results.len(), 1);
1356                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1357                uwrite!(self.src, "}}\n");
1358            }
1359
1360            Instruction::FixedLengthListLower { size, .. } => {
1361                let tmp = self.tmp();
1362                let array = format!("array{tmp}");
1363                uwriteln!(self.src, "const {array} = {};", operands[0]);
1364                for i in 0..*size {
1365                    results.push(format!("{array}[{i}]"));
1366                }
1367            }
1368
1369            Instruction::FixedLengthListLift { .. } => {
1370                let tmp = self.tmp();
1371                let result = format!("result{tmp}");
1372                uwriteln!(self.src, "const {result} = [{}];", operands.join(", "));
1373                results.push(result);
1374            }
1375
1376            Instruction::FixedLengthListLowerToMemory {
1377                element, size: len, ..
1378            } => {
1379                let (body, body_results) = self.blocks.pop().unwrap();
1380                assert!(body_results.is_empty());
1381
1382                let tmp = self.tmp();
1383                let array = format!("array{tmp}");
1384                uwriteln!(self.src, "const {array} = {};", operands[0]);
1385                let addr = format!("addr{tmp}");
1386                uwriteln!(self.src, "const {addr} = {};", operands[1]);
1387                let elem_size = self.sizes.size(element).size_wasm32();
1388
1389                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1390                uwriteln!(self.src, "const e = {array}[i];");
1391                uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1392                self.src.push_str(&body);
1393                uwrite!(self.src, "}}\n");
1394            }
1395
1396            Instruction::FixedLengthListLiftFromMemory {
1397                element, size: len, ..
1398            } => {
1399                let (body, body_results) = self.blocks.pop().unwrap();
1400                assert_eq!(body_results.len(), 1);
1401
1402                let tmp = self.tmp();
1403                let addr = format!("addr{tmp}");
1404                uwriteln!(self.src, "const {addr} = {};", operands[0]);
1405                let elem_size = self.sizes.size(element).size_wasm32();
1406                let result = format!("result{tmp}");
1407                uwriteln!(self.src, "const {result} = [];");
1408                results.push(result.clone());
1409
1410                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1411                uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1412                self.src.push_str(&body);
1413                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1414                uwrite!(self.src, "}}\n");
1415            }
1416
1417            Instruction::IterElem { .. } => results.push("e".to_string()),
1418
1419            Instruction::IterBasePointer => results.push("base".to_string()),
1420
1421            Instruction::CallWasm { name, sig } => {
1422                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1423                let has_post_return = self.post_return.is_some();
1424                let is_async = self.is_async;
1425                uwriteln!(
1426                    self.src,
1427                    "{debug_log_fn}('{prefix} [Instruction::CallWasm] enter', {{
1428                         funcName: '{name}',
1429                         paramCount: {param_count},
1430                         async: {is_async},
1431                         postReturn: {has_post_return},
1432                      }});",
1433                    param_count = sig.params.len(),
1434                    prefix = self.tracing_prefix,
1435                );
1436
1437                // Write out whether the caller was host provided
1438                // (if we're calling into wasm then we know it was not)
1439                uwriteln!(self.src, "const hostProvided = false;");
1440
1441                // Inject machinery for starting a 'current' task
1442                // (this will define the 'task' variable)
1443                self.start_current_task(inst);
1444
1445                // TODO: trap if this component is already on the call stack (re-entrancy)
1446
1447                // TODO(threads): start a thread
1448                // TODO(threads): Task#enter needs to be called with the thread that is executing (inside thread_func)
1449                // TODO(threads): thread_func will contain the actual call rather than attempting to execute immediately
1450
1451                // If we're dealing with an async task, do explicit task enter
1452                if self.is_async || self.requires_async_porcelain {
1453                    uwriteln!(
1454                        self.src,
1455                        r#"
1456                        const started = await task.enter();
1457                        if (!started) {{
1458                            {debug_log_fn}('[Instruction::AsyncTaskReturn] failed to enter task', {{
1459                                taskID: preparedTask.id(),
1460                                subtaskID: currentSubtask?.id(),
1461                            }});
1462                            throw new Error("failed to enter task");
1463                        }}
1464                        "#,
1465                    );
1466                } else {
1467                    uwriteln!(self.src, "const started = task.enterSync();",);
1468                }
1469
1470                // Save the memory for this task,
1471                // which will be used for any subtasks that might be spawned
1472                if let Some(mem_idx) = self.canon_opts.memory() {
1473                    let idx = mem_idx.as_u32();
1474                    uwriteln!(self.src, "task.setReturnMemoryIdx({idx});");
1475                    uwriteln!(self.src, "task.setReturnMemory(memory{idx});");
1476                }
1477
1478                // Output result binding preamble (e.g. 'var ret =', 'var [ ret0, ret1] = exports...() ')
1479                // along with the code to perofrm the call
1480                let sig_results_length = sig.results.len();
1481                let s = self.generate_result_assignment_lhs(sig_results_length, results, is_async);
1482
1483                let (call_prefix, call_wrapper) = if self.requires_async_porcelain | self.is_async {
1484                    ("await ", Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name())
1485                } else {
1486                    ("", Intrinsic::WithGlobalCurrentTaskMetaFn.name())
1487                };
1488                uwriteln!(
1489                    self.src,
1490                    r#"{s} {call_prefix} {call_wrapper}({{
1491                              taskID: task.id(),
1492                              componentIdx: task.componentIdx(),
1493                              fn: () => {callee}({args}),
1494                          }});
1495                          "#,
1496                    callee = self.callee,
1497                    args = operands.join(", "),
1498                );
1499
1500                if self.tracing_enabled {
1501                    let prefix = self.tracing_prefix;
1502                    let to_result_string =
1503                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1504                    uwriteln!(
1505                        self.src,
1506                        "console.error(`{prefix} return {}`);",
1507                        if sig_results_length > 0 || !results.is_empty() {
1508                            format!("result=${{{to_result_string}(ret)}}")
1509                        } else {
1510                            "".to_string()
1511                        }
1512                    );
1513                }
1514            }
1515
1516            // Call to an imported interface (normally provided by the host)
1517            Instruction::CallInterface { func, async_ } => {
1518                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1519                let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
1520                    AsyncTaskIntrinsic::CreateNewCurrentTask,
1521                ));
1522                let current_task_get_fn =
1523                    self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1524                let component_instance_idx = self.canon_opts.instance.as_u32();
1525
1526                uwriteln!(
1527                    self.src,
1528                    "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ enter)');",
1529                    prefix = self.tracing_prefix,
1530                    async_ = async_.then_some("async").unwrap_or("sync"),
1531                );
1532
1533                // Determine the callee function and arguments
1534                let (callee_fn_js, callee_args_js) = if self.callee_resource_dynamic {
1535                    (
1536                        format!("{}.{}", operands[0], self.callee),
1537                        operands[1..].join(", "),
1538                    )
1539                } else {
1540                    (self.callee.into(), operands.join(", "))
1541                };
1542
1543                uwriteln!(self.src, "let hostProvided = true;");
1544
1545                // Start the necessary subtasks and/or host task
1546                //
1547                // We must create a subtask in the case of an async host import.
1548                //
1549                // If there's no parent task, we're not executing in a subtask situation,
1550                // so we can just create the new task and immediately continue execution.
1551                //
1552                // If there *is* a parent task, then we are likely about to create new task that
1553                // matches/belongs to an existing subtask in the parent task.
1554                //
1555                // If we're dealing with a function that has been marked as a host import, then
1556                // we expect that `Trampoline::LowerImport` and relevant intrinsics were called before
1557                // this, and a subtask has been set up.
1558                //
1559                uwriteln!(
1560                    self.src,
1561                    r#"
1562                    let parentTask;
1563                    let task;
1564                    let subtask;
1565
1566                    const createTask = () => {{
1567                        const results = {start_current_task_fn}({{
1568                            componentIdx: -1, // {component_instance_idx},
1569                            isAsync: {is_async},
1570                            entryFnName: '{fn_name}',
1571                            getCallbackFn: () => {callback_fn_js},
1572                            callbackFnName: '{callback_fn_js}',
1573                            errHandling: '{err_handling}',
1574                            callingWasmExport: false,
1575                        }});
1576                        task = results[0];
1577                    }};
1578
1579                    taskCreation: {{
1580                        parentTask = {current_task_get_fn}({component_instance_idx})?.task;
1581                        if (!parentTask) {{
1582                            createTask();
1583                            break taskCreation;
1584                        }}
1585
1586                        createTask();
1587
1588                        if (hostProvided) {{
1589                            subtask = parentTask.getLatestSubtask();
1590                            if (!subtask) {{
1591                                throw new Error(`Missing subtask (in parent task [${{parentTask.id()}}]) for host import, has the import been lowered? (ensure asyncImports are set properly)`);
1592                            }}
1593                            task.setParentSubtask(subtask);
1594                        }}
1595                    }}
1596                    "#,
1597                    is_async = self.is_async,
1598                    fn_name = self.callee,
1599                    err_handling = self.err.to_js_string(),
1600                    callback_fn_js = self
1601                        .canon_opts
1602                        .callback
1603                        .as_ref()
1604                        .map(|v| format!("callback_{}", v.as_u32()))
1605                        .unwrap_or_else(|| "null".into()),
1606                );
1607
1608                let is_async = self.requires_async_porcelain || *async_;
1609
1610                // If we're async then we *know* that there is a result, even if the functoin doesn't have one
1611                // at the CM level -- async functions always return
1612                let fn_wasm_result_count = if func.result.is_none() { 0 } else { 1 };
1613
1614                // If the task is async, do an explicit wait for backpressure before the call execution
1615                if is_async {
1616                    uwriteln!(
1617                        self.src,
1618                        r#"
1619                        const started = await task.enter({{ isHost: hostProvided }});
1620                        if (!started) {{
1621                            {debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
1622                                taskID: preparedTask.id(),
1623                                subtaskID: currentSubtask?.id(),
1624                            }});
1625                            throw new Error("failed to enter task");
1626                        }}
1627                        "#,
1628                    );
1629                } else {
1630                    uwriteln!(self.src, "const started = task.enterSync();",);
1631                }
1632
1633                // Build the JS expression that calls the callee
1634                let (call_prefix, call_wrapper) = if is_async || self.requires_async_porcelain {
1635                    ("await ", Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name())
1636                } else {
1637                    ("", Intrinsic::WithGlobalCurrentTaskMetaFn.name())
1638                };
1639                let call = format!(
1640                    r#"{call_prefix} {call_wrapper}({{
1641                             componentIdx: task.componentIdx(),
1642                             taskID: task.id(),
1643                             fn: () => {callee_fn_js}({callee_args_js})
1644                         }})
1645                        "#,
1646                );
1647
1648                match self.err {
1649                    // If configured to do *no* error handling at all or throw
1650                    // error objects directly, we can simply perform the call
1651                    ErrHandling::None | ErrHandling::ThrowResultErr => {
1652                        let s = self.generate_result_assignment_lhs(
1653                            fn_wasm_result_count,
1654                            results,
1655                            is_async,
1656                        );
1657                        uwriteln!(self.src, "{s}{call};");
1658                    }
1659                    // If configured to force all thrown errors into result objects,
1660                    // then we add a try/catch around the call
1661                    ErrHandling::ResultCatchHandler => {
1662                        // result<_, string> allows JS error coercion only, while
1663                        // any other result type will trap for arbitrary JS errors.
1664                        let err_payload = if let (_, Some(Type::Id(err_ty))) =
1665                            get_thrown_type(self.resolve, func.result).unwrap()
1666                        {
1667                            match &self.resolve.types[*err_ty].kind {
1668                                TypeDefKind::Type(Type::String) => {
1669                                    self.intrinsic(Intrinsic::GetErrorPayloadString)
1670                                }
1671                                _ => self.intrinsic(Intrinsic::GetErrorPayload),
1672                            }
1673                        } else {
1674                            self.intrinsic(Intrinsic::GetErrorPayload)
1675                        };
1676                        uwriteln!(
1677                            self.src,
1678                            r#"
1679                            let ret;
1680                            try {{
1681                                ret = {{ tag: 'ok', val: {call} }};
1682                            }} catch (e) {{
1683                                ret = {{ tag: 'err', val: {err_payload}(e) }};
1684                            }}
1685                            "#,
1686                        );
1687                        results.push("ret".to_string());
1688                    }
1689                }
1690
1691                if self.tracing_enabled {
1692                    let prefix = self.tracing_prefix;
1693                    let to_result_string =
1694                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1695                    uwriteln!(
1696                        self.src,
1697                        "console.error(`{prefix} return {}`);",
1698                        if fn_wasm_result_count > 0 || !results.is_empty() {
1699                            format!("result=${{{to_result_string}(ret)}}")
1700                        } else {
1701                            "".to_string()
1702                        }
1703                    );
1704                }
1705
1706                // TODO: if it was an async call, we may not be able to clear the borrows yet.
1707                // save them to the task/ensure they are added to the task's list of borrows?
1708                //
1709                // TODO: if there is a subtask, we must not clear borrows until subtask.deliverReturn
1710                // is called.
1711
1712                // After a high level call, we need to deactivate the component resource borrows.
1713                if self.clear_resource_borrows {
1714                    let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1715                    let cur_resource_borrows =
1716                        self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
1717                    let is_host = matches!(
1718                        self.resource_map.iter().nth(0).unwrap().1.data,
1719                        ResourceData::Host { .. }
1720                    );
1721
1722                    if is_host {
1723                        uwriteln!(
1724                            self.src,
1725                            "for (const rsc of {cur_resource_borrows}) {{
1726                                rsc[{symbol_resource_handle}] = undefined;
1727                            }}
1728                            {cur_resource_borrows} = [];"
1729                        );
1730                    } else {
1731                        uwriteln!(
1732                            self.src,
1733                            "for (const {{ rsc, drop }} of {cur_resource_borrows}) {{
1734                                if (rsc[{symbol_resource_handle}]) {{
1735                                    drop(rsc[{symbol_resource_handle}]);
1736                                    rsc[{symbol_resource_handle}] = undefined;
1737                                }}
1738                            }}
1739                            {cur_resource_borrows} = [];"
1740                        );
1741                    }
1742                    self.clear_resource_borrows = false;
1743                }
1744            }
1745
1746            Instruction::Return {
1747                func,
1748                amt: stack_value_count,
1749            } => {
1750                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1751                uwriteln!(
1752                    self.src,
1753                    "{debug_log_fn}('{prefix} [Instruction::Return]', {{
1754                         funcName: '{func_name}',
1755                         paramCount: {stack_value_count},
1756                         async: {is_async},
1757                         postReturn: {post_return_present}
1758                      }});",
1759                    func_name = func.name,
1760                    post_return_present = self.post_return.is_some(),
1761                    is_async = self.is_async,
1762                    prefix = self.tracing_prefix,
1763                );
1764
1765                // Build the post return functionality
1766                // to clean up tasks and possibly return values
1767                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
1768                    ComponentIntrinsic::GetOrCreateAsyncState,
1769                ));
1770                let gen_post_return_js =
1771                    |(post_return_call, ret_stmt): (String, Option<String>)| {
1772                        format!(
1773                            r#"
1774                        let cstate = {get_or_create_async_state_fn}({component_idx});
1775                        cstate.mayLeave = false;
1776                        {post_return_call}
1777                        cstate.mayLeave = true;
1778                        task.exit();
1779                        {ret_stmt}
1780                            "#,
1781                            component_idx = self.canon_opts.instance.as_u32(),
1782                            ret_stmt = ret_stmt.unwrap_or_default(),
1783                        )
1784                    };
1785
1786                assert!(!self.is_async, "async functions should use AsyncTaskReturn");
1787
1788                // Depending how many values are on the stack after returning, we must execute differently.
1789                //
1790                // In particular, if this function is async (distinct from whether async porcelain was necessary or not),
1791                // rather than simply executing the function we must return (or block for) the promise that was created
1792                // for the task.
1793                match stack_value_count {
1794                    // (sync) Handle no result case
1795                    0 => {
1796                        uwriteln!(self.src, "task.resolve([ret]);");
1797                        if let Some(f) = &self.post_return {
1798                            uwriteln!(
1799                                self.src,
1800                                "{post_return_js}",
1801                                post_return_js = gen_post_return_js((format!("{f}();"), None)),
1802                            );
1803                        } else {
1804                            uwriteln!(self.src, "task.exit();");
1805                        }
1806                    }
1807
1808                    // (sync) Handle single `result<t>` case
1809                    1 if self.err == ErrHandling::ThrowResultErr => {
1810                        let component_err = self.intrinsic(Intrinsic::ComponentError);
1811                        let op = &operands[0];
1812
1813                        uwriteln!(self.src, "const retCopy = {op};");
1814                        uwriteln!(self.src, "task.resolve([retCopy.val]);");
1815
1816                        if let Some(f) = &self.post_return {
1817                            uwriteln!(
1818                                self.src,
1819                                "{}",
1820                                gen_post_return_js((format!("{f}(ret);"), None))
1821                            );
1822                        } else {
1823                            uwriteln!(self.src, "task.exit();");
1824                        }
1825
1826                        uwriteln!(
1827                            self.src,
1828                            r#"
1829                              if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
1830                                  throw new {component_err}(retCopy.val);
1831                              }}
1832                              return retCopy.val;
1833                            "#
1834                        );
1835                    }
1836
1837                    // (sync) Handle all other cases (including single parameter non-result<t>)
1838                    stack_value_count => {
1839                        let ret_val = match stack_value_count {
1840                            0 => unreachable!(
1841                                "unexpectedly zero return values for synchronous return"
1842                            ),
1843                            1 => operands[0].to_string(),
1844                            _ => format!("[{}]", operands.join(", ")),
1845                        };
1846
1847                        uwriteln!(self.src, "task.resolve([{ret_val}]);");
1848
1849                        // Handle the post return if necessary
1850                        if let Some(post_return_fn) = self.post_return {
1851                            // In the case there is a post return function, we'll want to copy the value
1852                            // then perform the post return before leaving
1853
1854                            // Write out the assignment for the given return value
1855                            uwriteln!(self.src, "const retCopy = {ret_val};");
1856
1857                            // Generate the JS that should perform the post return w/ the result
1858                            // and pass a copy fo the result to the actual caller
1859                            let post_return_js = gen_post_return_js((
1860                                format!("{post_return_fn}(ret);"),
1861                                Some(["return retCopy;"].join("\n")),
1862                            ));
1863                            uwriteln!(self.src, "{post_return_js}");
1864                        } else {
1865                            uwriteln!(self.src, "task.exit();");
1866                            uwriteln!(self.src, "return {ret_val};")
1867                        }
1868                    }
1869                }
1870            }
1871
1872            Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1873
1874            Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1875
1876            Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1877
1878            Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1879
1880            Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1881
1882            Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
1883
1884            Instruction::I32Load16U { offset } => {
1885                self.load("getUint16", *offset, operands, results)
1886            }
1887
1888            Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
1889
1890            Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
1891
1892            Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
1893
1894            Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
1895
1896            Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
1897
1898            Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
1899
1900            Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
1901
1902            Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
1903
1904            Instruction::LengthLoad { offset } => {
1905                self.load("getUint32", *offset, operands, results)
1906            }
1907
1908            Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
1909
1910            Instruction::PointerLoad { offset } => {
1911                self.load("getUint32", *offset, operands, results)
1912            }
1913
1914            Instruction::Malloc { size, align, .. } => {
1915                let tmp = self.tmp();
1916                let realloc = self.realloc.as_ref().unwrap();
1917                let ptr = format!("ptr{tmp}");
1918                uwriteln!(
1919                    self.src,
1920                    "var {ptr} = {realloc_call}(0, 0, {align}, {size});",
1921                    align = align.align_wasm32(),
1922                    realloc_call = if self.is_async {
1923                        format!("await {realloc}")
1924                    } else {
1925                        realloc.to_string()
1926                    },
1927                    size = size.size_wasm32()
1928                );
1929                results.push(ptr);
1930            }
1931
1932            Instruction::HandleLift { handle, .. } => {
1933                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1934                let resource_ty = &crate::dealias(self.resolve, *ty);
1935                let ResourceTable { imported, data } = &self.resource_map[resource_ty];
1936
1937                let is_own = matches!(handle, Handle::Own(_));
1938                let rsc = format!("rsc{}", self.tmp());
1939                let handle = format!("handle{}", self.tmp());
1940                uwriteln!(self.src, "var {handle} = {};", &operands[0]);
1941
1942                match data {
1943                    ResourceData::Host {
1944                        tid,
1945                        rid,
1946                        local_name,
1947                        dtor_name,
1948                    } => {
1949                        let tid = tid.as_u32();
1950                        let rid = rid.as_u32();
1951                        let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1952                        let rsc_table_remove = self
1953                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
1954                        let rsc_flag = self
1955                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
1956                        if !imported {
1957                            let symbol_resource_handle =
1958                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1959
1960                            uwriteln!(
1961                                self.src,
1962                                "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);"
1963                            );
1964
1965                            if is_own {
1966                                // Sending an own handle out to JS as a return value - set up finalizer and disposal.
1967                                let empty_func = self
1968                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
1969                                uwriteln!(self.src,
1970                                            "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1971                                    finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
1972                                if let Some(dtor) = dtor_name {
1973                                    // The Symbol.dispose function gets disabled on drop, so we can rely on the own handle remaining valid.
1974                                    uwriteln!(
1975                                                self.src,
1976                                                "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
1977                                            finalizationRegistry{tid}.unregister({rsc});
1978                                            {rsc_table_remove}(handleTable{tid}, {handle});
1979                                            {rsc}[{symbol_dispose}] = {empty_func};
1980                                            {rsc}[{symbol_resource_handle}] = undefined;
1981                                            {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
1982                                        }}}});"
1983                                            );
1984                                } else {
1985                                    // Set up Symbol.dispose for borrows to allow its call, even though it does nothing.
1986                                    uwriteln!(
1987                                        self.src,
1988                                        "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});",
1989                                    );
1990                                }
1991                            } else {
1992                                // Borrow handles of local resources have rep handles, which we carry through here.
1993                                uwriteln!(
1994                                    self.src,
1995                                    "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
1996                                );
1997                            }
1998                        } else {
1999                            let rep = format!("rep{}", self.tmp());
2000                            // Imported handles either lift as instance capture from a previous lowering,
2001                            // or we create a new JS class to represent it.
2002                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2003                            let symbol_resource_handle =
2004                                self.intrinsic(Intrinsic::SymbolResourceHandle);
2005
2006                            uwriteln!(
2007                                self.src,
2008                                r#"
2009                                  var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2010                                  var {rsc} = captureTable{rid}.get({rep});
2011                                  if (!{rsc}) {{
2012                                      {rsc} = Object.create({local_name}.prototype);
2013                                      Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2014                                      Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2015                                  }}
2016                                "#,
2017                            );
2018
2019                            if is_own {
2020                                // An own lifting is a transfer to JS, so existing own handle is implicitly dropped.
2021                                uwriteln!(
2022                                    self.src,
2023                                    "else {{
2024                                        captureTable{rid}.delete({rep});
2025                                    }}
2026                                    {rsc_table_remove}(handleTable{tid}, {handle});"
2027                                );
2028                            }
2029                        }
2030
2031                        // Borrow handles are tracked to release after the call by CallInterface.
2032                        if !is_own {
2033                            let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2034                                ResourceIntrinsic::CurResourceBorrows,
2035                            ));
2036                            uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2037                            self.clear_resource_borrows = true;
2038                        }
2039                    }
2040
2041                    ResourceData::Guest {
2042                        resource_name,
2043                        prefix,
2044                        extra,
2045                    } => {
2046                        assert!(
2047                            extra.is_none(),
2048                            "plain resource handles do not carry extra data"
2049                        );
2050
2051                        let symbol_resource_handle =
2052                            self.intrinsic(Intrinsic::SymbolResourceHandle);
2053                        let prefix = prefix.as_deref().unwrap_or("");
2054                        let lower_camel = resource_name.to_lower_camel_case();
2055
2056                        if !imported {
2057                            if is_own {
2058                                uwriteln!(
2059                                    self.src,
2060                                    "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;"
2061                                );
2062                                uwrite!(
2063                                    self.src,
2064                                    r#"
2065                                      repTable.delete({handle});
2066                                      delete {rsc}[{symbol_resource_handle}];
2067                                      finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2068                                    "#
2069                                );
2070                            } else {
2071                                uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2072                            }
2073                        } else {
2074                            let upper_camel = resource_name.to_upper_camel_case();
2075
2076                            uwrite!(
2077                                self.src,
2078                                r#"
2079                                  var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
2080                                   Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2081                                "#
2082                            );
2083
2084                            uwriteln!(
2085                                self.src,
2086                                "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
2087                            );
2088
2089                            if !is_own {
2090                                let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2091                                    ResourceIntrinsic::CurResourceBorrows,
2092                                ));
2093                                uwriteln!(
2094                                    self.src,
2095                                    "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2096                                );
2097                                self.clear_resource_borrows = true;
2098                            }
2099                        }
2100                    }
2101                }
2102                results.push(rsc);
2103            }
2104
2105            Instruction::HandleLower { handle, name, .. } => {
2106                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2107                let is_own = matches!(handle, Handle::Own(_));
2108                let ResourceTable { imported, data } =
2109                    &self.resource_map[&crate::dealias(self.resolve, *ty)];
2110
2111                let class_name = name.to_upper_camel_case();
2112                let handle = format!("handle{}", self.tmp());
2113                let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2114                let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2115                let op = &operands[0];
2116
2117                match data {
2118                    ResourceData::Host {
2119                        tid,
2120                        rid,
2121                        local_name,
2122                        ..
2123                    } => {
2124                        let tid = tid.as_u32();
2125                        let rid = rid.as_u32();
2126                        if !imported {
2127                            if is_own {
2128                                let empty_func = self
2129                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2130                                uwriteln!(
2131                                            self.src,
2132                                            "var {handle} = {op}[{symbol_resource_handle}];
2133                                    if (!{handle}) {{
2134                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2135                                    }}
2136                                    finalizationRegistry{tid}.unregister({op});
2137                                    {op}[{symbol_dispose}] = {empty_func};
2138                                    {op}[{symbol_resource_handle}] = undefined;",
2139                                        );
2140                            } else {
2141                                // When expecting a borrow, the JS resource provided will always be an own
2142                                // handle. This is because it is not possible for borrow handles to be passed
2143                                // back reentrantly.
2144                                // We then set the handle to the rep per the local borrow rule.
2145                                let rsc_flag = self.intrinsic(Intrinsic::Resource(
2146                                    ResourceIntrinsic::ResourceTableFlag,
2147                                ));
2148                                let own_handle = format!("handle{}", self.tmp());
2149                                uwriteln!(self.src,
2150                                            "var {own_handle} = {op}[{symbol_resource_handle}];
2151                                    if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
2152                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2153                                    }}
2154                                    var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};",
2155                                        );
2156                            }
2157                        } else {
2158                            // Imported resources may already have a handle if they were constructed
2159                            // by a component and then passed out.
2160                            uwriteln!(
2161                                        self.src,
2162                                        "if (!({op} instanceof {local_name})) {{
2163                                     throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2164                                 }}
2165                                 var {handle} = {op}[{symbol_resource_handle}];",
2166                                    );
2167                            // Otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
2168                            // to get the resource rep.
2169                            // Fall back to assign a new rep in the capture table, when the imported
2170                            // resource was constructed externally.
2171                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2172
2173                            // Build the code to initialize the owned/borrowed resource handle
2174                            let handle_init_js = if is_own {
2175                                let create_own_fn = self.intrinsic(Intrinsic::Resource(
2176                                    ResourceIntrinsic::ResourceTableCreateOwn,
2177                                ));
2178                                format!("{handle} = {create_own_fn}(handleTable{tid}, rep);")
2179                            } else {
2180                                let scope_id = self.intrinsic(Intrinsic::ScopeId);
2181                                let create_borrow_fn = self.intrinsic(Intrinsic::Resource(
2182                                    ResourceIntrinsic::ResourceTableCreateBorrow,
2183                                ));
2184                                format!(
2185                                    "{handle} = {create_borrow_fn}(handleTable{tid}, rep, {scope_id});"
2186                                )
2187                            };
2188
2189                            uwriteln!(
2190                                self.src,
2191                                "if (!{handle}) {{
2192                                    const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2193                                    captureTable{rid}.set(rep, {op});
2194                                    {handle_init_js}
2195                                }}"
2196                            );
2197                        }
2198                    }
2199
2200                    ResourceData::Guest {
2201                        resource_name,
2202                        prefix,
2203                        extra,
2204                    } => {
2205                        assert!(
2206                            extra.is_none(),
2207                            "plain resource handles do not carry extra data"
2208                        );
2209
2210                        let upper_camel = resource_name.to_upper_camel_case();
2211                        let lower_camel = resource_name.to_lower_camel_case();
2212                        let prefix = prefix.as_deref().unwrap_or("");
2213
2214                        if !imported {
2215                            let local_rep = format!("localRep{}", self.tmp());
2216                            uwriteln!(
2217                                        self.src,
2218                                        "if (!({op} instanceof {upper_camel})) {{
2219                                    throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2220                                }}
2221                                let {handle} = {op}[{symbol_resource_handle}];",
2222                                    );
2223
2224                            if is_own {
2225                                uwriteln!(
2226                                            self.src,
2227                                            "if ({handle} === undefined) {{
2228                                        var {local_rep} = repCnt++;
2229                                        repTable.set({local_rep}, {{ rep: {op}, own: true }});
2230                                        {handle} = $resource_{prefix}new${lower_camel}({local_rep});
2231                                        {op}[{symbol_resource_handle}] = {handle};
2232                                        finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
2233                                    }}
2234                                    "
2235                                        );
2236                            } else {
2237                                uwriteln!(
2238                                    self.src,
2239                                    "if ({handle} === undefined) {{
2240                                        var {local_rep} = repCnt++;
2241                                        repTable.set({local_rep}, {{ rep: {op}, own: false }});
2242                                        {op}[{symbol_resource_handle}] = {local_rep};
2243                                    }}
2244                                    "
2245                                );
2246                            }
2247                        } else {
2248                            let symbol_resource_handle =
2249                                self.intrinsic(Intrinsic::SymbolResourceHandle);
2250                            uwrite!(
2251                                self.src,
2252                                "var {handle} = {op}[{symbol_resource_handle}];
2253                                 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
2254                                "
2255                            );
2256                        }
2257                    }
2258                }
2259                results.push(handle);
2260            }
2261
2262            Instruction::DropHandle { ty } => {
2263                let _ = ty;
2264                todo!("[Instruction::DropHandle] not yet implemented")
2265            }
2266
2267            Instruction::Flush { amt } => {
2268                for item in operands.iter().take(*amt) {
2269                    results.push(item.clone());
2270                }
2271            }
2272
2273            Instruction::ErrorContextLift => {
2274                let item = operands
2275                    .first()
2276                    .expect("unexpectedly missing ErrorContextLift arg");
2277                results.push(item.clone());
2278            }
2279
2280            Instruction::ErrorContextLower => {
2281                let item = operands
2282                    .first()
2283                    .expect("unexpectedly missing ErrorContextLower arg");
2284                results.push(item.clone());
2285            }
2286
2287            Instruction::FutureLower { .. } => {
2288                // TODO: convert this return of the lifted Future:
2289                //
2290                // ```
2291                //     return BigInt(writeEndWaitableIdx) << 32n | BigInt(readEndWaitableIdx);
2292                // ```
2293                //
2294                // Into a component-local Future instance
2295                //
2296                let future_arg = operands
2297                    .first()
2298                    .expect("unexpectedly missing ErrorContextLower arg");
2299                results.push(future_arg.clone());
2300            }
2301
2302            Instruction::FutureLift { payload, ty } => {
2303                let future_ty = &crate::dealias(self.resolve, *ty);
2304
2305                // TODO: we must generate the lifting function *before* function bindgen happens
2306                // (see commented async param lift code generation), because inside here
2307                // we do not have access to the interface types required to generate
2308                //
2309                // Alternatively, we can implement gen_flat_{lift,lower}_fn_js_expr for
2310                // TypeDefs with a resolve as well (and make sure the code works with either)
2311                //
2312                // TODO(breaking): consider adding more information to bindgen (pointer to component types?)
2313                match payload {
2314                    Some(payload_ty) => {
2315                        match payload_ty {
2316                            // TODO: reuse existing lifts
2317                            Type::Bool
2318                            | Type::U8
2319                            | Type::U16
2320                            | Type::U32
2321                            | Type::U64
2322                            | Type::S8
2323                            | Type::S16
2324                            | Type::S32
2325                            | Type::S64
2326                            | Type::F32
2327                            | Type::F64
2328                            | Type::Char
2329                            | Type::String
2330                            | Type::ErrorContext => uwriteln!(
2331                                self.src,
2332                                "const payloadLiftFn = () => {{ throw new Error('lift for {payload_ty:?}'); }}",
2333                            ),
2334                            Type::Id(payload_ty_id) => {
2335                                if self.resource_map.contains_key(payload_ty_id) {
2336                                    let ResourceTable { data, .. } =
2337                                        &self.resource_map[payload_ty_id];
2338                                    uwriteln!(
2339                                        self.src,
2340                                        "const payloadLiftFn = () => {{ throw new Error('lift for {} (identifier {})'); }}",
2341                                        payload_ty_id.index(),
2342                                        match data {
2343                                            ResourceData::Host { local_name, .. } => local_name,
2344                                            ResourceData::Guest { resource_name, .. } =>
2345                                                resource_name,
2346                                        }
2347                                    );
2348                                } else {
2349                                    // TODO: generate lift fns (see TODO above)
2350                                    // NOTE: the missing type here is normally a result with nested types...
2351                                    // the resource_map may not be indexing these properly
2352                                    //
2353                                    // eprintln!("warning: missing resource map def {:#?}", self.resolve.types[*payload_ty_id]);
2354                                }
2355                            }
2356                        };
2357
2358                        // // TODO: save payload type size below and more information about the type w/ the future?
2359                        // let payload_ty_size = self.sizes.size(payload_ty).size_wasm32();
2360
2361                        // NOTE: here, rather than create a new `Future` "resource" using the saved
2362                        // ResourceData, we use the future.new intrinsic directly.
2363                        //
2364                        // TODO: differentiate "locally" created futures and futures that are lifted in?
2365                        //
2366                        let tmp = self.tmp();
2367                        let result_var = format!("futureResult{tmp}");
2368                        let component_idx = self.canon_opts.instance.as_u32();
2369                        let future_new_fn =
2370                            self.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureNew));
2371                        uwriteln!(
2372                            self.src,
2373                            "const {result_var} = {future_new_fn}({{ componentIdx: {component_idx}, futureTypeRep: {} }});",
2374                            future_ty.index(),
2375                        );
2376                        results.push(result_var.clone());
2377                    }
2378
2379                    None => unreachable!("future with no payload unsupported"),
2380                }
2381            }
2382
2383            Instruction::StreamLower { .. } => {
2384                // TODO: convert this return of the lifted Future:
2385                // ```
2386                //     return BigInt(writeEndWaitableIdx) << 32n | BigInt(readEndWaitableIdx);
2387                // ```
2388                //
2389                // Into a component-local Future instance
2390                //
2391                let stream_arg = operands
2392                    .first()
2393                    .expect("unexpectedly missing StreamLower arg");
2394                results.push(stream_arg.clone());
2395            }
2396
2397            Instruction::StreamLift { payload, ty } => {
2398                let component_idx = self.canon_opts.instance.as_u32();
2399                let stream_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncStream(
2400                    AsyncStreamIntrinsic::StreamNewFromLift,
2401                ));
2402
2403                // We must look up the type idx to find the stream
2404                let type_id = &crate::dealias(self.resolve, *ty);
2405                let ResourceTable {
2406                    imported: true,
2407                    data:
2408                        ResourceData::Guest {
2409                            extra:
2410                                Some(ResourceExtraData::Stream {
2411                                    table_idx: stream_table_idx_ty,
2412                                    elem_ty: stream_element_ty,
2413                                }),
2414                            ..
2415                        },
2416                } = self
2417                    .resource_map
2418                    .get(type_id)
2419                    .expect("missing resource mapping for stream lift")
2420                else {
2421                    unreachable!("invalid resource table observed during stream lift");
2422                };
2423
2424                assert_eq!(
2425                    *stream_element_ty, **payload,
2426                    "stream element type mismatch"
2427                );
2428
2429                let arg_stream_end_idx = operands
2430                    .first()
2431                    .expect("unexpectedly missing stream table idx arg in StreamLift");
2432
2433                let (payload_lift_fn, payload_lower_fn) = match payload {
2434                    None => ("null".into(), "null".into()),
2435                    Some(payload_ty) => {
2436                        match payload_ty {
2437                            // TODO: reuse existing lifts
2438                            Type::Bool
2439                            | Type::U8
2440                            | Type::U16
2441                            | Type::U32
2442                            | Type::U64
2443                            | Type::S8
2444                            | Type::S16
2445                            | Type::S32
2446                            | Type::S64
2447                            | Type::F32
2448                            | Type::F64
2449                            | Type::Char
2450                            | Type::String
2451                            | Type::ErrorContext => (
2452                                format!(
2453                                    "const payloadLiftFn = () => {{ throw new Error('lift for {payload_ty:?}'); }};"
2454                                ),
2455                                format!(
2456                                    "const payloadLowerFn = () => {{ throw new Error('lower for {payload_ty:?}'); }};"
2457                                ),
2458                            ),
2459
2460                            Type::Id(payload_ty_id) => {
2461                                if let Some(ResourceTable { data, .. }) =
2462                                    &self.resource_map.get(payload_ty_id)
2463                                {
2464                                    let identifier = match data {
2465                                        ResourceData::Host { local_name, .. } => local_name,
2466                                        ResourceData::Guest { resource_name, .. } => resource_name,
2467                                    };
2468
2469                                    (
2470                                        format!(
2471                                            "const payloadLiftFn = () => {{ throw new Error('lift for {} (identifier {identifier})'); }};",
2472                                            payload_ty_id.index(),
2473                                        ),
2474                                        format!(
2475                                            "const payloadLowerFn = () => {{ throw new Error('lower for {} (identifier {identifier})'); }};",
2476                                            payload_ty_id.index(),
2477                                        ),
2478                                    )
2479                                } else {
2480                                    (
2481                                        format!(
2482                                            "const payloadLiftFn = () => {{ throw new Error('lift for missing type with type idx {payload_ty:?}'); }};",
2483                                        ),
2484                                        format!(
2485                                            "const payloadLowerFn = () => {{ throw new Error('lower for missing type with type idx {payload_ty:?}'); }};",
2486                                        ),
2487                                    )
2488                                }
2489                            }
2490                        }
2491                    }
2492                };
2493
2494                let payload_ty_size_js = if let Some(payload_ty) = payload {
2495                    self.sizes.size(payload_ty).size_wasm32().to_string()
2496                } else {
2497                    "null".into()
2498                };
2499
2500                let stream_table_idx = stream_table_idx_ty.as_u32();
2501                let is_unit_stream = payload.is_none();
2502
2503                let tmp = self.tmp();
2504                let result_var = format!("streamResult{tmp}");
2505                uwriteln!(
2506                    self.src,
2507                    "
2508                    {payload_lift_fn}
2509                    {payload_lower_fn}
2510                    const {result_var} = {stream_new_from_lift_fn}({{
2511                        componentIdx: {component_idx},
2512                        streamTableIdx: {stream_table_idx},
2513                        streamEndWaitableIdx: {arg_stream_end_idx},
2514                        payloadLiftFn,
2515                        payloadTypeSize32: {payload_ty_size_js},
2516                        payloadLowerFn,
2517                        isUnitStream: {is_unit_stream},
2518                    }});",
2519                );
2520                results.push(result_var.clone());
2521            }
2522
2523            // Instruction::AsyncTaskReturn does *not* correspond to an canonical `task.return`,
2524            // but rather to a "return"/exit from an a lifted async function (e.g. pre-callback)
2525            //
2526            // To modify behavior of the `task.return` intrinsic, see:
2527            //   - `Trampoline::TaskReturn`
2528            //   - `AsyncTaskIntrinsic::TaskReturn`
2529            //
2530            // This is simply the end of the async function definition (e.g. `CallWasm`) that has been
2531            // lifted, which contains information about the async state.
2532            //
2533            // For an async function 'some-func', this instruction is triggered w/ the following `name`s:
2534            // - '[task-return]some-func'
2535            //
2536            // At this point in code generation, the following things have already been set:
2537            // - `parentTask`: A parent task, if one was executing before
2538            // - `subtask`: A subtask, if the current task is a subtask of a parent task
2539            // - `task`: the currently executing task
2540            // - `ret`: the original function return value, via (i.e. via `CallWasm`/`CallInterface`)
2541            // - `hostProvided`: whether the original function was a host-provided (i.e. host provided import)
2542            //
2543            Instruction::AsyncTaskReturn { name, params } => {
2544                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2545                let component_instance_idx = self.canon_opts.instance.as_u32();
2546                let is_async_js = self.requires_async_porcelain | self.is_async;
2547                let async_driver_loop_fn =
2548                    self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::DriverLoop));
2549                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2550                    ComponentIntrinsic::GetOrCreateAsyncState,
2551                ));
2552
2553                uwriteln!(
2554                    self.src,
2555                    "{debug_log_fn}('{prefix}  [Instruction::AsyncTaskReturn]', {{
2556                         funcName: '{name}',
2557                         paramCount: {param_count},
2558                         componentIdx: {component_instance_idx},
2559                         postReturn: {post_return_present},
2560                         hostProvided,
2561                      }});",
2562                    param_count = params.len(),
2563                    post_return_present = self.post_return.is_some(),
2564                    prefix = self.tracing_prefix,
2565                );
2566
2567                assert!(
2568                    self.is_async,
2569                    "non-async functions should not be performing async returns (func {name})",
2570                );
2571
2572                // If we're dealing with an async call, then `ret` is actually the
2573                // state of async behavior.
2574                //
2575                // The result *should* be a Promise that resolves to whatever the current task
2576                // will eventually resolve to.
2577                //
2578                // NOTE: Regardless of whether async porcelain is required here, we want to return the result
2579                // of the computation as a whole, not the current async state (which is what `ret` currently is).
2580                //
2581                // `ret` is only a Promise if we have async-lowered the function in question (e.g. via JSPI)
2582                //
2583                // ```ts
2584                // type ret = number | Promise<number>;
2585                // ```ts
2586                //
2587                // If the import was host provided we *already* have the result via
2588                // JSPI and simply calling the host provided JS function -- there is no need
2589                // to drive the async loop as with an async import that came from a component.
2590                //
2591                // If a subtask is defined, then we're in the case of a lowered async import,
2592                // which means that the first async call (to the callee fn) has occurred,
2593                // and a subtask has been created, but has not been triggered as started.
2594                //
2595                // NOTE: for host provided functions, we know that the resolution fo the
2596                // function itself are the lifted (component model -- i.e. a string not a pointer + len)
2597                // results. In those cases, we can simply return the result that was provided by the host.
2598                //
2599                // Alternatively, if we have entered an async return, and are part of a subtask
2600                // then we should start it, given that the task we have recently created (however we got to
2601                // the async return) is going to continue to be polled soon (via the driver loop).
2602                //
2603                uwriteln!(
2604                    self.src,
2605                    r#"
2606                      if (hostProvided) {{
2607                          {debug_log_fn}('[Instruction::AsyncTaskReturn] signaling host-provided async return completion', {{
2608                              task: task.id(),
2609                              subtask: subtask?.id(),
2610                              result: ret,
2611                          }})
2612                          task.resolve([ret]);
2613                          task.exit();
2614                          return task.completionPromise();
2615                      }}
2616
2617                      const componentState = {get_or_create_async_state_fn}({component_instance_idx});
2618                      if (!componentState) {{ throw new Error('failed to lookup current component state'); }}
2619
2620                      queueMicrotask(async (resolve, reject) => {{
2621                          try {{
2622                              {debug_log_fn}("[Instruction::AsyncTaskReturn] starting driver loop", {{
2623                                  fnName: '{name}',
2624                                  componentInstanceIdx: {component_instance_idx},
2625                                  taskID: task.id(),
2626                              }});
2627                              await {async_driver_loop_fn}({{
2628                                  componentInstanceIdx: {component_instance_idx},
2629                                  componentState,
2630                                  task,
2631                                  fnName: '{name}',
2632                                  isAsync: {is_async_js},
2633                                  callbackResult: ret,
2634                              }});
2635                          }} catch (err) {{
2636                              {debug_log_fn}("[Instruction::AsyncTaskReturn] driver loop call failure", {{ err }});
2637                          }}
2638                      }});
2639
2640                      let taskRes = await task.completionPromise();
2641                      if (task.getErrHandling() === 'throw-result-err') {{
2642                          if (typeof taskRes !== 'object') {{ return taskRes; }}
2643                          if (taskRes.tag === 'err') {{ throw taskRes.val; }}
2644                          if (taskRes.tag === 'ok') {{ taskRes = taskRes.val; }}
2645                      }}
2646
2647                      return taskRes;
2648                      "#,
2649                );
2650            }
2651
2652            Instruction::GuestDeallocate { .. }
2653            | Instruction::GuestDeallocateString
2654            | Instruction::GuestDeallocateList { .. }
2655            | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
2656        }
2657    }
2658}
2659
2660/// Tests whether `ty` can be represented with `null`, and if it can then
2661/// the "other type" is returned. If `Some` is returned that means that `ty`
2662/// is `null | <return>`. If `None` is returned that means that `null` can't
2663/// be used to represent `ty`.
2664pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
2665    let id = match ty {
2666        Type::Id(id) => *id,
2667        _ => return None,
2668    };
2669    match &resolve.types[id].kind {
2670        // If `ty` points to an `option<T>`, then `ty` can be represented
2671        // with `null` if `t` itself can't be represented with null. For
2672        // example `option<option<u32>>` can't be represented with `null`
2673        // since that's ambiguous if it's `none` or `some(none)`.
2674        //
2675        // Note, oddly enough, that `option<option<option<u32>>>` can be
2676        // represented as `null` since:
2677        //
2678        // * `null` => `none`
2679        // * `{ tag: "none" }` => `some(none)`
2680        // * `{ tag: "some", val: null }` => `some(some(none))`
2681        // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
2682        //
2683        // It's doubtful anyone would actually rely on that though due to
2684        // how confusing it is.
2685        TypeDefKind::Option(t) => {
2686            if !maybe_null(resolve, t) {
2687                Some(t)
2688            } else {
2689                None
2690            }
2691        }
2692        TypeDefKind::Type(t) => as_nullable(resolve, t),
2693        _ => None,
2694    }
2695}
2696
2697pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
2698    as_nullable(resolve, ty).is_some()
2699}
2700
2701/// Retrieve the specialized JS array type that would contain a given element type,
2702/// if one exists.
2703///
2704/// e.g. a Wasm [`Type::U8`] would be represetned by a JS `Uint8Array`
2705///
2706/// # Arguments
2707///
2708/// * `resolve` - The [`Resolve`] used to look up nested type IDs if necessary
2709/// * `element_ty` - The [`Type`] that represents elements of the array
2710pub fn js_array_ty(resolve: &Resolve, element_ty: &Type) -> Option<&'static str> {
2711    match element_ty {
2712        Type::Bool => None,
2713        Type::U8 => Some("Uint8Array"),
2714        Type::S8 => Some("Int8Array"),
2715        Type::U16 => Some("Uint16Array"),
2716        Type::S16 => Some("Int16Array"),
2717        Type::U32 => Some("Uint32Array"),
2718        Type::S32 => Some("Int32Array"),
2719        Type::U64 => Some("BigUint64Array"),
2720        Type::S64 => Some("BigInt64Array"),
2721        Type::F32 => Some("Float32Array"),
2722        Type::F64 => Some("Float64Array"),
2723        Type::Char => None,
2724        Type::String => None,
2725        Type::ErrorContext => None,
2726        Type::Id(id) => match &resolve.types[*id].kind {
2727            // Recur to resolve type aliases, etc.
2728            TypeDefKind::Type(t) => js_array_ty(resolve, t),
2729            _ => None,
2730        },
2731    }
2732}