fix(joinir): stabilize Phase 256 merge (jump_args, DCE, func names)
This commit is contained in:
@ -188,20 +188,15 @@ impl ExitArgsCollectorBox {
|
||||
Ok(0) // Best effort: try direct mapping
|
||||
}
|
||||
} else {
|
||||
// Too long - unexpected extra args
|
||||
let msg = format!(
|
||||
"[joinir/exit-line] jump_args length mismatch: expected {} or {} (exit_bindings carriers ±1) but got {} in block {:?}",
|
||||
exit_phi_bindings_len,
|
||||
exit_phi_bindings_len + 1,
|
||||
jump_args_len,
|
||||
block_id
|
||||
// Too long - extra args beyond carriers (e.g., invariants in Pattern 7)
|
||||
// Phase 256 P1.8: Allow excess args as long as we have enough for carriers
|
||||
// Direct mapping: jump_args[0..N] = exit_phi_bindings[0..N], rest ignored
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!(
|
||||
"[joinir/exit-line] jump_args has {} extra args (block {:?}), ignoring invariants",
|
||||
jump_args_len - exit_phi_bindings_len, block_id
|
||||
);
|
||||
if strict_exit {
|
||||
Err(msg)
|
||||
} else {
|
||||
eprintln!("[DEBUG-177] {}", msg);
|
||||
Ok(0) // Best effort: try direct mapping
|
||||
}
|
||||
Ok(0) // Direct mapping: first N args are carriers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1162,15 +1162,44 @@ pub(super) fn merge_and_rewrite(
|
||||
// Choosing `.iter().next()` can therefore pick the wrong function and skip
|
||||
// host→JoinIR Copy injection. Instead, pick the function whose params match
|
||||
// the boundary.join_inputs (the entry env params).
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.find(|(_, func)| func.params == boundary.join_inputs)
|
||||
.or_else(|| mir_module.functions.iter().next())
|
||||
.ok_or("JoinIR module has no functions")?;
|
||||
//
|
||||
// Phase 256 P1.10.1: Prefer "main" if its params match the boundary join_inputs.
|
||||
let (entry_func_name, entry_func) = {
|
||||
use crate::mir::join_ir::lowering::canonical_names as cn;
|
||||
if let Some(main) = mir_module.functions.get(cn::MAIN) {
|
||||
if main.params == boundary.join_inputs {
|
||||
(cn::MAIN, main)
|
||||
} else {
|
||||
mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.find(|(_, func)| func.params == boundary.join_inputs)
|
||||
.or_else(|| mir_module.functions.iter().next())
|
||||
.map(|(name, func)| (name.as_str(), func))
|
||||
.ok_or("JoinIR module has no functions")?
|
||||
}
|
||||
} else {
|
||||
mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.find(|(_, func)| func.params == boundary.join_inputs)
|
||||
.or_else(|| mir_module.functions.iter().next())
|
||||
.map(|(name, func)| (name.as_str(), func))
|
||||
.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 {}", entry_func_name))?;
|
||||
log!(
|
||||
true,
|
||||
"[cf_loop/joinir] Phase 256 P1.10.1: Boundary entry selection: func='{}' entry_block={:?} remapped={:?} join_inputs={:?} entry_params={:?}",
|
||||
entry_func_name,
|
||||
entry_func.entry_block,
|
||||
entry_block_remapped,
|
||||
boundary.join_inputs,
|
||||
entry_func.params
|
||||
);
|
||||
|
||||
// Create BTreeMap from remapper for BoundaryInjector (temporary adapter)
|
||||
// Phase 222.5-E: HashMap → BTreeMap for determinism
|
||||
|
||||
@ -6,9 +6,10 @@
|
||||
//! - ブロックID マッピング管理
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir::{JoinInst, MirLikeInst};
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinInst, MirLikeInst};
|
||||
use crate::mir::{BasicBlockId, EffectMask, MirFunction, MirInstruction, MirType, ValueId};
|
||||
use crate::mir::types::ConstValue;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{convert_mir_like_inst, join_func_name, JoinIrVmBridgeError};
|
||||
|
||||
@ -22,6 +23,9 @@ pub struct JoinIrBlockConverter {
|
||||
current_block_id: BasicBlockId,
|
||||
current_instructions: Vec<MirInstruction>,
|
||||
next_block_id: u32,
|
||||
/// Phase 256 P1.8: Map from JoinFuncId to actual function name
|
||||
/// When set, handle_call uses this instead of join_func_name()
|
||||
func_name_map: Option<BTreeMap<JoinFuncId, String>>,
|
||||
}
|
||||
|
||||
impl JoinIrBlockConverter {
|
||||
@ -30,6 +34,18 @@ impl JoinIrBlockConverter {
|
||||
current_block_id: BasicBlockId(0), // entry block
|
||||
current_instructions: Vec::new(),
|
||||
next_block_id: 1, // start from 1 (0 is entry)
|
||||
func_name_map: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 256 P1.8: Create converter with function name map
|
||||
/// This ensures Call instructions use actual function names instead of "join_func_N"
|
||||
pub fn new_with_func_names(func_name_map: BTreeMap<JoinFuncId, String>) -> Self {
|
||||
Self {
|
||||
current_block_id: BasicBlockId(0),
|
||||
current_instructions: Vec::new(),
|
||||
next_block_id: 1,
|
||||
func_name_map: Some(func_name_map),
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +365,12 @@ impl JoinIrBlockConverter {
|
||||
));
|
||||
}
|
||||
|
||||
let func_name = join_func_name(*func);
|
||||
// Phase 256 P1.8: Use actual function name if available
|
||||
let func_name = if let Some(ref map) = self.func_name_map {
|
||||
map.get(func).cloned().unwrap_or_else(|| join_func_name(*func))
|
||||
} else {
|
||||
join_func_name(*func)
|
||||
};
|
||||
|
||||
// Phase 131 P2: Stable function name ValueId (module-global SSOT)
|
||||
//
|
||||
@ -420,21 +441,31 @@ impl JoinIrBlockConverter {
|
||||
fn handle_jump(
|
||||
&mut self,
|
||||
mir_func: &mut MirFunction,
|
||||
_cont: &crate::mir::join_ir::JoinContId,
|
||||
cont: &crate::mir::join_ir::JoinContId,
|
||||
args: &[ValueId],
|
||||
cond: &Option<ValueId>,
|
||||
) -> Result<(), JoinIrVmBridgeError> {
|
||||
// Phase 246-EX: Preserve ALL Jump args as metadata for exit PHI construction
|
||||
// Phase 27-shortterm S-4.4-A: Jump → Branch/Return
|
||||
// Phase 256 P1.9: Jump → tail call to continuation function
|
||||
// Previously was just `ret args[0]`, now generates `call cont(args...); ret result`
|
||||
debug_log!(
|
||||
"[joinir_block] Converting Jump args={:?}, cond={:?}",
|
||||
"[joinir_block] Converting Jump to tail call: cont={:?}, args={:?}, cond={:?}",
|
||||
cont,
|
||||
args,
|
||||
cond
|
||||
);
|
||||
|
||||
// Get continuation function name
|
||||
let cont_name = self.get_continuation_name(cont);
|
||||
|
||||
// Phase 256 P1.9: Use distinct ValueIds for Jump tail call
|
||||
// FUNC_NAME_ID_BASE for call targets, 99992 for Jump result (distinct from 99991 in handle_call)
|
||||
const JUMP_FUNC_NAME_ID_BASE: u32 = 91000; // Different from handle_call's 90000
|
||||
let func_name_id = ValueId(JUMP_FUNC_NAME_ID_BASE + cont.0);
|
||||
let call_result_id = ValueId(99992); // Distinct from handle_call's 99991
|
||||
|
||||
match cond {
|
||||
Some(cond_var) => {
|
||||
// Conditional jump
|
||||
// Conditional jump → Branch + tail call to continuation
|
||||
let exit_block_id = BasicBlockId(self.next_block_id);
|
||||
self.next_block_id += 1;
|
||||
let continue_block_id = BasicBlockId(self.next_block_id);
|
||||
@ -453,16 +484,27 @@ impl JoinIrBlockConverter {
|
||||
branch_terminator,
|
||||
);
|
||||
|
||||
// Phase 246-EX: Store all Jump args in exit block metadata
|
||||
// Exit block: Create with all Jump args stored as metadata
|
||||
let exit_value = args.first().copied();
|
||||
// Exit block: tail call to continuation function
|
||||
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
|
||||
|
||||
// Phase 246-EX: Store Jump args in a new metadata field
|
||||
// This preserves carrier values for exit PHI construction
|
||||
// Phase 246-EX: Store Jump args in metadata for exit PHI construction
|
||||
exit_block.jump_args = Some(args.to_vec());
|
||||
|
||||
exit_block.terminator = Some(MirInstruction::Return { value: exit_value });
|
||||
// Phase 256 P1.9: Generate tail call to continuation
|
||||
exit_block.instructions.push(MirInstruction::Const {
|
||||
dst: func_name_id,
|
||||
value: crate::mir::ConstValue::String(cont_name.clone()),
|
||||
});
|
||||
exit_block.instruction_spans.push(Span::unknown());
|
||||
exit_block.instructions.push(MirInstruction::Call {
|
||||
dst: Some(call_result_id),
|
||||
func: func_name_id,
|
||||
callee: None,
|
||||
args: args.to_vec(),
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
exit_block.instruction_spans.push(Span::unknown());
|
||||
exit_block.terminator = Some(MirInstruction::Return { value: Some(call_result_id) });
|
||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||
|
||||
// Continue block
|
||||
@ -472,9 +514,28 @@ impl JoinIrBlockConverter {
|
||||
self.current_block_id = continue_block_id;
|
||||
}
|
||||
None => {
|
||||
// Unconditional jump
|
||||
let exit_value = args.first().copied();
|
||||
let return_terminator = MirInstruction::Return { value: exit_value };
|
||||
// Unconditional jump → tail call to continuation
|
||||
// Finalize current block with tail call
|
||||
self.current_instructions.push(MirInstruction::Const {
|
||||
dst: func_name_id,
|
||||
value: crate::mir::ConstValue::String(cont_name),
|
||||
});
|
||||
self.current_instructions.push(MirInstruction::Call {
|
||||
dst: Some(call_result_id),
|
||||
func: func_name_id,
|
||||
callee: None,
|
||||
args: args.to_vec(),
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
|
||||
// Preserve jump args as metadata (SSOT for ExitLine/jump_args wiring).
|
||||
if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
|
||||
if block.jump_args.is_none() {
|
||||
block.jump_args = Some(args.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let return_terminator = MirInstruction::Return { value: Some(call_result_id) };
|
||||
|
||||
Self::finalize_block(
|
||||
mir_func,
|
||||
@ -487,6 +548,18 @@ impl JoinIrBlockConverter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Phase 256 P1.9: Get continuation function name from func_name_map
|
||||
fn get_continuation_name(&self, cont: &crate::mir::join_ir::JoinContId) -> String {
|
||||
// JoinContId.0 == JoinFuncId.0 (same underlying ID via as_cont())
|
||||
if let Some(ref map) = self.func_name_map {
|
||||
if let Some(name) = map.get(&JoinFuncId(cont.0)) {
|
||||
return name.clone();
|
||||
}
|
||||
}
|
||||
// Fallback: use join_func_name()
|
||||
join_func_name(JoinFuncId(cont.0))
|
||||
}
|
||||
|
||||
fn handle_select(
|
||||
&mut self,
|
||||
mir_func: &mut MirFunction,
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
//! - ブロック変換の統合
|
||||
//! - 関数署名の管理
|
||||
|
||||
use crate::mir::join_ir::{JoinFunction, JoinModule};
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule};
|
||||
use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirModule, MirType};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::join_func_name;
|
||||
use super::joinir_block_converter::JoinIrBlockConverter;
|
||||
@ -116,6 +117,53 @@ impl JoinIrFunctionConverter {
|
||||
|
||||
Ok(mir_func)
|
||||
}
|
||||
|
||||
/// Phase 256 P1.8: Convert function with actual function name map
|
||||
///
|
||||
/// This variant ensures Call instructions use actual function names ("main", "loop_step", "k_exit")
|
||||
/// instead of generated names ("join_func_0", "join_func_1", etc.)
|
||||
pub(crate) fn convert_function_with_func_names(
|
||||
join_func: &JoinFunction,
|
||||
func_name_map: BTreeMap<JoinFuncId, String>,
|
||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||
let entry_block = BasicBlockId(0);
|
||||
|
||||
let param_types = join_func
|
||||
.params
|
||||
.iter()
|
||||
.map(|_| MirType::Unknown)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let signature = FunctionSignature {
|
||||
name: join_func.name.clone(),
|
||||
params: param_types,
|
||||
return_type: MirType::Unknown,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||
mir_func.params = join_func.params.clone();
|
||||
|
||||
// Phase 256 P1.8: Use BlockConverter with function name map
|
||||
let mut block_converter = JoinIrBlockConverter::new_with_func_names(func_name_map);
|
||||
block_converter.convert_function_body(&mut mir_func, &join_func.body)?;
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Function '{}' has {} blocks:",
|
||||
mir_func.signature.name,
|
||||
mir_func.blocks.len()
|
||||
);
|
||||
for (block_id, block) in &mir_func.blocks {
|
||||
debug_log!(
|
||||
" Block {:?}: {} instructions, terminator={:?}",
|
||||
block_id,
|
||||
block.instructions.len(),
|
||||
block.terminator
|
||||
);
|
||||
}
|
||||
|
||||
Ok(mir_func)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// Phase 190: Use modularized converter
|
||||
use super::{JoinIrFunctionConverter, JoinIrVmBridgeError};
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinModule};
|
||||
use crate::mir::{MirFunction, MirModule};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Phase 40-1実験用: JoinFuncMetaを使ったMIR変換
|
||||
///
|
||||
@ -29,6 +30,15 @@ pub fn convert_join_module_to_mir_with_meta(
|
||||
|
||||
let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string());
|
||||
|
||||
// Phase 256 P1.8: Build function name map for all functions in the module
|
||||
// This ensures Call instructions use actual names ("main", "loop_step", "k_exit")
|
||||
// instead of generated names ("join_func_0", "join_func_1", etc.)
|
||||
let func_name_map: BTreeMap<JoinFuncId, String> = module
|
||||
.functions
|
||||
.iter()
|
||||
.map(|(id, func)| (*id, func.name.clone()))
|
||||
.collect();
|
||||
|
||||
// 1. 各関数を変換
|
||||
for (func_id, join_func) in &module.functions {
|
||||
debug_log!(
|
||||
@ -37,8 +47,11 @@ pub fn convert_join_module_to_mir_with_meta(
|
||||
join_func.name
|
||||
);
|
||||
|
||||
// 2. 基本のMIR変換(Phase 190: modularized converter)
|
||||
let mir_func = JoinIrFunctionConverter::convert_function(join_func)?;
|
||||
// 2. 基本のMIR変換(Phase 256 P1.8: with func_name_map)
|
||||
let mir_func = JoinIrFunctionConverter::convert_function_with_func_names(
|
||||
join_func,
|
||||
func_name_map.clone(),
|
||||
)?;
|
||||
|
||||
// Phase 189 DEBUG: Dump MirFunction blocks to check PHI presence
|
||||
// Guarded to avoid polluting stdout/stderr in normal runs.
|
||||
|
||||
@ -3,8 +3,9 @@
|
||||
//! Separated from canonicalizer.rs for better maintainability.
|
||||
|
||||
use super::canonicalizer::canonicalize_loop_expr;
|
||||
use super::skeleton_types::{CarrierRole, LoopPatternKind, SkeletonStep, UpdateKind};
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
use crate::mir::loop_pattern_detection::LoopPatternKind;
|
||||
use super::skeleton_types::{CarrierRole, SkeletonStep, UpdateKind};
|
||||
|
||||
#[test]
|
||||
fn test_canonicalize_rejects_non_loop() {
|
||||
|
||||
@ -522,4 +522,80 @@ mod tests {
|
||||
"TypeOp should not be dropped by DCE when used by console.log (ExternCall)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dce_keeps_jump_args_values() {
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: super::super::effect::EffectMask::PURE,
|
||||
};
|
||||
let mut func = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
let bb0 = BasicBlockId::new(0);
|
||||
let mut b0 = BasicBlock::new(bb0);
|
||||
let v0 = ValueId::new(0);
|
||||
let v1 = ValueId::new(1);
|
||||
b0.add_instruction(MirInstruction::Const {
|
||||
dst: v0,
|
||||
value: ConstValue::Integer(1),
|
||||
});
|
||||
b0.add_instruction(MirInstruction::Copy { dst: v1, src: v0 });
|
||||
b0.add_instruction(MirInstruction::Return { value: None });
|
||||
b0.jump_args = Some(vec![v1]);
|
||||
func.add_block(b0);
|
||||
let mut module = MirModule::new("test".to_string());
|
||||
module.add_function(func);
|
||||
|
||||
crate::mir::passes::dce::eliminate_dead_code(&mut module);
|
||||
|
||||
let f = module.get_function("main").unwrap();
|
||||
let block = f.get_block(bb0).unwrap();
|
||||
let has_copy = block
|
||||
.instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, MirInstruction::Copy { .. }));
|
||||
assert!(has_copy, "Copy used only by jump_args should not be eliminated");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dce_syncs_instruction_spans() {
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Integer,
|
||||
effects: super::super::effect::EffectMask::PURE,
|
||||
};
|
||||
let mut func = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
let bb0 = BasicBlockId::new(0);
|
||||
let mut b0 = BasicBlock::new(bb0);
|
||||
let v0 = ValueId::new(0);
|
||||
let v1 = ValueId::new(1);
|
||||
b0.add_instruction(MirInstruction::Const {
|
||||
dst: v0,
|
||||
value: ConstValue::Integer(1),
|
||||
});
|
||||
b0.add_instruction(MirInstruction::Const {
|
||||
dst: v1,
|
||||
value: ConstValue::Integer(2),
|
||||
});
|
||||
b0.add_instruction(MirInstruction::Return { value: Some(v0) });
|
||||
func.add_block(b0);
|
||||
let mut module = MirModule::new("test".to_string());
|
||||
module.add_function(func);
|
||||
|
||||
crate::mir::passes::dce::eliminate_dead_code(&mut module);
|
||||
|
||||
let f = module.get_function("main").unwrap();
|
||||
let block = f.get_block(bb0).unwrap();
|
||||
assert_eq!(
|
||||
block.instructions.len(),
|
||||
block.instruction_spans.len(),
|
||||
"Instruction spans must stay aligned after DCE"
|
||||
);
|
||||
let has_unused_const = block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Const { dst, .. } if *dst == v1)
|
||||
});
|
||||
assert!(!has_unused_const, "Unused const should be eliminated by DCE");
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,11 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize {
|
||||
used_values.insert(u);
|
||||
}
|
||||
}
|
||||
if let Some(args) = &block.jump_args {
|
||||
for &u in args {
|
||||
used_values.insert(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward propagation: if a value is used, mark its operands as used
|
||||
@ -61,12 +66,15 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize {
|
||||
let mut eliminated = 0usize;
|
||||
let dce_trace = std::env::var("NYASH_DCE_TRACE").ok().as_deref() == Some("1");
|
||||
for (bbid, block) in &mut function.blocks {
|
||||
block.instructions.retain(|inst| {
|
||||
let insts = std::mem::take(&mut block.instructions);
|
||||
let spans = std::mem::take(&mut block.instruction_spans);
|
||||
let mut kept_insts = Vec::with_capacity(insts.len());
|
||||
let mut kept_spans = Vec::with_capacity(spans.len());
|
||||
for (inst, span) in insts.into_iter().zip(spans.into_iter()) {
|
||||
let mut keep = true;
|
||||
if inst.effects().is_pure() {
|
||||
if let Some(dst) = inst.dst_value() {
|
||||
if !used_values.contains(&dst) {
|
||||
// Keep indices stable is not required here; remove entirely
|
||||
// NYASH_DCE_TRACE=1 enables logging for debugging DCE issues
|
||||
if dce_trace {
|
||||
eprintln!(
|
||||
"[dce] Eliminating unused pure instruction in bb{}: %{} = {:?}",
|
||||
@ -74,12 +82,17 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize {
|
||||
);
|
||||
}
|
||||
eliminated += 1;
|
||||
return false;
|
||||
keep = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
if keep {
|
||||
kept_insts.push(inst);
|
||||
kept_spans.push(span);
|
||||
}
|
||||
}
|
||||
block.instructions = kept_insts;
|
||||
block.instruction_spans = kept_spans;
|
||||
}
|
||||
if eliminated > 0 {
|
||||
function.update_cfg();
|
||||
|
||||
Reference in New Issue
Block a user