feat(llvm): Phase 132-P0 - block_end_values tuple-key fix for cross-function isolation

## Problem
`block_end_values` used block ID only as key, causing collisions when
multiple functions share the same block IDs (e.g., bb0 in both
condition_fn and main).

## Root Cause
- condition_fn's bb0 → block_end_values[0]
- main's bb0 → block_end_values[0] (OVERWRITES!)
- PHI resolution gets wrong snapshot → dominance error

## Solution (Box-First principle)
Change key from `int` to `Tuple[str, int]` (func_name, block_id):

```python
# Before
block_end_values: Dict[int, Dict[int, ir.Value]]

# After
block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]]
```

## Files Modified (Python - 6 files)

1. `llvm_builder.py` - Type annotation update
2. `function_lower.py` - Pass func_name to lower_blocks
3. `block_lower.py` - Use tuple keys for snapshot save/load
4. `resolver.py` - Add func_name parameter to resolve_incoming
5. `wiring.py` - Thread func_name through PHI wiring
6. `phi_manager.py` - Debug traces

## Files Modified (Rust - cleanup)

- Removed deprecated `loop_to_join.rs` (297 lines deleted)
- Updated pattern lowerers for cleaner exit handling
- Added lifecycle management improvements

## Verification

-  Pattern 1: VM RC: 3, LLVM Result: 3 (no regression)
- ⚠️ Case C: Still has dominance error (separate root cause)
  - Needs additional scope fixes (phi_manager, resolver caches)

## Design Principles

- **Box-First**: Each function is an isolated Box with scoped state
- **SSOT**: (func_name, block_id) uniquely identifies block snapshots
- **Fail-Fast**: No cross-function state contamination

## Known Issues (Phase 132-P1)

Other function-local state needs same treatment:
- phi_manager.predeclared
- resolver caches (i64_cache, ptr_cache, etc.)
- builder._jump_only_blocks

## Documentation

- docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md
- docs/development/current/main/investigations/phase132-p0-tuple-key-implementation.md

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-15 05:36:50 +09:00
parent 18d56d5b88
commit 3f58f34592
22 changed files with 1076 additions and 384 deletions

View File

@ -14,7 +14,8 @@
//! ## 解決策
//!
//! ConditionPatternBox を導入し、if条件が「単純比較」かどうかを判定する。
//! AST-based lowerer は単純比較のみ処理可能とし、複雑条件はlegacy modeへフォールバック。
//! ただし「単純/複雑/legacy」の語彙は混線しやすいので、routing 用には
//! `ConditionCapability` を使って「どの経路で扱うか」を明示する。
//!
//! Phase 222: 左右反転literal on left → var on leftと変数同士の比較をサポート。
//!
@ -35,6 +36,64 @@
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::CompareOp;
/// ConditionCapability: 条件式をどの戦略で扱えるかrouting 用)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConditionCapability {
/// Pattern3 if-sum AST-based lowerer で比較として扱える
IfSumComparable,
/// 上記以外caller が別経路を選ぶ)
Unsupported,
}
fn is_if_sum_value_expr(expr: &ASTNode) -> bool {
match expr {
ASTNode::Variable { .. } | ASTNode::Literal { .. } => true,
ASTNode::BinaryOp {
operator, left, right, ..
} => matches!(
operator,
BinaryOperator::Add
| BinaryOperator::Subtract
| BinaryOperator::Multiply
| BinaryOperator::Divide
| BinaryOperator::Modulo
) && is_if_sum_value_expr(left.as_ref())
&& is_if_sum_value_expr(right.as_ref()),
_ => false,
}
}
/// 条件式の“能力”を判定routing のための入口)
pub fn analyze_condition_capability(cond: &ASTNode) -> ConditionCapability {
match cond {
ASTNode::BinaryOp {
operator,
left,
right,
..
} => {
let is_comparison = matches!(
operator,
BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::Less
| BinaryOperator::Greater
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
);
if !is_comparison {
return ConditionCapability::Unsupported;
}
if is_if_sum_value_expr(left.as_ref()) && is_if_sum_value_expr(right.as_ref()) {
ConditionCapability::IfSumComparable
} else {
ConditionCapability::Unsupported
}
}
_ => ConditionCapability::Unsupported,
}
}
/// if条件のパターン種別
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConditionPattern {
@ -176,8 +235,8 @@ pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern {
/// // i > 0 → true
/// assert!(is_simple_comparison(&simple_condition));
///
/// // i % 2 == 1 → false
/// assert!(!is_simple_comparison(&complex_condition));
/// // i % 2 == 1 → truePhase 242-EX-A で比較のオペランドに算術式を許可)
/// assert!(is_simple_comparison(&complex_condition));
/// ```
pub fn is_simple_comparison(cond: &ASTNode) -> bool {
analyze_condition_pattern(cond) == ConditionPattern::SimpleComparison
@ -605,4 +664,51 @@ mod tests {
);
assert!(is_simple_comparison(&cond));
}
// ========================================================================
// ConditionCapability (routing) Tests
// ========================================================================
#[test]
fn test_capability_if_sum_comparable_simple() {
let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0));
assert_eq!(
analyze_condition_capability(&cond),
ConditionCapability::IfSumComparable
);
}
#[test]
fn test_capability_if_sum_comparable_binop_operand() {
let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2));
let cond = binop(BinaryOperator::Equal, lhs, int_lit(1));
assert_eq!(
analyze_condition_capability(&cond),
ConditionCapability::IfSumComparable
);
}
#[test]
fn test_capability_rejects_logical_and() {
let cond = binop(BinaryOperator::And, var("a"), var("b"));
assert_eq!(
analyze_condition_capability(&cond),
ConditionCapability::Unsupported
);
}
#[test]
fn test_capability_rejects_method_call_operand() {
let method_call = ASTNode::MethodCall {
object: Box::new(var("obj")),
method: "get".to_string(),
arguments: vec![],
span: Span::unknown(),
};
let cond = binop(BinaryOperator::Greater, method_call, int_lit(0));
assert_eq!(
analyze_condition_capability(&cond),
ConditionCapability::Unsupported
);
}
}

