mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: num-bigint 接入任意精度 BigInt(字面量、算术、toString、asIntN/asUintN)
This commit is contained in:
@@ -3,6 +3,11 @@ use crate::vm_trace;
|
||||
use oxide_runtime_api as coercion;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// 判断 JsValue 是否为 BigInt 且值为 0(num_bigint 零值判断)。
|
||||
fn bigint_is_zero(v: &num_bigint::BigInt) -> bool {
|
||||
num_traits::Zero::is_zero(v)
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
@@ -22,36 +27,43 @@ impl Vm {
|
||||
// 必须放在 coerce 之后。
|
||||
let lhs = self.coerce_primitive_bounded(lv, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(rv, false)?;
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
self.regs[rd] = self.concat_strings(lhs, rhs);
|
||||
return Ok(());
|
||||
}
|
||||
if lhs.is_bigint() && rhs.is_bigint() {
|
||||
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) + 2n)。
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
|
||||
return Ok(());
|
||||
}
|
||||
if lhs.is_bigint() != rhs.is_bigint() {
|
||||
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 操作数混合加法必须抛
|
||||
// TypeError(规范不允许 BigInt 与 Number/String 相加)。
|
||||
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 非字符串操作数混合加法
|
||||
// 必须抛 TypeError(规范只允许 String + BigInt 走字符串拼接)。
|
||||
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
|
||||
}
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
|
||||
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
|
||||
// 预分配精确容量:字符串操作数 O(1) 取字节长,一次分配写齐,免 push_str
|
||||
// 几何 realloc 的二次拷贝(字符串拼接热路径的主要额外成本)。
|
||||
// 32 字节余量覆盖数字/布尔等格式化文本(f64 文本最长约 24 字节),
|
||||
// 避免追加非字符串操作数时二次扩容。
|
||||
let mut buf = String::with_capacity(lbytes + rbytes + 32);
|
||||
coercion::push_to_string(lhs, &mut buf);
|
||||
coercion::push_to_string(rhs, &mut buf);
|
||||
let result = self.new_string_owned(buf);
|
||||
self.regs[rd] = result;
|
||||
} else {
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
}
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 字符串拼接热路径:一次预分配写齐两个操作数的 ToString 文本。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 按字符串操作数字节长预分配容量(余量覆盖数字/布尔等格式化文本)。
|
||||
/// 2. 两个操作数依次走 push_to_string(BigInt 输出十进制,Symbol 由调用方先行拒绝)。
|
||||
/// 3. 生成会话字符串写入目标寄存器。
|
||||
///
|
||||
/// # 副作用
|
||||
/// 新建一个会话字符串,登记到 session 生命周期。
|
||||
fn concat_strings(&mut self, lhs: JsValue, rhs: JsValue) -> JsValue {
|
||||
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
|
||||
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
|
||||
let mut buf = String::with_capacity(lbytes + rbytes + 32);
|
||||
coercion::push_to_string(lhs, &mut buf);
|
||||
coercion::push_to_string(rhs, &mut buf);
|
||||
self.new_string_owned(buf)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]);
|
||||
@@ -69,12 +81,14 @@ impl Vm {
|
||||
pub(crate) fn dispatch_unary_plus(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("UNARY_PLUS rd={} r{}={:?}", rd, a, self.regs[a]);
|
||||
let v = self.regs[a];
|
||||
if v.is_bigint() {
|
||||
// 一元 + 对 BigInt 必须抛 TypeError(ToNumber(BigInt) 在隐式路径禁止)。
|
||||
// ToPrimitive 先解盒(BigInt 包装对象如 Object(1n) 的 valueOf 返回 BigInt),
|
||||
// 解盒后是 BigInt 才抛 TypeError——用户覆盖 valueOf/toString 的普通对象不受影响。
|
||||
let prim = self.coerce_primitive_bounded(v, false)?;
|
||||
if prim.is_bigint() {
|
||||
return self.raise_type_error("Cannot convert a BigInt value to a number");
|
||||
}
|
||||
let v = self.coerce_number_bounded(v)?;
|
||||
self.regs[rd] = JsValue::float(v);
|
||||
let n = coercion::to_number(prim);
|
||||
self.regs[rd] = JsValue::float(n);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -92,30 +106,22 @@ impl Vm {
|
||||
}
|
||||
let lhs = self.coerce_primitive_bounded(lv, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(rv, false)?;
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
self.regs[rd] = self.concat_strings(lhs, rhs);
|
||||
return Ok(());
|
||||
}
|
||||
if lhs.is_bigint() && rhs.is_bigint() {
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
|
||||
return Ok(());
|
||||
}
|
||||
if lhs.is_bigint() != rhs.is_bigint() {
|
||||
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 非字符串操作数混合加法
|
||||
// 必须抛 TypeError(规范只允许 String + BigInt 走字符串拼接)。
|
||||
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
|
||||
}
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
|
||||
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
|
||||
// 预分配精确容量:字符串操作数 O(1) 取字节长,一次分配写齐,免 push_str
|
||||
// 几何 realloc 的二次拷贝(字符串拼接热路径的主要额外成本)。
|
||||
// 32 字节余量覆盖数字/布尔等格式化文本(f64 文本最长约 24 字节),
|
||||
// 避免追加非字符串操作数时二次扩容。
|
||||
let mut buf = String::with_capacity(lbytes + rbytes + 32);
|
||||
coercion::push_to_string(lhs, &mut buf);
|
||||
coercion::push_to_string(rhs, &mut buf);
|
||||
let result = self.new_string_owned(buf);
|
||||
self.regs[rd] = result;
|
||||
} else {
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
}
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -186,7 +192,7 @@ impl Vm {
|
||||
}
|
||||
if lv.is_bigint() && rv.is_bigint() {
|
||||
let r = self.bigint_value(rv);
|
||||
if r == 0 {
|
||||
if bigint_is_zero(r) {
|
||||
return self.raise_error_kind("RangeError", "Division by zero");
|
||||
}
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r);
|
||||
@@ -196,7 +202,7 @@ impl Vm {
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
let rv = self.bigint_value(r);
|
||||
if rv == 0 {
|
||||
if bigint_is_zero(rv) {
|
||||
return self.raise_error_kind("RangeError", "Division by zero");
|
||||
}
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(l) / rv);
|
||||
@@ -222,7 +228,7 @@ impl Vm {
|
||||
}
|
||||
if lv.is_bigint() && rv.is_bigint() {
|
||||
let r = self.bigint_value(rv);
|
||||
if r == 0 {
|
||||
if bigint_is_zero(r) {
|
||||
return self.raise_error_kind("RangeError", "Division by zero");
|
||||
}
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r);
|
||||
@@ -232,7 +238,7 @@ impl Vm {
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
let rv = self.bigint_value(r);
|
||||
if rv == 0 {
|
||||
if bigint_is_zero(rv) {
|
||||
return self.raise_error_kind("RangeError", "Division by zero");
|
||||
}
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(l) % rv);
|
||||
@@ -265,6 +271,13 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_inc_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("INC_PRE rd={} a={}", rd, a);
|
||||
if self.regs[rd].is_bigint() {
|
||||
let v = self.bigint_value(self.regs[rd]) + 1;
|
||||
let result = self.new_bigint(v);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
return Ok(());
|
||||
}
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n + 1.0);
|
||||
self.regs[rd] = result;
|
||||
@@ -275,6 +288,12 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_inc_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("INC_POST rd={} a={}", rd, a);
|
||||
if self.regs[rd].is_bigint() {
|
||||
let v = self.bigint_value(self.regs[rd]).clone();
|
||||
self.regs[a] = self.regs[rd];
|
||||
self.regs[rd] = self.new_bigint(v + 1);
|
||||
return Ok(());
|
||||
}
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n + 1.0);
|
||||
@@ -284,6 +303,13 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_dec_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("DEC_PRE rd={} a={}", rd, a);
|
||||
if self.regs[rd].is_bigint() {
|
||||
let v = self.bigint_value(self.regs[rd]).clone() - 1;
|
||||
let result = self.new_bigint(v);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
return Ok(());
|
||||
}
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n - 1.0);
|
||||
self.regs[rd] = result;
|
||||
@@ -294,6 +320,12 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_dec_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
vm_trace!("DEC_POST rd={} a={}", rd, a);
|
||||
if self.regs[rd].is_bigint() {
|
||||
let v = self.bigint_value(self.regs[rd]).clone();
|
||||
self.regs[a] = self.regs[rd];
|
||||
self.regs[rd] = self.new_bigint(v - 1);
|
||||
return Ok(());
|
||||
}
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n - 1.0);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use num_traits::Zero;
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use smallvec::SmallVec;
|
||||
@@ -85,9 +86,9 @@ macro_rules! binary_arith {
|
||||
if lv.is_int() && rv.is_int() {
|
||||
$self.regs[$rd] = JsValue::float(lv.as_int() as f64 $op rv.as_int() as f64);
|
||||
} else if lv.is_bigint() && rv.is_bigint() {
|
||||
let l = $self.bigint_value(lv);
|
||||
let r = $self.bigint_value(rv);
|
||||
if r == 0 {
|
||||
let l = $self.bigint_value(lv).clone();
|
||||
let r = $self.bigint_value(rv).clone();
|
||||
if r.is_zero() {
|
||||
// BigInt 除/模零:规范要求 RangeError(Number 路径走 f64 inf/NaN)。
|
||||
$self.raise_error_kind("RangeError", "Division by zero")?;
|
||||
$self.regs[$rd] = JsValue::undefined();
|
||||
@@ -101,9 +102,9 @@ macro_rules! binary_arith {
|
||||
let r = $self.coerce_primitive_bounded(rv, false)?;
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) / 2n)。
|
||||
let lv = $self.bigint_value(l);
|
||||
let rv = $self.bigint_value(r);
|
||||
if rv == 0 {
|
||||
let lv = $self.bigint_value(l).clone();
|
||||
let rv = $self.bigint_value(r).clone();
|
||||
if rv.is_zero() {
|
||||
$self.raise_error_kind("RangeError", "Division by zero")?;
|
||||
$self.regs[$rd] = JsValue::undefined();
|
||||
} else {
|
||||
@@ -1781,10 +1782,10 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn new_string(&mut self, s: &str) -> JsValue {
|
||||
self.new_string(s)
|
||||
}
|
||||
fn new_bigint(&mut self, v: i128) -> JsValue {
|
||||
fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue {
|
||||
Vm::new_bigint(self, v)
|
||||
}
|
||||
fn bigint_value(&mut self, val: JsValue) -> i128 {
|
||||
fn bigint_value(&mut self, val: JsValue) -> &num_bigint::BigInt {
|
||||
Vm::bigint_value(self, val)
|
||||
}
|
||||
fn kernel_core(&self) -> &Arc<KernelCore> {
|
||||
@@ -1983,11 +1984,11 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec<Arc<Compiled
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{opcode, JsValue, TryHandler, Vm};
|
||||
use std::sync::Arc;
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::object::NativeFnPtr;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::int(7))
|
||||
|
||||
@@ -13,10 +13,10 @@ use std::collections::HashMap;
|
||||
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use oxide_types::object::{JsObject, JsString};
|
||||
use oxide_types::value::JsValue;
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm::ForInIter;
|
||||
use oxide_types::object::{JsObject, JsString};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// session arena 与 GC 簿记。
|
||||
///
|
||||
@@ -32,7 +32,7 @@ pub(crate) struct GcState {
|
||||
/// BigInt 堆 box(`Box<i128>`)追踪表。`RefCell` 使 `&self` 的
|
||||
/// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与
|
||||
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。
|
||||
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut i128>>,
|
||||
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut num_bigint::BigInt>>,
|
||||
pub(crate) session_bytes_allocated: usize,
|
||||
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||
}
|
||||
|
||||
@@ -484,23 +484,23 @@ impl Vm {
|
||||
///
|
||||
/// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。
|
||||
/// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。
|
||||
pub fn new_bigint(&self, v: i128) -> JsValue {
|
||||
pub fn new_bigint(&self, v: num_bigint::BigInt) -> 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 {
|
||||
pub fn bigint_value(&self, val: JsValue) -> &num_bigint::BigInt {
|
||||
// SAFETY: bigint 指针由 new_bigint 经 Box::into_raw 产生,存活至 full_reset。
|
||||
unsafe { *val.as_bigint_ptr() }
|
||||
unsafe { &*val.as_bigint_ptr() }
|
||||
}
|
||||
|
||||
/// 释放全部 session 堆 `i128` box。仅在完全隔离重置(`full_reset`)时调用,
|
||||
/// 释放全部 session 堆 BigInt 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)),
|
||||
// SAFETY: 每个指针来自 new_bigint 的 Box::into_raw(Box::new(BigInt)),
|
||||
// 且只在这里恰好释放一次。
|
||||
unsafe {
|
||||
drop(Box::from_raw(ptr));
|
||||
@@ -512,7 +512,7 @@ impl Vm {
|
||||
match constant {
|
||||
Constant::Number(v) => JsValue::float(*v),
|
||||
Constant::Int(v) => JsValue::int(*v),
|
||||
Constant::BigInt(v) => self.new_bigint(*v),
|
||||
Constant::BigInt(v) => self.new_bigint(v.clone()),
|
||||
Constant::String(s) => self.perm_string(s),
|
||||
Constant::Boolean(b) => JsValue::bool(*b),
|
||||
Constant::Null => JsValue::null(),
|
||||
@@ -835,10 +835,10 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
let result = run_source(&mut vm, "100n + 23n");
|
||||
assert!(result.is_bigint());
|
||||
assert_eq!(vm.bigint_value(result), 123);
|
||||
assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
|
||||
vm.reset();
|
||||
// reset 保留 session 字符串/bigint box:值仍可读。
|
||||
assert_eq!(vm.bigint_value(result), 123);
|
||||
assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user