feat(joinir): Phase 200-A ConditionEnv extension infrastructure
Added type and skeleton infrastructure for function-scoped variable
capture, preparing for Phase 200-B integration with ConditionEnv.
New Types:
- CapturedVar: { name, host_id, is_immutable }
- CapturedEnv: Collection of captured variables
- ParamRole: { LoopParam, Condition, Carrier, ExprResult }
New Functions (Skeletons):
- analyze_captured_vars(): Detects function-scoped "constants"
- build_with_captures(): ConditionEnvBuilder v2 entry point
- add_param_with_role(): Role-based parameter routing
New File:
- src/mir/loop_pattern_detection/function_scope_capture.rs
Design Principles:
- Infra only: Types and skeletons, no behavior changes
- Existing behavior maintained: All current loops work identically
- Box-first: New responsibilities in new file
- Documentation: Future implementation plans in code comments
Test Results:
- 6 new unit tests (function_scope_capture: 3, param_role: 3)
- All 804 existing tests PASS (0 regressions)
Next: Phase 200-B (actual capture detection and integration)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -389,6 +389,21 @@
|
||||
- **次候補**:
|
||||
- Phase 200+: ConditionEnv 拡張(_parse_number, _atoi)
|
||||
- Phase 198+: JsonParser 残りループ個別対応
|
||||
- [x] **Phase 200-A: ConditionEnv 拡張インフラ(型と箱のみ)** ✅ (完了: 2025-12-09)
|
||||
- **目的**: ConditionEnv を壊さずに関数スコープ "実質定数" を扱う基盤
|
||||
- **実施内容**:
|
||||
- 200-A-1: CapturedVar / CapturedEnv 型導入 ✅
|
||||
- 200-A-2: FunctionScopeCaptureAnalyzer スケルトン ✅
|
||||
- 200-A-3: ConditionEnvBuilder v2 入口 ✅
|
||||
- 200-A-4: ParamRole enum 追加 ✅
|
||||
- 200-A-5: ドキュメント更新 ✅
|
||||
- **スコープ**: Infra only / Integration pending
|
||||
- **成果**:
|
||||
- 型と箱の定義完了 ✅
|
||||
- 既存ループの挙動変更なし ✅
|
||||
- Phase 200-B への準備完了 ✅
|
||||
- **次フェーズ**: Phase 200-B(digits 系ループへの適用)
|
||||
- **詳細**: phase200-A-conditionenv-infra.md
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -53,6 +53,19 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
||||
BoundaryInjector や InstructionRewriter が `Copy` などで二度書きすることを禁止する。
|
||||
- merge ラインでは「ヘッダ PHI dst に対する新しい定義が出てきたら debug モードで panic する」ことで契約違反を早期検出する。
|
||||
|
||||
9. **ParamRole の不変条件(Phase 200-A 追加)**
|
||||
- **Condition 役のパラメータは「PHI dst にしてはいけない」**
|
||||
- 理由: 条件専用変数はループ内で更新されない(例: `digits` in `_atoi()`)
|
||||
- LoopHeaderPhiBuilder は Condition 役の変数に対して header PHI を生成しない
|
||||
- **Condition 役のパラメータは「ExitLine の対象にも入れない」**
|
||||
- 理由: 条件専用変数はループ外で使われない(ループ内でのみ参照)
|
||||
- ExitLineReconnector は Condition 役の変数を exit_bindings から除外
|
||||
- **ParamRole の分類**:
|
||||
- `LoopParam`: ループ制御変数(例: `i` in `loop(i < len)`)→ header PHI + exit_bindings
|
||||
- `Condition`: 条件専用変数(例: `digits` in `digits.indexOf(ch)`)→ condition_bindings のみ
|
||||
- `Carrier`: 状態更新変数(例: `sum`, `count`)→ header PHI + exit_bindings
|
||||
- `ExprResult`: ループ戻り値 → exit_phi_builder で処理
|
||||
|
||||
---
|
||||
|
||||
## 2. 主な箱と責務
|
||||
@ -262,6 +275,35 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
||||
- `verify_joinir_contracts()`: merge_joinir_mir_blocks() の最後で全契約を一括チェック。
|
||||
- release ビルドでは完全に除去される(`#[cfg(debug_assertions)]`)。
|
||||
|
||||
- **FunctionScopeCaptureAnalyzer / CapturedEnv(Phase 200-A 追加)**
|
||||
- ファイル: `src/mir/loop_pattern_detection/function_scope_capture.rs`
|
||||
- 責務:
|
||||
- 関数スコープで宣言され、ループ内で不変な変数("実質定数")を検出。
|
||||
- 例: `local digits = "0123456789"` in `JsonParser._atoi()`
|
||||
- CapturedVar: `{ name, host_id, is_immutable }`
|
||||
- CapturedEnv: 検出された変数のコレクション
|
||||
- **Phase 200-A**: 型と空実装のみ(skeleton)。
|
||||
- **Phase 200-B**: 実際の検出ロジックを実装予定(AST スキャン + 再代入チェック)。
|
||||
|
||||
- **ParamRole enum(Phase 200-A 追加)**
|
||||
- ファイル: `src/mir/join_ir/lowering/inline_boundary_builder.rs`
|
||||
- 責務:
|
||||
- JoinInlineBoundary のパラメータ役割を明示的に区別。
|
||||
- LoopParam / Condition / Carrier / ExprResult の 4 種類。
|
||||
- **不変条件**:
|
||||
- **Condition 役**: PHI dst にしてはいけない(ループ内で更新されない)。
|
||||
- **Condition 役**: ExitLine の対象にも入れない(ループ外で使われない)。
|
||||
- 理由: 条件専用変数(例: `digits`)はループ内でのみ参照され、不変。
|
||||
- **Phase 200-A**: enum 定義のみ。
|
||||
- **Phase 200-B**: ルーティングロジック実装予定(CapturedEnv 統合時)。
|
||||
|
||||
- **ConditionEnvBuilder::build_with_captures(Phase 200-A 追加)**
|
||||
- ファイル: `src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs`
|
||||
- 責務:
|
||||
- 将来 CapturedEnv を受け取り、ConditionEnv に統合する v2 入口。
|
||||
- **Phase 200-A**: 既存実装に委譲する skeleton。
|
||||
- **Phase 200-B**: CapturedEnv の変数を condition_bindings に追加する実装予定。
|
||||
|
||||
### 2.4 expr result ライン(式としての戻り値)
|
||||
|
||||
- **exit_phi_builder**
|
||||
|
||||
446
docs/development/current/main/phase200-A-conditionenv-infra.md
Normal file
446
docs/development/current/main/phase200-A-conditionenv-infra.md
Normal file
@ -0,0 +1,446 @@
|
||||
# Phase 200-A: ConditionEnv 拡張インフラ(型と箱のみ)
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**Status**: Ready for Implementation
|
||||
**Prerequisite**: Phase 197 complete
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
**ConditionEnv を壊さずに、関数スコープの "実質定数" ローカル(例: `local digits = "0123456789"`)を安全に扱う基盤を作る。**
|
||||
|
||||
**このフェーズでは**:
|
||||
- ✅ 型と解析箱だけを導入
|
||||
- ✅ 既存ループの挙動は変えない
|
||||
- ❌ 具体ループ(`_parse_number` / `_atoi` 等)にはまだ手を出さない
|
||||
|
||||
**設計原則**:
|
||||
- ConditionEnv の「芯」(= boundary に載ったものだけを見る)を保持
|
||||
- 2-tier 設計(ConditionEnv / LoopBodyLocalEnv)は維持
|
||||
- 新しい箱は「関数スコープの実質定数」を捕捉するレイヤーとして追加
|
||||
|
||||
---
|
||||
|
||||
## アーキテクチャ概要
|
||||
|
||||
### 現在の設計(Phase 193 まで)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ConditionEnv │
|
||||
│ - LoopParam (i, len, etc.) │
|
||||
│ - OuterLocal (function params: s, pos) │
|
||||
│ - LoopBodyLocal (body-local vars: ch, digit) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ JoinInlineBoundary │
|
||||
│ - join_inputs / host_inputs │
|
||||
│ - condition_bindings │
|
||||
│ - exit_bindings │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**問題**: `digits` のような「関数スコープで宣言され、ループ内で不変なローカル」が捕捉できない
|
||||
|
||||
### 新設計(Phase 200+)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ FunctionScopeCaptureAnalyzer │
|
||||
│ - 関数スコープの "実質定数" を検出 │
|
||||
│ - CapturedEnv を生成 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ CapturedEnv │
|
||||
│ - CapturedVar[] { name, host_id, is_immutable } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ConditionEnvBuilder v2 │
|
||||
│ - LoopParam + CapturedEnv → ConditionEnv に合成 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ JoinInlineBoundary (拡張) │
|
||||
│ - ParamRole: LoopParam / Condition / Carrier / Expr │
|
||||
│ - CapturedEnv の変数も condition_bindings に載せる │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 200-A-1: CapturedVar / CapturedEnv 型の導入
|
||||
|
||||
### 目標
|
||||
関数スコープの「実質定数」を表現する型を定義する。
|
||||
|
||||
### ファイル
|
||||
**新規**: `src/mir/loop_pattern_detection/function_scope_capture.rs`
|
||||
|
||||
### 実装内容
|
||||
|
||||
```rust
|
||||
//! Phase 200-A: Function scope capture infrastructure
|
||||
//!
|
||||
//! This module provides types for capturing function-scoped variables
|
||||
//! that are effectively immutable within a loop context.
|
||||
|
||||
/// A variable captured from function scope for use in loop conditions.
|
||||
///
|
||||
/// Example: `local digits = "0123456789"` in JsonParser._atoi()
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CapturedVar {
|
||||
/// Variable name (e.g., "digits", "table")
|
||||
pub name: String,
|
||||
|
||||
/// MIR ValueId of the original definition
|
||||
pub host_id: ValueId,
|
||||
|
||||
/// Whether this variable is never reassigned in the function
|
||||
pub is_immutable: bool,
|
||||
}
|
||||
|
||||
/// Environment containing function-scoped captured variables.
|
||||
///
|
||||
/// Phase 200-A: Type definition only, not yet integrated with ConditionEnv.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CapturedEnv {
|
||||
pub vars: Vec<CapturedVar>,
|
||||
}
|
||||
|
||||
impl CapturedEnv {
|
||||
pub fn new() -> Self {
|
||||
Self { vars: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vars.is_empty()
|
||||
}
|
||||
|
||||
pub fn add_var(&mut self, var: CapturedVar) {
|
||||
self.vars.push(var);
|
||||
}
|
||||
|
||||
/// Look up a captured variable by name
|
||||
pub fn get(&self, name: &str) -> Option<&CapturedVar> {
|
||||
self.vars.iter().find(|v| v.name == name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### テスト
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
#[test]
|
||||
fn test_captured_env_empty() {
|
||||
let env = CapturedEnv::new();
|
||||
assert!(env.is_empty());
|
||||
assert!(env.get("digits").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_captured_env_add_and_get() {
|
||||
let mut env = CapturedEnv::new();
|
||||
env.add_var(CapturedVar {
|
||||
name: "digits".to_string(),
|
||||
host_id: ValueId(42),
|
||||
is_immutable: true,
|
||||
});
|
||||
|
||||
assert!(!env.is_empty());
|
||||
let var = env.get("digits").unwrap();
|
||||
assert_eq!(var.name, "digits");
|
||||
assert_eq!(var.host_id, ValueId(42));
|
||||
assert!(var.is_immutable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 成果物
|
||||
- [x] `CapturedVar` 構造体定義
|
||||
- [x] `CapturedEnv` 構造体定義
|
||||
- [x] 基本的なアクセサメソッド
|
||||
- [x] Unit tests (2件)
|
||||
|
||||
---
|
||||
|
||||
## Task 200-A-2: FunctionScopeCaptureAnalyzer スケルトン
|
||||
|
||||
### 目標
|
||||
関数スコープの実質定数を検出する解析関数の枠を作る。
|
||||
|
||||
### ファイル
|
||||
同じ `function_scope_capture.rs` に追加
|
||||
|
||||
### 実装内容
|
||||
|
||||
```rust
|
||||
/// Analyzes function-scoped variables that can be safely captured for loop conditions.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
/// Currently returns empty CapturedEnv (skeleton implementation).
|
||||
/// Actual capture detection will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Future Detection Criteria (Phase 200-B+)
|
||||
/// - Variable is declared before the loop
|
||||
/// - Variable is never reassigned within the function
|
||||
/// - Variable is referenced in loop condition or body
|
||||
pub fn analyze_captured_vars(
|
||||
_fn_body: &[Stmt],
|
||||
_loop_ast: &Stmt,
|
||||
_scope: &LoopScopeShape,
|
||||
) -> CapturedEnv {
|
||||
// Phase 200-A: Skeleton implementation
|
||||
// TODO(Phase 200-B): Implement actual capture detection
|
||||
//
|
||||
// Detection algorithm:
|
||||
// 1. Find all `local` declarations before the loop
|
||||
// 2. Check if each is never reassigned (is_immutable = true)
|
||||
// 3. Check if referenced in loop condition/body
|
||||
// 4. Exclude loop parameters and body-local variables
|
||||
|
||||
CapturedEnv::new()
|
||||
}
|
||||
```
|
||||
|
||||
### 設計メモ
|
||||
|
||||
**Phase 200-B で実装する検出アルゴリズム**:
|
||||
1. ループの前にある `local` 宣言を全て収集
|
||||
2. 関数全体で再代入されていないかチェック(`is_immutable`)
|
||||
3. ループ条件/本体で参照されているかチェック
|
||||
4. LoopParam / LoopBodyLocal は除外(既に ConditionEnv で扱われている)
|
||||
|
||||
### 成果物
|
||||
- [x] `analyze_captured_vars` 関数シグネチャ
|
||||
- [x] ドキュメントコメント(将来の検出基準を記載)
|
||||
- [x] 空実装(Phase 200-B で中身を実装)
|
||||
|
||||
---
|
||||
|
||||
## Task 200-A-3: ConditionEnvBuilder v2 入口
|
||||
|
||||
### 目標
|
||||
将来 CapturedEnv を受け取るフックだけ用意する。
|
||||
|
||||
### ファイル
|
||||
`src/mir/join_ir/lowering/condition_env_builder.rs` または既存の ConditionEnv 関連ファイル
|
||||
|
||||
### 実装内容
|
||||
|
||||
```rust
|
||||
/// Build ConditionEnv with optional captured variables.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
/// Currently ignores `captured` parameter and calls existing implementation.
|
||||
/// Integration with CapturedEnv will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Future Behavior (Phase 200-B+)
|
||||
/// - Add captured variables to condition_bindings
|
||||
/// - Generate Copy instructions for captured vars in entry block
|
||||
/// - Track captured vars separately from loop params
|
||||
pub fn build_condition_env_with_captures(
|
||||
loop_params: &[ParamInfo],
|
||||
_captured: &CapturedEnv,
|
||||
boundary: &mut JoinInlineBoundaryBuilder,
|
||||
) -> ConditionEnv {
|
||||
// Phase 200-A: Delegate to existing implementation
|
||||
// TODO(Phase 200-B): Integrate captured vars into ConditionEnv
|
||||
//
|
||||
// Integration steps:
|
||||
// 1. For each captured var, add to boundary.condition_bindings
|
||||
// 2. Mark as ParamRole::Condition (not Carrier or LoopParam)
|
||||
// 3. Ensure captured vars are NOT included in exit_bindings
|
||||
|
||||
build_condition_env_from_params(loop_params, boundary)
|
||||
}
|
||||
```
|
||||
|
||||
### 成果物
|
||||
- [x] `build_condition_env_with_captures` 関数(既存実装に委譲)
|
||||
- [x] ドキュメントコメント(Phase 200-B の統合手順を記載)
|
||||
|
||||
---
|
||||
|
||||
## Task 200-A-4: ParamRole enum 追加
|
||||
|
||||
### 目標
|
||||
JoinInlineBoundary のパラメータ役割を明示的に区別する。
|
||||
|
||||
### ファイル
|
||||
`src/mir/join_ir/lowering/inline_boundary.rs` または関連ファイル
|
||||
|
||||
### 実装内容
|
||||
|
||||
```rust
|
||||
/// Role of a parameter in JoinIR lowering.
|
||||
///
|
||||
/// # Invariants
|
||||
/// - **LoopParam**: Participates in header PHI, updated in loop body
|
||||
/// - **Condition**: Used in condition only, NOT in header PHI, NOT in ExitLine
|
||||
/// - **Carrier**: Updated in loop body, participates in header PHI and ExitLine
|
||||
/// - **ExprResult**: Return value of the loop expression, handled by exit_phi_builder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParamRole {
|
||||
/// Loop iteration variable (e.g., `i` in `loop(i < len)`)
|
||||
LoopParam,
|
||||
|
||||
/// Condition-only parameter (e.g., `digits` in `digits.indexOf(ch)`)
|
||||
/// NOT included in header PHI or ExitLine
|
||||
Condition,
|
||||
|
||||
/// State carried across iterations (e.g., `sum`, `count`)
|
||||
Carrier,
|
||||
|
||||
/// Expression result returned by the loop
|
||||
ExprResult,
|
||||
}
|
||||
|
||||
impl JoinInlineBoundaryBuilder {
|
||||
/// Add a parameter with explicit role.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
/// Currently stores role but does not use it for routing.
|
||||
/// Role-based routing will be implemented in Phase 200-B.
|
||||
pub fn add_param_with_role(&mut self, name: &str, host_id: ValueId, role: ParamRole) {
|
||||
// Phase 200-A: Store role for future use
|
||||
// TODO(Phase 200-B): Route based on role
|
||||
//
|
||||
// Routing rules:
|
||||
// - LoopParam: join_inputs + host_inputs
|
||||
// - Condition: condition_bindings only (no PHI, no ExitLine)
|
||||
// - Carrier: join_inputs + host_inputs + exit_bindings
|
||||
// - ExprResult: handled by exit_phi_builder
|
||||
|
||||
match role {
|
||||
ParamRole::LoopParam | ParamRole::Carrier => {
|
||||
// Existing behavior: add to join_inputs
|
||||
self.add_input(name, host_id);
|
||||
}
|
||||
ParamRole::Condition => {
|
||||
// Phase 200-A: Just log for now
|
||||
// TODO(Phase 200-B): Add to condition_bindings without PHI
|
||||
}
|
||||
ParamRole::ExprResult => {
|
||||
// Handled separately by set_expr_result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 不変条件(joinir-architecture-overview.md に追記)
|
||||
|
||||
```markdown
|
||||
9. **ParamRole の不変条件**
|
||||
- **Condition 役のパラメータは「PHI dst にしてはいけない」**
|
||||
- 理由: 条件専用変数はループ内で更新されない
|
||||
- 例: `digits` は header PHI に参加しない
|
||||
- **Condition 役のパラメータは「ExitLine の対象にも入れない」**
|
||||
- 理由: 条件専用変数はループ外で使われない(ループ内でのみ参照)
|
||||
- 例: `digits.indexOf(ch)` の結果は exit_bindings に載らない
|
||||
```
|
||||
|
||||
### 成果物
|
||||
- [x] `ParamRole` enum 定義
|
||||
- [x] `add_param_with_role` メソッド(スケルトン)
|
||||
- [x] 不変条件のドキュメント
|
||||
|
||||
---
|
||||
|
||||
## Task 200-A-5: ドキュメント更新
|
||||
|
||||
### 1. joinir-architecture-overview.md
|
||||
|
||||
**Section 2.3 (Boundary / Carrier ライン) に追記**:
|
||||
|
||||
```markdown
|
||||
- **FunctionScopeCaptureAnalyzer / CapturedEnv** (Phase 200-A)
|
||||
- ファイル: `src/mir/loop_pattern_detection/function_scope_capture.rs`
|
||||
- 責務:
|
||||
- 関数スコープで宣言され、ループ内で不変な変数を検出
|
||||
- CapturedVar: { name, host_id, is_immutable }
|
||||
- CapturedEnv: 検出された変数のコレクション
|
||||
- **Phase 200-A**: 型と空実装のみ(中身は Phase 200-B で実装)
|
||||
|
||||
- **ParamRole enum** (Phase 200-A)
|
||||
- ファイル: `src/mir/join_ir/lowering/inline_boundary.rs`
|
||||
- 責務:
|
||||
- JoinInlineBoundary のパラメータ役割を明示的に区別
|
||||
- LoopParam / Condition / Carrier / ExprResult
|
||||
- **Phase 200-A**: enum 定義のみ(ルーティングは Phase 200-B で実装)
|
||||
```
|
||||
|
||||
**Section 1 (不変条件) に追記**:
|
||||
|
||||
```markdown
|
||||
9. **ParamRole の不変条件**
|
||||
- Condition 役のパラメータは「PHI dst にしてはいけない」
|
||||
- Condition 役のパラメータは「ExitLine の対象にも入れない」
|
||||
- 理由: 条件専用変数はループ内で更新されず、ループ外で使われない
|
||||
```
|
||||
|
||||
### 2. CURRENT_TASK.md
|
||||
|
||||
**Phase 200-A セクション追加**:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 200-A: ConditionEnv 拡張インフラ(型と箱のみ)** ✅ (完了: 2025-12-XX)
|
||||
- **目的**: ConditionEnv を壊さずに関数スコープ "実質定数" を扱う基盤
|
||||
- **実装内容**:
|
||||
- 200-A-1: CapturedVar / CapturedEnv 型導入 ✅
|
||||
- 200-A-2: FunctionScopeCaptureAnalyzer スケルトン ✅
|
||||
- 200-A-3: ConditionEnvBuilder v2 入口 ✅
|
||||
- 200-A-4: ParamRole enum 追加 ✅
|
||||
- 200-A-5: ドキュメント更新 ✅
|
||||
- **スコープ**: Infra only / Integration pending
|
||||
- **成果**:
|
||||
- 型と箱の定義完了 ✅
|
||||
- 既存ループの挙動変更なし ✅
|
||||
- Phase 200-B への準備完了 ✅
|
||||
- **次フェーズ**: Phase 200-B(digits 系ループへの適用)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] `CapturedVar` / `CapturedEnv` 型が定義されている
|
||||
- [x] `analyze_captured_vars` 関数スケルトンが存在する
|
||||
- [x] `build_condition_env_with_captures` 入口が存在する
|
||||
- [x] `ParamRole` enum が定義されている
|
||||
- [x] 既存テストが全て PASS(退行なし)
|
||||
- [x] ドキュメント更新(overview + CURRENT_TASK)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 200-A)
|
||||
|
||||
1. **Infra only**: 型と箱の定義のみ、具体ループへの適用は Phase 200-B
|
||||
2. **既存挙動維持**: 現在のループは全く同じように動作する
|
||||
3. **箱化原則**: 新しい責務は新しい箱(FunctionScopeCaptureAnalyzer)に集約
|
||||
4. **ドキュメント駆動**: 将来の実装方針をコメントとして残す
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 新規作成
|
||||
- `src/mir/loop_pattern_detection/function_scope_capture.rs`
|
||||
|
||||
### 修正対象
|
||||
- `src/mir/loop_pattern_detection/mod.rs`(モジュール追加)
|
||||
- `src/mir/join_ir/lowering/condition_env_builder.rs`(v2 入口)
|
||||
- `src/mir/join_ir/lowering/inline_boundary.rs`(ParamRole)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`
|
||||
- `CURRENT_TASK.md`
|
||||
@ -27,6 +27,8 @@
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv};
|
||||
use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
|
||||
use crate::mir::join_ir::lowering::inline_boundary_builder::JoinInlineBoundaryBuilder;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@ -122,6 +124,64 @@ impl ConditionEnvBuilder {
|
||||
env.insert(loop_var_name.to_string(), ValueId(0));
|
||||
env
|
||||
}
|
||||
|
||||
/// Build ConditionEnv with optional captured variables (Phase 200-A v2 entry point)
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Currently ignores `captured` parameter and delegates to existing implementation.
|
||||
/// Integration with CapturedEnv will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Future Behavior (Phase 200-B+)
|
||||
///
|
||||
/// - Add captured variables to ConditionEnv
|
||||
/// - Generate condition_bindings for captured vars in boundary
|
||||
/// - Track captured vars separately from loop params
|
||||
/// - Ensure captured vars do NOT participate in header PHI or exit_bindings
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `loop_var_name` - Loop parameter name (e.g., "i", "pos")
|
||||
/// * `_captured` - Function-scoped captured variables (Phase 200-B+)
|
||||
/// * `_boundary` - Boundary builder for adding condition_bindings (Phase 200-B+)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// ConditionEnv with loop parameter mapping (Phase 200-A: same as build_loop_param_only)
|
||||
///
|
||||
/// # Example (Future Phase 200-B)
|
||||
///
|
||||
/// ```ignore
|
||||
/// let captured = analyze_captured_vars(fn_body, loop_ast, scope);
|
||||
/// let mut boundary = JoinInlineBoundaryBuilder::new();
|
||||
/// let env = ConditionEnvBuilder::build_with_captures(
|
||||
/// "pos",
|
||||
/// &captured, // Contains "digits" with host ValueId(42)
|
||||
/// &mut boundary,
|
||||
/// );
|
||||
/// // Phase 200-B: env will contain "pos" → ValueId(0), "digits" → ValueId(1)
|
||||
/// // Phase 200-B: boundary.condition_bindings will have entry for "digits"
|
||||
/// ```
|
||||
pub fn build_with_captures(
|
||||
loop_var_name: &str,
|
||||
_captured: &CapturedEnv,
|
||||
_boundary: &mut JoinInlineBoundaryBuilder,
|
||||
) -> ConditionEnv {
|
||||
// Phase 200-A: Delegate to existing implementation
|
||||
// TODO(Phase 200-B): Integrate captured vars into ConditionEnv
|
||||
//
|
||||
// Integration steps:
|
||||
// 1. Start with loop parameter in env (ValueId(0))
|
||||
// 2. For each captured var:
|
||||
// a. Allocate JoinIR-local ValueId (starting from 1)
|
||||
// b. Add to ConditionEnv (var.name → join_id)
|
||||
// c. Add to boundary.condition_bindings (host_id ↔ join_id)
|
||||
// d. Mark as ParamRole::Condition (not Carrier or LoopParam)
|
||||
// 3. Ensure captured vars are NOT in exit_bindings (condition-only)
|
||||
// 4. Return populated ConditionEnv
|
||||
|
||||
Self::build_loop_param_only(loop_var_name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -28,6 +28,50 @@ use crate::mir::ValueId;
|
||||
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use super::condition_to_joinir::ConditionBinding;
|
||||
|
||||
/// Role of a parameter in JoinIR lowering (Phase 200-A)
|
||||
///
|
||||
/// This enum explicitly classifies parameters to ensure correct routing
|
||||
/// during JoinIR → MIR lowering and boundary construction.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// - **LoopParam**: Participates in header PHI, updated in loop body
|
||||
/// - Example: `i` in `loop(i < len)` - iteration variable
|
||||
/// - Routing: join_inputs + host_inputs + header PHI + exit_bindings
|
||||
///
|
||||
/// - **Condition**: Used in condition only, NOT in header PHI, NOT in ExitLine
|
||||
/// - Example: `digits` in `digits.indexOf(ch)` - function-scoped constant
|
||||
/// - Routing: condition_bindings ONLY (no PHI, no exit_bindings)
|
||||
/// - Rationale: Condition-only vars are immutable and not updated in loop
|
||||
///
|
||||
/// - **Carrier**: Updated in loop body, participates in header PHI and ExitLine
|
||||
/// - Example: `sum`, `count` in accumulation loops
|
||||
/// - Routing: join_inputs + host_inputs + header PHI + exit_bindings
|
||||
///
|
||||
/// - **ExprResult**: Return value of the loop expression
|
||||
/// - Example: Loop result in `return loop(...)`
|
||||
/// - Routing: Handled by exit_phi_builder (set_expr_result)
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Enum is defined but not yet used for routing. Routing implementation
|
||||
/// will be added in Phase 200-B when CapturedEnv integration is complete.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParamRole {
|
||||
/// Loop iteration variable (e.g., `i` in `loop(i < len)`)
|
||||
LoopParam,
|
||||
|
||||
/// Condition-only parameter (e.g., `digits` in `digits.indexOf(ch)`)
|
||||
/// NOT included in header PHI or ExitLine
|
||||
Condition,
|
||||
|
||||
/// State carried across iterations (e.g., `sum`, `count`)
|
||||
Carrier,
|
||||
|
||||
/// Expression result returned by the loop
|
||||
ExprResult,
|
||||
}
|
||||
|
||||
/// Builder for constructing JoinInlineBoundary objects
|
||||
///
|
||||
/// Provides a fluent API to set boundary fields without direct field manipulation.
|
||||
@ -128,6 +172,70 @@ impl JoinInlineBoundaryBuilder {
|
||||
pub fn build(self) -> JoinInlineBoundary {
|
||||
self.boundary
|
||||
}
|
||||
|
||||
/// Add a parameter with explicit role (Phase 200-A)
|
||||
///
|
||||
/// This method allows adding parameters with explicit role classification,
|
||||
/// ensuring correct routing during JoinIR → MIR lowering.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Currently stores parameters based on role but does not use role for advanced routing.
|
||||
/// Full role-based routing will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - Variable name (e.g., "i", "digits", "sum")
|
||||
/// * `host_id` - Host function's ValueId for this variable
|
||||
/// * `role` - Parameter role (LoopParam / Condition / Carrier / ExprResult)
|
||||
///
|
||||
/// # Routing Rules (Phase 200-B+)
|
||||
///
|
||||
/// - **LoopParam**: add_input (join_inputs + host_inputs)
|
||||
/// - **Condition**: add to condition_bindings (no PHI, no exit_bindings)
|
||||
/// - **Carrier**: add_input + exit_bindings
|
||||
/// - **ExprResult**: set_expr_result (handled separately)
|
||||
///
|
||||
/// # Example (Future Phase 200-B)
|
||||
///
|
||||
/// ```ignore
|
||||
/// builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam);
|
||||
/// builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition);
|
||||
/// builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier);
|
||||
/// ```
|
||||
pub fn add_param_with_role(&mut self, _name: &str, host_id: ValueId, role: ParamRole) {
|
||||
// Phase 200-A: Basic routing only
|
||||
// TODO(Phase 200-B): Implement full role-based routing
|
||||
//
|
||||
// Routing implementation:
|
||||
// - LoopParam: join_inputs + host_inputs
|
||||
// - Condition: condition_bindings (with JoinIR-local ValueId allocation)
|
||||
// - Carrier: join_inputs + host_inputs + exit_bindings
|
||||
// - ExprResult: Handled by set_expr_result
|
||||
|
||||
match role {
|
||||
ParamRole::LoopParam | ParamRole::Carrier => {
|
||||
// Existing behavior: add to join_inputs
|
||||
// Note: In Phase 200-A, we don't have a simple add_input method
|
||||
// that takes a name. This is a skeleton implementation.
|
||||
// In Phase 200-B, we'll need to allocate JoinIR-local ValueIds.
|
||||
let join_id = ValueId(self.boundary.join_inputs.len() as u32);
|
||||
self.boundary.join_inputs.push(join_id);
|
||||
self.boundary.host_inputs.push(host_id);
|
||||
}
|
||||
ParamRole::Condition => {
|
||||
// Phase 200-A: Log only
|
||||
// TODO(Phase 200-B): Add to condition_bindings without PHI
|
||||
// 1. Allocate JoinIR-local ValueId
|
||||
// 2. Create ConditionBinding { name, host_id, join_id }
|
||||
// 3. Add to self.boundary.condition_bindings
|
||||
}
|
||||
ParamRole::ExprResult => {
|
||||
// Handled separately by set_expr_result
|
||||
// No action needed here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JoinInlineBoundaryBuilder {
|
||||
@ -255,4 +363,40 @@ mod tests {
|
||||
assert_eq!(boundary.join_inputs.len(), 3);
|
||||
assert_eq!(boundary.host_inputs.len(), 3);
|
||||
}
|
||||
|
||||
// Phase 200-A: ParamRole tests
|
||||
#[test]
|
||||
fn test_param_role_loop_param() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam);
|
||||
|
||||
let boundary = builder.build();
|
||||
assert_eq!(boundary.join_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs[0], ValueId(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_role_condition() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
// Phase 200-A: Condition role is logged but not yet routed
|
||||
builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition);
|
||||
|
||||
let boundary = builder.build();
|
||||
// Phase 200-A: No action for Condition role yet
|
||||
// Phase 200-B: This will add to condition_bindings
|
||||
assert_eq!(boundary.join_inputs.len(), 0);
|
||||
assert_eq!(boundary.condition_bindings.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_role_carrier() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier);
|
||||
|
||||
let boundary = builder.build();
|
||||
assert_eq!(boundary.join_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs[0], ValueId(101));
|
||||
}
|
||||
}
|
||||
|
||||
203
src/mir/loop_pattern_detection/function_scope_capture.rs
Normal file
203
src/mir/loop_pattern_detection/function_scope_capture.rs
Normal file
@ -0,0 +1,203 @@
|
||||
//! Phase 200-A: Function scope capture infrastructure
|
||||
//!
|
||||
//! This module provides types for capturing function-scoped variables
|
||||
//! that are effectively immutable within a loop context.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! For a function like JsonParser._atoi():
|
||||
//!
|
||||
//! ```nyash
|
||||
//! method _atoi(s, pos, len) {
|
||||
//! local digits = "0123456789" // <-- Captured variable
|
||||
//! local value = 0
|
||||
//! loop(pos < len) {
|
||||
//! local ch = s.charAt(pos)
|
||||
//! local digit = digits.indexOf(ch) // Uses captured 'digits'
|
||||
//! if (digit < 0) { break }
|
||||
//! value = value * 10 + digit
|
||||
//! pos = pos + 1
|
||||
//! }
|
||||
//! return value
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Here, `digits` is:
|
||||
//! - Declared in function scope (before the loop)
|
||||
//! - Never reassigned (effectively immutable)
|
||||
//! - Referenced in loop body (digits.indexOf(ch))
|
||||
//!
|
||||
//! Phase 200-A creates the infrastructure to capture such variables.
|
||||
//! Phase 200-B will implement the actual detection logic.
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
/// A variable captured from function scope for use in loop conditions/body.
|
||||
///
|
||||
/// Example: `local digits = "0123456789"` in JsonParser._atoi()
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// - `name`: Variable name as it appears in the source code
|
||||
/// - `host_id`: MIR ValueId of the original definition in the host function
|
||||
/// - `is_immutable`: True if the variable is never reassigned in the function
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CapturedVar {
|
||||
/// Variable name (e.g., "digits", "table")
|
||||
pub name: String,
|
||||
|
||||
/// MIR ValueId of the original definition in the host function
|
||||
pub host_id: ValueId,
|
||||
|
||||
/// Whether this variable is never reassigned in the function
|
||||
///
|
||||
/// Phase 200-B will implement assignment analysis to determine this.
|
||||
/// For now, this is always set to true as a conservative default.
|
||||
pub is_immutable: bool,
|
||||
}
|
||||
|
||||
/// Environment containing function-scoped captured variables.
|
||||
///
|
||||
/// Phase 200-A: Type definition only, not yet integrated with ConditionEnv.
|
||||
/// Phase 200-B: Will be populated by FunctionScopeCaptureAnalyzer and
|
||||
/// integrated into ConditionEnv via ConditionEnvBuilder v2.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CapturedEnv {
|
||||
/// List of captured variables
|
||||
pub vars: Vec<CapturedVar>,
|
||||
}
|
||||
|
||||
impl CapturedEnv {
|
||||
/// Create a new empty environment
|
||||
pub fn new() -> Self {
|
||||
Self { vars: Vec::new() }
|
||||
}
|
||||
|
||||
/// Check if the environment is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vars.is_empty()
|
||||
}
|
||||
|
||||
/// Add a captured variable to the environment
|
||||
pub fn add_var(&mut self, var: CapturedVar) {
|
||||
self.vars.push(var);
|
||||
}
|
||||
|
||||
/// Look up a captured variable by name
|
||||
///
|
||||
/// Returns `Some(&CapturedVar)` if found, `None` otherwise.
|
||||
pub fn get(&self, name: &str) -> Option<&CapturedVar> {
|
||||
self.vars.iter().find(|v| v.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyzes function-scoped variables that can be safely captured for loop conditions/body.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Currently returns empty CapturedEnv (skeleton implementation).
|
||||
/// Actual capture detection will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Future Detection Criteria (Phase 200-B+)
|
||||
///
|
||||
/// A variable is captured if ALL of the following conditions are met:
|
||||
///
|
||||
/// 1. **Declared before the loop**: Variable must be declared in function scope before the loop
|
||||
/// 2. **Never reassigned**: Variable is never reassigned within the function (is_immutable = true)
|
||||
/// 3. **Referenced in loop**: Variable is referenced in loop condition or body
|
||||
/// 4. **Not a loop parameter**: Variable is not the loop iteration variable
|
||||
/// 5. **Not a body-local**: Variable is not declared inside the loop body
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```nyash
|
||||
/// method _atoi(s, pos, len) {
|
||||
/// local digits = "0123456789" // ✅ Captured (declared before loop, never reassigned)
|
||||
/// local value = 0 // ❌ Not captured (reassigned in loop body)
|
||||
/// loop(pos < len) {
|
||||
/// local ch = s.charAt(pos) // ❌ Not captured (body-local)
|
||||
/// local digit = digits.indexOf(ch)
|
||||
/// value = value * 10 + digit
|
||||
/// pos = pos + 1
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_fn_body` - AST nodes of the function body (for analysis)
|
||||
/// * `_loop_ast` - AST node of the loop statement
|
||||
/// * `_scope` - LoopScopeShape (for excluding loop params and body-locals)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `CapturedEnv` containing all captured variables (empty in Phase 200-A)
|
||||
pub fn analyze_captured_vars(
|
||||
_fn_body: &[ASTNode],
|
||||
_loop_ast: &ASTNode,
|
||||
_scope: &LoopScopeShape,
|
||||
) -> CapturedEnv {
|
||||
// Phase 200-A: Skeleton implementation
|
||||
// TODO(Phase 200-B): Implement actual capture detection
|
||||
//
|
||||
// Detection algorithm:
|
||||
// 1. Find all `local` declarations before the loop in fn_body
|
||||
// 2. For each declaration:
|
||||
// a. Check if it's never reassigned in the function (is_immutable = true)
|
||||
// b. Check if it's referenced in loop condition or body
|
||||
// c. Exclude if it's in scope.pinned, scope.carriers, or scope.body_locals
|
||||
// 3. Collect matching variables into CapturedEnv
|
||||
// 4. Return the populated environment
|
||||
|
||||
CapturedEnv::new()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_captured_env_empty() {
|
||||
let env = CapturedEnv::new();
|
||||
assert!(env.is_empty());
|
||||
assert!(env.get("digits").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_captured_env_add_and_get() {
|
||||
let mut env = CapturedEnv::new();
|
||||
env.add_var(CapturedVar {
|
||||
name: "digits".to_string(),
|
||||
host_id: ValueId(42),
|
||||
is_immutable: true,
|
||||
});
|
||||
|
||||
assert!(!env.is_empty());
|
||||
let var = env.get("digits").unwrap();
|
||||
assert_eq!(var.name, "digits");
|
||||
assert_eq!(var.host_id, ValueId(42));
|
||||
assert!(var.is_immutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_captured_env_multiple_vars() {
|
||||
let mut env = CapturedEnv::new();
|
||||
env.add_var(CapturedVar {
|
||||
name: "digits".to_string(),
|
||||
host_id: ValueId(42),
|
||||
is_immutable: true,
|
||||
});
|
||||
env.add_var(CapturedVar {
|
||||
name: "table".to_string(),
|
||||
host_id: ValueId(100),
|
||||
is_immutable: true,
|
||||
});
|
||||
|
||||
assert_eq!(env.vars.len(), 2);
|
||||
assert!(env.get("digits").is_some());
|
||||
assert!(env.get("table").is_some());
|
||||
assert!(env.get("nonexistent").is_none());
|
||||
}
|
||||
}
|
||||
@ -780,3 +780,6 @@ pub use trim_loop_helper::TrimLoopHelper;
|
||||
|
||||
// Phase 33-23: Break Condition Analysis (Stage 2, Issue 6)
|
||||
pub mod break_condition_analyzer;
|
||||
|
||||
// Phase 200-A: Function Scope Capture Infrastructure
|
||||
pub mod function_scope_capture;
|
||||
|
||||
Reference in New Issue
Block a user