feat: 实现最小 BigInt 子集(字面量/算术/比较/构造器,Temporal 前置)

This commit is contained in:
爱特每羊队
2026-08-10 23:02:31 +08:00
parent 46b60ff003
commit 7e3d7b43b5
22 changed files with 706 additions and 32 deletions
+107
View File
@@ -79,6 +79,7 @@ impl Vm {
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
@@ -171,6 +172,7 @@ impl Vm {
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
@@ -275,6 +277,7 @@ impl Vm {
self.gc_state.session_bytes_allocated = 0;
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
self.free_session_string_heap_data();
self.free_session_bigint_heap_data();
self.symbols.reset();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
@@ -477,10 +480,39 @@ impl Vm {
format!("{val}")
}
/// 分配一个 BigInt 值:把 `i128` 堆分配为 box 并返回携带指针的 `JsValue`。
///
/// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。
/// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。
pub fn new_bigint(&self, v: i128) -> JsValue {
let ptr = Box::into_raw(Box::new(v));
self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr);
JsValue::bigint(ptr)
}
/// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。
pub fn bigint_value(&self, val: JsValue) -> i128 {
// SAFETY: bigint 指针由 new_bigint 经 Box::into_raw 产生,存活至 full_reset。
unsafe { *val.as_bigint_ptr() }
}
/// 释放全部 session 堆 `i128` box。仅在完全隔离重置(`full_reset`)时调用,
/// 此时没有存活的 session 对象/寄存器会引用它们。
fn free_session_bigint_heap_data(&mut self) {
for ptr in self.gc_state.session_bigint_ptrs.borrow_mut().drain(..) {
// SAFETY: 每个指针来自 new_bigint 的 Box::into_raw(Box::new(i128)),
// 且只在这里恰好释放一次。
unsafe {
drop(Box::from_raw(ptr));
}
}
}
fn convert_constant(&self, constant: &Constant) -> JsValue {
match constant {
Constant::Number(v) => JsValue::float(*v),
Constant::Int(v) => JsValue::int(*v),
Constant::BigInt(v) => self.new_bigint(*v),
Constant::String(s) => self.perm_string(s),
Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(),
@@ -752,4 +784,79 @@ mod tests {
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn bigint_literal_arithmetic_and_comparison() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n"));
assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n"));
assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n"));
assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false));
assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false));
let typeof_result = run_source(&mut vm, "typeof 123n");
assert!(typeof_result.is_string());
assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint"));
}
#[test]
fn bigint_constructor_and_string() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n"));
let s = run_source(&mut vm, "String(123n)");
assert!(s.is_string());
assert_eq!(vm.lookup_str(s).as_deref(), Some("123"));
let ts = run_source(&mut vm, "(123n).toString()");
assert!(ts.is_string());
assert_eq!(vm.lookup_str(ts).as_deref(), Some("123"));
assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5));
}
#[test]
fn bigint_mixed_type_throws() {
let mut vm = Vm::new();
let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }");
assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError"));
let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }");
assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError"));
}
#[test]
fn bigint_survives_reset_and_gc() {
let mut vm = Vm::new();
let result = run_source(&mut vm, "100n + 23n");
assert!(result.is_bigint());
assert_eq!(vm.bigint_value(result), 123);
vm.reset();
// reset 保留 session 字符串/bigint box:值仍可读。
assert_eq!(vm.bigint_value(result), 123);
}
#[test]
fn bigint_wrapped_and_number_comparison() {
let mut vm = Vm::new();
// 包装对象 coerce 后双 BigInt 运算。
assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n"));
assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n"));
// BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。
assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true));
// NaN 关系比较为 false。
assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false));
// 混合算术抛 TypeError。
let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
}
}