diff --git a/Cargo.lock b/Cargo.lock index 4013b798..ad11ee67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.16.21" +version = "0.16.22" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.16.21" +version = "0.16.22" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.16.21" +version = "0.16.22" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.16.21" +version = "0.16.22" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.16.21" +version = "0.16.22" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.16.21" +version = "0.16.22" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.16.21" +version = "0.16.22" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.16.21" +version = "0.16.22" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.16.21" +version = "0.16.22" [[package]] name = "oxide_parser" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.16.21" +version = "0.16.22" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.16.21" +version = "0.16.22" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.16.21" +version = "0.16.22" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.16.21" +version = "0.16.22" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 5456cbcc..28347425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.16.21" +version = "0.16.22" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_fold/src/fold_table.rs b/crates/oxide_fold/src/fold_table.rs index 430ab7b1..e5d7d0b5 100644 --- a/crates/oxide_fold/src/fold_table.rs +++ b/crates/oxide_fold/src/fold_table.rs @@ -1,9 +1,11 @@ -//! 折叠表:一元与算术常量折叠(A 组)加逻辑短路选支(B 组)。 +//! 折叠表:一元与算术常量折叠(A 组)、位运算与比较折叠(C 组)加逻辑短路 +//! 选支(B 组)。 //! //! 折值编码逐运算符镜像运行时产物编码,保证物化后与运行时执行结果逐字节 //! 一致:折值计算复用 `oxide_runtime_api::coercion` 的纯函数族(`to_number` / -//! `to_int32` / `to_boolean`),与 emit 层字面量折叠同源,杜绝两层折叠各自 -//! 镜像运行时语义的漂移风险。 +//! `to_int32` / `to_uint32` / `to_boolean` / `relational_compare` / +//! `abstract_eq_primitives` / `strict_equality`),与 emit 层字面量折叠同源, +//! 杜绝两层折叠各自镜像运行时语义的漂移风险。 //! //! 语义守卫: //! - 算术二元(ADD/SUB/MUL/DIV/MOD/EXP)任一操作数非数值不折;BigInt 混合 @@ -15,13 +17,19 @@ //! (镜像运行时 `int_add`);其余组合 f64 加(不做恒等捷径,负零操作数 //! 经实加得 `+0.0`)。 //! - UNARY_PLUS 作用 BigInt 不折(运行期抛 TypeError)。 +//! - C 组位运算与比较:双操作数须均在 `Int`/`Number`/`Boolean`/`Null` 四变体 +//! 域,`BigInt`/`String`/`Undefined` 操作数不折(与共享表 `const_to_jsvalue` +//! 四臂口径一致,字符串比较路径降为后续任务)。 //! //! B 组逻辑短路:结果恒为某一操作数的原值(无类型转换,任何类型安全), //! 仅左操作数已知即可选支,右操作数类型不限。 use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; -use oxide_runtime_api::{to_boolean, to_int32, to_number}; +use oxide_runtime_api::{ + abstract_eq_primitives, relational_compare, strict_equality, to_boolean, to_int32, to_number, + to_uint32, +}; use oxide_types::string_forge::encode_key; use oxide_types::value::JsValue; @@ -196,3 +204,82 @@ pub fn short_circuit(op: OpCode, l: &Constant) -> Option { _ => None, } } + +/// C 组操作数域守卫:仅 `Int`/`Number`/`Boolean`/`Null` 四变体可折。 +/// +/// 传播映射可承载 `BigInt`/`String`/`Undefined` 常量(来自 `LOAD_CONST`), +/// 这三类操作数不折(与共享表 `const_to_jsvalue` 四臂口径一致)。 +fn is_foldable_c_operand(c: &Constant) -> bool { + matches!(c, Constant::Int(_) | Constant::Number(_) | Constant::Boolean(_) | Constant::Null) +} + +/// C 组位运算与比较折叠:按操作码计算折值,守卫命中返回 `None`。 +/// +/// 折值编码逐运算符镜像运行时产物: +/// - `BIT_AND`/`BIT_OR`/`BIT_XOR`:`Int`(`ToInt32` 按位与/或/异或) +/// - `SHL`/`SHR`:`Int`(`ToInt32` 左移/右移,移位量 `ToUint32` 与 0x1F) +/// - `USHR`:结果不超 i32::MAX 保 `Int`,否则升 `Number`(镜像 +/// `write_ushr_result`) +/// - `LT`/`GT`/`LTE`/`GTE`:`Boolean`(镜像 `relational_compare`,GT/LTE +/// 交换操作数,LTE/GTE 取反,`None` 归 false) +/// - `EQ`/`NEQ`:`Boolean`(镜像 `abstract_eq_primitives`) +/// - `STRICT_EQ`/`STRICT_NEQ`:`Boolean`(镜像 `strict_equality`) +/// +/// 操作数域守卫:双操作数须均在 `Int`/`Number`/`Boolean`/`Null` 四变体域, +/// `BigInt`/`String`/`Undefined` 操作数不折(命中即返回 `None`)。 +pub fn fold_c_group(op: OpCode, l: &Constant, r: &Constant) -> Option { + // 操作数域守卫先行:传播映射可承载 BigInt/String/Undefined 常量, + // C 组表只接受四变体域。 + if !is_foldable_c_operand(l) || !is_foldable_c_operand(r) { + return None; + } + let (lv, rv) = (const_to_jsvalue(l)?, const_to_jsvalue(r)?); + match op { + OpCode::BIT_AND | OpCode::BIT_OR | OpCode::BIT_XOR => { + let (a, b) = (to_int32(lv), to_int32(rv)); + Some(match op { + OpCode::BIT_AND => Constant::Int(a & b), + OpCode::BIT_OR => Constant::Int(a | b), + _ => Constant::Int(a ^ b), + }) + } + OpCode::SHL | OpCode::SHR => { + let a = to_int32(lv); + let shift = to_uint32(rv) & 0x1F; + Some(match op { + OpCode::SHL => Constant::Int(a << shift), + _ => Constant::Int(a >> shift), + }) + } + OpCode::USHR => { + let a = to_int32(lv) as u32; + let shift = to_uint32(rv) & 0x1F; + let result = a >> shift; + // 结果不超 i32::MAX 保 Int,否则升 Number(镜像 write_ushr_result)。 + if result <= i32::MAX as u32 { + Some(Constant::Int(result as i32)) + } else { + Some(Constant::Number(result as f64)) + } + } + OpCode::LT | OpCode::GT | OpCode::LTE | OpCode::GTE => { + // GT/LTE 交换操作数,LTE/GTE 取反(镜像运行时派发口径)。 + let (a, b) = match op { + OpCode::GT | OpCode::LTE => (rv, lv), + _ => (lv, rv), + }; + let cmp = relational_compare(a, b).unwrap_or(false); + let negate = matches!(op, OpCode::LTE | OpCode::GTE); + Some(Constant::Boolean(if negate { !cmp } else { cmp })) + } + OpCode::EQ | OpCode::NEQ => { + let eq = abstract_eq_primitives(lv, rv); + Some(Constant::Boolean(if op == OpCode::NEQ { !eq } else { eq })) + } + OpCode::STRICT_EQ | OpCode::STRICT_NEQ => { + let eq = strict_equality(lv, rv); + Some(Constant::Boolean(if op == OpCode::STRICT_NEQ { !eq } else { eq })) + } + _ => None, + } +} diff --git a/crates/oxide_fold/src/lib.rs b/crates/oxide_fold/src/lib.rs index 644c95c8..214eac5e 100644 --- a/crates/oxide_fold/src/lib.rs +++ b/crates/oxide_fold/src/lib.rs @@ -3,7 +3,8 @@ //! `fold` 对每个函数(递归 nested,每个 nested 函数有独立常量池与指令流,独立前扫) //! 运行单向前扫:维护 per-register known 表记录各虚拟寄存器的已知常量值,命中 //! 改写表即就地改写——A 组一元与算术常量折叠改写为 `LOAD_CONST`(新常量入池), -//! B 组逻辑短路改写为 `MOV`(无新常量)。 +//! C 组位运算与比较常量折叠改写为 `LOAD_CONST`(新常量入池),B 组逻辑短路 +//! 改写为 `MOV`(无新常量)。 //! //! 不变量: //! - 不删指令、不重映射 `label_pos`:改写只改变指令的 op 与操作数,不改变跳转 @@ -12,12 +13,13 @@ //! 跨汇合的陈旧知识不用于折叠。 //! - 负零守卫:算术折叠做实际运算(f64 加),不取恒等捷径——`-0.0 + 0.0` 得 //! `+0.0`(`Object.is` 可区分)。 -//! - 池溢出守卫:常量池满(`u16::MAX`)时 A 组改写禁用(不新增常量),B 组 -//! (MOV)不受影响。 +//! - 池溢出守卫:常量池满(`u16::MAX`)时 A 组与 C 组改写禁用(不新增常量), +//! B 组(MOV)不受影响;`const_overflow` 已被 emit 层置位时整个 pass 跳过。 //! //! 折值计算复用 `oxide_runtime_api::coercion` 的纯函数族(`to_number` / -//! `to_int32` / `to_boolean`),与 emit 层字面量折叠同源,杜绝两层折叠各自镜像 -//! 运行时语义的漂移风险。 +//! `to_int32` / `to_uint32` / `to_boolean` / `relational_compare` / +//! `abstract_eq_primitives` / `strict_equality`),与 emit 层字面量折叠同源, +//! 杜绝两层折叠各自镜像运行时语义的漂移风险。 mod fold_log; mod fold_table; diff --git a/crates/oxide_fold/src/propagate.rs b/crates/oxide_fold/src/propagate.rs index b48a6e12..d72e6e45 100644 --- a/crates/oxide_fold/src/propagate.rs +++ b/crates/oxide_fold/src/propagate.rs @@ -23,7 +23,15 @@ use crate::fold_table; /// 返回是否发生改写(仅调试日志用)。不删指令、不重映射 label;改写只改变指令 /// 的 op 与操作数,不改变跳转结构,CFG 的块划分与边集不变,由下游 DCE 在折叠后 /// 的指令流上重建 CFG 吸收。 +/// +/// pass 级前置:`const_overflow` 已被 emit 层置位时程序已知无效,直接返回不改写 +/// (无折叠无效程序的意义,且加常量会雪上加霜)。 pub fn fold_function(f: &mut IRFunction) -> bool { + // pass 级前置:const_overflow 已置位即程序已知无效,跳过整个 pass。 + if f.const_overflow { + return false; + } + let insts = &mut f.insts; let constants = &mut f.constants; let label_pos = &f.label_pos; @@ -106,6 +114,20 @@ pub fn fold_function(f: &mut IRFunction) -> bool { _ => None, } } + // C 组位运算与比较:两操作数须为已知寄存器且均在四变体域 + // (BigInt/String/Undefined 操作数由表内守卫不折)。 + OpCode::BIT_AND | OpCode::BIT_OR | OpCode::BIT_XOR | OpCode::SHL | OpCode::SHR + | OpCode::USHR | OpCode::LT | OpCode::GT | OpCode::LTE | OpCode::GTE | OpCode::EQ + | OpCode::NEQ | OpCode::STRICT_EQ | OpCode::STRICT_NEQ => 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_c_group(inst.op, l, r), + _ => None, + }, + _ => None, + }, _ => None, }; @@ -156,7 +178,8 @@ fn const_eq(a: &Constant, b: &Constant) -> bool { } /// 新常量入池:线性扫描判等(命中复用既有下标);未命中推尾取新下标;池满 -/// (`u16::MAX`)时返回 `None` 禁用 A 组改写(B 组 MOV 不新增常量,不受影响)。 +/// (`u16::MAX`)时返回 `None` 禁用 A 组与 C 组改写(不置 `const_overflow`, +/// 程序在 pass 前是有效的;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); diff --git a/crates/oxide_fold/tests/fold_tests.rs b/crates/oxide_fold/tests/fold_tests.rs index 24587dbf..92e5305d 100644 --- a/crates/oxide_fold/tests/fold_tests.rs +++ b/crates/oxide_fold/tests/fold_tests.rs @@ -1,10 +1,13 @@ //! 常量折叠 pass 集成测试:手工构造 IR,断言前扫后指令流与常量池形态。 //! -//! 覆盖四类行为: +//! 覆盖五类行为: //! - 常量传播(LOAD_CONST 与 MOV 的 known 表流动) -//! - 折叠(A 组一元与算术改写为 LOAD_CONST,B 组逻辑短路改写为 MOV) +//! - 折叠(A 组一元与算术改写为 LOAD_CONST,C 组位运算与比较改写为 +//! LOAD_CONST,B 组逻辑短路改写为 MOV) //! - 汇合点(label 目标整体清空 known,跨汇合不折) //! - 负零守卫(`-0.0 + 0.0` 得 `+0.0`,不取恒等捷径) +//! - 池溢出守卫(池满时 A 组与 C 组不折、B 组照折;`const_overflow` +//! 置位时整个 pass 跳过) use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; @@ -506,6 +509,200 @@ fn negative_zero_neg_yields_positive_zero() { } } +// ── 折叠:C 组位运算 ── + +#[test] +fn fold_bit_and_or_xor() { + // 12 & 10 = 8 / 12 | 10 = 14 / 12 ^ 10 = 6,均恒 Int。 + let (a, b) = (Constant::Int(12), 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::BIT_AND, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::BIT_OR, Operand::Reg(4), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::BIT_XOR, Operand::Reg(5), Operand::Reg(1), Operand::Reg(2)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 2); + assert_eq!(f.constants[2], Constant::Int(8)); + assert_load_const(&f.insts[3], 4, 3); + assert_eq!(f.constants[3], Constant::Int(14)); + assert_load_const(&f.insts[4], 5, 4); + assert_eq!(f.constants[4], Constant::Int(6)); +} + +#[test] +fn fold_shifts() { + // 1 << 34 = 1 << 2 = 4(移位量与 0x1F 掩码)/ + // -2147483648 >> 1 = -1073741824(算术右移)/ + // -1 >>> 0 = 4294967295(结果超 i32::MAX,升 Number)。 + let (a, b) = (Constant::Int(1), Constant::Int(34)); + let (c, d) = (Constant::Int(i32::MIN), Constant::Int(1)); + let (e, f2) = (Constant::Int(-1), Constant::Int(0)); + let mut f = func( + &[a, b, c, d, e, f2], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::SHL, 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::SHR, 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::USHR, Operand::Reg(9), Operand::Reg(7), Operand::Reg(8)), + ], + ); + fold(&mut f); + assert_load_const(&f.insts[2], 3, 6); + assert_eq!(f.constants[6], Constant::Int(4)); + assert_load_const(&f.insts[5], 6, 7); + assert_eq!(f.constants[7], Constant::Int(-1_073_741_824)); + assert_load_const(&f.insts[8], 9, 8); + assert_eq!(f.constants[8], Constant::Number(4_294_967_295.0)); +} + +// ── 折叠:C 组比较 ── + +#[test] +fn fold_comparisons() { + // 1 < 2 = true / 1 > 2 = false / 1 <= 1 = true / 2 >= 3 = false / + // 1 == 1.0 = true / 1 != 2 = true / 1 === 1.0 = true / 1 !== 2 = true / + // NaN < 1 = false(关系比较对 NaN 返回 None,归 false)。 + let (a, b, c, d) = (Constant::Int(1), Constant::Int(2), Constant::Number(1.0), Constant::Int(3)); + let (e, f2) = (Constant::Number(f64::NAN), Constant::Int(1)); + let mut f = func( + &[a, b, c, d, e, f2], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::LT, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::GT, Operand::Reg(4), Operand::Reg(1), Operand::Reg(2)), + Inst::load_const(Operand::Reg(5), 0), + Inst::new(OpCode::LTE, Operand::Reg(6), Operand::Reg(5), Operand::Reg(1)), + Inst::load_const(Operand::Reg(7), 1), + Inst::load_const(Operand::Reg(8), 3), + Inst::new(OpCode::GTE, Operand::Reg(9), Operand::Reg(7), Operand::Reg(8)), + Inst::load_const(Operand::Reg(10), 2), + Inst::new(OpCode::EQ, Operand::Reg(11), Operand::Reg(1), Operand::Reg(10)), + Inst::new(OpCode::NEQ, Operand::Reg(12), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::STRICT_EQ, Operand::Reg(13), Operand::Reg(1), Operand::Reg(10)), + Inst::new(OpCode::STRICT_NEQ, Operand::Reg(14), Operand::Reg(1), Operand::Reg(2)), + Inst::load_const(Operand::Reg(15), 4), + Inst::new(OpCode::LT, Operand::Reg(16), Operand::Reg(15), Operand::Reg(1)), + ], + ); + fold(&mut f); + // 池原有 6 条:Boolean(true) 首落位下标 6、Boolean(false) 下标 7, + // 后续同值折叠复用。 + assert_load_const(&f.insts[2], 3, 6); + assert_eq!(f.constants[6], Constant::Boolean(true)); + assert_load_const(&f.insts[3], 4, 7); + assert_eq!(f.constants[7], Constant::Boolean(false)); + assert_load_const(&f.insts[5], 6, 6); + assert_load_const(&f.insts[8], 9, 7); + assert_load_const(&f.insts[10], 11, 6); + assert_load_const(&f.insts[11], 12, 6); + assert_load_const(&f.insts[12], 13, 6); + assert_load_const(&f.insts[13], 14, 6); + assert_load_const(&f.insts[15], 16, 7); + assert_eq!(f.constants.len(), 8, "同值折叠应复用池下标"); +} + +// ── C 组操作数域守卫(BigInt/String/Undefined 不折) ── + +#[test] +fn c_group_bigint_operand_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::BIT_AND, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "BigInt 操作数不应折叠"); + assert_eq!(f.insts[2].op, OpCode::BIT_AND); + assert_eq!(f.constants.len(), 2, "不应新增常量"); +} + +#[test] +fn c_group_string_operand_not_folded() { + // "1" < "2" → 不折(String 操作数不折,字符串比较路径为后续任务)。 + let (a, b) = (Constant::String("1".to_string()), Constant::String("2".to_string())); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::LT, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "String 操作数不应折叠"); + assert_eq!(f.insts[2].op, OpCode::LT); +} + +#[test] +fn c_group_undefined_operand_not_folded() { + // 1 < undefined → 不折(Undefined 操作数不折,与共享表四臂口径一致)。 + let (a, b) = (Constant::Int(1), Constant::Undefined); + let mut f = func( + &[a, b], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::LT, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)), + ], + ); + assert!(!fold(&mut f), "Undefined 操作数不应折叠"); + assert_eq!(f.insts[2].op, OpCode::LT); +} + +// ── 池溢出守卫 ── + +#[test] +fn pool_full_disables_a_and_c_groups() { + // 池 65535 条(Int(0)..Int(65534))即满:NEG(A 组)与 EQ(C 组)折值 + // 不在池内且无空间推尾 → 不折;AND(B 组)MOV 不新增常量 → 照折。 + let mut f = IRFunction::new(); + f.constants = (0..65535).map(Constant::Int).collect(); + f.insts = vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 65534), + Inst::new(OpCode::NEG, Operand::Reg(3), Operand::Reg(1), Operand::None), + Inst::new(OpCode::EQ, Operand::Reg(4), Operand::Reg(1), Operand::Reg(2)), + Inst::new(OpCode::AND, Operand::Reg(5), Operand::Reg(1), Operand::Reg(2)), + ]; + fold(&mut f); + assert_eq!(f.insts[2].op, OpCode::NEG, "池满时 A 组不应折叠"); + assert_eq!(f.insts[3].op, OpCode::EQ, "池满时 C 组不应折叠"); + assert_mov(&f.insts[4], 5, 1); + assert_eq!(f.constants.len(), 65535, "池满时不应新增常量"); +} + +#[test] +fn const_overflow_skips_pass() { + // const_overflow 已置位(emit 层):整个 pass 跳过,不改写。 + 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)), + ], + ); + f.const_overflow = true; + assert!(!fold(&mut f), "const_overflow 置位时不应改写"); + assert_eq!(f.insts[2].op, OpCode::ADD); + assert_eq!(f.constants.len(), 2); +} + // ── 确定性 ── #[test] @@ -532,3 +729,31 @@ fn fold_is_deterministic() { assert_eq!(fingerprint(&g), first, "折叠结果应确定"); } } + +#[test] +fn c_group_fold_is_deterministic() { + // 同一源(C 组位运算加比较)连续折叠八次,指令流与常量池指纹全等。 + let build = || { + func( + &[Constant::Int(12), Constant::Int(10), Constant::Int(-1), Constant::Int(0)], + vec![ + Inst::load_const(Operand::Reg(1), 0), + Inst::load_const(Operand::Reg(2), 1), + Inst::new(OpCode::BIT_XOR, 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::USHR, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)), + Inst::new(OpCode::LT, Operand::Reg(7), Operand::Reg(3), Operand::Reg(6)), + ], + ) + }; + 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, "折叠结果应确定"); + } +}