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