diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index 313421795c..2649c6991b 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -5475,6 +5475,16 @@ pub fn run_with_parse_cache( if is_dylib_windows { // MSVC link.exe takes the output path as `/OUT:`, not `-o`. cmd.arg(format!("/OUT:{}", exe_path.display())); + // Pull in the MSVC static C runtime (libcmt) so the CRT + // auto-generated DllMain + `_fltused` etc. resolve. Without + // this, `LoadLibraryW` of the plugin DLL returns + // `ERROR_DLL_INIT_FAILED` (Win32 error 1114) because the + // plugin's auto-emitted `DllMain` references unresolved + // CRT symbols. `/FORCE:UNRESOLVED` lets the link succeed + // with those still-unresolved entries, but the loader + // fails DLL_PROCESS_ATTACH. Linking libcmt resolves + // everything in the plugin itself. + cmd.arg("/defaultlib:libcmt"); } else { cmd.arg("-o").arg(&exe_path); } diff --git a/crates/perry/src/commands/compile/link/build_and_run.rs b/crates/perry/src/commands/compile/link/build_and_run.rs index 5604b9d7db..cf7bdb3cde 100644 --- a/crates/perry/src/commands/compile/link/build_and_run.rs +++ b/crates/perry/src/commands/compile/link/build_and_run.rs @@ -455,6 +455,77 @@ pub(crate) fn build_and_run_link( for sym in PLUGIN_HOST_SYMBOLS { let _ = writeln!(def_file, " {}", sym); } + // Enumerate every `js_*` / `perry_*` symbol from the auto-built + // runtime + stdlib .lib archives. Plugins reference these via + // `GetProcAddress` after `LoadLibraryW`, so the host must + // export them — listing only the static PLUGIN_HOST_SYMBOLS + // subset leaves hundreds of `js_gc_*`, `js_array_*`, + // `js_string_*` etc. unresolved at LoadLibrary time and the + // DLL fails to initialize (Win32 error 1114 = + // ERROR_DLL_INIT_FAILED). The `llvm-nm` enumeration + // guarantees the full surface; it costs ~5000 .def lines and + // a few hundred ms of `nm` time per link. + let mut libs: Vec = vec![runtime_lib.to_path_buf()]; + if let Some(ref s) = stdlib_lib { + libs.push(s.clone()); + } + if let Some(nm_path) = find_llvm_tool("llvm-nm") { + let mut all_syms: std::collections::BTreeSet = + std::collections::BTreeSet::new(); + for lib in &libs { + if !std::path::Path::new(lib).exists() { + continue; + } + if let Ok(out) = std::process::Command::new(&nm_path) + .args(["--extern-only", "--defined-only"]) + .arg(lib) + .output() + { + if out.status.success() { + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + // llvm-nm format: " " e.g. + // "00000000 T _js_array_alloc" + // "00000000 B PERRY_CLASS_FIELD_INLINE_GUARD_DISABLED" + // "00000000 D __imp_PERRY_CLASS_FIELD_INLINE_GUARD_DISABLED" + // We capture T (text), B (BSS), D (data), R (rodata) and + // skip the __imp_ PE-import stubs (the leading-underscore + // non-prefixed name is the actual export). + let mut parts = line.split_whitespace(); + let _addr = parts.next(); + let ty = parts.next(); + let name_field = parts.next(); + if let (Some(ty), Some(rest)) = (ty, name_field) { + if !matches!(ty, "T" | "B" | "D" | "R") { + continue; + } + let bare = rest.strip_prefix('_').unwrap_or(rest); + let export_name = + if let Some(stripped) = bare.strip_prefix("__imp_") { + stripped + } else { + bare + }; + if export_name.starts_with("js_") + || export_name.starts_with("perry_") + || export_name.starts_with("PERRY_") + { + all_syms.insert(export_name.to_string()); + } + } + } + } + } + } + for s in &all_syms { + let _ = writeln!(def_file, " {}", s); + } + eprintln!( + "[perry] plugin-host .def: {} symbols ({} base + enumerated)", + all_syms.len() + PLUGIN_HOST_SYMBOLS.len(), + PLUGIN_HOST_SYMBOLS.len() + ); + } } cmd.arg(format!("/DEF:{}", def_path.display())); } else { diff --git a/crates/perry/src/commands/compile/link/mod.rs b/crates/perry/src/commands/compile/link/mod.rs index 139d63aa1d..1433b620d2 100644 --- a/crates/perry/src/commands/compile/link/mod.rs +++ b/crates/perry/src/commands/compile/link/mod.rs @@ -93,10 +93,8 @@ pub(super) const PLUGIN_HOST_SYMBOLS: &[&str] = &[ "perry_debug_trace_init_done", "perry_init_guard_check_and_set", // Plugin manager (perry-plugin) - "perry_plugin_abi_version", "perry_plugin_load", "perry_plugin_unload", - "perry_plugin_lookup_symbol", "perry_plugin_emit_hook", "perry_plugin_emit_event", "perry_plugin_invoke_tool", @@ -112,14 +110,21 @@ pub(super) const PLUGIN_HOST_SYMBOLS: &[&str] = &[ "perry_plugin_list_plugins", "perry_plugin_list_hooks", "perry_plugin_list_tools", - "perry_plugin_plugin_count", + "perry_plugin_count", "perry_plugin_set_metadata", "perry_plugin_get_config", - "perry_plugin_subscribe_event", - "perry_plugin_unsubscribe_event", - "perry_plugin_emit_event_bus", + "perry_plugin_on", + "perry_plugin_off", + "perry_plugin_emit", + "perry_plugin_log", + "perry_plugin_set_config", + "perry_plugin_discover", "perry_plugin_init", - "perry_plugin_last_load_error", + // Process-wide class-field inline guard (perry-runtime/src/object/mod.rs). + // A `#[no_mangle] pub static`; not picked up by `llvm-nm` filter heuristics + // for every codebase, so list it explicitly so the plugin DLL can find it + // at LoadLibrary time. + "PERRY_CLASS_FIELD_INLINE_GUARD_DISABLED", ]; #[derive(Debug, Clone, PartialEq, Eq)]