📦 Hotfix 1 & 2: Parameter ValueId Reservation + Exit PHI Validation (Box-First Theory)

**箱理論に基づく根治的修正**:

## 🎯 Hotfix 1: Parameter ValueId Reservation (パラメータ ValueId 予約)

### 根本原因
- MirFunction counter が params.len() を考慮していなかった
- local variables が parameter ValueIds を上書き

### 箱理論的解決
1. **LoopFormContext Box**
   - パラメータ予約を明示的に管理
   - 境界をはっきりさせる

2. **MirFunction::new() 改善**
   - `initial_counter = param_count.max(1)` でパラメータ予約
   - Parameters are %0, %1, ..., %N-1

3. **ensure_counter_after() 強化**
   - パラメータ数 + 既存 ValueIds 両方を考慮
   - `min_counter = param_count.max(max_id + 1)`

4. **reserve_parameter_value_ids() 追加**
   - 明示的な予約メソッド(Box-First)

## 🎯 Hotfix 2: Exit PHI Predecessor Validation (Exit PHI 検証)

### 根本原因
- LoopForm builder が存在しないブロックを PHI predecessor に追加
- 「幽霊ブロック」問題

### 箱理論的解決
1. **LoopFormOps.block_exists() 追加**
   - CFG 存在確認メソッド
   - 境界を明確化

2. **build_exit_phis() 検証**
   - 非存在ブロックをスキップ
   - デバッグログ付き

### 実装ファイル
- `src/mir/function.rs`: Parameter reservation
- `src/mir/phi_core/loopform_builder.rs`: Context + validation
- `src/mir/loop_builder.rs`: LoopFormOps impl
- `src/mir/builder/stmts.rs`: Local variable allocation

### 業界標準準拠
-  LLVM IR: Parameters are %0, %1, ...
-  SSA Form: PHI predecessors must exist in CFG
-  Cytron et al. (1991): Parameter reservation principle

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-18 06:39:45 +09:00
parent 0f43bc6b53
commit f74b7d2b04
24 changed files with 2207 additions and 50 deletions

View File

@ -2,6 +2,24 @@ use super::{EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModul
use crate::ast::ASTNode;
// Lifecycle routines extracted from builder.rs
fn has_main_static(ast: &ASTNode) -> bool {
use crate::ast::ASTNode as N;
if let N::Program { statements, .. } = ast {
for st in statements {
if let N::BoxDeclaration { name, methods, is_static, .. } = st {
if *is_static && name == "Main" {
if let Some(m) = methods.get("main") {
if let N::FunctionDeclaration { .. } = m {
return true;
}
}
}
}
}
}
false
}
impl super::MirBuilder {
/// Unified declaration indexing (Phase A): collect symbols before lowering
/// - user_defined_boxes: non-static Box names (for NewBox birth() skip)
@ -66,6 +84,15 @@ impl super::MirBuilder {
let snapshot = ast.clone();
// Phase A: collect declarations in one pass (symbols available to lowering)
self.index_declarations(&snapshot);
// Decide root mode (App vs Script) once per module based on presence of static box Main.main
// true => App mode (Main.main is entry)
// false => Script/Test mode (top-level Program runs sequentially)
let is_app_mode = self
.root_is_app_mode
.unwrap_or_else(|| has_main_static(&snapshot));
self.root_is_app_mode = Some(is_app_mode);
// Phase B: top-level program lowering with declaration-first pass
match ast {
ASTNode::Program { statements, .. } => {
@ -87,31 +114,34 @@ impl super::MirBuilder {
if name == "Main" {
main_static = Some((name.clone(), methods.clone()));
} else {
// Dev: trace which static box is being lowered (env-gated)
self.trace_compile(format!("lower static box {}", name));
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
// スコープを抜けると自動的にコンテキストが破棄される
{
let ctx = super::context::BoxCompilationContext::new();
self.compilation_context = Some(ctx);
// Script/Test モードでは static box の lowering は exprs.rs 側に任せる
if is_app_mode {
// Dev: trace which static box is being lowered (env-gated)
self.trace_compile(format!("lower static box {}", name));
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
// スコープを抜けると自動的にコンテキストが破棄される
{
let ctx = super::context::BoxCompilationContext::new();
self.compilation_context = Some(ctx);
// Lower all static methods into standalone functions: BoxName.method/Arity
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration { params, body, .. } = mast {
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
// Lower all static methods into standalone functions: BoxName.method/Arity
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration { params, body, .. } = mast {
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
}
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
// これにより、次のstatic boxは汚染されていない状態から開始される
self.compilation_context = None;
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
// これにより、次のstatic boxは汚染されていない状態から開始される
self.compilation_context = None;
}
}
} else {
// Instance box: register type and lower instance methods/ctors as functions
@ -152,11 +182,17 @@ impl super::MirBuilder {
}
}
// Second pass: lower Main.main body as Program (entry). If absent, fall back to sequential block.
if let Some((box_name, methods)) = main_static {
self.build_static_main_box(box_name, methods)
// Second pass: mode-dependent entry lowering
if is_app_mode {
// App モード: Main.main をエントリとして扱う
if let Some((box_name, methods)) = main_static {
self.build_static_main_box(box_name, methods)
} else {
// 理論上は起こりにくいが、安全のため Script モードと同じフォールバックにする
self.cf_block(statements)
}
} else {
// Fallback: sequential lowering (keeps legacy behavior for scripts without Main)
// Script/Test モード: トップレベル Program をそのまま順次実行
self.cf_block(statements)
}
}