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::{ResourceIndex, TypeResourceTableIndex};
7use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
8use wit_component::StringEncoding;
9use wit_parser::abi::WasmType;
10use wit_parser::{ArchitectureSize, Handle, Resolve, SizeAlign, Type, TypeDefKind, TypeId};
11
12use crate::intrinsics::Intrinsic;
13use crate::source;
14use crate::{uwrite, uwriteln};
15
16#[derive(PartialEq)]
17pub enum ErrHandling {
18    None,
19    ThrowResultErr,
20    ResultCatchHandler,
21}
22
23#[derive(Clone, Debug, PartialEq)]
24pub enum ResourceData {
25    Host {
26        tid: TypeResourceTableIndex,
27        rid: ResourceIndex,
28        local_name: String,
29        dtor_name: Option<String>,
30    },
31    Guest {
32        resource_name: String,
33        prefix: Option<String>,
34    },
35}
36
37///
38/// Map used for resource function bindgen within a given component
39///
40/// Mapping from the instance + resource index in that component (internal or external)
41/// to the unique global resource id used to key the resource tables for this resource.
42///
43/// The id value uniquely identifies the resource table so that if a resource is used
44/// by n components, there should be n different indices and spaces in use. The map is
45/// therefore entirely unique and fully distinct for each instance's function bindgen.
46///
47/// The second bool is true if it is an imported resource.
48///
49/// For a given resource table id {x}, with resource index {y} the local variables are assumed:
50/// - handleTable{x}
51/// - captureTable{y} (rep to instance map for captured imported tables, only for JS import bindgen,
52///                    not hybrid)
53/// - captureCnt{y} for assigning capture rep
54///
55/// For component-defined resources:
56/// - finalizationRegistry{x}
57///
58/// handleTable internally will be allocated with { rep: i32, own: bool } entries
59///
60/// In the case of an imported resource tables, in place of "rep" we just store
61/// the direct JS object being referenced, since in JS the object is its own handle.
62///
63///
64#[derive(Clone, Debug, PartialEq)]
65pub struct ResourceTable {
66    pub imported: bool,
67    pub data: ResourceData,
68}
69pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
70
71pub struct FunctionBindgen<'a> {
72    pub resource_map: &'a ResourceMap,
73    pub cur_resource_borrows: bool,
74    pub intrinsics: &'a mut BTreeSet<Intrinsic>,
75    pub valid_lifting_optimization: bool,
76    pub sizes: &'a SizeAlign,
77    pub err: ErrHandling,
78    pub tmp: usize,
79    pub src: source::Source,
80    pub block_storage: Vec<source::Source>,
81    pub blocks: Vec<(String, Vec<String>)>,
82    pub params: Vec<String>,
83    pub memory: Option<&'a String>,
84    pub realloc: Option<&'a String>,
85    pub post_return: Option<&'a String>,
86    pub tracing_prefix: Option<&'a String>,
87    pub encoding: StringEncoding,
88    pub callee: &'a str,
89    pub callee_resource_dynamic: bool,
90    pub resolve: &'a Resolve,
91    pub is_async: bool,
92}
93
94impl FunctionBindgen<'_> {
95    fn tmp(&mut self) -> usize {
96        let ret = self.tmp;
97        self.tmp += 1;
98        ret
99    }
100
101    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
102        self.intrinsics.insert(intrinsic);
103        intrinsic.name().to_string()
104    }
105
106    fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
107    where
108        T: std::fmt::Display,
109    {
110        let clamp = self.intrinsic(Intrinsic::ClampGuest);
111        results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
112    }
113
114    fn load(&mut self, method: &str, offset: i32, operands: &[String], results: &mut Vec<String>) {
115        let view = self.intrinsic(Intrinsic::DataView);
116        let memory = self.memory.as_ref().unwrap();
117        results.push(format!(
118            "{view}({memory}).{method}({} + {offset}, true)",
119            operands[0],
120        ));
121    }
122
123    fn store(&mut self, method: &str, offset: i32, operands: &[String]) {
124        let view = self.intrinsic(Intrinsic::DataView);
125        let memory = self.memory.as_ref().unwrap();
126        uwriteln!(
127            self.src,
128            "{view}({memory}).{method}({} + {offset}, {}, true);",
129            operands[1],
130            operands[0]
131        );
132    }
133
134    fn bind_results(&mut self, amt: usize, results: &mut Vec<String>) {
135        match amt {
136            0 => {}
137            1 => {
138                uwrite!(self.src, "const ret = ");
139                results.push("ret".to_string());
140            }
141            n => {
142                uwrite!(self.src, "var [");
143                for i in 0..n {
144                    if i > 0 {
145                        uwrite!(self.src, ", ");
146                    }
147                    uwrite!(self.src, "ret{}", i);
148                    results.push(format!("ret{}", i));
149                }
150                uwrite!(self.src, "] = ");
151            }
152        }
153    }
154
155    fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
156        match cast {
157            Bitcast::I32ToF32 => {
158                let cvt = self.intrinsic(Intrinsic::I32ToF32);
159                format!("{cvt}({op})")
160            }
161            Bitcast::F32ToI32 => {
162                let cvt = self.intrinsic(Intrinsic::F32ToI32);
163                format!("{cvt}({op})")
164            }
165            Bitcast::I64ToF64 => {
166                let cvt = self.intrinsic(Intrinsic::I64ToF64);
167                format!("{cvt}({op})")
168            }
169            Bitcast::F64ToI64 => {
170                let cvt = self.intrinsic(Intrinsic::F64ToI64);
171                format!("{}({})", cvt, op)
172            }
173            Bitcast::I32ToI64 => format!("BigInt({op})"),
174            Bitcast::I64ToI32 => format!("Number({op})"),
175            Bitcast::I64ToF32 => {
176                let cvt = self.intrinsic(Intrinsic::I32ToF32);
177                format!("{cvt}(Number({op}))")
178            }
179            Bitcast::F32ToI64 => {
180                let cvt = self.intrinsic(Intrinsic::F32ToI32);
181                format!("BigInt({cvt}({op}))")
182            }
183            Bitcast::None
184            | Bitcast::P64ToI64
185            | Bitcast::LToI32
186            | Bitcast::I32ToL
187            | Bitcast::LToP
188            | Bitcast::PToL
189            | Bitcast::PToI32
190            | Bitcast::I32ToP => op.to_string(),
191            Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
192            Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
193            Bitcast::Sequence(casts) => {
194                let mut statement = op.to_string();
195                for cast in casts.iter() {
196                    statement = self.bitcast(cast, &statement);
197                }
198                statement
199            }
200        }
201    }
202}
203
204impl Bindgen for FunctionBindgen<'_> {
205    type Operand = String;
206
207    fn sizes(&self) -> &SizeAlign {
208        self.sizes
209    }
210
211    fn push_block(&mut self) {
212        let prev = mem::take(&mut self.src);
213        self.block_storage.push(prev);
214    }
215
216    fn finish_block(&mut self, operands: &mut Vec<String>) {
217        let to_restore = self.block_storage.pop().unwrap();
218        let src = mem::replace(&mut self.src, to_restore);
219        self.blocks.push((src.into(), mem::take(operands)));
220    }
221
222    fn return_pointer(&mut self, _size: usize, _align: usize) -> String {
223        unimplemented!();
224    }
225
226    fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool {
227        array_ty(resolve, ty).is_some()
228    }
229
230    fn emit(
231        &mut self,
232        resolve: &Resolve,
233        inst: &Instruction<'_>,
234        operands: &mut Vec<String>,
235        results: &mut Vec<String>,
236    ) {
237        match inst {
238            Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
239            Instruction::I32Const { val } => results.push(val.to_string()),
240            Instruction::ConstZero { tys } => {
241                for t in tys.iter() {
242                    match t {
243                        WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
244                        WasmType::I32
245                        | WasmType::F32
246                        | WasmType::F64
247                        | WasmType::Pointer
248                        | WasmType::Length => results.push("0".to_string()),
249                    }
250                }
251            }
252
253            // The representation of i32 in JS is a number, so 8/16-bit values
254            // get further clamped to ensure that the upper bits aren't set when
255            // we pass the value, ensuring that only the right number of bits
256            // are transferred.
257            Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
258            Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
259            Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
260            Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
261            // Use `>>>0` to ensure the bits of the number are treated as
262            // unsigned.
263            Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
264            // All bigints coming from wasm are treated as signed, so convert
265            // it to ensure it's treated as unsigned.
266            Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])),
267            // Nothing to do signed->signed where the representations are the
268            // same.
269            Instruction::S32FromI32 | Instruction::S64FromI64 => {
270                results.push(operands.pop().unwrap())
271            }
272
273            // All values coming from the host and going to wasm need to have
274            // their ranges validated, since the host could give us any value.
275            Instruction::I32FromU8 => {
276                let conv = self.intrinsic(Intrinsic::ToUint8);
277                results.push(format!("{conv}({op})", op = operands[0]))
278            }
279            Instruction::I32FromS8 => {
280                let conv = self.intrinsic(Intrinsic::ToInt8);
281                results.push(format!("{conv}({op})", op = operands[0]))
282            }
283            Instruction::I32FromU16 => {
284                let conv = self.intrinsic(Intrinsic::ToUint16);
285                results.push(format!("{conv}({op})", op = operands[0]))
286            }
287            Instruction::I32FromS16 => {
288                let conv = self.intrinsic(Intrinsic::ToInt16);
289                results.push(format!("{conv}({op})", op = operands[0]))
290            }
291            Instruction::I32FromU32 => {
292                let conv = self.intrinsic(Intrinsic::ToUint32);
293                results.push(format!("{conv}({op})", op = operands[0]))
294            }
295            Instruction::I32FromS32 => {
296                let conv = self.intrinsic(Intrinsic::ToInt32);
297                results.push(format!("{conv}({op})", op = operands[0]))
298            }
299            Instruction::I64FromU64 => {
300                let conv = self.intrinsic(Intrinsic::ToBigUint64);
301                results.push(format!("{conv}({op})", op = operands[0]))
302            }
303            Instruction::I64FromS64 => {
304                let conv = self.intrinsic(Intrinsic::ToBigInt64);
305                results.push(format!("{conv}({op})", op = operands[0]))
306            }
307
308            // The native representation in JS of f32 and f64 is just a number,
309            // so there's nothing to do here. Everything wasm gives us is
310            // representable in JS.
311            Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
312                results.push(operands.pop().unwrap())
313            }
314
315            // Use a unary `+` to cast to a float.
316            Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
317                results.push(format!("+{}", operands[0]))
318            }
319
320            // Validate that i32 values coming from wasm are indeed valid code
321            // points.
322            Instruction::CharFromI32 => {
323                let validate = self.intrinsic(Intrinsic::ValidateGuestChar);
324                results.push(format!("{}({})", validate, operands[0]));
325            }
326
327            // Validate that strings are indeed 1 character long and valid
328            // unicode.
329            Instruction::I32FromChar => {
330                let validate = self.intrinsic(Intrinsic::ValidateHostChar);
331                results.push(format!("{}({})", validate, operands[0]));
332            }
333
334            Instruction::Bitcasts { casts } => {
335                for (cast, op) in casts.iter().zip(operands) {
336                    results.push(self.bitcast(cast, op));
337                }
338            }
339
340            Instruction::BoolFromI32 => {
341                let tmp = self.tmp();
342                uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
343                if self.valid_lifting_optimization {
344                    results.push(format!("!!bool{tmp}"));
345                } else {
346                    let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
347                    results.push(format!(
348                        "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
349                    ));
350                }
351            }
352            Instruction::I32FromBool => {
353                results.push(format!("{} ? 1 : 0", operands[0]));
354            }
355
356            Instruction::RecordLower { record, .. } => {
357                // use destructuring field access to get each
358                // field individually.
359                let tmp = self.tmp();
360                let mut expr = "var {".to_string();
361                for (i, field) in record.fields.iter().enumerate() {
362                    if i > 0 {
363                        expr.push_str(", ");
364                    }
365                    let name = format!("v{}_{}", tmp, i);
366                    expr.push_str(&field.name.to_lower_camel_case());
367                    expr.push_str(": ");
368                    expr.push_str(&name);
369                    results.push(name);
370                }
371                uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
372            }
373
374            Instruction::RecordLift { record, .. } => {
375                // records are represented as plain objects, so we
376                // make a new object and set all the fields with an object
377                // literal.
378                let mut result = "{\n".to_string();
379                for (field, op) in record.fields.iter().zip(operands) {
380                    result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
381                }
382                result.push('}');
383                results.push(result);
384            }
385
386            Instruction::TupleLower { tuple, .. } => {
387                // Tuples are represented as an array, sowe can use
388                // destructuring assignment to lower the tuple into its
389                // components.
390                let tmp = self.tmp();
391                let mut expr = "var [".to_string();
392                for i in 0..tuple.types.len() {
393                    if i > 0 {
394                        expr.push_str(", ");
395                    }
396                    let name = format!("tuple{}_{}", tmp, i);
397                    expr.push_str(&name);
398                    results.push(name);
399                }
400                uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
401            }
402
403            Instruction::TupleLift { .. } => {
404                // Tuples are represented as an array, so we just shove all
405                // the operands into an array.
406                results.push(format!("[{}]", operands.join(", ")));
407            }
408
409            // This lowers flags from a dictionary of booleans in accordance with https://webidl.spec.whatwg.org/#es-dictionary.
410            Instruction::FlagsLower { flags, .. } => {
411                let op0 = &operands[0];
412
413                // Generate the result names.
414                for _ in 0..flags.repr().count() {
415                    let tmp = self.tmp();
416                    let name = format!("flags{tmp}");
417                    // Default to 0 so that in the null/undefined case, everything is false by
418                    // default.
419                    uwrite!(self.src, "let {name} = 0;\n");
420                    results.push(name);
421                }
422
423                uwrite!(
424                    self.src,
425                    "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
426                );
427
428                for (i, chunk) in flags.flags.chunks(32).enumerate() {
429                    let result_name = &results[i];
430
431                    uwrite!(self.src, "{result_name} = ");
432                    for (i, flag) in chunk.iter().enumerate() {
433                        if i != 0 {
434                            uwrite!(self.src, " | ");
435                        }
436
437                        let flag = flag.name.to_lower_camel_case();
438                        uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
439                    }
440                    uwrite!(self.src, ";\n");
441                }
442
443                uwrite!(
444                    self.src,
445                    "\
446                    }} else if ({op0} !== null && {op0} !== undefined) {{
447                        throw new TypeError('only an object, undefined or null can be converted to flags');
448                    }}
449                ");
450
451                // We don't need to do anything else for the null/undefined
452                // case, since that's interpreted as everything false, and we
453                // already defaulted everyting to 0.
454            }
455
456            Instruction::FlagsLift { flags, .. } => {
457                let tmp = self.tmp();
458                results.push(format!("flags{tmp}"));
459
460                if let Some(op) = operands.last() {
461                    // We only need an extraneous bits check if the number of flags isn't a multiple
462                    // of 32, because if it is then all the bits are used and there are no
463                    // extraneous bits.
464                    if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
465                        let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
466                        uwriteln!(
467                            self.src,
468                            "if (({op} & {mask}) !== 0) {{
469                                throw new TypeError('flags have extraneous bits set');
470                            }}"
471                        );
472                    }
473                }
474
475                uwriteln!(self.src, "var flags{tmp} = {{");
476
477                for (i, flag) in flags.flags.iter().enumerate() {
478                    let flag = flag.name.to_lower_camel_case();
479                    let op = &operands[i / 32];
480                    let mask: u32 = 1 << (i % 32);
481                    uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
482                }
483
484                uwriteln!(self.src, "}};");
485            }
486
487            Instruction::VariantPayloadName => results.push("e".to_string()),
488
489            Instruction::VariantLower {
490                variant,
491                results: result_types,
492                name,
493                ..
494            } => {
495                let blocks = self
496                    .blocks
497                    .drain(self.blocks.len() - variant.cases.len()..)
498                    .collect::<Vec<_>>();
499                let tmp = self.tmp();
500                let op = &operands[0];
501                uwriteln!(self.src, "var variant{tmp} = {op};");
502
503                for i in 0..result_types.len() {
504                    uwriteln!(self.src, "let variant{tmp}_{i};");
505                    results.push(format!("variant{}_{}", tmp, i));
506                }
507
508                let expr_to_match = format!("variant{tmp}.tag");
509
510                uwriteln!(self.src, "switch ({expr_to_match}) {{");
511                for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
512                    uwriteln!(self.src, "case '{}': {{", case.name.as_str());
513                    if case.ty.is_some() {
514                        uwriteln!(self.src, "const e = variant{tmp}.val;");
515                    }
516                    self.src.push_str(&block);
517
518                    for (i, result) in block_results.iter().enumerate() {
519                        uwriteln!(self.src, "variant{tmp}_{i} = {result};");
520                    }
521                    uwriteln!(
522                        self.src,
523                        "break;
524                        }}"
525                    );
526                }
527                let variant_name = name.to_upper_camel_case();
528                uwriteln!(
529                    self.src,
530                    r#"default: {{
531                        throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
532                    }}"#,
533                );
534                uwriteln!(self.src, "}}");
535            }
536
537            Instruction::VariantLift { variant, name, .. } => {
538                let blocks = self
539                    .blocks
540                    .drain(self.blocks.len() - variant.cases.len()..)
541                    .collect::<Vec<_>>();
542
543                let tmp = self.tmp();
544                let op = &operands[0];
545
546                uwriteln!(
547                    self.src,
548                    "let variant{tmp};
549                    switch ({op}) {{"
550                );
551
552                for (i, (case, (block, block_results))) in
553                    variant.cases.iter().zip(blocks).enumerate()
554                {
555                    let tag = case.name.as_str();
556                    uwriteln!(
557                        self.src,
558                        "case {i}: {{
559                            {block}\
560                            variant{tmp} = {{
561                                tag: '{tag}',"
562                    );
563                    if case.ty.is_some() {
564                        assert!(block_results.len() == 1);
565                        uwriteln!(self.src, "   val: {}", block_results[0]);
566                    } else {
567                        assert!(block_results.is_empty());
568                    }
569                    uwriteln!(
570                        self.src,
571                        "   }};
572                        break;
573                        }}"
574                    );
575                }
576                let variant_name = name.to_upper_camel_case();
577                if !self.valid_lifting_optimization {
578                    uwriteln!(
579                        self.src,
580                        "default: {{
581                            throw new TypeError('invalid variant discriminant for {variant_name}');
582                        }}",
583                    );
584                }
585                uwriteln!(self.src, "}}");
586                results.push(format!("variant{}", tmp));
587            }
588
589            Instruction::OptionLower {
590                payload,
591                results: result_types,
592                ..
593            } => {
594                let (mut some, some_results) = self.blocks.pop().unwrap();
595                let (mut none, none_results) = self.blocks.pop().unwrap();
596
597                let tmp = self.tmp();
598                let op = &operands[0];
599                uwriteln!(self.src, "var variant{tmp} = {op};");
600
601                for i in 0..result_types.len() {
602                    uwriteln!(self.src, "let variant{tmp}_{i};");
603                    results.push(format!("variant{tmp}_{i}"));
604
605                    let some_result = &some_results[i];
606                    let none_result = &none_results[i];
607                    uwriteln!(some, "variant{tmp}_{i} = {some_result};");
608                    uwriteln!(none, "variant{tmp}_{i} = {none_result};");
609                }
610
611                if maybe_null(resolve, payload) {
612                    uwriteln!(
613                        self.src,
614                        "switch (variant{tmp}.tag) {{
615                            case 'none': {{
616                                {none}\
617                                break;
618                            }}
619                            case 'some': {{
620                                const e = variant{tmp}.val;
621                                {some}\
622                                break;
623                            }}
624                            default: {{
625                                throw new TypeError('invalid variant specified for option');
626                            }}
627                        }}",
628                    );
629                } else {
630                    uwriteln!(
631                        self.src,
632                        "if (variant{tmp} === null || variant{tmp} === undefined) {{
633                            {none}\
634                        }} else {{
635                            const e = variant{tmp};
636                            {some}\
637                        }}"
638                    );
639                }
640            }
641
642            Instruction::OptionLift { payload, .. } => {
643                let (some, some_results) = self.blocks.pop().unwrap();
644                let (none, none_results) = self.blocks.pop().unwrap();
645                assert!(none_results.is_empty());
646                assert!(some_results.len() == 1);
647                let some_result = &some_results[0];
648
649                let tmp = self.tmp();
650                let op = &operands[0];
651
652                let (v_none, v_some) = if maybe_null(resolve, payload) {
653                    (
654                        "{ tag: 'none' }",
655                        format!(
656                            "{{
657                                tag: 'some',
658                                val: {some_result}
659                            }}"
660                        ),
661                    )
662                } else {
663                    ("undefined", some_result.into())
664                };
665
666                if !self.valid_lifting_optimization {
667                    uwriteln!(
668                        self.src,
669                        "let variant{tmp};
670                        switch ({op}) {{
671                            case 0: {{
672                                {none}\
673                                variant{tmp} = {v_none};
674                                break;
675                            }}
676                            case 1: {{
677                                {some}\
678                                variant{tmp} = {v_some};
679                                break;
680                            }}
681                            default: {{
682                                throw new TypeError('invalid variant discriminant for option');
683                            }}
684                        }}",
685                    );
686                } else {
687                    uwriteln!(
688                        self.src,
689                        "let variant{tmp};
690                        if ({op}) {{
691                            {some}\
692                            variant{tmp} = {v_some};
693                        }} else {{
694                            {none}\
695                            variant{tmp} = {v_none};
696                        }}"
697                    );
698                }
699
700                results.push(format!("variant{tmp}"));
701            }
702
703            Instruction::ResultLower {
704                results: result_types,
705                ..
706            } => {
707                let (mut err, err_results) = self.blocks.pop().unwrap();
708                let (mut ok, ok_results) = self.blocks.pop().unwrap();
709
710                let tmp = self.tmp();
711                let op = &operands[0];
712                uwriteln!(self.src, "var variant{tmp} = {op};");
713
714                for i in 0..result_types.len() {
715                    uwriteln!(self.src, "let variant{tmp}_{i};");
716                    results.push(format!("variant{tmp}_{i}"));
717
718                    let ok_result = &ok_results[i];
719                    let err_result = &err_results[i];
720                    uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
721                    uwriteln!(err, "variant{tmp}_{i} = {err_result};");
722                }
723
724                uwriteln!(
725                    self.src,
726                    "switch (variant{tmp}.tag) {{
727                        case 'ok': {{
728                            const e = variant{tmp}.val;
729                            {ok}\
730                            break;
731                        }}
732                        case 'err': {{
733                            const e = variant{tmp}.val;
734                            {err}\
735                            break;
736                        }}
737                        default: {{
738                            throw new TypeError('invalid variant specified for result');
739                        }}
740                    }}",
741                );
742            }
743
744            Instruction::ResultLift { result, .. } => {
745                let (err, err_results) = self.blocks.pop().unwrap();
746                let (ok, ok_results) = self.blocks.pop().unwrap();
747                let ok_result = if result.ok.is_some() {
748                    assert_eq!(ok_results.len(), 1);
749                    ok_results[0].to_string()
750                } else {
751                    assert_eq!(ok_results.len(), 0);
752                    String::from("undefined")
753                };
754                let err_result = if result.err.is_some() {
755                    assert_eq!(err_results.len(), 1);
756                    err_results[0].to_string()
757                } else {
758                    assert_eq!(err_results.len(), 0);
759                    String::from("undefined")
760                };
761                let tmp = self.tmp();
762                let op0 = &operands[0];
763
764                if !self.valid_lifting_optimization {
765                    uwriteln!(
766                        self.src,
767                        "let variant{tmp};
768                        switch ({op0}) {{
769                            case 0: {{
770                                {ok}\
771                                variant{tmp} = {{
772                                    tag: 'ok',
773                                    val: {ok_result}
774                                }};
775                                break;
776                            }}
777                            case 1: {{
778                                {err}\
779                                variant{tmp} = {{
780                                    tag: 'err',
781                                    val: {err_result}
782                                }};
783                                break;
784                            }}
785                            default: {{
786                                throw new TypeError('invalid variant discriminant for expected');
787                            }}
788                        }}",
789                    );
790                } else {
791                    uwriteln!(
792                        self.src,
793                        "let variant{tmp};
794                        if ({op0}) {{
795                            {err}\
796                            variant{tmp} = {{
797                                tag: 'err',
798                                val: {err_result}
799                            }};
800                        }} else {{
801                            {ok}\
802                            variant{tmp} = {{
803                                tag: 'ok',
804                                val: {ok_result}
805                            }};
806                        }}"
807                    );
808                }
809                results.push(format!("variant{tmp}"));
810            }
811
812            // Lowers an enum in accordance with https://webidl.spec.whatwg.org/#es-enumeration.
813            Instruction::EnumLower { name, enum_, .. } => {
814                let tmp = self.tmp();
815
816                let op = &operands[0];
817                uwriteln!(self.src, "var val{tmp} = {op};");
818
819                // Declare a variable to hold the result.
820                uwriteln!(
821                    self.src,
822                    "let enum{tmp};
823                    switch (val{tmp}) {{"
824                );
825                for (i, case) in enum_.cases.iter().enumerate() {
826                    uwriteln!(
827                        self.src,
828                        "case '{case}': {{
829                            enum{tmp} = {i};
830                            break;
831                        }}",
832                        case = case.name
833                    );
834                }
835                uwriteln!(self.src, "default: {{");
836                if !self.valid_lifting_optimization {
837                    uwriteln!(
838                        self.src,
839                        "if (({op}) instanceof Error) {{
840                        console.error({op});
841                    }}"
842                    );
843                }
844                uwriteln!(
845                    self.src,
846                    "
847                            throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
848                        }}
849                    }}",
850                );
851
852                results.push(format!("enum{tmp}"));
853            }
854
855            Instruction::EnumLift { name, enum_, .. } => {
856                let tmp = self.tmp();
857
858                uwriteln!(
859                    self.src,
860                    "let enum{tmp};
861                    switch ({}) {{",
862                    operands[0]
863                );
864                for (i, case) in enum_.cases.iter().enumerate() {
865                    uwriteln!(
866                        self.src,
867                        "case {i}: {{
868                            enum{tmp} = '{case}';
869                            break;
870                        }}",
871                        case = case.name
872                    );
873                }
874                if !self.valid_lifting_optimization {
875                    let name = name.to_upper_camel_case();
876                    uwriteln!(
877                        self.src,
878                        "default: {{
879                            throw new TypeError('invalid discriminant specified for {name}');
880                        }}",
881                    );
882                }
883                uwriteln!(self.src, "}}");
884
885                results.push(format!("enum{tmp}"));
886            }
887
888            Instruction::ListCanonLower { element, .. } => {
889                let tmp = self.tmp();
890                let memory = self.memory.as_ref().unwrap();
891                let realloc = self.realloc.unwrap();
892
893                let size = self.sizes.size(element).size_wasm32();
894                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
895                uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
896                if matches!(element, Type::U8) {
897                    uwriteln!(self.src, "var len{tmp} = val{tmp}.byteLength;");
898                } else {
899                    uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
900                }
901
902                uwriteln!(
903                    self.src,
904                    "var ptr{tmp} = {realloc}(0, 0, {align}, len{tmp} * {size});",
905                );
906                // TODO: this is the wrong endianness
907                if matches!(element, Type::U8) {
908                    uwriteln!(
909                        self.src,
910                        "var src{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, len{tmp} * {size});",
911                    );
912                } else {
913                    uwriteln!(
914                        self.src,
915                        "var src{tmp} = new Uint8Array(val{tmp}.buffer, val{tmp}.byteOffset, len{tmp} * {size});",
916                    );
917                }
918                uwriteln!(
919                    self.src,
920                    "(new Uint8Array({memory}.buffer, ptr{tmp}, len{tmp} * {size})).set(src{tmp});",
921                );
922                results.push(format!("ptr{}", tmp));
923                results.push(format!("len{}", tmp));
924            }
925            Instruction::ListCanonLift { element, .. } => {
926                let tmp = self.tmp();
927                let memory = self.memory.as_ref().unwrap();
928                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
929                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
930                // TODO: this is the wrong endianness
931                let array_ty = array_ty(resolve, element).unwrap();
932                uwriteln!(
933                    self.src,
934                    "var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));",
935                    self.sizes.size(element).size_wasm32(),
936                );
937                results.push(format!("result{tmp}"));
938            }
939            Instruction::StringLower { .. } => {
940                // Only Utf8 and Utf16 supported for now
941                assert!(matches!(
942                    self.encoding,
943                    StringEncoding::UTF8 | StringEncoding::UTF16
944                ));
945                let intrinsic = if self.encoding == StringEncoding::UTF16 {
946                    Intrinsic::Utf16Encode
947                } else {
948                    Intrinsic::Utf8Encode
949                };
950                let encode = self.intrinsic(intrinsic);
951                let tmp = self.tmp();
952                let memory = self.memory.as_ref().unwrap();
953                let str = String::from("cabi_realloc");
954                let realloc = self.realloc.unwrap_or(&str);
955                uwriteln!(
956                    self.src,
957                    "var ptr{tmp} = {encode}({}, {realloc}, {memory});",
958                    operands[0],
959                );
960                if self.encoding == StringEncoding::UTF8 {
961                    let encoded_len = self.intrinsic(Intrinsic::Utf8EncodedLen);
962                    uwriteln!(self.src, "var len{tmp} = {encoded_len};");
963                } else {
964                    uwriteln!(self.src, "var len{tmp} = {}.length;", operands[0]);
965                }
966                results.push(format!("ptr{}", tmp));
967                results.push(format!("len{}", tmp));
968            }
969            Instruction::StringLift => {
970                // Only Utf8 and Utf16 supported for now
971                assert!(matches!(
972                    self.encoding,
973                    StringEncoding::UTF8 | StringEncoding::UTF16
974                ));
975                let intrinsic = if self.encoding == StringEncoding::UTF16 {
976                    Intrinsic::Utf16Decoder
977                } else {
978                    Intrinsic::Utf8Decoder
979                };
980                let decoder = self.intrinsic(intrinsic);
981                let tmp = self.tmp();
982                let memory = self.memory.as_ref().unwrap();
983                uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
984                uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
985                uwriteln!(
986                    self.src,
987                    "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
988                    if self.encoding == StringEncoding::UTF16 { "16" } else { "8" }
989                );
990                results.push(format!("result{tmp}"));
991            }
992
993            Instruction::ListLower { element, .. } => {
994                let (body, body_results) = self.blocks.pop().unwrap();
995                assert!(body_results.is_empty());
996                let tmp = self.tmp();
997                let vec = format!("vec{}", tmp);
998                let result = format!("result{}", tmp);
999                let len = format!("len{}", tmp);
1000                let size = self.sizes.size(element).size_wasm32();
1001                let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1002
1003                // first store our vec-to-lower in a temporary since we'll
1004                // reference it multiple times.
1005                uwriteln!(self.src, "var {vec} = {};", operands[0]);
1006                uwriteln!(self.src, "var {len} = {vec}.length;");
1007
1008                // ... then realloc space for the result in the guest module
1009                let realloc = self.realloc.as_ref().unwrap();
1010                uwriteln!(
1011                    self.src,
1012                    "var {result} = {realloc}(0, 0, {align}, {len} * {size});"
1013                );
1014
1015                // ... then consume the vector and use the block to lower the
1016                // result.
1017                uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1018                uwriteln!(self.src, "const e = {vec}[i];");
1019                uwrite!(self.src, "const base = {result} + i * {size};");
1020                self.src.push_str(&body);
1021                uwrite!(self.src, "}}\n");
1022
1023                results.push(result);
1024                results.push(len);
1025            }
1026
1027            Instruction::ListLift { element, .. } => {
1028                let (body, body_results) = self.blocks.pop().unwrap();
1029                let tmp = self.tmp();
1030                let size = self.sizes.size(element).size_wasm32();
1031                let len = format!("len{tmp}");
1032                uwriteln!(self.src, "var {len} = {};", operands[1]);
1033                let base = format!("base{tmp}");
1034                uwriteln!(self.src, "var {base} = {};", operands[0]);
1035                let result = format!("result{tmp}");
1036                uwriteln!(self.src, "var {result} = [];");
1037                results.push(result.clone());
1038
1039                uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1040                uwriteln!(self.src, "const base = {base} + i * {size};");
1041                self.src.push_str(&body);
1042                assert_eq!(body_results.len(), 1);
1043                uwriteln!(self.src, "{result}.push({});", body_results[0]);
1044                uwrite!(self.src, "}}\n");
1045            }
1046
1047            Instruction::IterElem { .. } => results.push("e".to_string()),
1048
1049            Instruction::IterBasePointer => results.push("base".to_string()),
1050
1051            Instruction::CallWasm { sig, .. } => {
1052                let sig_results_length = sig.results.len();
1053                self.bind_results(sig_results_length, results);
1054                let maybe_async_await = if self.is_async { "await " } else { "" };
1055                uwriteln!(
1056                    self.src,
1057                    "{maybe_async_await}{}({});",
1058                    self.callee,
1059                    operands.join(", ")
1060                );
1061
1062                if let Some(prefix) = self.tracing_prefix {
1063                    let to_result_string = self.intrinsic(Intrinsic::ToResultString);
1064                    uwriteln!(
1065                        self.src,
1066                        "console.error(`{prefix} return {}`);",
1067                        if sig_results_length > 0 || !results.is_empty() {
1068                            format!("result=${{{to_result_string}(ret)}}")
1069                        } else {
1070                            "".to_string()
1071                        }
1072                    );
1073                }
1074            }
1075
1076            Instruction::CallInterface { func, .. } => {
1077                let results_length = func.results.len();
1078                let maybe_async_await = if self.is_async { "await " } else { "" };
1079                let call = if self.callee_resource_dynamic {
1080                    format!(
1081                        "{maybe_async_await}{}.{}({})",
1082                        operands[0],
1083                        self.callee,
1084                        operands[1..].join(", ")
1085                    )
1086                } else {
1087                    format!(
1088                        "{maybe_async_await}{}({})",
1089                        self.callee,
1090                        operands.join(", ")
1091                    )
1092                };
1093                if self.err == ErrHandling::ResultCatchHandler {
1094                    // result<_, string> allows JS error coercion only, while
1095                    // any other result type will trap for arbitrary JS errors.
1096                    let err_payload = if let (_, Some(Type::Id(err_ty))) =
1097                        func.results.throws(self.resolve).unwrap()
1098                    {
1099                        match &self.resolve.types[*err_ty].kind {
1100                            TypeDefKind::Type(Type::String) => {
1101                                self.intrinsic(Intrinsic::GetErrorPayloadString)
1102                            }
1103                            _ => self.intrinsic(Intrinsic::GetErrorPayload),
1104                        }
1105                    } else {
1106                        self.intrinsic(Intrinsic::GetErrorPayload)
1107                    };
1108                    uwriteln!(
1109                        self.src,
1110                        "let ret;
1111                        try {{
1112                            ret = {{ tag: 'ok', val: {call} }};
1113                        }} catch (e) {{
1114                            ret = {{ tag: 'err', val: {err_payload}(e) }};
1115                        }}",
1116                    );
1117                    results.push("ret".to_string());
1118                } else {
1119                    self.bind_results(results_length, results);
1120                    uwriteln!(self.src, "{call};");
1121                }
1122
1123                if let Some(prefix) = self.tracing_prefix {
1124                    let to_result_string = self.intrinsic(Intrinsic::ToResultString);
1125                    uwriteln!(
1126                        self.src,
1127                        "console.error(`{prefix} return {}`);",
1128                        if results_length > 0 || !results.is_empty() {
1129                            format!("result=${{{to_result_string}(ret)}}")
1130                        } else {
1131                            "".to_string()
1132                        }
1133                    );
1134                }
1135
1136                // After a high level call, we need to deactivate the component resource borrows.
1137                if self.cur_resource_borrows {
1138                    let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1139                    let cur_resource_borrows = self.intrinsic(Intrinsic::CurResourceBorrows);
1140                    let host = matches!(
1141                        self.resource_map.iter().nth(0).unwrap().1.data,
1142                        ResourceData::Host { .. }
1143                    );
1144                    if host {
1145                        uwriteln!(
1146                            self.src,
1147                            "for (const rsc of {cur_resource_borrows}) {{
1148                                rsc[{symbol_resource_handle}] = undefined;
1149                            }}
1150                            {cur_resource_borrows} = [];"
1151                        );
1152                    } else {
1153                        uwriteln!(
1154                            self.src,
1155                            "for (const {{ rsc, drop }} of {cur_resource_borrows}) {{
1156                                if (rsc[{symbol_resource_handle}]) {{
1157                                    drop(rsc[{symbol_resource_handle}]);
1158                                    rsc[{symbol_resource_handle}] = undefined;
1159                                }}
1160                            }}
1161                            {cur_resource_borrows} = [];"
1162                        );
1163                    }
1164                    self.cur_resource_borrows = false;
1165                }
1166            }
1167
1168            Instruction::Return { amt, .. } => {
1169                if *amt == 0 {
1170                    if let Some(f) = &self.post_return {
1171                        uwriteln!(self.src, "{f}();");
1172                    }
1173                } else if *amt == 1 && self.err == ErrHandling::ThrowResultErr {
1174                    let component_err = self.intrinsic(Intrinsic::ComponentError);
1175                    let op = &operands[0];
1176                    uwriteln!(self.src, "const retVal = {op};");
1177                    if let Some(f) = &self.post_return {
1178                        uwriteln!(self.src, "{f}(ret);");
1179                    }
1180                    uwriteln!(
1181                        self.src,
1182                        "if (typeof retVal === 'object' && retVal.tag === 'err') {{
1183                            throw new {component_err}(retVal.val);
1184                        }}
1185                        return retVal.val;"
1186                    );
1187                } else {
1188                    let ret_assign = if self.post_return.is_some() {
1189                        "const retVal ="
1190                    } else {
1191                        "return"
1192                    };
1193                    if *amt == 1 {
1194                        uwriteln!(self.src, "{ret_assign} {};", operands[0]);
1195                    } else {
1196                        uwriteln!(self.src, "{ret_assign} [{}];", operands.join(", "));
1197                    }
1198                    if let Some(f) = &self.post_return {
1199                        uwriteln!(
1200                            self.src,
1201                            "{f}(ret);
1202                            return retVal;"
1203                        );
1204                    }
1205                }
1206            }
1207
1208            Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1209            Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1210            Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1211            Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1212            Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1213            Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
1214            Instruction::I32Load16U { offset } => {
1215                self.load("getUint16", *offset, operands, results)
1216            }
1217            Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
1218            Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
1219            Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
1220            Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
1221            Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
1222            Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
1223            Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
1224
1225            Instruction::LengthStore { offset } => self.store("setInt32", *offset, operands),
1226            Instruction::LengthLoad { offset } => self.load("getInt32", *offset, operands, results),
1227            Instruction::PointerStore { offset } => self.store("setInt32", *offset, operands),
1228            Instruction::PointerLoad { offset } => {
1229                self.load("getInt32", *offset, operands, results)
1230            }
1231
1232            Instruction::Malloc { size, align, .. } => {
1233                let tmp = self.tmp();
1234                let realloc = self.realloc.as_ref().unwrap();
1235                let ptr = format!("ptr{tmp}");
1236                uwriteln!(self.src, "var {ptr} = {realloc}(0, 0, {align}, {size});",);
1237                results.push(ptr);
1238            }
1239
1240            Instruction::HandleLift { handle, .. } => {
1241                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1242                let resource_ty = &crate::dealias(self.resolve, *ty);
1243                let ResourceTable { imported, data } = &self.resource_map[resource_ty];
1244
1245                let is_own = matches!(handle, Handle::Own(_));
1246                let rsc = format!("rsc{}", self.tmp());
1247                let handle = format!("handle{}", self.tmp());
1248                uwriteln!(self.src, "var {handle} = {};", &operands[0]);
1249
1250                match data {
1251                    ResourceData::Host {
1252                        tid,
1253                        rid,
1254                        local_name,
1255                        dtor_name,
1256                    } => {
1257                        let tid = tid.as_u32();
1258                        let rid = rid.as_u32();
1259                        let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1260                        let rsc_table_remove = self.intrinsic(Intrinsic::ResourceTableRemove);
1261                        let rsc_flag = self.intrinsic(Intrinsic::ResourceTableFlag);
1262                        if !imported {
1263                            let symbol_resource_handle =
1264                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1265                            uwriteln!(self.src, "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);");
1266                            if is_own {
1267                                // Sending an own handle out to JS as a return value - set up finalizer and disposal.
1268                                let empty_func = self.intrinsic(Intrinsic::EmptyFunc);
1269                                uwriteln!(self.src,
1270                                    "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1271                                    finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
1272                                if let Some(dtor) = dtor_name {
1273                                    // The Symbol.dispose function gets disabled on drop, so we can rely on the own handle remaining valid.
1274                                    uwriteln!(
1275                                        self.src,
1276                                        "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
1277                                            finalizationRegistry{tid}.unregister({rsc});
1278                                            {rsc_table_remove}(handleTable{tid}, {handle});
1279                                            {rsc}[{symbol_dispose}] = {empty_func};
1280                                            {rsc}[{symbol_resource_handle}] = undefined;
1281                                            {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
1282                                        }}}});"
1283                                    );
1284                                } else {
1285                                    // Set up Symbol.dispose for borrows to allow its call, even though it does nothing.
1286                                    uwriteln!(
1287                                        self.src,
1288                                        "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});",
1289                                    );
1290                                }
1291                            } else {
1292                                // Borrow handles of local resources have rep handles, which we carry through here.
1293                                uwriteln!(self.src, "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});");
1294                            }
1295                        } else {
1296                            let rep = format!("rep{}", self.tmp());
1297                            // Imported handles either lift as instance capture from a previous lowering,
1298                            // or we create a new JS class to represent it.
1299                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
1300                            let symbol_resource_handle =
1301                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1302                            uwriteln!(self.src,
1303                                "var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
1304                                var {rsc} = captureTable{rid}.get({rep});
1305                                if (!{rsc}) {{
1306                                    {rsc} = Object.create({local_name}.prototype);
1307                                    Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1308                                    Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
1309                                }}"
1310                            );
1311                            if is_own {
1312                                // An own lifting is a transfer to JS, so existing own handle is implicitly dropped.
1313                                uwriteln!(
1314                                    self.src,
1315                                    "else {{
1316                                        captureTable{rid}.delete({rep});
1317                                    }}
1318                                    {rsc_table_remove}(handleTable{tid}, {handle});"
1319                                );
1320                            }
1321                        }
1322
1323                        // Borrow handles are tracked to release after the call by CallInterface.
1324                        if !is_own {
1325                            let cur_resource_borrows =
1326                                self.intrinsic(Intrinsic::CurResourceBorrows);
1327                            uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
1328                            self.cur_resource_borrows = true;
1329                        }
1330                    }
1331
1332                    ResourceData::Guest {
1333                        resource_name,
1334                        prefix,
1335                    } => {
1336                        let symbol_resource_handle =
1337                            self.intrinsic(Intrinsic::SymbolResourceHandle);
1338                        let prefix = prefix.as_deref().unwrap_or("");
1339                        let lower_camel = resource_name.to_lower_camel_case();
1340
1341                        if !imported {
1342                            if is_own {
1343                                uwriteln!(self.src, "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;");
1344                                uwrite!(
1345                                    self.src,
1346                                    "repTable.delete({handle});
1347                                     delete {rsc}[{symbol_resource_handle}];
1348                                     finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
1349                                    "
1350                                );
1351                            } else {
1352                                uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
1353                            }
1354                        } else {
1355                            let upper_camel = resource_name.to_upper_camel_case();
1356
1357                            uwrite!(
1358                                self.src,
1359                                "var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
1360                                 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1361                                "
1362                            );
1363
1364                            uwriteln!(
1365                                self.src,
1366                                "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
1367                            );
1368
1369                            if !is_own {
1370                                let cur_resource_borrows =
1371                                    self.intrinsic(Intrinsic::CurResourceBorrows);
1372                                uwriteln!(self.src, "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});");
1373                                self.cur_resource_borrows = true;
1374                            }
1375                        }
1376                    }
1377                }
1378                results.push(rsc);
1379            }
1380
1381            Instruction::HandleLower { handle, name, .. } => {
1382                let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1383                let is_own = matches!(handle, Handle::Own(_));
1384                let ResourceTable { imported, data } =
1385                    &self.resource_map[&crate::dealias(self.resolve, *ty)];
1386
1387                let class_name = name.to_upper_camel_case();
1388                let handle = format!("handle{}", self.tmp());
1389                let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1390                let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1391                let op = &operands[0];
1392
1393                match data {
1394                    ResourceData::Host {
1395                        tid,
1396                        rid,
1397                        local_name,
1398                        ..
1399                    } => {
1400                        let tid = tid.as_u32();
1401                        let rid = rid.as_u32();
1402                        if !imported {
1403                            if is_own {
1404                                let empty_func = self.intrinsic(Intrinsic::EmptyFunc);
1405                                uwriteln!(
1406                                    self.src,
1407                                    "var {handle} = {op}[{symbol_resource_handle}];
1408                                    if (!{handle}) {{
1409                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1410                                    }}
1411                                    finalizationRegistry{tid}.unregister({op});
1412                                    {op}[{symbol_dispose}] = {empty_func};
1413                                    {op}[{symbol_resource_handle}] = undefined;",
1414                                );
1415                            } else {
1416                                // When expecting a borrow, the JS resource provided will always be an own
1417                                // handle. This is because it is not possible for borrow handles to be passed
1418                                // back reentrantly.
1419                                // We then set the handle to the rep per the local borrow rule.
1420                                let rsc_flag = self.intrinsic(Intrinsic::ResourceTableFlag);
1421                                let own_handle = format!("handle{}", self.tmp());
1422                                uwriteln!(self.src,
1423                                    "var {own_handle} = {op}[{symbol_resource_handle}];
1424                                    if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
1425                                        throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1426                                    }}
1427                                    var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};",
1428                                );
1429                            }
1430                        } else {
1431                            // Imported resources may already have a handle if they were constructed
1432                            // by a component and then passed out.
1433                            uwriteln!(
1434                                self.src,
1435                                "if (!({op} instanceof {local_name})) {{
1436                                     throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
1437                                 }}
1438                                 var {handle} = {op}[{symbol_resource_handle}];",
1439                            );
1440                            // Otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
1441                            // to get the resource rep.
1442                            // Fall back to assign a new rep in the capture table, when the imported
1443                            // resource was constructed externally.
1444                            let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
1445                            let rsc_table_create = if is_own {
1446                                self.intrinsic(Intrinsic::ResourceTableCreateOwn)
1447                            } else {
1448                                self.intrinsic(Intrinsic::ScopeId);
1449                                self.intrinsic(Intrinsic::ResourceTableCreateBorrow)
1450                            };
1451                            uwriteln!(
1452                                self.src,
1453                                "if (!{handle}) {{
1454                                    const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
1455                                    captureTable{rid}.set(rep, {op});
1456                                    {handle} = {rsc_table_create}(handleTable{tid}, rep);
1457                                }}"
1458                            );
1459                        }
1460                    }
1461
1462                    ResourceData::Guest {
1463                        resource_name,
1464                        prefix,
1465                    } => {
1466                        let upper_camel = resource_name.to_upper_camel_case();
1467                        let lower_camel = resource_name.to_lower_camel_case();
1468                        let prefix = prefix.as_deref().unwrap_or("");
1469
1470                        if !imported {
1471                            let local_rep = format!("localRep{}", self.tmp());
1472                            uwriteln!(
1473                                self.src,
1474                                "if (!({op} instanceof {upper_camel})) {{
1475                                    throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
1476                                }}
1477                                let {handle} = {op}[{symbol_resource_handle}];",
1478                            );
1479
1480                            if is_own {
1481                                uwriteln!(
1482                                    self.src,
1483                                    "if ({handle} === undefined) {{
1484                                        var {local_rep} = repCnt++;
1485                                        repTable.set({local_rep}, {{ rep: {op}, own: true }});
1486                                        {handle} = $resource_{prefix}new${lower_camel}({local_rep});
1487                                        {op}[{symbol_resource_handle}] = {handle};
1488                                        finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
1489                                    }}
1490                                    "
1491                                );
1492                            } else {
1493                                uwriteln!(
1494                                    self.src,
1495                                    "if ({handle} === undefined) {{
1496                                        var {local_rep} = repCnt++;
1497                                        repTable.set({local_rep}, {{ rep: {op}, own: false }});
1498                                        {op}[{symbol_resource_handle}] = {local_rep};
1499                                    }}
1500                                    "
1501                                );
1502                            }
1503                        } else {
1504                            let symbol_resource_handle =
1505                                self.intrinsic(Intrinsic::SymbolResourceHandle);
1506                            uwrite!(
1507                                self.src,
1508                                "var {handle} = {op}[{symbol_resource_handle}];
1509                                 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
1510                                "
1511                            );
1512                        }
1513                    }
1514                }
1515                results.push(handle);
1516            }
1517
1518            // For most non-Promise objects, flushing or evaluating the object is a no-op,
1519            // so until async is implemented, we can only pass through objects without modification
1520            //
1521            // During async implementation this flush should check for Promises and await them
1522            // as necessary
1523            Instruction::Flush { amt } => {
1524                for n in 0..*amt {
1525                    results.push(operands[n].clone());
1526                }
1527            }
1528
1529            // TODO: implement async
1530            Instruction::FutureLower { .. }
1531            | Instruction::FutureLift { .. }
1532            | Instruction::StreamLower { .. }
1533            | Instruction::StreamLift { .. }
1534            | Instruction::AsyncMalloc { .. }
1535            | Instruction::AsyncCallWasm { .. }
1536            | Instruction::AsyncPostCallInterface { .. }
1537            | Instruction::AsyncCallReturn { .. }
1538            | Instruction::ErrorContextLift { .. }
1539            | Instruction::ErrorContextLower { .. } => {
1540                uwrite!(self.src, "throw new Error('async is not yet implemented');");
1541            }
1542
1543            Instruction::GuestDeallocate { .. }
1544            | Instruction::GuestDeallocateString
1545            | Instruction::GuestDeallocateList { .. }
1546            | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
1547        }
1548    }
1549}
1550
1551/// Tests whether `ty` can be represented with `null`, and if it can then
1552/// the "other type" is returned. If `Some` is returned that means that `ty`
1553/// is `null | <return>`. If `None` is returned that means that `null` can't
1554/// be used to represent `ty`.
1555pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
1556    let id = match ty {
1557        Type::Id(id) => *id,
1558        _ => return None,
1559    };
1560    match &resolve.types[id].kind {
1561        // If `ty` points to an `option<T>`, then `ty` can be represented
1562        // with `null` if `t` itself can't be represented with null. For
1563        // example `option<option<u32>>` can't be represented with `null`
1564        // since that's ambiguous if it's `none` or `some(none)`.
1565        //
1566        // Note, oddly enough, that `option<option<option<u32>>>` can be
1567        // represented as `null` since:
1568        //
1569        // * `null` => `none`
1570        // * `{ tag: "none" }` => `some(none)`
1571        // * `{ tag: "some", val: null }` => `some(some(none))`
1572        // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
1573        //
1574        // It's doubtful anyone would actually rely on that though due to
1575        // how confusing it is.
1576        TypeDefKind::Option(t) => {
1577            if !maybe_null(resolve, t) {
1578                Some(t)
1579            } else {
1580                None
1581            }
1582        }
1583        TypeDefKind::Type(t) => as_nullable(resolve, t),
1584        _ => None,
1585    }
1586}
1587
1588pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
1589    as_nullable(resolve, ty).is_some()
1590}
1591
1592pub fn array_ty(resolve: &Resolve, ty: &Type) -> Option<&'static str> {
1593    match ty {
1594        Type::Bool => None,
1595        Type::U8 => Some("Uint8Array"),
1596        Type::S8 => Some("Int8Array"),
1597        Type::U16 => Some("Uint16Array"),
1598        Type::S16 => Some("Int16Array"),
1599        Type::U32 => Some("Uint32Array"),
1600        Type::S32 => Some("Int32Array"),
1601        Type::U64 => Some("BigUint64Array"),
1602        Type::S64 => Some("BigInt64Array"),
1603        Type::F32 => Some("Float32Array"),
1604        Type::F64 => Some("Float64Array"),
1605        Type::Char => None,
1606        Type::String => None,
1607        Type::Id(id) => match &resolve.types[*id].kind {
1608            TypeDefKind::Type(t) => array_ty(resolve, t),
1609            _ => None,
1610        },
1611    }
1612}