Repository navigation
wpfacts: dead-body removal (PERRY_FACTS_DCE) is on by default; PERRY_FACTS_DCE=0 turns it off - #14630
Merged
Merged
Conversation
added 2 commits
November 13, 2026 12:51
|
Caution Review failedThe pull request is closed.
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
verify_facts::dce_enabled()is on unlessPERRY_FACTS_DCE=0(before: on only for=1).dce_switch(value)holds the rule; unit test
dce_switch_tests.(solve complete, body classified, no path reaches it) gets an
llvm.trapentry and LLVM drops its statements; anincomplete reach, a missing or a new identity stays active; with
PERRY_VERIFY_FACTS=1the claim report runs first.PERRY_FACTS_DCEstays in the sortedcompile_envlist (compile-cache input); the module digest still carries theinactive set.
PERRY_FACTS_DCE=0emits the same code as main's default (chess binary byte-identical, see below).crates/perry/tests/facts_dce_default.rs(unset and=1emit >= 3 traps and the same count,=0none, sameoutput);
scripts/test-dce-stub.pynow 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 thereach lane, so it is now a "kept body" control (output = node with removal on), and the deliberate false claim is the
S0 control
noreachprotoaccessorsonscripts/verify_facts_s0/reach-proto-accessor-negative.ts(default: trap andwrong output;
=0: node; check mode: rc 86 first).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=1arm(sha c6aa971624c58421) and its
=0binary to the off arm (sha d67418301986f49d), so the on-arm numbers are the flip's.scripts/test-verify-facts-s0.py): 208 of 208 controls pass, off and withPERRY_FACTS_DCE=1in the env.With
--canonnum-runtime(target built withPERRY_FACTS_SABOTAGE=canonnum) the canonnum arm fails the SAME way inboth arms: the false proof is reported, but by the
array repwitness first, notcanonical number(stale S0 arm onmain, not DCE; follow-up).
PERRY_VERIFY_FACTS=1, tests/parity/language//.ts, 2 715 programs): rc 86 = 0, nofalse-proof line, no witness abort; 5 compile failures = main's known five.
PERRY_GC_INSTRUMENTS=1: life and family claims): rc 86 = 2, both verdictLifetime/dead after scope, noActivation, nofamily:generator/test_gap_async_generator_record.ts(closure __73) andgenerator/test_gap_async_generator_retention_11726.ts(abandon$spec_b). 24 compile failures = the same 24 asfamreach3'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 nolonger claimed inactive: 0 traps, output = node with removal on); every earlier arm passes. The updated script
passes on the flip build ("dce stub: ok").
/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.
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()orMath.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.
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.)
(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 3) and skin (three.js skinbench,full 20), compiled via heavy.sh:Check mode (
PERRY_VERIFY_FACTS=1) runs of both: rc 0, 0 false proofs.facts_dce_default1 / 0; codegendce_switch_tests1 / 0; perrycompile_env49 / 0;
scripts/check_file_size.shOK; release build warnings 125 = main's 125. Mac:cargo check -p perry-codegen -p perry-wpfacts -p perry --lib --testsgreen 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=0is the escape hatch; an incomplete solve removes nothing.