feat(ir): 实现 IRFunction 到字节码的 lowering 转换
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
Reference in New Issue
Block a user