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

@ -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;