Files
OxideJS-New/crates/oxide_cfg/src/split.rs
T

161 lines
6.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Pass 2:线性切块 + 出边(跳转/fallthrough)。
//!
//! 消费 Pass 1 的 `heads`,产出实块(`preds` 暂空,`succs` 已填跳转/fallthrough 边)
//! 与 `exit_id`(= 实块数,exit 哨兵将放在该下标)。
use oxide_bytecode::opcode::OpCode;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
use crate::{BasicBlock, EdgeKind};
/// 按块尾指令判定出边(含 label 目标解析)。
pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec<BasicBlock>, usize) {
let len = f.insts.len();
let head_positions: Vec<usize> = heads.iter().enumerate().filter(|(_, &h)| h).map(|(i, _)| i).collect();
let mut blocks: Vec<BasicBlock> = Vec::with_capacity(head_positions.len());
for (idx, &start) in head_positions.iter().enumerate() {
let end = head_positions.get(idx + 1).copied().unwrap_or(len);
blocks.push(BasicBlock {
inst_range: start..end,
preds: Vec::new(),
succs: Vec::new(),
});
}
let exit_id = blocks.len(); // exit 哨兵块号 = 实块数
for i in 0..blocks.len() {
if blocks[i].inst_range.is_empty() {
continue; // 空尾块(label 指向 len):无指令可判出边
}
let last = &f.insts[blocks[i].inst_range.end - 1];
if !crate::is_terminator(last.op) {
// 非 terminator:顺序落入下一块;已是最后一块则无出边。
if i + 1 < blocks.len() {
blocks[i].succs.push((i + 1, EdgeKind::Fallthrough));
}
continue;
}
match last.op {
OpCode::JMP | OpCode::BREAK | OpCode::CONTINUE => {
if let Operand::Label(l) = last.b {
match f.label_pos.get(l as usize).and_then(|p| *p) {
Some(p) => {
let target = block_id_of(p, &blocks);
blocks[i].succs.push((target, EdgeKind::Jump));
}
None => debug_assert!(false, "unresolved label {l}"),
}
}
}
OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH => {
if let Operand::Label(l) = last.b {
match f.label_pos.get(l as usize).and_then(|p| *p) {
Some(p) => {
let target = block_id_of(p, &blocks);
blocks[i].succs.push((target, EdgeKind::Jump));
}
None => debug_assert!(false, "unresolved label {l}"),
}
}
// fallthrough 后继:下一块(Pass 1 置 heads[i+1] 保证存在)。
blocks[i].succs.push((i + 1, EdgeKind::Fallthrough));
}
OpCode::RETURN | OpCode::HALT => {
// RETURN/HALT 汇入 exit 哨兵,用 Fallthrough 表达正常流出口。
blocks[i].succs.push((exit_id, EdgeKind::Fallthrough));
}
OpCode::THROW => { /* 无出边:异常传播,不建恢复路径 */ }
_ => unreachable!("is_terminator 已穷尽其余分支"),
}
}
(blocks, exit_id)
}
/// label 目标指令位置 → 块 id。blocks 按 `inst_range.start` 升序,p 必为某块块头
/// (Pass 1 已把所有 label 目标置为块头)。
pub(super) fn block_id_of(p: usize, blocks: &[BasicBlock]) -> usize {
blocks.partition_point(|b| b.inst_range.start <= p) - 1
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
/// JMP → Jump 边指向 label 目标块。
#[test]
fn unconditional_jmp_produces_jump_edge() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp(0)); // 0: → L0
f.insts
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 1: L0 目标
f.label_pos = vec![Some(1)];
f.label_count = 1;
// heads: 0(entry)+ 1(label 目标)+ JMP 后继 1
let heads = crate::partition::partition_blocks(&f);
let (blocks, _exit_id) = split_and_edges(&f, &heads);
assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Jump)], "JMP 产出 Jump 边");
assert_eq!(blocks[1].succs, vec![(2, EdgeKind::Fallthrough)], "RETURN 汇 exit");
}
/// 条件跳转双出边:Jump → 目标块 + Fallthrough → 下一块。
#[test]
fn conditional_jump_dual_edges() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp_if_false(1, 0)); // 0: cond → L0(else)
f.insts
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then
f.insts
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2: else 入口
f.label_pos = vec![Some(2)];
f.label_count = 1;
let heads = crate::partition::partition_blocks(&f);
let (blocks, _exit_id) = split_and_edges(&f, &heads);
assert_eq!(
blocks[0].succs,
vec![(2, EdgeKind::Jump), (1, EdgeKind::Fallthrough)],
"条件跳转双出边:Jump→else + Fallthrough→then"
);
}
/// 非 terminator 块尾:Fallthrough → 下一块;RETURN 汇 exit 哨兵。
#[test]
fn non_terminator_fallthrough_and_return_to_exit() {
let mut f = IRFunction::new();
f.insts
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0
f.insts
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1
f.insts
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
let heads = crate::partition::partition_blocks(&f);
let (blocks, exit_id) = split_and_edges(&f, &heads);
assert_eq!(blocks.len(), 1, "线性函数单块");
assert_eq!(exit_id, 1, "exit 哨兵 id = 实块数");
assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Fallthrough)], "RETURN Fallthrough 汇 exit");
}
/// exit_id 恒为实块数(split 的 exit 约定,finalize 追加哨兵)。
#[test]
fn exit_id_equals_block_count() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp_if_false(1, 0));
f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1));
f.insts.push(Inst::jmp(1));
f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None));
f.label_pos = vec![Some(3), Some(4)];
f.label_count = 2;
let heads = crate::partition::partition_blocks(&f);
let (blocks, exit_id) = split_and_edges(&f, &heads);
assert_eq!(exit_id, blocks.len(), "exit_id = 实块数(4 实块)");
}
}