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
This commit is contained in:
117
docs/design/loopform-scope-debug-and-aot.md
Normal file
117
docs/design/loopform-scope-debug-and-aot.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Loop‑Form 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 dev‑only by default, zero‑cost when off.
|
||||
|
||||
Scope Model (structured on Loop‑Form)
|
||||
- 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 fine‑grained). 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 pre‑if 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; function‑filtered)
|
||||
- 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 roll‑up:
|
||||
- `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) Phase‑1: 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) Phase‑2: 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) Phase‑3: Options
|
||||
- ExpressionBox (function‑filtered) 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 zero‑cost OFF
|
||||
- Scope drift: single Hub + 3 inspectors; optional boxes are late‑binding and dev‑only
|
||||
|
||||
49
docs/design/using-and-dispatch.md
Normal file
49
docs/design/using-and-dispatch.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Using Resolution and Runtime Dispatch — Design Overview (SSOT + AST, Phase‑15)
|
||||
|
||||
Purpose
|
||||
- Make name/module resolution deterministic at build time while keeping runtime dispatch only for plugin/extern paths.
|
||||
- Preserve the language surface (`obj.method()`) and guarantee it executes in prod without runtime fallbacks.
|
||||
|
||||
Key Principles
|
||||
- Single Source of Truth (SSOT): `nyash.toml` governs using packages/aliases.
|
||||
- Profiles: dev|ci|prod
|
||||
- prod: file‑using is rejected; only toml packages/aliases are allowed; AST prelude merge is required when using is present.
|
||||
- dev/ci: file‑using can be enabled via env; AST prelude merge on by default.
|
||||
- Static resolution at using time:
|
||||
- Strip `using` lines, collect prelude source paths, parse each to AST, and merge before macro expansion.
|
||||
- Materialize user box methods as standalone functions: `Box.method/Arity`.
|
||||
- Builder rewrites instance calls: `obj.m(a)` → call `Box.m/Arity(obj,a)`.
|
||||
- Runtime resolution:
|
||||
- Plugin/extern dispatch remains dynamic.
|
||||
- User instance BoxCall fallback (VM) is disallowed in prod to catch builder misses; dev/ci may allow with WARN.
|
||||
|
||||
Environment Knobs (Summary)
|
||||
- Using system
|
||||
- `NYASH_ENABLE_USING` (default ON)
|
||||
- `NYASH_USING_PROFILE={dev|ci|prod}` (default dev)
|
||||
- `NYASH_USING_AST=1|0` (dev/ci default ON; prod default OFF)
|
||||
- `NYASH_ALLOW_USING_FILE=1|0` (default OFF; dev only when needed)
|
||||
- Builder
|
||||
- `NYASH_BUILDER_REWRITE_INSTANCE=1|0` (default ON across profiles)
|
||||
- VM
|
||||
- `NYASH_VM_USER_INSTANCE_BOXCALL=1|0` (default: prod=0, dev/ci=1)
|
||||
|
||||
Code Responsibilities
|
||||
- Using resolution (static)
|
||||
- Entry: `src/runner/modes/common_util/resolve/resolve_prelude_paths_profiled`
|
||||
- Core: `collect_using_and_strip` (SSOT enforcement, path/alias/package resolution, profile gates)
|
||||
- Consumers: all runners (VM, LLVM/harness, PyVM, selfhost) call the same helper to avoid drift.
|
||||
- Builder (lowering)
|
||||
- Instance→Function rewrite and method materialization live in `src/mir/builder/*`.
|
||||
- VM (runtime)
|
||||
- User Instance BoxCall fallback gate in `src/backend/mir_interpreter/handlers/boxes.rs`.
|
||||
|
||||
Testing Strategy
|
||||
- Prod safety: obj.method() works under prod with runtime fallback disabled (rewrite required).
|
||||
- Smoke: `tools/smokes/v2/profiles/quick/core/oop_instance_call_vm.sh` (prod + forbid VM fallback)
|
||||
- Using/AST parity: quick/integration call `resolve_prelude_paths_profiled` from all runner modes.
|
||||
|
||||
Future Small Refactors (non‑behavioral)
|
||||
- Factor a helper to parse prelude paths into ASTs (single place), and use it from all runners.
|
||||
- Add dev‑only WARN when a candidate `Box.method/Arity` is missing from `module.functions` during rewrite.
|
||||
|
||||
Reference in New Issue
Block a user