Files
hakorune/docs/design/loopform-scope-debug-and-aot.md
nyash-codex 34be7d2d79 vm/router: minimal special-method extension (equals/1); toString mapping kept
mir: add TypeCertainty to Callee::Method (diagnostic only); plumb through builder/JSON/printer; backends ignore behaviorally

using: confirm unified prelude resolver entry for all runner modes

docs: update Callee architecture with certainty; update call-instructions; CURRENT_TASK note

tests: quick 40/40 PASS; integration (LLVM) 17/17 PASS
2025-09-28 01:33:58 +09:00

118 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LoopForm Scopes Debug & AOT — Design (PoC → Stabilize)
Purpose
- Make method resolution (rewrite) and SSA/PHI decisions observable per structured scope (Loop/Join), then derive AOT needs/provides from the same structure. Keep it devonly by default, zerocost when off.
Scope Model (structured on LoopForm)
- Hierarchy: ProgramScope → FunctionScope → RegionScope*
- RegionScope kinds (only two):
- LoopScope: preheader → header(φ) → body → latch → {header|exit}
- JoinScope: if/elseif/else diamond join (then_exit, else_exit → join)
- No BlockScope (too finegrained). Region level is sufficient for diagnostics + AOT.
Invariants (builder verifies in dev)
- LoopScope
- header φ has exactly two incomings: {preheader, latch}
- backedge latch → header exists; critical edges are split
- JoinScope
- join φ has incomings: {then_exit, else_exit}; when else is missing, else takes preif value
- Pin completeness
- Branch conditions, compare operands, and field receivers are slotified via `ensure_slotified_for_use`.
Events & Hub (single sink)
- DebugHub (central)
- enable/disable kinds, set level, sink(file)
- format: JSONL per event
- common fields: `ts, phase(builder|vm|llvm), fn, region_id, cat(resolve|ssa|op|aot), kind, meta{...}`
- metrics (dev only): `fallback_count, reentry_guard_hits, phi_pred_mismatch, birth_me_void_hits …`
- env knobs (proposal):
- `NYASH_DEBUG_ENABLE=1` (master gate)
- `NYASH_DEBUG_KINDS=resolve,ssa[,op,aot]`
- `NYASH_DEBUG_SINK=tmp/nyash_debug.jsonl`
- `NYASH_DEBUG_RATE=1000/s` (optional), `NYASH_DEBUG_SAMPLE=0.1` (optional)
Inspectors (boxes/components)
- ResolveInspector (method resolution)
- emit:
- `resolve.try {recv_cls?, inferred_cls?, method, arity, candidates, unique}`
- `resolve.choose {chosen, reason}`
- `materialize.func {name, present_before, present_after}`
- `module.index {function_count}` (coarse)
- API (internal): `explain(recv, m, n), has(name), functions(prefix?)`
- SSAInspector (PHI and invariants)
- emit:
- `ssa.phi {dst, preds:[{bb,v,type,origin}], decided_type?, decided_origin?}`
- `ssa.verify {rule, ok, detail}` for Loop/Join invariants
- OperatorInspector (later; dev only)
- emit:
- `op.apply {op, lhs_type, rhs_type, result_type, adopted?, fallback?, guard?}`
Optional (later)
- ExpressionBox: instrumented expression tree for a target function (heavy; functionfiltered)
- ProbeBox: dynamic proxy for object method observation (dev only)
RegionScope State (minimal)
- `env`: `ValueId → {type, origin_cls, pinned_slot?}` (surface at region boundaries)
- `calls_seen`: `Vec<CalleeSig>` where CalleeSig = UserMethod{cls,name,arity} | Plugin{box,method_id|name} | Global
- `phis`: `dst → {preds:[{bb,v,type,origin}], decided_type?, decided_origin?}`
- `rewrite_log`: `Vec<{recv_cls?, method, arity, candidates, chosen, reason}>`
- AOT rollup:
- `requires_local`: referenced functions in this region (from calls)
- `provides_local`: materialized functions in this region (from lowering)
Deriving AOT (natural path)
- Fold RegionScope → FunctionScope → ProgramScope:
- `requires += child.requires - child.provides`
- The folded graph gives a call graph; compute SCC → compile/link units and order
- Output (example `plan.json`):
```json
{
"units": [
{"scc": ["Foo.a/1","Foo.b/1"], "order": 0},
{"scc": ["Main.main/0"], "order": 1}
],
"plugins": ["StringBox.substring/2"]
}
```
Builder/VM Attachment Points (minimal)
- ScopeCtx stack in builder: `enter_scope(kind,id)`, `exit_scope()` at Loop/Join construction points
- Method resolution decisions: `src/mir/builder/method_call_handlers.rs` (try/choose, toString→stringify)
- PHI capture (+dev meta propagation): `src/mir/builder.rs: emit_instruction(Phi)`
- Materialize hooks: `lower_method_as_function` end, and/or module finalize (counts)
- Operator (optional Stage 2): `src/mir/builder/ops.rs` (Compare/Add/stringify)
Event Examples
```json
{"ts":"…","phase":"builder","fn":"Foo.main/0","region_id":"loop#12/header","cat":"ssa","kind":"phi","meta":{"dst":63,"preds":[{"bb":2067,"v":57,"type":"i64","origin":"Foo"},{"bb":2070,"v":62,"type":"i64","origin":"Const"}],"decided_type":"i64","decided_origin":"merge(Foo,Const)"}}
{"ts":"…","phase":"builder","fn":"Foo.main/0","region_id":"join#7/join","cat":"resolve","kind":"choose","meta":{"recv_cls":"Foo","method":"add2","arity":2,"candidates":["Foo.add2/2"],"unique":true,"chosen":"Foo.add2/2","reason":"unique-suffix"}}
```
PoC Plan (phased)
1) Phase1: Hub + Resolve/SSA (dev only; default OFF)
- Add ScopeCtx stack (enter/exit at Loop/Join points)
- Fire `resolve.try/choose` and `ssa.phi` events; keep existing dev logs intact
- Smokes: run userbox_branch_phi_vm.sh, userbox_method_arity_vm.sh with `NYASH_DEBUG_ENABLE=1 NYASH_DEBUG_SINK=…`
2) Phase2: Operator + materialize/module + AOT fold
- Add OperatorInspector (Compare/Add/stringify)
- Emit `materialize.func` and `module.index`; collect requires/provides per region
- Fold to `plan.json` (AOT units order), dev only
3) Phase3: Options
- ExpressionBox (functionfiltered) and ProbeBox (dev only)
Acceptance (PoC)
- Debug JSONL contains resolve/ssa lines with `region_id` and decisions
- SKIP中のケース分岐/アリティで「どのregionで落ちたか」がログで一発特定可能
- 既存PASSケースは挙動不変既定OFF
Risks & Mitigations
- Log volume: kinds/level filters, sampling (`NYASH_DEBUG_SAMPLE`), rate limit (`NYASH_DEBUG_RATE`)
- Observation changing meaning: emit after values are finalized; keep zerocost OFF
- Scope drift: single Hub + 3 inspectors; optional boxes are latebinding and devonly