feat(joinir): FuncScannerBox.trim/1 JoinIR VM Bridge A/B test
Phase 30.x: JoinIR VM Bridge でPHI問題を設計で解決できることを実証 変更内容: - src/runner/modes/vm.rs: FuncScannerBox.trim/1 検出時にJoinIR経由実行 - src/mir/join_ir_vm_bridge.rs: Non-tail call サポート追加 - dst=Some(id) の場合、結果を格納して次の命令へ継続 - src/tests/joinir_vm_bridge_trim.rs: A/Bテスト新規作成 - メインテスト: Route A (VM) → "void" (PHI bug), Route C (JoinIR) → "abc" ✅ - エッジケース: 5パターン全てPASS - src/config/env.rs: joinir_vm_bridge_debug() 追加 - docs: Phase 30 TASKS.md に L-0.4 追加 テスト結果: Route A (MIR→VM直接): "void" ← PHI バグ Route C (MIR→JoinIR→VM): "abc" ← 正解 ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -189,6 +189,26 @@ pub fn fail_fast() -> bool {
|
|||||||
env_bool_default("NYASH_FAIL_FAST", true)
|
env_bool_default("NYASH_FAIL_FAST", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Phase 29/30 JoinIR toggles ----
|
||||||
|
/// JoinIR experiment mode. Required for JoinIR-related experimental paths.
|
||||||
|
/// Set NYASH_JOINIR_EXPERIMENT=1 to enable.
|
||||||
|
pub fn joinir_experiment_enabled() -> bool {
|
||||||
|
env_bool("NYASH_JOINIR_EXPERIMENT")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JoinIR VM bridge mode. When enabled with NYASH_JOINIR_EXPERIMENT=1,
|
||||||
|
/// specific functions can be executed via JoinIR → VM bridge instead of direct MIR → VM.
|
||||||
|
/// Set NYASH_JOINIR_VM_BRIDGE=1 to enable.
|
||||||
|
pub fn joinir_vm_bridge_enabled() -> bool {
|
||||||
|
env_bool("NYASH_JOINIR_VM_BRIDGE")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JoinIR VM bridge debug output. Enables verbose logging of JoinIR→MIR conversion.
|
||||||
|
/// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable.
|
||||||
|
pub fn joinir_vm_bridge_debug() -> bool {
|
||||||
|
env_bool("NYASH_JOINIR_VM_BRIDGE_DEBUG")
|
||||||
|
}
|
||||||
|
|
||||||
// VM legacy by-name call fallback was removed (Phase 2 complete).
|
// VM legacy by-name call fallback was removed (Phase 2 complete).
|
||||||
|
|
||||||
// ---- Phase 11.8 MIR cleanup toggles ----
|
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||||
|
|||||||
@ -23,15 +23,26 @@
|
|||||||
//! Phase 27-shortterm scope: skip_ws で green 化できれば成功
|
//! Phase 27-shortterm scope: skip_ws で green 化できれば成功
|
||||||
|
|
||||||
use crate::backend::{MirInterpreter, VMError, VMValue};
|
use crate::backend::{MirInterpreter, VMError, VMValue};
|
||||||
|
use crate::config::env::joinir_vm_bridge_debug;
|
||||||
use crate::mir::join_ir::{
|
use crate::mir::join_ir::{
|
||||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst,
|
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst,
|
||||||
};
|
};
|
||||||
use crate::mir::join_ir_ops::JoinValue;
|
use crate::mir::join_ir_ops::JoinValue;
|
||||||
|
use crate::ast::Span;
|
||||||
use crate::mir::{
|
use crate::mir::{
|
||||||
BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask,
|
BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask,
|
||||||
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId,
|
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 条件付きデバッグ出力マクロ(NYASH_JOINIR_VM_BRIDGE_DEBUG=1 で有効)
|
||||||
|
macro_rules! debug_log {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
if joinir_vm_bridge_debug() {
|
||||||
|
eprintln!($($arg)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 27-shortterm S-4 エラー型
|
/// Phase 27-shortterm S-4 エラー型
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct JoinIrVmBridgeError {
|
pub struct JoinIrVmBridgeError {
|
||||||
@ -52,6 +63,40 @@ impl From<VMError> for JoinIrVmBridgeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ブロックを確定する(instructions + spans + terminator を設定)
|
||||||
|
fn finalize_block(
|
||||||
|
mir_func: &mut MirFunction,
|
||||||
|
block_id: BasicBlockId,
|
||||||
|
instructions: Vec<MirInstruction>,
|
||||||
|
terminator: MirInstruction,
|
||||||
|
) {
|
||||||
|
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||||
|
let inst_count = instructions.len();
|
||||||
|
block.instructions = instructions;
|
||||||
|
block.instruction_spans = vec![Span::unknown(); inst_count];
|
||||||
|
block.terminator = Some(terminator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JoinFuncId から MIR 用の関数名を生成
|
||||||
|
fn join_func_name(id: JoinFuncId) -> String {
|
||||||
|
format!("join_func_{}", id.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JoinValue → MirConstValue 変換
|
||||||
|
fn join_value_to_mir_const(value: &JoinValue) -> Result<MirConstValue, JoinIrVmBridgeError> {
|
||||||
|
match value {
|
||||||
|
JoinValue::Int(v) => Ok(MirConstValue::Integer(*v)),
|
||||||
|
JoinValue::Bool(b) => Ok(MirConstValue::Bool(*b)),
|
||||||
|
JoinValue::Str(s) => Ok(MirConstValue::String(s.clone())),
|
||||||
|
JoinValue::Unit => Ok(MirConstValue::Null),
|
||||||
|
_ => Err(JoinIrVmBridgeError::new(format!(
|
||||||
|
"Unsupported JoinValue type: {:?}",
|
||||||
|
value
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
||||||
///
|
///
|
||||||
/// ## Arguments
|
/// ## Arguments
|
||||||
@ -78,23 +123,21 @@ pub fn run_joinir_via_vm(
|
|||||||
entry_func: JoinFuncId,
|
entry_func: JoinFuncId,
|
||||||
args: &[JoinValue],
|
args: &[JoinValue],
|
||||||
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
||||||
eprintln!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||||
eprintln!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||||
|
|
||||||
// Step 1: JoinIR → MIR 変換
|
// Step 1: JoinIR → MIR 変換 (with argument wrapper)
|
||||||
let mir_module = convert_joinir_to_mir(join_module, entry_func)?;
|
let mir_module = convert_joinir_to_mir_with_args(join_module, entry_func, args)?;
|
||||||
|
|
||||||
eprintln!(
|
debug_log!(
|
||||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR",
|
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR (+ entry wrapper)",
|
||||||
join_module.functions.len()
|
join_module.functions.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Step 2: VM 実行
|
// Step 2: VM 実行
|
||||||
let mut vm = MirInterpreter::new();
|
let mut vm = MirInterpreter::new();
|
||||||
|
|
||||||
// 初期引数を VM に設定(暫定実装: 環境変数経由)
|
debug_log!(
|
||||||
// TODO: S-4.4 で引数渡し機構を追加
|
|
||||||
eprintln!(
|
|
||||||
"[joinir_vm_bridge] Executing via VM with {} arguments",
|
"[joinir_vm_bridge] Executing via VM with {} arguments",
|
||||||
args.len()
|
args.len()
|
||||||
);
|
);
|
||||||
@ -106,44 +149,106 @@ pub fn run_joinir_via_vm(
|
|||||||
let join_result = JoinValue::from_vm_value(&vm_value)
|
let join_result = JoinValue::from_vm_value(&vm_value)
|
||||||
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
||||||
|
|
||||||
eprintln!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||||
|
|
||||||
Ok(join_result)
|
Ok(join_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 27-shortterm S-4.3: JoinIR → MIR 変換器
|
/// Phase 30.x: JoinIR → MIR 変換器 (with entry arguments)
|
||||||
///
|
///
|
||||||
/// JoinIR の関数群を MIR モジュールに変換する。
|
/// Creates a wrapper "main" function that:
|
||||||
/// JoinIR の正規化構造(Pinned/Carrier、Exit φ)は関数引数として表現されているため、
|
/// 1. Creates constant values for the entry arguments
|
||||||
/// MIR に変換しても構造的意味は保持される。
|
/// 2. Calls the actual entry function with those arguments
|
||||||
fn convert_joinir_to_mir(
|
/// 3. Returns the result
|
||||||
|
fn convert_joinir_to_mir_with_args(
|
||||||
join_module: &JoinModule,
|
join_module: &JoinModule,
|
||||||
entry_func: JoinFuncId,
|
entry_func: JoinFuncId,
|
||||||
|
args: &[JoinValue],
|
||||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||||
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
||||||
|
|
||||||
// すべての JoinIR 関数を MIR 関数に変換
|
// Convert all JoinIR functions to MIR (entry function becomes "skip" or similar)
|
||||||
for (func_id, join_func) in &join_module.functions {
|
for (func_id, join_func) in &join_module.functions {
|
||||||
eprintln!(
|
debug_log!(
|
||||||
"[joinir_vm_bridge] Converting JoinFunction {} ({})",
|
"[joinir_vm_bridge] Converting JoinFunction {} ({})",
|
||||||
func_id.0, join_func.name
|
func_id.0, join_func.name
|
||||||
);
|
);
|
||||||
|
|
||||||
let mir_func = convert_join_function_to_mir(join_func)?;
|
let mir_func = convert_join_function_to_mir(join_func)?;
|
||||||
|
|
||||||
// Entry function を "main" として登録
|
// Use actual function name (not "main") since we'll create a wrapper
|
||||||
let func_name = if *func_id == entry_func {
|
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
||||||
"main".to_string()
|
|
||||||
} else {
|
|
||||||
format!("join_func_{}", func_id.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
mir_module.functions.insert(func_name, mir_func);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a wrapper "main" function that calls the entry function with args
|
||||||
|
let wrapper = create_entry_wrapper(entry_func, args)?;
|
||||||
|
mir_module.functions.insert("main".to_string(), wrapper);
|
||||||
|
|
||||||
Ok(mir_module)
|
Ok(mir_module)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a wrapper "main" function that calls the entry function with constant arguments
|
||||||
|
fn create_entry_wrapper(
|
||||||
|
entry_func: JoinFuncId,
|
||||||
|
args: &[JoinValue],
|
||||||
|
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||||
|
let entry_block = BasicBlockId(0);
|
||||||
|
|
||||||
|
let signature = FunctionSignature {
|
||||||
|
name: "main".to_string(),
|
||||||
|
params: vec![], // No parameters - args are hardcoded
|
||||||
|
return_type: MirType::Unknown,
|
||||||
|
effects: EffectMask::PURE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||||
|
|
||||||
|
// Generate instructions to create constant arguments and call entry function
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
|
||||||
|
// Create constant values for each argument
|
||||||
|
let mut arg_value_ids = Vec::new();
|
||||||
|
for (i, arg) in args.iter().enumerate() {
|
||||||
|
let dst = ValueId(50000 + i as u32); // Use high ValueIds to avoid conflicts
|
||||||
|
arg_value_ids.push(dst);
|
||||||
|
|
||||||
|
let const_value = join_value_to_mir_const(arg)?;
|
||||||
|
instructions.push(MirInstruction::Const { dst, value: const_value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create function name constant
|
||||||
|
let func_name_id = ValueId(59990);
|
||||||
|
instructions.push(MirInstruction::Const {
|
||||||
|
dst: func_name_id,
|
||||||
|
value: MirConstValue::String(join_func_name(entry_func)),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Call instruction
|
||||||
|
let result_id = ValueId(59991);
|
||||||
|
instructions.push(MirInstruction::Call {
|
||||||
|
dst: Some(result_id),
|
||||||
|
func: func_name_id,
|
||||||
|
callee: None,
|
||||||
|
args: arg_value_ids,
|
||||||
|
effects: EffectMask::PURE,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up entry block
|
||||||
|
finalize_block(
|
||||||
|
&mut mir_func,
|
||||||
|
entry_block,
|
||||||
|
instructions,
|
||||||
|
MirInstruction::Return { value: Some(result_id) },
|
||||||
|
);
|
||||||
|
|
||||||
|
debug_log!(
|
||||||
|
"[joinir_vm_bridge] Created entry wrapper with {} args",
|
||||||
|
args.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(mir_func)
|
||||||
|
}
|
||||||
|
|
||||||
/// JoinFunction → MirFunction 変換
|
/// JoinFunction → MirFunction 変換
|
||||||
fn convert_join_function_to_mir(
|
fn convert_join_function_to_mir(
|
||||||
join_func: &crate::mir::join_ir::JoinFunction,
|
join_func: &crate::mir::join_ir::JoinFunction,
|
||||||
@ -170,6 +275,10 @@ fn convert_join_function_to_mir(
|
|||||||
|
|
||||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||||
|
|
||||||
|
// Phase 30.x: Set parameter ValueIds from JoinIR function
|
||||||
|
// JoinIR's VarId is an alias for ValueId, so direct copy works
|
||||||
|
mir_func.params = join_func.params.clone();
|
||||||
|
|
||||||
// Phase 27-shortterm S-4.4: Multi-block conversion for Jump instructions
|
// Phase 27-shortterm S-4.4: Multi-block conversion for Jump instructions
|
||||||
// Strategy:
|
// Strategy:
|
||||||
// - Accumulate Compute instructions in current block
|
// - Accumulate Compute instructions in current block
|
||||||
@ -191,55 +300,66 @@ fn convert_join_function_to_mir(
|
|||||||
dst,
|
dst,
|
||||||
k_next,
|
k_next,
|
||||||
} => {
|
} => {
|
||||||
// Phase 27-shortterm S-4.4-A: skip_ws pattern only (tail calls)
|
// Phase 30.x: Support both tail calls and non-tail calls
|
||||||
// Validate: dst=None, k_next=None (tail call)
|
// - dst=None, k_next=None: Tail call → call + return
|
||||||
if dst.is_some() || k_next.is_some() {
|
// - dst=Some(id), k_next=None: Non-tail call → call + store + continue
|
||||||
|
// - k_next=Some: Not yet supported
|
||||||
|
|
||||||
|
if k_next.is_some() {
|
||||||
return Err(JoinIrVmBridgeError::new(format!(
|
return Err(JoinIrVmBridgeError::new(format!(
|
||||||
"Non-tail Call not supported (dst={:?}, k_next={:?}). Only skip_ws tail call pattern is supported.",
|
"Call with k_next is not yet supported (k_next={:?})",
|
||||||
dst, k_next
|
k_next
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert JoinFuncId to function name
|
// Convert JoinFuncId to function name
|
||||||
// For skip_ws: func=0 → "main", func=1 → "join_func_1"
|
let func_name = join_func_name(*func);
|
||||||
let func_name = if func.0 == 0 {
|
|
||||||
"main".to_string()
|
|
||||||
} else {
|
|
||||||
format!("join_func_{}", func.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!(
|
// Create temporary ValueId for function name
|
||||||
"[joinir_vm_bridge] Converting Call to function '{}' with {} args",
|
let func_name_id = ValueId(99990 + next_block_id);
|
||||||
func_name,
|
next_block_id += 1;
|
||||||
args.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a temporary ValueId to hold the function name (string constant)
|
|
||||||
// This is a workaround for MIR's string-based Call resolution
|
|
||||||
// In Phase 27-shortterm, we use a high ValueId to avoid conflicts
|
|
||||||
let func_name_id = ValueId(9999);
|
|
||||||
|
|
||||||
// Add Const instruction for function name
|
// Add Const instruction for function name
|
||||||
current_instructions.push(MirInstruction::Const {
|
current_instructions.push(MirInstruction::Const {
|
||||||
dst: func_name_id,
|
dst: func_name_id,
|
||||||
value: MirConstValue::String(func_name.clone()),
|
value: MirConstValue::String(func_name),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create Call instruction (legacy string-based)
|
match dst {
|
||||||
// Phase 27-shortterm: No Callee yet, use func field with string ValueId
|
Some(result_dst) => {
|
||||||
|
// Non-tail call: store result in dst and continue
|
||||||
current_instructions.push(MirInstruction::Call {
|
current_instructions.push(MirInstruction::Call {
|
||||||
dst: None, // tail call, no return value captured
|
dst: Some(*result_dst),
|
||||||
func: func_name_id,
|
func: func_name_id,
|
||||||
callee: None, // Phase 27-shortterm: no Callee resolution
|
callee: None,
|
||||||
args: args.clone(),
|
args: args.clone(),
|
||||||
effects: EffectMask::PURE,
|
effects: EffectMask::PURE,
|
||||||
});
|
});
|
||||||
|
// Continue to next instruction (no block termination)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Tail call: call + return result
|
||||||
|
let call_result_id = ValueId(99991);
|
||||||
|
current_instructions.push(MirInstruction::Call {
|
||||||
|
dst: Some(call_result_id),
|
||||||
|
func: func_name_id,
|
||||||
|
callee: None,
|
||||||
|
args: args.clone(),
|
||||||
|
effects: EffectMask::PURE,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the result of the tail call
|
||||||
|
let terminator = MirInstruction::Return { value: Some(call_result_id) };
|
||||||
|
finalize_block(&mut mir_func, current_block_id, current_instructions, terminator);
|
||||||
|
current_instructions = Vec::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
JoinInst::Jump { cont, args, cond } => {
|
JoinInst::Jump { cont, args, cond } => {
|
||||||
// Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return
|
// Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return
|
||||||
// Jump represents an exit continuation (k_exit) in skip_ws pattern
|
// Jump represents an exit continuation (k_exit) in skip_ws pattern
|
||||||
|
|
||||||
eprintln!(
|
debug_log!(
|
||||||
"[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}",
|
"[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}",
|
||||||
cont, args, cond
|
cont, args, cond
|
||||||
);
|
);
|
||||||
@ -252,24 +372,20 @@ fn convert_join_function_to_mir(
|
|||||||
let continue_block_id = BasicBlockId(next_block_id);
|
let continue_block_id = BasicBlockId(next_block_id);
|
||||||
next_block_id += 1;
|
next_block_id += 1;
|
||||||
|
|
||||||
// Emit Branch in current block
|
// Phase 30.x: Branch terminator (separate from instructions)
|
||||||
current_instructions.push(MirInstruction::Branch {
|
let branch_terminator = MirInstruction::Branch {
|
||||||
condition: *cond_var,
|
condition: *cond_var,
|
||||||
then_bb: exit_block_id,
|
then_bb: exit_block_id,
|
||||||
else_bb: continue_block_id,
|
else_bb: continue_block_id,
|
||||||
});
|
};
|
||||||
|
|
||||||
// Finalize current block
|
// Finalize current block with Branch terminator
|
||||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
finalize_block(&mut mir_func, current_block_id, current_instructions, branch_terminator);
|
||||||
block.instructions = current_instructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create exit block with Return
|
// Create exit block with Return terminator
|
||||||
let exit_value = args.first().copied();
|
let exit_value = args.first().copied();
|
||||||
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
|
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
|
||||||
exit_block
|
exit_block.terminator = Some(MirInstruction::Return { value: exit_value });
|
||||||
.instructions
|
|
||||||
.push(MirInstruction::Return { value: exit_value });
|
|
||||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||||
|
|
||||||
// Create continue block (will be populated by subsequent instructions)
|
// Create continue block (will be populated by subsequent instructions)
|
||||||
@ -281,14 +397,12 @@ fn convert_join_function_to_mir(
|
|||||||
current_instructions = Vec::new();
|
current_instructions = Vec::new();
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Unconditional jump: direct Return
|
// Unconditional jump: direct Return terminator
|
||||||
let exit_value = args.first().copied();
|
let exit_value = args.first().copied();
|
||||||
current_instructions.push(MirInstruction::Return { value: exit_value });
|
let return_terminator = MirInstruction::Return { value: exit_value };
|
||||||
|
|
||||||
// Finalize current block
|
// Finalize current block with Return terminator
|
||||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||||
block.instructions = current_instructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No continuation after unconditional return
|
// No continuation after unconditional return
|
||||||
current_instructions = Vec::new();
|
current_instructions = Vec::new();
|
||||||
@ -296,12 +410,11 @@ fn convert_join_function_to_mir(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
JoinInst::Ret { value } => {
|
JoinInst::Ret { value } => {
|
||||||
current_instructions.push(MirInstruction::Return { value: *value });
|
// Phase 30.x: Return terminator (separate from instructions)
|
||||||
|
let return_terminator = MirInstruction::Return { value: *value };
|
||||||
|
|
||||||
// Finalize current block
|
// Finalize current block with Return terminator
|
||||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||||
block.instructions = current_instructions;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_instructions = Vec::new();
|
current_instructions = Vec::new();
|
||||||
}
|
}
|
||||||
@ -310,11 +423,34 @@ fn convert_join_function_to_mir(
|
|||||||
|
|
||||||
// Finalize any remaining instructions in the last block
|
// Finalize any remaining instructions in the last block
|
||||||
if !current_instructions.is_empty() {
|
if !current_instructions.is_empty() {
|
||||||
|
debug_log!(
|
||||||
|
"[joinir_vm_bridge] Final block {:?} has {} remaining instructions",
|
||||||
|
current_block_id,
|
||||||
|
current_instructions.len()
|
||||||
|
);
|
||||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||||
|
// Phase 30.x: VM requires instruction_spans to match instructions length
|
||||||
|
let inst_count = current_instructions.len();
|
||||||
block.instructions = current_instructions;
|
block.instructions = current_instructions;
|
||||||
|
block.instruction_spans = vec![Span::unknown(); inst_count];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug: print all blocks and their instruction counts + terminators
|
||||||
|
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)
|
Ok(mir_func)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,13 @@ use super::super::NyashRunner;
|
|||||||
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
||||||
use std::{fs, process};
|
use std::{fs, process};
|
||||||
|
|
||||||
|
// Phase 30.x: JoinIR VM Bridge integration (experimental)
|
||||||
|
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||||
|
use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled};
|
||||||
|
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, lower_skip_ws_to_joinir, JoinFuncId};
|
||||||
|
use crate::mir::join_ir_ops::JoinValue;
|
||||||
|
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||||
|
|
||||||
impl NyashRunner {
|
impl NyashRunner {
|
||||||
/// Execute VM mode with full plugin initialization and AST prelude merge
|
/// Execute VM mode with full plugin initialization and AST prelude merge
|
||||||
pub(crate) fn execute_vm_mode(&self, filename: &str) {
|
pub(crate) fn execute_vm_mode(&self, filename: &str) {
|
||||||
@ -495,6 +502,106 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 30.x: JoinIR VM Bridge experimental path
|
||||||
|
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||||
|
// Currently only supports minimal_ssa_skip_ws.hako (Main.skip function)
|
||||||
|
let joinir_path_attempted = if joinir_experiment_enabled() && joinir_vm_bridge_enabled() {
|
||||||
|
// Check if this module contains Main.skip/1 (minimal_ssa_skip_ws target)
|
||||||
|
// Note: function names include arity suffix like "Main.skip/1"
|
||||||
|
let has_main_skip = module_vm.functions.contains_key("Main.skip/1");
|
||||||
|
let has_trim = module_vm.functions.contains_key("FuncScannerBox.trim/1");
|
||||||
|
|
||||||
|
if has_main_skip {
|
||||||
|
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Main.skip");
|
||||||
|
match lower_skip_ws_to_joinir(&module_vm) {
|
||||||
|
Some(join_module) => {
|
||||||
|
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||||
|
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||||
|
.unwrap_or_else(|_| " abc".to_string());
|
||||||
|
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||||
|
|
||||||
|
match run_joinir_via_vm(
|
||||||
|
&join_module,
|
||||||
|
JoinFuncId::new(0),
|
||||||
|
&[JoinValue::Str(input)],
|
||||||
|
) {
|
||||||
|
Ok(result) => {
|
||||||
|
let exit_code = match &result {
|
||||||
|
JoinValue::Int(v) => *v as i32,
|
||||||
|
JoinValue::Bool(b) => if *b { 1 } else { 0 },
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result);
|
||||||
|
if !quiet_pipe {
|
||||||
|
println!("RC: {}", exit_code);
|
||||||
|
}
|
||||||
|
process::exit(exit_code);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", e);
|
||||||
|
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||||
|
false // Continue to normal VM execution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
eprintln!("[joinir/vm_bridge] lower_skip_ws_to_joinir returned None");
|
||||||
|
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if has_trim {
|
||||||
|
// Phase 30.x: FuncScannerBox.trim/1 JoinIR path
|
||||||
|
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for FuncScannerBox.trim");
|
||||||
|
match lower_funcscanner_trim_to_joinir(&module_vm) {
|
||||||
|
Some(join_module) => {
|
||||||
|
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||||
|
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||||
|
.unwrap_or_else(|_| " abc ".to_string());
|
||||||
|
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||||
|
|
||||||
|
match run_joinir_via_vm(
|
||||||
|
&join_module,
|
||||||
|
JoinFuncId::new(0),
|
||||||
|
&[JoinValue::Str(input)],
|
||||||
|
) {
|
||||||
|
Ok(result) => {
|
||||||
|
// trim returns a string, print it and exit with 0
|
||||||
|
eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result);
|
||||||
|
if !quiet_pipe {
|
||||||
|
match &result {
|
||||||
|
JoinValue::Str(s) => println!("{}", s),
|
||||||
|
_ => println!("{:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e);
|
||||||
|
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
eprintln!("[joinir/vm_bridge] lower_funcscanner_trim_to_joinir returned None");
|
||||||
|
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false // No supported JoinIR target function
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normal VM execution path (fallback or default)
|
||||||
|
if joinir_path_attempted {
|
||||||
|
// This branch is never reached because successful JoinIR path calls process::exit()
|
||||||
|
unreachable!("JoinIR path should have exited");
|
||||||
|
}
|
||||||
|
|
||||||
match vm.execute_module(&module_vm) {
|
match vm.execute_module(&module_vm) {
|
||||||
Ok(ret) => {
|
Ok(ret) => {
|
||||||
use crate::box_trait::{BoolBox, IntegerBox};
|
use crate::box_trait::{BoolBox, IntegerBox};
|
||||||
|
|||||||
222
src/tests/joinir_vm_bridge_trim.rs
Normal file
222
src/tests/joinir_vm_bridge_trim.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
// Phase 30.x: JoinIR → Rust VM Bridge A/B Test for FuncScannerBox.trim/1
|
||||||
|
//
|
||||||
|
// 目的:
|
||||||
|
// - JoinIR を VM ブリッジ経由で実行し、直接 VM 実行の結果と一致することを確認する
|
||||||
|
// - Route A (AST→MIR→VM) と Route C (AST→MIR→JoinIR→MIR'→VM) の比較
|
||||||
|
//
|
||||||
|
// Test Pattern:
|
||||||
|
// - trim(" abc ") → "abc" (leading + trailing whitespace removed)
|
||||||
|
//
|
||||||
|
// Implementation Status:
|
||||||
|
// - Phase 30.x: Non-tail call support in VM bridge ✅
|
||||||
|
// - A/B test (this file) ✅
|
||||||
|
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::backend::VM;
|
||||||
|
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, JoinFuncId};
|
||||||
|
use crate::mir::join_ir_ops::JoinValue;
|
||||||
|
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||||
|
use crate::mir::MirCompiler;
|
||||||
|
use crate::parser::NyashParser;
|
||||||
|
|
||||||
|
fn require_experiment_toggle() -> bool {
|
||||||
|
if std::env::var("NYASH_JOINIR_VM_BRIDGE").ok().as_deref() != Some("1") {
|
||||||
|
eprintln!("[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal FuncScannerBox.trim/1 implementation for testing
|
||||||
|
const TRIM_SOURCE: &str = r#"
|
||||||
|
static box FuncScannerBox {
|
||||||
|
trim(s) {
|
||||||
|
local str = "" + s
|
||||||
|
local n = str.length()
|
||||||
|
local b = me._skip_leading(str, 0, n)
|
||||||
|
local e = n + 0
|
||||||
|
return me._trim_trailing(str, b, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
_skip_leading(str, i, n) {
|
||||||
|
loop(i < n) {
|
||||||
|
local ch = str.substring(i, i + 1)
|
||||||
|
local is_ws = (ch == " ") or (ch == "\t") or (ch == "\n") or (ch == "\r")
|
||||||
|
if (not is_ws) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
_trim_trailing(str, b, e) {
|
||||||
|
loop(e > b) {
|
||||||
|
local ch = str.substring(e - 1, e)
|
||||||
|
local is_ws = (ch == " ") or (ch == "\t") or (ch == "\n") or (ch == "\r")
|
||||||
|
if (not is_ws) {
|
||||||
|
return str.substring(b, e)
|
||||||
|
}
|
||||||
|
e = e - 1
|
||||||
|
}
|
||||||
|
return str.substring(b, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn joinir_vm_bridge_trim_matches_direct_vm() {
|
||||||
|
if !require_experiment_toggle() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||||
|
std::env::set_var("NYASH_VM_MAX_STEPS", "100000");
|
||||||
|
|
||||||
|
let runner = r#"
|
||||||
|
static box Runner {
|
||||||
|
main(args) {
|
||||||
|
return FuncScannerBox.trim(" abc ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let full_src = format!("{TRIM_SOURCE}\n{runner}");
|
||||||
|
|
||||||
|
let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("trim: parse failed");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let compiled = mc.compile(ast).expect("trim: MIR compile failed");
|
||||||
|
|
||||||
|
// Route A: AST → MIR → VM (direct)
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] Route A: Direct VM execution");
|
||||||
|
std::env::set_var("NYASH_ENTRY", "Runner.main");
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let vm_out = vm
|
||||||
|
.execute_module(&compiled.module)
|
||||||
|
.expect("trim: VM execution failed");
|
||||||
|
let vm_result = vm_out.to_string_box().value;
|
||||||
|
std::env::remove_var("NYASH_ENTRY");
|
||||||
|
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] Route A result: {}", vm_result);
|
||||||
|
|
||||||
|
// Route C: AST → MIR → JoinIR → MIR' → VM (via bridge)
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] Route C: JoinIR → VM bridge execution");
|
||||||
|
let join_module = lower_funcscanner_trim_to_joinir(&compiled.module)
|
||||||
|
.expect("lower_funcscanner_trim_to_joinir failed");
|
||||||
|
|
||||||
|
let bridge_result = run_joinir_via_vm(
|
||||||
|
&join_module,
|
||||||
|
JoinFuncId::new(0),
|
||||||
|
&[JoinValue::Str(" abc ".to_string())],
|
||||||
|
)
|
||||||
|
.expect("JoinIR VM bridge failed for trim");
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[joinir_vm_bridge_test/trim] Route C result: {:?}",
|
||||||
|
bridge_result
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assertions:
|
||||||
|
// Note: Route A (direct VM) may return incorrect result due to PHI bugs.
|
||||||
|
// This is expected and is exactly what JoinIR is designed to fix.
|
||||||
|
// We verify that JoinIR returns the correct result.
|
||||||
|
match bridge_result {
|
||||||
|
JoinValue::Str(s) => {
|
||||||
|
assert_eq!(s, "abc", "Route C (JoinIR→VM bridge) trim result mismatch");
|
||||||
|
if vm_result == "abc" {
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] ✅ A/B test passed: both routes returned 'abc'");
|
||||||
|
} else {
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] ⚠️ Route A (VM) returned '{}' (PHI bug), Route C (JoinIR) returned 'abc' (correct)", vm_result);
|
||||||
|
eprintln!("[joinir_vm_bridge_test/trim] ✅ JoinIR correctly handles PHI issues that affect direct VM path");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => panic!("JoinIR VM bridge returned non-string value: {:?}", other),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||||
|
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||||
|
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||||
|
std::env::remove_var("NYASH_VM_MAX_STEPS");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn joinir_vm_bridge_trim_edge_cases() {
|
||||||
|
if !require_experiment_toggle() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||||
|
|
||||||
|
// Test cases: (input, expected_output)
|
||||||
|
// Note: Escape characters like \t\n\r are not tested here because
|
||||||
|
// Nyash string literal parsing differs from Rust escape handling.
|
||||||
|
let test_cases = [
|
||||||
|
(" abc ", "abc"),
|
||||||
|
("hello", "hello"),
|
||||||
|
(" ", ""),
|
||||||
|
(" x ", "x"),
|
||||||
|
(" hello world ", "hello world"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in test_cases {
|
||||||
|
// Re-set environment variables at each iteration to avoid race conditions
|
||||||
|
// with parallel test execution
|
||||||
|
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||||
|
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||||
|
|
||||||
|
let runner = format!(
|
||||||
|
r#"
|
||||||
|
static box Runner {{
|
||||||
|
main(args) {{
|
||||||
|
return FuncScannerBox.trim("{input}")
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
let full_src = format!("{TRIM_SOURCE}\n{runner}");
|
||||||
|
|
||||||
|
let ast: ASTNode =
|
||||||
|
NyashParser::parse_from_string(&full_src).expect("trim edge case: parse failed");
|
||||||
|
let mut mc = MirCompiler::with_options(false);
|
||||||
|
let compiled = mc.compile(ast).expect("trim edge case: MIR compile failed");
|
||||||
|
|
||||||
|
// JoinIR path only
|
||||||
|
let join_module = lower_funcscanner_trim_to_joinir(&compiled.module)
|
||||||
|
.expect("lower_funcscanner_trim_to_joinir failed");
|
||||||
|
|
||||||
|
let bridge_result = run_joinir_via_vm(
|
||||||
|
&join_module,
|
||||||
|
JoinFuncId::new(0),
|
||||||
|
&[JoinValue::Str(input.to_string())],
|
||||||
|
)
|
||||||
|
.expect("JoinIR VM bridge failed for trim edge case");
|
||||||
|
|
||||||
|
match bridge_result {
|
||||||
|
JoinValue::Str(s) => {
|
||||||
|
assert_eq!(
|
||||||
|
s, expected,
|
||||||
|
"trim({:?}) expected {:?} but got {:?}",
|
||||||
|
input, expected, s
|
||||||
|
);
|
||||||
|
eprintln!(
|
||||||
|
"[joinir_vm_bridge_test/trim] ✅ trim({:?}) = {:?}",
|
||||||
|
input, s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other => panic!(
|
||||||
|
"trim({:?}) returned non-string value: {:?}",
|
||||||
|
input, other
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||||
|
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||||
|
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ pub mod joinir_json_min; // Phase 30.x: JoinIR JSON シリアライズテスト
|
|||||||
pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト
|
pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト
|
||||||
pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト
|
pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト
|
||||||
pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test
|
pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test
|
||||||
|
pub mod joinir_vm_bridge_trim; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for trim
|
||||||
pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix
|
pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix
|
||||||
pub mod mir_breakfinder_ssa;
|
pub mod mir_breakfinder_ssa;
|
||||||
pub mod mir_funcscanner_parse_params_trim_min;
|
pub mod mir_funcscanner_parse_params_trim_min;
|
||||||
|
|||||||
Reference in New Issue
Block a user