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