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