wasm_bindgen_cli_support/js/
binding.rs

1//! Support for actually generating a JS function shim.
2//!
3//! This `Builder` type is used to generate JS function shims which sit between
4//! exported functions, table elements, imports, etc. All function shims
5//! generated by `wasm-bindgen` run through this type.
6
7use crate::js::Context;
8use crate::wit::InstructionData;
9use crate::wit::{
10    Adapter, AdapterId, AdapterKind, AdapterType, AuxFunctionArgumentData, Instruction,
11};
12use anyhow::{bail, Error};
13use std::collections::HashSet;
14use std::fmt::Write;
15use walrus::{Module, ValType};
16
17/// A one-size-fits-all builder for processing WebIDL bindings and generating
18/// JS.
19pub struct Builder<'a, 'b> {
20    /// Parent context used to expose helper functions and such.
21    pub cx: &'a mut Context<'b>,
22    /// Whether or not this is building a constructor for a Rust class, and if
23    /// so what class it's constructing.
24    constructor: Option<String>,
25    /// Whether or not this is building a method of a Rust class instance, and
26    /// whether or not the method consumes `self` or not.
27    method: Option<bool>,
28    /// Whether this is a classless this function (receives JS `this` as first param)
29    classless_this: bool,
30    /// Whether or not we're catching exceptions from the main function
31    /// invocation. Currently only used for imports.
32    catch: bool,
33    /// Whether or not we're logging the error coming out of this intrinsic
34    log_error: bool,
35}
36
37/// Helper struct used to create JS to process all instructions in an adapter
38/// function.
39pub struct JsBuilder<'a, 'b> {
40    /// General context for building JS, used to manage intrinsic names, exposed
41    /// JS functions, etc.
42    cx: &'a mut Context<'b>,
43
44    /// A debug name for the function being generated, used for error messages
45    debug_name: &'a str,
46
47    /// The "prelude" of the function, or largely just the JS function we've
48    /// built so far.
49    prelude: String,
50
51    /// Code which should go before the `try {` in a try-finally block.
52    pre_try: String,
53
54    /// JS code to execute in a `finally` block in case any exceptions happen.
55    finally: String,
56
57    /// An index used to manage allocation of temporary indices, used to name
58    /// variables to ensure nothing clashes with anything else.
59    tmp: usize,
60
61    /// Names or expressions representing the arguments to the adapter. This is
62    /// use to translate the `arg.get` instruction.
63    args: Vec<String>,
64
65    /// The Wasm interface types "stack". The expressions pushed onto this stack
66    /// are intended to be *pure*, and if they're not, they should be pushed
67    /// into the `prelude`, assigned to a variable, and the variable should be
68    /// pushed to the stack. We're not super principled about this though, so
69    /// improvements will likely happen here over time.
70    stack: Vec<String>,
71}
72
73pub struct JsFunction {
74    pub code: String,
75    pub ts_sig: String,
76    pub js_doc: String,
77    pub ts_doc: String,
78    pub ts_arg_tys: Vec<String>,
79    pub ts_ret_ty: Option<String>,
80    pub ts_refs: HashSet<TsReference>,
81    /// Whether this function has a single optional argument.
82    ///
83    /// If the function is a setter, that means that the field it sets is optional.
84    pub might_be_optional_field: bool,
85    pub catch: bool,
86    pub log_error: bool,
87}
88
89/// A references to an (likely) exported symbol used in TS type expression.
90///
91/// Right now, only string enum require this type of analysis.
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93pub enum TsReference {
94    StringEnum(String),
95}
96
97impl<'a, 'b> Builder<'a, 'b> {
98    pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> {
99        Builder {
100            log_error: false,
101            cx,
102            constructor: None,
103            method: None,
104            classless_this: false,
105            catch: false,
106        }
107    }
108
109    pub fn method(&mut self, consumed: bool) {
110        self.method = Some(consumed);
111    }
112
113    pub fn classless_this(&mut self) {
114        self.classless_this = true;
115    }
116
117    pub fn constructor(&mut self, class: &str) {
118        self.constructor = Some(class.to_string());
119    }
120
121    pub fn catch(&mut self, catch: bool) {
122        self.catch = catch;
123    }
124
125    pub fn log_error(&mut self, log: bool) {
126        self.log_error = log;
127    }
128
129    pub fn process(
130        &mut self,
131        adapter: &Adapter,
132        instructions: &[InstructionData],
133        args_data: &Option<Vec<AuxFunctionArgumentData>>,
134        asyncness: bool,
135        variadic: bool,
136        generate_jsdoc: bool,
137        debug_name: &str,
138        ret_ty_override: &Option<String>,
139        ret_desc: &Option<String>,
140    ) -> Result<JsFunction, Error> {
141        if self
142            .cx
143            .aux
144            .imports_with_assert_no_shim
145            .contains(&adapter.id)
146        {
147            bail!("generating a shim for something asserted to have no shim");
148        }
149
150        let mut params = adapter.params.iter();
151        let mut function_args = Vec::new();
152        let mut arg_tys = Vec::new();
153
154        // If this is a method then we're generating this as part of a class
155        // method, so the leading parameter is the this pointer stored on
156        // the JS object, so synthesize that here.
157        let mut js = JsBuilder::new(self.cx, debug_name);
158        if let Some(consumes_self) = self.method {
159            let _ = params.next();
160            if js.cx.config.generate_reset_state {
161                js.prelude(
162                    "
163                    if (this.__wbg_inst !== undefined && this.__wbg_inst !== __wbg_instance_id) {
164                        throw new Error('Invalid stale object from previous Wasm instance');
165                    }
166                    ",
167                );
168            }
169            if js.cx.config.debug {
170                js.prelude(
171                    "if (this.__wbg_ptr == 0) throw new Error('Attempt to use a moved value');",
172                );
173            }
174            if consumes_self {
175                js.prelude("const ptr = this.__destroy_into_raw();");
176                js.args.push("ptr".into());
177            } else {
178                js.args.push("this.__wbg_ptr".into());
179            }
180        } else if self.classless_this {
181            let _ = params.next();
182            js.args.push("this".into());
183        }
184        for (i, param) in params.enumerate() {
185            let arg = match args_data {
186                Some(list) => list[i].clone(),
187                None => AuxFunctionArgumentData {
188                    name: format!("arg{i}"),
189                    ty_override: None,
190                    desc: None,
191                },
192            };
193            js.args.push(arg.name.clone());
194            function_args.push(arg);
195            arg_tys.push(param);
196        }
197
198        // Translate all instructions, the fun loop!
199        //
200        // This loop will process all instructions for this adapter function.
201        // Each instruction will push/pop from the `js.stack` variable, and will
202        // eventually build up the entire `js.prelude` variable with all the JS
203        // code that we're going to be adding. Note that the stack at the end
204        // represents all returned values.
205        //
206        // We don't actually manage a literal stack at runtime, but instead we
207        // act as more of a compiler to generate straight-line code to make it
208        // more JIT-friendly. The generated code should be equivalent to the
209        // Wasm interface types stack machine, however.
210        for instr in instructions {
211            instruction(
212                &mut js,
213                &instr.instr,
214                &mut self.log_error,
215                &self.constructor,
216            )?;
217        }
218
219        assert_eq!(
220            js.stack.len(),
221            adapter.results.len(),
222            "stack size mismatch for {debug_name}"
223        );
224        match js.stack.len() {
225            0 => {}
226            1 => {
227                let val = js.pop();
228                js.prelude(&format!("return {val};"));
229            }
230
231            // TODO: this should be pretty trivial to support (commented out
232            // code below), but we should be sure to have a test for this
233            // somewhere. Currently I don't think it's possible to actually
234            // exercise this with just Rust code, so let's wait until we get
235            // some tests to enable this path.
236            _ => bail!("multi-value returns from adapters not supported yet"),
237            // _ => {
238            //     let expr = js.stack.join(", ");
239            //     js.stack.truncate(0);
240            //     js.prelude(&format!("return [{}];", expr));
241            // }
242        }
243        assert!(js.stack.is_empty());
244
245        // // Remove extraneous typescript args which were synthesized and aren't
246        // // part of our function shim.
247        // while self.ts_args.len() > function_args.len() {
248        //     self.ts_args.remove(0);
249        // }
250
251        let mut code = String::new();
252        code.push('(');
253        for (i, v) in function_args.iter().enumerate() {
254            if i != 0 {
255                code.push_str(", ");
256            }
257
258            if variadic && i == function_args.len() - 1 {
259                code.push_str("...");
260            }
261
262            code.push_str(&v.name);
263        }
264        code.push_str(") {\n");
265
266        let call = if !js.finally.is_empty() {
267            format!(
268                "{}try {{\n{}}} finally {{\n{}}}\n",
269                js.pre_try, js.prelude, js.finally
270            )
271        } else {
272            js.pre_try + &js.prelude
273        };
274
275        if self.catch {
276            js.cx.expose_handle_error()?;
277        }
278
279        // Generate a try/catch block in debug mode which handles unexpected and
280        // unhandled exceptions, typically used on imports. This currently just
281        // logs what happened, but keeps the exception being thrown to propagate
282        // elsewhere.
283        if self.log_error {
284            js.cx.expose_log_error();
285        }
286
287        code.push_str(&call);
288        code.push('}');
289
290        // Rust Structs' fields converted into Getter and Setter functions before
291        // we decode them from webassembly, finding if a function is a field
292        // should start from here. Struct fields(Getter) only have one arg, and
293        // this is the clue we can infer if a function might be a field.
294        let mut might_be_optional_field = false;
295        let (ts_sig, ts_arg_tys, ts_ret_ty, ts_refs) = self.typescript_signature(
296            &function_args,
297            &arg_tys,
298            &adapter.inner_results,
299            &mut might_be_optional_field,
300            asyncness,
301            variadic,
302            ret_ty_override,
303        );
304        let js_doc = if generate_jsdoc {
305            self.js_doc_comments(
306                &function_args,
307                &arg_tys,
308                &ts_ret_ty,
309                variadic,
310                ret_ty_override,
311                ret_desc,
312            )
313        } else {
314            String::new()
315        };
316
317        // generate ts_doc
318        // ts doc is slightly different than js doc, where there is no
319        // arguments types followed after @param tag, as well as no special
320        // casings for arguments names such as "@param {string} [arg]" that
321        // tags the argument as optional, for ts doc we only need arg names
322        // and rest are just derived from function ts signature
323        let ts_doc = self.ts_doc_comments(&function_args, ret_desc);
324
325        Ok(JsFunction {
326            code,
327            ts_sig,
328            js_doc,
329            ts_doc,
330            ts_arg_tys,
331            ts_ret_ty,
332            ts_refs,
333            might_be_optional_field,
334            catch: self.catch,
335            log_error: self.log_error,
336        })
337    }
338
339    /// Returns the typescript signature of the binding that this has described.
340    /// This is used to generate all the TypeScript definitions later on.
341    ///
342    /// Note that the TypeScript returned here is just the argument list and the
343    /// return value, it doesn't include the function name in any way.
344    fn typescript_signature(
345        &self,
346        args_data: &[AuxFunctionArgumentData],
347        arg_tys: &[&AdapterType],
348        result_tys: &[AdapterType],
349        might_be_optional_field: &mut bool,
350        asyncness: bool,
351        variadic: bool,
352        ret_ty_override: &Option<String>,
353    ) -> (String, Vec<String>, Option<String>, HashSet<TsReference>) {
354        // Build up the typescript signature as well
355        let mut omittable = true;
356        let mut ts_args = Vec::new();
357        let mut ts_arg_tys = Vec::new();
358        let mut ts_refs = HashSet::new();
359        for (
360            AuxFunctionArgumentData {
361                name, ty_override, ..
362            },
363            ty,
364        ) in args_data.iter().zip(arg_tys).rev()
365        {
366            // In TypeScript, we can mark optional parameters as omittable
367            // using the `?` suffix, but only if they're not followed by
368            // non-omittable parameters. Therefore iterate the parameter list
369            // in reverse and stop using the `?` suffix for optional params as
370            // soon as a non-optional parameter is encountered.
371            let mut arg = name.to_string();
372            let mut ts = String::new();
373            if let Some(v) = ty_override {
374                omittable = false;
375                arg.push_str(": ");
376                ts.push_str(v);
377            } else {
378                match ty {
379                    AdapterType::Option(ty) if omittable => {
380                        // e.g. `foo?: string | null`
381                        arg.push_str("?: ");
382                        adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
383                        ts.push_str(" | null");
384                    }
385                    ty => {
386                        adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
387                        omittable = false;
388                        arg.push_str(": ");
389                    }
390                }
391            }
392            arg.push_str(&ts);
393            ts_arg_tys.push(ts);
394            ts_args.push(arg);
395        }
396        ts_args.reverse();
397        ts_arg_tys.reverse();
398        let mut ts = String::from("(");
399        if variadic {
400            if let Some((last, non_variadic_args)) = ts_args.split_last() {
401                ts.push_str(&non_variadic_args.join(", "));
402                if !non_variadic_args.is_empty() {
403                    ts.push_str(", ");
404                }
405                ts.push_str((String::from("...") + last).as_str())
406            }
407        } else {
408            ts.push_str(&ts_args.join(", "));
409        };
410        ts.push(')');
411
412        // If this function is an optional field's setter, it should have only
413        // one arg, and omittable should be `true`.
414        if ts_args.len() == 1 && omittable {
415            *might_be_optional_field = true;
416        }
417
418        // Constructors have no listed return type in typescript
419        let mut ts_ret = None;
420        if self.constructor.is_none() {
421            ts.push_str(": ");
422            let mut ret = String::new();
423            if let Some(v) = &ret_ty_override {
424                ret.push_str(v);
425            } else {
426                match result_tys.len() {
427                    0 => ret.push_str("void"),
428                    1 => adapter2ts(
429                        &result_tys[0],
430                        TypePosition::Return,
431                        &mut ret,
432                        Some(&mut ts_refs),
433                    ),
434                    _ => ret.push_str("[any]"),
435                }
436            }
437            if asyncness {
438                ret = format!("Promise<{ret}>");
439            }
440            ts.push_str(&ret);
441            ts_ret = Some(ret);
442        }
443        (ts, ts_arg_tys, ts_ret, ts_refs)
444    }
445
446    /// Returns a helpful JS doc comment which lists types for all parameters
447    /// and the return value.
448    fn js_doc_comments(
449        &self,
450        args_data: &[AuxFunctionArgumentData],
451        arg_tys: &[&AdapterType],
452        ts_ret: &Option<String>,
453        variadic: bool,
454        ret_ty_override: &Option<String>,
455        ret_desc: &Option<String>,
456    ) -> String {
457        let (variadic_arg, fn_arg_names) = match args_data.split_last() {
458            Some((last, args)) if variadic => (Some(last), args),
459            _ => (None, args_data),
460        };
461
462        let mut omittable = true;
463        let mut js_doc_args = Vec::new();
464
465        for (
466            AuxFunctionArgumentData {
467                name,
468                ty_override,
469                desc,
470            },
471            ty,
472        ) in fn_arg_names.iter().zip(arg_tys).rev()
473        {
474            let mut arg = "@param {".to_string();
475
476            if let Some(v) = ty_override {
477                omittable = false;
478                arg.push_str(v);
479                arg.push_str("} ");
480                arg.push_str(name);
481            } else {
482                match ty {
483                    AdapterType::Option(ty) if omittable => {
484                        adapter2ts(ty, TypePosition::Argument, &mut arg, None);
485                        arg.push_str(" | null} ");
486                        arg.push('[');
487                        arg.push_str(name);
488                        arg.push(']');
489                    }
490                    _ => {
491                        omittable = false;
492                        adapter2ts(ty, TypePosition::Argument, &mut arg, None);
493                        arg.push_str("} ");
494                        arg.push_str(name);
495                    }
496                }
497            }
498            // append description
499            if let Some(v) = desc {
500                arg.push_str(" - ");
501                arg.push_str(v);
502            }
503            arg.push('\n');
504            js_doc_args.push(arg);
505        }
506
507        let mut ret: String = js_doc_args.into_iter().rev().collect();
508
509        if let (
510            Some(AuxFunctionArgumentData {
511                name,
512                ty_override,
513                desc,
514            }),
515            Some(ty),
516        ) = (variadic_arg, arg_tys.last())
517        {
518            ret.push_str("@param {...");
519            if let Some(v) = ty_override {
520                ret.push_str(v);
521            } else {
522                adapter2ts(ty, TypePosition::Argument, &mut ret, None);
523            }
524            ret.push_str("} ");
525            ret.push_str(name);
526
527            // append desc
528            if let Some(v) = desc {
529                ret.push_str(" - ");
530                ret.push_str(v);
531            }
532            ret.push('\n');
533        }
534        if let Some(ts) = ret_ty_override.as_ref().or(ts_ret.as_ref()) {
535            // skip if type is void and there is no description
536            if ts != "void" || ret_desc.is_some() {
537                ret.push_str(&format!("@returns {{{ts}}}"));
538            }
539            // append return description
540            if let Some(v) = ret_desc {
541                ret.push(' ');
542                ret.push_str(v);
543            }
544        }
545        ret
546    }
547
548    /// Returns a helpful TS doc comment which lists all parameters and
549    /// the return value descriptions.
550    fn ts_doc_comments(
551        &self,
552        args_data: &[AuxFunctionArgumentData],
553        ret_desc: &Option<String>,
554    ) -> String {
555        let mut ts_doc = String::new();
556        // ofc we dont need arg type for ts doc, only arg name
557        for AuxFunctionArgumentData { name, desc, .. } in args_data.iter() {
558            ts_doc.push_str("@param ");
559            ts_doc.push_str(name);
560
561            // append desc
562            if let Some(v) = desc {
563                ts_doc.push_str(" - ");
564                ts_doc.push_str(v);
565            }
566            ts_doc.push('\n');
567        }
568
569        // only if there is return description, as we dont want empty @return tag
570        if let Some(ret_desc) = ret_desc {
571            ts_doc.push_str("@returns ");
572            ts_doc.push_str(ret_desc);
573        }
574        ts_doc
575    }
576}
577
578impl<'a, 'b> JsBuilder<'a, 'b> {
579    pub fn new(cx: &'a mut Context<'b>, debug_name: &'a str) -> JsBuilder<'a, 'b> {
580        JsBuilder {
581            cx,
582            debug_name,
583            args: Vec::new(),
584            tmp: 0,
585            pre_try: String::new(),
586            finally: String::new(),
587            prelude: String::new(),
588            stack: Vec::new(),
589        }
590    }
591
592    pub fn arg(&self, idx: u32) -> &str {
593        &self.args[idx as usize]
594    }
595
596    pub fn prelude(&mut self, prelude: &str) {
597        for line in prelude.trim().lines().map(|l| l.trim()) {
598            if !line.is_empty() {
599                self.prelude.push_str(line);
600                self.prelude.push('\n');
601            }
602        }
603    }
604
605    pub fn finally(&mut self, finally: &str) {
606        for line in finally.trim().lines().map(|l| l.trim()) {
607            if !line.is_empty() {
608                self.finally.push_str(line);
609                self.finally.push('\n');
610            }
611        }
612    }
613
614    pub fn tmp(&mut self) -> usize {
615        let ret = self.tmp;
616        self.tmp += 1;
617        ret
618    }
619
620    fn pop(&mut self) -> String {
621        match self.stack.pop() {
622            Some(s) => s,
623            None => panic!("popping an empty stack in {}", self.debug_name),
624        }
625    }
626
627    fn push(&mut self, arg: String) {
628        self.stack.push(arg);
629    }
630
631    fn assert_class(&mut self, arg: &str, class: &str) {
632        self.cx.expose_assert_class();
633        self.prelude(&format!("_assertClass({arg}, {class});"));
634    }
635
636    fn assert_number(&mut self, arg: &str) {
637        if !self.cx.config.debug {
638            return;
639        }
640        self.cx.expose_assert_num();
641        self.prelude(&format!("_assertNum({arg});"));
642    }
643
644    fn assert_bigint(&mut self, arg: &str) {
645        if !self.cx.config.debug {
646            return;
647        }
648        self.cx.expose_assert_bigint();
649        self.prelude(&format!("_assertBigInt({arg});"));
650    }
651
652    fn assert_bool(&mut self, arg: &str) {
653        if !self.cx.config.debug {
654            return;
655        }
656        self.cx.expose_assert_bool();
657        self.prelude(&format!("_assertBoolean({arg});"));
658    }
659
660    fn assert_optional_number(&mut self, arg: &str) {
661        if !self.cx.config.debug {
662            return;
663        }
664        self.cx.expose_is_like_none();
665        self.prelude(&format!("if (!isLikeNone({arg})) {{"));
666        self.assert_number(arg);
667        self.prelude("}");
668    }
669
670    fn assert_non_null(&mut self, arg: &str) {
671        self.cx.expose_assert_non_null();
672        self.prelude(&format!("_assertNonNull({arg});"));
673    }
674
675    fn assert_char(&mut self, arg: &str) {
676        self.cx.expose_assert_char();
677        self.prelude(&format!("_assertChar({arg});"));
678    }
679
680    fn assert_optional_bigint(&mut self, arg: &str) {
681        if !self.cx.config.debug {
682            return;
683        }
684        self.cx.expose_is_like_none();
685        self.prelude(&format!("if (!isLikeNone({arg})) {{"));
686        self.assert_bigint(arg);
687        self.prelude("}");
688    }
689
690    fn assert_optional_bool(&mut self, arg: &str) {
691        if !self.cx.config.debug {
692            return;
693        }
694        self.cx.expose_is_like_none();
695        self.prelude(&format!("if (!isLikeNone({arg})) {{"));
696        self.assert_bool(arg);
697        self.prelude("}");
698    }
699
700    fn assert_not_moved(&mut self, arg: &str) {
701        if self.cx.config.generate_reset_state {
702            // Under reset state, we need comprehensive validation
703            self.prelude(&format!(
704                "\
705                if (({arg}).__wbg_inst !== undefined && ({arg}).__wbg_inst !== __wbg_instance_id) {{
706                    throw new Error('Invalid stale object from previous Wasm instance');
707                }}
708                "
709            ));
710        }
711        if self.cx.config.debug {
712            // Debug mode only checks for moved values
713            self.prelude(&format!(
714                "\
715                if ({arg}.__wbg_ptr === 0) {{
716                    throw new Error('Attempt to use a moved value');
717                }}
718                ",
719            ));
720        }
721    }
722
723    fn string_to_memory(
724        &mut self,
725        mem: walrus::MemoryId,
726        malloc: walrus::FunctionId,
727        realloc: Option<walrus::FunctionId>,
728    ) {
729        let pass = self.cx.expose_pass_string_to_wasm(mem);
730        let val = self.pop();
731        let malloc = self.cx.export_name_of(malloc);
732        let i = self.tmp();
733        let realloc = match realloc {
734            Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)),
735            None => String::new(),
736        };
737        self.prelude(&format!(
738            "const ptr{i} = {pass}({val}, wasm.{malloc}{realloc});",
739        ));
740        self.prelude(&format!("const len{i} = WASM_VECTOR_LEN;"));
741        self.push(format!("ptr{i}"));
742        self.push(format!("len{i}"));
743    }
744}
745
746fn instruction(
747    js: &mut JsBuilder,
748    instr: &Instruction,
749    log_error: &mut bool,
750    constructor: &Option<String>,
751) -> Result<(), Error> {
752    fn wasm_to_string_enum(name: &str, index: &str) -> String {
753        // e.g. ["a","b","c"][someIndex]
754        format!("__wbindgen_enum_{name}[{index}]")
755    }
756    fn string_enum_to_wasm(name: &str, invalid: u32, enum_val: &str) -> String {
757        // e.g. (["a","b","c"].indexOf(someEnumVal) + 1 || 4) - 1
758        //                                                 |
759        //                                           invalid + 1
760        //
761        // The idea is that `indexOf` returns -1 if someEnumVal is invalid,
762        // and with +1 we get 0 which is falsey, so we can use || to
763        // substitute invalid+1. Finally, we just do -1 to get the correct
764        // values for everything.
765        format!(
766            "(__wbindgen_enum_{name}.indexOf({enum_val}) + 1 || {invalid}) - 1",
767            invalid = invalid + 1
768        )
769    }
770
771    fn int128_to_int64x2(val: &str) -> (String, String) {
772        // we don't need to perform any conversion here, because the JS
773        // WebAssembly API will automatically convert the bigints to 64 bits
774        // for us. This even allows us to ignore signedness.
775        let low = val.to_owned();
776        let high = format!("{val} >> BigInt(64)");
777        (low, high)
778    }
779    fn int64x2_to_int128(low: String, high: String, signed: bool) -> String {
780        let low = format!("BigInt.asUintN(64, {low})");
781        if signed {
782            format!("({low} | ({high} << BigInt(64)))")
783        } else {
784            format!("({low} | (BigInt.asUintN(64, {high}) << BigInt(64)))")
785        }
786    }
787
788    match instr {
789        Instruction::ArgGet(n) => {
790            let arg = js.arg(*n).to_string();
791            js.push(arg);
792        }
793
794        Instruction::CallExport(_)
795        | Instruction::CallAdapter(_)
796        | Instruction::DeferFree { .. } => {
797            let invoc = Invocation::from(instr, js.cx.module);
798            let (mut params, results) = invoc.params_results(js.cx);
799
800            let mut args = Vec::new();
801            let tmp = js.tmp();
802            if invoc.defer() {
803                if let Instruction::DeferFree { .. } = instr {
804                    // Ignore `free`'s final `align` argument, since that's manually inserted later.
805                    params -= 1;
806                }
807                // If the call is deferred, the arguments to the function still need to be
808                // accessible in the `finally` block, so we declare variables to hold the args
809                // outside of the try-finally block and then set those to the args.
810                for (i, arg) in js.stack[js.stack.len() - params..].iter().enumerate() {
811                    let name = format!("deferred{tmp}_{i}");
812                    writeln!(js.pre_try, "let {name};").unwrap();
813                    writeln!(js.prelude, "{name} = {arg};").unwrap();
814                    args.push(name);
815                }
816                if let Instruction::DeferFree { align, .. } = instr {
817                    // add alignment
818                    args.push(align.to_string());
819                }
820            } else {
821                // Otherwise, pop off the number of parameters for the function we're calling.
822                for _ in 0..params {
823                    args.push(js.pop());
824                }
825                args.reverse();
826            }
827
828            // Call the function through an export of the underlying module.
829            let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?;
830
831            // And then figure out how to actually handle where the call
832            // happens. This is pretty conditional depending on the number of
833            // return values of the function.
834            match (invoc.defer(), results) {
835                (true, 0) => {
836                    js.finally(&format!("{call};"));
837                }
838                (true, _) => panic!("deferred calls must have no results"),
839                (false, 0) => js.prelude(&format!("{call};")),
840                (false, n) => {
841                    js.prelude(&format!("const ret = {call};"));
842                    if n == 1 {
843                        js.push("ret".to_string());
844                    } else {
845                        for i in 0..n {
846                            js.push(format!("ret[{i}]"));
847                        }
848                    }
849                }
850            }
851        }
852
853        Instruction::Int32ToWasm => {
854            let val = js.pop();
855            js.assert_number(&val);
856            js.push(val);
857        }
858        Instruction::WasmToInt32 { unsigned_32 } => {
859            let val = js.pop();
860            if *unsigned_32 {
861                // When converting to a JS number we need to specially handle the `u32`
862                // case because if the high bit is set then it comes out as a negative
863                // number, but we want to switch that to an unsigned representation.
864                js.push(format!("{val} >>> 0"))
865            } else {
866                js.push(val)
867            }
868        }
869
870        Instruction::Int64ToWasm => {
871            let val = js.pop();
872            js.assert_bigint(&val);
873            js.push(val);
874        }
875        Instruction::WasmToInt64 { unsigned } => {
876            let val = js.pop();
877            if *unsigned {
878                js.push(format!("BigInt.asUintN(64, {val})"))
879            } else {
880                js.push(val)
881            }
882        }
883
884        Instruction::Int128ToWasm => {
885            let val = js.pop();
886            js.assert_bigint(&val);
887            let (low, high) = int128_to_int64x2(&val);
888            js.push(low);
889            js.push(high);
890        }
891        Instruction::WasmToInt128 { signed } => {
892            let high = js.pop();
893            let low = js.pop();
894            js.push(int64x2_to_int128(low, high, *signed));
895        }
896
897        Instruction::OptionInt128ToWasm => {
898            let val = js.pop();
899            js.cx.expose_is_like_none();
900            js.assert_optional_bigint(&val);
901            let (low, high) = int128_to_int64x2(&val);
902            js.push(format!("!isLikeNone({val})"));
903            js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}"));
904            js.push(format!("isLikeNone({val}) ? BigInt(0) : {high}"));
905        }
906        Instruction::OptionWasmToInt128 { signed } => {
907            let high = js.pop();
908            let low = js.pop();
909            let present = js.pop();
910            let val = int64x2_to_int128(low, high, *signed);
911            js.push(format!("{present} === 0 ? undefined : {val}"));
912        }
913
914        Instruction::WasmToStringEnum { name } => {
915            let index = js.pop();
916            js.cx.expose_string_enum(name);
917            js.push(wasm_to_string_enum(name, &index))
918        }
919
920        Instruction::OptionWasmToStringEnum { name } => {
921            // Since hole is currently variant_count+1 and the lookup is
922            // ["a","b","c"][index], the lookup will implicitly return map
923            // the hole to undefined, because OOB indexes will return undefined.
924            let index = js.pop();
925            js.cx.expose_string_enum(name);
926            js.push(wasm_to_string_enum(name, &index))
927        }
928
929        Instruction::StringEnumToWasm { name, invalid } => {
930            let enum_val = js.pop();
931            js.cx.expose_string_enum(name);
932            js.push(string_enum_to_wasm(name, *invalid, &enum_val))
933        }
934
935        Instruction::OptionStringEnumToWasm {
936            name,
937            invalid,
938            hole,
939        } => {
940            let enum_val = js.pop();
941            js.cx.expose_string_enum(name);
942            let enum_val_expr = string_enum_to_wasm(name, *invalid, &enum_val);
943            js.cx.expose_is_like_none();
944
945            // e.g. isLikeNone(someEnumVal) ? 4 : (string_enum_to_wasm(someEnumVal))
946            js.push(format!(
947                "isLikeNone({enum_val}) ? {hole} : ({enum_val_expr})"
948            ))
949        }
950
951        Instruction::MemoryToString(mem) => {
952            let len = js.pop();
953            let ptr = js.pop();
954            let get = js.cx.expose_get_string_from_wasm(*mem);
955            js.push(format!("{get}({ptr}, {len})"));
956        }
957
958        Instruction::StringToMemory {
959            mem,
960            malloc,
961            realloc,
962        } => {
963            js.string_to_memory(*mem, *malloc, *realloc);
964        }
965
966        Instruction::Retptr { size } => {
967            js.cx.inject_stack_pointer_shim()?;
968            js.prelude(&format!(
969                "const retptr = wasm.__wbindgen_add_to_stack_pointer(-{size});"
970            ));
971            js.finally(&format!("wasm.__wbindgen_add_to_stack_pointer({size});"));
972            js.stack.push("retptr".to_string());
973        }
974
975        Instruction::StoreRetptr { ty, offset, mem } => {
976            let mem = js.cx.expose_dataview_memory(*mem);
977            let (method, size) = match ty {
978                AdapterType::I32 => ("setInt32", 4),
979                AdapterType::I64 => ("setBigInt64", 8),
980                AdapterType::F32 => ("setFloat32", 4),
981                AdapterType::F64 => ("setFloat64", 8),
982                other => bail!("invalid aggregate return type {other:?}"),
983            };
984            // Note that we always assume the return pointer is argument 0,
985            // which is currently the case for LLVM.
986            let val = js.pop();
987            let expr = format!(
988                "{mem}().{method}({} + {size} * {offset}, {val}, true);",
989                js.arg(0),
990            );
991            js.prelude(&expr);
992        }
993
994        Instruction::LoadRetptr { ty, offset, mem } => {
995            let mem = js.cx.expose_dataview_memory(*mem);
996            let (method, quads) = match ty {
997                AdapterType::I32 => ("getInt32", 1),
998                AdapterType::I64 => ("getBigInt64", 2),
999                AdapterType::F32 => ("getFloat32", 1),
1000                AdapterType::F64 => ("getFloat64", 2),
1001                other => bail!("invalid aggregate return type {other:?}"),
1002            };
1003            let size = quads * 4;
1004            // Separate the offset and the scaled offset, because otherwise you don't guarantee
1005            // that the variable names will be unique.
1006            let scaled_offset = offset / quads;
1007            // If we're loading from the return pointer then we must have pushed
1008            // it earlier, and we always push the same value, so load that value
1009            // here
1010            let expr = format!("{mem}().{method}(retptr + {size} * {scaled_offset}, true)");
1011            js.prelude(&format!("var r{offset} = {expr};"));
1012            js.push(format!("r{offset}"));
1013        }
1014
1015        Instruction::I32FromBool => {
1016            let val = js.pop();
1017            js.assert_bool(&val);
1018            // JS will already coerce booleans into numbers for us
1019            js.push(val);
1020        }
1021
1022        Instruction::I32FromStringFirstChar => {
1023            let val = js.pop();
1024            let i = js.tmp();
1025            js.prelude(&format!("const char{i} = {val}.codePointAt(0);"));
1026            let val = format!("char{i}");
1027            js.assert_char(&val);
1028            js.push(val);
1029        }
1030
1031        Instruction::I32FromExternrefOwned => {
1032            js.cx.expose_add_heap_object();
1033            let val = js.pop();
1034            js.push(format!("addHeapObject({val})"));
1035        }
1036
1037        Instruction::I32FromExternrefBorrow => {
1038            js.cx.expose_borrowed_objects();
1039            js.cx.expose_global_stack_pointer();
1040            let val = js.pop();
1041            js.push(format!("addBorrowedObject({val})"));
1042            js.finally("heap[stack_pointer++] = undefined;");
1043        }
1044
1045        Instruction::I32FromExternrefRustOwned { class } => {
1046            let val = js.pop();
1047            js.assert_class(&val, class);
1048            js.assert_not_moved(&val);
1049            let i = js.tmp();
1050            js.prelude(&format!("var ptr{i} = {val}.__destroy_into_raw();"));
1051            js.push(format!("ptr{i}"));
1052        }
1053
1054        Instruction::I32FromExternrefRustBorrow { class } => {
1055            let val = js.pop();
1056            js.assert_class(&val, class);
1057            js.assert_not_moved(&val);
1058            js.push(format!("{val}.__wbg_ptr"));
1059        }
1060
1061        Instruction::I32FromOptionRust { class } => {
1062            let val = js.pop();
1063            js.cx.expose_is_like_none();
1064            let i = js.tmp();
1065            js.prelude(&format!("let ptr{i} = 0;"));
1066            js.prelude(&format!("if (!isLikeNone({val})) {{"));
1067            js.assert_class(&val, class);
1068            js.assert_not_moved(&val);
1069            js.prelude(&format!("ptr{i} = {val}.__destroy_into_raw();"));
1070            js.prelude("}");
1071            js.push(format!("ptr{i}"));
1072        }
1073
1074        Instruction::I32FromOptionExternref { table_and_alloc } => {
1075            let val = js.pop();
1076            js.cx.expose_is_like_none();
1077            match table_and_alloc {
1078                Some((table, alloc)) => {
1079                    let alloc = js.cx.expose_add_to_externref_table(*table, *alloc);
1080                    js.push(format!("isLikeNone({val}) ? 0 : {alloc}({val})"));
1081                }
1082                None => {
1083                    js.cx.expose_add_heap_object();
1084                    js.push(format!("isLikeNone({val}) ? 0 : addHeapObject({val})"));
1085                }
1086            }
1087        }
1088
1089        Instruction::I32FromOptionU32Sentinel => {
1090            let val = js.pop();
1091            js.cx.expose_is_like_none();
1092            js.assert_optional_number(&val);
1093            js.push(format!("isLikeNone({val}) ? 0xFFFFFF : {val}"));
1094        }
1095
1096        Instruction::I32FromOptionBool => {
1097            let val = js.pop();
1098            js.cx.expose_is_like_none();
1099            js.assert_optional_bool(&val);
1100            js.push(format!("isLikeNone({val}) ? 0xFFFFFF : {val} ? 1 : 0"));
1101        }
1102
1103        Instruction::I32FromOptionChar => {
1104            let val = js.pop();
1105            let i = js.tmp();
1106            js.cx.expose_is_like_none();
1107            js.prelude(&format!(
1108                "const char{i} = isLikeNone({val}) ? 0xFFFFFF : {val}.codePointAt(0);"
1109            ));
1110            let val = format!("char{i}");
1111            js.cx.expose_assert_char();
1112            js.prelude(&format!(
1113                "if ({val} !== 0xFFFFFF) {{ _assertChar({val}); }}"
1114            ));
1115            js.push(val);
1116        }
1117
1118        Instruction::I32FromOptionEnum { hole } => {
1119            let val = js.pop();
1120            js.cx.expose_is_like_none();
1121            js.assert_optional_number(&val);
1122            js.push(format!("isLikeNone({val}) ? {hole} : {val}"));
1123        }
1124
1125        Instruction::F64FromOptionSentinelInt { signed } => {
1126            let val = js.pop();
1127            js.cx.expose_is_like_none();
1128            js.assert_optional_number(&val);
1129
1130            // We need to convert the given number to a 32-bit integer before
1131            // passing it to the ABI for 2 reasons:
1132            // 1. Rust's behavior for `value_f64 as i32/u32` is different from
1133            //    the WebAssembly behavior for values outside the 32-bit range.
1134            //    We could implement this behavior in Rust too, but it's easier
1135            //    to do it in JS.
1136            // 2. If we allowed values outside the 32-bit range, the sentinel
1137            //    value itself would be allowed. This would make it impossible
1138            //    to distinguish between the sentinel value and a valid value.
1139            //
1140            // To perform the actual conversion, we use JS bit shifts. Handily,
1141            // >> and >>> perform a conversion to i32 and u32 respectively
1142            // to apply the bit shift, so we can use e.g. x >>> 0 to convert to
1143            // u32.
1144
1145            let op = if *signed { ">>" } else { ">>>" };
1146            js.push(format!("isLikeNone({val}) ? 0x100000001 : ({val}) {op} 0"));
1147        }
1148        Instruction::F64FromOptionSentinelF32 => {
1149            let val = js.pop();
1150            js.cx.expose_is_like_none();
1151            js.assert_optional_number(&val);
1152
1153            // Similar to the above 32-bit integer variant, we convert the
1154            // number to a 32-bit *float* before passing it to the ABI. This
1155            // ensures consistent behavior with WebAssembly and makes it
1156            // possible to use a sentinel value.
1157
1158            js.push(format!(
1159                "isLikeNone({val}) ? 0x100000001 : Math.fround({val})"
1160            ));
1161        }
1162
1163        Instruction::FromOptionNative { ty } => {
1164            let val = js.pop();
1165            js.cx.expose_is_like_none();
1166            if *ty == ValType::I64 {
1167                js.assert_optional_bigint(&val);
1168            } else {
1169                js.assert_optional_number(&val);
1170            }
1171            js.push(format!("!isLikeNone({val})"));
1172            js.push(format!(
1173                "isLikeNone({val}) ? {zero} : {val}",
1174                zero = if *ty == ValType::I64 {
1175                    // We can't use bigint literals for now. See:
1176                    // https://github.com/wasm-bindgen/wasm-bindgen/issues/4246
1177                    "BigInt(0)"
1178                } else {
1179                    "0"
1180                }
1181            ));
1182        }
1183
1184        Instruction::VectorToMemory { kind, malloc, mem } => {
1185            let val = js.pop();
1186            let func = js.cx.pass_to_wasm_function(kind.clone(), *mem);
1187            let malloc = js.cx.export_name_of(*malloc);
1188            let i = js.tmp();
1189            js.prelude(&format!("const ptr{i} = {func}({val}, wasm.{malloc});",));
1190            js.prelude(&format!("const len{i} = WASM_VECTOR_LEN;"));
1191            js.push(format!("ptr{i}"));
1192            js.push(format!("len{i}"));
1193        }
1194
1195        Instruction::UnwrapResult { table_and_drop } => {
1196            let take_object = if let Some((table, drop)) = *table_and_drop {
1197                js.cx
1198                    .expose_take_from_externref_table(table, drop)
1199                    .to_string()
1200            } else {
1201                js.cx.expose_take_object();
1202                "takeObject".to_string()
1203            };
1204            // is_err is popped first. The original layout was: ResultAbi {
1205            //    abi: ResultAbiUnion<T>,
1206            //    err: u32,
1207            //    is_err: u32,
1208            // }
1209            // So is_err is last to be added to the stack.
1210            let is_err = js.pop();
1211            let err = js.pop();
1212            js.prelude(&format!(
1213                "
1214                if ({is_err}) {{
1215                    throw {take_object}({err});
1216                }}
1217                ",
1218            ));
1219        }
1220
1221        Instruction::UnwrapResultString { table_and_drop } => {
1222            let take_object = if let Some((table, drop)) = *table_and_drop {
1223                js.cx
1224                    .expose_take_from_externref_table(table, drop)
1225                    .to_string()
1226            } else {
1227                js.cx.expose_take_object();
1228                "takeObject".to_string()
1229            };
1230            let is_err = js.pop();
1231            let err = js.pop();
1232            let len = js.pop();
1233            let ptr = js.pop();
1234            let i = js.tmp();
1235            js.prelude(&format!(
1236                "
1237                var ptr{i} = {ptr};
1238                var len{i} = {len};
1239                if ({is_err}) {{
1240                    ptr{i} = 0; len{i} = 0;
1241                    throw {take_object}({err});
1242                }}
1243                ",
1244            ));
1245            js.push(format!("ptr{i}"));
1246            js.push(format!("len{i}"));
1247        }
1248
1249        Instruction::OptionString {
1250            mem,
1251            malloc,
1252            realloc,
1253        } => {
1254            let func = js.cx.expose_pass_string_to_wasm(*mem);
1255            js.cx.expose_is_like_none();
1256            let i = js.tmp();
1257            let malloc = js.cx.export_name_of(*malloc);
1258            let val = js.pop();
1259            let realloc = match realloc {
1260                Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)),
1261                None => String::new(),
1262            };
1263            js.prelude(&format!(
1264                "var ptr{i} = isLikeNone({val}) ? 0 : {func}({val}, wasm.{malloc}{realloc});",
1265            ));
1266            js.prelude(&format!("var len{i} = WASM_VECTOR_LEN;"));
1267            js.push(format!("ptr{i}"));
1268            js.push(format!("len{i}"));
1269        }
1270
1271        Instruction::OptionVector { kind, mem, malloc } => {
1272            let func = js.cx.pass_to_wasm_function(kind.clone(), *mem);
1273            js.cx.expose_is_like_none();
1274            let i = js.tmp();
1275            let malloc = js.cx.export_name_of(*malloc);
1276            let val = js.pop();
1277            js.prelude(&format!(
1278                "var ptr{i} = isLikeNone({val}) ? 0 : {func}({val}, wasm.{malloc});",
1279            ));
1280            js.prelude(&format!("var len{i} = WASM_VECTOR_LEN;"));
1281            js.push(format!("ptr{i}"));
1282            js.push(format!("len{i}"));
1283        }
1284
1285        Instruction::MutableSliceToMemory { kind, malloc, mem } => {
1286            // Copy the contents of the typed array into wasm.
1287            let val = js.pop();
1288            let func = js.cx.pass_to_wasm_function(kind.clone(), *mem);
1289            let malloc = js.cx.export_name_of(*malloc);
1290            let i = js.tmp();
1291            js.prelude(&format!("var ptr{i} = {func}({val}, wasm.{malloc});",));
1292            js.prelude(&format!("var len{i} = WASM_VECTOR_LEN;"));
1293            // Then pass it the pointer and the length of where we copied it.
1294            js.push(format!("ptr{i}"));
1295            js.push(format!("len{i}"));
1296            // Then we give Wasm a reference to the original typed array, so that it can
1297            // update it with modifications made on the Wasm side before returning.
1298            js.push(val);
1299        }
1300
1301        Instruction::BoolFromI32 => {
1302            let val = js.pop();
1303            js.push(format!("{val} !== 0"));
1304        }
1305
1306        Instruction::ExternrefLoadOwned { table_and_drop } => {
1307            let take_object = if let Some((table, drop)) = *table_and_drop {
1308                js.cx
1309                    .expose_take_from_externref_table(table, drop)
1310                    .to_string()
1311            } else {
1312                js.cx.expose_take_object();
1313                "takeObject".to_string()
1314            };
1315            let val = js.pop();
1316            js.push(format!("{take_object}({val})"));
1317        }
1318
1319        Instruction::StringFromChar => {
1320            let val = js.pop();
1321            js.push(format!("String.fromCodePoint({val})"));
1322        }
1323
1324        Instruction::RustFromI32 { class } => {
1325            let val = js.pop();
1326            match constructor {
1327                Some(name) if name == class => {
1328                    let (ptr_assignment, register_data) = if js.cx.config.generate_reset_state {
1329                        (
1330                            format!(
1331                                "\
1332                                this.__wbg_ptr = {val} >>> 0;
1333                                this.__wbg_inst = __wbg_instance_id;
1334                                "
1335                            ),
1336                            format!("{{ ptr: {val} >>> 0, instance: __wbg_instance_id }}"),
1337                        )
1338                    } else {
1339                        (
1340                            format!("this.__wbg_ptr = {val} >>> 0;"),
1341                            "this.__wbg_ptr".to_string(),
1342                        )
1343                    };
1344
1345                    js.prelude(&format!(
1346                        "
1347                        {ptr_assignment}
1348                        {name}Finalization.register(this, {register_data}, this);
1349                        "
1350                    ));
1351                    js.push(String::from("this"));
1352                }
1353                Some(_) | None => {
1354                    let identifier = js.cx.require_class_wrap(class);
1355                    js.push(format!("{identifier}.__wrap({val})"));
1356                }
1357            }
1358        }
1359
1360        Instruction::OptionRustFromI32 { class } => {
1361            assert!(constructor.is_none());
1362            let val = js.pop();
1363            let identifier = js.cx.require_class_wrap(class);
1364            js.push(format!(
1365                "{val} === 0 ? undefined : {identifier}.__wrap({val})",
1366            ));
1367        }
1368
1369        Instruction::CachedStringLoad {
1370            owned,
1371            mem,
1372            free,
1373            table,
1374        } => {
1375            let len = js.pop();
1376            let ptr = js.pop();
1377            let tmp = js.tmp();
1378
1379            let get = js.cx.expose_get_cached_string_from_wasm(*mem, *table);
1380
1381            js.prelude(&format!("var v{tmp} = {get}({ptr}, {len});"));
1382
1383            if *owned {
1384                let free = js.cx.export_name_of(*free);
1385                js.prelude(&format!(
1386                    "if ({ptr} !== 0) {{ wasm.{free}({ptr}, {len}, 1); }}",
1387                ));
1388            }
1389
1390            js.push(format!("v{tmp}"));
1391        }
1392
1393        Instruction::TableGet => {
1394            let val = js.pop();
1395            js.cx.expose_get_object();
1396            js.push(format!("getObject({val})"));
1397        }
1398
1399        Instruction::Closure {
1400            adapter,
1401            nargs,
1402            mutable,
1403            dtor_if_persistent,
1404        } => {
1405            let b = js.pop();
1406            let a = js.pop();
1407            let wrapper = js.cx.export_adapter_name(*adapter);
1408
1409            // TODO: further merge the heap and stack closure handling as
1410            // they're almost identical (by nature) except for ownership
1411            // integration.
1412            if let Some(dtor) = dtor_if_persistent {
1413                let make_closure = if *mutable {
1414                    js.cx.expose_make_mut_closure();
1415                    "makeMutClosure"
1416                } else {
1417                    js.cx.expose_make_closure();
1418                    "makeClosure"
1419                };
1420
1421                let dtor = &js.cx.module.exports.get(*dtor).name;
1422
1423                js.push(format!("{make_closure}({a}, {b}, wasm.{dtor}, {wrapper})"));
1424            } else {
1425                let i = js.tmp();
1426                js.prelude(&format!("var state{i} = {{a: {a}, b: {b}}};"));
1427                let args = (0..*nargs)
1428                    .map(|i| format!("arg{i}"))
1429                    .collect::<Vec<_>>()
1430                    .join(", ");
1431                if *mutable {
1432                    // Mutable closures need protection against being called
1433                    // recursively, so ensure that we clear out one of the
1434                    // internal pointers while it's being invoked.
1435                    js.prelude(&format!(
1436                        "var cb{i} = ({args}) => {{
1437                            const a = state{i}.a;
1438                            state{i}.a = 0;
1439                            try {{
1440                                return {wrapper}(a, state{i}.b, {args});
1441                            }} finally {{
1442                                state{i}.a = a;
1443                            }}
1444                        }};",
1445                    ));
1446                } else {
1447                    js.prelude(&format!(
1448                        "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});",
1449                    ));
1450                }
1451
1452                // Make sure to null out our internal pointers when we return
1453                // back to Rust to ensure that any lingering references to the
1454                // closure will fail immediately due to null pointers passed in
1455                // to Rust.
1456                js.finally(&format!("state{i}.a = state{i}.b = 0;"));
1457                js.push(format!("cb{i}"));
1458            }
1459        }
1460
1461        Instruction::VectorLoad { kind, mem, free } => {
1462            let len = js.pop();
1463            let ptr = js.pop();
1464            let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem);
1465            let i = js.tmp();
1466            let free = js.cx.export_name_of(*free);
1467            js.prelude(&format!("var v{i} = {f}({ptr}, {len}).slice();"));
1468            js.prelude(&format!(
1469                "wasm.{free}({ptr}, {len} * {size}, {size});",
1470                size = kind.size()
1471            ));
1472            js.push(format!("v{i}"))
1473        }
1474
1475        Instruction::OptionVectorLoad { kind, mem, free } => {
1476            let len = js.pop();
1477            let ptr = js.pop();
1478            let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem);
1479            let i = js.tmp();
1480            let free = js.cx.export_name_of(*free);
1481            js.prelude(&format!("let v{i};"));
1482            js.prelude(&format!("if ({ptr} !== 0) {{"));
1483            js.prelude(&format!("v{i} = {f}({ptr}, {len}).slice();"));
1484            js.prelude(&format!(
1485                "wasm.{free}({ptr}, {len} * {size}, {size});",
1486                size = kind.size()
1487            ));
1488            js.prelude("}");
1489            js.push(format!("v{i}"));
1490        }
1491
1492        Instruction::View { kind, mem } => {
1493            let len = js.pop();
1494            let ptr = js.pop();
1495            let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem);
1496            js.push(format!("{f}({ptr}, {len})"));
1497        }
1498
1499        Instruction::OptionView { kind, mem } => {
1500            let len = js.pop();
1501            let ptr = js.pop();
1502            let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem);
1503            js.push(format!("{ptr} === 0 ? undefined : {f}({ptr}, {len})"));
1504        }
1505
1506        Instruction::OptionF64Sentinel => {
1507            let val = js.pop();
1508            js.push(format!("{val} === 0x100000001 ? undefined : {val}"));
1509        }
1510
1511        Instruction::OptionU32Sentinel => {
1512            let val = js.pop();
1513            js.push(format!("{val} === 0xFFFFFF ? undefined : {val}"));
1514        }
1515
1516        Instruction::ToOptionNative { ty, signed } => {
1517            let val = js.pop();
1518            let present = js.pop();
1519            js.push(format!(
1520                "{present} === 0 ? undefined : {}",
1521                if *signed {
1522                    val
1523                } else {
1524                    match ty {
1525                        ValType::I32 => format!("{val} >>> 0"),
1526                        ValType::I64 => format!("BigInt.asUintN(64, {val})"),
1527                        _ => unreachable!("unsigned non-integer"),
1528                    }
1529                },
1530            ));
1531        }
1532
1533        Instruction::OptionBoolFromI32 => {
1534            let val = js.pop();
1535            js.push(format!("{val} === 0xFFFFFF ? undefined : {val} !== 0"));
1536        }
1537
1538        Instruction::OptionCharFromI32 => {
1539            let val = js.pop();
1540            js.push(format!(
1541                "{val} === 0xFFFFFF ? undefined : String.fromCodePoint({val})",
1542            ));
1543        }
1544
1545        Instruction::OptionEnumFromI32 { hole } => {
1546            let val = js.pop();
1547            js.push(format!("{val} === {hole} ? undefined : {val}"));
1548        }
1549
1550        Instruction::I32FromNonNull => {
1551            let val = js.pop();
1552            js.assert_non_null(&val);
1553            js.push(val);
1554        }
1555
1556        Instruction::I32FromOptionNonNull => {
1557            let val = js.pop();
1558            js.cx.expose_is_like_none();
1559            js.assert_optional_number(&val);
1560            js.push(format!("isLikeNone({val}) ? 0 : {val}"));
1561        }
1562
1563        Instruction::OptionNonNullFromI32 => {
1564            let val = js.pop();
1565            js.push(format!("{val} === 0 ? undefined : {val} >>> 0"));
1566        }
1567    }
1568    Ok(())
1569}
1570
1571enum Invocation {
1572    Core { id: walrus::FunctionId, defer: bool },
1573    Adapter(AdapterId),
1574}
1575
1576impl Invocation {
1577    fn from(instr: &Instruction, module: &Module) -> Invocation {
1578        use Instruction::*;
1579        match instr {
1580            DeferFree { free, .. } => Invocation::Core {
1581                id: *free,
1582                defer: true,
1583            },
1584
1585            CallExport(e) => match module.exports.get(*e).item {
1586                walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false },
1587                _ => panic!("can only call exported function"),
1588            },
1589
1590            CallAdapter(id) => Invocation::Adapter(*id),
1591
1592            // this function is only called for the above instructions
1593            _ => unreachable!(),
1594        }
1595    }
1596
1597    fn params_results(&self, cx: &Context) -> (usize, usize) {
1598        match self {
1599            Invocation::Core { id, .. } => {
1600                let ty = cx.module.funcs.get(*id).ty();
1601                let ty = cx.module.types.get(ty);
1602                (ty.params().len(), ty.results().len())
1603            }
1604            Invocation::Adapter(id) => {
1605                let adapter = &cx.wit.adapters[id];
1606                (adapter.params.len(), adapter.results.len())
1607            }
1608        }
1609    }
1610
1611    fn invoke(
1612        &self,
1613        cx: &mut Context,
1614        args: &[String],
1615        prelude: &mut String,
1616        log_error: &mut bool,
1617    ) -> Result<String, Error> {
1618        match self {
1619            Invocation::Core { id, .. } => {
1620                let name = cx.export_name_of(*id);
1621                Ok(format!("wasm.{name}({})", args.join(", ")))
1622            }
1623            Invocation::Adapter(id) => {
1624                let adapter = &cx.wit.adapters[id];
1625                let kind = match adapter.kind {
1626                    AdapterKind::Import { kind, .. } => kind,
1627                    AdapterKind::Local { .. } => {
1628                        bail!("adapter-to-adapter calls not supported yet");
1629                    }
1630                };
1631                let import = &cx.aux.import_map[id];
1632                let variadic = cx.aux.imports_with_variadic.contains(id);
1633                if cx.import_never_log_error(import) {
1634                    *log_error = false;
1635                }
1636                cx.invoke_import(import, kind, args, variadic, prelude)
1637            }
1638        }
1639    }
1640
1641    fn defer(&self) -> bool {
1642        match self {
1643            Invocation::Core { defer, .. } => *defer,
1644            _ => false,
1645        }
1646    }
1647}
1648
1649#[derive(Debug, Clone, Copy)]
1650enum TypePosition {
1651    Argument,
1652    Return,
1653}
1654
1655fn adapter2ts(
1656    ty: &AdapterType,
1657    position: TypePosition,
1658    dst: &mut String,
1659    refs: Option<&mut HashSet<TsReference>>,
1660) {
1661    match ty {
1662        AdapterType::I32
1663        | AdapterType::S8
1664        | AdapterType::S16
1665        | AdapterType::S32
1666        | AdapterType::U8
1667        | AdapterType::U16
1668        | AdapterType::U32
1669        | AdapterType::F32
1670        | AdapterType::F64
1671        | AdapterType::NonNull => dst.push_str("number"),
1672        AdapterType::I64
1673        | AdapterType::S64
1674        | AdapterType::U64
1675        | AdapterType::S128
1676        | AdapterType::U128 => dst.push_str("bigint"),
1677        AdapterType::String => dst.push_str("string"),
1678        AdapterType::Externref => dst.push_str("any"),
1679        AdapterType::Bool => dst.push_str("boolean"),
1680        AdapterType::Vector(kind) => dst.push_str(&kind.js_ty()),
1681        AdapterType::Option(ty) => {
1682            adapter2ts(ty, position, dst, refs);
1683            dst.push_str(match position {
1684                TypePosition::Argument => " | null | undefined",
1685                TypePosition::Return => " | undefined",
1686            });
1687        }
1688        AdapterType::NamedExternref(name) => dst.push_str(name),
1689        AdapterType::Struct(name) => dst.push_str(name),
1690        AdapterType::Enum(name) => dst.push_str(name),
1691        AdapterType::StringEnum(name) => {
1692            if let Some(refs) = refs {
1693                refs.insert(TsReference::StringEnum(name.clone()));
1694            }
1695
1696            dst.push_str(name);
1697        }
1698        AdapterType::Function => dst.push_str("any"),
1699    }
1700}