302 lines
10 KiB
Markdown
302 lines
10 KiB
Markdown
|
|
# Phase 33-16: Q&A - Implementation Flow Details
|
||
|
|
|
||
|
|
## Your Questions Answered
|
||
|
|
|
||
|
|
### Q1: Where exactly should LoopHeaderPhiBuilder::build() be called?
|
||
|
|
|
||
|
|
**Answer**: Between Phase 3 (remap_values) and Phase 4 (instruction_rewriter) in `merge/mod.rs`
|
||
|
|
|
||
|
|
**Location**: Line 107, after `remap_values()`
|
||
|
|
|
||
|
|
**Why here**:
|
||
|
|
- ✅ **After** remap_values: We have remapped ValueIds (needed for phi_dst allocation)
|
||
|
|
- ✅ **Before** instruction_rewriter: We need to know PHI dsts so instruction_rewriter can use them in exit values
|
||
|
|
- ✅ Clear phase boundary: Phase 3.5
|
||
|
|
|
||
|
|
**Code location in file**:
|
||
|
|
```rust
|
||
|
|
// Line 107: After remap_values(...)
|
||
|
|
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||
|
|
|
||
|
|
// INSERT HERE: Phase 3.5 - Build loop header PHIs
|
||
|
|
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||
|
|
// ... build logic ...
|
||
|
|
} else {
|
||
|
|
loop_header_phi_builder::LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||
|
|
};
|
||
|
|
|
||
|
|
// Phase 4: Merge blocks and rewrite instructions
|
||
|
|
// PASS loop_header_phi_info to instruction_rewriter
|
||
|
|
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||
|
|
builder,
|
||
|
|
mir_module,
|
||
|
|
&mut remapper,
|
||
|
|
&value_to_func_name,
|
||
|
|
&function_params,
|
||
|
|
boundary,
|
||
|
|
&mut loop_header_phi_info, // NEW: Pass mutable reference
|
||
|
|
debug,
|
||
|
|
)?;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Q2: How do I get the header_block_id (loop_step's entry block after remapping)?
|
||
|
|
|
||
|
|
**Answer**: It's the entry function's entry block, obtained via remapper
|
||
|
|
|
||
|
|
**Exact code**:
|
||
|
|
```rust
|
||
|
|
let (entry_func_name, entry_func) = mir_module
|
||
|
|
.functions
|
||
|
|
.iter()
|
||
|
|
.next()
|
||
|
|
.ok_or("JoinIR module has no functions")?;
|
||
|
|
|
||
|
|
let entry_block_remapped = remapper
|
||
|
|
.get_block(entry_func_name, entry_func.entry_block)
|
||
|
|
.ok_or_else(|| format!("Entry block not found"))?;
|
||
|
|
|
||
|
|
// For Pattern 2, entry block == header block
|
||
|
|
let header_block_id = entry_block_remapped;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why this works**:
|
||
|
|
- JoinIR's first function is always the entry function
|
||
|
|
- `entry_func.entry_block` is the BasicBlockId in JoinIR space
|
||
|
|
- `remapper.get_block()` returns the remapped BasicBlockId in the host MIR
|
||
|
|
|
||
|
|
**For more complex patterns** (future):
|
||
|
|
- Pattern 3/4 might have different header block logic
|
||
|
|
- For now, assume entry_block == header_block (safe for Pattern 2)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Q3: How do I get the loop variable's initial value (host-side)?
|
||
|
|
|
||
|
|
**Answer**: Get it from the remapper (it's the remapped join_inputs[0])
|
||
|
|
|
||
|
|
**Exact code**:
|
||
|
|
```rust
|
||
|
|
// Loop variable's initial value is from join_inputs[0]
|
||
|
|
// It's been remapped by remap_values() in Phase 3
|
||
|
|
let loop_var_init = remapper
|
||
|
|
.get_value(ValueId(0)) // JoinIR param slot (always 0 for loop var)
|
||
|
|
.ok_or("Loop var init not remapped")?;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why ValueId(0)**:
|
||
|
|
- In JoinIR, loop parameter is always allocated as ValueId(0)
|
||
|
|
- Pattern 2 lowerer does this (pattern2_with_break.rs line 84):
|
||
|
|
```rust
|
||
|
|
env.insert(loop_var_name.clone(), crate::mir::ValueId(0));
|
||
|
|
```
|
||
|
|
- After remap_values(), this becomes a new ValueId in host space
|
||
|
|
|
||
|
|
**What you DON'T need**:
|
||
|
|
- ❌ Don't look in boundary.host_inputs[0] directly
|
||
|
|
- ❌ Don't use boundary.join_inputs[0] (it's the pre-remap value)
|
||
|
|
- ✅ Use remapper.get_value(ValueId(0)) (it's the post-remap value)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Q4: Where should instruction_rewriter record latch_incoming?
|
||
|
|
|
||
|
|
**Answer**: In the tail call handling section, after parameter bindings
|
||
|
|
|
||
|
|
**Location**: `instruction_rewriter.rs`, in the tail call branch (lines 276-335)
|
||
|
|
|
||
|
|
**Exact code**:
|
||
|
|
```rust
|
||
|
|
if let Some((target_block, args)) = tail_call_target {
|
||
|
|
// ... existing parameter binding code (lines 276-319) ...
|
||
|
|
|
||
|
|
// NEW: Track latch incoming AFTER param bindings
|
||
|
|
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||
|
|
if !args.is_empty() {
|
||
|
|
let latch_value = args[0]; // Updated loop variable from tail call args
|
||
|
|
loop_header_phi_info.set_latch_incoming(
|
||
|
|
loop_var_name,
|
||
|
|
target_block, // This is the loop header block (from tail call target)
|
||
|
|
latch_value, // This is i_next (the updated value)
|
||
|
|
);
|
||
|
|
|
||
|
|
if debug {
|
||
|
|
eprintln!("[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': {:?}",
|
||
|
|
loop_var_name, latch_value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ... then set terminator to Jump (line 321-323) ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why this location**:
|
||
|
|
- Tail call args are the ACTUAL updated values (i_next, not i_param)
|
||
|
|
- args[0] is guaranteed to be the loop variable (Pattern 2 guarantees)
|
||
|
|
- target_block is the loop header (where we're jumping back to)
|
||
|
|
- Called for EACH block that has a tail call (ensures all paths tracked)
|
||
|
|
|
||
|
|
**Key insight**: The latch block is NOT explicitly identified; we identify it by the Jump target!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Q5: Should the Phase 33-15 skip logic be removed or modified to use header PHI dst?
|
||
|
|
|
||
|
|
**Answer**: Modify, NOT remove. Use header PHI dst when available, fallback to parameter.
|
||
|
|
|
||
|
|
**What to do**:
|
||
|
|
|
||
|
|
1. **Replace skip logic** (lines 354-398 in instruction_rewriter.rs):
|
||
|
|
```rust
|
||
|
|
// OLD: Skip exit_phi_inputs collection
|
||
|
|
// if debug { eprintln!(...skip...); }
|
||
|
|
|
||
|
|
// NEW: Use header PHI dst if available
|
||
|
|
if let Some(ret_val) = value {
|
||
|
|
let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val);
|
||
|
|
|
||
|
|
// Phase 33-16: Prefer header PHI dst
|
||
|
|
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||
|
|
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) {
|
||
|
|
// Use PHI dst (SSA-correct!)
|
||
|
|
exit_phi_inputs.push((exit_block_id, phi_dst));
|
||
|
|
} else {
|
||
|
|
// Fallback: Use parameter (for backward compatibility)
|
||
|
|
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// No boundary or loop_var_name: use parameter
|
||
|
|
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Modify carrier_inputs logic** (lines 400-431):
|
||
|
|
```rust
|
||
|
|
// OLD: Skip carrier_inputs collection
|
||
|
|
// if debug { eprintln!(...skip...); }
|
||
|
|
|
||
|
|
// NEW: Use header PHI dsts for carriers
|
||
|
|
if let Some(boundary) = boundary {
|
||
|
|
for binding in &boundary.exit_bindings {
|
||
|
|
// Phase 33-16: Look up header PHI dst for this carrier
|
||
|
|
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
|
||
|
|
carrier_inputs.entry(binding.carrier_name.clone())
|
||
|
|
.or_insert_with(Vec::new)
|
||
|
|
.push((exit_block_id, phi_dst));
|
||
|
|
}
|
||
|
|
// If no PHI dst, skip this carrier (not yet implemented)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why this approach**:
|
||
|
|
- ✅ Phase 33-16 adds header PHIs → use them (SSA-correct)
|
||
|
|
- ✅ If no header PHIs → fallback to old behavior (backward compat)
|
||
|
|
- ✅ Gradual migration: Patterns enable loop_var_name progressively
|
||
|
|
- ✅ Easy to debug: Explicit "Using PHI" vs "Fallback" logs
|
||
|
|
|
||
|
|
**Don't do**:
|
||
|
|
- ❌ Don't remove skip logic entirely (patterns without loop_var_name would break)
|
||
|
|
- ❌ Don't add loop_header_phi_info to merge_and_rewrite() signature if you don't track latch
|
||
|
|
- ✅ Do add both build() and finalize() to merge/mod.rs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Q6: Flow Summary - How does it all fit together?
|
||
|
|
|
||
|
|
**Complete flow**:
|
||
|
|
|
||
|
|
```
|
||
|
|
merge_joinir_mir_blocks() {
|
||
|
|
// Phase 1: Allocate block IDs
|
||
|
|
allocate_blocks()
|
||
|
|
|
||
|
|
// Phase 2: Collect values
|
||
|
|
collect_values()
|
||
|
|
|
||
|
|
// Phase 3: Remap ValueIds
|
||
|
|
remap_values(builder, &used_values, &mut remapper)
|
||
|
|
|
||
|
|
// ===== Phase 3.5 (NEW) =====
|
||
|
|
// Build loop header PHIs with entry incoming edges
|
||
|
|
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||
|
|
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||
|
|
// Get header_block_id (entry block after remap)
|
||
|
|
let entry_block = remapper.get_block(entry_func, entry_func.entry_block)?;
|
||
|
|
|
||
|
|
// Get loop_var_init (remapped ValueId(0))
|
||
|
|
let loop_var_init = remapper.get_value(ValueId(0))?;
|
||
|
|
|
||
|
|
// Build header PHIs (allocates PHI dsts, doesn't emit yet)
|
||
|
|
LoopHeaderPhiBuilder::build(
|
||
|
|
builder,
|
||
|
|
entry_block, // header_block_id
|
||
|
|
entry_block, // entry_block_id
|
||
|
|
loop_var_name,
|
||
|
|
loop_var_init,
|
||
|
|
&[], // No other carriers yet
|
||
|
|
boundary.expr_result.is_some(),
|
||
|
|
debug,
|
||
|
|
)?
|
||
|
|
} else {
|
||
|
|
LoopHeaderPhiInfo::empty(...)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
LoopHeaderPhiInfo::empty(...)
|
||
|
|
};
|
||
|
|
|
||
|
|
// ===== Phase 4 (MODIFIED) =====
|
||
|
|
// Instruction rewriter sets latch incoming and uses PHI dsts
|
||
|
|
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||
|
|
builder,
|
||
|
|
mir_module,
|
||
|
|
&mut remapper,
|
||
|
|
...,
|
||
|
|
&mut loop_header_phi_info, // PASS MUTABLE REFERENCE
|
||
|
|
debug,
|
||
|
|
)?;
|
||
|
|
// Inside merge_and_rewrite:
|
||
|
|
// - When processing tail calls: record latch_incoming
|
||
|
|
// - When processing Return: use header PHI dsts (not parameters)
|
||
|
|
|
||
|
|
// ===== Phase 5 =====
|
||
|
|
// Build exit PHI from exit_phi_inputs and carrier_inputs
|
||
|
|
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(...)?;
|
||
|
|
|
||
|
|
// ===== Phase 4.5 (NEW) =====
|
||
|
|
// Finalize loop header PHIs (insert into blocks)
|
||
|
|
LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?;
|
||
|
|
|
||
|
|
// ===== Phase 6 =====
|
||
|
|
// Reconnect exit values using carrier_phis from Phase 5
|
||
|
|
if let Some(boundary) = boundary {
|
||
|
|
ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ... continue with boundary jump and exit block switch ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Key transitions**:
|
||
|
|
1. Phase 3 → Phase 3.5: remap_values() gives us remapped ValueIds
|
||
|
|
2. Phase 3.5 → Phase 4: loop_header_phi_info allocated (PHI dsts ready)
|
||
|
|
3. Phase 4 → Phase 4.5: instruction_rewriter sets latch_incoming
|
||
|
|
4. Phase 4.5 → Phase 5: Finalize emits PHIs into blocks
|
||
|
|
5. Phase 5 → Phase 6: exit_phi_builder returns carrier_phis (PHI dsts)
|
||
|
|
6. Phase 6: ExitLineOrchestrator uses carrier_phis to update variable_map
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary: Exact Answer to Your Core Question
|
||
|
|
|
||
|
|
**Where to call build()**: Line 107 in merge/mod.rs, after remap_values()
|
||
|
|
**How to get header_block_id**: `remapper.get_block(entry_func_name, entry_func.entry_block)?`
|
||
|
|
**How to get loop_var_init**: `remapper.get_value(ValueId(0))?`
|
||
|
|
**Where to record latch_incoming**: In tail call handling (line ~300), use args[0] as latch_value
|
||
|
|
**Replace skip logic**: Yes, with fallback mechanism for backward compatibility
|
||
|
|
|
||
|
|
**The magic**: Loop header PHI dst (allocated in Phase 3.5, finalized in Phase 4.5) is SSA-defined and can be safely used in exit values instead of parameters!
|