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::{CanonicalOptions, ResourceIndex, TypeResourceTableIndex};
7use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
8use wit_component::StringEncoding;
9use wit_parser::abi::WasmType;
10use wit_parser::{
11    Alignment, ArchitectureSize, Handle, Resolve, SizeAlign, Type, TypeDefKind, TypeId,
12};
13
14use crate::intrinsics::component::ComponentIntrinsic;
15use crate::intrinsics::conversion::ConversionIntrinsic;
16use crate::intrinsics::js_helper::JsHelperIntrinsic;
17use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
18use crate::intrinsics::resource::ResourceIntrinsic;
19use crate::intrinsics::string::StringIntrinsic;
20use crate::intrinsics::Intrinsic;
21use crate::{get_thrown_type, source};
22use crate::{uwrite, uwriteln};
23
24/// Method of error handling
25#[derive(PartialEq)]
26pub enum ErrHandling {
27    /// Do no special handling of errors, requiring users to return objects that represent
28    /// errors as represented in WIT
29    None,
30    /// Require throwing of result error objects
31    ThrowResultErr,
32    /// Catch thrown errors and convert them into result<t,e> error variants
33    ResultCatchHandler,
34}
35
36/// Data related to a given resource
37#[derive(Clone, Debug, PartialEq)]
38pub enum ResourceData {
39    Host {
40        tid: TypeResourceTableIndex,
41        rid: ResourceIndex,
42        local_name: String,
43        dtor_name: Option<String>,
44    },
45    Guest {
46        resource_name: String,
47        prefix: Option<String>,
48    },
49}
50
51/// Map used for resource function bindgen within a given component
52///
53/// Mapping from the instance + resource index in that component (internal or external)
54/// to the unique global resource id used to key the resource tables for this resource.
55///
56/// The id value uniquely identifies the resource table so that if a resource is used
57/// by n components, there should be n different indices and spaces in use. The map is
58/// therefore entirely unique and fully distinct for each instance's function bindgen.
59///
60/// The second bool is true if it is an imported resource.
61///
62/// For a given resource table id {x}, with resource index {y} the local variables are assumed:
63/// - handleTable{x}
64/// - captureTable{y} (rep to instance map for captured imported tables, only for JS import bindgen, not hybrid)
65/// - captureCnt{y} for assigning capture rep
66///
67/// For component-defined resources:
68/// - finalizationRegistry{x}
69///
70/// handleTable internally will be allocated with { rep: i32, own: bool } entries
71///
72/// In the case of an imported resource tables, in place of "rep" we just store
73/// the direct JS object being referenced, since in JS the object is its own handle.
74///
75#[derive(Clone, Debug, PartialEq)]
76pub struct ResourceTable {
77    /// Whether a resource was imported
78    ///
79    /// This should be tracked because imported types cannot be re-exported uniquely (?)
80    pub imported: bool,
81
82    /// Data related to the actual resource
83    pub data: ResourceData,
84}
85
86/// A mapping of type IDs to the resources that they represent
87pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
88
89/// A mapping of remote reps that represent imported resources
90pub type RemoteResourceMap = BTreeMap<u32, ResourceTable>;
91
92pub struct FunctionBindgen<'a> {
93    /// Mapping of resources for types that have corresponding definitions locally
94    pub resource_map: &'a ResourceMap,
95
96    /// Mapping of resources for types that are defined only in the remote component
97    /// and must be auto-vivicated locally.
98    pub remote_resource_map: &'a RemoteResourceMap,
99
100    /// Whether current resource borrows need to be deactivated
101    pub clear_resource_borrows: bool,
102
103    /// Set of intrinsics
104    pub intrinsics: &'a mut BTreeSet<Intrinsic>,
105
106    /// Whether to perform valid lifting optimization
107    pub valid_lifting_optimization: bool,
108
109    /// Sizes and alignments for sub elements
110    pub sizes: &'a SizeAlign,
111
112    /// Method of error handling
113    pub err: ErrHandling,
114
115    /// Temporary values
116    pub tmp: usize,
117
118    /// Source code of the function
119    pub src: source::Source,
120
121    /// Block storage
122    pub block_storage: Vec<source::Source>,
123
124    /// Blocks of the fucntion
125    pub blocks: Vec<(String, Vec<String>)>,
126
127    /// Parameters of the function
128    pub params: Vec<String>,
129
130    /// Memory variable
131    pub memory: Option<&'a String>,
132
133    /// Realloc function name
134    pub realloc: Option<&'a String>,
135
136    /// Post return function name
137    pub post_return: Option<&'a String>,
138
139    /// Prefix to use when printing tracing information
140    pub tracing_prefix: &'a String,
141
142    /// Whether tracing is enabled
143    pub tracing_enabled: bool,
144
145    /// Method if string encoding
146    pub encoding: StringEncoding,
147
148    /// Callee of the function
149    pub callee: &'a str,
150
151    /// Whether the callee is dynamic (i.e. has multiple operands)
152    pub callee_resource_dynamic: bool,
153
154    /// The [`wit_bindgen::Resolve`] containing extracted WIT information
155    pub resolve: &'a Resolve,
156
157    /// Whether the function requires async porcelain
158    ///
159    /// In the case of an import this likely implies the use of JSPI
160    /// and in the case of an export this is simply code generation metadata.
161    pub requires_async_porcelain: bool,
162
163    /// Whether the function is guest async lifted (i.e. WASI P3)
164    pub is_guest_async_lifted: bool,
165
166    /// Canon opts
167    pub canon_opts: &'a CanonicalOptions,
168
169    /// Interface name
170    pub iface_name: Option<&'a str>,
171}
172
173impl FunctionBindgen<'_> {
174    fn tmp(&mut self) -> usize {
175        let ret = self.tmp;
176        self.tmp += 1;
177        ret
178    }
179
180    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
181        self.intrinsics.insert(intrinsic);
182        intrinsic.name().to_string()
183    }
184
185    fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
186    where
187        T: std::fmt::Display,
188    {
189        let clamp = self.intrinsic(Intrinsic::ClampGuest);
190        results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
191    }
192
193    fn load(
194        &mut self,
195        method: &str,
196        offset: ArchitectureSize,
197        operands: &[String],
198        results: &mut Vec<String>,
199    ) {
200        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
201        let memory = self.memory.as_ref().unwrap();
202        results.push(format!(
203            "{view}({memory}).{method}({} + {offset}, true)",
204            operands[0],
205            offset = offset.size_wasm32()
206        ));
207    }
208
209    fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
210        let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
211        let memory = self.memory.as_ref().unwrap();
212        uwriteln!(
213            self.src,
214            "{view}({memory}).{method}({} + {offset}, {}, true);",
215            operands[1],
216            operands[0],
217            offset = offset.size_wasm32()
218        );
219    }
220
221    /// Write result assignment lines to output
222    ///
223    /// In general this either means writing preambles, for example that look like the following:
224    /// ```js
225    /// const ret =
226    /// ```
227    ///
228    /// ```
229    /// var [ ret0, ret1, ret2 ] =
230    /// ```
231    fn write_result_assignment(&mut self, amt: usize, results: &mut Vec<String>) {
232        match amt {
233            0 => {}
234            1 => {
235                uwrite!(self.src, "const ret = ");
236                results.push("ret".to_string());
237            }
238            n => {
239                uwrite!(self.src, "var [");
240                for i in 0..n {
241                    if i > 0 {
242                        uwrite!(self.src, ", ");
243                    }
244                    uwrite!(self.src, "ret{}", i);
245                    results.push(format!("ret{i}"));
246                }
247                uwrite!(self.src, "] = ");
248            }
249        }
250    }
251
252    fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
253        match cast {
254            Bitcast::I32ToF32 => {
255                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
256                format!("{cvt}({op})")
257            }
258            Bitcast::F32ToI32 => {
259                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
260                format!("{cvt}({op})")
261            }
262            Bitcast::I64ToF64 => {
263                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
264                format!("{cvt}({op})")
265            }
266            Bitcast::F64ToI64 => {
267                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
268                format!("{cvt}({op})")
269            }
270            Bitcast::I32ToI64 => format!("BigInt({op})"),
271            Bitcast::I64ToI32 => format!("Number({op})"),
272            Bitcast::I64ToF32 => {
273                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
274                format!("{cvt}(Number({op}))")
275            }
276            Bitcast::F32ToI64 => {
277                let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
278                format!("BigInt({cvt}({op}))")
279            }
280            Bitcast::None
281            | Bitcast::P64ToI64
282            | Bitcast::LToI32
283            | Bitcast::I32ToL
284            | Bitcast::LToP
285            | Bitcast::PToL
286            | Bitcast::PToI32
287            | Bitcast::I32ToP => op.to_string(),
288            Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
289            Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
290            Bitcast::Sequence(casts) => {
291                let mut statement = op.to_string();
292                for cast in casts.iter() {
293                    statement = self.bitcast(cast, &statement);
294                }
295                statement
296            }
297        }
298    }
299
300    /// Start the current task
301    fn start_current_task(&mut self, instr: &Instruction, is_async: bool, fn_name: &str) {
302        let prefix = match instr {
303            Instruction::CallWasm { .. } => "_wasm_call_",
304            Instruction::CallInterface { .. } => "_interface_call_",
305            _ => unreachable!(
306                "unrecognized instruction triggering start of current task: [{instr:?}]"
307            ),
308        };
309        let start_current_task_fn =
310            self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::StartCurrentTask));
311        let component_instance_idx = self.canon_opts.instance.as_u32();
312        uwriteln!(
313            self.src,
314            "const {prefix}currentTaskID = {start_current_task_fn}({component_instance_idx}, {is_async}, '{fn_name}');"
315        );
316    }
317
318    /// End an an existing current task
319    ///
320    /// Optionally, ending the current task can also return the value
321    /// collected by the task, primarily relevant when dealing with
322    /// async lowered imports (in particular AsyncTaskReturn) that must return
323    /// collected values from the current task in question.
324    fn end_current_task(&mut self) {
325        let end_current_task_fn =
326            self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::EndCurrentTask));
327        let component_instance_idx = self.canon_opts.instance.as_u32();
328        uwriteln!(self.src, "{end_current_task_fn}({component_instance_idx});",);
329    }
330}
331
332impl Bindgen for FunctionBindgen<'_> {
333    type Operand = String;
334
335    fn sizes(&self) -> &SizeAlign {
336        self.sizes
337    }
338
339    fn push_block(&mut self) {
340        let prev = mem::take(&mut self.src);
341        self.block_storage.push(prev);
342    }
343
344    fn finish_block(&mut self, operands: &mut Vec<String>) {
345        let to_restore = self.block_storage.pop().unwrap();
346        let src = mem::replace(&mut self.src, to_restore);
347        self.blocks.push((src.into(), mem::take(operands)));
348    }
349
350    fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
351        unimplemented!();
352    }
353
354    fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool {
355        array_ty(resolve, ty).is_some()
356    }
357
358    fn emit(
359        &mut self,
360        resolve: &Resolve,
361        inst: &Instruction<'_>,
362        operands: &mut Vec<String>,
363        results: &mut Vec<String>,
364    ) {
365        match inst {
366            Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
367
368            Instruction::I32Const { val } => results.push(val.to_string()),
369
370            Instruction::ConstZero { tys } => {
371                for t in tys.iter() {
372                    match t {
373                        WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
374                        WasmType::I32
375                        | WasmType::F32
376                        | WasmType::F64
377                        | WasmType::Pointer
378                        | WasmType::Length => results.push("0".to_string()),
379                    }
380                }
381            }
382
383            Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
384
385            Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
386
387            Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
388
389            Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
390
391            Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
392
393            Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])),
394
395            Instruction::S32FromI32 | Instruction::S64FromI64 => {
396                results.push(operands.pop().unwrap())
397            }
398
399            Instruction::I32FromU8 => {
400                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
401                results.push(format!("{conv}({op})", op = operands[0]))
402            }
403
404            Instruction::I32FromS8 => {
405                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
406                results.push(format!("{conv}({op})", op = operands[0]))
407            }
408
409            Instruction::I32FromU16 => {
410                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
411                results.push(format!("{conv}({op})", op = operands[0]))
412            }
413
414            Instruction::I32FromS16 => {
415                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
416                results.push(format!("{conv}({op})", op = operands[0]))
417            }
418
419            Instruction::I32FromU32 => {
420                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
421                results.push(format!("{conv}({op})", op = operands[0]))
422            }
423
424            Instruction::I32FromS32 => {
425                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
426                results.push(format!("{conv}({op})", op = operands[0]))
427            }
428
429            Instruction::I64FromU64 => {
430                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
431                results.push(format!("{conv}({op})", op = operands[0]))
432            }
433
434            Instruction::I64FromS64 => {
435                let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
436                results.push(format!("{conv}({op})", op = operands[0]))
437            }
438
439            Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
440                results.push(operands.pop().unwrap())
441            }
442
443            Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
444                results.push(format!("+{}", operands[0]))
445            }
446
447            Instruction::CharFromI32 => {
448                let validate =
449                    self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
450                results.push(format!("{}({})", validate, operands[0]));
451            }
452
453            Instruction::I32FromChar => {
454                let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
455                results.push(format!("{}({})", validate, operands[0]));
456            }
457
458            Instruction::Bitcasts { casts } => {
459                for (cast, op) in casts.iter().zip(operands) {
460                    results.push(self.bitcast(cast, op));
461                }
462            }
463
464            Instruction::BoolFromI32 => {
465                let tmp = self.tmp();
466                uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
467                if self.valid_lifting_optimization {
468                    results.push(format!("!!bool{tmp}"));
469                } else {
470                    let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
471                    results.push(format!(
472                        "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
473                    ));
474                }
475            }
476
477            Instruction::I32FromBool => {
478                results.push(format!("{} ? 1 : 0", operands[0]));
479            }
480
481            Instruction::RecordLower { record, .. } => {
482                // use destructuring field access to get each
483                // field individually.
484                let tmp = self.tmp();
485                let mut expr = "var {".to_string();
486                for (i, field) in record.fields.iter().enumerate() {
487                    if i > 0 {
488                        expr.push_str(", ");
489                    }
490                    let name = format!("v{tmp}_{i}");
491                    expr.push_str(&field.name.to_lower_camel_case());
492                    expr.push_str(": ");
493                    expr.push_str(&name);
494                    results.push(name);
495                }
496                uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
497            }
498
499            Instruction::RecordLift { record, .. } => {
500                // records are represented as plain objects, so we
501                // make a new object and set all the fields with an object
502                // literal.
503                let mut result = "{\n".to_string();
504                for (field, op) in record.fields.iter().zip(operands) {
505                    result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
506                }
507                result.push('}');
508                results.push(result);
509            }
510
511            Instruction::TupleLower { tuple, .. } => {
512                // Tuples are represented as an array, sowe can use
513                // destructuring assignment to lower the tuple into its
514                // components.
515                let tmp = self.tmp();
516                let mut expr = "var [".to_string();
517                for i in 0..tuple.types.len() {
518                    if i > 0 {
519                        expr.push_str(", ");
520                    }
521                    let name = format!("tuple{tmp}_{i}");
522                    expr.push_str(&name);
523                    results.push(name);
524                }
525                uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
526            }
527
528            Instruction::TupleLift { .. } => {
529                // Tuples are represented as an array, so we just shove all
530                // the operands into an array.
531                results.push(format!("[{}]", operands.join(", ")));
532            }
533
534            Instruction::FlagsLower { flags, .. } => {
535                let op0 = &operands[0];
536
537                // Generate the result names.
538                for _ in 0..flags.repr().count() {
539                    let tmp = self.tmp();
540                    let name = format!("flags{tmp}");
541                    // Default to 0 so that in the null/undefined case, everything is false by
542                    // default.
543                    uwrite!(self.src, "let {name} = 0;\n");
544                    results.push(name);
545                }
546
547                uwrite!(
548                    self.src,
549                    "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
550                );
551
552                for (i, chunk) in flags.flags.chunks(32).enumerate() {
553                    let result_name = &results[i];
554
555                    uwrite!(self.src, "{result_name} = ");
556                    for (i, flag) in chunk.iter().enumerate() {
557                        if i != 0 {
558                            uwrite!(self.src, " | ");
559                        }
560
561                        let flag = flag.name.to_lower_camel_case();
562                        uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
563                    }
564                    uwrite!(self.src, ";\n");
565                }
566
567                uwrite!(
568                            self.src,
569                            "\
570                    }} else if ({op0} !== null && {op0} !== undefined) {{
571                        throw new TypeError('only an object, undefined or null can be converted to flags');
572                    }}
573                ");
574
575                // We don't need to do anything else for the null/undefined
576                // case, since that's interpreted as everything false, and we
577                // already defaulted everyting to 0.
578            }
579
580            Instruction::FlagsLift { flags, .. } => {
581                let tmp = self.tmp();
582                results.push(format!("flags{tmp}"));
583
584                if let Some(op) = operands.last() {
585                    // We only need an extraneous bits check if the number of flags isn't a multiple
586                    // of 32, because if it is then all the bits are used and there are no
587                    // extraneous bits.
588                    if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
589                        let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
590                        uwriteln!(
591                            self.src,
592                            "if (({op} & {mask}) !== 0) {{
593                                throw new TypeError('flags have extraneous bits set');
594                            }}"
595                        );
596                    }
597                }
598
599                uwriteln!(self.src, "var flags{tmp} = {{");
600
601                for (i, flag) in flags.flags.iter().enumerate() {
602                    let flag = flag.name.to_lower_camel_case();
603                    let op = &operands[i / 32];
604                    let mask: u32 = 1 << (i % 32);
605                    uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
606                }
607
608                uwriteln!(self.src, "}};");
609            }
610
611            Instruction::VariantPayloadName => results.push("e".to_string()),
612
613            Instruction::VariantLower {
614                variant,
615                results: result_types,
616                name,
617                ..
618            } => {
619                let blocks = self
620                    .blocks
621                    .drain(self.blocks.len() - variant.cases.len()..)
622                    .collect::<Vec<_>>();
623                let tmp = self.tmp();
624                let op = &operands[0];
625                uwriteln!(self.src, "var variant{tmp} = {op};");
626
627                for i in 0..result_types.len() {
628                    uwriteln!(self.src, "let variant{tmp}_{i};");
629                    results.push(format!("variant{tmp}_{i}"));
630                }
631
632                let expr_to_match = format!("variant{tmp}.tag");
633
634                uwriteln!(self.src, "switch ({expr_to_match}) {{");
635                for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
636                    uwriteln!(self.src, "case '{}': {{", case.name.as_str());
637                    if case.ty.is_some() {
638                        uwriteln!(self.src, "const e = variant{tmp}.val;");
639                    }
640                    self.src.push_str(&block);
641
642                    for (i, result) in block_results.iter().enumerate() {
643                        uwriteln!(self.src, "variant{tmp}_{i} = {result};");
644                    }
645                    uwriteln!(
646                        self.src,
647                        "break;
648                        }}"
649                    );
650                }
651                let variant_name = name.to_upper_camel_case();
652                uwriteln!(
653                    self.src,
654                    r#"default: {{
655                        throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
656                    }}"#,
657                );
658                uwriteln!(self.src, "}}");
659            }
660
661            Instruction::VariantLift { variant, name, .. } => {
662                let blocks = self
663                    .blocks
664                    .drain(self.blocks.len() - variant.cases.len()..)
665                    .collect::<Vec<_>>();
666
667                let tmp = self.tmp();
668                let op = &operands[0];
669
670                uwriteln!(
671                    self.src,
672                    "let variant{tmp};
673                    switch ({op}) {{"
674                );
675
676                for (i, (case, (block, block_results))) in
677                    variant.cases.iter().zip(blocks).enumerate()
678                {
679                    let tag = case.name.as_str();
680                    uwriteln!(
681                        self.src,
682                        "case {i}: {{
683                            {block}\
684                            variant{tmp} = {{
685                                tag: '{tag}',"
686                    );
687                    if case.ty.is_some() {
688                        assert!(block_results.len() == 1);
689                        uwriteln!(self.src, "   val: {}", block_results[0]);
690                    } else {
691                        assert!(block_results.is_empty());
692                    }
693                    uwriteln!(
694                        self.src,
695                        "   }};
696                        break;
697                        }}"
698                    );
699                }
700                let variant_name = name.to_upper_camel_case();
701                if !self.valid_lifting_optimization {
702                    uwriteln!(
703                        self.src,
704                        "default: {{
705                            throw new TypeError('invalid variant discriminant for {variant_name}');
706                        }}",
707                    );
708                }
709                uwriteln!(self.src, "}}");
710                results.push(format!("variant{tmp}"));
711            }
712
713            Instruction::OptionLower {
714                payload,
715                results: result_types,
716                ..
717            } => {
718                let (mut some, some_results) = self.blocks.pop().unwrap();
719                let (mut none, none_results) = self.blocks.pop().unwrap();
720
721                let tmp = self.tmp();
722                let op = &operands[0];
723                uwriteln!(self.src, "var variant{tmp} = {op};");
724
725                for i in 0..result_types.len() {
726                    uwriteln!(self.src, "let variant{tmp}_{i};");
727                    results.push(format!("variant{tmp}_{i}"));
728
729                    let some_result = &some_results[i];
730                    let none_result = &none_results[i];
731                    uwriteln!(some, "variant{tmp}_{i} = {some_result};");
732                    uwriteln!(none, "variant{tmp}_{i} = {none_result};");
733                }
734
735                if maybe_null(resolve, payload) {
736                    uwriteln!(
737                        self.src,
738                        "switch (variant{tmp}.tag) {{
739                            case 'none': {{
740                                {none}\
741                                break;
742                            }}
743                            case 'some': {{
744                                const e = variant{tmp}.val;
745                                {some}\
746                                break;
747                            }}
748                            default: {{
749                                throw new TypeError('invalid variant specified for option');
750                            }}
751                        }}",
752                    );
753                } else {
754                    uwriteln!(
755                        self.src,
756                        "if (variant{tmp} === null || variant{tmp} === undefined) {{
757                            {none}\
758                        }} else {{
759                            const e = variant{tmp};
760                            {some}\
761                        }}"
762                    );
763                }
764            }
765
766            Instruction::OptionLift { payload, .. } => {
767                let (some, some_results) = self.blocks.pop().unwrap();
768                let (none, none_results) = self.blocks.pop().unwrap();
769                assert!(none_results.is_empty());
770                assert!(some_results.len() == 1);
771                let some_result = &some_results[0];
772
773                let tmp = self.tmp();
774                let op = &operands[0];
775
776                let (v_none, v_some) = if maybe_null(resolve, payload) {
777                    (
778                        "{ tag: 'none' }",
779                        format!(
780                            "{{
781                                tag: 'some',
782                                val: {some_result}
783                            }}"
784                        ),
785                    )
786                } else {
787                    ("undefined", some_result.into())
788                };
789
790                if !self.valid_lifting_optimization {
791                    uwriteln!(
792                        self.src,
793                        "let variant{tmp};
794                        switch ({op}) {{
795                            case 0: {{
796                                {none}\
797                                variant{tmp} = {v_none};
798                                break;
799                            }}
800                            case 1: {{
801                                {some}\
802                                variant{tmp} = {v_some};
803                                break;
804                            }}
805                            default: {{
806                                throw new TypeError('invalid variant discriminant for option');
807                            }}
808                        }}",
809                    );
810                } else {
811                    uwriteln!(
812                        self.src,
813                        "let variant{tmp};
814                        if ({op}) {{
815                            {some}\
816                            variant{tmp} = {v_some};
817                        }} else {{
818                            {none}\
819                            variant{tmp} = {v_none};
820                        }}"
821                    );
822                }
823
824                results.push(format!("variant{tmp}"));
825            }
826
827            Instruction::ResultLower {
828                results: result_types,
829                ..
830            } => {
831                let (mut err, err_results) = self.blocks.pop().unwrap();
832                let (mut ok, ok_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 ok_result = &ok_results[i];
843                    let err_result = &err_results[i];
844                    uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
845                    uwriteln!(err, "variant{tmp}_{i} = {err_result};");
846                }
847
848                uwriteln!(
849                    self.src,
850                    "switch (variant{tmp}.tag) {{
851                        case 'ok': {{
852                            const e = variant{tmp}.val;
853                            {ok}\
854                            break;
855                        }}
856                        case 'err': {{
857                            const e = variant{tmp}.val;
858                            {err}\
859                            break;
860                        }}
861                        default: {{
862                            throw new TypeError('invalid variant specified for result');
863                        }}
864                    }}",
865                );
866            }
867
868            Instruction::ResultLift { result, .. } => {
869                let (err, err_results) = self.blocks.pop().unwrap();
870                let (ok, ok_results) = self.blocks.pop().unwrap();
871                let ok_result = if result.ok.is_some() {
872                    assert_eq!(ok_results.len(), 1);
873                    ok_results[0].to_string()
874                } else {
875                    assert_eq!(ok_results.len(), 0);
876                    String::from("undefined")
877                };
878                let err_result = if result.err.is_some() {
879                    assert_eq!(err_results.len(), 1);
880                    err_results[0].to_string()
881                } else {
882                    assert_eq!(err_results.len(), 0);
883                    String::from("undefined")
884                };
885                let tmp = self.tmp();
886                let op0 = &operands[0];
887
888                if !self.valid_lifting_optimization {
889                    uwriteln!(
890                        self.src,
891                        "let variant{tmp};
892                        switch ({op0}) {{
893                            case 0: {{
894                                {ok}\
895                                variant{tmp} = {{
896                                    tag: 'ok',
897                                    val: {ok_result}
898                                }};
899                                break;
900                            }}
901                            case 1: {{
902                                {err}\
903                                variant{tmp} = {{
904                                    tag: 'err',
905                                    val: {err_result}
906                                }};
907                                break;
908                            }}
909                            default: {{
910                                throw new TypeError('invalid variant discriminant for expected');
911                            }}
912                        }}",
913                    );
914                } else {
915                    uwriteln!(
916                        self.src,
917                        "let variant{tmp};
918                        if ({op0}) {{
919                            {err}\
920                            variant{tmp} = {{
921                                tag: 'err',
922                                val: {err_result}
923                            }};
924                        }} else {{
925                            {ok}\
926                            variant{tmp} = {{
927                                tag: 'ok',
928                                val: {ok_result}
929                            }};
930                        }}"
931                    );
932                }
933                results.push(format!("variant{tmp}"));
934            }
935
936            Instruction::EnumLower { name, enum_, .. } => {
937                let tmp = self.tmp();
938
939                let op = &operands[0];
940                uwriteln!(self.src, "var val{tmp} = {op};");
941
942                // Declare a variable to hold the result.
943                uwriteln!(
944                    self.src,
945                    "let enum{tmp};
946                    switch (val{tmp}) {{"
947                );
948                for (i, case) in enum_.cases.iter().enumerate() {
949                    uwriteln!(
950                        self.src,
951                        "case '{case}': {{
952                            enum{tmp} = {i};
953                            break;
954                        }}",
955                        case = case.name
956                    );
957                }
958                uwriteln!(self.src, "default: {{");
959                if !self.valid_lifting_optimization {
960                    uwriteln!(
961                        self.src,
962                        "if (({op}) instanceof Error) {{
963                        console.error({op});
964                    }}"
965                    );
966                }
967                uwriteln!(
968                            self.src,
969                            "
970                            throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
971                        }}
972                    }}",
973                        );
974
975                results.push(format!("enum{tmp}"));
976            }
977
978            Instruction::EnumLift { name, enum_, .. } => {
979                let tmp = self.tmp();
980
981                uwriteln!(
982                    self.src,
983                    "let enum{tmp};
984                    switch ({}) {{",
985                    operands[0]
986                );
987                for (i, case) in enum_.cases.iter().enumerate() {
988                    uwriteln!(
989                        self.src,
990                        "case {i}: {{
991                            enum{tmp} = '{case}';
992                            break;
993                        }}",
994                        case = case.name
995                    );
996                }
997                if !self.valid_lifting_optimization {
998                    let name = name.to_upper_camel_case();
999                    uwriteln!(
1000                        self.src,
1001                        "default: {{
1002                            throw new TypeError('invalid discriminant specified for {name}');
1003                        }}",
1004                    );
1005                }
1006                uwriteln!(self.src, "}}");
1007
1008                results.push(format!("enum{tmp}"));
1009            }
1010
1011            Instruction::ListCanonLower { element, .. } => {
1012                let tmp = self.tmp();
1013                let memory = self.memory.as_ref().unwrap();
1014                let realloc = self.realloc.unwrap();
1015
1016                let size = self.sizes.size(element).size_wasm32();
1017                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1018                uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1019                if matches!(element, Type::U8) {
1020                    uwriteln!(self.src, "var len{tmp} = val{tmp}.byteLength;");
1021                } else {
1022                    uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1023                }
1024
1025                uwriteln!(
1026                    self.src,
1027                    "var ptr{tmp} = {realloc}(0, 0, {align}, len{tmp} * {size});",
1028                );
1029                // TODO: this is the wrong endianness
1030                if matches!(element, Type::U8) {
1031                    uwriteln!(
1032                                self.src,
1033                                "var src{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, len{tmp} * {size});",
1034                            );
1035                } else {
1036                    uwriteln!(
1037                                self.src,
1038                                "var src{tmp} = new Uint8Array(val{tmp}.buffer, val{tmp}.byteOffset, len{tmp} * {size});",
1039                            );
1040                }
1041                uwriteln!(
1042                    self.src,
1043                    "(new Uint8Array({memory}.buffer, ptr{tmp}, len{tmp} * {size})).set(src{tmp});",
1044                );
1045                results.push(format!("ptr{tmp}"));
1046                results.push(format!("len{tmp}"));
1047            }
1048
1049            Instruction::ListCanonLift { element, .. } => {
1050                let tmp = self.tmp();
1051                let memory = self.memory.as_ref().unwrap();
1052                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1053                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1054                // TODO: this is the wrong endianness
1055                let array_ty = array_ty(resolve, element).unwrap();
1056                uwriteln!(
1057                            self.src,
1058                            "var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));",
1059                            self.sizes.size(element).size_wasm32(),
1060                        );
1061                results.push(format!("result{tmp}"));
1062            }
1063
1064            Instruction::StringLower { .. } => {
1065                // Only Utf8 and Utf16 supported for now
1066                assert!(matches!(
1067                    self.encoding,
1068                    StringEncoding::UTF8 | StringEncoding::UTF16
1069                ));
1070                let intrinsic = if self.encoding == StringEncoding::UTF16 {
1071                    Intrinsic::String(StringIntrinsic::Utf16Encode)
1072                } else {
1073                    Intrinsic::String(StringIntrinsic::Utf8Encode)
1074                };
1075                let encode = self.intrinsic(intrinsic);
1076                let tmp = self.tmp();
1077                let memory = self.memory.as_ref().unwrap();
1078                let str = String::from("cabi_realloc");
1079                let realloc = self.realloc.unwrap_or(&str);
1080                uwriteln!(
1081                    self.src,
1082                    "var ptr{tmp} = {encode}({}, {realloc}, {memory});",
1083                    operands[0],
1084                );
1085                if self.encoding == StringEncoding::UTF8 {
1086                    let encoded_len =
1087                        self.intrinsic(Intrinsic::String(StringIntrinsic::Utf8EncodedLen));
1088                    uwriteln!(self.src, "var len{tmp} = {encoded_len};");
1089                } else {
1090                    uwriteln!(self.src, "var len{tmp} = {}.length;", operands[0]);
1091                }
1092                results.push(format!("ptr{tmp}"));
1093                results.push(format!("len{tmp}"));
1094            }
1095
1096            Instruction::StringLift => {
1097                // Only Utf8 and Utf16 supported for now
1098                assert!(matches!(
1099                    self.encoding,
1100                    StringEncoding::UTF8 | StringEncoding::UTF16
1101                ));
1102                let intrinsic = if self.encoding == StringEncoding::UTF16 {
1103                    Intrinsic::String(StringIntrinsic::Utf16Decoder)
1104                } else {
1105                    Intrinsic::String(StringIntrinsic::Utf8Decoder)
1106                };
1107                let decoder = self.intrinsic(intrinsic);
1108                let tmp = self.tmp();
1109                let memory = self.memory.as_ref().unwrap();
1110                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1111                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1112                uwriteln!(
1113                            self.src,
1114                            "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
1115                            if self.encoding == StringEncoding::UTF16 { "16" } else { "8" }
1116                        );
1117                results.push(format!("result{tmp}"));
1118            }
1119
1120            Instruction::ListLower { element, .. } => {
1121                let (body, body_results) = self.blocks.pop().unwrap();
1122                assert!(body_results.is_empty());
1123                let tmp = self.tmp();
1124                let vec = format!("vec{tmp}");
1125                let result = format!("result{tmp}");
1126                let len = format!("len{tmp}");
1127                let size = self.sizes.size(element).size_wasm32();
1128                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1129
1130                // first store our vec-to-lower in a temporary since we'll
1131                // reference it multiple times.
1132                uwriteln!(self.src, "var {vec} = {};", operands[0]);
1133                uwriteln!(self.src, "var {len} = {vec}.length;");
1134
1135                // ... then realloc space for the result in the guest module
1136                let realloc = self.realloc.as_ref().unwrap();
1137                uwriteln!(
1138                    self.src,
1139                    "var {result} = {realloc}(0, 0, {align}, {len} * {size});"
1140                );
1141
1142                // ... then consume the vector and use the block to lower the
1143                // result.
1144                uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1145                uwriteln!(self.src, "const e = {vec}[i];");
1146                uwrite!(self.src, "const base = {result} + i * {size};");
1147                self.src.push_str(&body);
1148                uwrite!(self.src, "}}\n");
1149
1150                results.push(result);
1151                results.push(len);
1152            }
1153
1154            Instruction::ListLift { element, .. } => {
1155                let (body, body_results) = self.blocks.pop().unwrap();
1156                let tmp = self.tmp();
1157                let size = self.sizes.size(element).size_wasm32();
1158                let len = format!("len{tmp}");
1159                uwriteln!(self.src, "var {len} = {};", operands[1]);
1160                let base = format!("base{tmp}");
1161                uwriteln!(self.src, "var {base} = {};", operands[0]);
1162                let result = format!("result{tmp}");
1163                uwriteln!(self.src, "var {result} = [];");
1164                results.push(result.clone());
1165
1166                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1167                uwriteln!(self.src, "const base = {base} + i * {size};");
1168                self.src.push_str(&body);
1169                assert_eq!(body_results.len(), 1);
1170                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1171                uwrite!(self.src, "}}\n");
1172            }
1173
1174            Instruction::IterElem { .. } => results.push("e".to_string()),
1175
1176            Instruction::IterBasePointer => results.push("base".to_string()),
1177
1178            Instruction::CallWasm { sig, .. } => {
1179                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1180                uwriteln!(
1181                    self.src,
1182                    "{debug_log_fn}('{prefix} [Instruction::CallWasm] (async? {async_}, @ enter)');",
1183                    prefix = self.tracing_prefix,
1184                    async_ = self.is_guest_async_lifted,
1185                );
1186
1187                // Inject machinery for starting an async 'current' task
1188                self.start_current_task(inst, self.is_guest_async_lifted, self.callee);
1189
1190                // Output result binding preamble (e.g. 'var ret =', 'var [ ret0, ret1] = exports...() ')
1191                let sig_results_length = sig.results.len();
1192                self.write_result_assignment(sig_results_length, results);
1193
1194                // Output the function call
1195                let maybe_async_await = if self.requires_async_porcelain {
1196                    "await "
1197                } else {
1198                    ""
1199                };
1200                uwriteln!(
1201                    self.src,
1202                    "{maybe_async_await}{callee}({args});",
1203                    callee = self.callee,
1204                    args = operands.join(", ")
1205                );
1206
1207                // Print post-return if tracing is enabled
1208                if self.tracing_enabled {
1209                    let prefix = self.tracing_prefix;
1210                    let to_result_string =
1211                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1212                    uwriteln!(
1213                        self.src,
1214                        "console.error(`{prefix} return {}`);",
1215                        if sig_results_length > 0 || !results.is_empty() {
1216                            format!("result=${{{to_result_string}(ret)}}")
1217                        } else {
1218                            "".to_string()
1219                        }
1220                    );
1221                }
1222
1223                // Inject machinery for ending the current task,
1224                // which may return a result if necessary
1225                self.end_current_task();
1226            }
1227
1228            // Call to an interface, usually but not always an externally imported interface
1229            Instruction::CallInterface { func, async_ } => {
1230                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1231                uwriteln!(
1232                    self.src,
1233                    "{debug_log_fn}('{prefix} [Instruction::CallInterface] (async? {async_}, @ enter)');",
1234                    prefix = self.tracing_prefix,
1235                    async_ = async_.then_some("async").unwrap_or("sync"),
1236                );
1237
1238                // Inject machinery for starting an async 'current' task
1239                self.start_current_task(inst, *async_, &func.name);
1240
1241                let results_length = if func.result.is_none() { 0 } else { 1 };
1242                let maybe_async_await = if self.requires_async_porcelain {
1243                    "await "
1244                } else {
1245                    ""
1246                };
1247                let call = if self.callee_resource_dynamic {
1248                    format!(
1249                        "{maybe_async_await}{}.{}({})",
1250                        operands[0],
1251                        self.callee,
1252                        operands[1..].join(", ")
1253                    )
1254                } else {
1255                    format!(
1256                        "{maybe_async_await}{}({})",
1257                        self.callee,
1258                        operands.join(", ")
1259                    )
1260                };
1261
1262                if self.err == ErrHandling::ResultCatchHandler {
1263                    // result<_, string> allows JS error coercion only, while
1264                    // any other result type will trap for arbitrary JS errors.
1265                    let err_payload = if let (_, Some(Type::Id(err_ty))) =
1266                        get_thrown_type(self.resolve, func.result).unwrap()
1267                    {
1268                        match &self.resolve.types[*err_ty].kind {
1269                            TypeDefKind::Type(Type::String) => {
1270                                self.intrinsic(Intrinsic::GetErrorPayloadString)
1271                            }
1272                            _ => self.intrinsic(Intrinsic::GetErrorPayload),
1273                        }
1274                    } else {
1275                        self.intrinsic(Intrinsic::GetErrorPayload)
1276                    };
1277                    uwriteln!(
1278                        self.src,
1279                        "let ret;
1280                        try {{
1281                            ret = {{ tag: 'ok', val: {call} }};
1282                        }} catch (e) {{
1283                            ret = {{ tag: 'err', val: {err_payload}(e) }};
1284                        }}",
1285                    );
1286                    results.push("ret".to_string());
1287                } else {
1288                    self.write_result_assignment(results_length, results);
1289                    uwriteln!(self.src, "{call};");
1290                }
1291
1292                uwriteln!(
1293                    self.src,
1294                    "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ post-call)');",
1295                    prefix = self.tracing_prefix,
1296                    async_ = async_.then_some("async").unwrap_or("sync"),
1297                );
1298
1299                if self.tracing_enabled {
1300                    let prefix = self.tracing_prefix;
1301                    let to_result_string =
1302                        self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1303                    uwriteln!(
1304                        self.src,
1305                        "console.error(`{prefix} return {}`);",
1306                        if results_length > 0 || !results.is_empty() {
1307                            format!("result=${{{to_result_string}(ret)}}")
1308                        } else {
1309                            "".to_string()
1310                        }
1311                    );
1312                }
1313
1314                // TODO: if it was an async call, we may not be able to clear the borrows yet.
1315                // save them to the task/ensure they are added to the task's list of borrows?
1316
1317                // After a high level call, we need to deactivate the component resource borrows.
1318                if self.clear_resource_borrows {
1319                    let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1320                    let cur_resource_borrows =
1321                        self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
1322                    let is_host = matches!(
1323                        self.resource_map.iter().nth(0).unwrap().1.data,
1324                        ResourceData::Host { .. }
1325                    );
1326
1327                    if is_host {
1328                        uwriteln!(
1329                            self.src,
1330                            "for (const rsc of {cur_resource_borrows}) {{
1331                                rsc[{symbol_resource_handle}] = undefined;
1332                            }}
1333                            {cur_resource_borrows} = [];"
1334                        );
1335                    } else {
1336                        uwriteln!(
1337                            self.src,
1338                            "for (const {{ rsc, drop }} of {cur_resource_borrows}) {{
1339                                if (rsc[{symbol_resource_handle}]) {{
1340                                    drop(rsc[{symbol_resource_handle}]);
1341                                    rsc[{symbol_resource_handle}] = undefined;
1342                                }}
1343                            }}
1344                            {cur_resource_borrows} = [];"
1345                        );
1346                    }
1347                    self.clear_resource_borrows = false;
1348                }
1349
1350                // For non-async p2 tasks, the current task should end as we will never call task.return
1351                if !async_ {
1352                    self.end_current_task();
1353                }
1354            }
1355
1356            Instruction::Return { func, amt } => {
1357                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1358                uwriteln!(
1359                    self.src,
1360                    "{debug_log_fn}('{prefix} [Instruction::Return]', {{
1361                         funcName: '{func_name}',
1362                         paramCount: {amt},
1363                         postReturn: {post_return_present}
1364                      }});",
1365                    func_name = func.name,
1366                    post_return_present = self.post_return.is_some(),
1367                    prefix = self.tracing_prefix,
1368                );
1369
1370                // Build the post return functionality
1371                // to clean up tasks and possibly return values
1372                let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
1373                    ComponentIntrinsic::GetOrCreateAsyncState,
1374                ));
1375                let gen_post_return_js = |(invocation_stmt, ret_stmt): (String, Option<String>)| {
1376                    format!(
1377                        "
1378                        let cstate = {get_or_create_async_state_fn}({component_idx});
1379                        cstate.mayLeave = false;
1380                        {invocation_stmt}
1381                        cstate.mayLeave = true;
1382                        {ret_stmt}
1383                  ",
1384                        component_idx = self.canon_opts.instance.as_u32(),
1385                        ret_stmt = ret_stmt.unwrap_or_default(),
1386                    )
1387                };
1388
1389                // Output code for combinations of results
1390                match amt {
1391                    // Handle no result case
1392                    0 => {
1393                        assert!(
1394                            !self.is_guest_async_lifted,
1395                            "async lifted guest functions must return a single i32"
1396                        );
1397                        if let Some(f) = &self.post_return {
1398                            uwriteln!(
1399                                self.src,
1400                                "{}",
1401                                gen_post_return_js((format!("{f}();"), None))
1402                            );
1403                        }
1404                    }
1405
1406                    // Handle single result<t> case
1407                    1 if self.err == ErrHandling::ThrowResultErr => {
1408                        assert!(
1409                            !self.is_guest_async_lifted,
1410                            "async lifted guest functions must return a single i32"
1411                        );
1412                        let component_err = self.intrinsic(Intrinsic::ComponentError);
1413                        let op = &operands[0];
1414                        uwriteln!(self.src, "const retCopy = {op};");
1415                        if let Some(f) = &self.post_return {
1416                            uwriteln!(
1417                                self.src,
1418                                "{}",
1419                                gen_post_return_js((format!("{f}(ret);"), None))
1420                            );
1421                        }
1422                        uwriteln!(
1423                            self.src,
1424                            "
1425                            if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
1426                                throw new {component_err}(retCopy.val);
1427                            }}
1428                            return retCopy.val;
1429                            "
1430                        );
1431                    }
1432
1433                    // Handle all other cases (including single parameter non-result<t>)
1434                    amt => {
1435                        let ret_val = match amt {
1436                            0 => unreachable!(
1437                                "unexpectedly zero return values for synchronous return"
1438                            ),
1439                            1 => operands[0].to_string(),
1440                            _ => format!("[{}]", operands.join(", ")),
1441                        };
1442
1443                        match (self.post_return, self.is_guest_async_lifted) {
1444                            (Some(_), true) => unreachable!(
1445                                "async lifted guest functions cannot have post returns"
1446                            ),
1447                            (Some(post_return_fn), _) => {
1448                                // In the case there is a post return function, we'll want to copy the value
1449                                // then perform the post return before leaving
1450
1451                                // Write out the assignment for the given return value
1452                                uwriteln!(self.src, "const retCopy = {ret_val};");
1453
1454                                // Perform the post return (if present) w/ the result, and
1455                                // pass a copy fo the result to the actual caller
1456                                uwriteln!(
1457                                    self.src,
1458                                    "{}",
1459                                    gen_post_return_js((
1460                                        format!("{post_return_fn}(ret);"),
1461                                        Some("return retCopy;".into())
1462                                    ))
1463                                );
1464                            }
1465                            // Sync functions without post returns can simply return the lifted value
1466                            (None, _is_guest_async_lifted @ false) => {
1467                                uwriteln!(self.src, "return {ret_val};",)
1468                            }
1469                            // Async functions never have post returns, but they must interpret async behavior
1470                            (None, _is_guest_async_lifted @ true) => {
1471                                uwriteln!(self.src, r#"throw new Error("not yet implemented!");"#,);
1472                            }
1473                        }
1474                    }
1475                }
1476            }
1477
1478            Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1479
1480            Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1481
1482            Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1483
1484            Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1485
1486            Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1487
1488            Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
1489
1490            Instruction::I32Load16U { offset } => {
1491                self.load("getUint16", *offset, operands, results)
1492            }
1493
1494            Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
1495
1496            Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
1497
1498            Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
1499
1500            Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
1501
1502            Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
1503
1504            Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
1505
1506            Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
1507
1508            Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
1509
1510            Instruction::LengthLoad { offset } => {
1511                self.load("getUint32", *offset, operands, results)
1512            }
1513
1514            Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
1515
1516            Instruction::PointerLoad { offset } => {
1517                self.load("getUint32", *offset, operands, results)
1518            }
1519
1520            Instruction::Malloc { size, align, .. } => {
1521                let tmp = self.tmp();
1522                let realloc = self.realloc.as_ref().unwrap();
1523                let ptr = format!("ptr{tmp}");
1524                uwriteln!(
1525                    self.src,
1526                    "var {ptr} = {realloc}(0, 0, {align}, {size});",
1527                    align = align.align_wasm32(),
1528                    size = size.size_wasm32()
1529                );
1530                results.push(ptr);
1531            }
1532
1533            Instruction::HandleLift { handle, .. } => {
1534                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1535                let resource_ty = &crate::dealias(self.resolve, *ty);
1536                let ResourceTable { imported, data } = &self.resource_map[resource_ty];
1537
1538                let is_own = matches!(handle, Handle::Own(_));
1539                let rsc = format!("rsc{}", self.tmp());
1540                let handle = format!("handle{}", self.tmp());
1541                uwriteln!(self.src, "var {handle} = {};", &operands[0]);
1542
1543                match data {
1544                    ResourceData::Host {
1545                        tid,
1546                        rid,
1547                        local_name,
1548                        dtor_name,
1549                    } => {
1550                        let tid = tid.as_u32();
1551                        let rid = rid.as_u32();
1552                        let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1553                        let rsc_table_remove = self
1554                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
1555                        let rsc_flag = self
1556                            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
1557                        if !imported {
1558                            let symbol_resource_handle =
1559                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1560                            uwriteln!(self.src, "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);");
1561                            if is_own {
1562                                // Sending an own handle out to JS as a return value - set up finalizer and disposal.
1563                                let empty_func = self
1564                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
1565                                uwriteln!(self.src,
1566                                            "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1567                                    finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
1568                                if let Some(dtor) = dtor_name {
1569                                    // The Symbol.dispose function gets disabled on drop, so we can rely on the own handle remaining valid.
1570                                    uwriteln!(
1571                                                self.src,
1572                                                "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
1573                                            finalizationRegistry{tid}.unregister({rsc});
1574                                            {rsc_table_remove}(handleTable{tid}, {handle});
1575                                            {rsc}[{symbol_dispose}] = {empty_func};
1576                                            {rsc}[{symbol_resource_handle}] = undefined;
1577                                            {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
1578                                        }}}});"
1579                                            );
1580                                } else {
1581                                    // Set up Symbol.dispose for borrows to allow its call, even though it does nothing.
1582                                    uwriteln!(
1583                                                self.src,
1584                                                "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});",
1585                                            );
1586                                }
1587                            } else {
1588                                // Borrow handles of local resources have rep handles, which we carry through here.
1589                                uwriteln!(self.src, "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});");
1590                            }
1591                        } else {
1592                            let rep = format!("rep{}", self.tmp());
1593                            // Imported handles either lift as instance capture from a previous lowering,
1594                            // or we create a new JS class to represent it.
1595                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
1596                            let symbol_resource_handle =
1597                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1598                            uwriteln!(self.src,
1599                                        "var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
1600                                var {rsc} = captureTable{rid}.get({rep});
1601                                if (!{rsc}) {{
1602                                    {rsc} = Object.create({local_name}.prototype);
1603                                    Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1604                                    Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
1605                                }}"
1606                                    );
1607                            if is_own {
1608                                // An own lifting is a transfer to JS, so existing own handle is implicitly dropped.
1609                                uwriteln!(
1610                                    self.src,
1611                                    "else {{
1612                                        captureTable{rid}.delete({rep});
1613                                    }}
1614                                    {rsc_table_remove}(handleTable{tid}, {handle});"
1615                                );
1616                            }
1617                        }
1618
1619                        // Borrow handles are tracked to release after the call by CallInterface.
1620                        if !is_own {
1621                            let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
1622                                ResourceIntrinsic::CurResourceBorrows,
1623                            ));
1624                            uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
1625                            self.clear_resource_borrows = true;
1626                        }
1627                    }
1628
1629                    ResourceData::Guest {
1630                        resource_name,
1631                        prefix,
1632                    } => {
1633                        let symbol_resource_handle =
1634                            self.intrinsic(Intrinsic::SymbolResourceHandle);
1635                        let prefix = prefix.as_deref().unwrap_or("");
1636                        let lower_camel = resource_name.to_lower_camel_case();
1637
1638                        if !imported {
1639                            if is_own {
1640                                uwriteln!(self.src, "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;");
1641                                uwrite!(
1642                                            self.src,
1643                                            "repTable.delete({handle});
1644                                     delete {rsc}[{symbol_resource_handle}];
1645                                     finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
1646                                    "
1647                                        );
1648                            } else {
1649                                uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
1650                            }
1651                        } else {
1652                            let upper_camel = resource_name.to_upper_camel_case();
1653
1654                            uwrite!(
1655                                        self.src,
1656                                        "var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
1657                                 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1658                                "
1659                                    );
1660
1661                            uwriteln!(
1662                                        self.src,
1663                                        "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
1664                                    );
1665
1666                            if !is_own {
1667                                let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
1668                                    ResourceIntrinsic::CurResourceBorrows,
1669                                ));
1670                                uwriteln!(self.src, "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});");
1671                                self.clear_resource_borrows = true;
1672                            }
1673                        }
1674                    }
1675                }
1676                results.push(rsc);
1677            }
1678
1679            Instruction::HandleLower { handle, name, .. } => {
1680                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1681                let is_own = matches!(handle, Handle::Own(_));
1682                let ResourceTable { imported, data } =
1683                    &self.resource_map[&crate::dealias(self.resolve, *ty)];
1684
1685                let class_name = name.to_upper_camel_case();
1686                let handle = format!("handle{}", self.tmp());
1687                let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1688                let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1689                let op = &operands[0];
1690
1691                match data {
1692                    ResourceData::Host {
1693                        tid,
1694                        rid,
1695                        local_name,
1696                        ..
1697                    } => {
1698                        let tid = tid.as_u32();
1699                        let rid = rid.as_u32();
1700                        if !imported {
1701                            if is_own {
1702                                let empty_func = self
1703                                    .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
1704                                uwriteln!(
1705                                            self.src,
1706                                            "var {handle} = {op}[{symbol_resource_handle}];
1707                                    if (!{handle}) {{
1708                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1709                                    }}
1710                                    finalizationRegistry{tid}.unregister({op});
1711                                    {op}[{symbol_dispose}] = {empty_func};
1712                                    {op}[{symbol_resource_handle}] = undefined;",
1713                                        );
1714                            } else {
1715                                // When expecting a borrow, the JS resource provided will always be an own
1716                                // handle. This is because it is not possible for borrow handles to be passed
1717                                // back reentrantly.
1718                                // We then set the handle to the rep per the local borrow rule.
1719                                let rsc_flag = self.intrinsic(Intrinsic::Resource(
1720                                    ResourceIntrinsic::ResourceTableFlag,
1721                                ));
1722                                let own_handle = format!("handle{}", self.tmp());
1723                                uwriteln!(self.src,
1724                                            "var {own_handle} = {op}[{symbol_resource_handle}];
1725                                    if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
1726                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1727                                    }}
1728                                    var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};",
1729                                        );
1730                            }
1731                        } else {
1732                            // Imported resources may already have a handle if they were constructed
1733                            // by a component and then passed out.
1734                            uwriteln!(
1735                                        self.src,
1736                                        "if (!({op} instanceof {local_name})) {{
1737                                     throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1738                                 }}
1739                                 var {handle} = {op}[{symbol_resource_handle}];",
1740                                    );
1741                            // Otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
1742                            // to get the resource rep.
1743                            // Fall back to assign a new rep in the capture table, when the imported
1744                            // resource was constructed externally.
1745                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
1746                            let rsc_table_create = if is_own {
1747                                self.intrinsic(Intrinsic::Resource(
1748                                    ResourceIntrinsic::ResourceTableCreateOwn,
1749                                ))
1750                            } else {
1751                                self.intrinsic(Intrinsic::ScopeId);
1752                                self.intrinsic(Intrinsic::Resource(
1753                                    ResourceIntrinsic::ResourceTableCreateBorrow,
1754                                ))
1755                            };
1756                            uwriteln!(
1757                                self.src,
1758                                "if (!{handle}) {{
1759                                    const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
1760                                    captureTable{rid}.set(rep, {op});
1761                                    {handle} = {rsc_table_create}(handleTable{tid}, rep);
1762                                }}"
1763                            );
1764                        }
1765                    }
1766
1767                    ResourceData::Guest {
1768                        resource_name,
1769                        prefix,
1770                    } => {
1771                        let upper_camel = resource_name.to_upper_camel_case();
1772                        let lower_camel = resource_name.to_lower_camel_case();
1773                        let prefix = prefix.as_deref().unwrap_or("");
1774
1775                        if !imported {
1776                            let local_rep = format!("localRep{}", self.tmp());
1777                            uwriteln!(
1778                                        self.src,
1779                                        "if (!({op} instanceof {upper_camel})) {{
1780                                    throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
1781                                }}
1782                                let {handle} = {op}[{symbol_resource_handle}];",
1783                                    );
1784
1785                            if is_own {
1786                                uwriteln!(
1787                                            self.src,
1788                                            "if ({handle} === undefined) {{
1789                                        var {local_rep} = repCnt++;
1790                                        repTable.set({local_rep}, {{ rep: {op}, own: true }});
1791                                        {handle} = $resource_{prefix}new${lower_camel}({local_rep});
1792                                        {op}[{symbol_resource_handle}] = {handle};
1793                                        finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
1794                                    }}
1795                                    "
1796                                        );
1797                            } else {
1798                                uwriteln!(
1799                                    self.src,
1800                                    "if ({handle} === undefined) {{
1801                                        var {local_rep} = repCnt++;
1802                                        repTable.set({local_rep}, {{ rep: {op}, own: false }});
1803                                        {op}[{symbol_resource_handle}] = {local_rep};
1804                                    }}
1805                                    "
1806                                );
1807                            }
1808                        } else {
1809                            let symbol_resource_handle =
1810                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1811                            uwrite!(
1812                                self.src,
1813                                "var {handle} = {op}[{symbol_resource_handle}];
1814                                 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
1815                                "
1816                            );
1817                        }
1818                    }
1819                }
1820                results.push(handle);
1821            }
1822
1823            Instruction::DropHandle { ty } => {
1824                let _ = ty;
1825                todo!("[Instruction::DropHandle] not yet implemented")
1826            }
1827
1828            Instruction::Flush { amt } => {
1829                for item in operands.iter().take(*amt) {
1830                    results.push(item.clone());
1831                }
1832            }
1833
1834            Instruction::ErrorContextLift { .. } => {
1835                uwrite!(self.src, "throw new Error('[Instruction::ErrorContextLift] async is not yet implemented');");
1836            }
1837            Instruction::ErrorContextLower { .. } => {
1838                uwrite!(self.src, "throw new Error('[Instruction::ErrorContextLower] async is not yet implemented');");
1839            }
1840
1841            Instruction::FutureLower { .. } => {
1842                uwrite!(
1843                    self.src,
1844                    "throw new Error('[Instruction::FutureLower] async is not yet implemented');"
1845                );
1846            }
1847            Instruction::FutureLift { .. } => {
1848                uwrite!(
1849                    self.src,
1850                    "throw new Error('[Instruction::FutureLift] async is not yet implemented');"
1851                );
1852            }
1853
1854            Instruction::StreamLower { .. } => {
1855                uwrite!(
1856                    self.src,
1857                    "throw new Error('[Instruction::StreamLower] async is not yet implemented');"
1858                );
1859            }
1860
1861            Instruction::StreamLift { .. } => {
1862                uwrite!(
1863                    self.src,
1864                    "throw new Error('[Instruction::StreamLift] async is not yet implemented');"
1865                );
1866            }
1867
1868            // Instruction::AsyncTaskReturn does *not* correspond to an canonical `task.return`,
1869            // but rather to a "return"/exit from an async function (e.g. pre-callback)
1870            //
1871            // At this point, `ret` has already been declared as the original return value
1872            // of the function that was called.
1873            //
1874            // For an async function 'some-func', this instruction is triggered w/ the following `name`s:
1875            // - '[task-return]some-func'
1876            //
1877            Instruction::AsyncTaskReturn { name, params } => {
1878                let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1879                uwriteln!(
1880                    self.src,
1881                    "{debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn]', {{
1882                         funcName: '{name}',
1883                         paramCount: {param_count},
1884                         postReturn: {post_return_present}
1885                      }});",
1886                    param_count = params.len(),
1887                    post_return_present = self.post_return.is_some(),
1888                    prefix = self.tracing_prefix,
1889                );
1890
1891                assert!(
1892                    self.is_guest_async_lifted,
1893                    "non-async functions should not be performing async returns (func {name})",
1894                );
1895                assert!(
1896                    self.post_return.is_none(),
1897                    "async fns cannot have post_return specified (func {name})"
1898                );
1899
1900                let get_current_task_fn =
1901                    self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1902                let component_idx = self.canon_opts.instance.as_u32();
1903
1904                let i32_typecheck = self.intrinsic(Intrinsic::TypeCheckValidI32);
1905                let to_int32_fn =
1906                    self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
1907                let unpack_callback_result_fn = self.intrinsic(Intrinsic::AsyncTask(
1908                    AsyncTaskIntrinsic::UnpackCallbackResult,
1909                ));
1910                let callback_fn_name = self
1911                    .canon_opts
1912                    .callback
1913                    // see: GlobalInitializer::ExtractCallback
1914                    .map(|v| format!("callback_{}", v.as_u32()))
1915                    .expect("callback function name missing");
1916
1917                // Generate the fn signatures for task function calls,
1918                // since we may be using async porcelain or not for this function
1919                let (
1920                    task_fn_call_prefix,
1921                    task_yield_fn,
1922                    task_wait_for_event_fn,
1923                    task_poll_for_event_fn,
1924                ) = if self.requires_async_porcelain {
1925                    (
1926                        "await ",
1927                        "task.yield",
1928                        "task.waitForEvent",
1929                        "task.pollForEvent",
1930                    )
1931                } else {
1932                    (
1933                        "",
1934                        "task.yieldSync",
1935                        "task.waitForEventSync",
1936                        "task.pollForEventSync",
1937                    )
1938                };
1939
1940                uwriteln!(
1941                    self.src,
1942                    r#"
1943                    const retCopy = {first_op};
1944                    if (retCopy !== undefined) {{
1945                        if (!({i32_typecheck}(retCopy))) {{ throw new Error('invalid async return value [' + retCopy + '], not a number'); }}
1946                        if (retCopy < 0 || retCopy > 3) {{
1947                            throw new Error('invalid async return value, outside callback code range');
1948                        }}
1949                    }}
1950
1951                    const taskMeta = {get_current_task_fn}({component_idx});
1952                    if (!taskMeta) {{ throw new Error('missing/invalid current task metadata'); }}
1953
1954                    const task = taskMeta.task;
1955                    if (!task) {{ throw new Error('missing/invalid current task in metadata'); }}
1956
1957                    let currentRes = retCopy;
1958                    let taskRes, eventCode, index, result;
1959                    if (currentRes !== undefined) {{
1960                        while (true) {{
1961                            let [code, waitableSetIdx] = {unpack_callback_result_fn}(currentRes);
1962                            switch (code) {{
1963                                case 0: // EXIT
1964                                    {debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn] exit', {{ fn: '{name}' }});
1965                                    task.exit();
1966                                    const results = task.takeResults();
1967                                    if (!results || !Array.isArray(results)) {{
1968                                        throw new Error('missing/invalid results, non-array');
1969                                    }}
1970                                    switch (results.length) {{
1971                                        case 0: return undefined;
1972                                        case 1: return results[0];
1973                                        default:
1974                                            throw new Error('multi-return is not supported');
1975                                    }}
1976                                case 1: // YIELD
1977                                    {debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn] yield', {{ fn: '{name}' }});
1978                                    taskRes = {task_fn_call_prefix}{task_yield_fn}({{ isCancellable: true, forCallback: true }});
1979                                    break;
1980                                case 2: // WAIT for a given waitable set
1981                                    {debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn] waiting for event', {{ waitableSetIdx }});
1982                                    taskRes = {task_fn_call_prefix}{task_wait_for_event_fn}({{ isAsync: true, waitableSetIdx }});
1983                                    break;
1984                                case 3: // POLL
1985                                    {debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn] polling for event', {{ waitableSetIdx }});
1986                                    taskRes = {task_fn_call_prefix}{task_poll_for_event_fn}({{ isAsync: true, waitableSetIdx }});
1987                                    break;
1988                                default:
1989                                    throw new Error('invalid async return value [' + retCopy + ']');
1990                            }}
1991
1992                            eventCode = taskRes[0];
1993                            index = taskRes[1];
1994                            result = taskRes[2];
1995                            {debug_log_fn}('performing callback', {{ fn: "{callback_fn_name}", eventCode, index, result }});
1996                            currentRes = {callback_fn_name}(
1997                                {to_int32_fn}(eventCode),
1998                                {to_int32_fn}(index),
1999                                {to_int32_fn}(result),
2000                            );
2001                        }}
2002                    }}
2003                    "#,
2004                    first_op = operands.first().map(|s| s.as_str()).unwrap_or("undefined"),
2005                    prefix = self.tracing_prefix,
2006                );
2007
2008                // Inject machinery for ending an async 'current' task
2009                // which may return a result if necessary
2010                self.end_current_task();
2011            }
2012
2013            Instruction::GuestDeallocate { .. }
2014            | Instruction::GuestDeallocateString
2015            | Instruction::GuestDeallocateList { .. }
2016            | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
2017        }
2018    }
2019}
2020
2021/// Tests whether `ty` can be represented with `null`, and if it can then
2022/// the "other type" is returned. If `Some` is returned that means that `ty`
2023/// is `null | <return>`. If `None` is returned that means that `null` can't
2024/// be used to represent `ty`.
2025pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
2026    let id = match ty {
2027        Type::Id(id) => *id,
2028        _ => return None,
2029    };
2030    match &resolve.types[id].kind {
2031        // If `ty` points to an `option<T>`, then `ty` can be represented
2032        // with `null` if `t` itself can't be represented with null. For
2033        // example `option<option<u32>>` can't be represented with `null`
2034        // since that's ambiguous if it's `none` or `some(none)`.
2035        //
2036        // Note, oddly enough, that `option<option<option<u32>>>` can be
2037        // represented as `null` since:
2038        //
2039        // * `null` => `none`
2040        // * `{ tag: "none" }` => `some(none)`
2041        // * `{ tag: "some", val: null }` => `some(some(none))`
2042        // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
2043        //
2044        // It's doubtful anyone would actually rely on that though due to
2045        // how confusing it is.
2046        TypeDefKind::Option(t) => {
2047            if !maybe_null(resolve, t) {
2048                Some(t)
2049            } else {
2050                None
2051            }
2052        }
2053        TypeDefKind::Type(t) => as_nullable(resolve, t),
2054        _ => None,
2055    }
2056}
2057
2058pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
2059    as_nullable(resolve, ty).is_some()
2060}
2061
2062pub fn array_ty(resolve: &Resolve, ty: &Type) -> Option<&'static str> {
2063    match ty {
2064        Type::Bool => None,
2065        Type::U8 => Some("Uint8Array"),
2066        Type::S8 => Some("Int8Array"),
2067        Type::U16 => Some("Uint16Array"),
2068        Type::S16 => Some("Int16Array"),
2069        Type::U32 => Some("Uint32Array"),
2070        Type::S32 => Some("Int32Array"),
2071        Type::U64 => Some("BigUint64Array"),
2072        Type::S64 => Some("BigInt64Array"),
2073        Type::F32 => Some("Float32Array"),
2074        Type::F64 => Some("Float64Array"),
2075        Type::Char => None,
2076        Type::String => None,
2077        Type::ErrorContext => None,
2078        Type::Id(id) => match &resolve.types[*id].kind {
2079            TypeDefKind::Type(t) => array_ty(resolve, t),
2080            _ => None,
2081        },
2082    }
2083}