From c46f7023c6f27471fdeb822244db9f8c11277bb0 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 6 Aug 2026 16:34:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20MOV/SPILL/UNSPILL?= =?UTF-8?q?=20opcode=20=E7=99=BB=E8=AE=B0=20+=20=E6=9E=84=E9=80=A0=20API?= =?UTF-8?q?=20+=20=E5=A5=91=E7=BA=A6=E5=B1=82=EF=BC=88D-06/D-07=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 9 +++++++ crates/oxide_ir/src/contract.rs | 30 +++++++++++++++++++++ crates/oxide_ir/src/inst.rs | 42 +++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 4f49905d..4f92fd75 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -64,6 +64,9 @@ define_opcodes! { COMPOUND_DIV = 0x09 => "COMPOUND_DIV", COMPOUND_MOD = 0x0A => "COMPOUND_MOD", COMPOUND_EXP = 0x0B => "COMPOUND_EXP", + MOV = 0x0C => "MOV", // rd=dst, a=src(寄存器复制,D-07,区间拆分搬值) + SPILL = 0x0D => "SPILL", // rd=src, ext=[slot u16](溢出写 VM spill 栈,D-06) + UNSPILL = 0x0E => "UNSPILL", // rd=dst, ext=[slot u16](从 VM spill 栈恢复,D-06) // -- Comparison (0x10-0x1F) -- EQ = 0x10 => "EQ", @@ -322,8 +325,14 @@ mod tests { assert_eq!(OpCode::STRICT_EQ as u8, 0x1A); assert_eq!(OpCode::JMP_IF_NULLISH as u8, 0x8F); assert_eq!(OpCode::VOID as u8, 0xF3); + assert_eq!(OpCode::MOV as u8, 0x0C); + assert_eq!(OpCode::SPILL as u8, 0x0D); + assert_eq!(OpCode::UNSPILL as u8, 0x0E); assert_eq!(OpCode::ADD.to_string(), "ADD"); assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); + assert_eq!(OpCode::MOV.to_string(), "MOV"); + assert_eq!(OpCode::SPILL.to_string(), "SPILL"); + assert_eq!(OpCode::UNSPILL.to_string(), "UNSPILL"); assert!(OpCode::try_from(0x1B).is_err()); assert!(OpCode::try_from(0xFF).is_err()); diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 81d46c4f..80df9a04 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -58,6 +58,8 @@ impl Inst { | OpCode::DELETE_PROP_DYNAMIC | OpCode::DEFINE_ACCESSOR | OpCode::SET_HOME_OBJECT + // SPILL 写 VM spill 栈而非寄存器(恢复由 UNSPILL 写 rd) + | OpCode::SPILL | OpCode::FOR_IN_INIT | OpCode::FOR_OF_INIT | OpCode::FOR_IN_CLEANUP @@ -172,6 +174,10 @@ impl Inst { OpCode::LOAD_VAR | OpCode::STORE_VAR | OpCode::CELL_GET => { push_operand(&mut uses, &self.a); } + // RegAlloc 辅助:MOV 读 src(a 槽);SPILL 读待溢出寄存器(rd);UNSPILL 仅写 rd 无寄存器读 + OpCode::MOV => push_operand(&mut uses, &self.a), + OpCode::SPILL => push_operand(&mut uses, &self.rd), + OpCode::UNSPILL => {} // MAKE_CELL:cell 初值读 rd;CELL_SET/STORE_UPVALUE 读 a OpCode::MAKE_CELL => push_operand(&mut uses, &self.rd), OpCode::CELL_SET | OpCode::STORE_UPVALUE => push_operand(&mut uses, &self.a), @@ -234,6 +240,10 @@ impl Inst { // 分配 / 常量 / 空操作(a 槽非寄存器) OpCode::NOP | OpCode::LOAD_CONST | OpCode::VOID | OpCode::TYPEOF | OpCode::NEW_OBJECT | OpCode::NEW_ARRAY => true, + // MOV:寄存器复制无副作用,可删无害 + OpCode::MOV => true, + // SPILL/UNSPILL:RegAlloc 插入的指令,删 SPILL 后 UNSPILL 读陈旧值(Pitfall 7),永不删 + OpCode::SPILL | OpCode::UNSPILL => false, // 闭包 / cell 读取:无抛错路径(D-07) OpCode::CREATE_CLOSURE | OpCode::LOAD_UPVALUE | OpCode::CELL_GET => true, // 模板字符串:纯拼接写 rd,无抛错(use 统计覆盖其 ext) @@ -472,6 +482,21 @@ mod tests { assert_eq!(inst.use_regs().as_slice(), &[1]); } + #[test] + fn regalloc_opcodes_def_use_contract() { + let mov = Inst::inst_mov(Operand::Reg(3), Operand::Reg(7)); + assert_eq!(mov.def_reg(), Some(3)); + assert_eq!(mov.use_regs().as_slice(), &[7]); + + let spill = Inst::inst_spill(Operand::Reg(5), 42); + assert_eq!(spill.def_reg(), None); + assert_eq!(spill.use_regs().as_slice(), &[5]); + + let unspill = Inst::inst_unspill(Operand::Reg(9), 42); + assert_eq!(unspill.def_reg(), Some(9)); + assert!(unspill.use_regs().is_empty()); + } + // ── is_pure 契约测试(Pattern 1 表,纯/非纯族代表 opcode)── #[test] @@ -504,6 +529,8 @@ mod tests { assert!(Inst::new(OpCode::CELL_GET, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); // 模板字符串 assert!(Inst::template_str(Operand::Reg(1), 1, 10, &[0x1234]).is_pure(&f)); + // RegAlloc 辅助:MOV 纯 + assert!(Inst::inst_mov(Operand::Reg(1), Operand::Reg(2)).is_pure(&f)); // LOAD_VAR 普通(a=Reg)纯 assert!(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::Reg(2), Operand::None).is_pure(&f)); // STORE_VAR 一律有副作用(Pitfall 6:顶层变量赋值全局可观察,不删) @@ -553,6 +580,9 @@ mod tests { assert!(!Inst::new(OpCode::STORE_UPVALUE, Operand::None, Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::CELL_SET, Operand::None, Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::MAKE_CELL, Operand::Reg(5), Operand::None, Operand::None).is_pure(&f)); + // RegAlloc 辅助:SPILL/UNSPILL 有副作用(删 SPILL 后 UNSPILL 读陈旧值,Pitfall 7) + assert!(!Inst::inst_spill(Operand::Reg(5), 42).is_pure(&f)); + assert!(!Inst::inst_unspill(Operand::Reg(9), 42).is_pure(&f)); // 占位 opcode 防御性有副作用 assert!(!Inst::new(OpCode::SWITCH_TABLE, Operand::None, Operand::None, Operand::None).is_pure(&f)); } diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index a10b4286..550a7bad 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -148,6 +148,24 @@ impl Inst { } } + // ── RegAlloc 辅助指令(D-06/D-07)── + + /// 寄存器复制 rd = a(D-07,RegAlloc 区间拆分搬值)。 + /// 不可用 LOAD_VAR/STORE_VAR 组合模拟——引入变量绑定语义会误触 const guard。 + pub fn inst_mov(dst: Operand, src: Operand) -> Self { + Self::with_ext(OpCode::MOV, dst, src, Operand::None, &[]) + } + + /// 溢出:regs[rd] → spill_stack[frame_base + slot],ext=[slot u16](D-06)。 + pub fn inst_spill(src: Operand, slot: u16) -> Self { + Self::with_ext(OpCode::SPILL, src, Operand::None, Operand::None, &[slot as u32]) + } + + /// 恢复:spill_stack[frame_base + slot] → regs[rd],ext=[slot u16](D-06)。 + pub fn inst_unspill(dst: Operand, slot: u16) -> Self { + Self::with_ext(OpCode::UNSPILL, dst, Operand::None, Operand::None, &[slot as u32]) + } + // ── 无 ext:立即数/索引指令(拆字是 lowering 职责)── /// 加载常量池常量:`dst = constants[idx]`。a 槽 Const 下标由 lowering 拆字。 @@ -273,6 +291,30 @@ mod tests { assert_eq!(rest.ext.as_slice(), &[7]); } + #[test] + fn regalloc_constructors_carry_expected_slots() { + let mov = Inst::inst_mov(Operand::Reg(3), Operand::Reg(7)); + assert_eq!(mov.op, OpCode::MOV); + assert_eq!(mov.rd, Operand::Reg(3)); + assert_eq!(mov.a, Operand::Reg(7)); + assert_eq!(mov.b, Operand::None); + assert!(mov.ext.is_empty()); + + let spill = Inst::inst_spill(Operand::Reg(5), 42); + assert_eq!(spill.op, OpCode::SPILL); + assert_eq!(spill.rd, Operand::Reg(5)); + assert_eq!(spill.a, Operand::None); + assert_eq!(spill.b, Operand::None); + assert_eq!(spill.ext.as_slice(), &[42]); + + let unspill = Inst::inst_unspill(Operand::Reg(9), 0xFFFF); + assert_eq!(unspill.op, OpCode::UNSPILL); + assert_eq!(unspill.rd, Operand::Reg(9)); + assert_eq!(unspill.a, Operand::None); + assert_eq!(unspill.b, Operand::None); + assert_eq!(unspill.ext.as_slice(), &[0xFFFF]); + } + #[test] fn load_const_and_create_closure_keep_semantic_operands() { let lc = Inst::load_const(Operand::Reg(4), 300);