Skip to main content

js_component_bindgen/
function_bindgen.rs

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