feat(control_tree): Phase 131 P1.5-P2 DirectValue exit reconnection
Implement DirectValue mode for Normalized shadow exit handling:
**P1.5 Changes**:
- Add ExitReconnectMode::DirectValue (skip exit PHI generation)
- Carry remapped_exit_values through merge result
- Update host variable_map directly with exit values
- Fix loop(true) { x = 1; break }; return x to return 1 correctly
**P2 Changes**:
- Normalize k_exit continuation entry/exit edges
- Rewrite TailCall(k_exit) → Jump(exit_block) for proper merge
- Add verify_all_terminator_targets_exist contract check
- Extend ExitLineReconnector to handle DirectValue mode
**Infrastructure**:
- tools/build_llvm.sh: Force TMPDIR under target/ (EXDEV mitigation)
- llvm_exe_runner.sh: Add exit_code verification support
- Phase 131 smokes: Update for dev-only + exit code validation
**Contracts**:
- PHI-free: Normalized path uses continuations only
- Exit values reconnect via remapped ValueIds
- Existing patterns unaffected (既定挙動不変)
Related: Phase 131 loop(true) break-once Normalized support
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -251,8 +251,9 @@ impl MirBuilder {
|
||||
/// Phase 49-3: JoinIR Frontend integration implementation
|
||||
///
|
||||
/// Routes loop compilation through either:
|
||||
/// 1. Pattern-based router (Phase 194+) - preferred for new patterns
|
||||
/// 2. Legacy binding path (Phase 49-3) - for whitelisted functions only
|
||||
/// 1. Normalized shadow (Phase 131 P1) - dev-only for loop(true) break-once
|
||||
/// 2. Pattern-based router (Phase 194+) - preferred for new patterns
|
||||
/// 3. Legacy binding path (Phase 49-3) - for whitelisted functions only
|
||||
pub(in crate::mir::builder) fn cf_loop_joinir_impl(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
@ -260,6 +261,13 @@ impl MirBuilder {
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// Phase 131 P1: Try Normalized shadow first (dev-only)
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
if let Some(result) = self.try_normalized_shadow(condition, body, func_name, debug)? {
|
||||
return Ok(Some(result));
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 137-2/137-4: Dev-only observation via Loop Canonicalizer
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
use crate::ast::Span;
|
||||
@ -390,4 +398,169 @@ impl MirBuilder {
|
||||
// Delegate to legacy binding path (routing_legacy_binding.rs)
|
||||
self.cf_loop_joinir_legacy_binding(condition, body, func_name, debug)
|
||||
}
|
||||
|
||||
/// Phase 131 P1: Try Normalized shadow lowering (dev-only)
|
||||
///
|
||||
/// Returns:
|
||||
/// - Ok(Some(value_id)): Successfully lowered and merged via Normalized
|
||||
/// - Ok(None): Out of scope (not a Normalized pattern)
|
||||
/// - Err(msg): In scope but failed (Fail-Fast in strict mode)
|
||||
fn try_normalized_shadow(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::ast::Span;
|
||||
use crate::mir::control_tree::normalized_shadow::available_inputs_collector::AvailableInputsCollectorBox;
|
||||
use crate::mir::control_tree::normalized_shadow::StepTreeNormalizedShadowLowererBox;
|
||||
use crate::mir::control_tree::StepTreeBuilderBox;
|
||||
|
||||
// Build StepTree from loop AST
|
||||
let loop_ast = ASTNode::Loop {
|
||||
condition: Box::new(condition.clone()),
|
||||
body: body.to_vec(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let tree = StepTreeBuilderBox::build_from_ast(&loop_ast);
|
||||
|
||||
// Collect available inputs from MirBuilder state
|
||||
let available_inputs = AvailableInputsCollectorBox::collect(self, None);
|
||||
|
||||
trace::trace().routing(
|
||||
"router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Trying Normalized shadow lowering (available_inputs: {})",
|
||||
available_inputs.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Try Normalized lowering (loop(true) break-once pattern)
|
||||
match StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs) {
|
||||
Ok(Some((join_module, join_meta))) => {
|
||||
trace::trace().routing(
|
||||
"router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Normalized lowering succeeded ({} functions)",
|
||||
join_module.functions.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Phase 131 P1.5: Create boundary with DirectValue mode
|
||||
//
|
||||
// Strategy (SSOT: merge owns remapper):
|
||||
// 1. Create boundary with exit_bindings from meta
|
||||
// 2. Set exit_reconnect_mode = DirectValue (no PHI generation)
|
||||
// 3. Merge populates MergeResult.remapped_exit_values (JoinIR → Host ValueIds)
|
||||
// 4. Use remapped_exit_values for direct variable_map reconnection
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, ExitReconnectMode};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
|
||||
// Build exit_bindings from meta
|
||||
let exit_bindings: Vec<LoopExitBinding> = join_meta
|
||||
.exit_meta
|
||||
.exit_values
|
||||
.iter()
|
||||
.map(|(carrier_name, join_exit_value)| {
|
||||
// Get host_slot from variable_map
|
||||
let host_slot = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(carrier_name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[Phase 131 P1.5] Carrier '{}' not in variable_map (available: {:?})",
|
||||
carrier_name,
|
||||
self.variable_ctx.variable_map.keys().collect::<Vec<_>>()
|
||||
)
|
||||
});
|
||||
|
||||
LoopExitBinding {
|
||||
carrier_name: carrier_name.clone(),
|
||||
join_exit_value: *join_exit_value,
|
||||
host_slot,
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Create boundary with DirectValue mode
|
||||
let mut boundary = JoinInlineBoundary::new_with_exit_bindings(
|
||||
vec![], // No join_inputs for Normalized
|
||||
vec![], // No host_inputs for Normalized
|
||||
exit_bindings,
|
||||
);
|
||||
boundary.exit_reconnect_mode = ExitReconnectMode::DirectValue; // Phase 131 P1.5: No PHI
|
||||
|
||||
// Merge with boundary - this will populate MergeResult.remapped_exit_values
|
||||
use crate::mir::builder::control_flow::joinir::merge;
|
||||
use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta;
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
||||
let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[normalized/pipeline] MIR conversion failed: {:?}", e))?;
|
||||
|
||||
// Merge with boundary - this populates MergeResult.remapped_exit_values
|
||||
// and calls ExitLineOrchestrator with DirectValue mode
|
||||
let _exit_phi_result = merge::merge_joinir_mir_blocks(
|
||||
self,
|
||||
&mir_module,
|
||||
Some(&boundary),
|
||||
debug,
|
||||
)?;
|
||||
|
||||
trace::trace().routing(
|
||||
"router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Normalized merge + reconnection completed ({} exit bindings)",
|
||||
boundary.exit_bindings.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Phase 131 P1.5: Loop executed successfully, return void constant
|
||||
use crate::mir::{ConstValue, MirInstruction};
|
||||
let void_id = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_id,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
|
||||
Ok(Some(void_id))
|
||||
}
|
||||
Ok(None) => {
|
||||
// Out of scope (not a Normalized pattern)
|
||||
trace::trace().routing(
|
||||
"router/normalized",
|
||||
func_name,
|
||||
"Normalized lowering: out of scope",
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => {
|
||||
// In scope but failed - Fail-Fast in strict mode
|
||||
let msg = format!(
|
||||
"Phase 131/normalized: Failed to lower loop(true) break-once pattern in '{}': {}",
|
||||
func_name, e
|
||||
);
|
||||
if crate::config::env::joinir_dev::strict_enabled() {
|
||||
use crate::mir::join_ir::lowering::error_tags;
|
||||
return Err(error_tags::freeze_with_hint(
|
||||
"phase131/normalized_loop/internal",
|
||||
&e,
|
||||
"Loop should be supported by Normalized but conversion failed. \
|
||||
Check that condition is Bool(true) and body ends with break.",
|
||||
));
|
||||
}
|
||||
trace::trace().routing("router/normalized/error", func_name, &msg);
|
||||
Ok(None) // Non-strict: fall back to existing patterns
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user