diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 8c20db5791774..e24e7bad2b945 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -1286,9 +1286,24 @@ impl<'a> Linker for EmLinker<'a> { self.cc_arg("-s"); + // Emscripten exposes the program entry point under the JS name `_main` + // regardless of the underlying wasm symbol (which is `__main_argc_argv` + // per the wasm C ABI in the tool-conventions BasicCABI spec), bridging + // the two internally. So the entry symbol must be requested as `_main` + // here rather than as a `_`-prefixed form of its wasm name. + let entry_name = self.sess.target.entry_name.as_ref(); let mut arg = OsString::from("EXPORTED_FUNCTIONS="); let encoded = serde_json::to_string( - &symbols.iter().map(|sym| "_".to_owned() + &sym.name).collect::>(), + &symbols + .iter() + .map(|sym| { + if sym.name == entry_name { + "_main".to_owned() + } else { + "_".to_owned() + &sym.name + } + }) + .collect::>(), ) .unwrap(); debug!("{encoded}"); diff --git a/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs b/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs index 2104286ec8684..bda2681605814 100644 --- a/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs +++ b/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs @@ -29,6 +29,11 @@ pub(crate) fn target() -> Target { crt_static_default: true, crt_static_allows_dylibs: true, main_needs_argc_argv: true, + // Use the wasm C-ABI entry name from the tool-conventions BasicCABI + // spec rather than a raw `main`, as referenced by emscripten's crt/libc. + // Required for entry paths like `-sPROXY_TO_PTHREAD`, whose + // `crt1_proxy_main` links against `__main_argc_argv`. + entry_name: "__main_argc_argv".into(), panic_strategy: PanicStrategy::Unwind, no_default_libraries: false, families: cvs!["unix", "wasm"], diff --git a/tests/run-make/wasm-emscripten-main-symbol/main.rs b/tests/run-make/wasm-emscripten-main-symbol/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/wasm-emscripten-main-symbol/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/wasm-emscripten-main-symbol/rmake.rs b/tests/run-make/wasm-emscripten-main-symbol/rmake.rs new file mode 100644 index 0000000000000..03bbdd0bcb62b --- /dev/null +++ b/tests/run-make/wasm-emscripten-main-symbol/rmake.rs @@ -0,0 +1,45 @@ +//@ only-wasm32-unknown-emscripten + +// On wasm the age-old C trick of a `main` that is either `int main(void)` or +// `int main(int, char**)` doesn't work, because wasm requires caller and callee +// signatures to match. The platform ABI (as used by Clang) therefore mangles +// the entry point by signature; the argc/argv form is emitted as +// `__main_argc_argv`. Rust's emscripten target uses the argc/argv form, so its +// entry point must be emitted under that name rather than as a raw `main`. +// +// This matters beyond cosmetics: emscripten's own crt references +// `__main_argc_argv` directly on some entry paths (notably `crt1_proxy_main.o`, +// used by `-sPROXY_TO_PTHREAD`), which fail to link against a raw `main`. +// +// The JS-visible name of the entry point stays `_main`; emscripten bridges that +// to the wasm `__main_argc_argv` export internally. + +use run_make_support::{rfs, rustc, wasmparser}; +use wasmparser::ExternalKind; + +fn main() { + rustc().input("main.rs").target("wasm32-unknown-emscripten").output("main.js").run(); + + let file = rfs::read("main.wasm"); + let mut entry = None; + let mut has_plain_main = false; + for payload in wasmparser::Parser::new(0).parse_all(&file) { + if let wasmparser::Payload::ExportSection(s) = payload.unwrap() { + for export in s { + let export = export.unwrap(); + match export.name { + "__main_argc_argv" => entry = Some(export.kind), + "main" => has_plain_main = true, + _ => {} + } + } + } + } + + assert_eq!( + entry, + Some(ExternalKind::Func), + "the emscripten entry point must be exported as the wasm C-ABI symbol `__main_argc_argv`", + ); + assert!(!has_plain_main, "a raw `main` symbol must not be exported on wasm"); +}