Files
hakorune/src/mir/join_ir/lowering/common.rs

201 lines
6.4 KiB
Rust
Raw Normal View History

//! Phase 27.10: Common utilities for JoinIR lowering
//!
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction};
use crate::mir::query::{MirQuery, MirQueryBox};
/// 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
)
})
}
/// 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)
}
/// 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") {
eprintln!("[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)", tag);
mir_based(module)
} else {
eprintln!("[joinir/{}] Using handwritten lowering (default)", tag);
handwritten(module)
}
}