Skip to content

wip(events): eventemitter3 native-EventEmitter link investigation (#5140)#5172

Merged
proggeramlug merged 2 commits into
mainfrom
fix/5140-eventemitter3-link
Jun 15, 2026
Merged

wip(events): eventemitter3 native-EventEmitter link investigation (#5140)#5172
proggeramlug merged 2 commits into
mainfrom
fix/5140-eventemitter3-link

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Re #5140. Partial / WIP — not yet a complete fix, pushed to preserve the investigation.

What's done

Two of the three root causes of the eventemitter3 link failure (undefined _js_event_emitter_*) are addressed:

  1. Routing — a program that constructs a native EventEmitter by name (eventemitter3's default export binds local EventEmitter, routed through the builtin-new path that emits direct js_event_emitter_* externs) now marks the build as using events. Detected in collect_modules from the lowered class_name: "EventEmitter" token → new ctx.uses_event_emitter flag (threaded into the auto-optimize cache key) → inserts "events" into native_module_imports, so the full node:events wiring fires (well-known archive routing + bundled-events + external-events-construct), identical to a real import 'events'.

  2. Symbol retention — perry-ext-events is a non-tokio wrapper, so the auto-optimize linker uses its prebuilt standalone staticlib. Thin-LTO was internalizing and dropping the crate's entire #[no_mangle] C API from that .a (verified: lto=false restores them; a #[used] address anchor does not beat thin-LTO here). Mirroring the UI crates' profile (strip=false + codegen-units=16) keeps the exported symbols in libperry_ext_events.a.

What remains (the blocker)

Even with (1)+(2), the eventemitter3 repro still fails to link. The events archive is resolved and added to OptimizedLibs.well_known_libs (the routing events -> libperry_ext_events.a log prints) and now contains the symbols — yet for a compilePackages program the archive never reaches the final cc link line. Confirmed with a cc shim: a real import 'events' program has the archive on its link line (1 occurrence) and links fine; the eventemitter3 program has 0. So well_known_libs is being dropped from the link composition specifically on the compilePackages path. That last hop still needs tracing — likely a second compile/link pass for compilePackages that recomputes or discards well_known_libs.

A real import 'events' program (regression check) builds and runs correctly with these changes.

No changelog/version bump.

Summary by CodeRabbit

  • Chores
    • Updated the release build configuration for the EventEmitter events crate to preserve its exported C API and improve compilation parallelism.
    • Refreshed optional-feature detection during compilation by centralizing feature-gate analysis, leading to more accurate stdlib/native module selection.
    • Improved incremental build behavior by including EventEmitter usage in the optimized-build cache key, reducing incorrect cache reuse.

…5140)

PARTIAL — not yet a complete fix. Investigation of the eventemitter3 link
failure (undefined _js_event_emitter_* symbols) found three layers; this
commit addresses two of them:

1. Routing: a program that constructs a native EventEmitter *by name*
   (e.g. eventemitter3's default export, local binding `EventEmitter`) now
   marks the build as using events — detected in collect_modules via the
   lowered `class_name: "EventEmitter"` token (ctx.uses_event_emitter, new
   field in types.rs, threaded into the optimized-libs cache key) — and
   inserts "events" into native_module_imports so the full node:events
   wiring fires (well-known archive routing + bundled-events +
   external-events-construct), exactly as a real `import 'events'` would.

2. Symbol retention: perry-ext-events is a non-tokio wrapper, so the
   auto-optimize linker uses its prebuilt standalone staticlib. Thin-LTO
   was internalizing and dropping the crate's entire #[no_mangle] C API
   from that staticlib (verified: lto=false restores the symbols; a #[used]
   address-anchor does NOT beat thin-LTO here). Mirroring the UI crates'
   profile (strip=false + codegen-units=16) keeps the exported symbols in
   target/release/libperry_ext_events.a.

REMAINING (3rd layer, unsolved): even with (1) and (2), the eventemitter3
repro still fails to link. The well-known events archive IS resolved and
added to OptimizedLibs.well_known_libs (the 'routing events ->
libperry_ext_events.a' message prints), and the archive now contains the
symbols, yet for a compilePackages program the archive does NOT appear on
the final cc link line (confirmed via a cc shim: 1 occurrence for a real
`import 'events'` program, 0 for the eventemitter3 program). So
well_known_libs is being dropped from the link composition specifically on
the compilePackages path. That last hop still needs tracing.

No changelog/version bump.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors optional-feature detection in Perry by centralizing scattered HIR analysis logic into a new feature_detect module. Adds uses_event_emitter to CompilationContext to track native EventEmitter construction. Updates the optimized-libs cache key to include this flag. Adds a Cargo release profile for perry-ext-events to preserve FFI symbols during linking.

Changes

EventEmitter support and feature detection refactoring

Layer / File(s) Summary
CompilationContext EventEmitter field
crates/perry/src/commands/compile/types.rs
Adds uses_event_emitter: bool field to CompilationContext, initialized to false in the constructor.
Optional-feature detection refactoring to feature_detect module
crates/perry/src/commands/compile/collect_modules.rs, crates/perry/src/commands/compile/collect_modules/feature_detect.rs
Replaces inline optional-feature detection in collect_module_finish with a call to detect_optional_feature_usage. New module scans lowered HIR to detect fetch, wasm-runtime, crypto, regex, Temporal, native EventEmitter construction (sets flag and inserts "events" into native_module_imports), URL API, string normalization, Intl.Segmenter, diagnostics, dgram, readline, and ioredis usage.
Cache key and FFI release profile
crates/perry/src/commands/compile/optimized_libs.rs, Cargo.toml
Includes ctx.uses_event_emitter in the djb2 cache-key hash. Adds [profile.release.package.perry-ext-events] with strip = false and codegen-units = 16 to preserve exported C API symbols.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5159: Both PRs follow the same pattern of extending CompilationContext with a feature flag, centralizing HIR-based detection logic, and folding the flag into the cache key; this PR does so for EventEmitter while the retrieved PR handles diagnostics.
  • PerryTS/perry#5165: Both PRs refactor optional-feature detection in collect_module_finish into feature_detect, sharing identical dgram and readline detection logic paths.

Poem

🐇 Features scattered now unified,
EventEmitter's path clarified!
Through HIR we scan, through gates we gate,
FFI symbols preserved—no strip, no fate.
Cache keys fresh, detection sound,
All optional features safely crowned! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main investigation into eventemitter3 native-EventEmitter linking issues and their partial resolution.
Description check ✅ Passed The description covers all required template sections including summary, changes made, related issue reference, test plan, and contributor checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/5140-eventemitter3-link

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry/src/commands/compile/collect_modules.rs`:
- Around line 1896-1907: The implicit EventEmitter detection block sets
ctx.uses_event_emitter = true and inserts "events" into
ctx.native_module_imports, but it does not set ctx.needs_stdlib = true like
other implicit stdlib detections do. This causes link paths that gate
stdlib/well-known archives on needs_stdlib to be missing the events module. Add
ctx.needs_stdlib = true; in the same conditional block where
ctx.uses_event_emitter is set to true to ensure the stdlib is properly flagged
when EventEmitter usage is detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 83bc2c98-8241-49e1-b5b2-ebfa97e1cda4

📥 Commits

Reviewing files that changed from the base of the PR and between 08819f0 and 7fcd58c.

📒 Files selected for processing (4)
  • Cargo.toml
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/optimized_libs.rs
  • crates/perry/src/commands/compile/types.rs

Comment on lines +1896 to +1907
let hir_debug: String = format!("{:?}{:?}", &hir_module.init, &hir_module.functions);
if hir_debug.contains("class_name: \"EventEmitter\"")
|| hir_debug.contains("class_name: \"EventEmitterAsyncResource\"")
{
ctx.uses_event_emitter = true;
// Treat native EventEmitter use exactly like a `node:events` import
// so the full events wiring fires: the perry-ext-events well-known
// archive (which defines `js_event_emitter_*`) is linked, the
// `bundled-events` feature is enabled, and the construct dispatcher
// is registered (`external-events-construct`). Idempotent — a set.
ctx.native_module_imports.insert("events".to_string());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set needs_stdlib when implicit EventEmitter detection fires.

Line 1900 enables EventEmitter routing, but this block never sets ctx.needs_stdlib = true like other implicit stdlib detections. That can leave link paths that gate stdlib/well-known archives on needs_stdlib without events, which matches the unresolved compilePackages missing-archive behavior.

Suggested fix
         if hir_debug.contains("class_name: \"EventEmitter\"")
             || hir_debug.contains("class_name: \"EventEmitterAsyncResource\"")
         {
+            ctx.needs_stdlib = true;
             ctx.uses_event_emitter = true;
             // Treat native EventEmitter use exactly like a `node:events` import
             // so the full events wiring fires: the perry-ext-events well-known
             // archive (which defines `js_event_emitter_*`) is linked, the
             // `bundled-events` feature is enabled, and the construct dispatcher
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let hir_debug: String = format!("{:?}{:?}", &hir_module.init, &hir_module.functions);
if hir_debug.contains("class_name: \"EventEmitter\"")
|| hir_debug.contains("class_name: \"EventEmitterAsyncResource\"")
{
ctx.uses_event_emitter = true;
// Treat native EventEmitter use exactly like a `node:events` import
// so the full events wiring fires: the perry-ext-events well-known
// archive (which defines `js_event_emitter_*`) is linked, the
// `bundled-events` feature is enabled, and the construct dispatcher
// is registered (`external-events-construct`). Idempotent — a set.
ctx.native_module_imports.insert("events".to_string());
}
let hir_debug: String = format!("{:?}{:?}", &hir_module.init, &hir_module.functions);
if hir_debug.contains("class_name: \"EventEmitter\"")
|| hir_debug.contains("class_name: \"EventEmitterAsyncResource\"")
{
ctx.needs_stdlib = true;
ctx.uses_event_emitter = true;
// Treat native EventEmitter use exactly like a `node:events` import
// so the full events wiring fires: the perry-ext-events well-known
// archive (which defines `js_event_emitter_*`) is linked, the
// `bundled-events` feature is enabled, and the construct dispatcher
// is registered (`external-events-construct`). Idempotent — a set.
ctx.native_module_imports.insert("events".to_string());
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/collect_modules.rs` around lines 1896 -
1907, The implicit EventEmitter detection block sets ctx.uses_event_emitter =
true and inserts "events" into ctx.native_module_imports, but it does not set
ctx.needs_stdlib = true like other implicit stdlib detections do. This causes
link paths that gate stdlib/well-known archives on needs_stdlib to be missing
the events module. Add ctx.needs_stdlib = true; in the same conditional block
where ctx.uses_event_emitter is set to true to ensure the stdlib is properly
flagged when EventEmitter usage is detected.

…er 2000-line cap)

The #5140 EventEmitter-detection block pushed collect_modules.rs to 2023
lines, over the 2000-line lint cap. Move the whole optional-feature
detection sequence (fetch/wasm/crypto/regex/temporal/events/url/normalize/
diagnostics/dgram/readline/ioredis) into a new collect_modules/feature_detect.rs
submodule and call it from collect_module_finish.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/perry/src/commands/compile/collect_modules/feature_detect.rs (1)

125-137: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set ctx.needs_stdlib = true when EventEmitter detection fires.

This block sets ctx.uses_event_emitter and inserts "events" into native_module_imports, but unlike other implicit stdlib detections (crypto at line 67, readline at line 216, ioredis at line 238), it does not set ctx.needs_stdlib = true. Per the uses_event_emitter documentation in types.rs, the js_event_emitter_* helpers live in perry-stdlib. Without this flag, link paths gating stdlib/well-known archives on needs_stdlib may exclude the events archive entirely—matching the unresolved compilePackages missing-archive behavior described in the PR objectives.

Suggested fix
     {
         let hir_debug: String = format!("{:?}{:?}", &hir_module.init, &hir_module.functions);
         if hir_debug.contains("class_name: \"EventEmitter\"")
             || hir_debug.contains("class_name: \"EventEmitterAsyncResource\"")
         {
+            ctx.needs_stdlib = true;
             ctx.uses_event_emitter = true;
             // Treat native EventEmitter use exactly like a `node:events` import
             // so the full events wiring fires: the perry-ext-events well-known
             // archive (which defines `js_event_emitter_*`) is linked, the
             // `bundled-events` feature is enabled, and the construct dispatcher
             // is registered (`external-events-construct`). Idempotent — a set.
             ctx.native_module_imports.insert("events".to_string());
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/collect_modules/feature_detect.rs` around
lines 125 - 137, The EventEmitter detection block sets ctx.uses_event_emitter
and inserts "events" into ctx.native_module_imports, but fails to set
ctx.needs_stdlib = true as required by other implicit stdlib detections (crypto,
readline, ioredis). Since js_event_emitter_* helpers live in perry-stdlib, add
ctx.needs_stdlib = true in the conditional block where uses_event_emitter is set
to ensure the events archive is properly linked and not excluded due to missing
stdlib requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@crates/perry/src/commands/compile/collect_modules/feature_detect.rs`:
- Around line 125-137: The EventEmitter detection block sets
ctx.uses_event_emitter and inserts "events" into ctx.native_module_imports, but
fails to set ctx.needs_stdlib = true as required by other implicit stdlib
detections (crypto, readline, ioredis). Since js_event_emitter_* helpers live in
perry-stdlib, add ctx.needs_stdlib = true in the conditional block where
uses_event_emitter is set to ensure the events archive is properly linked and
not excluded due to missing stdlib requirements.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59b0ea94-ed8d-49f2-8fc1-cc0a464b3648

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcd58c and 1faf256.

📒 Files selected for processing (2)
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/collect_modules/feature_detect.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant