Skip to main content

wasm_bridge_jco/
function_bindgen.rs

1use crate::intrinsics::Intrinsic;
2use crate::source;
3use crate::{uwrite, uwriteln};
4use heck::*;
5use std::collections::BTreeSet;
6use std::fmt::Write;
7use std::mem;
8use wasmtime_environ::component::StringEncoding;
9use wit_parser::abi::{Bindgen, Bitcast, Instruction, WasmType};
10use wit_parser::*;
11
12#[derive(PartialEq)]
13pub enum ErrHandling {
14    None,
15    ThrowResultErr,
16    ResultCatchHandler,
17}
18
19pub struct FunctionBindgen<'a> {
20    pub intrinsics: &'a mut BTreeSet<Intrinsic>,
21    pub valid_lifting_optimization: bool,
22    pub sizes: &'a SizeAlign,
23    pub err: ErrHandling,
24    pub tmp: usize,
25    pub src: source::Source,
26    pub block_storage: Vec<source::Source>,
27    pub blocks: Vec<(String, Vec<String>)>,
28    pub params: Vec<String>,
29    pub memory: Option<&'a String>,
30    pub realloc: Option<&'a String>,
31    pub post_return: Option<&'a String>,
32    pub encoding: StringEncoding,
33    pub callee: &'a str,
34}
35
36impl FunctionBindgen<'_> {
37    fn tmp(&mut self) -> usize {
38        let ret = self.tmp;
39        self.tmp += 1;
40        ret
41    }
42    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
43        self.intrinsics.insert(intrinsic);
44        return intrinsic.name().to_string();
45    }
46
47    fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
48    where
49        T: std::fmt::Display,
50    {
51        let clamp = self.intrinsic(Intrinsic::ClampGuest);
52        results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
53    }
54
55    fn load(&mut self, method: &str, offset: i32, operands: &[String], results: &mut Vec<String>) {
56        let view = self.intrinsic(Intrinsic::DataView);
57        let memory = self.memory.as_ref().unwrap();
58        results.push(format!(
59            "{view}({memory}).{method}({} + {offset}, true)",
60            operands[0],
61        ));
62    }
63
64    fn store(&mut self, method: &str, offset: i32, operands: &[String]) {
65        let view = self.intrinsic(Intrinsic::DataView);
66        let memory = self.memory.as_ref().unwrap();
67        uwriteln!(
68            self.src,
69            "{view}({memory}).{method}({} + {offset}, {}, true);",
70            operands[1],
71            operands[0]
72        );
73    }
74
75    fn bind_results(&mut self, amt: usize, results: &mut Vec<String>) {
76        match amt {
77            0 => {}
78            1 => {
79                uwrite!(self.src, "const ret = ");
80                results.push("ret".to_string());
81            }
82            n => {
83                uwrite!(self.src, "const [");
84                for i in 0..n {
85                    if i > 0 {
86                        uwrite!(self.src, ", ");
87                    }
88                    uwrite!(self.src, "ret{}", i);
89                    results.push(format!("ret{}", i));
90                }
91                uwrite!(self.src, "] = ");
92            }
93        }
94    }
95}
96
97impl Bindgen for FunctionBindgen<'_> {
98    type Operand = String;
99
100    fn sizes(&self) -> &SizeAlign {
101        &self.sizes
102    }
103
104    fn push_block(&mut self) {
105        let prev = mem::take(&mut self.src);
106        self.block_storage.push(prev);
107    }
108
109    fn finish_block(&mut self, operands: &mut Vec<String>) {
110        let to_restore = self.block_storage.pop().unwrap();
111        let src = mem::replace(&mut self.src, to_restore);
112        self.blocks.push((src.into(), mem::take(operands)));
113    }
114
115    fn return_pointer(&mut self, _size: usize, _align: usize) -> String {
116        unimplemented!();
117    }
118
119    fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool {
120        array_ty(resolve, ty).is_some()
121    }
122
123    fn emit(
124        &mut self,
125        resolve: &Resolve,
126        inst: &Instruction<'_>,
127        operands: &mut Vec<String>,
128        results: &mut Vec<String>,
129    ) {
130        match inst {
131            Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
132            Instruction::I32Const { val } => results.push(val.to_string()),
133            Instruction::ConstZero { tys } => {
134                for t in tys.iter() {
135                    match t {
136                        WasmType::I64 => results.push("0n".to_string()),
137                        WasmType::I32 | WasmType::F32 | WasmType::F64 => {
138                            results.push("0".to_string());
139                        }
140                    }
141                }
142            }
143
144            // The representation of i32 in JS is a number, so 8/16-bit values
145            // get further clamped to ensure that the upper bits aren't set when
146            // we pass the value, ensuring that only the right number of bits
147            // are transferred.
148            Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
149            Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
150            Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
151            Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
152            // Use `>>>0` to ensure the bits of the number are treated as
153            // unsigned.
154            Instruction::U32FromI32 => {
155                results.push(format!("{} >>> 0", operands[0]));
156            }
157            // All bigints coming from wasm are treated as signed, so convert
158            // it to ensure it's treated as unsigned.
159            Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])),
160            // Nothing to do signed->signed where the representations are the
161            // same.
162            Instruction::S32FromI32 | Instruction::S64FromI64 => {
163                results.push(operands.pop().unwrap())
164            }
165
166            // All values coming from the host and going to wasm need to have
167            // their ranges validated, since the host could give us any value.
168            Instruction::I32FromU8 => {
169                let conv = self.intrinsic(Intrinsic::ToUint8);
170                results.push(format!("{conv}({op})", op = operands[0]))
171            }
172            Instruction::I32FromS8 => {
173                let conv = self.intrinsic(Intrinsic::ToInt8);
174                results.push(format!("{conv}({op})", op = operands[0]))
175            }
176            Instruction::I32FromU16 => {
177                let conv = self.intrinsic(Intrinsic::ToUint16);
178                results.push(format!("{conv}({op})", op = operands[0]))
179            }
180            Instruction::I32FromS16 => {
181                let conv = self.intrinsic(Intrinsic::ToInt16);
182                results.push(format!("{conv}({op})", op = operands[0]))
183            }
184            Instruction::I32FromU32 => {
185                let conv = self.intrinsic(Intrinsic::ToUint32);
186                results.push(format!("{conv}({op})", op = operands[0]))
187            }
188            Instruction::I32FromS32 => {
189                let conv = self.intrinsic(Intrinsic::ToInt32);
190                results.push(format!("{conv}({op})", op = operands[0]))
191            }
192            Instruction::I64FromU64 => {
193                let conv = self.intrinsic(Intrinsic::ToBigUint64);
194                results.push(format!("{conv}({op})", op = operands[0]))
195            }
196            Instruction::I64FromS64 => {
197                let conv = self.intrinsic(Intrinsic::ToBigInt64);
198                results.push(format!("{conv}({op})", op = operands[0]))
199            }
200
201            // The native representation in JS of f32 and f64 is just a number,
202            // so there's nothing to do here. Everything wasm gives us is
203            // representable in JS.
204            Instruction::Float32FromF32 | Instruction::Float64FromF64 => {
205                results.push(operands.pop().unwrap())
206            }
207
208            Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => {
209                // Use a unary `+` to cast to a float.
210                results.push(format!("+{}", operands[0]));
211            }
212
213            // Validate that i32 values coming from wasm are indeed valid code
214            // points.
215            Instruction::CharFromI32 => {
216                let validate = self.intrinsic(Intrinsic::ValidateGuestChar);
217                results.push(format!("{}({})", validate, operands[0]));
218            }
219
220            // Validate that strings are indeed 1 character long and valid
221            // unicode.
222            Instruction::I32FromChar => {
223                let validate = self.intrinsic(Intrinsic::ValidateHostChar);
224                results.push(format!("{}({})", validate, operands[0]));
225            }
226
227            Instruction::Bitcasts { casts } => {
228                for (cast, op) in casts.iter().zip(operands) {
229                    match cast {
230                        Bitcast::I32ToF32 => {
231                            let cvt = self.intrinsic(Intrinsic::I32ToF32);
232                            results.push(format!("{}({})", cvt, op));
233                        }
234                        Bitcast::F32ToI32 => {
235                            let cvt = self.intrinsic(Intrinsic::F32ToI32);
236                            results.push(format!("{}({})", cvt, op));
237                        }
238                        Bitcast::I64ToF64 => {
239                            let cvt = self.intrinsic(Intrinsic::I64ToF64);
240                            results.push(format!("{}({})", cvt, op));
241                        }
242                        Bitcast::F64ToI64 => {
243                            let cvt = self.intrinsic(Intrinsic::F64ToI64);
244                            results.push(format!("{}({})", cvt, op));
245                        }
246                        Bitcast::I32ToI64 => results.push(format!("BigInt({})", op)),
247                        Bitcast::I64ToI32 => results.push(format!("Number({})", op)),
248                        Bitcast::I64ToF32 => {
249                            let cvt = self.intrinsic(Intrinsic::I32ToF32);
250                            results.push(format!("{}(Number({}))", cvt, op));
251                        }
252                        Bitcast::F32ToI64 => {
253                            let cvt = self.intrinsic(Intrinsic::F32ToI32);
254                            results.push(format!("BigInt({}({}))", cvt, op));
255                        }
256                        Bitcast::None => results.push(op.clone()),
257                    }
258                }
259            }
260
261            Instruction::BoolFromI32 => {
262                let tmp = self.tmp();
263                uwrite!(self.src, "const bool{} = {};\n", tmp, operands[0]);
264                if self.valid_lifting_optimization {
265                    results.push(format!("!!bool{tmp}"));
266                } else {
267                    let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
268                    results.push(format!(
269                        "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
270                    ));
271                }
272            }
273            Instruction::I32FromBool => {
274                results.push(format!("{} ? 1 : 0", operands[0]));
275            }
276
277            Instruction::RecordLower { record, .. } => {
278                // use destructuring field access to get each
279                // field individually.
280                let tmp = self.tmp();
281                let mut expr = "const {".to_string();
282                for (i, field) in record.fields.iter().enumerate() {
283                    if i > 0 {
284                        expr.push_str(", ");
285                    }
286                    let name = format!("v{}_{}", tmp, i);
287                    expr.push_str(&field.name.to_lower_camel_case());
288                    expr.push_str(": ");
289                    expr.push_str(&name);
290                    results.push(name);
291                }
292                uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
293            }
294
295            Instruction::RecordLift { record, .. } => {
296                // records are represented as plain objects, so we
297                // make a new object and set all the fields with an object
298                // literal.
299                let mut result = "{\n".to_string();
300                for (field, op) in record.fields.iter().zip(operands) {
301                    result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
302                }
303                result.push_str("}");
304                results.push(result);
305            }
306
307            Instruction::TupleLower { tuple, .. } => {
308                // Tuples are represented as an array, sowe can use
309                // destructuring assignment to lower the tuple into its
310                // components.
311                let tmp = self.tmp();
312                let mut expr = "const [".to_string();
313                for i in 0..tuple.types.len() {
314                    if i > 0 {
315                        expr.push_str(", ");
316                    }
317                    let name = format!("tuple{}_{}", tmp, i);
318                    expr.push_str(&name);
319                    results.push(name);
320                }
321                uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
322            }
323
324            Instruction::TupleLift { .. } => {
325                // Tuples are represented as an array, so we just shove all
326                // the operands into an array.
327                results.push(format!("[{}]", operands.join(", ")));
328            }
329
330            // This lowers flags from a dictionary of booleans in accordance with https://webidl.spec.whatwg.org/#es-dictionary.
331            Instruction::FlagsLower { flags, .. } => {
332                let op0 = &operands[0];
333
334                // Generate the result names.
335                for _ in 0..flags.repr().count() {
336                    let tmp = self.tmp();
337                    let name = format!("flags{tmp}");
338                    // Default to 0 so that in the null/undefined case, everything is false by
339                    // default.
340                    uwrite!(self.src, "let {name} = 0;\n");
341                    results.push(name);
342                }
343
344                uwrite!(
345                    self.src,
346                    "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
347                );
348
349                for (i, chunk) in flags.flags.chunks(32).enumerate() {
350                    let result_name = &results[i];
351
352                    uwrite!(self.src, "{result_name} = ");
353                    for (i, flag) in chunk.iter().enumerate() {
354                        if i != 0 {
355                            uwrite!(self.src, " | ");
356                        }
357
358                        let flag = flag.name.to_lower_camel_case();
359                        uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
360                    }
361                    uwrite!(self.src, ";\n");
362                }
363
364                uwrite!(self.src, "\
365                    }} else if ({op0} !== null && {op0} !== undefined) {{
366                        throw new TypeError('only an object, undefined or null can be converted to flags');
367                    }}
368                ");
369
370                // We don't need to do anything else for the null/undefined
371                // case, since that's interpreted as everything false, and we
372                // already defaulted everyting to 0.
373            }
374
375            Instruction::FlagsLift { flags, .. } => {
376                let tmp = self.tmp();
377                results.push(format!("flags{tmp}"));
378
379                if let Some(op) = operands.last() {
380                    // We only need an extraneous bits check if the number of flags isn't a multiple
381                    // of 32, because if it is then all the bits are used and there are no
382                    // extraneous bits.
383                    if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
384                        let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
385                        uwriteln!(
386                            self.src,
387                            "if (({op} & {mask}) !== 0) {{
388                                throw new TypeError('flags have extraneous bits set');
389                            }}"
390                        );
391                    }
392                }
393
394                uwriteln!(self.src, "const flags{tmp} = {{");
395
396                for (i, flag) in flags.flags.iter().enumerate() {
397                    let flag = flag.name.to_lower_camel_case();
398                    let op = &operands[i / 32];
399                    let mask: u32 = 1 << (i % 32);
400                    uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
401                }
402
403                uwriteln!(self.src, "}};");
404            }
405
406            Instruction::VariantPayloadName => results.push("e".to_string()),
407
408            Instruction::VariantLower {
409                variant,
410                results: result_types,
411                name,
412                ..
413            } => {
414                let blocks = self
415                    .blocks
416                    .drain(self.blocks.len() - variant.cases.len()..)
417                    .collect::<Vec<_>>();
418                let tmp = self.tmp();
419                let operand = &operands[0];
420                uwriteln!(self.src, "const variant{tmp} = {operand};");
421
422                for i in 0..result_types.len() {
423                    uwriteln!(self.src, "let variant{tmp}_{i};");
424                    results.push(format!("variant{}_{}", tmp, i));
425                }
426
427                let expr_to_match = format!("variant{}.tag", tmp);
428
429                uwriteln!(self.src, "switch ({expr_to_match}) {{");
430                for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
431                    uwriteln!(self.src, "case '{}': {{", case.name.as_str());
432                    if case.ty.is_some() {
433                        uwriteln!(self.src, "const e = variant{tmp}.val;");
434                    }
435                    self.src.push_str(&block);
436
437                    for (i, result) in block_results.iter().enumerate() {
438                        uwriteln!(self.src, "variant{tmp}_{i} = {result};");
439                    }
440                    uwriteln!(
441                        self.src,
442                        "break;
443                        }}"
444                    );
445                }
446                let variant_name = name.to_upper_camel_case();
447                uwriteln!(
448                    self.src,
449                    "default: {{
450                        throw new TypeError('invalid variant specified for {variant_name}');
451                    }}"
452                );
453                uwriteln!(self.src, "}}");
454            }
455
456            Instruction::VariantLift { variant, name, .. } => {
457                let blocks = self
458                    .blocks
459                    .drain(self.blocks.len() - variant.cases.len()..)
460                    .collect::<Vec<_>>();
461
462                let tmp = self.tmp();
463                let operand = &operands[0];
464
465                uwriteln!(
466                    self.src,
467                    "let variant{tmp};
468                    switch ({operand}) {{"
469                );
470
471                for (i, (case, (block, block_results))) in
472                    variant.cases.iter().zip(blocks).enumerate()
473                {
474                    let tag = case.name.as_str();
475                    uwriteln!(
476                        self.src,
477                        "case {i}: {{
478                            {block}\
479                            variant{tmp} = {{
480                                tag: '{tag}',"
481                    );
482                    if case.ty.is_some() {
483                        assert!(block_results.len() == 1);
484                        uwriteln!(self.src, "   val: {}", block_results[0]);
485                    } else {
486                        assert!(block_results.len() == 0);
487                    }
488                    uwriteln!(
489                        self.src,
490                        "   }};
491                        break;
492                        }}"
493                    );
494                }
495                let variant_name = name.to_upper_camel_case();
496                if !self.valid_lifting_optimization {
497                    uwriteln!(
498                        self.src,
499                        "default: {{
500                            throw new TypeError('invalid variant discriminant for {variant_name}');
501                        }}"
502                    );
503                }
504                uwriteln!(self.src, "}}");
505                results.push(format!("variant{}", tmp));
506            }
507
508            Instruction::UnionLower {
509                union,
510                results: result_types,
511                name,
512                ..
513            } => {
514                let blocks = self
515                    .blocks
516                    .drain(self.blocks.len() - union.cases.len()..)
517                    .collect::<Vec<_>>();
518                let tmp = self.tmp();
519                let op0 = &operands[0];
520                uwriteln!(self.src, "const union{tmp} = {op0};");
521
522                for i in 0..result_types.len() {
523                    uwriteln!(self.src, "let union{tmp}_{i};");
524                    results.push(format!("union{tmp}_{i}"));
525                }
526
527                uwriteln!(self.src, "switch (union{tmp}.tag) {{");
528                for (i, (_case, (block, block_results))) in
529                    union.cases.iter().zip(blocks).enumerate()
530                {
531                    uwriteln!(
532                        self.src,
533                        "case {i}: {{
534                            const e = union{tmp}.val;
535                            {block}"
536                    );
537                    for (i, result) in block_results.iter().enumerate() {
538                        uwriteln!(self.src, "union{tmp}_{i} = {result};");
539                    }
540                    uwriteln!(
541                        self.src,
542                        "break;
543                        }}"
544                    );
545                }
546                let name = name.to_upper_camel_case();
547                uwriteln!(
548                    self.src,
549                    "default: {{
550                        throw new TypeError('invalid union specified for {name}');
551                    }}"
552                );
553                uwriteln!(self.src, "}}");
554            }
555
556            Instruction::UnionLift { union, name, .. } => {
557                let blocks = self
558                    .blocks
559                    .drain(self.blocks.len() - union.cases.len()..)
560                    .collect::<Vec<_>>();
561
562                let tmp = self.tmp();
563                let operand = &operands[0];
564
565                uwriteln!(
566                    self.src,
567                    "let union{tmp};
568                    switch ({operand}) {{"
569                );
570                for (i, (_case, (block, block_results))) in
571                    union.cases.iter().zip(blocks).enumerate()
572                {
573                    assert!(block_results.len() == 1);
574                    let block_result = &block_results[0];
575                    uwriteln!(
576                        self.src,
577                        "case {i}: {{
578                            {block}\
579                            union{tmp} = {{
580                                tag: {i},
581                                val: {block_result},
582                            }};
583                            break;
584                        }}"
585                    );
586                }
587                let name = name.to_upper_camel_case();
588                if !self.valid_lifting_optimization {
589                    uwriteln!(
590                        self.src,
591                        "default: {{
592                            throw new TypeError('invalid union discriminant for {name}');
593                        }}"
594                    );
595                }
596                uwriteln!(self.src, "}}");
597                results.push(format!("union{tmp}"));
598            }
599
600            Instruction::OptionLower {
601                payload,
602                results: result_types,
603                ..
604            } => {
605                let (mut some, some_results) = self.blocks.pop().unwrap();
606                let (mut none, none_results) = self.blocks.pop().unwrap();
607
608                let tmp = self.tmp();
609                let operand = &operands[0];
610                uwriteln!(self.src, "const variant{tmp} = {operand};");
611
612                for i in 0..result_types.len() {
613                    uwriteln!(self.src, "let variant{tmp}_{i};");
614                    results.push(format!("variant{tmp}_{i}"));
615
616                    let some_result = &some_results[i];
617                    let none_result = &none_results[i];
618                    uwriteln!(some, "variant{tmp}_{i} = {some_result};");
619                    uwriteln!(none, "variant{tmp}_{i} = {none_result};");
620                }
621
622                if maybe_null(resolve, payload) {
623                    uwriteln!(
624                        self.src,
625                        "switch (variant{tmp}.tag) {{
626                            case 'none': {{
627                                {none}\
628                                break;
629                            }}
630                            case 'some': {{
631                                const e = variant{tmp}.val;
632                                {some}\
633                                break;
634                            }}
635                            default: {{
636                                throw new TypeError('invalid variant specified for option');
637                            }}
638                        }}"
639                    );
640                } else {
641                    uwriteln!(
642                        self.src,
643                        "if (variant{tmp} === null || variant{tmp} === undefined) {{
644                            {none}\
645                        }} else {{
646                            const e = variant{tmp};
647                            {some}\
648                        }}"
649                    );
650                }
651            }
652
653            Instruction::OptionLift { payload, .. } => {
654                let (some, some_results) = self.blocks.pop().unwrap();
655                let (none, none_results) = self.blocks.pop().unwrap();
656                assert!(none_results.len() == 0);
657                assert!(some_results.len() == 1);
658                let some_result = &some_results[0];
659
660                let tmp = self.tmp();
661                let operand = &operands[0];
662
663                let (v_none, v_some) = if maybe_null(resolve, payload) {
664                    (
665                        "{ tag: 'none' }",
666                        format!(
667                            "{{
668                                tag: 'some',
669                                val: {some_result}
670                            }}"
671                        ),
672                    )
673                } else {
674                    ("null", some_result.into())
675                };
676
677                if !self.valid_lifting_optimization {
678                    uwriteln!(
679                        self.src,
680                        "let variant{tmp};
681                        switch ({operand}) {{
682                            case 0: {{
683                                {none}\
684                                variant{tmp} = {v_none};
685                                break;
686                            }}
687                            case 1: {{
688                                {some}\
689                                variant{tmp} = {v_some};
690                                break;
691                            }}
692                            default: {{
693                                throw new TypeError('invalid variant discriminant for option');
694                            }}
695                        }}"
696                    );
697                } else {
698                    uwriteln!(
699                        self.src,
700                        "let variant{tmp};
701                        if ({operand}) {{
702                            {some}\
703                            variant{tmp} = {v_some};
704                        }} else {{
705                            {none}\
706                            variant{tmp} = {v_none};
707                        }}"
708                    );
709                }
710
711                results.push(format!("variant{tmp}"));
712            }
713
714            Instruction::ResultLower {
715                results: result_types,
716                ..
717            } => {
718                let (mut err, err_results) = self.blocks.pop().unwrap();
719                let (mut ok, ok_results) = self.blocks.pop().unwrap();
720
721                let tmp = self.tmp();
722                let operand = &operands[0];
723                uwriteln!(self.src, "const variant{tmp} = {operand};");
724
725                for i in 0..result_types.len() {
726                    uwriteln!(self.src, "let variant{tmp}_{i};");
727                    results.push(format!("variant{tmp}_{i}"));
728
729                    let ok_result = &ok_results[i];
730                    let err_result = &err_results[i];
731                    uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
732                    uwriteln!(err, "variant{tmp}_{i} = {err_result};");
733                }
734
735                uwriteln!(
736                    self.src,
737                    "switch (variant{tmp}.tag) {{
738                        case 'ok': {{
739                            const e = variant{tmp}.val;
740                            {ok}\
741                            break;
742                        }}
743                        case 'err': {{
744                            const e = variant{tmp}.val;
745                            {err}\
746                            break;
747                        }}
748                        default: {{
749                            throw new TypeError('invalid variant specified for result');
750                        }}
751                    }}"
752                );
753            }
754
755            Instruction::ResultLift { result, .. } => {
756                let (err, err_results) = self.blocks.pop().unwrap();
757                let (ok, ok_results) = self.blocks.pop().unwrap();
758                let ok_result = if result.ok.is_some() {
759                    assert_eq!(ok_results.len(), 1);
760                    format!("{}", ok_results[0])
761                } else {
762                    assert_eq!(ok_results.len(), 0);
763                    String::from("undefined")
764                };
765                let err_result = if result.err.is_some() {
766                    assert_eq!(err_results.len(), 1);
767                    format!("{}", err_results[0])
768                } else {
769                    assert_eq!(err_results.len(), 0);
770                    String::from("undefined")
771                };
772                let tmp = self.tmp();
773                let op0 = &operands[0];
774
775                if !self.valid_lifting_optimization {
776                    uwriteln!(
777                        self.src,
778                        "let variant{tmp};
779                        switch ({op0}) {{
780                            case 0: {{
781                                {ok}\
782                                variant{tmp} = {{
783                                    tag: 'ok',
784                                    val: {ok_result}
785                                }};
786                                break;
787                            }}
788                            case 1: {{
789                                {err}\
790                                variant{tmp} = {{
791                                    tag: 'err',
792                                    val: {err_result}
793                                }};
794                                break;
795                            }}
796                            default: {{
797                                throw new TypeError('invalid variant discriminant for expected');
798                            }}
799                        }}"
800                    );
801                } else {
802                    uwriteln!(
803                        self.src,
804                        "let variant{tmp};
805                        if ({op0}) {{
806                            {err}\
807                            variant{tmp} = {{
808                                tag: 'err',
809                                val: {err_result}
810                            }};
811                        }} else {{
812                            {ok}\
813                            variant{tmp} = {{
814                                tag: 'ok',
815                                val: {ok_result}
816                            }};
817                        }}"
818                    );
819                }
820                results.push(format!("variant{tmp}"));
821            }
822
823            // Lowers an enum in accordance with https://webidl.spec.whatwg.org/#es-enumeration.
824            Instruction::EnumLower { name, enum_, .. } => {
825                let tmp = self.tmp();
826
827                let operand = &operands[0];
828                uwriteln!(self.src, "const val{tmp} = {operand};");
829
830                // Declare a variable to hold the result.
831                uwriteln!(
832                    self.src,
833                    "let enum{tmp};
834                    switch (val{tmp}) {{"
835                );
836                for (i, case) in enum_.cases.iter().enumerate() {
837                    uwriteln!(
838                        self.src,
839                        "case '{case}': {{
840                            enum{tmp} = {i};
841                            break;
842                        }}",
843                        case = case.name
844                    );
845                }
846                uwriteln!(self.src, "default: {{");
847                if !self.valid_lifting_optimization {
848                    uwriteln!(
849                        self.src,
850                        "if (({operand}) instanceof Error) {{
851                        console.error({operand});
852                    }}"
853                    );
854                }
855                uwriteln!(
856                    self.src,
857                    "
858                            throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
859                        }}
860                    }}"
861                );
862
863                results.push(format!("enum{tmp}"));
864            }
865
866            Instruction::EnumLift { name, enum_, .. } => {
867                let tmp = self.tmp();
868
869                uwriteln!(
870                    self.src,
871                    "let enum{tmp};
872                    switch ({}) {{",
873                    operands[0]
874                );
875                for (i, case) in enum_.cases.iter().enumerate() {
876                    uwriteln!(
877                        self.src,
878                        "case {i}: {{
879                            enum{tmp} = '{case}';
880                            break;
881                        }}",
882                        case = case.name
883                    );
884                }
885                if !self.valid_lifting_optimization {
886                    let name = name.to_upper_camel_case();
887                    uwriteln!(
888                        self.src,
889                        "default: {{
890                            throw new TypeError('invalid discriminant specified for {name}');
891                        }}",
892                    );
893                }
894                uwriteln!(self.src, "}}");
895
896                results.push(format!("enum{tmp}"));
897            }
898
899            Instruction::ListCanonLower { element, .. } => {
900                let tmp = self.tmp();
901                let memory = self.memory.as_ref().unwrap();
902                let realloc = self.realloc.unwrap();
903
904                let size = self.sizes.size(element);
905                let align = self.sizes.align(element);
906                uwriteln!(self.src, "const val{tmp} = {};", operands[0]);
907                if matches!(element, Type::U8) {
908                    uwriteln!(self.src, "const len{tmp} = val{tmp}.byteLength;");
909                } else {
910                    uwriteln!(self.src, "const len{tmp} = val{tmp}.length;");
911                }
912                uwriteln!(
913                    self.src,
914                    "const 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                        "const src{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, len{tmp} * {size});",
921                    );
922                } else {
923                    uwriteln!(
924                        self.src,
925                        "const 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, "const ptr{tmp} = {};", operands[0]);
939                uwriteln!(self.src, "const 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                    "const result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));",
945                    self.sizes.size(element),
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                    "const 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, "const len{tmp} = {encoded_len};");
973                } else {
974                    uwriteln!(self.src, "const 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, "const ptr{tmp} = {};", operands[0]);
994                uwriteln!(self.src, "const len{tmp} = {};", operands[1]);
995                uwriteln!(
996                    self.src,
997                    "const 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);
1011                let align = self.sizes.align(element);
1012
1013                // first store our vec-to-lower in a temporary since we'll
1014                // reference it multiple times.
1015                uwriteln!(self.src, "const {vec} = {};", operands[0]);
1016                uwriteln!(self.src, "const {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                    "const {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);
1041                let len = format!("len{tmp}");
1042                uwriteln!(self.src, "const {len} = {};", operands[1]);
1043                let base = format!("base{tmp}");
1044                uwriteln!(self.src, "const {base} = {};", operands[0]);
1045                let result = format!("result{tmp}");
1046                uwriteln!(self.src, "const {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                self.bind_results(sig.results.len(), results);
1063                uwriteln!(self.src, "{}({});", self.callee, operands.join(", "));
1064            }
1065
1066            Instruction::CallInterface { func } => {
1067                if self.err == ErrHandling::ResultCatchHandler {
1068                    let err_payload = self.intrinsic(Intrinsic::GetErrorPayload);
1069                    uwriteln!(
1070                        self.src,
1071                        "let ret;
1072                        try {{
1073                            ret = {{ tag: 'ok', val: {}({}) }};
1074                        }} catch (e) {{
1075                            ret = {{ tag: 'err', val: {}(e) }};
1076                        }}",
1077                        self.callee,
1078                        operands.join(", "),
1079                        err_payload,
1080                    );
1081                    results.push("ret".to_string());
1082                } else {
1083                    self.bind_results(func.results.len(), results);
1084                    uwriteln!(self.src, "{}({});", self.callee, operands.join(", "));
1085                }
1086            }
1087
1088            Instruction::Return { amt, .. } => {
1089                if let Some(f) = &self.post_return {
1090                    uwriteln!(self.src, "{f}(ret);");
1091                }
1092
1093                if self.err == ErrHandling::ThrowResultErr {
1094                    let component_err = self.intrinsic(Intrinsic::ComponentError);
1095                    let operand = &operands[0];
1096                    uwriteln!(
1097                        self.src,
1098                        "if ({operand}.tag === 'err') {{
1099                            throw new {component_err}({operand}.val);
1100                        }}
1101                        return {operand}.val;"
1102                    );
1103                } else {
1104                    match amt {
1105                        0 => {}
1106                        1 => uwriteln!(self.src, "return {};", operands[0]),
1107                        _ => uwriteln!(self.src, "return [{}];", operands.join(", ")),
1108                    }
1109                }
1110            }
1111
1112            Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1113            Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1114            Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1115            Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1116            Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1117            Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
1118            Instruction::I32Load16U { offset } => {
1119                self.load("getUint16", *offset, operands, results)
1120            }
1121            Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
1122            Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
1123            Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
1124            Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
1125            Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
1126            Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
1127            Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
1128
1129            Instruction::Malloc { size, align, .. } => {
1130                let tmp = self.tmp();
1131                let realloc = self.realloc.as_ref().unwrap();
1132                let ptr = format!("ptr{tmp}");
1133                uwriteln!(self.src, "const {ptr} = {realloc}(0, 0, {align}, {size});",);
1134                results.push(ptr);
1135            }
1136
1137            i => unimplemented!("{:?}", i),
1138        }
1139    }
1140}
1141
1142/// Tests whether `ty` can be represented with `null`, and if it can then
1143/// the "other type" is returned. If `Some` is returned that means that `ty`
1144/// is `null | <return>`. If `None` is returned that means that `null` can't
1145/// be used to represent `ty`.
1146pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
1147    let id = match ty {
1148        Type::Id(id) => *id,
1149        _ => return None,
1150    };
1151    match &resolve.types[id].kind {
1152        // If `ty` points to an `option<T>`, then `ty` can be represented
1153        // with `null` if `t` itself can't be represented with null. For
1154        // example `option<option<u32>>` can't be represented with `null`
1155        // since that's ambiguous if it's `none` or `some(none)`.
1156        //
1157        // Note, oddly enough, that `option<option<option<u32>>>` can be
1158        // represented as `null` since:
1159        //
1160        // * `null` => `none`
1161        // * `{ tag: "none" }` => `some(none)`
1162        // * `{ tag: "some", val: null }` => `some(some(none))`
1163        // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
1164        //
1165        // It's doubtful anyone would actually rely on that though due to
1166        // how confusing it is.
1167        TypeDefKind::Option(t) => {
1168            if !maybe_null(resolve, t) {
1169                Some(t)
1170            } else {
1171                None
1172            }
1173        }
1174        TypeDefKind::Type(t) => as_nullable(resolve, t),
1175        _ => None,
1176    }
1177}
1178
1179pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
1180    as_nullable(resolve, ty).is_some()
1181}
1182
1183pub fn array_ty(resolve: &Resolve, ty: &Type) -> Option<&'static str> {
1184    match ty {
1185        Type::Bool => None,
1186        Type::U8 => Some("Uint8Array"),
1187        Type::S8 => Some("Int8Array"),
1188        Type::U16 => Some("Uint16Array"),
1189        Type::S16 => Some("Int16Array"),
1190        Type::U32 => Some("Uint32Array"),
1191        Type::S32 => Some("Int32Array"),
1192        Type::U64 => Some("BigUint64Array"),
1193        Type::S64 => Some("BigInt64Array"),
1194        Type::Float32 => Some("Float32Array"),
1195        Type::Float64 => Some("Float64Array"),
1196        Type::Char => None,
1197        Type::String => None,
1198        Type::Id(id) => match &resolve.types[*id].kind {
1199            TypeDefKind::Type(t) => array_ty(resolve, t),
1200            _ => None,
1201        },
1202    }
1203}