feat: 新增 IR 层常量传播与折叠 pass

This commit is contained in:
2026-10-12 14:43:58 +00:00
parent b7b6d43d05
commit 66e240ad00
10 changed files with 1044 additions and 21 deletions
+198
View File
@@ -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<JsValue> {
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<Constant> {
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<u16> = 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<Constant> {
// 两操作数均须数值;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<bool> {
match op {
OpCode::AND => Some(!is_truthy(l)),
OpCode::OR => Some(is_truthy(l)),
OpCode::NULLISH => Some(!is_nullish(l)),
_ => None,
}
}