passes: implement control-flow pass (4 schemes + rotation)

FlowPass rebuilds one method per scheme picked from FlowConfig:
state-spread (dense tableswitch dispatcher), computed-switch
(sparse lookupswitch over a counter local), butterfly (branch
inversion), exception-goto (dispatcher under try with a dead
catch-all), plus opaque goto-splices inside every method and
StackMapTable recomputation afterwards. Now in standard_pipeline
(between constants and noise); --no-flow is an honest off-flag.

Acceptance is real JVM, not shapes: per-scheme verify, rotation
spread, calc-prints-45 execution, paranoid smoke on lorep-1.1.0
(131 methods rebuilt, no new failures over standard).

Two verifier rules learned the hard way are now construction
invariants: no inserted mark shares a bytecode offset with a
foreign one (nop pads), and no frameless gap follows a terminal
(dead blocks are opaque-entered and throw).

Also fixes a latent frame-emitter bug the pass exposed: wide
stack values were written as slot pairs ([Double, Top]) while
javac writes one entry ([Double]); HotSpot rejects the pair.
Stacks now collapse like locals (emit::stack_vars), with a
regression test in classfile/tests/frames.rs.
This commit is contained in:
Loki 2026-10-09 11:11:55 +02:00
parent 39178420e4
commit 2172206e83
Signed by: boba
GPG key ID: 253067914055423B
15 changed files with 1360 additions and 103 deletions

View file

@ -104,7 +104,18 @@ opaque-предикаты — условия, истинность которы
- Шаблонная схема (все методы перестроены одинаково) детектируется
гистограммой длины блоков.
**Статус: не реализован; блокер — `StackMapTable` (см. [`stack.md`](stack.md)).**
**Статус: готов** (`noxis-passes/src/flow/`). Одна схема на метод (ротация
через PRNG) плюс нарезка тела `goto`-сплайсами во всех схемах; кадр
`StackMapTable` пересчитывается после каждой перестройки. Приёмка —
настоящий `java -Xverify:all` и исполнение под JVM (`calc` печатает 45
после всех схем). Ограничения: `<init>` пропускается (форма
super-call-first); методы с `jsr`/`ret`, короче 3 инструкций и длиннее
`max_insns` пропускаются; `encrypt_block_bodies` добавляет opaque-guard
на входе. Два правила, выученных на крови (см. `StackMapTable`):
вставленная метка не должна делить байт-смещение с чужой (иначе кадры
мерджатся из несовместимых состояний — после каждой такой метки `nop`),
а инструкция после терминальной обязана иметь свой кадр (поэтому dead-блоки
входят через opaque-ветвь и кончаются `athrow`, а не висят недостижимыми).
## 5. Шум (`Noise`)