From ef3cd65eb6162c56c441a403ec25dc7dde895442 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 5 Aug 2026 17:24:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20IRFunction=20?= =?UTF-8?q?=E5=88=B0=E5=AD=97=E8=8A=82=E7=A0=81=E7=9A=84=20lowering=20?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/ir/lower.rs | 154 +++++++++++ crates/oxide_compiler/src/ir/mod.rs | 1 + crates/oxide_compiler/tests/lowering_tests.rs | 246 ++++++++++++++++++ 3 files changed, 401 insertions(+) create mode 100644 crates/oxide_compiler/src/ir/lower.rs create mode 100644 crates/oxide_compiler/tests/lowering_tests.rs diff --git a/crates/oxide_compiler/src/ir/lower.rs b/crates/oxide_compiler/src/ir/lower.rs new file mode 100644 index 00000000..7e158880 --- /dev/null +++ b/crates/oxide_compiler/src/ir/lower.rs @@ -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 { + 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 = Vec::new(); + let mut inst_to_instr: Vec = 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, 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 + ) +} diff --git a/crates/oxide_compiler/src/ir/mod.rs b/crates/oxide_compiler/src/ir/mod.rs index b00dbe9d..0d446b6c 100644 --- a/crates/oxide_compiler/src/ir/mod.rs +++ b/crates/oxide_compiler/src/ir/mod.rs @@ -12,6 +12,7 @@ //! 分析视图(CFG/LiveInfo/AllocMap)是 pass 输出,不住进 IR。 pub mod inst; +pub mod lower; pub mod operand; use oxide_bytecode::module::{Constant, UpvalueCapture}; diff --git a/crates/oxide_compiler/tests/lowering_tests.rs b/crates/oxide_compiler/tests/lowering_tests.rs new file mode 100644 index 00000000..27c348b2 --- /dev/null +++ b/crates/oxide_compiler/tests/lowering_tests.rs @@ -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}"); +}