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:
@ -57,7 +57,7 @@ impl MirBuilder {
|
||||
trace::trace().varmap("pattern1_start", &self.variable_map);
|
||||
|
||||
// Phase 202-A: Create JoinValueSpace for unified ValueId allocation
|
||||
// Pattern 1 uses Local region only (no Param region needed - no ConditionEnv)
|
||||
// Pattern 1 uses Param region for boundary input slots (loop var) and Local region for temps.
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
@ -80,6 +80,7 @@ impl MirBuilder {
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
use crate::mir::join_ir::lowering::join_value_space::PARAM_MIN;
|
||||
|
||||
// Phase 132-Post: Extract k_exit's parameter ValueId from join_module (Box-First)
|
||||
let k_exit_func = join_module.require_function("k_exit", "Pattern 1");
|
||||
@ -96,7 +97,7 @@ impl MirBuilder {
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(
|
||||
vec![ValueId(0)], // JoinIR's main() parameter (loop variable)
|
||||
vec![ValueId(PARAM_MIN)], // JoinIR's main() parameter (loop variable, Param region)
|
||||
vec![ctx.loop_var_id], // Host's loop variable
|
||||
)
|
||||
.with_exit_bindings(vec![exit_binding]) // Phase 132: Enable exit PHI & variable_map update
|
||||
|
||||
@ -188,19 +188,11 @@ impl PatternPipelineContext {
|
||||
// Complex conditions (e.g., i % 2 == 1) → fallback to legacy mode
|
||||
if let Some(ASTNode::If { condition, .. }) = if_stmt {
|
||||
use crate::mir::join_ir::lowering::condition_pattern::{
|
||||
analyze_condition_pattern, normalize_comparison, ConditionPattern,
|
||||
analyze_condition_capability, ConditionCapability,
|
||||
};
|
||||
|
||||
// (a) Pattern check: must be SimpleComparison
|
||||
let pattern = analyze_condition_pattern(condition);
|
||||
if pattern != ConditionPattern::SimpleComparison {
|
||||
// Complex condition → legacy mode (PoC lowering)
|
||||
return false;
|
||||
}
|
||||
|
||||
// (b) Normalization check: must be normalizable
|
||||
if normalize_comparison(condition).is_none() {
|
||||
// Normalization failed → legacy mode
|
||||
// Capability check: if-sum lowerer が扱える比較か
|
||||
if analyze_condition_capability(condition) != ConditionCapability::IfSumComparable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,6 +389,11 @@ impl super::MirBuilder {
|
||||
// After PHI types are corrected, re-infer BinOp result types
|
||||
self.repropagate_binop_types(&mut function);
|
||||
|
||||
// Phase 84-5 guard hardening: ensure call/await results are registered in `value_types`
|
||||
// before return type inference. This avoids "impossible" debug panics when the builder
|
||||
// emitted a value-producing instruction without annotating its dst type.
|
||||
self.annotate_missing_result_types_from_calls_and_await(&function, &module);
|
||||
|
||||
// Phase 131-9: Update function metadata with corrected types
|
||||
// MUST happen after PHI type correction above AND BinOp re-propagation
|
||||
function.metadata.value_types = self.value_types.clone();
|
||||
@ -600,6 +605,69 @@ impl super::MirBuilder {
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn annotate_missing_result_types_from_calls_and_await(
|
||||
&mut self,
|
||||
function: &super::MirFunction,
|
||||
module: &MirModule,
|
||||
) {
|
||||
use crate::mir::definitions::Callee;
|
||||
use crate::mir::MirInstruction;
|
||||
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
match inst {
|
||||
MirInstruction::Await { dst, future } => {
|
||||
if self.value_types.contains_key(dst) {
|
||||
continue;
|
||||
}
|
||||
let inferred = match self.value_types.get(future) {
|
||||
Some(MirType::Future(inner)) => (**inner).clone(),
|
||||
_ => MirType::Unknown,
|
||||
};
|
||||
self.value_types.insert(*dst, inferred);
|
||||
}
|
||||
MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
callee: Some(callee),
|
||||
..
|
||||
} => {
|
||||
if self.value_types.contains_key(dst) {
|
||||
continue;
|
||||
}
|
||||
let inferred = match callee {
|
||||
Callee::Global(name) => module
|
||||
.functions
|
||||
.get(name)
|
||||
.map(|f| f.signature.return_type.clone())
|
||||
.or_else(|| {
|
||||
crate::mir::builder::types::annotation::annotate_from_function(
|
||||
self, *dst, name,
|
||||
);
|
||||
self.value_types.get(dst).cloned()
|
||||
})
|
||||
.unwrap_or(MirType::Unknown),
|
||||
Callee::Constructor { box_type } => {
|
||||
let ret = MirType::Box(box_type.clone());
|
||||
self.value_origin_newbox.insert(*dst, box_type.clone());
|
||||
ret
|
||||
}
|
||||
_ => MirType::Unknown,
|
||||
};
|
||||
self.value_types.insert(*dst, inferred);
|
||||
}
|
||||
MirInstruction::ExternCall { dst: Some(dst), .. }
|
||||
| MirInstruction::BoxCall { dst: Some(dst), .. }
|
||||
| MirInstruction::PluginInvoke { dst: Some(dst), .. } => {
|
||||
if !self.value_types.contains_key(dst) {
|
||||
self.value_types.insert(*dst, MirType::Unknown);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 131-11-E: Re-propagate BinOp result types after PHI resolution
|
||||
// This fixes cases where BinOp instructions were created before PHI types were known
|
||||
fn repropagate_binop_types(&mut self, function: &mut super::MirFunction) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
||||
use super::{Effect, EffectMask, MirInstruction, MirType, ValueId};
|
||||
use crate::ast::{ASTNode, CallExpr};
|
||||
use crate::mir::utils::is_current_block_terminated;
|
||||
use crate::mir::TypeOpKind;
|
||||
@ -424,6 +424,10 @@ impl super::MirBuilder {
|
||||
args: arg_vals,
|
||||
effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io),
|
||||
})?;
|
||||
// Future spawn returns a Future<T>; the inner type is not statically known here.
|
||||
// Register at least Future<Unknown> to avoid later fail-fast type inference panics.
|
||||
self.value_types
|
||||
.insert(future_id, MirType::Future(Box::new(MirType::Unknown)));
|
||||
self.variable_map.insert(variable.clone(), future_id);
|
||||
if let Some(reg) = self.current_slot_registry.as_mut() {
|
||||
reg.ensure_slot(&variable, None);
|
||||
@ -436,6 +440,13 @@ impl super::MirBuilder {
|
||||
dst: future_id,
|
||||
value: expression_value,
|
||||
})?;
|
||||
let inner = self
|
||||
.value_types
|
||||
.get(&expression_value)
|
||||
.cloned()
|
||||
.unwrap_or(MirType::Unknown);
|
||||
self.value_types
|
||||
.insert(future_id, MirType::Future(Box::new(inner)));
|
||||
self.variable_map.insert(variable.clone(), future_id);
|
||||
if let Some(reg) = self.current_slot_registry.as_mut() {
|
||||
reg.ensure_slot(&variable, None);
|
||||
@ -455,6 +466,11 @@ impl super::MirBuilder {
|
||||
dst: result_id,
|
||||
future: future_value,
|
||||
})?;
|
||||
let result_type = match self.value_types.get(&future_value) {
|
||||
Some(MirType::Future(inner)) => (**inner).clone(),
|
||||
_ => MirType::Unknown,
|
||||
};
|
||||
self.value_types.insert(result_id, result_type);
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
Ok(result_id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user