Skip to content

wpfacts: dead-body removal (PERRY_FACTS_DCE) is on by default; PERRY_FACTS_DCE=0 turns it off - #14630

Merged
proggeramlug merged 2 commits into
mainfrom
perf/facts-dce-default
Nov 13, 2026
Merged

proggeramlug merged 2 commits into
mainfrom
perf/facts-dce-default

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

What changes

  • verify_facts::dce_enabled() is on unless PERRY_FACTS_DCE=0 (before: on only for =1). dce_switch(value)
    holds the rule; unit test dce_switch_tests.
  • What removal does is unchanged (#26a4cf2ac4 / DCE layer): a body the whole-program activation reach calls inactive
    (solve complete, body classified, no path reaches it) gets an llvm.trap entry and LLVM drops its statements; an
    incomplete reach, a missing or a new identity stays active; with PERRY_VERIFY_FACTS=1 the claim report runs first.
    PERRY_FACTS_DCE stays in the sorted compile_env list (compile-cache input); the module digest still carries the
    inactive set.
  • PERRY_FACTS_DCE=0 emits the same code as main's default (chess binary byte-identical, see below).
  • Tests: crates/perry/tests/facts_dce_default.rs (unset and =1 emit >= 3 traps and the same count, =0 none, same
    output); scripts/test-dce-stub.py now pins the default. Its old false-inactive witness
    (scripts/dce_stub/false-inactive.ts, CXCHK0 / test_gap_10086, iterator close by destructuring) is reached since the
    reach lane, so it is now a "kept body" control (output = node with removal on), and the deliberate false claim is the
    S0 control noreachprotoaccessors on scripts/verify_facts_s0/reach-proto-accessor-negative.ts (default: trap and
    wrong output; =0: node; check mode: rc 86 first).
  • changelog.d/0-facts-dce-default.md.

Why now

The check-mode false proofs that made removal unsound are fixed on main: activation classes A–G (reach lane, F in
#14569), H (#14618, 142bd2f), the call-kinds report (#14609). DCE report 012 (2026-11-06: 96 of 108 CXCHK0 witnesses
SIGILL with the switch on) no longer reproduces: see below.

Measured (qb10, ONE release build of main 46b2339; arms by the env switch)

The flip build (d6190ff) was built too: its default chess binary is byte-identical to the PERRY_FACTS_DCE=1 arm
(sha c6aa971624c58421) and its =0 binary to the off arm (sha d67418301986f49d), so the on-arm numbers are the flip's.

  • S0 (scripts/test-verify-facts-s0.py): 208 of 208 controls pass, off and with PERRY_FACTS_DCE=1 in the env.
    With --canonnum-runtime (target built with PERRY_FACTS_SABOTAGE=canonnum) the canonnum arm fails the SAME way in
    both arms: the false proof is reported, but by the array rep witness first, not canonical number (stale S0 arm on
    main, not DCE; follow-up).
  • Full-corpus check mode (PERRY_VERIFY_FACTS=1, tests/parity/language//.ts, 2 715 programs): rc 86 = 0, no
    false-proof line, no witness abort; 5 compile failures = main's known five.
  • Same with the instrumented runtime (PERRY_GC_INSTRUMENTS=1: life and family claims): rc 86 = 2, both verdict
    Lifetime / dead after scope, no Activation, no family:
    generator/test_gap_async_generator_record.ts (closure __73) and
    generator/test_gap_async_generator_retention_11726.ts (abandon$spec_b). 24 compile failures = the same 24 as
    famreach3's instrumented run. Under removal both are = node and = off in default mode, rc 0 with check mode + removal;
    with check + instruments + removal they report the same Lifetime claim only. A Lifetime claim is a life-census
    verdict (an object's death point) that only check mode computes ("release builds compute none"); removal consumes
    only the activation reach's inactive set. Not a DCE blocker; follow-up for CWN.
  • scripts/test-dce-stub.py (not run since 11-07): on main FAILS at its last arm only (the stale witness is no
    longer claimed inactive: 0 traps, output = node with removal on); every earlier arm passes. The updated script
    passes on the flip build ("dce stub: ok").
  • CXCHK0 witness re-take (the 108-of-132 index json did not survive qb9, so ALL programs were run: 129 originals in
    /root/CXCHK0/delivery/programs incl. node-suite / npm, 237 reductions incl. rejected candidates; node / off / on /
    check): originals 103 valid (off = node), 0 differ on vs off, 0 SIGILL, 0 check-mode rc 86, 39 with removed bodies;
    reductions 220 valid, 0 differ, 0 SIGILL, 0 rc 86, 56 with removed bodies. Invalid rows fail to compile in both arms
    (missing module scaffolds after the copy). Report 012's 96 / 108 SIGILL is gone.
  • Full-corpus default parity, removal on vs off vs node (2 715 programs): 0 SIGILL / SIGSEGV / abort / timeout in
    either arm; on = node exactly where off = node (2 453 each); 10 on != off rows = 5 compile failures (both arms) + 5
    programs that print elapsed times, Date.now() or Math.random() (array/test_issue63_arr, array/test_issue63_escape,
    date/test_date, math/test_math, object/test_issue58_object_string): rerun 3x, they differ run to run in the off arm too;
    with digits masked, on = off = node.
  • Chess (search 250000 / perft 4 / evaluate 100000 / legal 10000, instructions:u, each x2):
arm search M perft M evaluate M legal M peak RSS KiB (time %M) s / p / e / l binary
off (=0) 11 848.169 / 11 848.189 258.932 / 258.933 1 191.815 / 1 191.814 354.802 / 354.801 23 716 / 15 616 / 15 616 / 15 604 9 382 024
on (default) 11 848.161 / 11 848.169 258.931 / 258.933 1 191.812 / 1 191.816 354.799 / 354.801 23 460 / 15 360 / 15 360 / 15 604 9 341 056
delta -0.0001 % -0.0001 % -0.0001 % -0.0003 % not worse -40 968 B (-0.437 %)

Checksums / PV = reference on all four. Sampled peak RssAnon (KiB, x2): search 17 000 / 16 920 vs 16 876 / 16 924,
perft 9 464 / 9 100 vs 9 468 / 9 464, evaluate 9 456 x2 vs 9 456 x2, legal 9 456 x2 vs 9 456 / 9 464 (equal within
sampling). Sections (B, off -> on): .text 7 360 142 -> 7 349 006 (-11 136), perry_js_text 199 801 -> 171 569
(-28 232, -14.1 %), .rodata 617 778 -> 617 458 (-320), perry_strings -48, .perry_gcrec 14 809 -> 12 957 (-1 852),
.perry_gcmap 940 -> 800, .eh_frame_hdr -264, .eh_frame -1 712, .gcc_except_table 1 652 -> 1 284. Facts dump identical
(same solve). Check mode 0 misses, also with check mode + removal. (Census S5 said -45 064 B on an older main.)

  • hello / hello2: byte-identical on vs off (hello 14 472 B, hello2 7 360 936 B): nothing to remove. Start-up Ir
    (callgrind, x86-64-v3 builds; the default build has AVX-512 that valgrind cannot decode): hello 131 303 vs 131 317,
    hello2 791 780 vs 791 743, identical binaries (the difference is the program path length in argv).
  • tssrc (tsc from its sources, tssrc 3) and skin (three.js skinbench, full 20), compiled via heavy.sh:
program file off -> on exec code off -> on perry_js_text off -> on run instructions:u off / on (x2) output
tssrc 88 507 568 -> 72 316 216 (-18.3 %) 63 946 989 -> 50 287 802 (-21.4 %) 40 289 898 -> 29 151 551 (-27.6 %) 16 844.4 / 16 845.7 vs 16 845.5 / 16 845.8 M (+0.004 %, noise) on = off ("typescript 5.8.2 290280"; node cannot run the TS sources)
skin 34 604 000 -> 34 313 016 (-0.84 %) 25 619 365 -> 25 362 297 (-1.00 %) 11 702 906 -> 11 449 950 (-2.2 %) 5 449.22 / 5 449.22 vs 5 449.21 / 5 449.22 M = node

Check mode (PERRY_VERIFY_FACTS=1) runs of both: rc 0, 0 false proofs.

  • Tests on the flip build: perry facts_dce_default 1 / 0; codegen dce_switch_tests 1 / 0; perry compile_env
    49 / 0; scripts/check_file_size.sh OK; release build warnings 125 = main's 125. Mac: cargo check -p perry-codegen -p perry-wpfacts -p perry --lib --tests green after merging main.

Not run

Supabase (queued for the next SUPABASE session: head 1ca0e71, default vs PERRY_FACTS_DCE=0); IR census;
full suites (wpfacts, codegen, hir, runtime); GC schedule stress (no birth / GC change); macOS.

Risk

A false inactive claim is a SIGILL in default mode where it used to be a slow-but-right body. Mitigations: check mode
reports every entered inactive body (rc 86) and the corpus, the CXCHK0 witnesses, S0, chess, tssrc and skin are at zero;
PERRY_FACTS_DCE=0 is the escape hatch; an incomplete solve removes nothing.

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@proggeramlug
proggeramlug merged commit edd8d7f into main Nov 13, 2026
98 of 102 checks passed
@proggeramlug
proggeramlug deleted the perf/facts-dce-default branch November 13, 2026 13:52
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