feat(llvm/phi): Phase 277 P1 - fail-fast validation for PHI strict mode

## Summary
Implemented fail-fast validation for PHI ordering and value resolution in strict mode.

## Changes

### P1-1: Strict mode for "PHI after terminator"
- File: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- Behavior: `NYASH_LLVM_PHI_STRICT=1` → RuntimeError if PHI created after terminator
- Default: Warning only (no regression)

### P1-2: Strict mode for "fallback 0"
- File: `src/llvm_py/phi_wiring/wiring.py::wire_incomings`
- Behavior: Strict mode forbids silent fallback to 0 (2 locations)
  - Location 1: Unresolvable incoming value
  - Location 2: Type coercion failure
- Error messages point to next debug file: `llvm_builder.py::_value_at_end_i64`

### P1-3: Connect verify_phi_ordering() to execution path
- File: `src/llvm_py/builders/function_lower.py`
- Behavior: Verify PHI ordering after all instructions emitted
- Debug mode: Shows " All N blocks have correct PHI ordering"
- Strict mode: Raises RuntimeError with block list if violations found

## Testing
 Test 1: strict=OFF - passes without errors
 Test 2: strict=ON - passes without errors (no violations in test fixtures)
 Test 3: debug mode - verify_phi_ordering() connected and running

## Scope
- LLVM harness (Python) changes only
- No new environment variables (uses existing 3 from Phase 277 P2)
- No JoinIR/Rust changes (root fix is Phase 279)
- Default behavior unchanged (strict mode opt-in)

## Next Steps
- Phase 278: Remove deprecated env var support
- Phase 279: Root fix - unify "2本のコンパイラ" pipelines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 14:48:37 +09:00
parent 6e749b791e
commit 757193891f
74 changed files with 4178 additions and 575 deletions

View File