View File

@ -10,8 +10,9 @@
//!
//! ## Design Philosophy
//!
//! The JoinIR lowerer should work with **local ValueIds** (0, 1, 2, ...) without
//! knowing anything about the host function's ValueId space. This ensures:
//! The JoinIR lowerer should work with **JoinIR-side ValueIds** allocated via
//! `JoinValueSpace` (Param: 100-999, Local: 1000+) without knowing anything about
//! the host function's ValueId space. This ensures:
//!
//! 1. **Modularity**: JoinIR lowerers are pure transformers
//! 2. **Reusability**: Same lowerer can be used in different contexts
@ -26,13 +27,13 @@
//! Host Function:
//! ValueId(4) = Const 0 // i = 0 in host
//!
//! JoinIR Fragment (uses local IDs 0, 1, 2, ...):
//! ValueId(0) = param // i_param (local to JoinIR)
//! ValueId(1) = Const 3
//! ValueId(2) = Compare ...
//! JoinIR Fragment:
//! ValueId(100) = param // i_param (JoinIR Param region)
//! ValueId(1000) = Const 3
//! ValueId(1001) = Compare ...
//!
//! Boundary:
//! join_inputs: [ValueId(0)] // JoinIR's param slot
//! join_inputs: [ValueId(100)] // JoinIR's param slot (Param region)
//! host_inputs: [ValueId(4)] // Host's `i` variable
//!
//! Merged MIR (with Copy injection):
@ -115,11 +116,11 @@ pub struct LoopExitBinding {
pub struct JoinInlineBoundary {
/// JoinIR-local ValueIds that act as "input slots"
///
/// These are the ValueIds used **inside** the JoinIR fragment to refer
/// to values that come from the host. They should be small sequential
/// IDs (0, 1, 2, ...) since JoinIR lowerers allocate locally.
/// These are the ValueIds used **inside** the JoinIR fragment to refer
/// to values that come from the host. They should be in the JoinValueSpace
/// Param region (100-999). (They are typically allocated sequentially.)
///
/// Example: For a loop variable `i`, JoinIR uses ValueId(0) as the parameter.
/// Example: For a loop variable `i`, JoinIR uses ValueId(100) as the parameter.
pub join_inputs: Vec<ValueId>,
/// Host-function ValueIds that provide the input values

View File

@ -40,7 +40,7 @@
//! # Integration Points
//!
//! Called from:
//! - `loop_to_join.rs::LoopToJoinLowerer::lower_loop()`
//! - `loop_to_join::LoopToJoinLowerer::lower_loop()`
//! - `loop_form_intake.rs::handle_loop_form()`
use crate::mir::join_ir::JoinInst;
@ -94,7 +94,7 @@ use crate::mir::loop_form::LoopForm;
/// # Integration Point
///
/// This function should be called from loop lowering entry points:
/// - `loop_to_join.rs::LoopToJoinLowerer::lower_loop()`
/// - `loop_to_join::LoopToJoinLowerer::lower_loop()`
/// - `loop_form_intake.rs::handle_loop_form()`
///
/// # Example Usage

View File

@ -1,297 +0,0 @@
//! Phase 31: LoopToJoinLowerer - 統一 Loop→JoinIR 変換箱
//!
//! このモジュールは MIR の LoopForm を JoinIR に変換する統一インターフェースを提供する。
//!
//! ## 設計思想Phase 33-23 責務分離完了)
//!
//! - **単一エントリポイント**: `LoopToJoinLowerer::lower()` ですべてのループを処理
//! - **責務分離**: 検証・選択・調整を専用Boxに委譲
//! - `LoopPatternValidator`: 構造検証180行
//! - `LoopViewBuilder`: Lowering選択343行
//! - `LoopToJoinLowerer`: コーディネーター(本ファイル)
//! - **既存コード再利用**: generic_case_a の `_with_scope` 関数を内部で呼び出し
//!
//! ## 責務分離Phase 33-9.1
//!
//! **LoopToJoinLowerer の責務**:
//! - MirQuery/LoopFormIntake/LoopScopeShape構築
//! - Validator/Builder呼び出し調整
//! - Strict modeエラーハンドリング
//!
//! **非責務**:
//! - 構造検証(→ LoopPatternValidator
//! - Lowering選択→ LoopViewBuilder
//! - if/else の PHI には触らないIf lowering の責務)
//!
//! ## 使用例
//!
//! ```ignore
//! let lowerer = LoopToJoinLowerer::new();
//! let join_module = lowerer.lower(func, &loop_form, &query)?;
//! ```
use crate::mir::control_form::LoopId;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_pattern_validator::LoopPatternValidator;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_view_builder::LoopViewBuilder;
use crate::mir::join_ir::JoinModule;
use crate::mir::loop_form::LoopForm;
use crate::mir::query::MirQueryBox;
use crate::mir::MirFunction;
/// Phase 32 L-1.2: 汎用 Case-A lowering が有効かどうか
///
/// `NYASH_JOINIR_LOWER_GENERIC=1` の場合、関数名フィルタを外して
/// 構造ベースで Case-A ループを拾う。
fn generic_case_a_enabled() -> bool {
crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC")
}
/// Loop→JoinIR 変換の統一箱Phase 33-23 コーディネーター)
///
/// Phase 31 で導入された統一インターフェース。
/// Phase 33-23 で責務分離完了Validator/Builder委譲
pub struct LoopToJoinLowerer {
/// デバッグモード(詳細ログ出力)
debug: bool,
/// 構造検証箱Phase 33-23
validator: LoopPatternValidator,
/// Lowering選択箱Phase 33-23
builder: LoopViewBuilder,
}
impl Default for LoopToJoinLowerer {
fn default() -> Self {
Self::new()
}
}
impl LoopToJoinLowerer {
/// 新しい LoopToJoinLowerer を作成Phase 33-23 Boxインスタンス化
pub fn new() -> Self {
let debug = std::env::var("NYASH_LOOPTOJOIN_DEBUG")
.map(|v| v == "1")
.unwrap_or(false);
Self {
debug,
validator: LoopPatternValidator::new(),
builder: LoopViewBuilder::new(),
}
}
/// MIR LoopForm を JoinIR に変換
///
/// # Arguments
///
/// - `func`: MIR 関数
/// - `loop_form`: 変換対象の LoopForm
/// - `func_name`: 関数名(オプション、ルーティング用)
///
/// # Returns
///
/// - `Some(JoinModule)`: 変換成功
/// - `None`: 変換失敗(フォールバック経路へ)
pub fn lower(
&self,
func: &MirFunction,
loop_form: &LoopForm,
func_name: Option<&str>,
) -> Option<JoinModule> {
let strict_on = crate::config::env::joinir_strict_enabled();
let is_minimal_target = func_name
.map(super::loop_scope_shape::is_case_a_minimal_target)
.unwrap_or(false);
if self.debug {
eprintln!(
"[LoopToJoinLowerer] lower() called for {:?}",
func_name.unwrap_or("<unknown>")
);
}
// Phase 32 L-1.2: 早期フィルタは削除。構造チェック後に条件付きで適用する。
// Step 1: MirQuery を構築
let query = MirQueryBox::new(func);
// Step 2: LoopFormIntake を構築
// Phase 70-2: var_classes 引数削除完了Trio 依存ゼロ)
let intake = intake_loop_form(loop_form, &query, func)?;
// Step 3: LoopScopeShape を構築
// Phase 48-2: from_loop_form() で Trio を内部化LoopExitLivenessBox 依存削除)
let scope = LoopScopeShape::from_loop_form(loop_form, &intake, &query, func_name)?;
if self.debug {
eprintln!(
"[LoopToJoinLowerer] LoopScopeShape built: pinned={:?}, carriers={:?}, exit_live={:?}",
scope.pinned, scope.carriers, scope.exit_live
);
}
// Phase 32 Step 3-C: View メソッドで構造情報を取得(常に実行)
let loop_id = LoopId(0); // 単一ループの場合は 0
let region = loop_form.to_region_view(loop_id);
let _control = loop_form.to_control_view(loop_id);
let exit_edges = loop_form.to_exit_edges(loop_id);
// Debug: view ベースの情報をログ
if self.debug {
eprintln!(
"[LoopToJoinLowerer] Phase 32 views: func={:?} loop_id={:?} header={:?} exits={:?}",
func_name.unwrap_or("<unknown>"),
loop_id,
region.header,
exit_edges.iter().map(|e| e.to).collect::<Vec<_>>()
);
}
// Phase 33-23: Validator箱に検証を委譲
if !self
.validator
.is_supported_case_a(func, &region, &exit_edges, &scope)
{
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by validator: {:?}",
func_name.unwrap_or("<unknown>")
);
}
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: validator rejected {}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
// Phase 32 L-1.2: 環境変数で分岐
// OFF既定: 従来の minimal 4 本だけ
// ON: 構造だけで通す(関数名フィルタを外す)
if !generic_case_a_enabled() {
if !func_name.map_or(false, super::loop_scope_shape::is_case_a_minimal_target) {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by name filter (generic disabled): {:?}",
func_name.unwrap_or("<unknown>")
);
}
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: name filter rejected {}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
} else if self.debug {
eprintln!(
"[LoopToJoinLowerer] generic Case-A enabled, allowing {:?}",
func_name.unwrap_or("<unknown>")
);
}
// Phase 33-23: Builder箱にlowering選択を委譲
let out = self.builder.build(scope, func_name);
if out.is_none() && strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: lowering failed for {}",
func_name.unwrap_or("<unknown>")
);
}
out
}
// Phase 33-23: Validation/Lowering選択ロジックはValidator/Builderに移動
// - is_supported_case_a_loop_view() → LoopPatternValidator::is_supported_case_a()
// - lower_with_scope() → LoopViewBuilder::build()
// - has_safe_progress() → LoopPatternValidator::validate_progress_carrier()
// ========================================
// Case-A helpers for specific function patterns
// ========================================
/// Case-A 汎用 lowerer の「Main.skip/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_skip_ws(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("Main.skip/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.trim/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_trim(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.trim/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.append_defs/2 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_append_defs(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.append_defs/2"))
}
/// Case-A 汎用 lowerer の「Stage1UsingResolverBox.resolve_for_source/5 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stage1_resolver(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("Stage1UsingResolverBox.resolve_for_source/5"),
)
}
/// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stageb_body(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("StageBBodyExtractorBox.build_body_src/2"),
)
}
/// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stageb_funcscanner(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("StageBFuncScannerBox.scan_all_boxes/1"),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowerer_creation() {
let lowerer = LoopToJoinLowerer::new();
assert!(!lowerer.debug || lowerer.debug); // Just check it compiles
}
}

