1use 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
17pub struct Builder<'a, 'b> {
20 pub cx: &'a mut Context<'b>,
22 constructor: Option<String>,
25 method: Option<bool>,
28 classless_this: bool,
30 catch: bool,
33 log_error: bool,
35}
36
37pub struct JsBuilder<'a, 'b> {
40 cx: &'a mut Context<'b>,
43
44 debug_name: &'a str,
46
47 prelude: String,
50
51 pre_try: String,
53
54 finally: String,
56
57 tmp: usize,
60
61 args: Vec<String>,
64
65 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 pub might_be_optional_field: bool,
85 pub catch: bool,
86 pub log_error: bool,
87}
88
89#[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 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 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 _ => bail!("multi-value returns from adapters not supported yet"),
237 }
243 assert!(js.stack.is_empty());
244
245 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 if self.log_error {
284 js.cx.expose_log_error();
285 }
286
287 code.push_str(&call);
288 code.push('}');
289
290 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 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 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 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 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 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 ts_args.len() == 1 && omittable {
415 *might_be_optional_field = true;
416 }
417
418 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 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 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 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 if ts != "void" || ret_desc.is_some() {
537 ret.push_str(&format!("@returns {{{ts}}}"));
538 }
539 if let Some(v) = ret_desc {
541 ret.push(' ');
542 ret.push_str(v);
543 }
544 }
545 ret
546 }
547
548 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 for AuxFunctionArgumentData { name, desc, .. } in args_data.iter() {
558 ts_doc.push_str("@param ");
559 ts_doc.push_str(name);
560
561 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 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 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 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 format!("__wbindgen_enum_{name}[{index}]")
755 }
756 fn string_enum_to_wasm(name: &str, invalid: u32, enum_val: &str) -> String {
757 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 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 params -= 1;
806 }
807 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 args.push(align.to_string());
819 }
820 } else {
821 for _ in 0..params {
823 args.push(js.pop());
824 }
825 args.reverse();
826 }
827
828 let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?;
830
831 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 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 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 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 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 let scaled_offset = offset / quads;
1007 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.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 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 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 "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 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 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 js.push(format!("ptr{i}"));
1295 js.push(format!("len{i}"));
1296 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 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 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 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 _ => 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}