feat(ir): 实现 IRFunction 到字节码的 lowering 转换

This commit is contained in:
爱特每羊队
2026-08-05 17:24:24 +08:00
parent 2a3a9a1412
commit 097153ec08
3 changed files with 401 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
//! IRFunction → CompiledModule 等价转换。
//!
//! 字节码格式(4 字节 u32 + 扩展字)与现状逐指令一致(D-13 锚)。
//! label 用前缀和:`label_pos` 记 Inst 下标,平铺时维护 inst→instr 映射,
//! 跳转 offset 以 Instr(含 ext 字)为单位(D-17)。
use oxide_bytecode::module::CompiledModule;
use oxide_bytecode::opcode::{self, OpCode};
use super::inst::Inst;
use super::operand::Operand;
use super::IRFunction;
/// IRFunction → CompiledModule。错误消息与现状(退役前 compiler.rs)逐字一致。
pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
if f.reg_overflow || f.const_overflow {
// 溢出标志在下方统一检查;先做结构验证
}
// 溢出检查 1:寄存器(仅 Operand::Reg 变体;This/NewTarget 是语义操作数,映射 254/255 合法)
if f.reg_overflow {
return Err("RangeError: function body uses too many registers (max 253)".into());
}
for inst in &f.insts {
for o in [&inst.rd, &inst.a, &inst.b] {
if let Operand::Reg(r) = o {
if *r > 253 {
return Err("RangeError: function body uses too many registers (max 253)".into());
}
}
}
}
// 溢出检查 2:常量池
if f.const_overflow || f.constants.len() > u16::MAX as usize {
return Err("RangeError: too many constants".into());
}
let mut instrs: Vec<u32> = Vec::new();
let mut inst_to_instr: Vec<usize> = Vec::with_capacity(f.insts.len());
// (当前指令的起始 Instr index, label id)
let mut jumps: Vec<(usize, u32)> = Vec::new();
for inst in &f.insts {
inst_to_instr.push(instrs.len());
encode_inst(inst, &mut instrs, &mut jumps)?;
}
// 跳转回填:offset = 目标 Instr - 当前 Instr(前缀和,D-17)
for (instr_idx, label) in jumps {
let target_inst = f
.label_pos
.get(label as usize)
.and_then(|p| *p)
.ok_or_else(|| format!("Label {label} not found in bytecode map"))?;
if target_inst >= inst_to_instr.len() {
return Err(format!("Label {label} not found in bytecode map"));
}
let target_instr = inst_to_instr[target_inst];
let offset = target_instr as isize - instr_idx as isize;
if offset < i16::MIN as isize || offset > i16::MAX as isize {
return Err("RangeError: jump offset out of range".into());
}
let offset = offset as i16;
let existing = instrs[instr_idx];
let op = opcode::opcode(existing);
let rd = opcode::rd(existing);
instrs[instr_idx] = opcode::encode(op, rd, (offset as u16 & 0xFF) as u8, ((offset as u16 >> 8) & 0xFF) as u8);
}
let mut sub_modules = Vec::with_capacity(f.nested.len());
for nested in &f.nested {
sub_modules.push(lower(nested)?);
}
Ok(CompiledModule {
bytecode: instrs,
constants: f.constants.clone(),
n_registers: f.n_registers,
n_args: f.param_layout.count as u8,
param_base: f.param_layout.base as u8,
builtin_reg_map: f.builtin_reg_map.clone(),
sub_modules,
is_arrow: f.is_arrow,
captured_this_const_idx: f.captured_this_const_idx,
function_name: f.function_name.clone(),
is_class_constructor: f.is_class_constructor,
is_derived_constructor: f.is_derived_constructor,
needs_home_object: f.needs_home_object,
upvalue_captures: f.upvalue_captures.clone(),
cells_needed: f.cells_needed,
})
}
/// Operand → u8 槽位。Const/Imm/Label 不在通用槽位路径(拆字/offset 单独处理)。
fn operand_to_u8(o: &Operand) -> u8 {
match o {
Operand::Reg(r) => *r as u8,
Operand::This => 254,
Operand::NewTarget => 255,
Operand::None => 0,
Operand::Const(_) | Operand::Imm(_) | Operand::Label(_) => 0,
}
}
/// 普通指令的 a/b 槽:a 槽的 Const/Imm 拆 lo/hi(对应 LOAD_CONST/CREATE_CLOSURE 布局)。
fn operand_pair_to_u8(a: &Operand, b: &Operand) -> (u8, u8) {
match a {
Operand::Const(idx) => ((idx & 0xFF) as u8, (idx >> 8) as u8),
Operand::Imm(v) => ((v & 0xFF) as u8, (v >> 8) as u8),
_ => (operand_to_u8(a), operand_to_u8(b)),
}
}
fn encode_inst(inst: &Inst, instrs: &mut Vec<u32>, jumps: &mut Vec<(usize, u32)>) -> Result<(), String> {
if is_jump_op(inst.op) {
let label = match inst.b {
Operand::Label(id) => id,
_ => return Err(format!("jump op {:?} requires Label operand in b slot", inst.op)),
};
let cond = operand_to_u8(&inst.rd);
let start = instrs.len();
let instr = match inst.op {
OpCode::JMP => opcode::encode_jmp(0),
OpCode::JMP_IF_FALSE => opcode::encode_jmp_if_false(cond, 0),
OpCode::JMP_IF_TRUE => opcode::encode_jmp_if_true(cond, 0),
OpCode::JMP_IF_NULLISH => opcode::encode_jmp_if_nullish(cond, 0),
OpCode::TRY_BEGIN => opcode::encode_try_begin(0),
OpCode::TRY_FINALLY_BEGIN => opcode::encode_try_finally_begin(0),
_ => unreachable!("is_jump_op mismatch"),
};
instrs.push(instr);
jumps.push((start, label));
return Ok(());
}
let rd = operand_to_u8(&inst.rd);
let (a, b) = operand_pair_to_u8(&inst.a, &inst.b);
instrs.push(opcode::encode(inst.op, rd, a, b));
instrs.extend_from_slice(&inst.ext);
Ok(())
}
fn is_jump_op(op: OpCode) -> bool {
matches!(
op,
OpCode::JMP
| OpCode::JMP_IF_FALSE
| OpCode::JMP_IF_TRUE
| OpCode::JMP_IF_NULLISH
| OpCode::TRY_BEGIN
| OpCode::TRY_FINALLY_BEGIN
)
}
+1
View File
@@ -12,6 +12,7 @@
//! 分析视图(CFG/LiveInfo/AllocMap)是 pass 输出,不住进 IR。
pub mod inst;
pub mod lower;
pub mod operand;
use oxide_bytecode::module::{Constant, UpvalueCapture};
@@ -0,0 +1,246 @@
//! lowering 全编码形态断言:IRFunction → CompiledModule 逐 Instr 等价。
use oxide_bytecode::module::{CompiledModule, Constant};
use oxide_bytecode::opcode::{self, OpCode};
use oxide_compiler::ir::inst::Inst;
use oxide_compiler::ir::lower::lower;
use oxide_compiler::ir::operand::Operand;
use oxide_compiler::ir::IRFunction;
fn base_module() -> IRFunction {
IRFunction::new()
}
fn lower_ok(f: &IRFunction) -> CompiledModule {
lower(f).expect("lower should succeed")
}
fn lower_err(f: &IRFunction) -> String {
match lower(f) {
Ok(_) => panic!("lower should fail"),
Err(e) => e,
}
}
#[test]
fn three_operand_opcode_encodes_rd_a_b() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)));
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 1);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::ADD);
assert_eq!(opcode::rd(m.bytecode[0]), 0);
assert_eq!(opcode::a(m.bytecode[0]), 1);
assert_eq!(opcode::b(m.bytecode[0]), 2);
}
#[test]
fn load_const_splits_imm16_into_a_b() {
let mut f = base_module();
f.insts.push(Inst::load_const(Operand::Reg(4), 0x012C)); // 300
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 1);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::LOAD_CONST);
assert_eq!(opcode::rd(m.bytecode[0]), 4);
assert_eq!(opcode::a(m.bytecode[0]), 0x2C); // low byte
assert_eq!(opcode::b(m.bytecode[0]), 0x01); // high byte
}
#[test]
fn create_closure_splits_sub_idx_into_a_b() {
let mut f = base_module();
f.insts.push(Inst::create_closure(Operand::Reg(4), 5));
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 1);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::CREATE_CLOSURE);
assert_eq!(opcode::rd(m.bytecode[0]), 4);
assert_eq!(opcode::imm16(m.bytecode[0]), 5);
}
#[test]
fn forward_jump_encodes_positive_offset() {
let mut f = base_module();
f.insts.push(Inst::jmp(0));
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.label_pos = vec![Some(1)];
f.label_count = 1;
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 2);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::JMP);
assert_eq!(opcode::offset16(m.bytecode[0]), 1);
}
#[test]
fn backward_jump_encodes_negative_offset() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.insts.push(Inst::jmp(0));
f.label_pos = vec![Some(0)];
f.label_count = 1;
let m = lower_ok(&f);
assert_eq!(opcode::opcode(m.bytecode[1]), OpCode::JMP);
assert_eq!(opcode::offset16(m.bytecode[1]), -1);
}
#[test]
fn jump_offset_counts_ext_words_in_target() {
// jmp(0) -> ic_get (1 + 3 ext = 4 instr) -> ADD (instr 5)
let mut f = base_module();
f.insts.push(Inst::jmp(0));
f.insts.push(Inst::ic_get(Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)));
f.label_pos = vec![Some(2)];
f.label_count = 1;
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 1 + 4 + 1);
assert_eq!(opcode::offset16(m.bytecode[0]), 5);
}
#[test]
fn ic_get_carries_three_zero_ext_words() {
let mut f = base_module();
f.insts.push(Inst::ic_get(Operand::Reg(1), Operand::Reg(2)));
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 4);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::IC_GET_PROP);
assert_eq!(opcode::rd(m.bytecode[0]), 0);
assert_eq!(opcode::a(m.bytecode[0]), 1);
assert_eq!(opcode::b(m.bytecode[0]), 2);
assert_eq!(m.bytecode[1..], [0, 0, 0]);
}
#[test]
fn call_carries_nargs_ext_word() {
let mut f = base_module();
f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 3));
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 2);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::CALL);
assert_eq!(opcode::rd(m.bytecode[0]), 0);
assert_eq!(opcode::a(m.bytecode[0]), 1);
assert_eq!(opcode::b(m.bytecode[0]), 2);
assert_eq!(m.bytecode[1], 3);
}
#[test]
fn define_accessor_and_rest_object_carry_single_ext_word() {
let mut f = base_module();
f.insts.push(Inst::define_accessor(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 42));
f.insts.push(Inst::rest_object(Operand::Reg(3), Operand::Reg(4), 7));
let m = lower_ok(&f);
assert_eq!(m.bytecode.len(), 4);
assert_eq!(opcode::opcode(m.bytecode[0]), OpCode::DEFINE_ACCESSOR);
assert_eq!(m.bytecode[1], 42);
assert_eq!(opcode::opcode(m.bytecode[2]), OpCode::REST_OBJECT);
assert_eq!(opcode::rd(m.bytecode[2]), 3);
assert_eq!(opcode::a(m.bytecode[2]), 4);
assert_eq!(m.bytecode[3], 7);
}
#[test]
fn this_and_new_target_map_to_254_255() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::This, Operand::None));
f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(2), Operand::NewTarget, Operand::None));
let m = lower_ok(&f);
assert_eq!(opcode::a(m.bytecode[0]), 254);
assert_eq!(opcode::a(m.bytecode[1]), 255);
}
#[test]
fn nested_ir_functions_pack_into_sub_modules() {
let mut outer = base_module();
let mut inner = base_module();
inner.insts.push(Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)));
inner.n_registers = 3;
outer.nested.push(inner);
let m = lower_ok(&outer);
assert_eq!(m.sub_modules.len(), 1);
assert_eq!(m.sub_modules[0].bytecode.len(), 1);
assert_eq!(opcode::opcode(m.sub_modules[0].bytecode[0]), OpCode::ADD);
assert_eq!(m.sub_modules[0].n_registers, 3);
}
#[test]
fn module_fields_are_copied_through() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.constants.push(Constant::Int(42));
f.param_layout = oxide_compiler::ir::ParamLayout { base: 2, count: 3 };
f.n_registers = 8;
f.is_arrow = true;
f.builtin_reg_map.push(("Math".to_string(), 1));
f.upvalue_captures.push(oxide_bytecode::module::UpvalueCapture {
name: "x".to_string(),
enclosing_reg: 5,
cell_idx: 0,
});
f.cells_needed = 1;
let m = lower_ok(&f);
assert_eq!(m.constants, vec![Constant::Int(42)]);
assert_eq!(m.param_base, 2);
assert_eq!(m.n_args, 3);
assert_eq!(m.n_registers, 8);
assert!(m.is_arrow);
assert_eq!(m.builtin_reg_map, vec![("Math".to_string(), 1)]);
assert_eq!(m.upvalue_captures.len(), 1);
assert_eq!(m.cells_needed, 1);
}
#[test]
fn reg_overflow_flag_errors() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.reg_overflow = true;
let err = lower_err(&f);
assert!(err.contains("too many registers"), "unexpected error: {err}");
}
#[test]
fn explicit_reg_254_errors() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(1), Operand::Reg(2)));
let err = lower_err(&f);
assert!(err.contains("too many registers"), "unexpected error: {err}");
}
#[test]
fn jump_offset_overflow_errors() {
let mut f = base_module();
f.insts.push(Inst::jmp(0));
for _ in 0..40_000 {
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
}
f.label_pos = vec![Some(40_000)];
f.label_count = 1;
let err = lower_err(&f);
assert!(err.contains("jump offset"), "unexpected error: {err}");
}
#[test]
fn missing_label_errors() {
let mut f = base_module();
f.insts.push(Inst::jmp(0));
f.label_pos = vec![None];
f.label_count = 1;
let err = lower_err(&f);
assert!(err.contains("not found"), "unexpected error: {err}");
}
#[test]
fn const_overflow_errors() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.constants = vec![Constant::Int(0); 70_000];
let err = lower_err(&f);
assert!(err.contains("too many constants"), "unexpected error: {err}");
}
#[test]
fn const_overflow_flag_errors() {
let mut f = base_module();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.const_overflow = true;
let err = lower_err(&f);
assert!(err.contains("too many constants"), "unexpected error: {err}");
}