Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
// to match Clang's C ABI), bridging the two internally. So the entry
Comment thread
guybedford marked this conversation as resolved.
Outdated
// 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::<Vec<_>>(),
&symbols
.iter()
.map(|sym| {
if sym.name == entry_name {
"_main".to_owned()
} else {
"_".to_owned() + &sym.name
}
})
.collect::<Vec<_>>(),
)
.unwrap();
debug!("{encoded}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ pub(crate) fn target() -> Target {
crt_static_default: true,
crt_static_allows_dylibs: true,
main_needs_argc_argv: true,
// Emit the entry point under the wasm C-ABI name that Clang uses
// (`__main_argc_argv` for the argc/argv form) rather than a raw `main`.
// This is what emscripten's crt/libc references, and is required for
// entry paths such as `-sPROXY_TO_PTHREAD` whose `crt1_proxy_main`
// links against `__main_argc_argv`. A raw `main` only resolves on the
// JS-driven default entry path and fails to link on the proxied one.
entry_name: "__main_argc_argv".into(),
panic_strategy: PanicStrategy::Unwind,
no_default_libraries: false,
families: cvs!["unix", "wasm"],
Expand Down
1 change: 1 addition & 0 deletions tests/run-make/wasm-emscripten-main-symbol/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
45 changes: 45 additions & 0 deletions tests/run-make/wasm-emscripten-main-symbol/rmake.rs
Original file line number Diff line number Diff line change
@@ -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");
}
Loading