Skip to content

feat(runtime): gate cold-path diagnostic JSON serializers behind diagnostics (binary size)#5159

Merged
proggeramlug merged 1 commit into
mainfrom
feat/binary-size-diagnostics
Jun 14, 2026
Merged

feat(runtime): gate cold-path diagnostic JSON serializers behind diagnostics (binary size)#5159
proggeramlug merged 1 commit into
mainfrom
feat/binary-size-diagnostics

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

What

Gate the four cold-path diagnostic JSON serializers behind a new diagnostics cargo feature (in default), so binaries that don't use them — and the serde_json graph reachable only through them — dead-strip:

  • GC cycle telemetry (PERRY_GC_DIAG)
  • typed-feedback trace dump (PERRY_TYPED_FEEDBACK)
  • v8 heap-snapshot builder (v8.getHeapSnapshot / writeHeapSnapshot)
  • process.report

Result

A console.log hello-world drops ~95 KB (5.5 → 5.4 MB). No speed change — pure dead-code elimination on cold paths (opt-level untouched).

Verified: hello-world links + runs at 5.4 MB with diagnostics off; a program calling v8.getHeapSnapshot() + process.report.getReport() auto-enables the feature and returns real objects.

How

  • The compiler enables perry-runtime/diagnostics for the auto-optimize build only when a program uses a heap-snapshot API or process.report (detected in collect_modules via method: "getHeapSnapshot"/"writeHeapSnapshot" / property: "report"; forwarded + cache-keyed in optimized_libs).
  • The env-driven dev diagnostics (GC-diag / typed-feedback JSON) ride the same feature and degrade gracefully when off (terse non-JSON line / "{}" stub) — absent from size-optimized binaries; use a full build for those.
  • js_typed_feedback_maybe_dump_trace stays an always-compiled extern (codegen emits an unconditional call from main); only its JSON-building body is gated.

The feature is in default, so plain cargo build / cargo test --workspace / the shipped prebuilt keep all diagnostics.

Summary by CodeRabbit

Release Notes

  • New Features

    • Heap snapshot and process.report APIs are now available by default.
    • Garbage collection diagnostics and telemetry output (including telemetry/trace JSON) are now included when diagnostics are enabled.
  • Chores

    • Improved compile-time detection of when diagnostics-required APIs are used, and only enables diagnostics support accordingly.
    • Updated the optimized build cache key so switching diagnostics usage won’t reuse an incompatible build.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d72aa0dc-3840-4c4e-b0b6-e0240d53105b

📥 Commits

Reviewing files that changed from the base of the PR and between a87b80f and 4dadda8.

📒 Files selected for processing (20)
  • crates/perry-runtime/Cargo.toml
  • crates/perry-runtime/src/arena/mod.rs
  • crates/perry-runtime/src/arena/walk.rs
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/malloc.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/oldgen.rs
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/gc/roots.rs
  • crates/perry-runtime/src/gc/telemetry.rs
  • crates/perry-runtime/src/gc/types.rs
  • crates/perry-runtime/src/node_v8.rs
  • crates/perry-runtime/src/process.rs
  • crates/perry-runtime/src/typed_feedback.rs
  • crates/perry-runtime/src/typed_feedback/trace.rs
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/link/mod.rs
  • crates/perry/src/commands/compile/link/platform_cmd.rs
  • crates/perry/src/commands/compile/optimized_libs.rs
  • crates/perry/src/commands/compile/types.rs
✅ Files skipped from review due to trivial changes (5)
  • crates/perry/src/commands/compile/link/platform_cmd.rs
  • crates/perry/src/commands/compile/link/mod.rs
  • crates/perry-runtime/src/arena/walk.rs
  • crates/perry-runtime/src/gc/types.rs
  • crates/perry-runtime/src/gc/oldgen.rs
🚧 Files skipped from review as they are similar to previous changes (14)
  • crates/perry-runtime/src/node_v8.rs
  • crates/perry-runtime/src/gc/malloc.rs
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry/src/commands/compile/optimized_libs.rs
  • crates/perry-runtime/src/gc/roots.rs
  • crates/perry/src/commands/compile/types.rs
  • crates/perry-runtime/src/arena/mod.rs
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/process.rs
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry-runtime/Cargo.toml
  • crates/perry-runtime/src/typed_feedback.rs
  • crates/perry-runtime/src/typed_feedback/trace.rs
  • crates/perry-runtime/src/gc/telemetry.rs

📝 Walkthrough

Walkthrough

Introduces a diagnostics Cargo feature in perry-runtime and gates all GC telemetry JSON helpers, heap-snapshot, typed-feedback trace, and process-report code behind it. The compiler gains HIR-scanning logic to detect diagnostics API usage and a new CompilationContext::uses_diagnostics flag, which is included in the build-cache hash key and used to conditionally enable perry-runtime/diagnostics in optimized builds. Also reformats Android NDK path construction for clarity.

Changes

diagnostics Cargo feature: detection, gating, and build wiring

