From 66e240ad008ac52ffd2ff456ea8b8cfbab54433b Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 12 Oct 2026 14:43:58 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20IR=20=E5=B1=82?= =?UTF-8?q?=E5=B8=B8=E9=87=8F=E4=BC=A0=E6=92=AD=E4=B8=8E=E6=8A=98=E5=8F=A0?= =?UTF-8?q?=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 53 ++- Cargo.toml | 4 +- crates/oxide_compiler/Cargo.toml | 1 + crates/oxide_compiler/src/compiler.rs | 3 + crates/oxide_fold/Cargo.toml | 18 + crates/oxide_fold/src/fold_log.rs | 37 ++ crates/oxide_fold/src/fold_table.rs | 198 ++++++++++ crates/oxide_fold/src/lib.rs | 43 +++ crates/oxide_fold/src/propagate.rs | 171 ++++++++ crates/oxide_fold/tests/fold_tests.rs | 537 ++++++++++++++++++++++++++ 10 files changed, 1044 insertions(+), 21 deletions(-) create mode 100644 crates/oxide_fold/Cargo.toml create mode 100644 crates/oxide_fold/src/fold_log.rs create mode 100644 crates/oxide_fold/src/fold_table.rs create mode 100644 crates/oxide_fold/src/lib.rs create mode 100644 crates/oxide_fold/src/propagate.rs create mode 100644 crates/oxide_fold/tests/fold_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0675f259..4013b798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.16.20" +version = "0.16.21" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.16.20" +version = "0.16.21" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.16.20" +version = "0.16.21" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.16.20" +version = "0.16.21" dependencies = [ "lru", "oxide_bytecode", @@ -1237,13 +1237,14 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_cfg", "oxide_code_cache", "oxide_dce", "oxide_emit", + "oxide_fold", "oxide_ir", "oxide_liveness", "oxide_log", @@ -1257,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1271,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.16.20" +version = "0.16.21" dependencies = [ "num-bigint", "oxc_allocator", @@ -1287,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1297,9 +1298,21 @@ dependencies = [ "oxide_vm", ] +[[package]] +name = "oxide_fold" +version = "0.16.21" +dependencies = [ + "num-bigint", + "oxide_bytecode", + "oxide_ir", + "oxide_log", + "oxide_runtime_api", + "oxide_types", +] + [[package]] name = "oxide_ir" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1308,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.16.20" +version = "0.16.21" dependencies = [ "bumpalo", "dashmap", @@ -1322,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1334,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.16.20" +version = "0.16.21" dependencies = [ "tracing", "tracing-appender", @@ -1343,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.16.20" +version = "0.16.21" [[package]] name = "oxide_parser" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1362,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1378,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.16.20" +version = "0.16.21" dependencies = [ "num-bigint", "num-traits", @@ -1391,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.16.20" +version = "0.16.21" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1411,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.16.20" +version = "0.16.21" dependencies = [ "bumpalo", "dashmap", @@ -1424,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.16.20" +version = "0.16.21" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 9783549c..5456cbcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/oxide_emit", "crates/oxide_ir", "crates/oxide_cfg", + "crates/oxide_fold", "crates/oxide_dce", "crates/oxide_liveness", "crates/oxide_regalloc", @@ -24,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.16.20" +version = "0.16.21" edition = "2021" license = "MIT" rust-version = "1.80" @@ -39,6 +40,7 @@ oxc_diagnostics = "0.134.0" oxc_syntax = "0.134.0" oxide_ir = { path = "crates/oxide_ir" } oxide_emit = { path = "crates/oxide_emit" } +oxide_fold = { path = "crates/oxide_fold" } clap = { version = "4", features = ["derive"] } ctrlc = { version = "3.4", features = ["termination"] } rustyline = "14" diff --git a/crates/oxide_compiler/Cargo.toml b/crates/oxide_compiler/Cargo.toml index e024c082..88e3c5da 100644 --- a/crates/oxide_compiler/Cargo.toml +++ b/crates/oxide_compiler/Cargo.toml @@ -17,6 +17,7 @@ oxide_liveness = { path = "../oxide_liveness" } oxide_regalloc = { path = "../oxide_regalloc" } oxide_ir.workspace = true oxide_emit.workspace = true +oxide_fold.workspace = true serde.workspace = true serde_json.workspace = true rustc-hash.workspace = true diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 93d5a33d..6da3616c 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -98,6 +98,9 @@ impl Compiler { /// 零删除返回 `live_reusable`(免第二轮 CFG/活集重建)。 fn compile_ir(&self, mut ir: oxide_ir::IRFunction) -> Result { crate::compiler_debug!("compile_ir: after emit {} insts", ir.insts.len()); + // 常量折叠:单向前扫常量传播加就地改写,接在 emit 与 DCE 之间。 + // 不删指令、不重映射 label,改写造成的 CFG 失效由 DCE 自身的 build_cfg 吸收。 + oxide_fold::fold(&mut ir); // DCE 零删除时返回 Pass A CFG 供 liveness 复用;有删除或 DCE 关闭时为 None(须重建)。 let dce_cfg: Option = if self.enable_dce { let cfg = oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归 diff --git a/crates/oxide_fold/Cargo.toml b/crates/oxide_fold/Cargo.toml new file mode 100644 index 00000000..8f16d35c --- /dev/null +++ b/crates/oxide_fold/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "oxide_fold" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - forward constant propagation and constant folding pass over IR" + +[dependencies] +oxide_ir = { path = "../oxide_ir" } +oxide_bytecode = { path = "../oxide_bytecode" } +oxide_runtime_api = { path = "../oxide_runtime_api" } +oxide_types = { path = "../oxide_types" } +oxide_log = { path = "../oxide_log" } +num-bigint = "0.4" + +[lints] +workspace = true diff --git a/crates/oxide_fold/src/fold_log.rs b/crates/oxide_fold/src/fold_log.rs new file mode 100644 index 00000000..3b50a264 --- /dev/null +++ b/crates/oxide_fold/src/fold_log.rs @@ -0,0 +1,37 @@ +//! 常量折叠 pass 日志宏:`fold_error!/warn!/info!/debug!/trace!`,经由 oxide_log 上报。 +//! 编译管线属 Kernel 子系统,`OXIDE_LOG=oxide::kernel=debug` 控制本 crate 日志。 + +#[macro_export] +macro_rules! fold_error { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::ERROR, $($arg)*) + }; +} + +#[macro_export] +macro_rules! fold_warn { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::WARN, $($arg)*) + }; +} + +#[macro_export] +macro_rules! fold_info { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::INFO, $($arg)*) + }; +} + +#[macro_export] +macro_rules! fold_debug { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::DEBUG, $($arg)*) + }; +} + +#[macro_export] +macro_rules! fold_trace { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::TRACE, $($arg)*) + }; +} diff --git a/crates/oxide_fold/src/fold_table.rs b/crates/oxide_fold/src/fold_table.rs new file mode 100644 index 00000000..430ab7b1 --- /dev/null +++ b/crates/oxide_fold/src/fold_table.rs @@ -0,0 +1,198 @@ +//! 折叠表:一元与算术常量折叠(A 组)加逻辑短路选支(B 组)。 +//! +//! 折值编码逐运算符镜像运行时产物编码,保证物化后与运行时执行结果逐字节 +//! 一致:折值计算复用 `oxide_runtime_api::coercion` 的纯函数族(`to_number` / +//! `to_int32` / `to_boolean`),与 emit 层字面量折叠同源,杜绝两层折叠各自 +//! 镜像运行时语义的漂移风险。 +//! +//! 语义守卫: +//! - 算术二元(ADD/SUB/MUL/DIV/MOD/EXP)任一操作数非数值不折;BigInt 混合 +//! (一 BigInt 一非 BigInt)不折(运行期抛 TypeError,BigInt 与 BigInt 有 +//! 独立指令族);SUB/MUL/DIV/MOD/EXP 的 BigInt 操作数不折(折值恒升 f64, +//! 无法表示 BigInt 结果);DIV/MOD 除数为零不折、EXP 指数为负不折(保持 +//! 运行时指令为除零/负指数编码的唯一来源)。 +//! - ADD 的 int+int 经 `checked_add` 在 i32 内保 `Int`,溢出升 `Number` +//! (镜像运行时 `int_add`);其余组合 f64 加(不做恒等捷径,负零操作数 +//! 经实加得 `+0.0`)。 +//! - UNARY_PLUS 作用 BigInt 不折(运行期抛 TypeError)。 +//! +//! B 组逻辑短路:结果恒为某一操作数的原值(无类型转换,任何类型安全), +//! 仅左操作数已知即可选支,右操作数类型不限。 + +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::OpCode; +use oxide_runtime_api::{to_boolean, to_int32, to_number}; +use oxide_types::string_forge::encode_key; +use oxide_types::value::JsValue; + +/// 常量转 `JsValue`(纯 NaN-box 构造,零分配):`Int`/`Number`/`Boolean`/ +/// `Null`/`Undefined` 五臂;`BigInt`/`String` 是引用形态(指针载荷),不在此 +/// 转换,由调用方按常量类型分派处理。 +fn const_to_jsvalue(c: &Constant) -> Option { + match c { + Constant::Int(i) => Some(JsValue::int(*i)), + Constant::Number(d) => Some(JsValue::float(*d)), + Constant::Boolean(b) => Some(JsValue::bool(*b)), + Constant::Null => Some(JsValue::null()), + Constant::Undefined => Some(JsValue::undefined()), + Constant::BigInt(_) | Constant::String(_) => None, + } +} + +/// 常量的数值(`Int`/`Number` 臂,调用方已守卫非 BigInt 数值)。 +fn const_to_number(c: &Constant) -> f64 { + to_number(const_to_jsvalue(c).expect("数值常量(非 BigInt)必有 JsValue 形态")) +} + +/// 是否为数值常量(`Int`/`Number`/`BigInt` 三臂)。 +fn is_numeric(c: &Constant) -> bool { + matches!(c, Constant::Int(_) | Constant::Number(_) | Constant::BigInt(_)) +} + +/// 是否为 BigInt 常量。 +fn is_bigint(c: &Constant) -> bool { + matches!(c, Constant::BigInt(_)) +} + +/// 常量的布尔值(`ToBoolean` 口径):BigInt 臂判零、字符串臂判非空(池键形态 +/// 空串即空串),其余经 `const_to_jsvalue` 加 `to_boolean`。 +fn is_truthy(c: &Constant) -> bool { + match c { + Constant::BigInt(b) => *b != num_bigint::BigInt::from(0), + Constant::String(s) => !s.is_empty(), + _ => to_boolean(const_to_jsvalue(c).expect("非 BigInt/String 常量必有 JsValue 形态")), + } +} + +/// 常量是否为 nullish(`Null`/`Undefined` 两臂)。 +fn is_nullish(c: &Constant) -> bool { + matches!(c, Constant::Null | Constant::Undefined) +} + +/// A 组一元折叠:按操作码计算折值,守卫命中返回 `None`。 +/// +/// 折值编码逐运算符镜像运行时产物: +/// - `VOID`:恒 `Undefined`(操作数副作用已在发射期为独立指令,本指令恒装 undefined) +/// - `NOT`:`Boolean`(`ToBoolean` 取反,任意常量类型安全) +/// - `TYPEOF`:`String`(池键形态,逐类型镜像运行时 `TYPEOF_TEXTS` 分派: +/// Int/Number 归 "number"、BigInt 归 "bigint"、Boolean 归 "boolean"、 +/// Null 归 "object"、String 归 "string"、Undefined 归 "undefined") +/// - `NEG`:BigInt 取负保 `BigInt`;其余数值恒 `Number`(NEG 恒升 double, +/// 不保 `Int`);非数值不折 +/// - `UNARY_PLUS`:`Int`/`Number` 恒 `Number`(`ToNumber`);BigInt 不折 +/// (运行期抛 TypeError) +/// - `BIT_NOT`:BigInt 按位取反保 `BigInt`;其余数值恒 `Int`(`ToInt32` 取反) +pub fn fold_unary(op: OpCode, c: &Constant) -> Option { + match op { + OpCode::VOID => Some(Constant::Undefined), + OpCode::NOT => Some(Constant::Boolean(!is_truthy(c))), + OpCode::TYPEOF => { + let text = match c { + Constant::Int(_) | Constant::Number(_) => "number", + Constant::BigInt(_) => "bigint", + Constant::Boolean(_) => "boolean", + Constant::Null => "object", + Constant::String(_) => "string", + Constant::Undefined => "undefined", + }; + let units: Vec = text.encode_utf16().collect(); + Some(Constant::String(encode_key(&units))) + } + OpCode::NEG => match c { + Constant::BigInt(b) => Some(Constant::BigInt(-b.clone())), + Constant::Int(i) => Some(Constant::Number(-(*i as f64))), + Constant::Number(d) => Some(Constant::Number(-*d)), + _ => None, + }, + OpCode::UNARY_PLUS => match c { + Constant::Int(i) => Some(Constant::Number(*i as f64)), + Constant::Number(d) => Some(Constant::Number(*d)), + _ => None, + }, + OpCode::BIT_NOT => match c { + Constant::BigInt(b) => Some(Constant::BigInt(!b.clone())), + Constant::Int(_) | Constant::Number(_) => Some(Constant::Int(!to_int32( + const_to_jsvalue(c).expect("数值常量(非 BigInt)必有 JsValue 形态"), + ))), + _ => None, + }, + _ => None, + } +} + +/// A 组二元算术折叠:按操作码计算折值,守卫命中返回 `None`。 +/// +/// 折值编码逐运算符镜像运行时产物: +/// - `ADD`:int+int 经 `checked_add` 在 i32 内保 `Int`,溢出升 `Number` +/// (镜像 `int_add`);BigInt+BigInt 精确加;其余组合 f64 加(实加, +/// 不做恒等捷径) +/// - `SUB`/`MUL`/`DIV`/`MOD`:恒 `Number` 的 f64 运算(镜像 `binary_arith!` +/// 宏的 `as f64`);DIV/MOD 除数为零不折 +/// - `EXP`:恒 `Number` 的 `powf`(镜像 `exp_result`);指数为负不折 +pub fn fold_binary(op: OpCode, l: &Constant, r: &Constant) -> Option { + // 两操作数均须数值;BigInt 混合(一 BigInt 一非 BigInt)不折。 + if !is_numeric(l) || !is_numeric(r) { + return None; + } + if is_bigint(l) != is_bigint(r) { + return None; + } + match op { + OpCode::ADD => { + if is_bigint(l) { + return match (l, r) { + (Constant::BigInt(a), Constant::BigInt(b)) => Some(Constant::BigInt(a + b)), + _ => None, + }; + } + // int+int:checked_add 在 i32 内保 Int,溢出升 f64(镜像 int_add)。 + if let (Constant::Int(a), Constant::Int(b)) = (l, r) { + return a + .checked_add(*b) + .map(Constant::Int) + .or(Some(Constant::Number(*a as f64 + *b as f64))); + } + // 其余组合 f64 实加(负零操作数经实加得 +0.0,不取恒等捷径)。 + Some(Constant::Number(const_to_number(l) + const_to_number(r))) + } + OpCode::SUB | OpCode::MUL | OpCode::DIV | OpCode::MOD | OpCode::EXP => { + // BigInt 操作数不折:折值恒升 f64,无法表示 BigInt 结果。 + if is_bigint(l) { + return None; + } + // 除零守卫:Int(0) 与 Number(±0.0) 均判零(v == 0.0 对 -0.0 亦真); + // 负指数守卫:Int 与 Number 负值均判负。命中即不折,落回运行时指令。 + if (matches!(op, OpCode::DIV | OpCode::MOD) && const_to_number(r) == 0.0) + || (op == OpCode::EXP && const_to_number(r) < 0.0) + { + return None; + } + let (ln, rn) = (const_to_number(l), const_to_number(r)); + Some(match op { + OpCode::SUB => Constant::Number(ln - rn), + OpCode::MUL => Constant::Number(ln * rn), + OpCode::DIV => Constant::Number(ln / rn), + OpCode::MOD => Constant::Number(ln % rn), + OpCode::EXP => Constant::Number(ln.powf(rn)), + _ => unreachable!("守卫已限定 SUB/MUL/DIV/MOD/EXP"), + }) + } + _ => None, + } +} + +/// B 组逻辑短路选支:左操作数已知时返回选定操作数(`true` = 左,`false` = +/// 右),左操作数未知返回 `None`。 +/// +/// 结果恒为某一操作数的原值(无类型转换): +/// - `AND`:左真选右、左假选左 +/// - `OR`:左真选左、左假选右 +/// - `NULLISH`:左 nullish 选右、左非 nullish 选左 +pub fn short_circuit(op: OpCode, l: &Constant) -> Option { + match op { + OpCode::AND => Some(!is_truthy(l)), + OpCode::OR => Some(is_truthy(l)), + OpCode::NULLISH => Some(!is_nullish(l)), + _ => None, + } +} diff --git a/crates/oxide_fold/src/lib.rs b/crates/oxide_fold/src/lib.rs new file mode 100644 index 00000000..644c95c8 --- /dev/null +++ b/crates/oxide_fold/src/lib.rs @@ -0,0 +1,43 @@ +//! 常量折叠 pass:单向前扫常量传播加就地改写(IR 层)。 +//! +//! `fold` 对每个函数(递归 nested,每个 nested 函数有独立常量池与指令流,独立前扫) +//! 运行单向前扫:维护 per-register known 表记录各虚拟寄存器的已知常量值,命中 +//! 改写表即就地改写——A 组一元与算术常量折叠改写为 `LOAD_CONST`(新常量入池), +//! B 组逻辑短路改写为 `MOV`(无新常量)。 +//! +//! 不变量: +//! - 不删指令、不重映射 `label_pos`:改写只改变指令的 op 与操作数,不改变跳转 +//! 结构,CFG 的块划分与边集不变,由下游 DCE 在折叠后的指令流上重建 CFG 吸收。 +//! - 汇合点健全性:`label_pos` 指向的指令下标(控制流汇合点)整体清空 known 表, +//! 跨汇合的陈旧知识不用于折叠。 +//! - 负零守卫:算术折叠做实际运算(f64 加),不取恒等捷径——`-0.0 + 0.0` 得 +//! `+0.0`(`Object.is` 可区分)。 +//! - 池溢出守卫:常量池满(`u16::MAX`)时 A 组改写禁用(不新增常量),B 组 +//! (MOV)不受影响。 +//! +//! 折值计算复用 `oxide_runtime_api::coercion` 的纯函数族(`to_number` / +//! `to_int32` / `to_boolean`),与 emit 层字面量折叠同源,杜绝两层折叠各自镜像 +//! 运行时语义的漂移风险。 + +mod fold_log; +mod fold_table; +mod propagate; + +use oxide_ir::IRFunction; + +/// 常量传播与折叠:对函数(递归 nested)单向前扫,就地改写。 +/// +/// 返回是否发生改写(仅调试日志用)。接在 emit 与 DCE 之间:改写后失去消费者 +/// 的 `LOAD_CONST` 与死 `MOV` 由下游 DCE 清理,本 pass 不删指令。 +pub fn fold(f: &mut IRFunction) -> bool { + let mut changed = propagate::fold_function(f); + for nested in f.nested.iter_mut() { + if fold(nested) { + changed = true; + } + } + if changed { + fold_info!("fold: rewrites applied"); + } + changed +} diff --git a/crates/oxide_fold/src/propagate.rs b/crates/oxide_fold/src/propagate.rs new file mode 100644 index 00000000..9f435727 --- /dev/null +++ b/crates/oxide_fold/src/propagate.rs @@ -0,0 +1,171 @@ +//! 单向前扫常量传播:per-register known 表 + 汇合点清空 + 就地改写。 +//! +//! pass 形态:对单个函数逐条前扫,维护 `known: Vec>`(按 vreg +//! 号索引)记录各寄存器的已知常量值。命中改写表即就地改写指令(改 op 与操作数 +//! 槽,指令位置不动),不删除任何指令、不重映射 `label_pos`。 +//! +//! 汇合点健全性:`label_pos` 指向的指令下标(控制流汇合点)整体清空 known 表—— +//! 单向前扫状态只在前一条路径上有效,跨汇合的陈旧知识会误折。 +//! +//! 改写后失去消费者的 `LOAD_CONST` 与死 `MOV` 由下游 DCE 清理(保守 DCE 的 use +//! 计数迭代删纯指令、精确 DCE 删死 MOV),本 pass 不产生残留。 + +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; + +use crate::fold_table; + +/// 常量传播与折叠:单函数单向前扫,就地改写。 +/// +/// 返回是否发生改写(仅调试日志用)。不删指令、不重映射 label;改写只改变指令 +/// 的 op 与操作数,不改变跳转结构,CFG 的块划分与边集不变,由下游 DCE 在折叠后 +/// 的指令流上重建 CFG 吸收。 +pub fn fold_function(f: &mut IRFunction) -> bool { + let insts = &mut f.insts; + let constants = &mut f.constants; + let label_pos = &f.label_pos; + + // 预计算 label 目标位图(控制流汇合点)。 + let mut label_targets = vec![false; insts.len()]; + for &pos in label_pos.iter().flatten() { + if pos < insts.len() { + label_targets[pos] = true; + } + } + + // known 表上界 = 函数内 def/use 寄存器号最大值加一。 + let max_reg = insts.iter().fold(0u32, |m, inst| { + let mut mx = m; + if let Some(r) = inst.def_reg() { + mx = mx.max(r); + } + for r in inst.use_regs() { + mx = mx.max(r); + } + mx + }); + + let mut known: Vec> = vec![None; (max_reg + 1) as usize]; + let mut changed = false; + + for (i, inst) in insts.iter_mut().enumerate() { + // 汇合点:label 目标整体清空 known(单向前扫状态只在前一条路径上有效)。 + if label_targets[i] { + known.fill(None); + } + + match inst.op { + // LOAD_CONST 是常量值来源:known[rd] = constants[idx]。 + OpCode::LOAD_CONST => { + if let Some(rd) = inst.def_reg() { + known[rd as usize] = match inst.a { + Operand::Const(idx) => constants.get(idx as usize).cloned(), + _ => None, + }; + } + continue; + } + // MOV 是纯拷贝:known[rd] = known[src]。 + OpCode::MOV => { + if let Some(rd) = inst.def_reg() { + known[rd as usize] = match inst.a { + Operand::Reg(src) => known.get(src as usize).and_then(|c| c.clone()), + _ => None, + }; + } + continue; + } + _ => {} + } + + // A 组:常量折叠(操作数均已知)→ 改写为 LOAD_CONST。 + let folded = match inst.op { + // VOID 无操作数,恒折 Undefined。 + OpCode::VOID => Some(Constant::Undefined), + // 其余一元:操作数须为已知寄存器。 + OpCode::NOT | OpCode::TYPEOF | OpCode::NEG | OpCode::UNARY_PLUS | OpCode::BIT_NOT => { + match inst.a { + Operand::Reg(x) => known + .get(x as usize) + .and_then(|c| c.as_ref()) + .and_then(|c| fold_table::fold_unary(inst.op, c)), + _ => None, + } + } + // 二元算术:两操作数须为已知寄存器。 + OpCode::ADD | OpCode::SUB | OpCode::MUL | OpCode::DIV | OpCode::MOD | OpCode::EXP => { + match (inst.a, inst.b) { + (Operand::Reg(x), Operand::Reg(y)) => match ( + known.get(x as usize).and_then(|c| c.as_ref()), + known.get(y as usize).and_then(|c| c.as_ref()), + ) { + (Some(l), Some(r)) => fold_table::fold_binary(inst.op, l, r), + _ => None, + }, + _ => None, + } + } + _ => None, + }; + + if let Some(value) = folded { + // 新常量入池(去重加溢出守卫);池满时 A 组失效,跳过改写。 + if let Some(idx) = intern(constants, &value) { + *inst = Inst::load_const(inst.rd, idx); + changed = true; + if let Some(rd) = inst.def_reg() { + known[rd as usize] = Some(value); + } + } + } else { + // B 组:逻辑短路(左操作数已知)→ 改写为 MOV。 + let select_left = match (inst.op, inst.a) { + (OpCode::AND | OpCode::OR | OpCode::NULLISH, Operand::Reg(x)) => known + .get(x as usize) + .and_then(|c| c.as_ref()) + .and_then(|c| fold_table::short_circuit(inst.op, c)), + _ => None, + }; + if let Some(select_left) = select_left { + let src = if select_left { inst.a } else { inst.b }; + *inst = Inst::inst_mov(inst.rd, src); + changed = true; + if let (Some(rd), Operand::Reg(s)) = (inst.def_reg(), src) { + known[rd as usize] = known.get(s as usize).and_then(|c| c.clone()); + } + } else { + // 不可折:def 寄存器为不定值。 + if let Some(rd) = inst.def_reg() { + known[rd as usize] = None; + } + } + } + } + + changed +} + +/// 常量池判等:`Number` 臂按位模式比较(区分 `-0.0` 与 `+0.0`,与 emit 层 +/// `ConstantKey` 的 `to_bits` 口径一致),其余臂用派生 `PartialEq`。 +fn const_eq(a: &Constant, b: &Constant) -> bool { + match (a, b) { + (Constant::Number(x), Constant::Number(y)) => x.to_bits() == y.to_bits(), + _ => a == b, + } +} + +/// 新常量入池:线性扫描判等(命中复用既有下标);未命中推尾取新下标;池满 +/// (`u16::MAX`)时返回 `None` 禁用 A 组改写(B 组 MOV 不新增常量,不受影响)。 +fn intern(constants: &mut Vec, c: &Constant) -> Option { + if let Some(idx) = constants.iter().position(|x| const_eq(x, c)) { + return Some(idx as u16); + } + if constants.len() >= u16::MAX as usize { + return None; + } + constants.push(c.clone()); + Some((constants.len() - 1) as u16) +} diff --git a/crates/oxide_fold/tests/fold_tests.rs b/crates/oxide_fold/tests/fold_tests.rs new file mode 100644 index 00000000..7ee45fb1 --- /dev/null +++ b/crates/oxide_fold/tests/fold_tests.rs @@ -0,0 +1,537 @@ +//! 常量折叠 pass 集成测试:手工构造 IR,断言前扫后指令流与常量池形态。 +//! +//! 覆盖四类行为: +//! - 常量传播(LOAD_CONST 与 MOV 的 known 表流动) +//! - 折叠(A 组一元与算术改写为 LOAD_CONST,B 组逻辑短路改写为 MOV) +//! - 汇合点(label 目标整体清空 known,跨汇合不折) +//! - 负零守卫(`-0.0 + 0.0` 得 `+0.0`,不取恒等捷径) + +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::OpCode; +use oxide_fold::fold; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; + +/// 构造空函数并填常量池与指令流。 +fn func(constants: &[Constant; N], insts: Vec) -> IRFunction { + let mut f = IRFunction::new(); + f.constants = constants.to_vec(); + f.insts = insts; + f +} + +/// 取指令操作数:`LOAD_CONST r, idx` 返回 `Some(idx)`,其余返回 `None`。 +fn load_const_idx(inst: &Inst) -> Option { + if inst.op == OpCode::LOAD_CONST { + if let Operand::Const(idx) = inst.a { + return Some(idx); + } + } + None +} + +/// 断言指令是 `LOAD_CONST rd, idx`。 +fn assert_load_const(inst: &Inst, rd: u32, idx: u16) { + assert_eq!(inst.op, OpCode::LOAD_CONST, "expected LOAD_CONST, got {inst:?}"); + assert_eq!(inst.rd, Operand::Reg(rd), "expected rd={rd}, got {inst:?}"); + assert_eq!(load_const_idx(inst), Some(idx), "expected const[{idx}], got {inst:?}"); +} + +/// 断言指令是 `MOV rd, src`。 +fn assert_mov(inst: &Inst, rd: u32, src: u32) { + assert_eq!(inst.op, OpCode::MOV, "expected MOV, got {inst:?}"); + assert_eq!(inst.rd, Operand::Reg(rd), "expected rd={rd}, got {inst:?}"); + assert_eq!(inst.a, Operand::Reg(src), "expected src={src}, got {inst:?}"); +} + +// ── 常量传播 ── + +#[test] +fn propagation_through_mov() { + // LOAD_CONST r1, 1 / MOV r2, r1 / LOAD_CONST r3, 2 / ADD r4, r2, r3 + // 期望:ADD 折为 LOAD_CONST r4, 3(新常量 Int(3))。 + let c1 = Constant::Int(1); + let c2 = Constant::Int(2); + let mut f = func( + &[c1, c2], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::inst_mov(Operand::Reg(2), Operand::Reg(1)), + Inst::load_const(Operand::Reg(3), 1), + Inst::new(OpCode::ADD, Operand::Reg(4), Operand::Reg(2), Operand::Reg(3)), + ], + ); + assert!(fold(&mut f), "应发生改写"); + assert_load_const(&f.insts[3], 4, 2); + assert_eq!(f.constants[2], Constant::Int(3)); +} + +#[test] +fn propagation_chain() { + // LOAD_CONST r1, 5 / MOV r2, r1 / MOV r3, r2 / NEG r4, r3 + // 期望:NEG 折为 LOAD_CONST r4, -5(新常量 Number(-5.0),NEG 恒升 double)。 + let c = Constant::Int(5); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::inst_mov(Operand::Reg(2), Operand::Reg(1)), + Inst::inst_mov(Operand::Reg(3), Operand::Reg(2)), + Inst::new(OpCode::NEG, Operand::Reg(4), Operand::Reg(3), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[3], 4, 1); + assert_eq!(f.constants[1], Constant::Number(-5.0)); +} + +#[test] +fn unknown_operand_not_folded() { + // LOAD_CONST r1, 1 / LOAD_VAR r2, x / ADD r3, r1, r2 + // 期望:r2 未知(LOAD_VAR 不定值),ADD 不折。 + let c = Constant::Int(1); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::LOAD_VAR, Operand::Reg(2), Operand::Reg(7), Operand::None), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "不应发生改写"); + assert_eq!(f.insts[2].op, OpCode::ADD, "ADD 不应被折叠"); + assert_eq!(f.constants.len(), 1, "不应新增常量"); +} + +// ── 折叠:A 组一元 ── + +#[test] +fn fold_not() { + // LOAD_CONST r1, 1 / NOT r2, r1 → Boolean(false) + let c = Constant::Int(1); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::NOT, Operand::Reg(2), Operand::Reg(1), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + assert_eq!(f.constants[1], Constant::Boolean(false)); +} + +#[test] +fn fold_void() { + // VOID r1(无操作数)→ Undefined + let mut f = func(&[], vec![Inst::new(OpCode::VOID, Operand::Reg(1), Operand::None, Operand::None)]); + fold(&mut f); + assert_load_const(&f.insts[0], 1, 0); + assert_eq!(f.constants[0], Constant::Undefined); +} + +#[test] +fn fold_typeof() { + // LOAD_CONST r1, 1 / TYPEOF r2, r1 → "number" + let c = Constant::Int(1); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::TYPEOF, Operand::Reg(2), Operand::Reg(1), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + // "number" 的池键形态(无 FFFD,恒等)。 + assert_eq!(f.constants[1], Constant::String("number".to_string())); +} + +#[test] +fn fold_unary_plus() { + // LOAD_CONST r1, 2 / UNARY_PLUS r2, r1 → Number(2.0) + let c = Constant::Int(2); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::UNARY_PLUS, Operand::Reg(2), Operand::Reg(1), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + assert_eq!(f.constants[1], Constant::Number(2.0)); +} + +#[test] +fn fold_bit_not() { + // LOAD_CONST r1, 5 / BIT_NOT r2, r1 → Int(!5) = Int(-6) + let c = Constant::Int(5); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::BIT_NOT, Operand::Reg(2), Operand::Reg(1), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + assert_eq!(f.constants[1], Constant::Int(!5i32)); +} + +// ── 折叠:A 组二元算术 ── + +#[test] +fn fold_add_int_int() { + // LOAD_CONST r1, 1 / LOAD_CONST r2, 2 / ADD r3, r1, r2 → Int(3) + let (a, b) = (Constant::Int(1), Constant::Int(2)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 2); + assert_eq!(f.constants[2], Constant::Int(3)); +} + +#[test] +fn fold_add_int_overflow_promotes() { + // i32::MAX + 1 溢出 → Number(镜像 int_add)。 + let (a, b) = (Constant::Int(i32::MAX), Constant::Int(1)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 2); + assert_eq!(f.constants[2], Constant::Number(i32::MAX as f64 + 1.0)); +} + +#[test] +fn fold_sub_mul_div_mod() { + // 10 - 4 = 6 / 3 * 5 = 15 / 8 / 2 = 4 / 7 % 3 = 1,均恒 Number。 + let base = [ + Constant::Int(10), + Constant::Int(4), + Constant::Int(3), + Constant::Int(5), + Constant::Int(8), + Constant::Int(2), + Constant::Int(7), + ]; + let mut f = func( + &base, + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::SUB, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + Inst::load_const(Operand::Reg(4), 2), + Inst::load_const(Operand::Reg(5), 3), + Inst::new(OpCode::MUL, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)), + Inst::load_const(Operand::Reg(7), 4), + Inst::load_const(Operand::Reg(8), 5), + Inst::new(OpCode::DIV, Operand::Reg(9), Operand::Reg(7), Operand::Reg(8)), + Inst::load_const(Operand::Reg(10), 6), + Inst::new(OpCode::MOD, Operand::Reg(11), Operand::Reg(10), Operand::Reg(8)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 7); + assert_eq!(f.constants[7], Constant::Number(6.0)); + assert_load_const(&f.insts[5], 6, 8); + assert_eq!(f.constants[8], Constant::Number(15.0)); + assert_load_const(&f.insts[8], 9, 9); + assert_eq!(f.constants[9], Constant::Number(4.0)); + assert_load_const(&f.insts[10], 11, 10); + assert_eq!(f.constants[10], Constant::Number(1.0)); +} + +#[test] +fn fold_div_by_zero_not_folded() { + // 1 / 0 除零 → 不折(保持运行时指令为除零编码的唯一来源)。 + let (a, b) = (Constant::Int(1), Constant::Int(0)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::DIV, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "除零不应折叠"); + assert_eq!(f.insts[2].op, OpCode::DIV); + assert_eq!(f.constants.len(), 2); +} + +#[test] +fn fold_exp_negative_not_folded() { + // 2 ** -1 负指数 → 不折。 + let (a, b) = (Constant::Int(2), Constant::Int(-1)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::EXP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "负指数不应折叠"); + assert_eq!(f.insts[2].op, OpCode::EXP); +} + +#[test] +fn fold_exp_positive() { + // 2 ** 10 = 1024 → Number。 + let (a, b) = (Constant::Int(2), Constant::Int(10)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::EXP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 2); + assert_eq!(f.constants[2], Constant::Number(1024.0)); +} + +#[test] +fn fold_add_bigint() { + // 1n + 2n = 3n → BigInt。 + let (a, b) = ( + Constant::BigInt(num_bigint::BigInt::from(1)), + Constant::BigInt(num_bigint::BigInt::from(2)), + ); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 2); + assert_eq!(f.constants[2], Constant::BigInt(num_bigint::BigInt::from(3))); +} + +#[test] +fn fold_add_bigint_mixed_not_folded() { + // 1n + 1 BigInt 混合 → 不折(运行期抛 TypeError)。 + let (a, b) = ( + Constant::BigInt(num_bigint::BigInt::from(1)), + Constant::Int(1), + ); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "BigInt 混合不应折叠"); + assert_eq!(f.insts[2].op, OpCode::ADD); +} + +// ── 折叠:B 组逻辑短路 ── + +#[test] +fn fold_and_truthy_selects_right() { + // LOAD_CONST r1, 1(真)/ AND r2, r1, r3 → MOV r2, r3。 + let c = Constant::Int(1); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::AND, Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)), + ], + ); + fold(&mut f); + assert_mov(&f.insts[1], 2, 3); + assert_eq!(f.constants.len(), 1, "MOV 不新增常量"); +} + +#[test] +fn fold_and_falsy_selects_left() { + // LOAD_CONST r1, 0(假)/ AND r2, r1, r3 → MOV r2, r1。 + let c = Constant::Int(0); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::AND, Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)), + ], + ); + fold(&mut f); + assert_mov(&f.insts[1], 2, 1); +} + +#[test] +fn fold_or_falsy_selects_right() { + // LOAD_CONST r1, 0(假)/ OR r2, r1, r3 → MOV r2, r3。 + let c = Constant::Int(0); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::OR, Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)), + ], + ); + fold(&mut f); + assert_mov(&f.insts[1], 2, 3); +} + +#[test] +fn fold_nullish_nullish_selects_right() { + // LOAD_CONST r1, null / NULLISH r2, r1, r3 → MOV r2, r3。 + let c = Constant::Null; + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::NULLISH, Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)), + ], + ); + fold(&mut f); + assert_mov(&f.insts[1], 2, 3); +} + +#[test] +fn fold_nullish_non_nullish_selects_left() { + // LOAD_CONST r1, 5(非 nullish)/ NULLISH r2, r1, r3 → MOV r2, r1。 + let c = Constant::Int(5); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::NULLISH, Operand::Reg(2), Operand::Reg(1), Operand::Reg(3)), + ], + ); + fold(&mut f); + assert_mov(&f.insts[1], 2, 1); +} + +// ── 汇合点健全性 ── + +#[test] +fn merge_point_clears_known() { + // LOAD_CONST r1, 1 / JMP label0 / LOAD_CONST r1, 99 / label0: ADD r2, r1, r1 + // label0 是汇合点:整体清空 known,r1 未知 → ADD 不折。 + let (a, b) = (Constant::Int(1), Constant::Int(99)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::jmp(0), + Inst::load_const(Operand::Reg(1), 1), + Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1)), + ], + ); + f.label_pos = vec![Some(3)]; + f.label_count = 1; + assert!(!fold(&mut f), "汇合点后不应折叠"); + assert_eq!(f.insts[3].op, OpCode::ADD, "跨汇合的 ADD 不应被折叠"); + assert_eq!(f.constants.len(), 2, "不应新增常量"); +} + +#[test] +fn merge_point_does_not_block_same_path() { + // label0: LOAD_CONST r1, 1 / ADD r2, r1, r1(label 后同路径) + // label 清空 known,但其后的 LOAD_CONST 重新定值 r1 → ADD 折。 + let c = Constant::Int(1); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1)), + ], + ); + f.label_pos = vec![Some(0)]; + f.label_count = 1; + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + assert_eq!(f.constants[1], Constant::Int(2)); +} + +// ── 负零守卫 ── + +#[test] +fn negative_zero_add_yields_positive_zero() { + // LOAD_CONST r1, -0.0 / LOAD_CONST r2, 0.0 / ADD r3, r1, r2 + // 期望:折为 Number(+0.0)(符号位清零),不得取恒等捷径得 -0.0。 + let (a, b) = (Constant::Number(-0.0), Constant::Number(0.0)); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + // 折值 +0.0 经位精确判等去重,命中既有 +0.0 池条目(下标 1),不得误命中 + // 下标 0 的 -0.0 条目。 + assert_load_const(&f.insts[2], 3, 1); + // 断言符号位:+0.0 的位模式为 0,-0.0 为 0x8000_0000_0000_0000。 + match &f.constants[1] { + Constant::Number(d) => assert_eq!(d.to_bits(), 0x0000_0000_0000_0000, "应为 +0.0"), + other => panic!("期望 Number,got {other:?}"), + } +} + +#[test] +fn negative_zero_neg_yields_positive_zero() { + // LOAD_CONST r1, -0.0 / NEG r2, r1 → Number(+0.0)(NEG 翻转符号位,-(-0.0) = +0.0)。 + let c = Constant::Number(-0.0); + let mut f = func( + &[c], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::new(OpCode::NEG, Operand::Reg(2), Operand::Reg(1), Operand::None), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[1], 2, 1); + match &f.constants[1] { + Constant::Number(d) => assert_eq!(d.to_bits(), 0x0000_0000_0000_0000, "NEG(-0.0) 应为 +0.0"), + other => panic!("期望 Number,got {other:?}"), + } +} + +// ── 确定性 ── + +#[test] +fn fold_is_deterministic() { + // 同一源连续折叠八次,指令流与常量池指纹全等。 + let build = || { + func( + &[Constant::Int(1), Constant::Int(2)], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::NOT, Operand::Reg(4), Operand::Reg(3), Operand::None), + ], + ) + }; + let mut f = build(); + fold(&mut f); + let fingerprint = |f: &IRFunction| (f.insts.clone(), f.constants.clone()); + let first = fingerprint(&f); + for _ in 0..7 { + let mut g = build(); + fold(&mut g); + assert_eq!(fingerprint(&g), first, "折叠结果应确定"); + } +}