feat(weak): Phase 285A1 - Weak Field Contract (Strict Type Enforcement)
Remove automatic WeakNew conversion and enforce strict compile-time type checking for weak field assignments. Only 3 assignment types allowed: 1. Result of weak(x) call (WeakRef type) 2. Existing WeakRef variable (e.g., me.parent = other.parent) 3. Void/null (clear operation) **Implementation**: - Added MirType::WeakRef to type system (src/mir/types.rs) - Track WeakRef type in emit_weak_new() even in pure mode - Weak field reads return WeakRef without auto-upgrade - Removed automatic WeakNew conversion from field writes - Implemented check_weak_field_assignment() with actionable errors - Fixed null literal type tracking (Phase 285A1.1: Unknown → Void) **Testing**: - 5 test fixtures (3 OK, 2 NG cases) - all passing - Smoke test: phase285_weak_field_vm.sh - Error messages guide users to use weak() or null **Documentation**: - Updated lifecycle.md SSOT with weak field contract 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,143 @@
|
||||
# Claude Code Runbook (Phase 285): VM↔LLVM lifecycle conformance
|
||||
|
||||
This file is an instruction sheet for an implementation agent (Claude Code) to make the language SSOT pass end-to-end across backends.
|
||||
|
||||
Language SSOT:
|
||||
- Lifecycle/weak/fini/GC policy: `docs/reference/language/lifecycle.md`
|
||||
- Truthiness + `null`/`void`: `docs/reference/language/types.md`
|
||||
|
||||
Non-goal: changing language semantics. Any backend drift must be fixed as an implementation bug or explicitly tracked as “unsupported”.
|
||||
|
||||
## What to implement (in order)
|
||||
|
||||
### 0) Preflight (must-pass before any weak smokes)
|
||||
|
||||
Confirm the following are implemented; if any are missing, do **not** run weak fixtures yet:
|
||||
- `weak(x)` can be parsed and lowered into MIR (WeakRef/WeakNew).
|
||||
- VM has a handler for MIR `WeakRef/WeakNew/WeakLoad` (no panic/unimplemented).
|
||||
- `WeakRef.weak_to_strong()` exists at the language surface.
|
||||
|
||||
If any are missing, choose one:
|
||||
- **Option A**: build the missing weak infrastructure first.
|
||||
- **Option B**: temporarily scope to exit-time leak report only (skip weak smokes, document as “unsupported”).
|
||||
|
||||
### 1) WeakRef semantics (VM + LLVM)
|
||||
|
||||
Required behavior:
|
||||
- `weak(x)` creates a non-owning WeakRef.
|
||||
- `w.weak_to_strong()` returns a strong BoxRef when the target is usable; otherwise returns `null` (runtime `Void`).
|
||||
- WeakRef does not auto-upgrade on field access (field read returns WeakRef).
|
||||
- WeakRef equality uses a stable token (do not make `dropped==dropped` true for unrelated targets).
|
||||
|
||||
Conformance checks:
|
||||
- VM: weak works in real execution (not just `toString()`).
|
||||
- LLVM (harness): must match VM behavior for the same program output/exit code.
|
||||
- WASM: if unsupported, keep it explicitly documented as unsupported; do not pretend it is correct by copying strong refs.
|
||||
|
||||
### 2) Exit-time “roots still held” report (diagnostic, default-off)
|
||||
|
||||
Goal: when a program ends while strong references are still held in global roots, print a report so developers can see leaks/cycles.
|
||||
|
||||
Requirements:
|
||||
- Must be default-off.
|
||||
- Must not change program meaning (only prints when enabled).
|
||||
- Should report “what roots still hold strong references”, not attempt to “fix” them.
|
||||
|
||||
Suggested interface (choose one and document it):
|
||||
- Env: `NYASH_LEAK_LOG={1|2}`
|
||||
- `1`: summary counts
|
||||
- `2`: verbose (print up to N names/entries, with truncation)
|
||||
|
||||
Root candidates to include (best-effort):
|
||||
- `env.modules` registry
|
||||
- plugin singletons / plugin registry
|
||||
- host handles / external handles registry
|
||||
|
||||
Output stability:
|
||||
- Use stable tags like `[leak]` or `[lifecycle/leak]` so smokes can match logs.
|
||||
- Truncate long lists deterministically (e.g., first N sorted entries).
|
||||
|
||||
### 3) Cross-backend smokes (VM + LLVM)
|
||||
|
||||
Add smokes under `tools/smokes/v2/` (preferred) to lock behavior.
|
||||
Keep tests fast and deterministic.
|
||||
|
||||
Recommended fixtures (as `.hako` or inline sources in the smoke):
|
||||
|
||||
**A. Weak weak_to_strong success/fail**
|
||||
```nyash
|
||||
box SomeBox { x }
|
||||
static box Main {
|
||||
main() {
|
||||
local x = new SomeBox()
|
||||
local w = weak(x)
|
||||
x = null
|
||||
local y = w.weak_to_strong()
|
||||
if y == null { print("ok: dropped") }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
Expected (VM and LLVM): prints `ok: dropped`, exit 0.
|
||||
|
||||
**B. Strong cycle + leak report**
|
||||
```nyash
|
||||
box Node { other }
|
||||
static box Main {
|
||||
main() {
|
||||
local a = new Node()
|
||||
local b = new Node()
|
||||
a.other = b
|
||||
b.other = a
|
||||
print("ok: cycle-created")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
Expected:
|
||||
- Program output stays `ok: cycle-created`.
|
||||
- With leak report enabled, a report appears at exit (VM at minimum; LLVM if feasible).
|
||||
|
||||
**C. Weak breaks cycle (no strong-cycle leak)**
|
||||
```nyash
|
||||
box Node { other_weak }
|
||||
static box Main {
|
||||
main() {
|
||||
local a = new Node()
|
||||
local b = new Node()
|
||||
a.other_weak = weak(b)
|
||||
b.other_weak = weak(a)
|
||||
print("ok: weak-cycle")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
Expected:
|
||||
- Program output stays `ok: weak-cycle`.
|
||||
- Leak report should not claim an obvious strong-cycle root for these nodes (best-effort; depends on what is rooted globally).
|
||||
|
||||
### 4) Update docs after implementation
|
||||
|
||||
When the above is implemented:
|
||||
- Add the chosen env var to `docs/reference/environment-variables.md` (avoid env var sprawl; keep it in the diagnostics table).
|
||||
- If any backend remains unsupported, update `docs/reference/language/lifecycle.md` “Implementation status” with an explicit note and link.
|
||||
|
||||
## Commands (suggested)
|
||||
|
||||
Build:
|
||||
- `cargo build --release --features llvm`
|
||||
|
||||
Run VM:
|
||||
- `./target/release/hakorune --backend vm local_tests/phase285_weak_basic.hako`
|
||||
|
||||
Run LLVM:
|
||||
- `NYASH_LLVM_USE_HARNESS=1 ./target/release/hakorune --backend llvm local_tests/phase285_weak_basic.hako`
|
||||
|
||||
Leak report (example if env var chosen):
|
||||
- `NYASH_LEAK_LOG=1 ./target/release/hakorune --backend vm local_tests/phase285_cycle.hako`
|
||||
|
||||
## Done criteria (acceptance)
|
||||
|
||||
- VM and LLVM outputs match for weak fixtures (success/fail).
|
||||
- Strong-cycle fixture produces a visible exit-time report when the diagnostic is enabled (and produces no report when disabled).
|
||||
- Weak-cycle fixture does not falsely report a strong-cycle “leak” for the nodes (within the documented root scope).
|
||||
@ -5,23 +5,27 @@
|
||||
## 1. このP0でやること(コード変更なし)
|
||||
|
||||
1) 仕様SSOTを 1 ファイルにまとめる
|
||||
- `docs/development/current/main/phases/phase-285/README.md` を入口SSOTとして育てる。
|
||||
- 言語レベルの SSOT は `docs/reference/language/lifecycle.md`(lifecyle/weak/fini/GC)と `docs/reference/language/types.md`(truthiness と `null`/`void`)に集約する。
|
||||
- Phase 285 は「実装の棚卸し・差分追跡・受け入れ条件」を書く(言語SSOTを書き換えない)。
|
||||
|
||||
2) 用語と境界を固定する
|
||||
- strong/weak/roots/finalizer/collection の定義
|
||||
- weakref の API(upgrade/生存判定)
|
||||
- finalizer の禁止事項(再入・例外・順序)
|
||||
2) 用語と境界を固定する
|
||||
- strong/weak/roots/finalizer/collection の定義
|
||||
- weakref の API(weak_to_strong/生存判定)
|
||||
- finalizer の禁止事項(再入・例外・順序)
|
||||
|
||||
3) LLVM harness の扱いを明文化する
|
||||
- 未対応なら “未対応” を仕様として書く(差分を隠さない)。
|
||||
3) LLVM harness の扱いを明文化する
|
||||
- 未対応なら “未対応” を差分として書く(差分を隠さない)。
|
||||
- 差分は「仕様差」ではなく「未実装/バグ/保留」として分類する(言語SSOTは揺らさない)。
|
||||
|
||||
## 2. README に必ず書く事項(チェックリスト)
|
||||
|
||||
- [ ] “roots” は何か(stack/local/global/handle/plugin 等)
|
||||
- [ ] strong/weak の意味(upgrade の成否条件)
|
||||
- [ ] strong/weak の意味(weak_to_strong の成否条件)
|
||||
- [ ] strong/weak の意味(weak_to_strong の成否条件)
|
||||
- [ ] finalizer はあるか/いつ発火するか/何が禁止か
|
||||
- [ ] GC/解放のトリガ(自動/手動/閾値/テスト用)
|
||||
- [ ] VM と LLVM harness の差分(未対応の場合の方針)
|
||||
- 分類: (A) 仕様通り / (B) 未実装 / (C) 既知バグ / (D) 仕様外(禁止)
|
||||
|
||||
## 3. 次(P1/P2)への導線(箇条書きでOK)
|
||||
|
||||
@ -34,7 +38,7 @@
|
||||
|
||||
- `src/value.rs`
|
||||
- `NyashValue::WeakBox` の生成箇所(weak をどう作るか)
|
||||
- `upgrade()` 失敗時の観測方法(文字列化/判定API)
|
||||
- `weak_to_strong()` 失敗時の観測方法(文字列化/判定API)
|
||||
- unit test: `test_weak_reference_drop` の仕様(何を固定しているか)
|
||||
- `src/finalization.rs`
|
||||
- finalizer の存在(あれば: 登録、呼び出しタイミング、順序)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Phase 285: Box lifecycle / weakref / finalization / GC SSOT
|
||||
# Phase 285: Box lifecycle / weakref / finalization / GC conformance
|
||||
|
||||
Status: Planned (design-first)
|
||||
|
||||
@ -6,6 +6,12 @@ Status: Planned (design-first)
|
||||
|
||||
Box の生存期間(強参照/弱参照/解放/最終化/GC)を SSOT として固定し、移行期間でも意味論が割れない状態にする。
|
||||
|
||||
Language-level SSOT:
|
||||
- Lifecycle/weak/fini/GC policy: `docs/reference/language/lifecycle.md`
|
||||
- Truthiness + `null`/`void`: `docs/reference/language/types.md`
|
||||
|
||||
This Phase document is not the language SSOT; it tracks implementation status, backend gaps, and acceptance criteria.
|
||||
|
||||
## Why now
|
||||
|
||||
- JoinIR/Plan/compose の収束が進むほど、実行時の “値の寿命” の揺れが目立つ。
|
||||
@ -22,26 +28,41 @@ Box の生存期間(強参照/弱参照/解放/最終化/GC)を SSOT とし
|
||||
## Snapshot(今わかっていること)
|
||||
|
||||
- weakref は `Weak<Mutex<dyn NyashBox>>` で保持される(`NyashValue::WeakBox`)
|
||||
- `WeakBox` の `to_string()` は `upgrade()` を試み、`WeakRef(null)` 表示になりうる(観測可能)
|
||||
- `WeakBox` の `to_string()` は `weak_to_strong()` を試み、`WeakRef(null)` 表示になりうる(観測可能)
|
||||
- `src/value.rs` に weakref の drop 挙動を固定する unit test がある(`test_weak_reference_drop`)
|
||||
|
||||
## Responsibility Map(どこが仕様を決めるか)
|
||||
|
||||
- **SSOT(意味)**: Rust VM 実装(`src/value.rs`, `src/finalization.rs` 周辺)
|
||||
- **SSOT(観測)**: fixture/smoke(Phase 285 P2 で作る)
|
||||
- **LLVM harness**: まずは “差分を仕様として明文化” が優先(未対応なら SKIP を SSOT 化する)
|
||||
- **SSOT(意味)**: `docs/reference/language/*`(言語レベルのSSOT)
|
||||
- **Conformance**: Rust VM / LLVM harness / WASM / JIT など各バックエンド実装
|
||||
- **観測の固定**: fixture/smoke(Phase 285 P2 で作る)
|
||||
|
||||
## 用語(P0で固定する)
|
||||
|
||||
- **Strong reference**: 所有参照(`Arc` 等で Box を保持)
|
||||
- **Weak reference**: 非所有参照(`Weak` / `upgrade()` が失敗しうる)
|
||||
- **Upgrade**: weak → strong の昇格(成功/失敗が意味論)
|
||||
- **Weak reference**: 非所有参照(`Weak` / `weak_to_strong()` が失敗しうる)
|
||||
- **Weak-to-strong**: weak → strong の昇格(成功/失敗が意味論)
|
||||
- **Roots**: 解放/GC から保護される参照集合(stack/local/global/handle/plugin)
|
||||
- **Finalizer**: 解放に伴う最終化処理(もし存在するなら)
|
||||
|
||||
## P0 decisions (docs-only)
|
||||
|
||||
- Weak の観測は `weak_to_strong()` で行い、失敗値は `null`(= runtime `Void` の別名)。
|
||||
- `cleanup`(Stage‑3 block-postfix)が「出口で必ず走る」決定的 cleanup を保証する(`catch` の有無に関係なく、常に実行)。
|
||||
- GC は意味論ではなく補助(GC off で cycle はリークしうる)。
|
||||
- ByRef (`RefGet/RefSet`) は non-owning / non-escaping(寿命・弱参照・GC の道具にしない)。
|
||||
|
||||
## RUNBOOK caveat (implementation reality)
|
||||
|
||||
The runbook assumes WeakRef infrastructure exists in the VM and lowering.
|
||||
If any of the following are missing, treat weak smokes as **unsupported** and scope to exit-time leak report first:
|
||||
- `weak(x)` parse/lower
|
||||
- VM handler for MIR WeakRef/WeakNew/WeakLoad
|
||||
- language-surface `weak_to_strong()` on WeakRef
|
||||
|
||||
## Questions to Answer (P0/P1)
|
||||
|
||||
- weakref の “生存判定” は何で観測できるか(`toString` / `is_alive` / `upgrade` API など)
|
||||
- weakref の “生存判定” は何で観測できるか(`toString` / `is_alive` / `weak_to_strong` API など)
|
||||
- finalizer は存在するか / いつ発火するか(drop 時?GC 時?明示 API?)
|
||||
- finalizer 内での禁止事項(再入、例外、I/O、allocation)をどうするか
|
||||
- LLVM harness の扱い(現状未対応なら “未対応として SSOT 化”)
|
||||
@ -51,7 +72,7 @@ Box の生存期間(強参照/弱参照/解放/最終化/GC)を SSOT とし
|
||||
### P0(docs-only)
|
||||
|
||||
- 用語の固定(strong/weak/roots/finalizer/collection)
|
||||
- 仕様の固定(weakref の upgrade 成否、finalizer の発火条件、禁止事項)
|
||||
- 仕様の固定(weakref の weak_to_strong 成否、finalizer の発火条件、禁止事項)
|
||||
- “LLVM harness の扱い” を明文化(未対応なら未対応として SSOT に書く)
|
||||
|
||||
### P1(investigation)
|
||||
@ -69,3 +90,10 @@ Box の生存期間(強参照/弱参照/解放/最終化/GC)を SSOT とし
|
||||
|
||||
- GC アルゴリズム刷新(RC→tracing 等の設計変更)
|
||||
- LLVM harness に同等機能を “一気に” 実装(差分の記録→段階導入を優先)
|
||||
|
||||
## Acceptance criteria (P2+)
|
||||
|
||||
- VM と LLVM で、weak が仕様通り動作する(`weak_to_strong()` 成功/失敗が一致、失敗は `null`)。
|
||||
- 強参照サイクルを意図的に作ったとき、(GC off なら)回収されないことが観測できる。
|
||||
- 終了時に「強参照が残っている root」をデバッグ出力できる(default-off の診断フラグ)。
|
||||
- これは意味論ではなく診断であり、ON/OFF でプログラムの意味を変えない。
|
||||
|
||||
Reference in New Issue
Block a user