Layer / File(s) Summary
Cargo feature declaration and CompilationContext flag
crates/perry-runtime/Cargo.toml, crates/perry/src/commands/compile/types.rs
Declares diagnostics = [] in perry-runtime and adds it to default. Adds uses_diagnostics: bool to CompilationContext, initialized to false.
HIR detection and build-cache/cargo-feature wiring
crates/perry/src/commands/compile/collect_modules.rs, crates/perry/src/commands/compile/optimized_libs.rs
collect_module_finish scans HIR for getHeapSnapshot, writeHeapSnapshot, and process.report and sets ctx.uses_diagnostics = true. build_optimized_libs folds the flag into the hashed cache-directory key and conditionally appends perry-runtime/diagnostics to cross_features.
GC telemetry and heap-snapshot conditional compilation
crates/perry-runtime/src/gc/mod.rs, crates/perry-runtime/src/gc/cycle.rs, crates/perry-runtime/src/gc/malloc.rs, crates/perry-runtime/src/gc/policy.rs, crates/perry-runtime/src/gc/roots.rs, crates/perry-runtime/src/gc/oldgen.rs, crates/perry-runtime/src/gc/types.rs, crates/perry-runtime/src/gc/telemetry.rs
Gates heap_snapshot submodule, all as_str helpers on GC enum types, malloc telemetry snapshot methods, and all telemetry JSON-builder functions behind #[cfg(feature = "diagnostics")]. GcCycleTrace::emit gains a no-op fallback under #[cfg(not(feature = "diagnostics"))]. Dead-code suppression attributes added to unused telemetry structs.
Arena and typed-feedback conditional compilation
crates/perry-runtime/src/arena/mod.rs, crates/perry-runtime/src/arena/walk.rs, crates/perry-runtime/src/typed_feedback.rs, crates/perry-runtime/src/typed_feedback/trace.rs
ArenaRegionTelemetry re-export moved behind diagnostics. typed_feedback_trace_json, path helpers, and the operational body of js_typed_feedback_maybe_dump_trace gated behind diagnostics; TRACE_DUMPED and its atomic imports conditioned on cfg(any(feature = "diagnostics", test)).
Heap-snapshot and process.report API fallbacks
crates/perry-runtime/src/node_v8.rs, crates/perry-runtime/src/process.rs
js_v8_get_heap_snapshot, js_v8_write_heap_snapshot, and js_process_report_function_write_report return literal "{}" when diagnostics is disabled; process_report_json_string itself is gated behind the feature.

Android NDK path formatting

Layer / File(s) Summary
Android NDK clang path Windows suffix formatting
crates/perry/src/commands/compile/link/mod.rs, crates/perry/src/commands/compile/link/platform_cmd.rs
Reformats Windows .cmd suffix conditionals in Android NDK clang executable path construction to use multi-line if cfg!(target_os = "windows") expressions; the resulting paths remain equivalent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PerryTS/perry#5154: Both PRs modify the Android NDK ndk_clang executable path construction (.cmd suffix handling) in crates/perry/src/commands/compile/link/{mod.rs,platform_cmd.rs}.

Poem

🐇 A rabbit digs deep, but only when asked,
No telemetry trails when diagnostics are masked.
With cfg as my shovel and features my gate,
I build only what the compiler requests—never late.
The heap snapshot sleeps till diagnostics says go,
A lean, tidy burrow with just the right flow! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: gating diagnostic JSON serializers behind a diagnostics feature to reduce binary size.
Description check ✅ Passed The PR description is comprehensive, addressing what is being changed, why (binary size reduction), the result (95 KB savings), how it works (feature detection and gating), and graceful degradation. All key sections are well-documented.
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 feat/binary-size-diagnostics

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

…gnostics`

The GC cycle-telemetry (`PERRY_GC_DIAG`), typed-feedback trace dump
(`PERRY_TYPED_FEEDBACK`), v8 heap-snapshot builder
(`v8.getHeapSnapshot`/`writeHeapSnapshot`), and `process.report` JSON
serializers are never on a hot path. Gate them — and the `serde_json` graph
reachable only through them, which then dead-strips — behind a new
`diagnostics` cargo feature (in `default`). A `console.log` hello-world drops
~95 KB.

The compiler enables `perry-runtime/diagnostics` for the auto-optimize build
only when a program uses a heap-snapshot API or `process.report` (detected in
collect_modules; forwarded + cache-keyed in optimized_libs). The env-driven
dev diagnostics (GC-diag / typed-feedback JSON) ride the same feature and
degrade gracefully when off (terse non-JSON line / `"{}"` stub), so they're
absent from size-optimized binaries — use a full build for those.

`js_typed_feedback_maybe_dump_trace` stays an always-compiled extern (codegen
emits an unconditional call from `main`); only its JSON-building body is gated.
No speed change — pure dead-code elimination on cold paths.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Note: this PR also reformats link/mod.rs + link/platform_cmd.rs (2 lines) — they were fmt-dirty on main under the current stable rustfmt and were blocking the lint gate on every PR. Pure rustfmt, no logic change.

@proggeramlug proggeramlug force-pushed the feat/binary-size-diagnostics branch from a87b80f to 4dadda8 Compare June 14, 2026 20:58

@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: 2

🤖 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-runtime/src/typed_feedback/trace.rs`:
- Around line 390-391: The `#[used]` attribute on the static variable K23 that
anchors the `js_typed_feedback_maybe_dump_trace` function is conditionally
compiled only when the "diagnostics" feature is enabled, but the generated code
unconditionally calls this function from main. Remove the `#[cfg(feature =
"diagnostics")]` guard so the `#[used]` attribute is always applied regardless
of feature flags, ensuring the symbol is retained in all builds including
non-diagnostics builds where LTO might otherwise dead-strip the symbol and cause
link failures.

