323 lines
9.3 KiB
Markdown
323 lines
9.3 KiB
Markdown
|
|
# Phase 131 P1.5: Implementation Guide (Quick Reference)
|
||
|
|
|
||
|
|
Status: Implementation Guide
|
||
|
|
Scope: Step-by-step implementation instructions for Option B
|
||
|
|
Related:
|
||
|
|
- Root Cause: `p1.5-root-cause-summary.md`
|
||
|
|
- Design: `p1.5-option-b-analysis.md`
|
||
|
|
|
||
|
|
## Implementation Sequence
|
||
|
|
|
||
|
|
### Step 1: Create ExitReconnectorBox (New File)
|
||
|
|
|
||
|
|
**File**: `src/mir/control_tree/normalized_shadow/exit_reconnector.rs`
|
||
|
|
|
||
|
|
**Responsibility**: Generate host variable bindings from k_exit env parameters (Normalized IR only, PHI-free)
|
||
|
|
|
||
|
|
**API**:
|
||
|
|
```rust
|
||
|
|
pub struct ExitReconnectorBox;
|
||
|
|
|
||
|
|
impl ExitReconnectorBox {
|
||
|
|
/// Reconnect k_exit env parameters to host variable_map
|
||
|
|
///
|
||
|
|
/// For Normalized IR only: k_exit env params are SSOT for final values
|
||
|
|
pub fn reconnect(
|
||
|
|
builder: &mut MirBuilder,
|
||
|
|
exit_meta: &ExitMeta,
|
||
|
|
env_layout: &EnvLayout,
|
||
|
|
debug: bool,
|
||
|
|
) -> Result<(), String> {
|
||
|
|
// For each exit_value (var_name, k_exit_param_vid)
|
||
|
|
// - k_exit_param_vid is already the final value (SSOT)
|
||
|
|
// - Update builder.variable_ctx.variable_map[var_name] = k_exit_param_vid
|
||
|
|
//
|
||
|
|
// Strict mode: verify all env_layout.writes are in exit_meta
|
||
|
|
//
|
||
|
|
// Returns: Ok(()) on success, Err(msg) on contract violation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Algorithm**:
|
||
|
|
```rust
|
||
|
|
// 1. Validate (strict mode only)
|
||
|
|
if strict_enabled() {
|
||
|
|
for var in &env_layout.writes {
|
||
|
|
if !exit_meta.exit_values.iter().any(|(name, _)| name == var) {
|
||
|
|
return Err(freeze_with_hint(
|
||
|
|
"phase131/exit_reconnect/missing",
|
||
|
|
&format!("Variable '{}' in writes but not in k_exit env", var),
|
||
|
|
"Ensure env_layout.writes matches k_exit parameter list (SSOT)"
|
||
|
|
));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Update variable_map directly (no PHI)
|
||
|
|
for (var_name, k_exit_param_vid) in &exit_meta.exit_values {
|
||
|
|
// k_exit_param_vid is the final value (SSOT)
|
||
|
|
builder.variable_ctx.variable_map.insert(
|
||
|
|
var_name.clone(),
|
||
|
|
*k_exit_param_vid
|
||
|
|
);
|
||
|
|
|
||
|
|
if debug {
|
||
|
|
eprintln!(
|
||
|
|
"[phase131/exit_reconnect] {} → {:?}",
|
||
|
|
var_name, k_exit_param_vid
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**File Template**:
|
||
|
|
```rust
|
||
|
|
//! Phase 131 P1.5: ExitReconnectorBox - Normalized Exit Reconnection
|
||
|
|
//!
|
||
|
|
//! ## Responsibility
|
||
|
|
//!
|
||
|
|
//! - Wire k_exit env parameters to host variable_map (Normalized IR only)
|
||
|
|
//! - PHI-free: k_exit env params are SSOT for final values
|
||
|
|
//! - Fail-Fast: Strict mode catches contract violations
|
||
|
|
//!
|
||
|
|
//! ## Contract
|
||
|
|
//!
|
||
|
|
//! - Only used for Normalized shadow path
|
||
|
|
//! - k_exit env parameters represent final variable values (SSOT)
|
||
|
|
//! - No PHI generation (direct variable_map update)
|
||
|
|
//!
|
||
|
|
//! ## Design Philosophy
|
||
|
|
//!
|
||
|
|
//! Normalized IR uses continuation-based control flow:
|
||
|
|
//! - main(env) → loop_step(env) → loop_body(env) → k_exit(env)
|
||
|
|
//! - k_exit receives final env state as parameters
|
||
|
|
//! - No loop back edges, no PHI merging needed
|
||
|
|
//! - k_exit env params ARE the truth (SSOT)
|
||
|
|
|
||
|
|
use crate::mir::builder::MirBuilder;
|
||
|
|
use crate::mir::control_tree::normalized_shadow::env_layout::EnvLayout;
|
||
|
|
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
|
||
|
|
use crate::mir::join_ir::lowering::error_tags;
|
||
|
|
|
||
|
|
pub struct ExitReconnectorBox;
|
||
|
|
|
||
|
|
impl ExitReconnectorBox {
|
||
|
|
// ... implementation here ...
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
// ... tests here ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 2: Update routing.rs (Bypass Merge Pipeline)
|
||
|
|
|
||
|
|
**File**: `src/mir/builder/control_flow/joinir/routing.rs`
|
||
|
|
|
||
|
|
**Location**: Lines 441-532 (current Normalized shadow handling)
|
||
|
|
|
||
|
|
**Changes**:
|
||
|
|
|
||
|
|
1. **Add env_layout to JoinFragmentMeta** (if not already there)
|
||
|
|
2. **Detect Normalized shadow path**
|
||
|
|
3. **Use ExitReconnectorBox instead of merge pipeline**
|
||
|
|
|
||
|
|
**Code Change**:
|
||
|
|
```rust
|
||
|
|
// Around line 441
|
||
|
|
match StepTreeNormalizedShadowLowererBox::try_lower_if_only(&tree, &available_inputs) {
|
||
|
|
Ok(Some((join_module, join_meta))) => {
|
||
|
|
// ... existing trace ...
|
||
|
|
|
||
|
|
// Phase 131 P1.5: Normalized-specific exit reconnection
|
||
|
|
// Bypass PHI-based merge pipeline, use direct env→host wiring
|
||
|
|
use crate::mir::control_tree::normalized_shadow::exit_reconnector::ExitReconnectorBox;
|
||
|
|
use crate::mir::control_tree::normalized_shadow::env_layout::EnvLayout;
|
||
|
|
|
||
|
|
// Build env_layout from contract (or get from join_meta if available)
|
||
|
|
let env_layout = EnvLayout::from_contract(&tree.contract, &available_inputs);
|
||
|
|
|
||
|
|
// Direct reconnection (no PHI)
|
||
|
|
ExitReconnectorBox::reconnect(
|
||
|
|
self,
|
||
|
|
&join_meta.exit_meta,
|
||
|
|
&env_layout,
|
||
|
|
debug,
|
||
|
|
)?;
|
||
|
|
|
||
|
|
trace::trace().routing(
|
||
|
|
"router/normalized",
|
||
|
|
func_name,
|
||
|
|
"Normalized exit reconnection complete (PHI-free)",
|
||
|
|
);
|
||
|
|
|
||
|
|
// Return void constant (loop executed successfully)
|
||
|
|
// Note: If k_exit returns a value, use that instead
|
||
|
|
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) => {
|
||
|
|
// ... existing fallback ...
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
// ... existing error handling ...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note**: Remove the existing `ExitMetaCollector::collect()` and `JoinIRConversionPipeline::execute()` calls - those are for standard loops with PHI.
|
||
|
|
|
||
|
|
### Step 3: Update mod.rs (Export ExitReconnectorBox)
|
||
|
|
|
||
|
|
**File**: `src/mir/control_tree/normalized_shadow/mod.rs`
|
||
|
|
|
||
|
|
**Add**:
|
||
|
|
```rust
|
||
|
|
pub mod exit_reconnector;
|
||
|
|
pub use exit_reconnector::ExitReconnectorBox;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 4: Testing
|
||
|
|
|
||
|
|
#### Unit Tests (in exit_reconnector.rs)
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_empty_writes() {
|
||
|
|
// When env_layout.writes is empty
|
||
|
|
// Should return Ok(()) without updating variable_map
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_single_write() {
|
||
|
|
// When env_layout.writes = ["x"]
|
||
|
|
// And exit_meta.exit_values = [("x", ValueId(6))]
|
||
|
|
// Should update variable_map["x"] = ValueId(6)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
#[should_panic]
|
||
|
|
fn test_missing_variable_strict() {
|
||
|
|
// When strict mode enabled
|
||
|
|
// And env_layout.writes = ["x", "y"]
|
||
|
|
// But exit_meta.exit_values = [("x", ValueId(6))]
|
||
|
|
// Should panic with freeze_with_hint
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Integration Tests
|
||
|
|
|
||
|
|
1. **VM Smoke**:
|
||
|
|
```bash
|
||
|
|
bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh
|
||
|
|
```
|
||
|
|
Expected: `[PASS] Output verified: 1 (exit code: 1)`
|
||
|
|
|
||
|
|
2. **LLVM EXE Smoke**:
|
||
|
|
```bash
|
||
|
|
bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh
|
||
|
|
```
|
||
|
|
Expected: `[PASS] Output verified: 1`
|
||
|
|
|
||
|
|
3. **Regression**:
|
||
|
|
```bash
|
||
|
|
# If-only patterns (Phase 130)
|
||
|
|
bash tools/smokes/v2/profiles/integration/apps/phase130_if_only_post_if_add_vm.sh
|
||
|
|
|
||
|
|
# Existing JoinIR patterns (Phase 97)
|
||
|
|
bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh
|
||
|
|
```
|
||
|
|
Expected: All PASS (既定挙動不変)
|
||
|
|
|
||
|
|
### Step 5: Verification Checklist
|
||
|
|
|
||
|
|
Before committing:
|
||
|
|
- [ ] `cargo build --release` → 0 errors
|
||
|
|
- [ ] `cargo test --lib` → all tests pass
|
||
|
|
- [ ] Phase 131 VM smoke → PASS
|
||
|
|
- [ ] Phase 131 LLVM EXE smoke → PASS
|
||
|
|
- [ ] Phase 130 regression → PASS
|
||
|
|
- [ ] Phase 97 regression → PASS
|
||
|
|
|
||
|
|
## Commit Sequence
|
||
|
|
|
||
|
|
### Commit 1: Create ExitReconnectorBox
|
||
|
|
```
|
||
|
|
feat(normalized): Phase 131 P1.5 ExitReconnectorBox for host variable propagation
|
||
|
|
|
||
|
|
- Add exit_reconnector.rs with pure function design
|
||
|
|
- Wire k_exit env params to host variable_map (PHI-free)
|
||
|
|
- Strict mode verifier for contract violations
|
||
|
|
- Unit tests for empty/single/multiple writes
|
||
|
|
```
|
||
|
|
|
||
|
|
### Commit 2: Update Routing
|
||
|
|
```
|
||
|
|
fix(joinir/dev): bypass PHI generation for Normalized path
|
||
|
|
|
||
|
|
- Detect Normalized shadow in routing.rs
|
||
|
|
- Use ExitReconnectorBox instead of merge pipeline
|
||
|
|
- Direct env→host wiring (no PHI generation)
|
||
|
|
- Existing patterns (Pattern 1-4) unaffected
|
||
|
|
```
|
||
|
|
|
||
|
|
### Commit 3: Enable Tests
|
||
|
|
```
|
||
|
|
test(joinir): Phase 131 P1.5 enforce return parity (VM + LLVM EXE)
|
||
|
|
|
||
|
|
- Enable phase131 VM/LLVM EXE smokes
|
||
|
|
- Verify regression tests (phase130, phase97)
|
||
|
|
- Update test expectations
|
||
|
|
```
|
||
|
|
|
||
|
|
### Commit 4: Documentation
|
||
|
|
```
|
||
|
|
docs: Phase 131 P1.5 DONE (Normalized exit reconnection via Option B)
|
||
|
|
|
||
|
|
- Update 10-Now.md with completion status
|
||
|
|
- Update phase-131/README.md with P1.5 summary
|
||
|
|
- Add root cause analysis docs
|
||
|
|
```
|
||
|
|
|
||
|
|
## Common Pitfalls to Avoid
|
||
|
|
|
||
|
|
1. **Don't touch merge pipeline**: Standard loops (Pattern 1-4) still use it
|
||
|
|
2. **Don't generate PHI**: Normalized IR is PHI-free by design
|
||
|
|
3. **Don't forget strict mode**: Contract violations should freeze
|
||
|
|
4. **Don't skip regression tests**: 既定挙動不変 is critical
|
||
|
|
|
||
|
|
## Key Principles (SSOT)
|
||
|
|
|
||
|
|
1. **PHI禁止維持**: Normalized IR never generates PHI nodes
|
||
|
|
2. **既定挙動不変**: Standard patterns use existing merge pipeline
|
||
|
|
3. **責務分離**: ExitReconnectorBox handles Normalized-specific wiring
|
||
|
|
4. **箱化モジュール化**: New logic in dedicated box
|
||
|
|
5. **Fail-Fast**: Strict mode catches violations immediately
|
||
|
|
|
||
|
|
## Success Criteria
|
||
|
|
|
||
|
|
- [ ] loop(true) { x=1; break }; return x → returns 1 (not 0)
|
||
|
|
- [ ] VM and LLVM EXE both produce same result
|
||
|
|
- [ ] No regression in existing patterns
|
||
|
|
- [ ] Clean trace output (no PHI errors)
|
||
|
|
- [ ] Strict mode catches contract violations
|
||
|
|
|
||
|
|
## Reference
|
||
|
|
|
||
|
|
- Root Cause: `p1.5-root-cause-summary.md`
|
||
|
|
- Design Rationale: `p1.5-option-b-analysis.md`
|
||
|
|
- Phase 131 Overview: `README.md`
|