mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
perf: 相等/严格相等/ToNumber 改 js_type 单次提取收敛并修复混合 tag 严格相等 ±0 语义
This commit is contained in:
@@ -160,7 +160,11 @@ fn to_integer_or_infinity_bounded<H: VmHost>(vm: &mut H, val: JsValue) -> Result
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn clamp_relative(rel: f64, n_f: f64, n: usize) -> usize {
|
fn clamp_relative(rel: f64, n_f: f64, n: usize) -> usize {
|
||||||
if rel.is_infinite() {
|
if rel.is_infinite() {
|
||||||
if rel < 0.0 { 0 } else { n }
|
if rel < 0.0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
n
|
||||||
|
}
|
||||||
} else if rel < 0.0 {
|
} else if rel < 0.0 {
|
||||||
(n_f + rel).max(0.0) as usize
|
(n_f + rel).max(0.0) as usize
|
||||||
} else {
|
} else {
|
||||||
@@ -908,7 +912,7 @@ pub fn array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
};
|
};
|
||||||
for i in from_index..n {
|
for i in from_index..n {
|
||||||
let elem = arraylike_get_or_err!(vm, ptr, i);
|
let elem = arraylike_get_or_err!(vm, ptr, i);
|
||||||
if oxide_runtime_api::strict_eq(elem, target) {
|
if oxide_runtime_api::strict_equality(elem, target) {
|
||||||
return NativeResult::Ok(js_array_index(i));
|
return NativeResult::Ok(js_array_index(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,29 +952,13 @@ pub fn array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
for i in from_index..n {
|
for i in from_index..n {
|
||||||
let elem = arraylike_get_or_err!(vm, ptr, i);
|
let elem = arraylike_get_or_err!(vm, ptr, i);
|
||||||
// SameValueZero:NaN 视为相等、+0 与 -0 视为相同。
|
// SameValueZero:NaN 视为相等、+0 与 -0 视为相同。
|
||||||
if same_value_zero(elem, target) {
|
if oxide_runtime_api::same_value_zero(elem, target) {
|
||||||
return NativeResult::Ok(JsValue::bool(true));
|
return NativeResult::Ok(JsValue::bool(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NativeResult::Ok(JsValue::bool(false))
|
NativeResult::Ok(JsValue::bool(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SameValueZero(ES2015 §7.2.10):NaN 视为相等、+0 与 -0 视为相同。
|
|
||||||
fn same_value_zero(a: JsValue, b: JsValue) -> bool {
|
|
||||||
// 双数字时特殊处理。
|
|
||||||
let a_is_num = a.is_int() || a.is_double();
|
|
||||||
let b_is_num = b.is_int() || b.is_double();
|
|
||||||
if a_is_num && b_is_num {
|
|
||||||
let av = if a.is_int() { a.as_int() as f64 } else { a.as_double() };
|
|
||||||
let bv = if b.is_int() { b.as_int() as f64 } else { b.as_double() };
|
|
||||||
if av.is_nan() && bv.is_nan() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return av == bv; // Rust f64 中 +0 == -0。
|
|
||||||
}
|
|
||||||
oxide_runtime_api::strict_equality(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Array.prototype.reverse()`:原地反转元素顺序,返回 this。
|
/// `Array.prototype.reverse()`:原地反转元素顺序,返回 this。
|
||||||
pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
builtins_debug!("Array.prototype.reverse called with {} args", args.len());
|
builtins_debug!("Array.prototype.reverse called with {} args", args.len());
|
||||||
@@ -1523,7 +1511,7 @@ pub fn array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
}
|
}
|
||||||
for i in (0..=from_index_isize as usize).rev() {
|
for i in (0..=from_index_isize as usize).rev() {
|
||||||
let elem = arraylike_get_or_err!(vm, ptr, i);
|
let elem = arraylike_get_or_err!(vm, ptr, i);
|
||||||
if oxide_runtime_api::strict_eq(elem, search) {
|
if oxide_runtime_api::strict_equality(elem, search) {
|
||||||
return NativeResult::Ok(js_array_index(i));
|
return NativeResult::Ok(js_array_index(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -989,7 +989,7 @@ pub fn typed_array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
|||||||
};
|
};
|
||||||
for i in from_index..view.length {
|
for i in from_index..view.length {
|
||||||
let elem = native_try!(ta_read(vm, view, i));
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
if oxide_runtime_api::strict_eq(elem, target) {
|
if oxide_runtime_api::strict_equality(elem, target) {
|
||||||
return NativeResult::Ok(JsValue::int(i as i32));
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1027,7 +1027,7 @@ pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
|
|||||||
}
|
}
|
||||||
for i in (0..=from_index as usize).rev() {
|
for i in (0..=from_index as usize).rev() {
|
||||||
let elem = native_try!(ta_read(vm, view, i));
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
if oxide_runtime_api::strict_eq(elem, target) {
|
if oxide_runtime_api::strict_equality(elem, target) {
|
||||||
return NativeResult::Ok(JsValue::int(i as i32));
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+142
-206
@@ -20,7 +20,7 @@ use oxide_kernel::kernel::{KernelCore, KernelSession};
|
|||||||
use oxide_types::mem::{Epoch, P};
|
use oxide_types::mem::{Epoch, P};
|
||||||
use oxide_types::object::{JsObject, PropAttributes};
|
use oxide_types::object::{JsObject, PropAttributes};
|
||||||
use oxide_types::shape::EMPTY_SHAPE_ID;
|
use oxide_types::shape::EMPTY_SHAPE_ID;
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::{JsType, JsValue};
|
||||||
|
|
||||||
/// 每个 builtin native 函数的返回值。
|
/// 每个 builtin native 函数的返回值。
|
||||||
pub enum NativeResult {
|
pub enum NativeResult {
|
||||||
@@ -230,34 +230,29 @@ pub fn bigint_to_string(v: &num_bigint::BigInt) -> String {
|
|||||||
/// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`;
|
/// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`;
|
||||||
/// Object 与 Symbol 不在此处理,返回 `NaN`(对象需走 [`to_number_full`])。
|
/// Object 与 Symbol 不在此处理,返回 `NaN`(对象需走 [`to_number_full`])。
|
||||||
pub fn to_number(val: JsValue) -> f64 {
|
pub fn to_number(val: JsValue) -> f64 {
|
||||||
if val.is_int() {
|
match val.js_type() {
|
||||||
return val.as_int() as f64;
|
JsType::Int => val.as_int() as f64,
|
||||||
|
JsType::Double => val.as_double(),
|
||||||
|
JsType::Bool => {
|
||||||
|
if val.as_bool() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsType::Null => 0.0,
|
||||||
|
JsType::Undefined => f64::NAN,
|
||||||
|
JsType::String => {
|
||||||
|
let s = unsafe { string_data(val) };
|
||||||
|
parse_js_number(s)
|
||||||
|
}
|
||||||
|
JsType::BigInt => {
|
||||||
|
// BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。
|
||||||
|
// 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。
|
||||||
|
bigint_to_f64(unsafe { bigint_data(val) })
|
||||||
|
}
|
||||||
|
JsType::Object | JsType::Symbol => f64::NAN,
|
||||||
}
|
}
|
||||||
if val.is_double() {
|
|
||||||
return val.as_double();
|
|
||||||
}
|
|
||||||
if val.is_bool() {
|
|
||||||
return if val.as_bool() { 1.0 } else { 0.0 };
|
|
||||||
}
|
|
||||||
if val.is_null() {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
if val.is_undefined() {
|
|
||||||
return f64::NAN;
|
|
||||||
}
|
|
||||||
if val.is_string() {
|
|
||||||
let s = unsafe { string_data(val) };
|
|
||||||
return parse_js_number(s);
|
|
||||||
}
|
|
||||||
if val.is_bigint() {
|
|
||||||
// BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。
|
|
||||||
// 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。
|
|
||||||
return bigint_to_f64(unsafe { bigint_data(val) });
|
|
||||||
}
|
|
||||||
if val.is_object() {
|
|
||||||
return f64::NAN;
|
|
||||||
}
|
|
||||||
f64::NAN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按 ECMA-262 ToNumber 的 StringNumericLiteral 语法解析字符串。
|
/// 按 ECMA-262 ToNumber 的 StringNumericLiteral 语法解析字符串。
|
||||||
@@ -557,133 +552,61 @@ pub fn to_boolean(val: JsValue) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 两个值是否共享同一 ECMAScript 语言类型。Number 把 int 与 double tag 的值
|
|
||||||
/// 视为同一类型(都是 Number)。
|
|
||||||
fn same_type(a: JsValue, b: JsValue) -> bool {
|
|
||||||
if a.is_string() && b.is_string() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (a.is_int() || a.is_double()) && (b.is_int() || b.is_double()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_bool() && b.is_bool() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_null() && b.is_null() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_undefined() && b.is_undefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_object() && b.is_object() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_symbol() && b.is_symbol() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a.is_bigint() && b.is_bigint() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。
|
/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。
|
||||||
///
|
///
|
||||||
/// 省略 BigInt 步骤(引擎剪除 BigInt)。对象操作数经 ToPrimitive 强制转换,
|
/// 同类型委托严格相等;null 与 undefined 互等;BigInt/Number/String 两两
|
||||||
/// 可能调用用户 `valueOf` / `toString` / `@@toPrimitive`;因此需要 `VmHost`
|
/// 转数值比较;Boolean 操作数先 ToNumber;原始值 vs Object 经 ToPrimitive
|
||||||
/// 参数与 `Result`(这些回调抛出的 TypeError 以 `Err` 传播)。
|
/// 强制转换后重入(Symbol 与 Object 同样触发,规范步骤 10-11)。对象路径
|
||||||
/// 注意:`Object == Symbol` 不触发 ToPrimitive(规范步骤 11/12 只覆盖
|
/// 可能调用用户 `valueOf` / `toString` / `@@toPrimitive`,因此需要 `VmHost`
|
||||||
/// Number/String),直接落到 `false`。
|
/// 参数与 `Result`(回调抛出的 TypeError 以 `Err` 传播)。
|
||||||
pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result<bool, String> {
|
pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result<bool, String> {
|
||||||
// 步骤 1:同类型 → 严格相等。
|
let tl = lhs.js_type();
|
||||||
if same_type(lhs, rhs) {
|
let tr = rhs.js_type();
|
||||||
|
// 同类型 → 严格相等。
|
||||||
|
if tl == tr {
|
||||||
return Ok(strict_equality(lhs, rhs));
|
return Ok(strict_equality(lhs, rhs));
|
||||||
}
|
}
|
||||||
// 步骤 2-3:null 与 undefined 互等。
|
match (tl, tr) {
|
||||||
if (lhs.is_null() && rhs.is_undefined()) || (lhs.is_undefined() && rhs.is_null()) {
|
// null 与 undefined 互等。
|
||||||
return Ok(true);
|
(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:字符串解析为数字后比较。
|
||||||
|
(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) => {
|
||||||
|
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) => {
|
||||||
|
let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?;
|
||||||
|
abstract_eq(lhs, prim, host)
|
||||||
|
}
|
||||||
|
// 其余组合不相等(含 null/undefined 与其它类型、Object/Object 已由同类型分支处理)。
|
||||||
|
_ => Ok(false),
|
||||||
}
|
}
|
||||||
// BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。
|
|
||||||
if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) {
|
|
||||||
return Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == to_number(rhs));
|
|
||||||
}
|
|
||||||
if (lhs.is_int() || lhs.is_double()) && rhs.is_bigint() {
|
|
||||||
return Ok(to_number(lhs) == bigint_to_f64(unsafe { bigint_data(rhs) }));
|
|
||||||
}
|
|
||||||
// BigInt 与 String:字符串解析为数字后比较。
|
|
||||||
if lhs.is_bigint() && rhs.is_string() {
|
|
||||||
let r = parse_js_number(unsafe { string_data(rhs) });
|
|
||||||
return Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == r);
|
|
||||||
}
|
|
||||||
if lhs.is_string() && rhs.is_bigint() {
|
|
||||||
let l = parse_js_number(unsafe { string_data(lhs) });
|
|
||||||
return Ok(l == bigint_to_f64(unsafe { bigint_data(rhs) }));
|
|
||||||
}
|
|
||||||
// 步骤 5-6:Number 与 String。
|
|
||||||
if (lhs.is_int() || lhs.is_double()) && rhs.is_string() {
|
|
||||||
return Ok(strict_double_eq(to_number(lhs), to_number(rhs)));
|
|
||||||
}
|
|
||||||
if lhs.is_string() && (rhs.is_int() || rhs.is_double()) {
|
|
||||||
return Ok(strict_double_eq(to_number(lhs), to_number(rhs)));
|
|
||||||
}
|
|
||||||
// 步骤 9:x 为 Boolean → 比较 ToNumber(x)。
|
|
||||||
if lhs.is_bool() {
|
|
||||||
return abstract_eq(JsValue::float(to_number(lhs)), rhs, host);
|
|
||||||
}
|
|
||||||
// 步骤 10:y 为 Boolean → 比较 ToNumber(y)。
|
|
||||||
if rhs.is_bool() {
|
|
||||||
return abstract_eq(lhs, JsValue::float(to_number(rhs)), host);
|
|
||||||
}
|
|
||||||
// 步骤 11:x 为 Number/String,y 为 Object → ToPrimitive(y)。
|
|
||||||
if (lhs.is_int() || lhs.is_double() || lhs.is_string() || lhs.is_bigint() || lhs.is_symbol()) && rhs.is_object() {
|
|
||||||
let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?;
|
|
||||||
return abstract_eq(lhs, prim, host);
|
|
||||||
}
|
|
||||||
// 步骤 12:x 为 Object,y 为 Number/String → ToPrimitive(x)。
|
|
||||||
if lhs.is_object() && (rhs.is_int() || rhs.is_double() || rhs.is_string() || rhs.is_bigint() || rhs.is_symbol()) {
|
|
||||||
let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?;
|
|
||||||
return abstract_eq(prim, rhs, host);
|
|
||||||
}
|
|
||||||
// 步骤 14:否则不相等。
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Strict Equality Comparison(`===`,ECMA-262 §7.2.14)。
|
|
||||||
///
|
|
||||||
/// 类型不同直接为 false;同类型下 Object 按指针、其余按值比较。
|
|
||||||
pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool {
|
|
||||||
if lhs.is_int() && rhs.is_int() {
|
|
||||||
return lhs.as_int() == rhs.as_int();
|
|
||||||
}
|
|
||||||
// Number 跨 int/double 表示比较:`42 === 42.0` 按 SameValue 语义应相等。
|
|
||||||
if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) {
|
|
||||||
return strict_double_eq(to_f64(lhs), to_f64(rhs));
|
|
||||||
}
|
|
||||||
if lhs.is_double() && rhs.is_double() {
|
|
||||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
|
||||||
}
|
|
||||||
if lhs.is_bool() && rhs.is_bool() {
|
|
||||||
return lhs.as_bool() == rhs.as_bool();
|
|
||||||
}
|
|
||||||
if lhs.is_null() && rhs.is_null() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if lhs.is_undefined() && rhs.is_undefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if lhs.is_string() && rhs.is_string() {
|
|
||||||
return string_value_eq(lhs, rhs);
|
|
||||||
}
|
|
||||||
if lhs.is_object() && rhs.is_object() {
|
|
||||||
return lhs.as_ptr() == rhs.as_ptr();
|
|
||||||
}
|
|
||||||
if lhs.is_symbol() && rhs.is_symbol() {
|
|
||||||
return lhs.as_symbol_index() == rhs.as_symbol_index();
|
|
||||||
}
|
|
||||||
if lhs.is_bigint() && rhs.is_bigint() {
|
|
||||||
return unsafe { bigint_data(lhs) } == unsafe { bigint_data(rhs) };
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strict_double_eq(a: f64, b: f64) -> bool {
|
fn strict_double_eq(a: f64, b: f64) -> bool {
|
||||||
@@ -794,55 +717,41 @@ fn number_cmp_bigint(num: JsValue, big: &num_bigint::BigInt) -> Ordering {
|
|||||||
|
|
||||||
/// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。
|
/// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。
|
||||||
pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool {
|
pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool {
|
||||||
if lhs.is_double() && rhs.is_double() {
|
match (lhs.js_type(), rhs.js_type()) {
|
||||||
let a = lhs.as_double();
|
(JsType::Double, JsType::Double) => {
|
||||||
let b = rhs.as_double();
|
let a = lhs.as_double();
|
||||||
if a.is_nan() && b.is_nan() {
|
let b = rhs.as_double();
|
||||||
return true;
|
if a.is_nan() && b.is_nan() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if a == 0.0 && b == 0.0 {
|
||||||
|
return a.is_sign_negative() == b.is_sign_negative();
|
||||||
|
}
|
||||||
|
a == b
|
||||||
}
|
}
|
||||||
if a == 0.0 && b == 0.0 {
|
(JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(),
|
||||||
let a_neg = a.is_sign_negative();
|
// Number 混合表示(int vs double):SameValue 的 ±0/NaN 特判原样保留。
|
||||||
let b_neg = b.is_sign_negative();
|
(JsType::Int | JsType::Double, JsType::Int | JsType::Double) => {
|
||||||
return a_neg == b_neg;
|
let a = to_f64(lhs);
|
||||||
|
let b = to_f64(rhs);
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if a == 0.0 && b == 0.0 {
|
||||||
|
return a.is_sign_negative() == b.is_sign_negative();
|
||||||
|
}
|
||||||
|
a == b
|
||||||
}
|
}
|
||||||
return a == b;
|
(JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(),
|
||||||
|
(JsType::Null, JsType::Null) => true,
|
||||||
|
(JsType::Undefined, JsType::Undefined) => true,
|
||||||
|
(JsType::String, JsType::String) => string_value_eq(lhs, rhs),
|
||||||
|
(JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(),
|
||||||
|
(JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(),
|
||||||
|
// SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。
|
||||||
|
(JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) },
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
if lhs.is_int() && rhs.is_int() {
|
|
||||||
return lhs.as_int() == rhs.as_int();
|
|
||||||
}
|
|
||||||
if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) {
|
|
||||||
let a = to_f64(lhs);
|
|
||||||
let b = to_f64(rhs);
|
|
||||||
if a.is_nan() && b.is_nan() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if a == 0.0 && b == 0.0 {
|
|
||||||
return a.is_sign_negative() == b.is_sign_negative();
|
|
||||||
}
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
if lhs.is_bool() && rhs.is_bool() {
|
|
||||||
return lhs.as_bool() == rhs.as_bool();
|
|
||||||
}
|
|
||||||
if lhs.is_null() && rhs.is_null() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if lhs.is_undefined() && rhs.is_undefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if lhs.is_string() && rhs.is_string() {
|
|
||||||
return string_value_eq(lhs, rhs);
|
|
||||||
}
|
|
||||||
if lhs.is_object() && rhs.is_object() {
|
|
||||||
return lhs.as_ptr() == rhs.as_ptr();
|
|
||||||
}
|
|
||||||
if lhs.is_symbol() && rhs.is_symbol() {
|
|
||||||
return lhs.as_symbol_index() == rhs.as_symbol_index();
|
|
||||||
}
|
|
||||||
if lhs.is_bigint() && rhs.is_bigint() {
|
|
||||||
return unsafe { bigint_data(lhs) } == unsafe { bigint_data(rhs) };
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ToIntegerOrInfinity(argument) — ECMA-262 §7.1.4.
|
/// ToIntegerOrInfinity(argument) — ECMA-262 §7.1.4.
|
||||||
@@ -866,23 +775,50 @@ pub fn to_length(val: JsValue) -> u64 {
|
|||||||
|
|
||||||
/// SameValueZero(x, y) — ECMA-262 §7.2.11.
|
/// SameValueZero(x, y) — ECMA-262 §7.2.11.
|
||||||
pub fn same_value_zero(lhs: JsValue, rhs: JsValue) -> bool {
|
pub fn same_value_zero(lhs: JsValue, rhs: JsValue) -> bool {
|
||||||
if (lhs.is_double() || lhs.is_int()) && (rhs.is_double() || rhs.is_int()) {
|
match (lhs.js_type(), rhs.js_type()) {
|
||||||
let a = to_f64(lhs);
|
(JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(),
|
||||||
let b = to_f64(rhs);
|
(JsType::Double, JsType::Double) => {
|
||||||
if a.is_nan() && b.is_nan() {
|
let a = lhs.as_double();
|
||||||
return true;
|
let b = rhs.as_double();
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
a == b
|
||||||
}
|
}
|
||||||
return a == b;
|
// Number 混合表示:NaN 视为相等,+0/-0 相等(Rust f64 中 +0 == -0)。
|
||||||
|
(JsType::Int | JsType::Double, JsType::Int | JsType::Double) => {
|
||||||
|
let a = to_f64(lhs);
|
||||||
|
let b = to_f64(rhs);
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
_ => same_value(lhs, rhs),
|
||||||
}
|
}
|
||||||
same_value(lhs, rhs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 与 [`strict_eq`] 等价的规范层实现:双浮点走 NaN 安全比较,其余复用 [`same_value`]。
|
/// Strict Equality Comparison(`===`,ECMA-262 §7.2.14)。
|
||||||
|
///
|
||||||
|
/// 类型不同直接为 false;同类型下 Number(int/double 两种表示)按数值
|
||||||
|
/// 比较(NaN 恒 false、+0/-0 相等,含 int/double 混合——`0 === -0` 为 true),
|
||||||
|
/// Object 按指针、其余按值比较。
|
||||||
pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool {
|
pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool {
|
||||||
if lhs.is_double() && rhs.is_double() {
|
match (lhs.js_type(), rhs.js_type()) {
|
||||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
(JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(),
|
||||||
|
(JsType::Double, JsType::Double) => strict_double_eq(lhs.as_double(), rhs.as_double()),
|
||||||
|
// Number 跨 int/double 表示:`42 === 42.0`、`0 === -0` 均按数值语义。
|
||||||
|
(JsType::Int | JsType::Double, JsType::Int | JsType::Double) => strict_double_eq(to_f64(lhs), to_f64(rhs)),
|
||||||
|
(JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(),
|
||||||
|
(JsType::Null, JsType::Null) => true,
|
||||||
|
(JsType::Undefined, JsType::Undefined) => true,
|
||||||
|
(JsType::String, JsType::String) => string_value_eq(lhs, rhs),
|
||||||
|
(JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(),
|
||||||
|
(JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(),
|
||||||
|
// SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。
|
||||||
|
(JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) },
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
same_value(lhs, rhs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ToObject(ECMA-262 §7.1.13):null/undefined 抛 TypeError,其余原始值包装为对应包装对象。
|
/// ToObject(ECMA-262 §7.1.13):null/undefined 抛 TypeError,其余原始值包装为对应包装对象。
|
||||||
|
|||||||
@@ -47,6 +47,40 @@ fn get_tag(bits: u64) -> u64 {
|
|||||||
(bits & TAG_MASK) >> TAG_SHIFT
|
(bits & TAG_MASK) >> TAG_SHIFT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ECMAScript 语言类型分类(`typeof` 与相等/强转的分派粒度)。
|
||||||
|
///
|
||||||
|
/// Number 内部两种表示(int/double)分列两个变体;需按"都是 Number"
|
||||||
|
/// 合并判断时用 [`JsType::is_number`]。
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum JsType {
|
||||||
|
/// 32 位整数表示(tag 0)。
|
||||||
|
Int,
|
||||||
|
/// 普通双精度位模式(非 NaN-box,含 ±Infinity 与规范 NaN)。
|
||||||
|
Double,
|
||||||
|
/// 布尔值。
|
||||||
|
Bool,
|
||||||
|
/// `null`。
|
||||||
|
Null,
|
||||||
|
/// `undefined`。
|
||||||
|
Undefined,
|
||||||
|
/// 对象引用(48 位指针)。
|
||||||
|
Object,
|
||||||
|
/// 字符串引用(48 位指针)。
|
||||||
|
String,
|
||||||
|
/// 符号值(payload 为符号表下标)。
|
||||||
|
Symbol,
|
||||||
|
/// BigInt 指针(48 位指针,tag 7)。
|
||||||
|
BigInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsType {
|
||||||
|
/// 是否为 ECMAScript Number(int 或 double 表示)。
|
||||||
|
#[inline]
|
||||||
|
pub fn is_number(self) -> bool {
|
||||||
|
matches!(self, JsType::Int | JsType::Double)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 统一 ECMAScript 值:一个 NaN-boxed 的 64 位字。
|
/// 统一 ECMAScript 值:一个 NaN-boxed 的 64 位字。
|
||||||
///
|
///
|
||||||
/// `repr(transparent)` 包裹单个 `u64`。double 原样编码;非 double 类型
|
/// `repr(transparent)` 包裹单个 `u64`。double 原样编码;非 double 类型
|
||||||
@@ -108,6 +142,42 @@ impl JsValue {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 单次提取 ECMAScript 语言类型:一次范围判断 + 掩码位移,替代多个
|
||||||
|
/// `is_*` 串联判断。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 非 NaN-box 位模式(含规范 NaN 与 ±Infinity)一律归为 `Double`
|
||||||
|
/// - NaN-box 内按 tag 位映射(bits 50-48 三比特全被占用,无遗漏)
|
||||||
|
///
|
||||||
|
/// # 注意事项
|
||||||
|
/// int 与 double 是两种表示;按"都是 Number"判断用 [`JsType::is_number`]。
|
||||||
|
#[inline]
|
||||||
|
pub fn js_type(self) -> JsType {
|
||||||
|
let prefix = (self.0 >> TAG_SHIFT) as u16;
|
||||||
|
if !(0xFFF8..=0xFFFF).contains(&prefix) {
|
||||||
|
return JsType::Double;
|
||||||
|
}
|
||||||
|
match get_tag(self.0) {
|
||||||
|
TAG_INT => JsType::Int,
|
||||||
|
TAG_BOOL => JsType::Bool,
|
||||||
|
TAG_NULL => JsType::Null,
|
||||||
|
TAG_UNDEFINED => JsType::Undefined,
|
||||||
|
TAG_OBJECT => JsType::Object,
|
||||||
|
TAG_STRING => JsType::String,
|
||||||
|
TAG_SYMBOL => JsType::Symbol,
|
||||||
|
// tag 7 = BigInt(NaN 规范化编码已改用普通 quiet NaN,见 [`JsValue::float`])。
|
||||||
|
TAG_BIGINT => JsType::BigInt,
|
||||||
|
// 3 位 tag 全被占用,该分支不可达(仅满足类型系统穷尽性)。
|
||||||
|
_ => JsType::Double,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 是否为 ECMAScript Number(int 或 double 表示)。
|
||||||
|
#[inline]
|
||||||
|
pub fn is_number(self) -> bool {
|
||||||
|
matches!(self.js_type(), JsType::Int | JsType::Double)
|
||||||
|
}
|
||||||
|
|
||||||
/// 构造 32 位整数(tag = int,payload 为 `i32` 位模式)。
|
/// 构造 32 位整数(tag = int,payload 为 `i32` 位模式)。
|
||||||
pub fn int(v: i32) -> Self {
|
pub fn int(v: i32) -> Self {
|
||||||
Self(make_tag(TAG_INT) | (v as u32 as u64))
|
Self(make_tag(TAG_INT) | (v as u32 as u64))
|
||||||
@@ -407,7 +477,7 @@ impl fmt::Debug for JsValue {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::JsValue;
|
use super::{JsType, JsValue};
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use proptest::test_runner::TestRunner;
|
use proptest::test_runner::TestRunner;
|
||||||
@@ -477,6 +547,40 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn js_type_matches_tag_checks() {
|
||||||
|
let mut runner = TestRunner::default();
|
||||||
|
runner
|
||||||
|
.run(&any::<u64>(), |bits| {
|
||||||
|
let val = JsValue::from_bits(bits);
|
||||||
|
let t = val.js_type();
|
||||||
|
// 随机位模式上 js_type 与 9 个 is_* 检查 1:1 对拍。
|
||||||
|
let expected = if val.is_double() {
|
||||||
|
JsType::Double
|
||||||
|
} else if val.is_int() {
|
||||||
|
JsType::Int
|
||||||
|
} else if val.is_bool() {
|
||||||
|
JsType::Bool
|
||||||
|
} else if val.is_null() {
|
||||||
|
JsType::Null
|
||||||
|
} else if val.is_undefined() {
|
||||||
|
JsType::Undefined
|
||||||
|
} else if val.is_object() {
|
||||||
|
JsType::Object
|
||||||
|
} else if val.is_string() {
|
||||||
|
JsType::String
|
||||||
|
} else if val.is_symbol() {
|
||||||
|
JsType::Symbol
|
||||||
|
} else {
|
||||||
|
JsType::BigInt
|
||||||
|
};
|
||||||
|
assert_eq!(t, expected, "bits={bits:#018x} js_type={t:?}");
|
||||||
|
assert_eq!(t.is_number(), val.is_int() || val.is_double(), "bits={bits:#018x}");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn null_identity() {
|
fn null_identity() {
|
||||||
assert_eq!(JsValue::null(), JsValue::null());
|
assert_eq!(JsValue::null(), JsValue::null());
|
||||||
|
|||||||
@@ -201,6 +201,57 @@ fn strict_eq_nan_eval() {
|
|||||||
assert_eq!(eval("NaN === NaN"), "false");
|
assert_eq!(eval("NaN === NaN"), "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 严格相等:int 0 与 double -0.0 混合表示按数值语义相等(=== 的 ±0 修复)。
|
||||||
|
#[test]
|
||||||
|
fn strict_equality_int_zero_vs_float_neg_zero() {
|
||||||
|
assert!(coercion::strict_equality(JsValue::int(0), JsValue::float(-0.0)));
|
||||||
|
assert!(coercion::strict_equality(JsValue::int(0), JsValue::float(0.0)));
|
||||||
|
assert!(!coercion::strict_equality(JsValue::int(1), JsValue::float(-0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 严格相等:字面量 0(int)与 -0(double)跨表示 === 为 true。
|
||||||
|
#[test]
|
||||||
|
fn eval_int_zero_vs_neg_zero_strict() {
|
||||||
|
assert_eq!(eval("0 === -0"), "true");
|
||||||
|
assert_eq!(eval("-0 === -0"), "true");
|
||||||
|
assert_eq!(eval("0 !== -0"), "false");
|
||||||
|
assert_eq!(eval("42 === 42.0"), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抽象相等:0 == -0 跨表示按数值相等。
|
||||||
|
#[test]
|
||||||
|
fn eval_int_zero_vs_neg_zero_loose() {
|
||||||
|
assert_eq!(eval("0 == -0"), "true");
|
||||||
|
assert_eq!(eval("0 != -0"), "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SameValue:int 0 与 double -0.0 视为不同(Object.is 依赖)。
|
||||||
|
#[test]
|
||||||
|
fn same_value_int_zero_vs_float_neg_zero() {
|
||||||
|
assert!(!coercion::same_value(JsValue::int(0), JsValue::float(-0.0)));
|
||||||
|
assert!(coercion::same_value(JsValue::int(0), JsValue::float(0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SameValueZero:int 0 与 double -0.0 视为相同(includes 依赖)。
|
||||||
|
#[test]
|
||||||
|
fn same_value_zero_int_zero_vs_float_neg_zero() {
|
||||||
|
assert!(coercion::same_value_zero(JsValue::int(0), JsValue::float(-0.0)));
|
||||||
|
assert!(coercion::same_value_zero(JsValue::float(f64::NAN), JsValue::float(f64::NAN)));
|
||||||
|
assert!(coercion::same_value_zero(JsValue::float(f64::NAN), JsValue::float(-f64::NAN)));
|
||||||
|
assert!(!coercion::same_value_zero(JsValue::int(1), JsValue::float(2.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// eval 实况:Object.is / indexOf / includes 的 ±0 与 NaN 语义。
|
||||||
|
#[test]
|
||||||
|
fn eval_signed_zero_object_is_and_array_methods() {
|
||||||
|
assert_eq!(eval("Object.is(0, -0)"), "false");
|
||||||
|
assert_eq!(eval("Object.is(0, 0)"), "true");
|
||||||
|
assert_eq!(eval("[0].indexOf(-0)"), "0");
|
||||||
|
assert_eq!(eval("[0].includes(-0)"), "true");
|
||||||
|
assert_eq!(eval("[NaN].indexOf(NaN)"), "-1");
|
||||||
|
assert_eq!(eval("[NaN].includes(NaN)"), "true");
|
||||||
|
}
|
||||||
|
|
||||||
// 算术 +:对象操作数经 valueOf 强转。
|
// 算术 +:对象操作数经 valueOf 强转。
|
||||||
#[test]
|
#[test]
|
||||||
fn add_object_valueof_coercion() {
|
fn add_object_valueof_coercion() {
|
||||||
|
|||||||
Reference in New Issue
Block a user