162 lines
6.8 KiB
Markdown
162 lines
6.8 KiB
Markdown
|
|
Status: Completed (Blocked by Phase 255)
|
|||
|
|
Scope: Phase 254 (`--profile quick` 回帰: JoinIR 未対応 loop パターン(StringUtils.index_of/2))
|
|||
|
|
Related:
|
|||
|
|
- docs/development/current/main/10-Now.md
|
|||
|
|
- docs/development/current/main/phases/phase-253/README.md
|
|||
|
|
- docs/development/current/main/phases/phase-255/README.md (次フェーズ: multi-param loop wiring)
|
|||
|
|
|
|||
|
|
# Phase 254: `StringUtils.index_of/2` の loop パターンを JoinIR で受理する
|
|||
|
|
|
|||
|
|
## 現象(最初の FAIL)
|
|||
|
|
|
|||
|
|
`./tools/smokes/v2/run.sh --profile quick` が `json_lint_vm` で失敗する。
|
|||
|
|
|
|||
|
|
エラー:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.
|
|||
|
|
Function: StringUtils.index_of/2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 対象の Nyash コード(最小形)
|
|||
|
|
|
|||
|
|
`apps/lib/json_native/utils/string.hako`:
|
|||
|
|
|
|||
|
|
```nyash
|
|||
|
|
index_of(s, ch) {
|
|||
|
|
local i = 0
|
|||
|
|
loop(i < s.length()) {
|
|||
|
|
if s.substring(i, i + 1) == ch { return i }
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
return -1
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
特徴:
|
|||
|
|
- while ループ(`i < s.length()`)
|
|||
|
|
- loop body 内に `if (...) { return i }`
|
|||
|
|
- tail に `i = i + 1`
|
|||
|
|
- ループ後に `return -1`
|
|||
|
|
|
|||
|
|
## 問題の構造(なぜ落ちるか)
|
|||
|
|
|
|||
|
|
- 現状は LoopBuilder が削除済みなので、JoinIR パターンがマッチしない loop はすべて freeze する(フォールバックなし)。
|
|||
|
|
- つまり “この loop 形” を JoinIR パターンとして受理できるようにするしかない。
|
|||
|
|
|
|||
|
|
## 解決方針(構造的)
|
|||
|
|
|
|||
|
|
### 方針 A(推奨・最小): 「index_of 形」を Pattern として追加する
|
|||
|
|
|
|||
|
|
- “箱”として 1つ追加し、1つの質問だけに答える:
|
|||
|
|
- 「`index_of` 形の loop を JoinIR に lowering できるか?」
|
|||
|
|
- by-name(関数名/Box名)でのディスパッチは禁止。
|
|||
|
|
- 形(構造)だけでマッチすること。
|
|||
|
|
|
|||
|
|
### 方針 B(非推奨): freeze を回避するための例外ルート
|
|||
|
|
|
|||
|
|
- LoopBuilder が無い以上、ここで例外 fallback を入れるのは Fail-Fast 原則違反になりやすい。
|
|||
|
|
- 例: “Unsupported なら解釈器で実行” のような逃げ道は作らない。
|
|||
|
|
|
|||
|
|
## 実装タスク(P0)
|
|||
|
|
|
|||
|
|
### 1) 構造抽出(docs → interface)
|
|||
|
|
|
|||
|
|
- どの AST/StepTree 形を受理するかを README or doc comment に明記
|
|||
|
|
- 必須: loop cond が `<`/`<=` の比較で、rhs が `s.length()` または定数/変数(Phase 251/252 の流れと整合)
|
|||
|
|
- 必須: loop 内 if が “then return i” である
|
|||
|
|
- 必須: update が `i = i + 1`(Phase 253 で analyzer を緩くしたので、ここは loop パターン側の契約にする)
|
|||
|
|
|
|||
|
|
### 2) 新しい lowering 箱(案)
|
|||
|
|
|
|||
|
|
候補配置:
|
|||
|
|
- `src/mir/join_ir/lowering/loop_patterns/` 配下に `index_of/` を作り、`lower_box.rs` を置く
|
|||
|
|
- 目的: 既存 `loop_with_*` 巨大ファイルに寄せず、責務を分離する(設計優先)
|
|||
|
|
|
|||
|
|
入出力(案):
|
|||
|
|
- 入力: loop condition AST, loop body AST, `current_static_box_name`, env/JoinValueSpace など(Pattern2 と同等の配線)
|
|||
|
|
- 出力: `(JoinModule, JoinFragmentMeta)`(既存パターンと同様)
|
|||
|
|
|
|||
|
|
### 3) condition lowering の利用
|
|||
|
|
|
|||
|
|
- `s.substring(i, i+1) == ch` の中で `substring` が必要になる。
|
|||
|
|
- CoreMethodId では `StringSubstring` は `allowed_in_init=true` / `allowed_in_condition=false`。
|
|||
|
|
- ここは “value expression” として lowering するため、
|
|||
|
|
- 既存 `condition_lowerer::lower_value_expression` / `MethodCallLowerer::lower_for_init` のルールに合わせる
|
|||
|
|
- 条件全体の bool は `Compare` で生成(JoinIR の `MirLikeInst::Compare`)
|
|||
|
|
|
|||
|
|
### 4) テストと fixture(仕様固定)
|
|||
|
|
|
|||
|
|
- unit test:
|
|||
|
|
- “index_of 形の loop がマッチする” / “JoinIR が生成される” を固定
|
|||
|
|
- v2 smoke fixture:
|
|||
|
|
- `apps/tests/phase254_p0_index_of_min.hako`
|
|||
|
|
- `tools/smokes/v2/profiles/quick/apps/phase254_p0_index_of_vm.sh`(軽いなら quick へ、重いなら integration)
|
|||
|
|
|
|||
|
|
## 受け入れ基準
|
|||
|
|
|
|||
|
|
- `./tools/smokes/v2/run.sh --profile quick` が PASS
|
|||
|
|
- by-name 分岐を追加していない(構造のみでマッチ)
|
|||
|
|
- unsupported の場合は明確な Err(freeze)を維持しつつ、対象形は通る
|
|||
|
|
|
|||
|
|
## 進捗(P0/P1 完了)
|
|||
|
|
|
|||
|
|
### ✅ 完了項目
|
|||
|
|
|
|||
|
|
- **Task 1**: 最小 fixture + smoke scripts(integration): ✅ 完了
|
|||
|
|
- `apps/tests/phase254_p0_index_of_min.hako`
|
|||
|
|
- `tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh`
|
|||
|
|
- `tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_llvm_exe.sh`
|
|||
|
|
|
|||
|
|
- **Task 2**: デバッグ実行(現状把握): ✅ 完了
|
|||
|
|
- Pattern 3 と判定されるが "if-sum ではない" で reject される
|
|||
|
|
- loop_canonicalizer の `ConstStep` 不足で fail するケースがある
|
|||
|
|
|
|||
|
|
- **Task 3**: Pattern6 DetectorBox 実装: ✅ 完了
|
|||
|
|
- `src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs` - can_lower()
|
|||
|
|
- Pattern 3 より前に配置(router priority 調整)
|
|||
|
|
- `Pattern2Break` と `Pattern3IfPhi` の双方の分類に対応
|
|||
|
|
- **検出成功**: `Pattern6_ScanWithInit MATCHED` を確認
|
|||
|
|
|
|||
|
|
- **Task 4**: ScanWithInit Lowerer 実装: ✅ 完了
|
|||
|
|
- **Task 4-1**: extract_scan_with_init_parts() - 構造抽出関数実装
|
|||
|
|
- **Task 4-2**: scan_with_init_minimal.rs - JoinIR lowerer(main/loop_step/k_exit 生成)
|
|||
|
|
- **Task 4-3**: MirBuilder 統合 - boundary 構築と JoinIRConversionPipeline 実行
|
|||
|
|
- **Task 4-4**: mod.rs 登録
|
|||
|
|
|
|||
|
|
### ❌ ブロッカー(Phase 255 へ引き継ぎ)
|
|||
|
|
|
|||
|
|
**現象**: Integration テスト失敗
|
|||
|
|
```
|
|||
|
|
[ERROR] ❌ [rust-vm] VM error: Invalid value:
|
|||
|
|
[rust-vm] use of undefined value ValueId(10)
|
|||
|
|
(fn=StringUtils.index_of/2, last_block=Some(BasicBlockId(4)),
|
|||
|
|
last_inst=Some(Compare { dst: ValueId(14), op: Ge, lhs: ValueId(10), rhs: ValueId(13) }))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**根本原因**: JoinIR→MIR merge/boundary システムが**複数ループ変数を想定していない**
|
|||
|
|
|
|||
|
|
- 現状の仕様: Pattern 1-5 は単一ループ変数前提(例: `i` のみ)
|
|||
|
|
- Pattern 6 の要求: 3変数ループ(`s`, `ch`, `i`)
|
|||
|
|
- 問題: PHI ノードが 1つしか作られない(`s` のみ)、`ch` と `i` が undefined
|
|||
|
|
|
|||
|
|
**影響範囲**:
|
|||
|
|
- `JoinInlineBoundaryBuilder` の `with_loop_var_name()` が単一変数想定
|
|||
|
|
- `exit_bindings` が単一 carrier 用に設計
|
|||
|
|
- `JoinIRConversionPipeline` が複数 PHI 作成に未対応
|
|||
|
|
|
|||
|
|
**Phase 254 の受け入れ境界**:
|
|||
|
|
- ✅ Pattern 6 が検出される
|
|||
|
|
- ✅ JoinIR が正しく生成される(main/loop_step/k_exit 構造)
|
|||
|
|
- ✅ substring が BoxCall として init-time に emit される
|
|||
|
|
- ❌ 実行(VM/LLVM)で PASS にするのは **Phase 255 の範囲**
|
|||
|
|
|
|||
|
|
### 次フェーズ(Phase 255)
|
|||
|
|
|
|||
|
|
詳細: [docs/development/current/main/phases/phase-255/README.md](phase-255/README.md)
|
|||
|
|
|
|||
|
|
課題:
|
|||
|
|
- Multi-param loop の boundary/PHI/wiring を SSOT 化
|
|||
|
|
- LoopState(i)と invariants(s, ch)を分けて wiring
|
|||
|
|
- 受け入れ: phase254_p0_index_of の integration テストが PASS
|