From 2a3a9a1412456fbc0f5491107a263555813921a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Wed, 5 Aug 2026 17:17:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(ir):=20=E6=96=B0=E5=A2=9E=20Operand/Inst/I?= =?UTF-8?q?RFunction=20=E7=B1=BB=E5=9E=8B=E7=B3=BB=E7=BB=9F=E4=B8=8E?= =?UTF-8?q?=E6=9E=84=E9=80=A0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + crates/oxide_compiler/Cargo.toml | 1 + crates/oxide_compiler/src/ir/inst.rs | 265 ++++++++++++++++++++++++ crates/oxide_compiler/src/ir/mod.rs | 89 ++++++++ crates/oxide_compiler/src/ir/operand.rs | 22 ++ crates/oxide_compiler/src/lib.rs | 1 + 6 files changed, 379 insertions(+) create mode 100644 crates/oxide_compiler/src/ir/inst.rs create mode 100644 crates/oxide_compiler/src/ir/mod.rs create mode 100644 crates/oxide_compiler/src/ir/operand.rs diff --git a/Cargo.lock b/Cargo.lock index 917b0d2..097bb71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,6 +1116,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "smallvec", ] [[package]] diff --git a/crates/oxide_compiler/Cargo.toml b/crates/oxide_compiler/Cargo.toml index d1d3897..fc7cb63 100644 --- a/crates/oxide_compiler/Cargo.toml +++ b/crates/oxide_compiler/Cargo.toml @@ -13,6 +13,7 @@ oxide_log = { path = "../oxide_log" } serde.workspace = true serde_json.workspace = true rustc-hash.workspace = true +smallvec.workspace = true [dev-dependencies] oxide_code_cache = { path = "../oxide_code_cache" } diff --git a/crates/oxide_compiler/src/ir/inst.rs b/crates/oxide_compiler/src/ir/inst.rs new file mode 100644 index 0000000..05b4f7a --- /dev/null +++ b/crates/oxide_compiler/src/ir/inst.rs @@ -0,0 +1,265 @@ +//! IR 指令结构 + 构造 API。 +//! +//! `ext` 内部是裸 u32(lowering 直拼字节码扩展字)。扩展字的**数量与语义值编码** +//! 全部由 `inst_*` 构造 API 保证——emit 代码不直接触碰 `ext` 字段。 + +use oxide_bytecode::opcode::OpCode; +use smallvec::SmallVec; + +use super::operand::{LabelId, Operand}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Inst { + pub op: OpCode, + pub rd: Operand, + pub a: Operand, + pub b: Operand, + pub ext: SmallVec<[u32; 4]>, +} + +impl Inst { + /// 基础构造:ext 为空。 + pub fn new(op: OpCode, rd: Operand, a: Operand, b: Operand) -> Self { + Self { + op, + rd, + a, + b, + ext: SmallVec::new(), + } + } + + fn with_ext(op: OpCode, rd: Operand, a: Operand, b: Operand, ext: &[u32]) -> Self { + Self { + op, + rd, + a, + b, + ext: SmallVec::from_slice(ext), + } + } + + // ── IC 系:ext = [0, 0, 0](shape/slot/proto 占位字,VM 运行时 patch)── + + pub fn ic_get(dst: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::IC_GET_PROP, Operand::None, dst, key, &[0, 0, 0]) + } + + pub fn ic_set(obj: Operand, value: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::IC_SET_PROP, obj, value, key, &[0, 0, 0]) + } + + pub fn member_inc(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::MEMBER_INC, obj, val, key, &[0, 0, 0]) + } + + pub fn member_dec(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::MEMBER_DEC, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_add(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_ADD, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_sub(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_SUB, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_mul(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_MUL, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_div(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_DIV, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_mod(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_MOD, obj, val, key, &[0, 0, 0]) + } + + pub fn compound_member_exp(obj: Operand, val: Operand, key: Operand) -> Self { + Self::with_ext(OpCode::COMPOUND_MEMBER_EXP, obj, val, key, &[0, 0, 0]) + } + + // ── Call 系:ext = [nargs] ── + + pub fn call(callee: Operand, this: Operand, first_arg: Operand, nargs: u8) -> Self { + Self::with_ext(OpCode::CALL, callee, this, first_arg, &[nargs as u32]) + } + + pub fn call_native(callee: Operand, this: Operand, first_arg: Operand, nargs: u8) -> Self { + Self::with_ext(OpCode::CALL_NATIVE, callee, this, first_arg, &[nargs as u32]) + } + + pub fn new_expression(result: Operand, constructor: Operand, first_arg: Operand, nargs: u8) -> Self { + Self::with_ext(OpCode::NEW_EXPRESSION, result, constructor, first_arg, &[nargs as u32]) + } + + pub fn super_call(result: Operand, first_arg: Operand, nargs: u8) -> Self { + Self::with_ext(OpCode::SUPER_CALL, result, first_arg, Operand::None, &[nargs as u32]) + } + + // ── 其他带 ext 字 ── + + pub fn define_accessor(home: Operand, get: Operand, set: Operand, key_idx: u32) -> Self { + Self::with_ext(OpCode::DEFINE_ACCESSOR, home, get, set, &[key_idx]) + } + + pub fn rest_object(rest: Operand, src: Operand, excluded_idx: u32) -> Self { + Self::with_ext(OpCode::REST_OBJECT, rest, src, Operand::None, &[excluded_idx]) + } + + // ── 无 ext:立即数/索引指令(拆字是 lowering 职责)── + + pub fn load_const(dst: Operand, idx: u16) -> Self { + Self::new(OpCode::LOAD_CONST, dst, Operand::Const(idx), Operand::None) + } + + pub fn create_closure(dst: Operand, sub_idx: u16) -> Self { + Self::new(OpCode::CREATE_CLOSURE, dst, Operand::Imm(sub_idx), Operand::None) + } + + // ── 跳转族:label 放 b 槽,offset 计算是 lowering 职责 ── + + pub fn jmp(label: LabelId) -> Self { + Self::new(OpCode::JMP, Operand::None, Operand::None, Operand::Label(label)) + } + + pub fn jmp_if_false(cond_reg: u8, label: LabelId) -> Self { + Self::new( + OpCode::JMP_IF_FALSE, + Operand::Reg(cond_reg as u32), + Operand::None, + Operand::Label(label), + ) + } + + pub fn jmp_if_true(cond_reg: u8, label: LabelId) -> Self { + Self::new( + OpCode::JMP_IF_TRUE, + Operand::Reg(cond_reg as u32), + Operand::None, + Operand::Label(label), + ) + } + + pub fn jmp_if_nullish(cond_reg: u8, label: LabelId) -> Self { + Self::new( + OpCode::JMP_IF_NULLISH, + Operand::Reg(cond_reg as u32), + Operand::None, + Operand::Label(label), + ) + } + + pub fn try_begin(label: LabelId) -> Self { + Self::new(OpCode::TRY_BEGIN, Operand::None, Operand::None, Operand::Label(label)) + } + + pub fn try_finally_begin(label: LabelId) -> Self { + Self::new(OpCode::TRY_FINALLY_BEGIN, Operand::None, Operand::None, Operand::Label(label)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn inst_new_has_empty_ext() { + let inst = Inst::new(OpCode::ADD, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)); + assert!(inst.ext.is_empty()); + assert_eq!(inst.op, OpCode::ADD); + } + + #[test] + fn ic_instructions_carry_three_zero_ext_words() { + let insts = [ + Inst::ic_get(Operand::Reg(1), Operand::Reg(2)), + Inst::ic_set(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::member_inc(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::member_dec(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_add(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_sub(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_mul(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_div(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_mod(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + Inst::compound_member_exp(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)), + ]; + for inst in &insts { + assert_eq!(inst.ext.as_slice(), &[0, 0, 0], "IC op {} must carry 3 zero ext words", inst.op); + assert!(inst.ext.len() == 3); + } + } + + #[test] + fn call_instructions_carry_nargs() { + let call = Inst::call(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 3); + assert_eq!(call.ext.as_slice(), &[3]); + assert_eq!(call.rd, Operand::Reg(0)); + assert_eq!(call.a, Operand::Reg(1)); + assert_eq!(call.b, Operand::Reg(2)); + + let native = Inst::call_native(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 0); + assert_eq!(native.ext.as_slice(), &[0]); + + let new_expr = Inst::new_expression(Operand::Reg(3), Operand::Reg(0), Operand::Reg(1), 2); + assert_eq!(new_expr.ext.as_slice(), &[2]); + assert_eq!(new_expr.rd, Operand::Reg(3)); + assert_eq!(new_expr.a, Operand::Reg(0)); + + let super_call = Inst::super_call(Operand::Reg(3), Operand::Reg(1), 1); + assert_eq!(super_call.ext.as_slice(), &[1]); + assert_eq!(super_call.rd, Operand::Reg(3)); + assert_eq!(super_call.a, Operand::Reg(1)); + } + + #[test] + fn single_ext_word_instructions() { + let accessor = Inst::define_accessor(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 42); + assert_eq!(accessor.ext.as_slice(), &[42]); + assert_eq!(accessor.rd, Operand::Reg(0)); + assert_eq!(accessor.a, Operand::Reg(1)); + assert_eq!(accessor.b, Operand::Reg(2)); + + let rest = Inst::rest_object(Operand::Reg(0), Operand::Reg(1), 7); + assert_eq!(rest.ext.as_slice(), &[7]); + } + + #[test] + fn load_const_and_create_closure_keep_semantic_operands() { + let lc = Inst::load_const(Operand::Reg(4), 300); + assert_eq!(lc.a, Operand::Const(300)); + assert_eq!(lc.b, Operand::None); + assert!(lc.ext.is_empty()); + + let cc = Inst::create_closure(Operand::Reg(4), 5); + assert_eq!(cc.a, Operand::Imm(5)); + assert_eq!(cc.b, Operand::None); + assert!(cc.ext.is_empty()); + } + + #[test] + fn jump_family_puts_label_in_b_slot() { + let jmp = Inst::jmp(9); + assert_eq!(jmp.b, Operand::Label(9)); + assert_eq!(jmp.rd, Operand::None); + + let cond = Inst::jmp_if_false(3, 9); + assert_eq!(cond.rd, Operand::Reg(3)); + assert_eq!(cond.b, Operand::Label(9)); + + let true_jmp = Inst::jmp_if_true(3, 9); + assert_eq!(true_jmp.b, Operand::Label(9)); + + let nullish = Inst::jmp_if_nullish(3, 9); + assert_eq!(nullish.b, Operand::Label(9)); + + let try_begin = Inst::try_begin(9); + assert_eq!(try_begin.b, Operand::Label(9)); + assert_eq!(try_begin.rd, Operand::None); + + let try_fin = Inst::try_finally_begin(9); + assert_eq!(try_fin.b, Operand::Label(9)); + } +} diff --git a/crates/oxide_compiler/src/ir/mod.rs b/crates/oxide_compiler/src/ir/mod.rs new file mode 100644 index 0000000..b00dbe9 --- /dev/null +++ b/crates/oxide_compiler/src/ir/mod.rs @@ -0,0 +1,89 @@ +//! IR 中间表示:AST→IR→字节码 流水线的程序表示。 +//! +//! 单一程序表示(D-01),分域组合(D-02): +//! - code: 指令流 + label 位置 +//! - data: 常量池 +//! - bindings: 参数布局 +//! - builtins: 内置全局 → 寄存器 +//! - closures: 闭包捕获 + cell +//! - meta: 函数元信息 + 溢出证据 +//! - nested: 子函数(递归 IR,替代 sub_modules) +//! +//! 分析视图(CFG/LiveInfo/AllocMap)是 pass 输出,不住进 IR。 + +pub mod inst; +pub mod operand; + +use oxide_bytecode::module::{Constant, UpvalueCapture}; + +use self::inst::Inst; + +/// 参数段布局:base 起 count 个寄存器连续(VM 调用契约),RegAlloc 输入。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ParamLayout { + pub base: u32, + pub count: u32, +} + +/// 单函数 IR。分域组合,嵌套封顶两层。 +#[derive(Debug, Clone)] +pub struct IRFunction { + // code 域 + pub insts: Vec, + /// label id → 指令下标。id 连续递增,Vec 索引即 id;写入前须扩容。 + pub label_pos: Vec>, + pub label_count: u32, + // data 域 + pub constants: Vec, + // bindings 域 + pub param_layout: ParamLayout, + // builtins 域 + pub builtin_reg_map: Vec<(String, u8)>, + // closures 域 + pub upvalue_captures: Vec, + pub cells_needed: u8, + // meta 域 + pub n_registers: u8, + pub is_arrow: bool, + pub is_class_constructor: bool, + pub is_derived_constructor: bool, + pub needs_home_object: bool, + pub captured_this_const_idx: u16, + pub function_name: Option, + /// emit 侧溢出证据,lowering 读标志报错(D-20)。 + pub reg_overflow: bool, + pub const_overflow: bool, + // nested 域 + pub nested: Vec, +} + +impl IRFunction { + pub fn new() -> Self { + Self { + insts: Vec::new(), + label_pos: Vec::new(), + label_count: 0, + constants: Vec::new(), + param_layout: ParamLayout { base: 0, count: 0 }, + builtin_reg_map: Vec::new(), + upvalue_captures: Vec::new(), + cells_needed: 0, + n_registers: 0, + is_arrow: false, + is_class_constructor: false, + is_derived_constructor: false, + needs_home_object: false, + captured_this_const_idx: 0, + function_name: None, + reg_overflow: false, + const_overflow: false, + nested: Vec::new(), + } + } +} + +impl Default for IRFunction { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/oxide_compiler/src/ir/operand.rs b/crates/oxide_compiler/src/ir/operand.rs new file mode 100644 index 0000000..cbd0c3b --- /dev/null +++ b/crates/oxide_compiler/src/ir/operand.rs @@ -0,0 +1,22 @@ +//! 类型安全操作数。IR 中不出现裸寄存器下标。 +//! +//! - `Reg(u32)`:寄存器。本阶段存物理号(≤253);254/255 为 VM 保留,语义由 `This`/`NewTarget` 表达 +//! - `Const(u16)`:常量池下标 +//! - `Label(u32)`:跳转目标(`LabelId`) +//! - `Imm(u16)`:立即数 +//! - `This` / `NewTarget`:语义化特殊寄存器,由 lowering 映射物理 254/255(IR 中禁止裸下标) +//! - `None`:操作数槽未使用 + +/// 统一跳转目标标识(取代语义化 `Label` 枚举)。 +pub type LabelId = u32; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Operand { + Reg(u32), + Const(u16), + Label(u32), + Imm(u16), + This, + NewTarget, + None, +} diff --git a/crates/oxide_compiler/src/lib.rs b/crates/oxide_compiler/src/lib.rs index 9bad953..8bcd418 100644 --- a/crates/oxide_compiler/src/lib.rs +++ b/crates/oxide_compiler/src/lib.rs @@ -6,6 +6,7 @@ pub mod compiler_log; pub mod emit_ctx; pub mod expr; pub mod hash; +pub mod ir; pub mod shared; pub mod stmt; pub mod symbol_table;