Skip to main content

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