@ -1,14 +1,20 @@
# Phase 269: Pattern8 への Frag 適用P0=test-only → P1=実装)
Status: 🚧 進行中P1
Date: 2025-12-21
Status: ✅ 完了P1
Date: 2025-12-22
## サブフェーズ状況
- **P1Pattern8 EdgeCFG lowering**: ✅SSA の `i` PHI を含めて閉じた)
- **P1.1call_method return type**: ✅署名SSOTで型注釈
- **P1.2static box の this/me → static call 正規化)**: ✅runtime receiver を禁止して SSOT 化)
## 目的
**Pattern8BoolPredicateScanを EdgeCFG FragmentFrag + emit_fragで実装し、pattern番号の列挙を “exit配線” に収束させる。**
- **P0**: test-only stub + 最小 fixture/smoke で “入口” を固定DONE
- **P1**: 実装MIR CFG層で Frag を組み、emit_frag で terminator を SSOT 化)(IN PROGRESS
- **P1**: 実装MIR CFG層で Frag を組み、emit_frag で terminator を SSOT 化)(DONE
## 実装範囲(重要:スコープ境界)
@ -52,13 +58,57 @@ set_branch_with_edge_args() / set_jump_with_edge_args() (Phase 260 SSOT)
- header/body/step は `i_current` を参照
- step で `i_next = i_current + 1` を作り、backedge の入力にする
### 完了確認P1
- Pattern8 Frag lower が header に PHI を挿入し、`i_current``Compare/substring/step` の参照に使用する
- integration smoke:
- `tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh` PASS
- `tools/smokes/v2/profiles/integration/apps/phase259_p0_is_integer_vm.sh` PASS回帰なし
## P1.2DONE: static box の `this/me` を static call に正規化runtime receiver 禁止)
### 目的SSOT
static box 内の `this.method(...)` / `me.method(...)`**runtime receiverNewBox / 文字列 receiverにしない**
compile-time に `current_static_box.method/arity` の canonical key を構築し、static call へ正規化する。
### SSOT / 禁止(再掲)
- SSOT:
- `comp_ctx.current_static_box`box 名の唯一の出どころ)
- `BoxName.method/arity`canonical key: call_method 署名注釈と共用)
- 禁止:
- `emit_string("StringUtils")` などの文字列レシーバによる by-name 的回避
- static box の this/me を `NewBox` で runtime object 化(退行の原因)
### 実装(責務分離)
- `src/mir/builder/calls/build.rs`
- MethodCall の共通入口で `This/Me` receiver を最優先で検出し、static call に正規化する
- box 名は `comp_ctx.current_static_box` のみから取り出す(ハードコード禁止)
- `src/mir/builder/stmts.rs`
- static/instance の文脈エラーを Fail-Fast で明確化(誤誘導のメッセージ整理)
- `src/mir/builder/control_flow/joinir/patterns/pattern8_scan_bool_predicate.rs`
- **現状の安全策**: static box 文脈の loop は Pattern8 対象外にし、汎用 loweringPattern1 等)へ戻す
- 目的: receiver 正規化を “1箇所” に収束させ、Pattern8 が runtime receiver を作る経路を封じる
- 撤去条件: Pattern8 が「正規化後の MethodCallstatic call key」前提で安全に動くことを fixture/smoke で確認できたら、この除外を削除する
### 検証fixture/smoke
- `apps/tests/phase269_p1_2_this_method_in_loop_min.hako`
- `tools/smokes/v2/profiles/integration/apps/phase269_p1_2_this_method_in_loop_vm.sh`
- 受け入れ条件:
- MIR dump に `const "StringUtils"` が receiver として出ない
- `call_method StringUtils.is_digit/1`(または同等の static callになる
## テスト手順(固定)
1. `cargo build --release`
2. `cargo test -p nyash-rust --lib --release`
3. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase259_p0_is_integer_vm.sh`
4. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh`
5. `./tools/smokes/v2/run.sh --profile quick`45/46 を維持)
5. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase269_p1_2_this_method_in_loop_vm.sh`
6. `./tools/smokes/v2/run.sh --profile quick`45/46 を維持)
## P0historical

View File

@ -0,0 +1,166 @@
Status: Active
Date: 2025-12-22
Scope: Pattern6/7 を `Frag + emit_frag()` へ段階吸収pattern列挙の増殖を止める
Related:
- Design SSOT: `docs/development/current/main/design/edgecfg-fragments.md`
- Phase 269Pattern8 Frag: `docs/development/current/main/phases/phase-269/README.md`
- Phase 270Pattern9 bridge: `docs/development/current/main/phases/phase-270/README.md`
# Phase 272P0: Pattern6/7 を Frag+emit_frag へ吸収(段階適用)
## ステータス
- **P0.1Pattern6**: ✅ 完了Frag+emit_frag 経路へ移行)
- **P0.2Pattern7**: ✅ 完了Frag+emit_frag 経路へ移行)
## 目的
- Pattern6/7scan系の CFG 構築を “pattern番号ごとの推測分岐” から外し、**EdgeCFG Frag 合成ExitKind/wires/branches**に収束させる。
- terminator emission を SSOT`emit_frag()`へ集約し、block の successors/preds 同期漏れを構造で防ぐ。
## スコープ境界
### ✅ 触る
- `src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs`
- `src/mir/builder/control_flow/joinir/patterns/pattern7_split_scan.rs`
- `src/mir/builder/emission/`Pattern8 と同じ “薄い入口” の追加)
### ❌ 触らない
- merge/EdgeCFG plumbingPhase 260-268 の SSOT は維持)
- cf_loop の非JoinIR経路追加JoinIR-only hard-freeze 維持)
- by-name ハードコードBox名/Pattern名文字列での分岐増殖など
## 入口SSOTfixture/smoke
### Pattern6index_of
- fixture: `apps/tests/phase254_p0_index_of_min.hako`exit=1
- smoke (VM): `tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh`
### Pattern7split
- fixture: `apps/tests/phase256_p0_split_min.hako`exit=3
- smoke (VM): `tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh`
## 方針P0
P0 は “両方一気に” ではなく、以下の順で段階適用する。
1. Pattern6 を `Frag + emit_frag()` に切り替えwiring を SSOT 化)
2. Pattern7 を `Frag + emit_frag()` に切り替え(副作用 push を含む)
3. 旧 JoinIR 経路の撤去条件が満たせた時点で削除本READMEに明記
## 実装ガイド(共通)
- PHI は block 先頭after existing phisへ挿入し、入力を `[(pred_bb, val)]` の形で固定する:
- `crate::mir::ssot::cf_common::insert_phi_at_head_spanned`
- terminator emission は `crate::mir::builder::control_flow::edgecfg::api::emit_frag` に集約する。
- Pattern8 の構造(参考):
- emission 入口: `src/mir/builder/emission/loop_predicate_scan.rs`
## P0.1: Pattern6index_of— Frag 化
### 狙い
- loop 骨格header/body/step/after + early returnを Frag に落とし、Jump/Branch/Return を `emit_frag()` に集約する。
### 実装結果(✅ 完了)
- emission 入口を新設し、Pattern6 の terminator emission を `emit_frag()`SSOTへ集約
- 新規: `src/mir/builder/emission/loop_scan_with_init.rs`
- 更新: `src/mir/builder/emission/mod.rs`
- Pattern6 の JoinIRConversionPipeline 経路を撤去し、Frag 経路へ切り替え
- 更新: `src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs`
- P0 スコープ:
- forward scan`step=1`)のみ適用
- reverse/dynamic needle 等は `Ok(None)` で不適用(既定挙動不変)
- 旧 DCE 対策exit PHI 用の post-loop guardを撤去Frag が Return を直接 emit するため)
### 最小 CFG 形forward scan
- blocks: `header`, `body`, `step`, `after`, `ret_found`
- header:
- `i_current = phi [i_init, preheader], [i_next, step_bb]`
- `cond_loop = (i_current < len)`
- branch: true→body, false→after
- body:
- `ch = s.substring(i_current, i_current+1)`
- `cond_match = (ch == needle)`
- branch: true→ret_found, false→step
- step:
- `i_next = i_current + 1`
- jump: →header
- ret_found:
- wire: `Return(i_current)`
- after:
- P0 では `return -1` を既存 AST lowering に任せてもよいafter を current_block にする)
### 受け入れ
- `cargo test -p nyash-rust --lib --release`
- `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh`
### 追加の検証(推奨)
- `NYASH_VM_DUMP_MIR=1 ./target/release/hakorune --backend vm apps/tests/phase254_p0_index_of_min.hako` で PHI/terminator を確認(任意)
## P0.2: Pattern7split— Frag 化
### 狙い
- Pattern7 の terminator 配線if/loop の遷移)を Frag に集約し、副作用(`result.push`)を含む形でも CFG を壊さない。
### 注意点Fail-Fast
- carriers が複数(`i`, `start`なので、header の PHI を 2 本(以上)で SSA を閉じる必要がある。
- `result.push` は副作用なので、block 配置評価順を壊さないP0は固定形のみ受理
### 実装結果(✅ 完了)
- Pattern7 の JoinIRConversionPipeline 経路を撤去し、Frag+emit_frag 経路へ切り替え
- 更新: `src/mir/builder/control_flow/joinir/patterns/pattern7_split_scan.rs`
- emission 入口を新設し、terminator emission を `emit_frag()`SSOTへ集約
- 新規: `src/mir/builder/emission/loop_split_scan.rs`
- 更新: `src/mir/builder/emission/mod.rs`
- CFG 形P0:
- blocks: `header`, `body`, `then`, `else`, `step`, `after`+ 入口 preheader
- header: PHI`i_current`, `start_current` + loop condition
- body: delimiter match check
- then: `result.push(segment)` + `start_next_then` 計算dominance 安全)
- else: `i_next_else = i_current + 1`
- step: PHI`i_next`, `start_next` + jump header
- Compare は `CompareOp::Le``i <= limit`)を使用(固定形)
### リファクタ結果共通SSOT
Phase 272 P0.2 完了後、Pattern6/7/8 の重複を以下へ収束した(仕様不変):
- PHI 挿入の薄いラッパ: `src/mir/builder/emission/phi.rs`
- variable_map の fail-fast 取得: `src/mir/builder/variable_context.rs``require(name, ctx)`
- can_lower の戦略メモ: `src/mir/builder/control_flow/joinir/patterns/router.rs``CanLowerStrategy`
### 受け入れ
- `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh`
- PASSexit=3
- MIR 形PHI/Branch/Jump/BoxCall(push))を目視確認できること(任意)
## Nextplanned: Phase 273design-first— Pattern を Plan Extractor に降格して裾広がりを止める
Phase 272P0で “terminator SSOTemit_fragへ寄せる” を完了したら、次は上流の収束compiler flow の一本化)を行う。
- 相談メモ(外部レビュー用): `docs/development/current/main/investigations/phase-272-frag-plan-architecture-consult.md`
- ねらい:
- Pattern = **Plan 抽出pure** に降格builder を触らない)
- Plan = `seq/if/loop/exit/effect/let` の固定語彙(増殖しない)
- PlanLowerer が block/value/phi を作る唯一の箱emit_frag は SSOT のまま)
- 受け入れ(最小):
- extractor が `next_block_id/next_value_id/insert_phi_*` を呼ばない(純関数)
- Plan→Frag→emit_frag の本線が 1 本になるpattern番号列挙を中心にしない
## 旧 JoinIR 経路の撤去条件SSOT
`JoinIRConversionPipeline` 系の経路を削るのは、以下を満たした後に行う。
1. Pattern6/7 の fixture/smoke が Frag 経路で PASS
2. `tools/smokes/v2/run.sh --profile quick` が悪化しない
3. router から該当 pattern の “旧経路” が消せる(最小差分で削除可能)
## テスト手順(固定)
1. `cargo build --release`
2. `cargo test -p nyash-rust --lib --release`
3. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh`
4. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh`

View File

@ -0,0 +1,90 @@
# Phase 274 P1 — Implement `TypeOp` on Rust VM (Instruction Guide)
Goal: make `x.is("T")` / `x.as("T")` runnable on the primary backend (Rust VM), aligning language docs and runtime behavior.
Scope: small, behavior-preserving where possible; fail-fast on unsupported type operations.
SSOT:
- Language semantics: `docs/reference/language/types.md`
- MIR instruction: `MirInstruction::TypeOp` (`src/mir/instruction.rs`)
- Rust VM executor: `src/backend/mir_interpreter/*`
---
## 1) What already exists
- Frontend lowering emits `MirInstruction::TypeOp` for `.is("Type")` / `.as("Type")`:
- `src/mir/builder/exprs.rs`
- type-name mapping: `src/mir/builder/calls/special_handlers.rs` (`parse_type_name_to_mir`)
- A fixture exists for P1 acceptance:
- `apps/tests/phase274_p1_typeop_is_as_min.hako`
- smoke: `tools/smokes/v2/profiles/integration/apps/phase274_p1_typeop_is_as_vm.sh`
---
## 2) Implementation plan (Rust VM)
### 2.1 Add instruction execution
Implement execution of:
- `MirInstruction::TypeOp { dst, op: Check, value, ty }``dst = Bool(matches(value, ty))`
- `MirInstruction::TypeOp { dst, op: Cast, value, ty }``dst = value` if matches, else `TypeError`
Files:
- `src/backend/mir_interpreter/handlers/type_ops.rs` (new module is OK)
- `src/backend/mir_interpreter/handlers/mod.rs` (dispatch arm)
- `src/backend/mir_interpreter/mod.rs` (import `MirType`, `TypeOpKind` into interpreter module)
### 2.2 Matching rules (minimal, fail-fast)
Match by `MirType`:
- `Integer/Float/Bool/String/Void`: accept both primitive VM values and their core Box equivalents when present.
- `Box("Foo")`: accept:
- user-defined `InstanceBox` where `class_name == "Foo"`
- builtin/plugin boxes where `type_name() == "Foo"`
- (best-effort) builtin `InstanceBox(from_any_box)` inner `type_name() == "Foo"`
- Others:
- `Unknown` matches anything (diagnostic-friendly).
Do not add new environment variables. Keep behavior deterministic and fail-fast.
---
## 3) Testing / Verification
### 3.1 Build
```bash
cargo build --release
```
### 3.2 Smoke (required)
```bash
HAKORUNE_BIN=./target/release/hakorune bash \
tools/smokes/v2/profiles/integration/apps/phase274_p1_typeop_is_as_vm.sh
```
Expected:
- PASS (exit=3)
### 3.3 Optional MIR inspection
```bash
NYASH_VM_DUMP_MIR=1 ./target/release/hakorune --backend vm \
apps/tests/phase274_p1_typeop_is_as_min.hako
```
Confirm:
- MIR contains `TypeOp(check, ...)` and `TypeOp(cast, ...)`.
---
## 4) Acceptance criteria (P1)
- Rust VM executes `TypeOp` (no “unimplemented instruction”).
- `phase274_p1_typeop_is_as_vm.sh` passes.
- No new env vars are introduced.
- Docs remain consistent with runtime:
- `docs/reference/language/types.md` describes runtime `TypeOp` behavior.

View File

@ -0,0 +1,151 @@
# Phase 274 P2 (impl): LLVM (llvmlite harness) TypeOp alignment
Status: planned / design-first
Goal: make LLVM harness execution match the SSOT semantics in `docs/reference/language/types.md` for:
- `TypeOp(Check, value, ty)``Bool`
- `TypeOp(Cast, value, ty)``value` or `TypeError`
Primary reference implementation (SSOT runtime): `src/backend/mir_interpreter/handlers/type_ops.rs`
---
## 0. What is currently wrong (must-fix)
LLVM harness TypeOp is stubbed in `src/llvm_py/instructions/typeop.py`:
- `is`: returns 0 for most types (IntegerBox is “non-zero” heuristic)
- `cast/as`: pass-through (never errors)
This conflicts with SSOT:
- `is` must reflect actual runtime type match.
- `as` must fail-fast (`TypeError`) on mismatch.
Note:
- It is OK if the compiler constant-folds trivial cases (e.g. `1.is("Integer")`).
- For P2 verification, you must use a fixture that keeps `TypeOp` in MIR (runtime-unknown / union value).
---
## 1. Acceptance criteria (minimum)
1) Behavior parity with Rust VM (SSOT)
- With `NYASH_LLVM_USE_HARNESS=1` and `--backend llvm`, this fixture behaves the same as VM:
- `apps/tests/phase274_p2_typeop_primitives_only.hako` (recommended: harness-safe baseline)
2) Fail-fast
- `as` on mismatch must raise a TypeError (not return 0 / pass-through).
- `is` must return `0/1` deterministically (no “unknown → 0” unless it is truly not a match).
3) No hardcode / no new env sprawl
- No “BoxName string match special-cases” except small alias normalization shared with frontend (`IntegerBox`/`StringBox` etc.).
- Do not add new environment variables for behavior.
---
## 2. Design constraint: LLVM harness value representation (key risk)
In llvmlite harness, a runtime “value” is currently represented as an `i64`, but it mixes:
- raw integers (from `const i64`)
- boxed handles (e.g. strings are boxed to handles via `nyash.box.from_i8_string`)
- various call/bridge conventions
Because a handle is also an `i64`, **the harness cannot reliably decide at runtime** whether an `i64` is “raw int” or “handle”, unless the value representation is made uniform.
This means TypeOp parity cannot be achieved reliably without addressing representation.
---
## 3. Recommended implementation strategy (P2): make representation uniform for TypeOp
### Strategy A (recommended): “all values are handles” in LLVM harness
Make every runtime value in llvmlite harness be a handle (i64) to a boxed value:
- integers: `nyash.box.from_i64(i64) -> handle`
- floats: `nyash.box.from_f64(f64) -> handle`
- strings: already boxed (`nyash.box.from_i8_string`)
- bool/void: use existing conventions (or add kernel shims if needed)
Then TypeOp becomes implementable via runtime introspection on handles.
#### A.1 Kernel helper needed (small, SSOT-friendly)
Add a kernel export (in `crates/nyash_kernel/src/lib.rs`) that checks a handles runtime type:
- `nyash.any.is_type_h(handle: i64, type_name: *const i8) -> i64` (0/1)
- optionally `nyash.any.cast_h(handle: i64, type_name: *const i8) -> i64` (handle or 0; but prefer fail-fast at caller)
Implementation rule:
- Must use actual runtime object type (builtins + plugin boxes + InstanceBox class name).
- Must not guess via resolver/type facts.
#### A.2 LLVM harness lowering
Update `src/llvm_py/instructions/typeop.py`:
- Always resolve `src_val` as handle (`i64`).
- `check/is`: call `nyash.any.is_type_h(src_val, type_name_ptr)` → i64 0/1
- `cast/as`: call `is_type_h`; if false, emit a runtime error (use existing “panic”/error path if available) or call a kernel `nyash.panic.type_error` style function (add if missing).
Also update other lowerers incrementally so that values feeding TypeOp are handles (start with the fixture path).
### Strategy B (fallback): keep mixed representation, but document divergence (not parity)
If Strategy A is too large for P2, constrain scope:
- Implement `TypeOp` using compile-time `resolver.value_types` hints.
- Document clearly in Phase 274 README: LLVM harness TypeOp is “best-effort using type facts” and is not SSOT-correct under re-assignment.
This keeps the harness useful for SSA/CFG validation, but is not runtime-parity.
Note: Strategy B should be treated as temporary and must be called out as backend divergence in docs.
---
## 4. Concrete work items (P2)
1) Audit current failure path
- Identify how LLVM harness reports runtime errors today (type errors, asserts).
- Prefer a single runtime helper rather than sprinkling Python exceptions.
1.5) Fix MIR JSON emission for TypeOp (required)
The LLVM harness consumes MIR JSON emitted by the Rust runner.
If `TypeOp` is missing in that JSON, the harness will never see it (and the JSON can become invalid due to missing defs).
Checklist:
- `src/runner/mir_json_emit.rs` must emit `{"op":"typeop", ...}` in **both** emitters:
- `emit_mir_json_for_harness` (nyash_rust::mir) ✅ already supports TypeOp
- `emit_mir_json_for_harness_bin` (crate::mir) ⚠️ ensure TypeOp is included
2) Add kernel introspection helper(s)
- `crates/nyash_kernel/src/lib.rs`: add `nyash.any.is_type_h`.
- It must handle:
- primitives boxed (`IntegerBox`, `FloatBox`, `BoolBox`, `StringBox`, `VoidBox`)
- `InstanceBox` user classes (by `class_name`)
- plugin boxes (by metadata / resolved type name)
3) Implement real `TypeOp` lowering
- `src/llvm_py/instructions/typeop.py`:
- normalize `target_type` aliases (same mapping as frontend docs: `Int``IntegerBox`, etc.)
- `is` → call kernel check
- `as`/`cast` → check then return src or TypeError
4) Add LLVM smoke (integration)
- New script (name suggestion):
- `tools/smokes/v2/profiles/integration/apps/phase274_p2_typeop_is_as_llvm.sh`
- Run:
- `NYASH_LLVM_USE_HARNESS=1 ./target/release/hakorune --backend llvm apps/tests/phase274_p2_typeop_primitives_only.hako`
- Expect: exit code `3` (same as VM).
---
## 5. Notes / non-goals (P2)
- Do not implement a full static type system here.
- Do not add rule logic to the resolver (no “guessing chains”).
- Do not add new environment variables for behavior selection.
- If you must limit scope, limit it by fixtures and document it in Phase 274 README as explicit divergence.
### Fixture rule (important)
To avoid “TypeOp disappeared” false negatives:
- Do not use pure compile-time constants for `is/as` checks.
- Prefer a union value formed by a runtime-unknown branch (e.g. `process.argv().size() > 0`).
- Note: `env.process.argv` is currently not supported on Rust VM, and `env.get` is not linked for LLVM AOT yet; keep harness fixtures minimal unless the required externs are implemented in NyRT.

View File

@ -0,0 +1,162 @@
# Phase 274 P3 (decision): Coercion SSOT (truthiness / `==` / `+`)
Status: accepted (2025-12-22) / pending implementation
This document freezes **what coercions mean** at the language level, so runtime behavior cannot “emerge” from resolver/type-facts.
SSOT anchor (current executable behavior): `docs/reference/language/types.md`
Phase overview: `docs/development/current/main/phases/phase-274/README.md`
Implementation phase: `docs/development/current/main/phases/phase-275/README.md`
---
## 1) Terms
In this doc, “coercion” means: when operands/types differ, do we:
- convert implicitly,
- return a deterministic result (e.g. `false`),
- or fail-fast (`TypeError`)?
Target constraints:
- Fail-Fast where it prevents silent bugs
- No “JS-style surprise coercion”
- Dynamic runtime remains (no static type system required)
- Backend parity (VM/LLVM) or explicit divergence, never accidental drift
---
## 2) Proposed SSOT (recommended)
Based on the project philosophy, the recommended SSOT choice is:
- **truthiness: A1**
- **`==`: B2 (Number-only)**
- **`+`: C2 (Number-only promotion)**
The sections below define each choice precisely.
---
## 3) truthiness (boolean context)
### Decision: A1 (Fail-Fast)
`Void` in condition is **TypeError**.
Allowed in boolean context:
- `Bool` → itself
- `Integer``0` false, non-zero true
- `Float``0.0` false, non-zero true
- `String` → empty false, otherwise true
Disallowed (TypeError):
- `Void` (always error)
- `BoxRef` (by default)
- Exception: only **explicit bridge boxes** may be unboxed to the corresponding primitive for truthiness:
- `BoolBox` / `IntegerBox` / `StringBox`
- `VoidBox` is treated as `Void` → TypeError
Recommended explicit patterns:
- existence check: `x != Void`
- type check: `x.is("T")` / `x.as("T")`
- explicit conversion (if we add it): `bool(x)` (but `bool(Void)` remains TypeError)
Implementation impact (where to change):
- Rust VM: `src/backend/abi_util.rs::to_bool_vm`
- LLVM harness: must match the VM semantics used for branch conditions
---
## 4) `==` (equality)
### Decision: B2 (Number-only)
Rules:
- Same-kind primitives compare normally.
- `Int``Float` comparisons are allowed (Number-only).
- `Bool` is **not** a number: `Bool``Int/Float` has no coercion.
- Other mixed kinds: deterministic **`false`** (not an error).
- `BoxRef == BoxRef`: identity only.
#### Precise rule for `Int == Float` (avoid “accidental true”)
To avoid float rounding making `true` incorrectly:
For `Int == Float` (or `Float == Int`):
1) If Float is NaN → `false`
2) If Float is finite, integral (fractional part is 0), and within `i64` exact range:
- convert Float → Int exactly, then compare Ints
3) Otherwise → `false`
Migration note:
- If legacy behavior existed for `1 == true`, prefer a transition phase where it becomes **TypeError first** (to surface bugs), then settle to `false` if desired.
Implementation impact:
- Rust VM: `src/backend/abi_util.rs::eq_vm` (and any helpers)
- LLVM harness: must mirror the same decision for `compare ==` lowering
---
## 5) `+` (add / concat)
### Decision: C2 (Number-only promotion)
Rules:
- `Int + Int``Int`
- `Float + Float``Float`
- `Int + Float` / `Float + Int``Float` (promote Int→Float)
- `String + String` → concat
- `String + non-string` / `non-string + String`**TypeError** (no implicit stringify)
- Other combos → TypeError
Implementation impact:
- Rust VM: `src/backend/mir_interpreter/helpers.rs::eval_binop` (BinaryOp::Add)
- LLVM harness: binop `+` lowering must follow the same coercion rules
---
## 6) Minimum test matrix (SSOT lock)
### 6.1 truthiness
- Bool: `if true`, `if false`
- Int: `if 0`, `if 1`, `if -1`
- Float: `if 0.0`, `if 0.5`, `if NaN` (define if NaN counts as truthy)
- String: `if ""`, `if "a"`
- Void: `if Void` → TypeError (A1)
- BoxRef:
- bridge: `BoolBox(true)`, `IntegerBox(0)`, `StringBox("")`
- non-bridge: `Foo()` → TypeError
### 6.2 equality
- same-kind primitives
- Int↔Float:
- `1 == 1.0` true
- `1 == 1.1` false
- `NaN == NaN` false
- Bool↔Int:
- `true == 1` (explicitly decide: TypeError during migration vs final false)
- BoxRef identity:
- same handle true, different handles false
### 6.3 plus
- Int/Float add
- Int+Float promotion
- String+String concat
- String mixed TypeError
---
## 7) Migration plan (if changing behavior)
Recommended two-step approach:
1) Compatibility freeze (Phase 274)
- Document current behavior (already in `types.md`)
- Add warnings / diagnostics where possible (no new env sprawl)
2) Switch semantics (Phase 275 or later)
- Implement A1/B2/C2 in VM and LLVM
- Add fixtures to lock the SSOT
- Ensure error messages provide “fix-it” guidance (`str(x)`, `x != Void`, etc.)

View File

@ -0,0 +1,123 @@
# Phase 274 (active): Type SSOT Alignment (local + dynamic runtime)
Status: active / design-first
Goal: make the **language-level type semantics** and the **runtime behavior** consistent and easy to reason about, without turning Nyash into a statically typed language.
This phase is about:
- clarifying SSOT docs,
- closing “frontend emits it but VM cant run it” gaps,
- and preventing “type facts / resolver guessing” from silently becoming language semantics.
---
## Background (why this exists)
Current state:
- Nyash is dynamic at runtime (VM executes tagged values).
- MIR builder attaches type metadata (`value_types`, `value_origin_newbox`) for routing/optimization.
- Some docs describe stricter semantics than the VM actually implements.
- `TypeOp` is emitted by the frontend for `is/as`.
Problems:
- Spec drift: quick docs vs runtime behavior differ (truthiness / equality / compare / `+`).
- Capability drift across backends: “VM is correct” but “LLVM harness differs” (TypeOp).
- Type metadata risks becoming implicit semantics via resolver fallback chains.
SSOT decisions should be expressed in:
- language docs (meaning),
- runtime (execution),
- and only then optimization facts (rewrite / routing).
---
## SSOT references
- Language type semantics (SSOT): `docs/reference/language/types.md`
- VM semantics source: `src/backend/abi_util.rs`, `src/backend/mir_interpreter/helpers.rs`
- MIR type vocabulary: `src/mir/types.rs`
- Call certainty vocabulary: `src/mir/definitions/call_unified.rs`
---
## Scope (P0/P1/P2/P3)
### P0 (docs-only): establish SSOT and remove contradictions
Deliverables:
- `docs/reference/language/types.md` (SSOT)
- Quick-reference points to SSOT and stops contradicting runtime
Acceptance:
- docs no longer claim semantics that the Rust VM clearly violates.
### P1 (impl): make `TypeOp` runnable on Rust VM
Goal:
- `x.is("T")` / `x.as("T")` lowering exists already; make it executable on the primary backend (Rust VM).
Acceptance (minimum):
- Rust VM implements `MirInstruction::TypeOp { op: Check|Cast, value, ty }`
- Add a small executable fixture/smoke that exercises `is/as`
- No new env vars; fail-fast errors on unsupported casts/checks
Implementation guide:
- `docs/development/current/main/phases/phase-274/P1-INSTRUCTIONS.md`
Status: ✅ done (2025-12-22)
Artifacts:
- Fixture: `apps/tests/phase274_p1_typeop_is_as_min.hako`
- Smoke: `tools/smokes/v2/profiles/integration/apps/phase274_p1_typeop_is_as_vm.sh`
- VM handler: `src/backend/mir_interpreter/handlers/type_ops.rs`
### P2 (impl): align LLVM (llvmlite harness) `TypeOp` to SSOT
Goal:
- Make LLVM harness behavior match Rust VM (SSOT) for `TypeOp(Check/Cast)`.
Current mismatch:
- `src/llvm_py/instructions/typeop.py` is stubbed:
- `is` returns 0 for most types (special-cases `IntegerBox` as “non-zero”)
- `cast/as` are pass-through
Acceptance (minimum):
- With `NYASH_LLVM_USE_HARNESS=1` + `--backend llvm`, the P1 fixture has the same observable result as Rust VM.
- Unsupported cases fail-fast (TypeError), not silent 0/“passthrough”.
- No new environment-variable toggles; differences must be fixed or explicitly documented.
Implementation guide:
- `docs/development/current/main/phases/phase-274/P2-INSTRUCTIONS.md`
Status: ✅ done (2025-12-22)
Artifacts:
- Kernel type check helper: `crates/nyash_kernel/src/lib.rs` (`nyash.any.is_type_h`)
- LLVM TypeOp lowering: `src/llvm_py/instructions/typeop.py`
- MIR JSON emission fix (bin): `src/runner/mir_json_emit.rs` (emit `op:"typeop"`)
- Fixture (LLVM-safe): `apps/tests/phase274_p2_typeop_primitives_only.hako`
- Smoke (LLVM): `tools/smokes/v2/profiles/integration/apps/phase274_p2_typeop_is_as_llvm.sh`
### P3 (decision + optional impl): tighten or document coercions
Decision points to settle (SSOT):
- Truthiness for arbitrary BoxRef (allow “any object is truthy” vs fail-fast)
- Equality cross-coercions (`int↔bool`, `int↔float`) — keep, restrict, or gate behind a profile
- `+` mixed numeric types (`int+float`) — keep TypeError or add explicit conversions
Acceptance:
- whichever behavior is chosen becomes consistent across backends and docs.
Decision memo (P3):
- `docs/development/current/main/phases/phase-274/P3-DECISIONS.md`
Status:
- P3 decisions are ✅ accepted; implementation is tracked in Phase 275.
---
## Non-goals
- Full static typing / inference engine
- Widening the language surface area (new keywords) as the first move
- Adding more environment-variable toggles as a long-term solution

View File

@ -0,0 +1,147 @@
# Phase 275 P0 (impl): Coercion SSOT rollout
Status: planned / implementation guide
This is the “next instruction sheet” for implementing the accepted coercion SSOT (A1/B2/C2) across backends.
SSOT decisions:
- `docs/development/current/main/phases/phase-274/P3-DECISIONS.md`
---
## Scope (P0)
Implement and lock these three rule sets:
- truthiness: `Void`/`BoxRef` fail-fast (with bridge-box exceptions)
- equality: B2 (Number-only, precise Int↔Float)
- `+`: C2 (Number-only promotion; String+String only; String mixed → TypeError)
Backends in scope:
- Rust VM (primary SSOT)
- LLVM harness (llvmlite path) parity with Rust VM
Out of scope:
- adding new language features (keywords)
- expanding env var toggles
- rewriting the optimizer broadly (only touch what is required to enforce semantics)
---
## Step 0: Lock reference + plan
- Keep `docs/reference/language/types.md` as “current executable SSOT” until the implementation is complete.
- After implementation + tests land, update `types.md` to the new semantics (it becomes SSOT again).
---
## Step 1: Rust VM — truthiness (A1)
Target:
- `src/backend/abi_util.rs::to_bool_vm` (or equivalent truthiness entry)
Changes:
- `Void` in boolean context → return `TypeError`
- `BoxRef`:
- allow only explicit bridge boxes: BoolBox/IntegerBox/StringBox
- treat VoidBox as Void (→ TypeError)
- other BoxRef types → TypeError
Acceptance:
- A dedicated fixture demonstrates `if Void { ... }` is a runtime error (fail-fast).
---
## Step 2: Rust VM — equality (B2)
Target:
- `src/backend/abi_util.rs::eq_vm` (and any helpers it relies on)
Changes:
- Remove Bool↔Int coercion.
- Keep Int↔Float comparison, but make it precise:
- if Float is NaN → false
- if Float is integral and within i64 exact range → compare as Int exactly
- otherwise → false
- Mixed kinds (except Int↔Float) → false (not error).
- BoxRef equality stays identity.
Acceptance:
- Tests cover:
- `1 == 1.0` true
- `1 == 1.1` false
- `true == 1` false (or TypeError if you choose a transition rule; document explicitly)
---
## Step 3: Rust VM — `+` (C2)
Target:
- `src/backend/mir_interpreter/helpers.rs::eval_binop` for `BinaryOp::Add`
Changes:
- Numeric:
- Int+Int → Int
- Float+Float → Float
- Int+Float / Float+Int → Float (Int promoted to Float)
- String:
- String+String → concat
- String mixed → TypeError (no implicit stringify)
- Everything else → TypeError
Acceptance:
- Tests cover:
- `1 + 2.0``3.0`
- `"a" + "b"``"ab"`
- `"a" + 1` → TypeError
---
## Step 4: LLVM harness parity
Targets (likely):
- `src/llvm_py/instructions/binop.py` for `+`
- `src/llvm_py/instructions/compare.py` for `==`
- truthiness for branch conditions (inspect branch lowering):
- `src/llvm_py/instructions/controlflow/branch.py`
- and any “truthy” conversions used in control-flow lowering
Notes:
- LLVM harness uses MIR JSON metadata (`value_types`) for discriminating raw vs handle.
- Keep behavior identical to Rust VM; do not re-introduce “string mixed concat”.
Acceptance:
- VM and LLVM smokes use the same fixtures and produce identical exit codes.
---
## Step 5: Fixtures + smoke tests (SSOT lock)
Create minimal, self-contained fixtures under `apps/tests/` and add smokes under `tools/smokes/v2/profiles/integration/apps/`.
Suggested fixtures (names; adjust as needed):
- `apps/tests/phase275_p0_truthiness_void_error_min.hako`
- `apps/tests/phase275_p0_eq_number_only_min.hako`
- `apps/tests/phase275_p0_plus_number_only_min.hako`
Smoke targets:
- VM: `..._vm.sh`
- LLVM: `..._llvm.sh` (harness/EXE path consistent with current infra)
Rules:
- No reliance on hidden env toggles.
- If a test needs runtime-unknown values, avoid externs that arent supported on VM/LLVM.
---
## Step 6: Update SSOT docs
After the implementation is complete and tests pass:
- Update `docs/reference/language/types.md` to the new semantics:
- truthiness: Void/BoxRef fail-fast
- equality: B2
- `+`: C2
- Update Phase 274/275 status:
- `docs/development/current/main/phases/phase-274/README.md`
- `docs/development/current/main/10-Now.md`
- `docs/development/current/main/30-Backlog.md`

View File

@ -0,0 +1,74 @@
# Phase 275 P1 (docs/lock): Update types.md + lock coercion matrix
Status: planned / instruction sheet
This is the follow-up after Phase 275 P0 (implementation) lands.
Prereq:
- Phase 275 P0 is complete and VM/LLVM smokes are green.
- Decisions are frozen in `docs/development/current/main/phases/phase-274/P3-DECISIONS.md`.
---
## 1) Update the language SSOT doc
File:
- `docs/reference/language/types.md`
Edits:
- Move “Accepted (Phase 275)” rules from “pending” into the **current executable SSOT** sections:
- truthiness: `Void` and non-bridge `BoxRef` become `TypeError`
- `==`: B2 (Number-only) with precise Int↔Float; Bool↔Number has no coercion
- `+`: C2 (Number-only promotion) + String+String only; String mixed `TypeError`
- Add a short “Migration notes” subsection:
- how to rewrite old code (`x != Void`, `str(x)`, interpolation)
- explicitly call out any intentionally-breaking changes
Acceptance:
- `types.md` no longer describes the legacy behavior (e.g. `Void -> false` in conditions, `"a"+1` concat).
---
## 2) Add the minimum test matrix fixtures (SSOT lock)
Goal:
- prevent semantic drift by locking the truthiness / `==` / `+` matrix in fixtures + smokes.
Add fixtures under `apps/tests/` (minimal, self-contained):
- `apps/tests/phase275_p0_truthiness_min.hako`
- `apps/tests/phase275_p0_eq_min.hako`
- `apps/tests/phase275_p0_plus_min.hako`
Rules:
- no env toggles required
- no dependency on unsupported externs/symbols on LLVM line
Add smokes under `tools/smokes/v2/profiles/integration/apps/`:
- `phase275_p0_truthiness_vm.sh` / `phase275_p0_truthiness_llvm.sh`
- `phase275_p0_eq_vm.sh` / `phase275_p0_eq_llvm.sh`
- `phase275_p0_plus_vm.sh` / `phase275_p0_plus_llvm.sh`
Acceptance:
- all 6 smokes pass (expected exit codes fixed and documented in the scripts).
---
## 3) Update “Now / Backlog”
Files:
- `docs/development/current/main/10-Now.md`
- `docs/development/current/main/30-Backlog.md`
- `docs/development/current/main/phases/phase-275/README.md`
Edits:
- Mark Phase 275 as ✅ complete (P0 done; P1 done).
- Move any remaining work (warnings/lints, broader coercion coverage) into Phase 276+ entries.
---
## 4) Optional: add diagnostics without new env vars (future phase)
If you want to surface legacy patterns proactively (without changing semantics again), create a new phase and keep it strictly “diagnostic-only”:
- warnings for `if Void`, string-mixed `+`, etc.
- no behavior toggles via new env vars

View File

@ -0,0 +1,50 @@
# Phase 275: Coercion Implementation (truthiness / `==` / `+`)
Status: ✅ completed (Phase 275 P0)
Goal: implement the accepted coercion SSOT across backends (Rust VM + LLVM harness) and update language docs and fixtures so behavior cannot drift.
Accepted SSOT decisions:
- `docs/development/current/main/phases/phase-274/P3-DECISIONS.md`
Language SSOT (updated after Phase 275):
- `docs/reference/language/types.md`
---
## What changes in this phase
Implement these semantics (A1/B2/C2):
1) truthiness (boolean context)
- `Void` in condition → **TypeError**
- `BoxRef` in condition → **TypeError**, except explicit bridge boxes (BoolBox/IntegerBox/StringBox)
- “object is always truthy” remains prohibited
2) equality (`==`)
- allow Int↔Float numeric comparison only (precise rule; no accidental true via float rounding)
- Bool is not a number: no Bool↔Int coercion
- other mixed kinds: deterministic `false` (not error)
- BoxRef equality: identity only
3) `+`
- numeric add: Int+Int, Float+Float, Int↔Float promotion to Float
- string concat: **String+String only**
- String mixed (`"a"+1`, `1+"a"`) → TypeError (no implicit stringify)
---
## Acceptance criteria (minimum)
- Rust VM behavior matches `P3-DECISIONS.md` for truthiness / `==` / `+`.
- LLVM harness behavior matches Rust VM (no backend divergence).
- `docs/reference/language/types.md` is updated to reflect the new executable SSOT.
- New fixtures + smokes lock behavior (VM + LLVM) without introducing environment-variable sprawl.
- No by-name hardcoding for “special cases”; if something must be special, it must be a documented bridge rule.
---
## Implementation guide
- `docs/development/current/main/phases/phase-275/P0-INSTRUCTIONS.md`
- `docs/development/current/main/phases/phase-275/P1-INSTRUCTIONS.md`

View File

@ -0,0 +1,65 @@
# Phase 276 P0: Quick wins (LLVM harness maintainability)
Status: ✅ completed (2025-12-22)
This sheet documents what Phase 276 P0 targeted (and can be used to reproduce the intent for future refactors).
Reference completion:
- `docs/development/current/main/phases/phase-276/P0-COMPLETION.md`
Non-goals:
- pipeline unification / order drift fixes (tracked as Phase 279)
- new language features
- new env vars
---
## Task 1: Remove noisy debug leftovers
Target:
- `src/llvm_py/phi_wiring/wiring.py`
Acceptance:
- no stacktrace spam in normal runs
---
## Task 2: Consolidate PHI dst type lookup into SSOT
Create:
- `src/llvm_py/phi_wiring/type_helper.py`
Provide:
- `get_phi_dst_type(...)` (SSOT for “what is the intended type of this ValueId?”)
- `dst_type_to_llvm_type(...)` (SSOT for MIR dst_type → LLVM IR type)
Integrate (remove duplicate logic):
- `src/llvm_py/phi_wiring/tagging.py`
- `src/llvm_py/llvm_builder.py`
- `src/llvm_py/phi_wiring/wiring.py`
Acceptance:
- the same ValueId gets the same effective type across these entry points
- adding a new MIR dst_type requires changing only `type_helper.py`
---
## Task 3: Make PHI type mismatch visible
Target:
- `src/llvm_py/phi_wiring/wiring.py`
Policy:
- important mismatch warnings should be visible even without debug env vars
- keep the full trace behind the existing debug gate
---
## Task 4: Confirm the change does not regress existing smokes
Minimum:
- build still succeeds
- representative LLVM harness runs still pass (no new failures)
Note:
- If a failure points to “two pipelines / ordering drift”, treat it as Phase 279 scope.

View File

@ -0,0 +1,22 @@
# Phase 276 P0: Quick wins (type helper SSOT)
Status: ✅ completed (2025-12-22)
Goal: keep the LLVM harness maintainable by removing “local heuristics” and consolidating type lookup into a single SSOT helper.
Scope (P0):
- Consolidate PHI destination type lookup into `type_helper.py` (SSOT).
- Remove noisy debug leftovers.
- Make “PHI type mismatch” more visible (fail-fast friendly diagnostics).
Docs:
- Instructions: `docs/development/current/main/phases/phase-276/P0-INSTRUCTIONS.md`
- Completion: `docs/development/current/main/phases/phase-276/P0-COMPLETION.md`
Note (important):
- The “two pipelines / order drift” root cause is tracked separately as **Phase 279** (pipeline SSOT unification).
Non-goals:
- introduce new language features
- widen the type system (no Union/Any work here)
- broad optimizer rewrite

View File

@ -0,0 +1,74 @@
# Phase 277 P0: PHI型推論ドキュメント整備design-first
Status: planned / docs
Goal: Phase 275/276 で導入・修正された PHI 型推論MIR→LLVM harnessについて、導線・責務・SSOT を “迷子にならない形” で固定する。
Scope:
- 実装のリファクタではなく **ドキュメントSSOT**
- “どのファイルが何の責務か” を明確にし、次回のデバッグで「触る場所」を 1 本化する。
Non-goals:
- 新しい env var 追加
- PHI アルゴリズム変更(仕様変更)
- 既定挙動変更
---
## 1) SSOT の入口を定義する
必ず最初に入口を 1 箇所に固定する:
- `docs/development/current/main/phases/phase-277/README.md` を「入口SSOT」にする
- PHI 関連の “概念” と “コードの参照先” を README から辿れるようにする
---
## 2) “2本のパイプライン” を防ぐ説明を入れる
このセッションで顕在化した事故:
- ルートAでは BinOp 型伝播→PHI 型解決
- ルートBでは PHI 型解決→BinOp 型伝播
結果:
- 同じ fixture が片方で PASS、片方で FAIL実質 “2本のコンパイラ”
P0 では、これを README で明示し、根治フェーズPhase 279へ誘導する文言を固定する。
---
## 3) コード責務マップ(最低限)
以下の “責務の箱” を docs に書き出す(箇条書きでよい):
- **MIR 側型情報のSSOT**
- `MirInstruction.dst_type` の意味instruction-local SSOT
- `value_types`propagation/inference の結果の意味analysis SSOT
- PHI の `dst_type` の意味PHI-local SSOT
- **JoinIR / bridge 側**
- “どのルートで type propagation が走るか”
- “どの段で value_types が更新されるか”
- **LLVM harness 側(消費者)**
- `type_helper.py` が SSOT であることPhase 276 P0
- `dst_type_to_llvm_type` の contract`f64` / `i64` handle / `void`
---
## 4) デバッグ導線(最小)
“迷子防止” のため、以下を docs に固定する:
- PHI関連の推奨 env varPhase 277 P2 の統合版)
- `NYASH_LLVM_DEBUG_PHI=1`
- `NYASH_LLVM_DEBUG_PHI_TRACE=1`
- `NYASH_LLVM_PHI_STRICT=1`
- 典型的な確認コマンド1〜2本だけ
- 失敗時に「次に見るファイル」を 1 行で指示type_helper → wiring → resolver の順など)
---
## 5) 完了条件
- README から “PHI型推論の導線” が 1 本で読める
- “2本のパイプライン” の危険と、根治フェーズPhase 279へのリンクが明示されている
- 既存 docs との矛盾がないPhase 275/276/277 の整合)

View File

@ -0,0 +1,79 @@
# Phase 277 P0: PHI型推論 SSOT docs を完成させるClaude Code 指示書)
Status: instructions / docs-only
目的:
- PHI型推論MIR→LLVM harnessの導線・責務・SSOT を **1本に固定**し、次回のデバッグで迷子にならないようにする。
スコープ:
- docs のみ(設計と導線の固定)
- 実装変更・仕様変更は行わない
Non-goals:
- “2本のコンパイラパイプライン差” の根治Phase 279
- env var 追加(禁止)
入口SSOT:
- `docs/development/current/main/phases/phase-277/README.md`
参照:
- P0設計メモ: `docs/development/current/main/phases/phase-277/P0-DESIGN.md`
- P2完了env var 統合): `docs/development/current/main/phases/phase-277/P2-COMPLETION.md`
- env vars: `docs/reference/environment-variables.md`
- type helper SSOTPhase 276 P0: `src/llvm_py/phi_wiring/type_helper.py`
---
## Step 1: README を “PHI型推論の地図” にする
`docs/development/current/main/phases/phase-277/README.md` に以下の節を追加/更新して、READMEだけ読めば導線が分かる状態にする。
必須内容短くてOK:
- 何が SSOT か(どのファイルが “決める” か)
- どこが consumer かLLVM harness が何を期待するか)
- どこを見れば原因が特定できるか(迷子防止)
最低限の “責務マップ”:
- MIR 側:
- `MirInstruction.dst_type`instruction-local
- propagated `value_types`analysis
- PHI `dst_type`PHI-local
- LLVM harness 側:
- PHI env var SSOT: `src/llvm_py/phi_wiring/debug_helper.py`
- 型取得 SSOT: `src/llvm_py/phi_wiring/type_helper.py`
- PHI placeholder SSOT: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- 順序検証: `src/llvm_py/phi_placement.py`(現状は verify/report
注意:
- llvmlite は基本 “命令の並べ替え” ができないことを明記するPHI-first は生成時に守る)。
- “2本のパイプライン” 問題は Phase 279 へリンクし、P0 で根治しないことを明確化する。
---
## Step 2: デバッグ導線(最小)を README に固定
README に以下を固定する1〜2コマンドだけ、冗長にしない:
- 推奨 env varPhase 277 P2 統合版)
- `NYASH_LLVM_DEBUG_PHI=1`
- `NYASH_LLVM_DEBUG_PHI_TRACE=1`
- `NYASH_LLVM_PHI_STRICT=1`
- 典型コマンド(例)
- `NYASH_LLVM_DEBUG_PHI=1 NYASH_LLVM_USE_HARNESS=1 ./target/release/hakorune --backend llvm apps/tests/<fixture>.hako`
- 失敗時の “次に見るファイル” を 1 行で指示(固定順)
- `type_helper.py → wiring.py → llvm_builder.py → resolver.py`
---
## Step 3: docs 整合チェック(最小)
確認:
- `docs/development/current/main/10-Now.md` の “次にやる” が Phase 277 を指していること
- `docs/development/current/main/30-Backlog.md` の Phase 277/278/279 が矛盾していないこと
Acceptance:
- README が入口SSOTとして成立READMEだけで導線が追える
- P0-DESIGN/P1-VALIDATION/P2-COMPLETION へリンクがある
- 新しい env var を増やしていない

View File

@ -0,0 +1,100 @@
# Phase 277 P1: PHI順序検証を fail-fast 導線に接続するClaude Code 指示書)
Status: instructions / validation
目的:
- PHI の “順序違反/配線欠落/型不整合” を **原因箇所で止める**fail-fast
- strict mode`NYASH_LLVM_PHI_STRICT=1`)が “実際に効く” 状態にする。
重要な制約:
- Phase 277 P1 は **LLVM harnessPython側の検証強化**が主。JoinIR/Rust の型伝播パイプラインには踏み込まない(根治は Phase 279
- env var 増殖禁止既存の3つのみ
- by-name hardcode 禁止(特定関数名の例外分岐などは増やさない)。
参照:
- 検証方針: `docs/development/current/main/phases/phase-277/P1-VALIDATION.md`
- PHI placeholder SSOT: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- PHI ordering verifier: `src/llvm_py/phi_placement.py::verify_phi_ordering`
- 実行導線現状SSOT: `src/llvm_py/builders/function_lower.py``_finalize_phis` 経路)
- env vars SSOT: `src/llvm_py/phi_wiring/debug_helper.py`
---
## Step 1: strict mode で “PHIが遅く作られた” を即死にする
対象:
- `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
現状:
- `bb.terminator` がある状態で PHI を作ろうとすると warning を出すだけ。
P1の変更:
- `NYASH_LLVM_PHI_STRICT=1` のときは fail-fast:
- 例: `raise RuntimeError(...)`block_id/dst_vid を含める)
- strict 以外は従来どおり warning + 継続(既定挙動を壊さない)。
Acceptance:
- “PHI after terminator” が strict で必ず落ちる。
- エラー文に `block_id`, `dst_vid`, “next file” を含める(迷子防止)。
---
## Step 2: strict mode で “fallback 0” を禁止する
対象候補実態に合わせて最小1箇所から:
- `src/llvm_py/phi_wiring/wiring.py::wire_incomings`
- もしくは `src/llvm_py/llvm_builder.py::finalize_phis`(ローカル実装が残っているので注意)
方針:
- incoming が解決できずに `0` を入れる分岐があるなら、strict で Err にする。
- Err には `block_id/dst_vid/pred_bid` を必ず含める。
注意:
- どの finalize 経路が SSOT かを明確にする(現状は `builders/function_lower.py` が実行導線)。
- “2本の finalize 実装” を統合するのは Phase 279 のスコープ。P1では SSOT 経路に検証を接続する。
Acceptance:
- strict で silent fallback が残っていない(少なくとも PHI incoming の “解決不能→0” は落ちる)。
---
## Step 3: `verify_phi_ordering()` を実行導線に接続する
対象:
- `src/llvm_py/phi_placement.py::verify_phi_ordering(builder)`
現状:
- 定義されているが、実行導線から呼ばれていない。
- llvmlite は reorder ができないため、verify/report の位置が重要。
接続点(推奨):
- `src/llvm_py/builders/function_lower.py` の関数 lowering の終盤:
- `lower_terminators(...)` の後(全命令が出揃った後)
- strict のときは NG を Err にする
- debug のときは block ごとのサマリを stderr に出す(`NYASH_LLVM_DEBUG_PHI=1`
Acceptance:
- strict で ordering NG を確実に検出して落とせる。
- debug で NG block の数と block_id が出る(過剰ログは避ける)。
---
## Step 4: 最小の回帰確認
目的:
- “検証が増えたせいで全部が落ちる” を避けつつ、狙った違反を確実に捕まえる。
推奨:
- 代表 fixture を1つ選び、まず strict=OFF で PASS、strict=ON でも PASS を確認(正常系)。
- 既知の壊れ方PHI late create / missing incomingを意図的に起こす最小再現があるなら、それで strict で落ちることも確認。
No new env vars.
---
## Completion criteria
- strict mode が “順序違反/配線欠落” を原因箇所で fail-fast できる
- `verify_phi_ordering()` が実行導線に接続されている
- 既定strict=OFFでの挙動は壊さない
- Phase 279根治へ繋がる前提が docs で明確になっている

View File

@ -0,0 +1,102 @@
# Phase 277 P1: PHI順序検証強化validation
Status: planned / validation
Goal: PHI placement/order を fail-fast で検出しやすくし、LLVM harness の “後段で壊れる” ではなく “原因箇所で止まる” を実現する。
Scope:
- 検証とエラーメッセージの改善(実装は最小・局所)
- “順序違反” と “型不整合” の可観測性を上げる
Non-goals:
- 新しい env var 追加
- 大規模なパイプライン統一Phase 279
---
## 1) 何を検証するか契約SSOT
最低限、この契約を SSOT として明文化する:
- **Block内順序**:
- PHI 群
- non-PHI 命令群
- terminatorBranch/Jump/Return
- この順序以外は “バグ” として扱う
- **PHI 入力の完全性**:
- incoming が欠ける場合は fail-fast既定で silent fallback をしない)
- strict mode`NYASH_LLVM_PHI_STRICT=1`)では必ず Err
- **型整合**:
- `dst_type` と実際に生成する LLVM type が一致していること
- mismatch を “CRITICAL” として可視化するPhase 276 P0 の方針を踏襲)
---
## 2) 実装ポイント(現状コードに合わせた最小)
現状の構造(要点):
- llvmlite は “命令の並べ替え” が基本できないため、PHI-first は **生成時**に守る必要がある
- `src/llvm_py/phi_placement.py` は “reorder” ではなく “verify/report” が主
- PHI 配線は `finalize_phis` で行われるPHI placeholder 作成→incoming 配線)
- 実際のSSOT呼び出しは `src/llvm_py/builders/function_lower.py``_finalize_phis(builder, context)` 経路
- `NyashLLVMBuilder.finalize_phis()` は別実装が残っており、P1では **どちらをSSOTにするか**を明示する
実装点(推奨):
1) **“PHIを遅く作ってしまった” を strict で即死**
- 対象: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- すでに `bb.terminator` を検知して warning を出している
- P1では `NYASH_LLVM_PHI_STRICT=1` のとき、ここを fail-fast例: `raise` / `unreachable` 相当)にする
- 期待効果: “順序違反の原因” で止まる
2) **fallback 0 の採用を strict で禁止**
- 対象: PHI incoming を解決できず `0` を選ぶ箇所
- `src/llvm_py/llvm_builder.py` および `src/llvm_py/phi_wiring/wiring.py::wire_incomings` に存在
- P1では strict のとき:
- “missing snapshot / unresolved” を明示エラーにする
- エラー文に `block_id / dst_vid / pred_bid` を含める
3) **PHI ordering verifier を “実行経路に接続”**
- 現状 `src/llvm_py/phi_placement.py::verify_phi_ordering(builder)` が未使用
- P1では呼び出し点を 1 箇所に固定する:
- 候補: `src/llvm_py/builders/function_lower.py``lower_terminators(...)`
- strict のときは ordering NG を Err にする
- debug のときは詳細を stderr に出す(`NYASH_LLVM_DEBUG_PHI=1`
補足:
- ここで reorder はできないので、verifier は “最後に怒る” ではなく
“生成時の契約が破られていないことを確認する” 目的で使う
---
## 3) エラーメッセージ(迷子防止)
エラー文は必ず以下を含める:
- block id
- dst ValueIdPHIの対象
- expected vs actual型/順序)
- 次に見るファイル1つ、固定
推奨:
- ordering なら `src/llvm_py/phi_wiring/wiring.py`PHI生成の入口
- missing incoming なら `src/llvm_py/llvm_builder.py`snapshot/value解決の入口
---
## 4) 最小テスト
P1 の目的は “検証が働くこと” なので、最小の再現でよい:
- 既存の PHI を含む fixture を 1 つ選ぶPhase 275 のものなど)
- strict mode で実行して、違反があれば落ちることを確認する
No new CI jobs.
---
## 5) 完了条件
- PHI順序違反が “原因箇所で” fail-fast する
- strict mode が意味を持つsilent fallback が残っていない)
- 既存の正常ケース(代表スモーク)が退行しない

View File

@ -4,6 +4,15 @@
Phase 275/276で完了したFloat型PHI対応・型取得SSOT化の後続改善として、PHI関連の環境変数統合・ドキュメント整備を実施。
このPhaseの狙いは「PHIまわりの迷子を無くす」こと
- どの層が何を決めるかSSOTを固定する
- PHI順序/配線の違反を “後段で壊れる” ではなく “原因で止まる” に寄せる
- そして根治として「2本のコンパイラパイプライン差による二重バグ」を Phase 279 で潰せるように導線を引く
入口(関連):
- Now: `docs/development/current/main/10-Now.md`
- Backlog: `docs/development/current/main/30-Backlog.md`
---
## サブフェーズ一覧
@ -13,8 +22,12 @@ Phase 275/276で完了したFloat型PHI対応・型取得SSOT化の後続改善
- 目的: Phase 275/276で実装したPHI型推論ロジックのドキュメント化
- 内容:
- MIR型伝播 → LLVM IR型生成のフロー図
- type_helper.py の設計ドキュメント
- type_helper.pyLLVM harness 側の型取得SSOTの設計ドキュメント
- PHI型推論のベストプラクティス
- 設計メモこのPhase配下のSSOT案:
- `docs/development/current/main/phases/phase-277/P0-DESIGN.md`
- 指示書Claude Code:
- `docs/development/current/main/phases/phase-277/P0-INSTRUCTIONS.md`
### Phase 277 P1: PHI順序検証強化予定
@ -23,6 +36,10 @@ Phase 275/276で完了したFloat型PHI対応・型取得SSOT化の後続改善
- phi_placement.py の検証ロジック強化
- LLVM IR仕様準拠チェックPHI → 非PHI → terminator
- 順序違反時のエラーメッセージ改善
- 検証メモこのPhase配下のSSOT案:
- `docs/development/current/main/phases/phase-277/P1-VALIDATION.md`
- 指示書Claude Code:
- `docs/development/current/main/phases/phase-277/P1-INSTRUCTIONS.md`
### Phase 277 P2: PHI関連環境変数の統合・整理 ✅
@ -65,6 +82,7 @@ NYASH_LLVM_PHI_STRICT=1
- **Phase 275**: Float型PHI対応MIR型伝播 → LLVM IR double生成
- **Phase 276**: 型取得SSOT化type_helper.py
- **Phase 278**: 後方互換性削除(旧環境変数サポート削除予定)
- **Phase 279**: パイプラインSSOT統一“2本のコンパイラ” 根治)
---
@ -74,12 +92,36 @@ NYASH_LLVM_PHI_STRICT=1
phase-277/
├── README.md # 本ファイルPhase 277概要
├── P2-COMPLETION.md # P2完了報告
├── P0-DESIGN.md # P0設計ドキュメント予定
└── P1-VALIDATION.md # P1検証強化ドキュメント予定
├── P0-DESIGN.md # P0設計ドキュメントdocs
└── P1-VALIDATION.md # P1検証強化ドキュメントvalidation
```
---
## 重要なSSOTどこが何を決めるか
最小の地図(迷ったらここから辿る):
- **PHI env var統合SSOT**: `src/llvm_py/phi_wiring/debug_helper.py`
- 使う側は `is_phi_debug_enabled()` / `is_phi_trace_enabled()` / `is_phi_strict_enabled()` だけを見る
- 旧 env var の撤去は Phase 278
- **LLVM harness 側の型取得SSOT**: `src/llvm_py/phi_wiring/type_helper.py`
- `get_phi_dst_type(...)``dst_type_to_llvm_type(...)` が入口
- “PHIのdst_typeをどこから取るか” をここに集約するPhase 276 P0
- **PHI placeholder を block head に作るSSOT**: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- llvmlite は “後から命令を並べ替える” が基本できない
- よって PHI は “作るタイミング” が勝負PHI-first の契約をここで守る)
- **順序検証verifier**: `src/llvm_py/phi_placement.py`
- 現状は “並べ替え” ではなく “検証/レポート” のみllvmlite制約
- Phase 277 P1 で fail-fast 導線を強化するstrict mode の意味を強くする)
- **根治(パイプライン二重化の解消)**: `docs/development/current/main/phases/phase-279/README.md`
---
## 今後の予定
1. **Phase 277 P0**: PHI型推論ドキュメント整備

View File

@ -0,0 +1,89 @@
# Phase 278 P0: Remove deprecated PHI debug env vars
Status: planned / cleanup
Goal: remove legacy PHI-related environment variables that were consolidated in Phase 277 P2, so the ecosystem converges to a single set of PHI debug knobs.
SSOT references:
- Consolidation completion: `docs/development/current/main/phases/phase-277/P2-COMPLETION.md`
- Env var reference (current): `docs/reference/environment-variables.md`
Target SSOT (post-Phase 278):
- `NYASH_LLVM_DEBUG_PHI=1`
- `NYASH_LLVM_DEBUG_PHI_TRACE=1`
- `NYASH_LLVM_PHI_STRICT=1`
Non-goals:
- introduce new debug toggles
- change PHI behavior (only remove deprecated inputs)
- change CLI defaults
---
## 1) Identify deprecated env vars (remove inputs)
Remove support for the legacy variables that were “merged” in Phase 277 P2.
Expected deprecated set (verify in `docs/reference/environment-variables.md`):
- `NYASH_LLVM_PHI_DEBUG`
- `NYASH_PHI_TYPE_DEBUG`
- `NYASH_PHI_ORDERING_DEBUG`
- `NYASH_LLVM_TRACE_PHI`
- `NYASH_LLVM_VMAP_TRACE`
- `NYASH_PYVM_DEBUG_PHI` (if still present in code; PyVM line is historical)
Policy:
- Do not keep “silent compatibility”.
- If a deprecated var is detected, print a one-line error with the replacement and exit non-zero.
Rationale:
- Keeping deprecated behavior is how env var sprawl comes back.
---
## 2) Update the runtime checks to accept only the SSOT set
Target:
- `src/llvm_py/phi_wiring/debug_helper.py` (SSOT entry for PHI debug flags)
Acceptance:
- only the SSOT variables are read
- deprecated variables trigger a clear failure message (replacement shown)
---
## 3) Update documentation (SSOT)
Update:
- `docs/reference/environment-variables.md`
Requirements:
- Remove deprecated entries from tables (or move them to a “Removed in Phase 278” section).
- Keep examples only with `NYASH_LLVM_DEBUG_PHI`, `NYASH_LLVM_DEBUG_PHI_TRACE`, `NYASH_LLVM_PHI_STRICT`.
- Add a short migration note:
- “If you used X, replace with Y.”
---
## 4) Add/Update smoke coverage
Minimum:
- One LLVM harness smoke that runs with:
- `NYASH_LLVM_DEBUG_PHI=1`
- `NYASH_LLVM_DEBUG_PHI_TRACE=1`
- `NYASH_LLVM_PHI_STRICT=1`
and verifies the run completes (no strict-mode violations in the fixture).
Deprecation enforcement:
- One smoke (or a small shell snippet in an existing test) that sets a deprecated var and expects a non-zero exit.
No new env vars.
---
## 5) Completion criteria
- Deprecated env vars no longer affect behavior (removed from code paths).
- Deprecated env vars cause a fail-fast error with a replacement hint.
- Docs reflect only the SSOT set.
- The representative smokes remain green.

View File

@ -0,0 +1,18 @@
# Phase 278 (planned): Remove deprecated PHI debug env vars
Status: planned / cleanup
Goal: remove legacy PHI debug environment variables that were consolidated in Phase 277 P2, so the ecosystem converges to a single, memorable set.
Reference:
- Consolidation doc: `docs/development/current/main/phases/phase-277/P2-COMPLETION.md`
- Env var reference: `docs/reference/environment-variables.md` (PHI デバッグ関連)
Target SSOT (post-Phase 278):
- `NYASH_LLVM_DEBUG_PHI=1`
- `NYASH_LLVM_DEBUG_PHI_TRACE=1`
- `NYASH_LLVM_PHI_STRICT=1`
Implementation guide:
- `docs/development/current/main/phases/phase-278/P0-INSTRUCTIONS.md`

View File

@ -0,0 +1,96 @@
# Phase 279 P0: Unify type propagation pipeline (SSOT)
Status: planned / implementation
Problem:
- Type propagation and PHI type resolution are executed in multiple routes with different orderings.
- This creates “double bugs”: the same fixture can pass in one route and fail in another, even when the frontend and MIR are identical.
Goal:
- Define a single **TypePropagationPipeline** (SSOT) with a fixed order, and make all routes call it.
Constraints:
- No new environment variables.
- No by-name hardcode dispatch.
- Fail-fast on invariants (no silent fallback).
---
## 1) Define the SSOT pipeline (single entry)
Create one entry function (name is flexible, keep it unambiguous):
- `run_type_propagation_pipeline(module: &mut MirModule, mode: ...) -> Result<(), String>`
Fixed order (SSOT):
1. Copy type propagation
2. BinOp type re-propagation
3. PHI type resolution
4. Minimal follow-ups required for downstream typing (Compare / TypeOp / etc.), only if already needed by current backends
Hard rule:
- No route may run PHI type resolution before BinOp re-propagation.
Rationale:
- PHI type inference depends on stabilized incoming value types.
---
## 2) Route integration (remove local ordering)
Make all relevant routes call the SSOT entry and remove/disable any local ordering logic.
Known routes to check (examples; confirm in code):
- Builder lifecycle path (emits MIR directly)
- JoinIR → MIR bridge path
- Any “analysis/json emit” path that downstream LLVM harness relies on for `value_types`
Acceptance:
- Each route calls the same SSOT entry.
- There is no remaining “partial pipeline” that can reorder steps.
---
## 3) Fail-fast order guard (prevent regressions)
Add an invariant checker that makes order drift obvious:
- If PHI type resolution is invoked while BinOp re-propagation has not run, return `Err(...)`.
This is not a feature toggle. It is a structural guard.
---
## 4) Backends: define the contract for `value_types`
Document (in code/doc) what the downstream expects:
- A `ValueId` that is `f64` must be consistently typed as `f64` across:
- MIR instruction dst_type
- propagated/inferred `value_types`
- PHI dst_type
- “i64 handle” vs “unboxed f64” must be consistent for the LLVM harness.
Avoid “best-effort” inference at the harness layer. If the type is unknown, fail-fast where the SSOT contract is violated.
---
## 5) Minimal acceptance tests
Minimum:
- A representative fixture that exercises:
- Copy chain
- BinOp promotion (e.g. Int+Float)
- PHI over that promoted value
and is executed via all relevant routes.
- The MIR JSON `value_types` is consistent across routes.
Suggested validation commands (keep local, do not add CI jobs):
- `cargo build --release`
- relevant smoke(s): `tools/smokes/v2/run.sh --profile quick`
---
## 6) Completion criteria
- No route-specific ordering logic remains.
- Order guard prevents PHI-before-BinOp execution.
- A reproduction fixture cannot diverge across routes due to type propagation ordering.
- Documentation points to this phase as the SSOT for “pipeline unification”.

View File

@ -0,0 +1,25 @@
# Phase 279 (planned): Type propagation pipeline SSOT unification
Status: planned / implementation
Goal: eliminate “two compiler pipelines” by making type propagation run through **one SSOT entry** with a fixed order across routes, so the same fixture cannot pass in one route and fail in another purely due to ordering drift.
Background trigger:
- A real incident occurred where PHI type resolution ran before BinOp re-propagation in one route, but after it in another, producing LLVM parity breakage. This is effectively “two compilers”.
Scope:
- Define a single type propagation pipeline entry (SSOT).
- Make every route that emits MIR/LLVM metadata call that SSOT entry.
- Add fail-fast guards that make order drift impossible to miss.
SSOT references:
- Current status log: `docs/development/current/main/10-Now.md`
- Backlog entry: `docs/development/current/main/30-Backlog.md`
Implementation guide:
- `docs/development/current/main/phases/phase-279/P0-INSTRUCTIONS.md`
Non-goals:
- new language features (no Union/Any)
- broad optimizer rewrite
- adding new environment variables