perf: 数值域双常量折叠加语义守卫

This commit is contained in:
2026-10-12 12:47:12 +00:00
parent 3cabfb70f9
commit 84bcebee53
9 changed files with 461 additions and 55 deletions
Generated
+21 -20
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1257,7 +1257,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1271,7 +1271,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1279,6 +1279,7 @@ dependencies = [
"oxide_ir",
"oxide_log",
"oxide_parser",
"oxide_runtime_api",
"oxide_types",
"regex",
"regress",
@@ -1286,7 +1287,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1298,7 +1299,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1307,7 +1308,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"bumpalo",
"dashmap",
@@ -1321,7 +1322,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1333,7 +1334,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"tracing",
"tracing-appender",
@@ -1342,11 +1343,11 @@ dependencies = [
[[package]]
name = "oxide_net"
version = "0.16.14"
version = "0.16.15"
[[package]]
name = "oxide_parser"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1361,7 +1362,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1377,7 +1378,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"num-bigint",
"num-traits",
@@ -1390,7 +1391,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1410,7 +1411,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"bumpalo",
"dashmap",
@@ -1423,7 +1424,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.16.14"
version = "0.16.15"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -24,7 +24,7 @@ members = [
]
[workspace.package]
version = "0.16.14"
version = "0.16.15"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+1
View File
@@ -13,6 +13,7 @@ oxc_allocator.workspace = true
oxide_bytecode = { path = "../oxide_bytecode" }
oxide_ir = { path = "../oxide_ir" }
oxide_log = { path = "../oxide_log" }
oxide_runtime_api = { path = "../oxide_runtime_api" }
regex.workspace = true
regress.workspace = true
num-bigint = "0.4"
+190
View File
@@ -0,0 +1,190 @@
//! 双常量折叠:两操作数均为字面量且通过语义守卫时,编译期折成单常量,
//! 发射单条 `LOAD_CONST`。折值编码逐运算符镜像运行时指令的产物编码,
//! 保证物化后与运行时执行结果逐字节一致。
//!
//! 三条语义守卫:任一操作数为 BigInt 字面量不折(BigInt 混合运行期抛
//! TypeError,BigInt 与 BigInt 有独立指令族);`DIV`/`MOD` 除数为零不折、
//! `EXP` 指数为负不折(保持运行时指令为除零/负指数编码的唯一来源)。
//! 字符串字面量不折(字符串域是独立任务)。
use oxide_bytecode::module::Constant;
use oxide_parser::{BinaryOperator, Expression, UnaryOperator};
use oxide_runtime_api::{
abstract_eq_primitives, relational_compare, strict_equality, to_int32, to_number, to_uint32,
};
use oxide_types::value::JsValue;
use crate::is_int_literal;
/// 字面量表达式提取常量:剥括号与一元负号后,数字(`is_int_literal` 分派
/// `Int`/`Number`)、布尔、null 三类;BigInt 字面量、字符串字面量、RegExp 与
/// 非字面量一律 `None`。
///
/// 一元负号只沿 `UnaryNegation` 链剥(含嵌套负号与括号),其余一元运算符
/// (`!`/`typeof`/`void`/`~`/`+`/`delete`)终止剥壳不折。
pub(crate) fn literal_constant(expr: &Expression) -> Option<Constant> {
let (inner, negated) = peel_negation(expr);
match inner {
Expression::NumericLiteral(n) => {
let value = if negated { -n.value } else { n.value };
if is_int_literal(value) {
Some(Constant::Int(value as i32))
} else {
Some(Constant::Number(value))
}
}
Expression::BooleanLiteral(b) if !negated => Some(Constant::Boolean(b.value)),
Expression::NullLiteral(_) if !negated => Some(Constant::Null),
_ => None,
}
}
/// 剥括号与一元负号链:返回内层表达式与负号层数奇偶(奇数层取负)。
fn peel_negation<'a>(expr: &'a Expression<'a>) -> (&'a Expression<'a>, bool) {
let mut e = expr;
let mut negated = false;
loop {
while let Expression::ParenthesizedExpression(p) = e {
e = &p.expression;
}
if let Expression::UnaryExpression(u) = e {
if u.operator == UnaryOperator::UnaryNegation {
negated = !negated;
e = &u.argument;
continue;
}
}
break;
}
(e, negated)
}
/// 常量转 `JsValue`(纯 NaN-box 构造,零分配):`Int`/`Number`/`Boolean`/`Null`
/// 四臂,其余臂不可达(BigInt/String/Undefined 已被守卫排除)。
fn const_to_jsvalue(c: &Constant) -> JsValue {
match c {
Constant::Int(i) => JsValue::int(*i),
Constant::Number(d) => JsValue::float(*d),
Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(),
_ => unreachable!("折叠操作数守卫已排除 BigInt/String/Undefined"),
}
}
/// 常量的数值(`Int`/`Number`/`Boolean`/`Null` 均走 `ToNumber` 口径)。
fn const_to_number(c: &Constant) -> f64 {
to_number(const_to_jsvalue(c))
}
/// 双常量折叠分派:按操作码计算折值,守卫命中返回 `None`。
///
/// 折值编码逐运算符镜像运行时产物:
/// - `ADD`:int+int 经 `checked_add` 在 i32 内保 `Int`,溢出升 `Number`(镜像
/// `int_add`);其余组合 f64 加(镜像 `dispatch_add` 的 `to_number` 路径)
/// - `SUB`/`MUL`/`DIV`/`MOD`:恒 `Number`(镜像 `binary_arith!` 的 `as f64`)
/// - `EXP`:恒 `Number` 的 `powf`(镜像 `exp_result`)
/// - `BIT_AND`/`BIT_OR`/`BIT_XOR`/`SHL`/`SHR`:`Int`(镜像 `to_int32`)
/// - `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` 原始值核心);
/// `STRICT_EQ`/`STRICT_NEQ`:`Boolean`(镜像 `strict_equality`)
///
/// 守卫:`DIV`/`MOD` 除数为零(`Int(0)` 与 `Number(±0.0)` 均判零)、`EXP`
/// 指数为负(`Int`/`Number` 负值均判负)返回 `None`,落回运行时路径。
pub(crate) fn fold_binary(op: BinaryOperator, l: &Constant, r: &Constant) -> Option<Constant> {
match op {
BinaryOperator::Addition => {
// 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)));
}
Some(Constant::Number(const_to_number(l) + const_to_number(r)))
}
BinaryOperator::Subtraction
| BinaryOperator::Multiplication
| BinaryOperator::Division
| BinaryOperator::Remainder => {
// 除零守卫:Int(0) 与 Number(±0.0) 均判零(v == 0.0 对 -0.0 亦真)。
if matches!(op, BinaryOperator::Division | BinaryOperator::Remainder) && const_to_number(r) == 0.0 {
return None;
}
// 恒 f64(镜像 binary_arith! 宏的 as f64)。
let (ln, rn) = (const_to_number(l), const_to_number(r));
Some(match op {
BinaryOperator::Subtraction => Constant::Number(ln - rn),
BinaryOperator::Multiplication => Constant::Number(ln * rn),
BinaryOperator::Division => Constant::Number(ln / rn),
_ => Constant::Number(ln % rn),
})
}
BinaryOperator::Exponential => {
// 负指数守卫:Int 与 Number 负值均判负。
if const_to_number(r) < 0.0 {
return None;
}
// 恒 f64 powf(镜像 exp_result)。
Some(Constant::Number(const_to_number(l).powf(const_to_number(r))))
}
BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => {
let (a, b) = (to_int32(const_to_jsvalue(l)), to_int32(const_to_jsvalue(r)));
Some(match op {
BinaryOperator::BitwiseAnd => Constant::Int(a & b),
BinaryOperator::BitwiseOR => Constant::Int(a | b),
_ => Constant::Int(a ^ b),
})
}
BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => {
let a = to_int32(const_to_jsvalue(l));
let shift = to_uint32(const_to_jsvalue(r)) & 0x1F;
Some(match op {
BinaryOperator::ShiftLeft => Constant::Int(a << shift),
_ => Constant::Int(a >> shift),
})
}
BinaryOperator::ShiftRightZeroFill => {
let a = to_int32(const_to_jsvalue(l)) as u32;
let shift = to_uint32(const_to_jsvalue(r)) & 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))
}
}
BinaryOperator::LessThan => relational(l, r, false, false),
BinaryOperator::GreaterThan => relational(l, r, true, false),
BinaryOperator::LessEqualThan => relational(l, r, true, true),
BinaryOperator::GreaterEqualThan => relational(l, r, false, true),
BinaryOperator::Equality => {
Some(Constant::Boolean(abstract_eq_primitives(const_to_jsvalue(l), const_to_jsvalue(r))))
}
BinaryOperator::Inequality => {
Some(Constant::Boolean(!abstract_eq_primitives(const_to_jsvalue(l), const_to_jsvalue(r))))
}
BinaryOperator::StrictEquality => {
Some(Constant::Boolean(strict_equality(const_to_jsvalue(l), const_to_jsvalue(r))))
}
BinaryOperator::StrictInequality => {
Some(Constant::Boolean(!strict_equality(const_to_jsvalue(l), const_to_jsvalue(r))))
}
// IN/INSTANCEOF 是对象操作,不折。
BinaryOperator::In | BinaryOperator::Instanceof => None,
}
}
/// 关系比较折叠:镜像运行时 dispatch 口径(GT/LTE 交换操作数,LTE/GTE 取反,
/// `None` 归 false)。
fn relational(l: &Constant, r: &Constant, swap: bool, negate: bool) -> Option<Constant> {
let (a, b) = if swap {
(const_to_jsvalue(r), const_to_jsvalue(l))
} else {
(const_to_jsvalue(l), const_to_jsvalue(r))
};
let cmp = relational_compare(a, b).unwrap_or(false);
Some(Constant::Boolean(if negate { !cmp } else { cmp }))
}
@@ -2,6 +2,8 @@
//! 逻辑与 `??` 用短路跳转,复合求值利用 `is_side_effect_free` 优化。
//! 函数:`emit_operator` 及各类 `emit_*_expression`。
mod fold;
use crate::{is_side_effect_free, BinaryOperator, CompileCtx, Emitter};
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
@@ -30,6 +32,17 @@ impl Emitter {
fn emit_binary_expression(
&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx,
) -> Result<u32, String> {
// 双常量折叠:两操作数均为字面量且通过语义守卫时,编译期折单常量,
// 发射单条 LOAD_CONST(编码镜像运行时指令产物,见 fold.rs)。
// 置于 CONCAT_N 摊平之前,命中即提前返回。
if let (Some(l), Some(r)) = (fold::literal_constant(&bin.left), fold::literal_constant(&bin.right)) {
if let Some(folded) = fold::fold_binary(bin.operator, &l, &r) {
let idx = ctx.add_constant(folded);
let r = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(r), idx));
return Ok(r);
}
}
// Addition 左结合链摊平:≥3 操作数合并为单条 CONCAT_N(单趟预分配拼接,
// 消除 N-2 次中间串分配)。只下钻 left 链、right 括号不拆(a+(b+c) 退化 ADD),
// 保 f64 结合性与源码求值序。
+19
View File
@@ -409,3 +409,22 @@ fn block_fn_suppressed_by_destructured_param_leaf() {
"非抑制块函数应有入口 undefined 写与外层 var 写回"
);
}
/// 面 14:双常量折叠入口结构回归——双字面量算术编译后目标操作码计数为 0、
/// 常量池含折值,锁死折叠入口不随后续重构丢失。
#[test]
fn binary_literal_fold_entry() {
// `2 * 3` 乘法编码恒为 f64 → Number(6.0);`1 + 2` 为 int+int → Int(3)。
// 两操作数均为直接字面量才折(复合子表达式如 `1 + 2 * 3` 的外层加法不折)。
let ir = emit_ir("var a = 2 * 3; var b = 1 + 2;");
assert_eq!(count_op(&ir.insts, OpCode::MUL), 0, "双字面量乘法应折叠,MUL 计数为 0");
assert_eq!(count_op(&ir.insts, OpCode::ADD), 0, "双字面量加法应折叠,ADD 计数为 0");
assert!(
ir.constants.contains(&Constant::Number(6.0)),
"常量池应含乘法折值 Number(6.0)"
);
assert!(
ir.constants.contains(&Constant::Int(3)),
"常量池应含加法折值 Int(3)"
);
}
+145
View File
@@ -0,0 +1,145 @@
//! 双常量折叠的 IR 级结构断言:parse → `emit_program` 为止,钉死折叠命中、
//! 语义守卫不折与发射确定性。
//!
//! 命中矩阵断言目标操作码计数为 0 且常量池含折值;守卫矩阵断言目标操作码
//! 计数为 1(落回运行时路径);确定性断言同源串连续编译八字节码指纹全等。
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
use oxide_emit::Emitter;
use oxide_ir::inst::Inst;
use oxide_ir::IRFunction;
/// 夹具:parse + 脚本口径 emit,失败 panic 带源串。
fn emit_ir(src: &str) -> IRFunction {
let alloc = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&alloc, src).unwrap_or_else(|errs| panic!("parse 失败 {src:?}: {errs:?}"));
Emitter::new()
.emit_program(&program, false, false)
.unwrap_or_else(|e| panic!("emit 失败 {src:?}: {e}"))
}
fn count_op(insts: &[Inst], op: OpCode) -> usize {
insts.iter().filter(|i| i.op == op).count()
}
/// 常量池含指定常量(按 `PartialEq` 精确匹配,`Int(3)` 与 `Number(3.0)` 是不同键)。
fn pool_contains(ir: &IRFunction, c: &Constant) -> bool {
ir.constants.contains(c)
}
/// 命中矩阵:每行一条源串,断言目标操作码计数为 0 且常量池含折值。
/// 折值编码逐运算符镜像运行时产物(ADD 保 Int、SUB/MUL/DIV/MOD/EXP 恒 f64、
/// 位运算 Int、USHR 越 i32::MAX 升 Number、比较/等值 Boolean)。
#[test]
fn fold_hit_matrix() {
// (源串, 目标操作码, 期望折值)
let cases: &[(&str, OpCode, Constant)] = &[
("1 + 2", OpCode::ADD, Constant::Int(3)),
("1 - 2", OpCode::SUB, Constant::Number(-1.0)),
("2 * 3", OpCode::MUL, Constant::Number(6.0)),
("1 / 2", OpCode::DIV, Constant::Number(0.5)),
("7 % 3", OpCode::MOD, Constant::Number(1.0)),
("2 ** 10", OpCode::EXP, Constant::Number(1024.0)),
("5 & 3", OpCode::BIT_AND, Constant::Int(1)),
("5 | 3", OpCode::BIT_OR, Constant::Int(7)),
("5 ^ 3", OpCode::BIT_XOR, Constant::Int(6)),
// 移位量模 32:34 模 32 为 2。
("1 << 34", OpCode::SHL, Constant::Int(4)),
("-8 >> 1", OpCode::SHR, Constant::Int(-4)),
// USHR 越 i32::MAX 升 Number。
("-1 >>> 0", OpCode::USHR, Constant::Number(4294967295.0)),
("1 < 2", OpCode::LT, Constant::Boolean(true)),
("2 > 1", OpCode::GT, Constant::Boolean(true)),
("1 <= 1", OpCode::LTE, Constant::Boolean(true)),
("1 >= 2", OpCode::GTE, Constant::Boolean(false)),
// 布尔经 to_number。
("true < 2", OpCode::LT, Constant::Boolean(true)),
("null < 1", OpCode::LT, Constant::Boolean(true)),
// int 与 double 混合表示按数值。
("1 === 1.0", OpCode::STRICT_EQ, Constant::Boolean(true)),
("1 == 1.0", OpCode::EQ, Constant::Boolean(true)),
// 宽松相等 Boolean 重入。
("true == 1", OpCode::EQ, Constant::Boolean(true)),
("null == null", OpCode::EQ, Constant::Boolean(true)),
("1 !== 2", OpCode::STRICT_NEQ, Constant::Boolean(true)),
// 括号剥壳。
("(1) + (2)", OpCode::ADD, Constant::Int(3)),
];
for (src, op, folded) in cases {
let ir = emit_ir(src);
assert_eq!(count_op(&ir.insts, *op), 0, "{src:?} 应折叠,目标操作码 {op:?} 计数应为 0");
assert!(pool_contains(&ir, folded), "{src:?} 常量池应含折值 {folded:?}");
}
}
/// 守卫不折矩阵:断言目标操作码计数为 1(落回运行时路径)。
/// 除零守卫须同时覆盖 Int(0) 与 Number(±0.0);负指数守卫须同时覆盖 Int/Number
/// 负值;BigInt 字面量、字符串字面量、标识符均不折。
#[test]
fn fold_guard_matrix() {
// (源串, 目标操作码)
let cases: &[(&str, OpCode)] = &[
// DIV/MOD 除零(Int 0 与 Number ±0.0)。
("1 / 0", OpCode::DIV),
("1 / -0.0", OpCode::DIV),
("1 % 0", OpCode::MOD),
// EXP 负指数。
("2 ** -1", OpCode::EXP),
// BigInt 字面量(BigInt 混合、BigInt-BigInt 独立指令族)。
("1n + 2n", OpCode::ADD),
("1 + 1n", OpCode::ADD),
("1n < 2n", OpCode::LT),
// 字符串域不折。
("\"a\" + \"b\"", OpCode::ADD),
// 标识符不折。
("1 + x", OpCode::ADD),
];
for (src, op) in cases {
let ir = emit_ir(src);
assert_eq!(count_op(&ir.insts, *op), 1, "{src:?} 守卫命中,目标操作码 {op:?} 应保留 1 条");
}
}
/// 字节码指纹:`CompiledModule` 未派生 `Debug`,逐字段展开为确定串(子模块递归)。
fn bytecode_fingerprint(m: &oxide_bytecode::module::CompiledModule) -> String {
let mut out = format!(
"bc={:?};k={:?};nr={};na={};pb={};bim={:?};uv={:?};cn={};",
m.bytecode,
m.constants,
m.n_registers,
m.n_args,
m.param_base,
m.builtin_reg_map,
m.upvalue_captures,
m.cells_needed
);
for sub in &m.sub_modules {
out.push_str(&bytecode_fingerprint(sub));
}
out
}
/// 确定性:同一源串连续编译八次,IR 与字节码指纹全等(折叠路径纯函数无迭代序依赖)。
#[test]
fn fold_deterministic() {
let srcs = ["1 + 2 * 3;", "var a = -1 >>> 0; var b = 1 < 2;", "var c = 5 & 3 | 4;"];
for src in srcs {
let mut fingerprints: Vec<(String, String)> = Vec::new();
for _ in 0..8 {
let alloc = oxide_parser::Allocator::default();
let program =
oxide_parser::parse(&alloc, src).unwrap_or_else(|e| panic!("parse 失败 {src:?}: {e:?}"));
let ir = Emitter::new()
.emit_program(&program, false, false)
.unwrap_or_else(|e| panic!("emit 失败 {src:?}: {e}"));
let bytecode = oxide_ir::lower::lower(&ir).unwrap_or_else(|e| panic!("lower 失败 {src:?}: {e}"));
fingerprints.push((format!("{ir:?}"), bytecode_fingerprint(&bytecode)));
}
let first = &fingerprints[0];
for (i, fp) in fingerprints.iter().enumerate().skip(1) {
assert_eq!(fp.0, first.0, "第 {i} 次编译 IR 指纹漂移:{src:?}");
assert_eq!(fp.1, first.1, "第 {i} 次编译字节码指纹漂移:{src:?}");
}
}
}
+69 -33
View File
@@ -455,6 +455,52 @@ pub fn to_boolean(val: JsValue) -> bool {
}
}
/// IsLooselyEqual(x, y) 原始值核心 — ECMA-262 §7.2.15(`==`)的无宿主、
/// 无异常通道版本,两侧均须为原始值(对象臂不可达,由调用方保证)。
///
/// 同类型委托严格相等;null 与 undefined 互等;BigInt/Number/String 两两
/// 转数值比较;Boolean 操作数先 ToNumber 再重入。
pub fn abstract_eq_primitives(lhs: JsValue, rhs: JsValue) -> bool {
let tl = lhs.js_type();
let tr = rhs.js_type();
// 同类型 → 严格相等。
if tl == tr {
return strict_equality(lhs, rhs);
}
match (tl, tr) {
// null 与 undefined 互等。
(JsType::Null, JsType::Undefined) | (JsType::Undefined, JsType::Null) => true,
// Number 混合表示(int vs double):同属 ECMAScript Number,按严格相等。
(JsType::Int | JsType::Double, JsType::Int | JsType::Double) => strict_equality(lhs, rhs),
// BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。
(JsType::BigInt, JsType::Int | JsType::Double) => {
bigint_to_f64(unsafe { bigint_data(lhs) }) == to_number(rhs)
}
(JsType::Int | JsType::Double, JsType::BigInt) => {
to_number(lhs) == bigint_to_f64(unsafe { bigint_data(rhs) })
}
// BigInt 与 String:字符串解析为数字后比较(lossy 文本口径,见 to_number)。
(JsType::BigInt, JsType::String) => {
let r = parse_js_number(&unsafe { string_data(rhs) });
bigint_to_f64(unsafe { bigint_data(lhs) }) == r
}
(JsType::String, JsType::BigInt) => {
let l = parse_js_number(&unsafe { string_data(lhs) });
l == bigint_to_f64(unsafe { bigint_data(rhs) })
}
// Number 与 String:字符串经 ToNumber 后按严格数值比较。
(JsType::Int | JsType::Double, JsType::String) | (JsType::String, JsType::Int | JsType::Double) => {
strict_double_eq(to_number(lhs), to_number(rhs))
}
// Boolean 操作数:先 ToNumber 再重入。int(0/1) 轻量编码,省 float
// 装箱与 to_number 链;int 属于 Number,重入后各数值分支全部认。
(JsType::Bool, _) => abstract_eq_primitives(JsValue::int(lhs.as_bool() as i32), rhs),
(_, JsType::Bool) => abstract_eq_primitives(lhs, JsValue::int(rhs.as_bool() as i32)),
// 其余组合不相等(含 null/undefined 与其它类型)。
_ => false,
}
}
/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。
///
/// 同类型委托严格相等;null 与 undefined 互等;BigInt/Number/String 两两
@@ -479,49 +525,39 @@ pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul
return abstract_eq(lhs, JsValue::undefined(), host);
}
}
// 同类型 → 严格相等。
// 同类型 → 严格相等(含 Object/Object 指针同一)。
if tl == tr {
return Ok(strict_equality(lhs, rhs));
}
// 双侧原始值:委托原始值核心(无宿主、无异常通道)。
if tl != JsType::Object && tr != JsType::Object {
return Ok(abstract_eq_primitives(lhs, rhs));
}
// Object 与原始值:ToPrimitive 后重入。to_primitive 必返原始值,
// 重入即收敛到原始值核心,与单次委托等价。
match (tl, tr) {
// null 与 undefined 互等。
(JsType::Null, JsType::Undefined) | (JsType::Undefined, JsType::Null) => Ok(true),
// Number 混合表示(int vs double):同属 ECMAScript Number,按严格相等。
(JsType::Int | JsType::Double, JsType::Int | JsType::Double) => Ok(strict_equality(lhs, rhs)),
// BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。
(JsType::BigInt, JsType::Int | JsType::Double) => {
Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == to_number(rhs))
}
(JsType::Int | JsType::Double, JsType::BigInt) => {
Ok(to_number(lhs) == bigint_to_f64(unsafe { bigint_data(rhs) }))
}
// BigInt 与 String:字符串解析为数字后比较(lossy 文本口径,见 to_number)。
(JsType::BigInt, JsType::String) => {
let r = parse_js_number(&unsafe { string_data(rhs) });
Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == r)
}
(JsType::String, JsType::BigInt) => {
let l = parse_js_number(&unsafe { string_data(lhs) });
Ok(l == bigint_to_f64(unsafe { bigint_data(rhs) }))
}
// Number 与 String:字符串经 ToNumber 后按严格数值比较。
(JsType::Int | JsType::Double, JsType::String) | (JsType::String, JsType::Int | JsType::Double) => {
Ok(strict_double_eq(to_number(lhs), to_number(rhs)))
}
// Boolean 操作数:先 ToNumber 再重入。int(0/1) 轻量编码,省 float
// 装箱与 to_number 链;int 属于 Number,重入后各数值分支全部认。
(JsType::Bool, _) => abstract_eq(JsValue::int(lhs.as_bool() as i32), rhs, host),
(_, JsType::Bool) => abstract_eq(lhs, JsValue::int(rhs.as_bool() as i32), host),
// Object 与原始值(Number/String/BigInt/Symbol):ToPrimitive 后重入。
(JsType::Object, JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol) => {
(JsType::Object, JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol | JsType::Bool) => {
let rhs = if tr == JsType::Bool {
// Boolean 先 ToNumber(int(0/1) 轻量编码),与原始值核心
// 的 Boolean 重入口径一致。
JsValue::int(rhs.as_bool() as i32)
} else {
rhs
};
let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?;
abstract_eq(prim, rhs, host)
}
(JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol, JsType::Object) => {
(JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol | JsType::Bool, JsType::Object) => {
let lhs = if tl == JsType::Bool {
// 同上。
JsValue::int(lhs.as_bool() as i32)
} else {
lhs
};
let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?;
abstract_eq(lhs, prim, host)
}
// 其余组合不相等(含 null/undefined 与其它类型、Object/Object 已由同类型分支处理)。
// 其余组合不相等(含 null/undefined 与其它类型;Object/Object 已由同类型分支处理)。
_ => Ok(false),
}
}
+2 -1
View File
@@ -23,7 +23,8 @@ mod native_result;
mod runtime_api_log;
pub use coercion::{
abstract_eq, bigint_data, bigint_to_f64, bigint_to_string, format_error_message, js_number_to_string,
abstract_eq, abstract_eq_primitives, bigint_data, bigint_to_f64, bigint_to_string, format_error_message,
js_number_to_string,
materialize_string_box, push_to_string, push_units_to, relational_compare, same_value, same_value_zero,
strict_equality, string_concat, string_value_eq, to_bigint_full, to_boolean, to_int32, to_integer_or_infinity,
to_length, to_number, to_number_full, to_object, to_primitive, to_string, to_string_for_string_constructor,