Skip to content

snap census: self-classifying env/argv read sites, walker-lock unwind funnel, pending-job rows (instrumented builds only) - #14449

Merged
proggeramlug merged 29 commits into
mainfrom
snap/census-k3c
Nov 11, 2026
Merged

proggeramlug merged 29 commits into
mainfrom
snap/census-k3c

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Replaces draft #14259.

What

Instrumentation of the startup-snapshot census (instrumented compiles and the instrumented runtime only; production codegen and runtime bytes are unchanged):

  • K2b chain (to 51ae33d): process.env / process.argv read sites classify against their own parent expression instead of address-keyed site lists, so a later module can no longer read a stale site kind; this also covers literal operands on the scalar-replacement path of let. Also in the chain: the compare_literal hook from the compare lowering, K2b fixtures and checker, open-frame and job_queue rows.
  • Walker-lock deadlock fix: locked_walker returns a WalkerLock that holds the census Unwind funnel in the instrumented cohort; a native census event raised inside it (clock_gettime from mimalloc's arena free, reached from decode_fde_row inside find_handler) no longer re-enters the eh_walker and hangs. The row carries funnel=unwind. Test: row_pcs_inside_walker_lock_returns.
  • K3c pending-job census: a job_queue row names each pending job as kind:owner:code (Task variant, queueing init, callback code address), which identifies the job pending from init 1033 to the end of Supabase init. Test: pending_jobs_name_kind_callback_and_queueing_init.

Merged with origin/main 322604f (two mechanical conflicts, both sides kept: expr/compare.rs hook order, expr/mod.rs module list).

Tests (merged head)

  • cargo check --workspace --exclude perry-ui-gtk4: rc=0
  • cargo test -p perry-runtime snap (RUST_TEST_THREADS=1): 67 passed, 0 failed, 1 ignored (includes the two tests above)
  • cargo test -p perry-codegen snap (RUST_TEST_THREADS=1): 4 passed, 0 failed (snap_compare_site_tests, snap_env_site_tests)

Startup gate (exact callgrind Ir, 5 runs per arm, setarch -R, controls /root/ctl, same binary path, A/A first; main = 322604f)

witness Ir main Ir head delta .text main .text head .text delta
hello 868734 868734 0 5931550 5931550 0
commander_help 17211218 17211218 0 9936382 9936382 0
commander 17517338 17517338 0 9936382 9936382 0
fastify_inject 151178571 151178571 0 24021310 24021310 0
awfy_richards 37338672 37338672 0 9756814 9756814 0
binary_trees 14490924043 14490924043 0 5965214 5965214 0

A/A all zero. File-size deltas outside .text (bytes, not gated): hello +8, commander_help -640, commander -640, fastify_inject -744, awfy_richards +8, binary_trees +8 (link-pad CONVERGED; awfy_richards shows a 64-byte .rodata difference at the end of the main arm's link, no Ir or .text effect).

Ralph Küpper added 28 commits November 6, 2026 18:20
… (SNAP25)

Instrumented builds only (cfg(perry_gc_instruments)); the production build
is unchanged. PERRY_SNAP25=0 restores the SNAP23 rows.

- Native rows carry the captured return pcs.
- Funnels (exec_path, stack_maps, stack_bounds, parse, parallelism, gc,
  exec_argv) tag the syscalls and native rows issued inside them; the GC
  heap budget's first computation is in the gc funnel.
- navigator.hardwareConcurrency is an input call; env reads inside the
  exec_argv funnel are program inputs (guard members, never knobs).
- unsetenv/setenv rows record whether the name was set; timer, uptime,
  console.time and GC-stats clocks are input rows.
- Every Error stack capture records whether its pcs lie in the executable
  text.
- Census compiles mark each process.argv read: length, constant index
  (Number or Integer literal) or escape.
- The fault handler brings a tracked page whose policy is open but whose
  protection is still an earlier bulk protect back to its policy instead
  of treating the fault as foreign.
# Conflicts:
#	crates/perry-runtime/src/process/env_misc.rs
…P-reach rows; fixtures F1 F2 F3 F10 (instr-only)
…y words, owned storage in the (c) scan (instr-only)

- F1/F2/F3/F10 call gc() before the output, so the boundary report and the
  K2a rows are written; checkk2.py fails "no boundary row" first.
- on_trap applies protection after every store of the step is recorded;
  note_access returns whether the first-write watch ended and on_segv calls
  end_watch(). A nested fault inside on_trap reports 0xdead instead of
  dying on the blocked SIGTRAP.
- P-entry (a) uses its own word set `ew` (last writer is an entry); (a) is
  a conflict rule and is read from the PERRY_SNAP22_WATCH=0 run.
- The p_reach row follows the init_enter event (exact Ir accounting join).
- (c) scan: an entry birth reached through an owned-storage edge of a
  non-entry holder (ObjectHeader.meta; ObjectMeta spill, overflow,
  elements, expando, owned_state) is the holder's representation, not an
  entry value: no target, scanned as the holder's words. F2 named the
  global's meta record (born by the runtime, holding the spill that the
  entry's globalThis.k grew) as a (c) holder; checkk2 F2 now allows only
  the box.
- held rows name holder_type and holder_seq.
# Conflicts:
#	crates/perry-runtime/src/navigator.rs
…e and job-queue rows, runtime-pc STOP, worker-flag STOP
…lours, TERM=dumb does not)

util.styleText with the default stream check now follows node's
shouldColorize(process.stdout): with FORCE_COLOR set the text is styled when
getColorDepth() > 2, whatever stdout is; otherwise stdout must be a tty and
getColorDepth() > 2. The old check returned false for FORCE_COLOR="" (node
colours: depth 16) and coloured a tty with TERM=dumb or no TERM (node: depth 2).

The decision is tty::stdout_style_bit(), over the same colour-depth lookup as
process.stdout.getColorDepth().

tests/parity/run_style_text_color_tests.sh compares stdout with node for
FORCE_COLOR unset/""/0/1/2/3/false, NO_COLOR, NODE_DISABLE_COLORS, TERM=dumb,
TERM=xterm-256color, no TERM and two FORCE_COLOR combinations, each under a pty
and through a pipe.
tty.rs: stdout_style_bit is the style fix plus the instrumented StyleBit funnel and stdout_style_bit row (cfg perry_gc_instruments).
…g text

scripts/snap_inputs/checkk2b.py and fixtures/snapk2b/{f3n,f4,f5,f6,f7,f8,
f12,f13,fw,f2c}: each fixture is red with its rule knob off and green with
it on (f8 confirms its row only).

compare_literal read a heap string through the K2a value encoding
("string:<hex>") but a short string as its text, so equal text in the two
representations compared unequal and the row's literal held the encoding.
string_text now returns the text for both.

f12 queues a promise job at module scope that is still pending when the
next module starts (node order: n sees m unset); a TLA module does not
reach that state in perry, which runs it to completion before its
sibling. f13 makes its Error in a recursive function at module scope: a
module init body is not JS text, so an Error made there captures no frame;
the checker now requires a non-empty capture.
The compare_literal census hook kept a thread-local list of compare
sites keyed by Expr address. A compare whose numeric-literal or typeof
operand is lowered without lower_expr never completed its entry; once a
codegen thread moved to the next unit, an expression at the freed
literal's address emitted js_snap_compare_literal with the earlier
function's register (Supabase: precise_roots native-root lowering panic
in an unrelated closure).

