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