In `@crates/perry/src/commands/compile/collect_modules.rs`:
- Around line 1939-1943: The current substring match for "property: \"report\""
in the hir_debug check is too broad and will match any report property, not just
process.report. Replace this raw substring check with logic that specifically
detects when the report property is accessed on the global process object
(verify the receiver is the global process identifier). This ensures
ctx.uses_diagnostics is only set to true for actual process.report calls,
preventing incorrect over-enabling of perry-runtime/diagnostics for unrelated
report properties.
🪄 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: 7741173a-6d66-41fd-ae7a-2717a72bdd70

📥 Commits

Reviewing files that changed from the base of the PR and between c1da78e and a87b80f.

📒 Files selected for processing (18)
  • crates/perry-runtime/Cargo.toml
  • crates/perry-runtime/src/arena/mod.rs
  • crates/perry-runtime/src/arena/walk.rs
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/malloc.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/oldgen.rs
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/gc/roots.rs
  • crates/perry-runtime/src/gc/telemetry.rs
  • crates/perry-runtime/src/gc/types.rs
  • crates/perry-runtime/src/node_v8.rs
  • crates/perry-runtime/src/process.rs
  • crates/perry-runtime/src/typed_feedback.rs
  • crates/perry-runtime/src/typed_feedback/trace.rs
  • 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 +390 to 391
#[cfg(feature = "diagnostics")]
#[used] static K23: extern "C" fn() = js_typed_feedback_maybe_dump_trace;

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 | 🔴 Critical | ⚡ Quick win

Keep js_typed_feedback_maybe_dump_trace anchored in non-diagnostics builds.

Line 390 conditionally removes the #[used] retention anchor, but Line 311 says codegen still emits an unconditional call from main. In the auto-optimize/LTO path this can dead-strip the symbol and fail final link with an undefined external.

Suggested fix
-    #[cfg(feature = "diagnostics")]
     #[used] static K23: extern "C" fn() = js_typed_feedback_maybe_dump_trace;
📝 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
#[cfg(feature = "diagnostics")]
#[used] static K23: extern "C" fn() = js_typed_feedback_maybe_dump_trace;
#[used] static K23: extern "C" fn() = js_typed_feedback_maybe_dump_trace;
🤖 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-runtime/src/typed_feedback/trace.rs` around lines 390 - 391, The
`#[used]` attribute on the static variable K23 that anchors the
`js_typed_feedback_maybe_dump_trace` function is conditionally compiled only
when the "diagnostics" feature is enabled, but the generated code
unconditionally calls this function from main. Remove the `#[cfg(feature =
"diagnostics")]` guard so the `#[used]` attribute is always applied regardless
of feature flags, ensuring the symbol is retained in all builds including
non-diagnostics builds where LTO might otherwise dead-strip the symbol and cause
link failures.

Comment on lines +1939 to +1943
if hir_debug.contains("method: \"getHeapSnapshot\"")
|| hir_debug.contains("method: \"writeHeapSnapshot\"")
|| hir_debug.contains("property: \"report\"")
{
ctx.uses_diagnostics = true;

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

Narrow report detection to process.report only.

The current property: "report" match will flip uses_diagnostics for unrelated .report properties. That over-enables perry-runtime/diagnostics and defeats size pruning on non-diagnostics apps.

Suggested fix direction
-        if hir_debug.contains("method: \"getHeapSnapshot\"")
-            || hir_debug.contains("method: \"writeHeapSnapshot\"")
-            || hir_debug.contains("property: \"report\"")
-        {
-            ctx.uses_diagnostics = true;
-        }
+        if hir_debug.contains("method: \"getHeapSnapshot\"")
+            || hir_debug.contains("method: \"writeHeapSnapshot\"")
+            || module_uses_process_report(&hir_module)
+        {
+            ctx.uses_diagnostics = true;
+        }

Use a structured HIR walk for process.report (receiver must be global process), instead of a raw substring.

🤖 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 1939 -
1943, The current substring match for "property: \"report\"" in the hir_debug
check is too broad and will match any report property, not just process.report.
Replace this raw substring check with logic that specifically detects when the
report property is accessed on the global process object (verify the receiver is
the global process identifier). This ensures ctx.uses_diagnostics is only set to
true for actual process.report calls, preventing incorrect over-enabling of
perry-runtime/diagnostics for unrelated report properties.

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