View File

@ -0,0 +1,73 @@
use super::LoopToJoinLowerer;
use crate::mir::join_ir::JoinModule;
use crate::mir::loop_form::LoopForm;
use crate::mir::MirFunction;
impl LoopToJoinLowerer {
/// Case-A 汎用 lowerer の「Main.skip/1 用」薄いラッパー。
pub fn lower_case_a_for_skip_ws(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("Main.skip/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.trim/1 用」薄いラッパー。
pub fn lower_case_a_for_trim(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.trim/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.append_defs/2 用」薄いラッパー。
pub fn lower_case_a_for_append_defs(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.append_defs/2"))
}
/// Case-A 汎用 lowerer の「Stage1UsingResolverBox.resolve_for_source/5 用」薄いラッパー。
pub fn lower_case_a_for_stage1_resolver(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("Stage1UsingResolverBox.resolve_for_source/5"),
)
}
/// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。
pub fn lower_case_a_for_stageb_body(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("StageBBodyExtractorBox.build_body_src/2"),
)
}
/// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。
pub fn lower_case_a_for_stageb_funcscanner(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(
func,
loop_form,
Some("StageBFuncScannerBox.scan_all_boxes/1"),
)
}
}

View File

@ -0,0 +1,166 @@
use crate::mir::control_form::LoopId;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_pattern_validator::LoopPatternValidator;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_view_builder::LoopViewBuilder;
use crate::mir::join_ir::JoinModule;
use crate::mir::loop_form::LoopForm;
use crate::mir::query::MirQueryBox;
use crate::mir::MirFunction;
fn generic_case_a_enabled() -> bool {
crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC")
}
/// Loop→JoinIR 変換の統一箱coordinator
///
/// - MirQuery/Intake/LoopScopeShape の構築
/// - Validator/Builder 呼び出しの調整
/// - strict mode の fail-fast対象関数のみ
pub struct LoopToJoinLowerer {
debug: bool,
validator: LoopPatternValidator,
builder: LoopViewBuilder,
}
impl Default for LoopToJoinLowerer {
fn default() -> Self {
Self::new()
}
}
impl LoopToJoinLowerer {
pub fn new() -> Self {
let debug = std::env::var("NYASH_LOOPTOJOIN_DEBUG")
.map(|v| v == "1")
.unwrap_or(false);
Self {
debug,
validator: LoopPatternValidator::new(),
builder: LoopViewBuilder::new(),
}
}
/// MIR LoopForm を JoinIR (JoinModule) に変換
///
/// - `Some(JoinModule)`: 変換成功
/// - `None`: 未サポート(上位のフォールバックへ)
pub fn lower(
&self,
func: &MirFunction,
loop_form: &LoopForm,
func_name: Option<&str>,
) -> Option<JoinModule> {
let strict_on = crate::config::env::joinir_strict_enabled();
let is_minimal_target = func_name
.map(super::super::loop_scope_shape::is_case_a_minimal_target)
.unwrap_or(false);
if self.debug {
eprintln!(
"[LoopToJoinLowerer] lower() called for {:?}",
func_name.unwrap_or("<unknown>")
);
}
let query = MirQueryBox::new(func);
let intake = intake_loop_form(loop_form, &query, func)?;
let scope = LoopScopeShape::from_loop_form(loop_form, &intake, &query, func_name)?;
if self.debug {
eprintln!(
"[LoopToJoinLowerer] LoopScopeShape built: pinned={:?}, carriers={:?}, exit_live={:?}",
scope.pinned, scope.carriers, scope.exit_live
);
}
let loop_id = LoopId(0);
let region = loop_form.to_region_view(loop_id);
let exit_edges = loop_form.to_exit_edges(loop_id);
if self.debug {
eprintln!(
"[LoopToJoinLowerer] views: func={:?} loop_id={:?} header={:?} exits={:?}",
func_name.unwrap_or("<unknown>"),
loop_id,
region.header,
exit_edges.iter().map(|e| e.to).collect::<Vec<_>>()
);
}
if !self
.validator
.is_supported_case_a(func, &region, &exit_edges, &scope)
{
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by validator: {:?}",
func_name.unwrap_or("<unknown>")
);
}
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: validator rejected {}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
if !generic_case_a_enabled() {
if !func_name
.map_or(false, super::super::loop_scope_shape::is_case_a_minimal_target)
{
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by name filter (generic disabled): {:?}",
func_name.unwrap_or("<unknown>")
);
}
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: name filter rejected {}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
} else if self.debug {
eprintln!(
"[LoopToJoinLowerer] generic Case-A enabled, allowing {:?}",
func_name.unwrap_or("<unknown>")
);
}
let out = self.builder.build(scope, func_name);
if out.is_none() && strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: lowering failed for {}",
func_name.unwrap_or("<unknown>")
);
}
out
}
/// 旧コメント/ドキュメントとの整合のための別名(導線の明確化)
pub fn lower_loop(
&self,
func: &MirFunction,
loop_form: &LoopForm,
func_name: Option<&str>,
) -> Option<JoinModule> {
self.lower(func, loop_form, func_name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowerer_creation() {
let lowerer = LoopToJoinLowerer::new();
assert!(!lowerer.debug || lowerer.debug);
}
}

View File

@ -0,0 +1,19 @@
//! Phase 31/33-23: Loop→JoinIR lowering entry (LoopToJoinLowerer)
//!
//! このモジュールは「LoopForm を JoinIR (JoinModule) に変換する統一エントリ」を提供する。
//! 本体ロジックは coordinator に集約し、責務を分割して保守性を上げる。
//!
//! ## 責務境界Box化
//! - `LoopPatternValidator``../loop_pattern_validator.rs`: 構造検証shape guard
//! - `LoopViewBuilder``../loop_view_builder.rs`: lowerer 選択routing
//! - `LoopToJoinLowerer`(このモジュール): intake/scope 構築と strict ハンドリングcoordinator
//!
//! ## 注意
//! - Phase 進捗ログはここに混ぜない(現役の導線のみ)。
//! - “とりあえず通す”フォールバックは増やさない。失敗は `None` で上位に返す。
mod core;
mod case_a_entrypoints;
pub use core::LoopToJoinLowerer;

View File

@ -34,7 +34,7 @@ use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression;
#[cfg(debug_assertions)]
use crate::mir::join_ir::lowering::condition_pattern::{
analyze_condition_pattern, ConditionPattern,
analyze_condition_capability, ConditionCapability,
};
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::{
@ -71,11 +71,11 @@ pub fn lower_if_sum_pattern(
#[cfg(debug_assertions)]
if let ASTNode::If { condition, .. } = if_stmt {
let pattern = analyze_condition_pattern(condition);
let capability = analyze_condition_capability(condition);
debug_assert!(
matches!(pattern, ConditionPattern::SimpleComparison),
"[if-sum] Unexpected complex condition pattern passed to AST-based lowerer: {:?}",
pattern
matches!(capability, ConditionCapability::IfSumComparable),
"[if-sum] Unsupported condition passed to AST-based lowerer: {:?}",
capability
);
}

View File

@ -82,15 +82,16 @@ use crate::mir::join_ir::{
/// # Boundary Contract
///
/// This function returns a JoinModule with:
/// - **Input slot**: ValueId(0) in loop_step function represents the loop variable
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueId(0) to host's loop var
/// - **Input slot**: main() の paramJoinValueSpace の Param regionでループ変数を受け取る
/// - **Caller responsibility**: Create JoinInlineBoundary to map that param ValueId to host's loop var
pub(crate) fn lower_simple_while_minimal(
_scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule> {
// Phase 202-A: Use JoinValueSpace for Local region allocation (1000+)
// This ensures no collision with Param region (100-999) used by ConditionEnv/CarrierInfo
let mut alloc_value = || join_value_space.alloc_local();
// Phase 202-A/Phase 205: Use JoinValueSpace for Param/Local region allocation.
// - Params: boundary join_inputs (host loop var wiring)
// - Locals: constants, intermediate values
// NOTE: Avoid holding multiple closures borrowing join_value_space mutably at once.
let mut join_module = JoinModule::new();
@ -102,36 +103,34 @@ pub(crate) fn lower_simple_while_minimal(
let k_exit_id = JoinFuncId::new(2);
// ==================================================================
// ValueId allocation (Phase 188-Impl-3: Sequential local IDs)
// ValueId allocation
// ==================================================================
// main() locals
let i_init = alloc_value(); // ValueId(0) - loop init value
let loop_result = alloc_value(); // ValueId(1) - result from loop_step
let const_0_main = alloc_value(); // ValueId(2) - return value
// main() params/locals
let i_main_param = join_value_space.alloc_param(); // boundary input (host loop var → this param)
let loop_result = join_value_space.alloc_local(); // result from loop_step
let const_0_main = join_value_space.alloc_local(); // return value
// loop_step locals
let i_param = alloc_value(); // ValueId(3) - parameter
let const_3 = alloc_value(); // ValueId(4) - comparison constant
let cmp_lt = alloc_value(); // ValueId(5) - i < 3
let exit_cond = alloc_value(); // ValueId(6) - !(i < 3)
let const_1 = alloc_value(); // ValueId(7) - increment constant
let i_next = alloc_value(); // ValueId(8) - i + 1
// loop_step params/locals
let i_step_param = join_value_space.alloc_param(); // loop_step parameter
let const_3 = join_value_space.alloc_local(); // comparison constant
let cmp_lt = join_value_space.alloc_local(); // i < 3
let exit_cond = join_value_space.alloc_local(); // !(i < 3)
let const_1 = join_value_space.alloc_local(); // increment constant
let i_next = join_value_space.alloc_local(); // i + 1
// k_exit locals
// Phase 132: i_exit receives loop variable from Jump
let i_exit = alloc_value(); // ValueId(9) - exit parameter (loop variable)
// k_exit params
let i_exit_param = join_value_space.alloc_param(); // exit parameter (loop variable)
// ==================================================================
// main() function
// ==================================================================
// Phase 188-Impl-3: main() takes i as a parameter (boundary input)
// The host will inject a Copy instruction: i_init_local = Copy host_i
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init]);
// main() takes loop var as a param (boundary input)
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_main_param]);
// result = loop_step(i_init)
// result = loop_step(i_main_param)
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_init],
args: vec![i_main_param],
k_next: None,
dst: Some(loop_result),
});
@ -152,7 +151,7 @@ pub(crate) fn lower_simple_while_minimal(
// loop_step(i) function
// ==================================================================
let mut loop_step_func =
JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_param]);
JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_step_param]);
// exit_cond = !(i < 3)
// Step 1: const 3
@ -169,7 +168,7 @@ pub(crate) fn lower_simple_while_minimal(
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt,
lhs: i_param,
lhs: i_step_param,
rhs: const_3,
}));
@ -186,7 +185,7 @@ pub(crate) fn lower_simple_while_minimal(
// Pass loop variable to exit continuation for return value parity
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![i_param],
args: vec![i_step_param],
cond: Some(exit_cond),
});
@ -194,7 +193,7 @@ pub(crate) fn lower_simple_while_minimal(
// Phase 188-Impl-1-E: Use Print instruction
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Print { value: i_param }));
.push(JoinInst::Compute(MirLikeInst::Print { value: i_step_param }));
// i_next = i + 1
// Step 1: const 1
@ -211,7 +210,7 @@ pub(crate) fn lower_simple_while_minimal(
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_next,
op: BinOpKind::Add,
lhs: i_param,
lhs: i_step_param,
rhs: const_1,
}));
@ -228,12 +227,12 @@ pub(crate) fn lower_simple_while_minimal(
// ==================================================================
// k_exit(i_exit) function - Phase 132: receives loop variable
// ==================================================================
let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![i_exit]);
let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![i_exit_param]);
// Phase 132: return i_exit (loop variable at exit)
// This ensures VM/LLVM parity for `return i` after loop
k_exit_func.body.push(JoinInst::Ret {
value: Some(i_exit),
value: Some(i_exit_param),
});
join_module.add_function(k_exit_func);