2025-11-23 22:51:30 +09:00
|
|
|
|
//! Phase 27.10: Common utilities for JoinIR lowering
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
|
|
|
|
|
|
|
2025-11-24 14:17:02 +09:00
|
|
|
|
pub mod case_a;
|
|
|
|
|
|
|
2025-11-26 10:15:31 +09:00
|
|
|
|
use crate::mir::loop_form::LoopForm;
|
2025-11-23 22:51:30 +09:00
|
|
|
|
use crate::mir::query::{MirQuery, MirQueryBox};
|
2025-11-24 14:17:02 +09:00
|
|
|
|
use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction};
|
2025-11-23 22:51:30 +09:00
|
|
|
|
|
2025-11-26 10:15:31 +09:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Phase 32: LoopForm construction helpers
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/// 単純な while ループの LoopForm を CFG から構築する
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
/// - `entry`: 関数エントリブロック
|
|
|
|
|
|
/// - `query`: MIR クエリ
|
|
|
|
|
|
/// - `entry_is_preheader`: true なら entry を preheader として使う(trim 用)
|
|
|
|
|
|
/// false なら entry の succ を preheader とする(stage1 用)
|
|
|
|
|
|
/// - `has_break`: true なら exit を break_targets に含める
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Loop structure assumed
|
|
|
|
|
|
/// ```text
|
|
|
|
|
|
/// [entry] → [preheader] → [header] ─┬→ [body] → [latch] → [header]
|
|
|
|
|
|
/// └→ [exit]
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Note: latch は body と同じブロックとして扱う(is_simple_case_a_loop 対応)
|
|
|
|
|
|
pub fn construct_simple_while_loopform(
|
|
|
|
|
|
entry: BasicBlockId,
|
|
|
|
|
|
query: &MirQueryBox,
|
|
|
|
|
|
entry_is_preheader: bool,
|
|
|
|
|
|
has_break: bool,
|
|
|
|
|
|
) -> Option<LoopForm> {
|
|
|
|
|
|
let preheader = if entry_is_preheader {
|
|
|
|
|
|
entry
|
|
|
|
|
|
} else {
|
|
|
|
|
|
query.succs(entry).get(0).copied()?
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let header = query.succs(preheader).get(0).copied().unwrap_or(preheader);
|
|
|
|
|
|
let succs_header = query.succs(header);
|
|
|
|
|
|
let body = succs_header.get(0).copied().unwrap_or(header);
|
|
|
|
|
|
let exit = succs_header.get(1).copied().unwrap_or(header);
|
|
|
|
|
|
|
|
|
|
|
|
Some(LoopForm {
|
|
|
|
|
|
preheader,
|
|
|
|
|
|
header,
|
|
|
|
|
|
body,
|
|
|
|
|
|
latch: body, // is_simple_case_a_loop 対応: latch == body
|
|
|
|
|
|
exit,
|
|
|
|
|
|
continue_targets: vec![body],
|
|
|
|
|
|
break_targets: if has_break { vec![exit] } else { vec![] },
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 22:51:30 +09:00
|
|
|
|
/// Check if entry block has at least one successor
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the entry block has at least one successor, `false` otherwise.
|
|
|
|
|
|
/// This is a basic sanity check to ensure the MIR CFG is well-formed.
|
|
|
|
|
|
pub fn ensure_entry_has_succs(query: &MirQueryBox, entry: BasicBlockId) -> bool {
|
|
|
|
|
|
!query.succs(entry).is_empty()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a basic block contains `Const { value: Integer(value) }`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a constant integer instruction with the specified value.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if entry block contains Const(0)
|
|
|
|
|
|
/// if has_const_int(&query, entry_id, 0) {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_const_int(query: &MirQueryBox, bb: BasicBlockId, value: i64) -> bool {
|
|
|
|
|
|
query.insts_in_block(bb).iter().any(|inst| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
inst,
|
|
|
|
|
|
MirInstruction::Const {
|
|
|
|
|
|
value: ConstValue::Integer(v),
|
|
|
|
|
|
..
|
|
|
|
|
|
} if *v == value
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a basic block contains `Const { value: String(value) }`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a constant string instruction with the specified value.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if entry block contains Const("")
|
|
|
|
|
|
/// if has_const_string(&query, entry_id, "") {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_const_string(query: &MirQueryBox, bb: BasicBlockId, value: &str) -> bool {
|
|
|
|
|
|
query.insts_in_block(bb).iter().any(|inst| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
inst,
|
|
|
|
|
|
MirInstruction::Const {
|
|
|
|
|
|
value: ConstValue::String(s),
|
|
|
|
|
|
..
|
|
|
|
|
|
} if s == value
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a basic block contains `BoxCall { method: method_name }`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a BoxCall instruction with the specified method name.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if entry block contains String.length()
|
|
|
|
|
|
/// if has_string_method(&query, entry_id, "length") {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_string_method(query: &MirQueryBox, bb: BasicBlockId, method: &str) -> bool {
|
|
|
|
|
|
query.insts_in_block(bb).iter().any(|inst| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
inst,
|
|
|
|
|
|
MirInstruction::BoxCall { method: m, .. } if m == method
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a basic block contains `BinOp { op: operation }`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a binary operation instruction with the specified operation.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if entry block contains BinOp(Add)
|
|
|
|
|
|
/// if has_binop(&query, entry_id, BinaryOp::Add) {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_binop(query: &MirQueryBox, bb: BasicBlockId, op: BinaryOp) -> bool {
|
|
|
|
|
|
query.insts_in_block(bb).iter().any(|inst| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
inst,
|
|
|
|
|
|
MirInstruction::BinOp { op: o, .. } if *o == op
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 05:23:26 +09:00
|
|
|
|
/// Check if a basic block contains `BoxCall { method }`
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a BoxCall instruction with the specified method name.
|
|
|
|
|
|
/// Note: This cannot distinguish between different box types (e.g., ArrayBox.get vs StringBox.get)
|
|
|
|
|
|
/// since MIR's BoxCall uses ValueId instead of box_name.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// For more precise type checking, use TypeRegistry (future enhancement).
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if entry block contains ArrayBox.length()
|
|
|
|
|
|
/// // (Note: will also match StringBox.length() if it exists)
|
|
|
|
|
|
/// if has_array_method(&query, entry_id, "length") {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_array_method(query: &MirQueryBox, bb: BasicBlockId, method: &str) -> bool {
|
|
|
|
|
|
// Note: This is the same as has_string_method() since MIR BoxCall doesn't include box_name
|
|
|
|
|
|
// Future enhancement: Use TypeRegistry to check box_val's type
|
|
|
|
|
|
has_string_method(query, bb, method)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a basic block contains a loop increment pattern (`i + 1`)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `true` if the block contains a `BinOp::Add` instruction.
|
|
|
|
|
|
/// This is a convenience wrapper for `has_binop(query, bb, BinaryOp::Add)`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// // Check if loop body contains i + 1
|
|
|
|
|
|
/// if has_loop_increment(&query, loop_body_id) {
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn has_loop_increment(query: &MirQueryBox, bb: BasicBlockId) -> bool {
|
|
|
|
|
|
has_binop(query, bb, BinaryOp::Add)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 22:51:30 +09:00
|
|
|
|
/// Log fallback to handwritten lowering with reason
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Prints a diagnostic message when MIR-based lowering falls back to handwritten version.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// log_fallback("skip_ws", "entry has no successors");
|
|
|
|
|
|
/// // Output: [joinir/skip_ws/mir] unexpected MIR shape: entry has no successors, falling back to handwritten
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn log_fallback(tag: &str, reason: &str) {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[joinir/{}/mir] unexpected MIR shape: {}, falling back to handwritten",
|
|
|
|
|
|
tag, reason
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Dispatch between MIR-based and handwritten lowering based on environment variable
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Checks `NYASH_JOINIR_LOWER_FROM_MIR` and dispatches to the appropriate lowering function.
|
|
|
|
|
|
/// This consolidates the toggle pattern used across all JoinIR lowering implementations.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
/// - `tag`: Identifier for logging (e.g., "skip_ws", "trim")
|
|
|
|
|
|
/// - `module`: MIR module to lower
|
|
|
|
|
|
/// - `mir_based`: Closure for MIR-based lowering (returns `Option<JoinModule>`)
|
|
|
|
|
|
/// - `handwritten`: Closure for handwritten lowering (returns `Option<JoinModule>`)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
/// `Option<JoinModule>` - Both implementations may return None on errors
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
/// ```ignore
|
|
|
|
|
|
/// pub fn lower_skip_ws_to_joinir(module: &MirModule) -> Option<JoinModule> {
|
|
|
|
|
|
/// dispatch_lowering(
|
|
|
|
|
|
/// "skip_ws",
|
|
|
|
|
|
/// module,
|
|
|
|
|
|
/// lower_skip_ws_from_mir,
|
|
|
|
|
|
/// lower_skip_ws_handwritten,
|
|
|
|
|
|
/// )
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
pub fn dispatch_lowering<F1, F2>(
|
|
|
|
|
|
tag: &str,
|
|
|
|
|
|
module: &crate::mir::MirModule,
|
|
|
|
|
|
mir_based: F1,
|
|
|
|
|
|
handwritten: F2,
|
|
|
|
|
|
) -> Option<crate::mir::join_ir::JoinModule>
|
|
|
|
|
|
where
|
|
|
|
|
|
F1: FnOnce(&crate::mir::MirModule) -> Option<crate::mir::join_ir::JoinModule>,
|
|
|
|
|
|
F2: FnOnce(&crate::mir::MirModule) -> Option<crate::mir::join_ir::JoinModule>,
|
|
|
|
|
|
{
|
|
|
|
|
|
use crate::mir::join_ir::env_flag_is_1;
|
|
|
|
|
|
|
|
|
|
|
|
if env_flag_is_1("NYASH_JOINIR_LOWER_FROM_MIR") {
|
2025-11-24 14:17:02 +09:00
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)",
|
|
|
|
|
|
tag
|
|
|
|
|
|
);
|
2025-11-23 22:51:30 +09:00
|
|
|
|
mir_based(module)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
eprintln!("[joinir/{}] Using handwritten lowering (default)", tag);
|
|
|
|
|
|
handwritten(module)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-04 22:33:56 +09:00
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Phase 185: Type inference utilities (shared by if_select / if_merge)
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
use crate::mir::{MirFunction, MirType, ValueId};
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 185: MIR から ValueId の型を推論(共通化)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Const 命令を探して、ValueId に対応する MirType を返す。
|
|
|
|
|
|
/// Select/IfMerge の then_val / else_val から型ヒントを埋めるために使用。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Usage
|
|
|
|
|
|
/// - `if_select.rs`: Select の型ヒント埋め込み
|
|
|
|
|
|
/// - `if_merge.rs`: IfMerge の型ヒント埋め込み
|
|
|
|
|
|
pub fn infer_type_from_mir_pattern(func: &MirFunction, val_id: ValueId) -> Option<MirType> {
|
|
|
|
|
|
// 全ブロックの全命令を走査して Const 命令を探す
|
|
|
|
|
|
for block in func.blocks.values() {
|
|
|
|
|
|
for inst in &block.instructions {
|
|
|
|
|
|
if let MirInstruction::Const { dst, value } = inst {
|
|
|
|
|
|
if *dst == val_id {
|
|
|
|
|
|
return Some(match value {
|
|
|
|
|
|
ConstValue::Integer(_) => MirType::Integer,
|
|
|
|
|
|
ConstValue::Bool(_) => MirType::Bool,
|
|
|
|
|
|
ConstValue::String(_) => MirType::String,
|
|
|
|
|
|
ConstValue::Void => MirType::Void,
|
|
|
|
|
|
ConstValue::Null => MirType::Unknown, // Null は Unknown として扱う
|
|
|
|
|
|
// Float は現状未サポート
|
|
|
|
|
|
_ => return None,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|