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:
nyash-codex
2025-11-17 05:24:07 +09:00
parent f85e485195
commit c459135238
6 changed files with 258 additions and 11 deletions

View File

@ -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 &current_vars {
let is_param = self.is_parameter(name);
eprintln!(" {} -> {:?} (param={})", name, value, is_param);
}
}
loopform.prepare_structure(self, &current_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)
}
}