Wall of the pi TUI bring-up (gate 3, tracker #6564) — and a strategic one, because it's a PERFORMANCE cliff in the #6559 new Function interpreter, not a correctness bug, and it will hit every new-Function-heavy target (pi via TypeBox now; kimi via fastify's ajv + fast-json-stringify + find-my-way next).
Symptom
pi-native (bare, interactive TUI, under a PTY, fd pre-placed so the download wall # is skipped) produces ZERO output and pegs one core at 100% for ~30–40 seconds, where node pi-bundle.mjs renders the onboarding screen (~23 KB) in under a second. It is NOT an infinite loop — after the grind perry emits terminal-setup escapes and then a separate uncaught TypeError: Cannot read properties of undefined (reading 'get') (a downstream correctness wall to be filed separately once reachable in reasonable time).
Root cause (sampled, debug-symbol build)
sample of the hung process: main thread 100% busy (not blocked in a syscall). Hottest native frames:
swc_ecma_parser::parser::expr::… — 490 samples (perry's runtime parser)
perry_runtime::object::field_get_set::get_field_by_name… — ~700 samples
js_native_call_method, js_object_has_property, js_object_define_property, prototype/descriptor helpers — hundreds.
swc_ecma_parser runs at RUNTIME only via the #6559 dyn_eval path (new Function/eval). The bundle has 761 TypeBox references; TypeBox's TypeCompiler emits validators through new Function(generatedSource). So pi's TUI init compiles many TypeBox validators, and perry re-parses the generated source with SWC and tree-walks it on every call — vs node compiling/JITting once. The interpreter is correct (that's #6559) but orders of magnitude slower, and TUI init does enough of it to look like a hang.
Why it matters / decision
The #6559 interpreter was shipped as "necessary but not fast." This is the first workload where the speed is the blocker, and it's the SAME mechanism kimi's fastify stack needs. Options, roughly ascending:
- Cache the parsed AST / prepared interpreter form keyed by the source string, so repeated
new Function(sameSource) skips re-parsing. Cheap; helps if sources repeat (measure TypeBox's call pattern — many identical vs many distinct).
- Cache + optimize the tree-walk (precompile the AST to a faster internal form; intern property keys — note the huge
get_field_by_name/descriptor cost suggests the interpreted property accesses are also a hotspot, not just parsing).
- Actually compile
new Function bodies to native (perry already has the full compiler; wire runtime-generated source through it and cache the produced code) — matches node's JIT, biggest lift, the real fix. Removes the interpreter from the hot path entirely.
Recommendation: measure the call pattern first (how many new Function calls, how many distinct sources, time per call), then (1)+(2) as the quick win and (3) as the durable answer — (3) also retires the interpreter's correctness tail.
Repro: HOME=<clean+fd> ./pi-native under scratchpad/pty_cap.py; debug binary pi-target/dist/pi-native-dbg; sample saved. Blocks gate 3 (usable TUI) for pi and is on kimi's critical path.
Wall of the pi TUI bring-up (gate 3, tracker #6564) — and a strategic one, because it's a PERFORMANCE cliff in the #6559
new Functioninterpreter, not a correctness bug, and it will hit every new-Function-heavy target (pi via TypeBox now; kimi via fastify's ajv + fast-json-stringify + find-my-way next).Symptom
pi-native(bare, interactive TUI, under a PTY, fd pre-placed so the download wall # is skipped) produces ZERO output and pegs one core at 100% for ~30–40 seconds, wherenode pi-bundle.mjsrenders the onboarding screen (~23 KB) in under a second. It is NOT an infinite loop — after the grind perry emits terminal-setup escapes and then a separate uncaughtTypeError: Cannot read properties of undefined (reading 'get')(a downstream correctness wall to be filed separately once reachable in reasonable time).Root cause (sampled, debug-symbol build)
sampleof the hung process: main thread 100% busy (not blocked in a syscall). Hottest native frames:swc_ecma_parser::parser::expr::…— 490 samples (perry's runtime parser)perry_runtime::object::field_get_set::get_field_by_name…— ~700 samplesjs_native_call_method,js_object_has_property,js_object_define_property, prototype/descriptor helpers — hundreds.swc_ecma_parserruns at RUNTIME only via the #6559 dyn_eval path (new Function/eval). The bundle has 761 TypeBox references; TypeBox'sTypeCompileremits validators throughnew Function(generatedSource). So pi's TUI init compiles many TypeBox validators, and perry re-parses the generated source with SWC and tree-walks it on every call — vs node compiling/JITting once. The interpreter is correct (that's #6559) but orders of magnitude slower, and TUI init does enough of it to look like a hang.Why it matters / decision
The #6559 interpreter was shipped as "necessary but not fast." This is the first workload where the speed is the blocker, and it's the SAME mechanism kimi's fastify stack needs. Options, roughly ascending:
new Function(sameSource)skips re-parsing. Cheap; helps if sources repeat (measure TypeBox's call pattern — many identical vs many distinct).get_field_by_name/descriptor cost suggests the interpreted property accesses are also a hotspot, not just parsing).new Functionbodies to native (perry already has the full compiler; wire runtime-generated source through it and cache the produced code) — matches node's JIT, biggest lift, the real fix. Removes the interpreter from the hot path entirely.Recommendation: measure the call pattern first (how many
new Functioncalls, how many distinct sources, time per call), then (1)+(2) as the quick win and (3) as the durable answer — (3) also retires the interpreter's correctness tail.Repro:
HOME=<clean+fd> ./pi-nativeunderscratchpad/pty_cap.py; debug binarypi-target/dist/pi-native-dbg; sample saved. Blocks gate 3 (usable TUI) for pi and is on kimi's critical path.