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