1use anyhow::{bail, Context, Error};
2use std::collections::{BTreeMap, HashMap, HashSet};
3use std::env;
4use std::fs;
5use std::mem;
6use std::path::{Path, PathBuf};
7use std::str;
8use walrus::Module;
9
10pub(crate) const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
11
12mod decode;
13mod descriptor;
14mod descriptors;
15mod externref;
16mod interpreter;
17mod intrinsic;
18mod js;
19mod multivalue;
20mod transforms;
21pub mod wasm2es6js;
22mod wasm_conventions;
23mod wit;
24
25pub struct Bindgen {
26 input: Input,
27 out_name: Option<String>,
28 mode: OutputMode,
29 debug: bool,
30 typescript: bool,
31 omit_imports: bool,
32 demangle: bool,
33 keep_lld_exports: bool,
34 keep_debug: bool,
35 remove_name_section: bool,
36 remove_producers_section: bool,
37 omit_default_module_path: bool,
38 emit_start: bool,
39 externref: bool,
40 multi_value: bool,
41 encode_into: EncodeInto,
42 split_linked_modules: bool,
43 symbol_dispose: bool,
44 generate_reset_state: bool,
45}
46
47pub struct Output {
48 module: walrus::Module,
49 stem: String,
50 generated: Generated,
51}
52
53struct Generated {
54 mode: OutputMode,
55 js: String,
56 ts: String,
57 start: Option<String>,
58 snippets: HashMap<String, Vec<String>>,
59 local_modules: HashMap<String, String>,
60 npm_dependencies: HashMap<String, (PathBuf, String)>,
61 typescript: bool,
62}
63
64#[derive(Clone)]
65enum OutputMode {
66 Bundler { browser_only: bool },
67 Web,
68 NoModules { global: String },
69 Node { module: bool },
70 Deno,
71 Module,
72}
73
74enum Input {
75 Path(PathBuf),
76 Module(Module, String),
77 Bytes(Vec<u8>, String),
78 None,
79}
80
81#[derive(Debug, Clone, Copy)]
82pub enum EncodeInto {
83 Test,
84 Always,
85 Never,
86}
87
88impl Bindgen {
89 pub fn new() -> Bindgen {
90 let externref =
91 env::var("WASM_BINDGEN_ANYREF").is_ok() || env::var("WASM_BINDGEN_EXTERNREF").is_ok();
92 let multi_value = env::var("WASM_BINDGEN_MULTI_VALUE").is_ok();
93 let symbol_dispose = env::var("WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE").is_ok();
94 Bindgen {
95 input: Input::None,
96 out_name: None,
97 mode: OutputMode::Bundler {
98 browser_only: false,
99 },
100 debug: false,
101 typescript: false,
102 omit_imports: false,
103 demangle: true,
104 keep_lld_exports: false,
105 keep_debug: false,
106 remove_name_section: false,
107 remove_producers_section: false,
108 emit_start: true,
109 externref,
110 multi_value,
111 encode_into: EncodeInto::Test,
112 omit_default_module_path: true,
113 split_linked_modules: false,
114 symbol_dispose,
115 generate_reset_state: false,
116 }
117 }
118
119 pub fn input_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Bindgen {
120 self.input = Input::Path(path.as_ref().to_path_buf());
121 self
122 }
123
124 pub fn out_name(&mut self, name: &str) -> &mut Bindgen {
125 self.out_name = Some(name.to_string());
126 self
127 }
128
129 #[deprecated = "automatically detected via `-Ctarget-feature=+reference-types`"]
130 pub fn reference_types(&mut self, enable: bool) -> &mut Bindgen {
131 self.externref = enable;
132 self
133 }
134
135 pub fn input_module(&mut self, name: &str, module: Module) -> &mut Bindgen {
137 let name = name.to_string();
138 self.input = Input::Module(module, name);
139 self
140 }
141
142 pub fn input_bytes(&mut self, name: &str, bytes: Vec<u8>) -> &mut Bindgen {
144 let name = name.to_string();
145 self.input = Input::Bytes(bytes, name);
146 self
147 }
148
149 fn switch_mode(&mut self, mode: OutputMode, flag: &str) -> Result<(), Error> {
150 match self.mode {
151 OutputMode::Bundler { .. } => self.mode = mode,
152 _ => bail!(
153 "cannot specify `{}` with another output mode already specified",
154 flag
155 ),
156 }
157 Ok(())
158 }
159
160 pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
161 if node {
162 self.switch_mode(OutputMode::Node { module: false }, "--target nodejs")?;
163 }
164 Ok(self)
165 }
166
167 pub fn nodejs_module(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
168 if node {
169 self.switch_mode(
170 OutputMode::Node { module: true },
171 "--target experimental-nodejs-module",
172 )?;
173 }
174 Ok(self)
175 }
176
177 pub fn bundler(&mut self, bundler: bool) -> Result<&mut Bindgen, Error> {
178 if bundler {
179 self.switch_mode(
180 OutputMode::Bundler {
181 browser_only: false,
182 },
183 "--target bundler",
184 )?;
185 }
186 Ok(self)
187 }
188
189 pub fn web(&mut self, web: bool) -> Result<&mut Bindgen, Error> {
190 if web {
191 self.switch_mode(OutputMode::Web, "--target web")?;
192 }
193 Ok(self)
194 }
195
196 pub fn no_modules(&mut self, no_modules: bool) -> Result<&mut Bindgen, Error> {
197 if no_modules {
198 self.switch_mode(
199 OutputMode::NoModules {
200 global: "wasm_bindgen".to_string(),
201 },
202 "--target no-modules",
203 )?;
204 }
205 Ok(self)
206 }
207
208 pub fn browser(&mut self, browser: bool) -> Result<&mut Bindgen, Error> {
209 if browser {
210 match &mut self.mode {
211 OutputMode::Bundler { browser_only } => *browser_only = true,
212 _ => bail!("cannot specify `--browser` with other output types"),
213 }
214 }
215 Ok(self)
216 }
217
218 pub fn deno(&mut self, deno: bool) -> Result<&mut Bindgen, Error> {
219 if deno {
220 self.switch_mode(OutputMode::Deno, "--target deno")?;
221 self.encode_into(EncodeInto::Always);
222 }
223 Ok(self)
224 }
225
226 pub fn module(&mut self, source_phase: bool) -> Result<&mut Bindgen, Error> {
227 if source_phase {
228 self.switch_mode(OutputMode::Module, "--target module")?;
229 }
230 Ok(self)
231 }
232
233 pub fn no_modules_global(&mut self, name: &str) -> Result<&mut Bindgen, Error> {
234 match &mut self.mode {
235 OutputMode::NoModules { global } => *global = name.to_string(),
236 _ => bail!("can only specify `--no-modules-global` with `--target no-modules`"),
237 }
238 Ok(self)
239 }
240
241 pub fn debug(&mut self, debug: bool) -> &mut Bindgen {
242 self.debug = debug;
243 self
244 }
245
246 pub fn typescript(&mut self, typescript: bool) -> &mut Bindgen {
247 self.typescript = typescript;
248 self
249 }
250
251 pub fn omit_imports(&mut self, omit_imports: bool) -> &mut Bindgen {
252 self.omit_imports = omit_imports;
253 self
254 }
255
256 pub fn demangle(&mut self, demangle: bool) -> &mut Bindgen {
257 self.demangle = demangle;
258 self
259 }
260
261 pub fn keep_lld_exports(&mut self, keep_lld_exports: bool) -> &mut Bindgen {
262 self.keep_lld_exports = keep_lld_exports;
263 self
264 }
265
266 pub fn keep_debug(&mut self, keep_debug: bool) -> &mut Bindgen {
267 self.keep_debug = keep_debug;
268 self
269 }
270
271 pub fn remove_name_section(&mut self, remove: bool) -> &mut Bindgen {
272 self.remove_name_section = remove;
273 self
274 }
275
276 pub fn remove_producers_section(&mut self, remove: bool) -> &mut Bindgen {
277 self.remove_producers_section = remove;
278 self
279 }
280
281 pub fn emit_start(&mut self, emit: bool) -> &mut Bindgen {
282 self.emit_start = emit;
283 self
284 }
285
286 pub fn encode_into(&mut self, mode: EncodeInto) -> &mut Bindgen {
287 self.encode_into = mode;
288 self
289 }
290
291 pub fn omit_default_module_path(&mut self, omit_default_module_path: bool) -> &mut Bindgen {
292 self.omit_default_module_path = omit_default_module_path;
293 self
294 }
295
296 pub fn split_linked_modules(&mut self, split_linked_modules: bool) -> &mut Bindgen {
297 self.split_linked_modules = split_linked_modules;
298 self
299 }
300
301 pub fn reset_state_function(&mut self, generate_reset_state: bool) -> &mut Bindgen {
302 self.generate_reset_state = generate_reset_state;
303 self
304 }
305
306 pub fn generate<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
307 self.generate_output()?.emit(path.as_ref())
308 }
309
310 pub fn stem(&self) -> Result<&str, Error> {
311 Ok(match &self.input {
312 Input::None => bail!("must have an input by now"),
313 Input::Module(_, name) | Input::Bytes(_, name) => name,
314 Input::Path(path) => match &self.out_name {
315 Some(name) => name,
316 None => path.file_stem().unwrap().to_str().unwrap(),
317 },
318 })
319 }
320
321 pub fn generate_output(&mut self) -> Result<Output, Error> {
322 let mut module = match self.input {
323 Input::None => bail!("must have an input by now"),
324 Input::Module(ref mut m, _) => {
325 let blank_module = Module::default();
326 mem::replace(m, blank_module)
327 }
328 Input::Path(ref path) => {
329 let bytes = std::fs::read(path)
330 .with_context(|| format!("failed reading '{}'", path.display()))?;
331 self.module_from_bytes(&bytes).with_context(|| {
332 format!("failed getting Wasm module for '{}'", path.display())
333 })?
334 }
335 Input::Bytes(ref bytes, _) => self
336 .module_from_bytes(bytes)
337 .context("failed getting Wasm module")?,
338 };
339
340 if let Ok(true) = wasm_conventions::target_feature(&module, "reference-types") {
342 self.externref = true;
343 }
344
345 if let Ok(true) = wasm_conventions::target_feature(&module, "multivalue") {
347 self.multi_value = true;
348 }
349
350 if matches!(self.mode, OutputMode::Web)
352 && module.exports.iter().any(|export| export.name == "default")
353 {
354 bail!("exported symbol \"default\" not allowed for --target web")
355 }
356
357 if self.generate_reset_state && !matches!(self.mode, OutputMode::Module) {
359 bail!("--experimental-reset-state-function is only supported for --target module")
360 }
361
362 let thread_count = transforms::threads::run(&mut module)
363 .with_context(|| "failed to prepare module for threading")?;
364
365 if self.demangle {
368 demangle(&mut module);
369 }
370 if !self.keep_lld_exports {
371 unexported_unused_lld_things(&mut module);
372 }
373
374 module
376 .producers
377 .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
378
379 let mut storage = Vec::new();
388 let programs = wit::extract_programs(&mut module, &mut storage)?;
389
390 descriptors::execute(&mut module)?;
395
396 wit::process(self, &mut module, programs, thread_count)?;
402
403 if self.externref {
413 externref::process(&mut module)?;
414 } else {
415 let ids = module
416 .exports
417 .iter()
418 .filter(|e| e.name.starts_with("__externref"))
419 .map(|e| e.id())
420 .collect::<Vec<_>>();
421 for id in ids {
422 module.exports.delete(id);
423 }
424 externref::force_contiguous_elements(&mut module)?;
429 }
430
431 if self.multi_value {
434 multivalue::run(&mut module)
435 .context("failed to transform return pointers into multi-value Wasm")?;
436 }
437
438 gc_module_and_adapters(&mut module);
442
443 let stem = self.stem()?;
444
445 let aux = module
447 .customs
448 .delete_typed::<wit::WasmBindgenAux>()
449 .expect("aux section should be present");
450 let adapters = module
451 .customs
452 .delete_typed::<wit::NonstandardWitSection>()
453 .unwrap();
454 let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?;
455 cx.generate()?;
456 let (js, ts, start) = cx.finalize(stem)?;
457 let generated = Generated {
458 snippets: aux.snippets.clone(),
459 local_modules: aux.local_modules.clone(),
460 mode: self.mode.clone(),
461 typescript: self.typescript,
462 npm_dependencies: cx.npm_dependencies.clone(),
463 js,
464 ts,
465 start,
466 };
467
468 Ok(Output {
469 module,
470 stem: stem.to_string(),
471 generated,
472 })
473 }
474
475 fn module_from_bytes(&self, bytes: &[u8]) -> Result<Module, Error> {
476 walrus::ModuleConfig::new()
477 .strict_validate(false)
484 .generate_dwarf(self.keep_debug)
485 .generate_name_section(!self.remove_name_section)
486 .generate_producers_section(!self.remove_producers_section)
487 .parse(bytes)
488 .context("failed to parse input as wasm")
489 }
490
491 fn local_module_name(&self, module: &str) -> String {
492 format!("./snippets/{module}")
493 }
494
495 fn inline_js_module_name(
496 &self,
497 unique_crate_identifier: &str,
498 snippet_idx_in_crate: usize,
499 ) -> String {
500 format!("./snippets/{unique_crate_identifier}/inline{snippet_idx_in_crate}.js",)
501 }
502}
503
504fn reset_indentation(s: &str) -> String {
505 let mut indent: u32 = 0;
506 let mut dst = String::new();
507
508 fn is_doc_comment(line: &str) -> bool {
509 line.starts_with("*")
510 }
511
512 for line in s.lines() {
513 let line = line.trim();
514
515 if is_doc_comment(line) {
517 for _ in 0..indent {
518 dst.push_str(" ");
519 }
520 dst.push(' ');
521 dst.push_str(line);
522 dst.push('\n');
523 continue;
524 }
525
526 if line.starts_with('}') {
527 indent = indent.saturating_sub(1);
528 }
529
530 let extra = if line.starts_with(':') || line.starts_with('?') {
531 1
532 } else {
533 0
534 };
535 if !line.is_empty() {
536 for _ in 0..indent + extra {
537 dst.push_str(" ");
538 }
539 dst.push_str(line);
540 }
541 dst.push('\n');
542
543 if line.ends_with('{') {
544 indent += 1;
545 }
546 }
547 dst
548}
549
550fn demangle(module: &mut Module) {
551 for func in module.funcs.iter_mut() {
552 let name = match &func.name {
553 Some(name) => name,
554 None => continue,
555 };
556 if let Ok(sym) = rustc_demangle::try_demangle(name) {
557 func.name = Some(sym.to_string());
558 }
559 }
560}
561
562impl OutputMode {
563 fn uses_es_modules(&self) -> bool {
564 matches!(
565 self,
566 OutputMode::Bundler { .. }
567 | OutputMode::Web
568 | OutputMode::Node { module: true }
569 | OutputMode::Deno
570 )
571 }
572
573 fn nodejs(&self) -> bool {
574 matches!(self, OutputMode::Node { .. })
575 }
576
577 fn no_modules(&self) -> bool {
578 matches!(self, OutputMode::NoModules { .. })
579 }
580
581 fn esm_integration(&self) -> bool {
582 matches!(
583 self,
584 OutputMode::Bundler { .. } | OutputMode::Node { module: true }
585 )
586 }
587}
588
589fn unexported_unused_lld_things(module: &mut Module) {
593 let mut to_remove = Vec::new();
594 for export in module.exports.iter() {
595 match export.name.as_str() {
596 "__heap_base" | "__data_end" | "__indirect_function_table" => {
597 to_remove.push(export.id());
598 }
599 _ => {}
600 }
601 }
602 for id in to_remove {
603 module.exports.delete(id);
604 }
605}
606
607impl Output {
608 pub fn js(&self) -> &str {
609 &self.generated.js
610 }
611
612 pub fn ts(&self) -> Option<&str> {
613 if self.generated.typescript {
614 Some(&self.generated.ts)
615 } else {
616 None
617 }
618 }
619
620 pub fn start(&self) -> Option<&String> {
621 self.generated.start.as_ref()
622 }
623
624 pub fn snippets(&self) -> &HashMap<String, Vec<String>> {
625 &self.generated.snippets
626 }
627
628 pub fn local_modules(&self) -> &HashMap<String, String> {
629 &self.generated.local_modules
630 }
631
632 pub fn npm_dependencies(&self) -> &HashMap<String, (PathBuf, String)> {
633 &self.generated.npm_dependencies
634 }
635
636 pub fn wasm(&self) -> &walrus::Module {
637 &self.module
638 }
639
640 pub fn wasm_mut(&mut self) -> &mut walrus::Module {
641 &mut self.module
642 }
643
644 pub fn emit(&mut self, out_dir: impl AsRef<Path>) -> Result<(), Error> {
645 self._emit(out_dir.as_ref())
646 }
647
648 fn _emit(&mut self, out_dir: &Path) -> Result<(), Error> {
649 let wasm_name = format!("{}_bg", self.stem);
650 let wasm_path = out_dir.join(&wasm_name).with_extension("wasm");
651 fs::create_dir_all(out_dir)?;
652 let wasm_bytes = self.module.emit_wasm();
653 fs::write(&wasm_path, wasm_bytes)
654 .with_context(|| format!("failed to write `{}`", wasm_path.display()))?;
655
656 let gen = &self.generated;
657
658 for (identifier, list) in gen.snippets.iter() {
661 for (i, js) in list.iter().enumerate() {
662 let name = format!("inline{i}.js");
663 let path = out_dir.join("snippets").join(identifier).join(name);
664 fs::create_dir_all(path.parent().unwrap())?;
665 fs::write(&path, js)
666 .with_context(|| format!("failed to write `{}`", path.display()))?;
667 }
668 }
669
670 for (path, contents) in gen.local_modules.iter() {
671 let path = out_dir.join("snippets").join(path);
672 fs::create_dir_all(path.parent().unwrap())?;
673 fs::write(&path, contents)
674 .with_context(|| format!("failed to write `{}`", path.display()))?;
675 }
676
677 let is_genmode_nodemodule = matches!(gen.mode, OutputMode::Node { module: true });
678 if !gen.npm_dependencies.is_empty() || is_genmode_nodemodule {
679 #[derive(serde::Serialize)]
680 struct PackageJson<'a> {
681 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
682 ty: Option<&'static str>,
683 dependencies: BTreeMap<&'a str, &'a str>,
684 }
685 let pj = PackageJson {
686 ty: is_genmode_nodemodule.then_some("module"),
687 dependencies: gen
688 .npm_dependencies
689 .iter()
690 .map(|(k, v)| (k.as_str(), v.1.as_str()))
691 .collect(),
692 };
693 let json = serde_json::to_string_pretty(&pj)?;
694 fs::write(out_dir.join("package.json"), json)?;
695 }
696
697 let extension = "js";
700
701 fn write<P, C>(path: P, contents: C) -> Result<(), anyhow::Error>
702 where
703 P: AsRef<Path>,
704 C: AsRef<[u8]>,
705 {
706 fs::write(&path, contents)
707 .with_context(|| format!("failed to write `{}`", path.as_ref().display()))
708 }
709
710 let js_path = out_dir.join(&self.stem).with_extension(extension);
711
712 if matches!(&gen.mode, OutputMode::Module) {
713 let wasm_name = format!("{}_bg", self.stem);
714 let start = gen.start.as_deref().unwrap_or("");
715
716 write(
717 &js_path,
718 format!(
719 "\
720import source wasmModule from \"./{wasm_name}.wasm\";
721
722{start}{}",
723 reset_indentation(&gen.js)
724 ),
725 )?;
726 } else if gen.mode.esm_integration() {
727 let js_name = format!("{}_bg.{}", self.stem, extension);
728
729 let start = gen.start.as_deref().unwrap_or("");
730
731 if matches!(gen.mode, OutputMode::Node { .. }) {
732 write(
733 &js_path,
734 format!(
735 "\
736{start}
737export * from \"./{js_name}\";",
738 ),
739 )?;
740 } else {
741 write(
742 &js_path,
743 format!(
744 "\
745import * as wasm from \"./{wasm_name}.wasm\";
746export * from \"./{js_name}\";
747{start}"
748 ),
749 )?;
750 }
751 write(out_dir.join(&js_name), reset_indentation(&gen.js))?;
752 } else {
753 write(&js_path, reset_indentation(&gen.js))?;
754 }
755
756 if gen.typescript {
757 let ts_path = js_path.with_extension("d.ts");
758 fs::write(&ts_path, &gen.ts)
759 .with_context(|| format!("failed to write `{}`", ts_path.display()))?;
760 }
761
762 if gen.typescript {
763 let ts_path = wasm_path.with_extension("wasm.d.ts");
764 let ts = wasm2es6js::typescript(&self.module)?;
765 fs::write(&ts_path, ts)
766 .with_context(|| format!("failed to write `{}`", ts_path.display()))?;
767 }
768
769 Ok(())
770 }
771}
772
773fn gc_module_and_adapters(module: &mut Module) {
774 loop {
775 walrus::passes::gc::run(module);
779
780 let imports_remaining = module
783 .imports
784 .iter()
785 .map(|i| i.id())
786 .collect::<HashSet<_>>();
787 let mut section = module
788 .customs
789 .delete_typed::<wit::NonstandardWitSection>()
790 .unwrap();
791 section
792 .implements
793 .retain(|pair| imports_remaining.contains(&pair.0));
794
795 let any_removed = section.gc();
801 module.customs.add(*section);
802 if !any_removed {
803 break;
804 }
805 }
806}
807
808fn sorted_iter<K, V>(map: &HashMap<K, V>) -> impl Iterator<Item = (&K, &V)>
815where
816 K: Ord,
817{
818 let mut pairs = map.iter().collect::<Vec<_>>();
819 pairs.sort_by_key(|(k, _)| *k);
820 pairs.into_iter()
821}