feat(mir/phi): improve LoopForm parameter detection - track param names
**Problem**: is_parameter() was too simple, checking only ValueId which changes through copies/PHIs. This caused parameters like 'data' to be misclassified as carriers, leading to incorrect PHI construction. **Solution**: Track original parameter names at function entry. **Changes**: 1. **Added function_param_names field** (builder.rs): - HashSet<String> to track original parameter names - Populated in lower_static_method_as_function() - Cleared and repopulated for each new function 2. **Improved is_parameter()** (loop_builder.rs): - Check name against function_param_names instead of ValueId - More reliable than checking func.params (ValueIds change) - __pin$*$@* variables correctly classified as carriers - Added debug logging with NYASH_LOOPFORM_DEBUG 3. **Enhanced debug output** (loopform_builder.rs): - Show carrier/pinned classification during prepare_structure() - Show variable_map state after emit_header_phis() **Test Results**: - ✅ 'args' correctly identified as parameter (was working) - ✅ 'data' now correctly identified as parameter (was broken) - ✅ __pin variables correctly classified as carriers - ✅ PHI values allocated and variable_map updated correctly - ⚠️ ValueId undefined errors persist (separate issue) **Remaining Issue**: ValueId(10) undefined error suggests PHI visibility problem or VM verification issue. Needs further investigation of emit_phi_at_block_start() or VM executor. **Backward Compatibility**: - Flag OFF: 100% existing behavior preserved (legacy path unchanged) - Feature-flagged with NYASH_LOOPFORM_PHI_V2=1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -7,6 +7,7 @@
|
||||
|
||||
use super::{BasicBlockId, ConstValue, MirInstruction, ValueId};
|
||||
use crate::mir::phi_core::loop_phi::IncompletePhi;
|
||||
use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps};
|
||||
use crate::ast::ASTNode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -112,11 +113,137 @@ impl<'a> LoopBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// SSA形式でループを構築
|
||||
/// SSA形式でループを構築 (Feature flag dispatch)
|
||||
pub fn build_loop(
|
||||
&mut self,
|
||||
condition: ASTNode,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Check feature flag for LoopForm PHI v2
|
||||
let use_loopform_v2 = std::env::var("NYASH_LOOPFORM_PHI_V2")
|
||||
.map(|v| v == "1" || v.to_lowercase() == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
if use_loopform_v2 {
|
||||
self.build_loop_with_loopform(condition, body)
|
||||
} else {
|
||||
self.build_loop_legacy(condition, body)
|
||||
}
|
||||
}
|
||||
|
||||
/// SSA形式でループを構築 (LoopFormBuilder implementation)
|
||||
fn build_loop_with_loopform(
|
||||
&mut self,
|
||||
condition: ASTNode,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Create loop structure blocks
|
||||
let preheader_id = self.current_block()?;
|
||||
let header_id = self.new_block();
|
||||
let body_id = self.new_block();
|
||||
let latch_id = self.new_block();
|
||||
let exit_id = self.new_block();
|
||||
|
||||
// Initialize LoopFormBuilder with preheader and header blocks
|
||||
let mut loopform = LoopFormBuilder::new(preheader_id, header_id);
|
||||
|
||||
// Capture current variable map snapshot at preheader
|
||||
let current_vars = self.get_current_variable_map();
|
||||
|
||||
// Pass 1: Prepare structure (allocate all ValueIds upfront)
|
||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
||||
eprintln!("[loopform] variable_map at loop entry:");
|
||||
for (name, value) in ¤t_vars {
|
||||
let is_param = self.is_parameter(name);
|
||||
eprintln!(" {} -> {:?} (param={})", name, value, is_param);
|
||||
}
|
||||
}
|
||||
loopform.prepare_structure(self, ¤t_vars)?;
|
||||
|
||||
// Pass 2: Emit preheader (copies and jump to header)
|
||||
loopform.emit_preheader(self)?;
|
||||
|
||||
// Pass 3: Emit header PHIs (incomplete, only preheader edge)
|
||||
self.set_current_block(header_id)?;
|
||||
|
||||
// Ensure header block exists before emitting PHIs
|
||||
self.parent_builder.ensure_block_exists(header_id)?;
|
||||
|
||||
loopform.emit_header_phis(self)?;
|
||||
|
||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
||||
eprintln!("[loopform] variable_map after emit_header_phis:");
|
||||
for (name, value) in self.get_current_variable_map().iter() {
|
||||
eprintln!(" {} -> {:?}", name, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up loop context for break/continue
|
||||
crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id);
|
||||
self.loop_header = Some(header_id);
|
||||
self.continue_snapshots.clear();
|
||||
self.exit_snapshots.clear();
|
||||
|
||||
// Emit condition check in header
|
||||
let cond_value = self.parent_builder.build_expression(condition)?;
|
||||
self.emit_branch(cond_value, body_id, exit_id)?;
|
||||
|
||||
// Lower loop body
|
||||
self.set_current_block(body_id)?;
|
||||
for stmt in body {
|
||||
self.build_statement(stmt)?;
|
||||
if is_current_block_terminated(self.parent_builder)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture variable snapshot at end of body (before jumping to latch)
|
||||
let body_end_vars = self.get_current_variable_map();
|
||||
|
||||
// Jump to latch if not already terminated
|
||||
let actual_latch_id = if !is_current_block_terminated(self.parent_builder)? {
|
||||
let cur_body_end = self.current_block()?;
|
||||
self.emit_jump(latch_id)?;
|
||||
latch_id
|
||||
} else {
|
||||
// Body is terminated (break/continue), use current block as latch
|
||||
self.current_block()?
|
||||
};
|
||||
|
||||
// Latch: jump back to header
|
||||
self.set_current_block(latch_id)?;
|
||||
|
||||
// Update variable map with body end values for sealing
|
||||
for (name, value) in body_end_vars {
|
||||
self.update_variable(name, value);
|
||||
}
|
||||
|
||||
self.emit_jump(header_id)?;
|
||||
|
||||
// Pass 4: Seal PHIs with latch values
|
||||
loopform.seal_phis(self, actual_latch_id)?;
|
||||
|
||||
// Exit block
|
||||
self.set_current_block(exit_id)?;
|
||||
|
||||
// Build exit PHIs for break statements
|
||||
let exit_snaps = self.exit_snapshots.clone();
|
||||
loopform.build_exit_phis(self, exit_id, &exit_snaps)?;
|
||||
|
||||
// Pop loop context
|
||||
crate::mir::builder::loops::pop_loop_context(self.parent_builder);
|
||||
|
||||
// Return void value
|
||||
let void_dst = self.new_value();
|
||||
self.emit_const(void_dst, ConstValue::Void)?;
|
||||
Ok(void_dst)
|
||||
}
|
||||
|
||||
/// SSA形式でループを構築 (Legacy implementation)
|
||||
fn build_loop_legacy(
|
||||
&mut self,
|
||||
condition: ASTNode,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Reserve a deterministic loop id for debug region labeling
|
||||
let loop_id = self.parent_builder.debug_next_loop_id();
|
||||
@ -925,3 +1052,79 @@ impl crate::mir::phi_core::loop_phi::LoopPhiOps for LoopBuilder<'_> {
|
||||
self.add_predecessor(block, pred)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration
|
||||
impl<'a> LoopFormOps for LoopBuilder<'a> {
|
||||
fn new_value(&mut self) -> ValueId {
|
||||
self.parent_builder.value_gen.next()
|
||||
}
|
||||
|
||||
fn is_parameter(&self, name: &str) -> bool {
|
||||
// A parameter is a true function parameter that doesn't change across iterations
|
||||
// Pinned receivers (__pin$*$@*) are NOT parameters - they're carriers
|
||||
// because they can be reassigned in the loop body
|
||||
|
||||
// Pinned variables are always carriers (loop-variant)
|
||||
if name.starts_with("__pin$") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's the receiver
|
||||
if name == "me" {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's in the original function parameter names
|
||||
// This is more reliable than checking ValueIds, which can change through copies/PHIs
|
||||
let is_param = self.parent_builder.function_param_names.contains(name);
|
||||
|
||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
||||
eprintln!("[is_parameter] {} -> {} (param_names = {:?})",
|
||||
name, is_param, self.parent_builder.function_param_names);
|
||||
}
|
||||
|
||||
is_param
|
||||
}
|
||||
|
||||
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
|
||||
self.parent_builder.start_new_block(block)
|
||||
}
|
||||
|
||||
fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> {
|
||||
self.parent_builder.emit_instruction(MirInstruction::Copy { dst, src })
|
||||
}
|
||||
|
||||
fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> {
|
||||
self.emit_jump(target)
|
||||
}
|
||||
|
||||
fn emit_phi(
|
||||
&mut self,
|
||||
dst: ValueId,
|
||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String> {
|
||||
self.emit_phi_at_block_start(
|
||||
self.current_block()?,
|
||||
dst,
|
||||
inputs
|
||||
)
|
||||
}
|
||||
|
||||
fn update_phi_inputs(
|
||||
&mut self,
|
||||
block: BasicBlockId,
|
||||
phi_id: ValueId,
|
||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String> {
|
||||
self.parent_builder.update_phi_instruction(block, phi_id, inputs)
|
||||
}
|
||||
|
||||
fn update_var(&mut self, name: String, value: ValueId) {
|
||||
self.parent_builder.variable_map.insert(name, value);
|
||||
}
|
||||
|
||||
fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option<ValueId> {
|
||||
// Use the inherent method to avoid recursion
|
||||
LoopBuilder::get_variable_at_block(self, name, block)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user