expr::compare::lower now emits the hook from its own re-read operand
values, right where it compares them. Fast paths (typeof, numeric
literal, chained and typed compares) emit no row: the read stays a
value member. No site list remains. The new test compiles a module of
fast-path compares, then a module with no compare, and asserts the
second emits no hook (33 stray hooks on the old code).
The process.env / process.argv census arms looked their parent up in
site lists keyed by node address, so a later module whose nodes reused
freed addresses read stale entries. The lists are gone: FnCtx keeps the
two innermost expressions being lowered (installed by lower_expr and by
a module-global let, restored on every return), and each read
classifies itself against its own parent. The scalar-replacement paths
of lower_let lower literal operands with the literal installed as
their parent. Test env_argv_kinds_never_cross_reads.
…side it does not walk the stack

A census native event (clock_gettime from mimalloc's arena free, reached
from decode_fde_row inside find_handler) captured its stack through the
same eh_walker, whose lock the thread already held: the Supabase census
run hung at the ws buffer-util throw (init 272). locked_walker returns a
WalkerLock that holds the census Unwind funnel in the instrumented cohort;
native_event skips the capture inside it and the row carries
funnel=unwind. Production builds are unchanged (the funnel field is
instrumented-only).
…ode, queueing init)

The K3 Supabase census stops at init 1033 on job_queue (pending=1 at every
later boundary), but the row only counted the jobs. Each row now lists the
queued jobs in queue order as kind:owner:code: the Task variant, the init
that queued the job (the snapshot's snap_init, set at enqueue) and the
callback's JsFunctionInfo code address (0 for a reaction without a callback).
Read from the task itself; instrumented runtime only.
…sus-k3c

# Conflicts:
#	crates/perry-codegen/src/expr/compare.rs
#	crates/perry-codegen/src/expr/mod.rs
@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Explain Change →

Pre-merge checks: Inconclusive 2

Failed Failed checks: 2 inconclusive

Check name Status Explanation Resolution
Title check Pending This assessment has not completed. The check will be retried on the next pre-merge check run.
Description check Pending This assessment has not completed. The check will be retried on the next pre-merge check run.
  • 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 marked this pull request as ready for review November 11, 2026 12:01
@proggeramlug
proggeramlug merged commit 7b9b809 into main Nov 11, 2026
93 of 102 checks passed
@proggeramlug
proggeramlug deleted the snap/census-k3c branch November 11, 2026 12:03
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