diff --git a/Cargo.lock b/Cargo.lock index 9bc37664..50771e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,6 +1101,8 @@ dependencies = [ "fast-float", "indexmap", "memchr", + "num-bigint", + "num-traits", "oxide_compiler", "oxide_kernel", "oxide_log", @@ -1119,6 +1121,7 @@ dependencies = [ name = "oxide_bytecode" version = "0.1.0" dependencies = [ + "num-bigint", "oxide_log", ] @@ -1198,6 +1201,7 @@ dependencies = [ name = "oxide_emit" version = "0.1.0" dependencies = [ + "num-bigint", "oxide_bytecode", "oxide_ir", "oxide_log", @@ -1285,6 +1289,8 @@ dependencies = [ name = "oxide_runtime_api" version = "0.1.0" dependencies = [ + "num-bigint", + "num-traits", "oxide_kernel", "oxide_log", "oxide_types", @@ -1313,6 +1319,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "hashbrown 0.14.5", + "num-bigint", "oxide_log", "proptest", "rustc-hash", @@ -1328,6 +1335,8 @@ dependencies = [ "fast-float", "indexmap", "memchr", + "num-bigint", + "num-traits", "oxide_builtins", "oxide_bytecode", "oxide_compiler", diff --git a/crates/oxide_builtins/Cargo.toml b/crates/oxide_builtins/Cargo.toml index 1e498297..5ce3330e 100644 --- a/crates/oxide_builtins/Cargo.toml +++ b/crates/oxide_builtins/Cargo.toml @@ -20,6 +20,8 @@ serde_json.workspace = true chrono.workspace = true unicode-normalization.workspace = true indexmap.workspace = true +num-bigint = "0.4" +num-traits = "0.2" [dev-dependencies] oxide_vm = { path = "../oxide_vm" } diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs index d0bea0ed..6fe41a88 100644 --- a/crates/oxide_builtins/src/bigint.rs +++ b/crates/oxide_builtins/src/bigint.rs @@ -1,3 +1,4 @@ +use num_bigint::BigInt; use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost}; use oxide_types::value::JsValue; @@ -10,15 +11,12 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); if this_val.is_object() { // new 语义:BigInt 不可 new,抛 TypeError。 - return NativeResult::Err(crate::error::create_type_error( - vm, - "BigInt is not a constructor", - )); + return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor")); } } // 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。 if args.len() <= 1 { - return NativeResult::Ok(vm.new_bigint(0)); + return NativeResult::Ok(vm.new_bigint(BigInt::from(0))); } let val = vm.reg(args[1]); // ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString @@ -29,15 +27,12 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { if let Some(exc) = vm.take_uncaught_value() { return NativeResult::Err(exc); } - return NativeResult::Err(crate::error::create_type_error( - vm, - "Cannot convert value to a BigInt", - )); + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt")); } }; // 步骤 3:Type(prim) 为 Number → NumberToBigInt。 if prim.is_int() { - return NativeResult::Ok(vm.new_bigint(prim.as_int() as i128)); + return NativeResult::Ok(vm.new_bigint(BigInt::from(prim.as_int()))); } if prim.is_double() { let d = prim.as_double(); @@ -47,14 +42,7 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { "The number cannot be converted to a BigInt because it is not an integer", )); } - // i128 暂存上限:超出范围的整数后续由任意精度表示补齐。 - if d < i128::MIN as f64 || d > i128::MAX as f64 { - return NativeResult::Err(crate::error::create_range_error( - vm, - "The number is too large to be converted to a BigInt", - )); - } - return NativeResult::Ok(vm.new_bigint(d.trunc() as i128)); + return NativeResult::Ok(vm.new_bigint(BigInt::from(d.trunc() as i128))); } // 步骤 4:其它原始类型走 ToBigInt。 match to_bigint(vm, prim) { @@ -99,7 +87,7 @@ pub fn bigint_to_string(vm: &mut H, args: &[u8]) -> NativeResult { } else { 10u32 }; - let text = i128_to_radix_string(vm.bigint_value(v), radix); + let text = vm.bigint_value(v).to_str_radix(radix); NativeResult::Ok(vm.new_string(&text)) } @@ -131,20 +119,16 @@ pub fn bigint_as_int_n(vm: &mut H, args: &[u8]) -> NativeResult { }; let v = vm.bigint_value(val); if bits == 0 { - return NativeResult::Ok(vm.new_bigint(0)); + return NativeResult::Ok(vm.new_bigint(BigInt::from(0))); } - if bits >= 127 { - // i128 表示范围内 `2^bits` 恒大于 |v|:取模后符号位折叠回原值。 - return NativeResult::Ok(val); - } - let modulus = 1i128 << bits; - let half = 1i128 << (bits - 1); - let mut m = v % modulus; - if m < 0 { - m += modulus; + let modulus = BigInt::from(1) << bits; + let half = BigInt::from(1) << (bits - 1); + let mut m = v % &modulus; + if m.sign() == num_bigint::Sign::Minus { + m += &modulus; } if m >= half { - m -= modulus; + return NativeResult::Ok(vm.new_bigint(m - modulus)); } NativeResult::Ok(vm.new_bigint(m)) } @@ -162,16 +146,12 @@ pub fn bigint_as_uint_n(vm: &mut H, args: &[u8]) -> NativeResult { }; let v = vm.bigint_value(val); if bits == 0 { - return NativeResult::Ok(vm.new_bigint(0)); + return NativeResult::Ok(vm.new_bigint(BigInt::from(0))); } - if bits >= 128 { - // i128 上限:非负值取模不变;负值结果超出表示范围,待任意精度补齐。 - return NativeResult::Ok(val); - } - let modulus = 1i128 << bits; - let mut m = v % modulus; - if m < 0 { - m += modulus; + let modulus = BigInt::from(1) << bits; + let mut m = v % &modulus; + if m.sign() == num_bigint::Sign::Minus { + m += &modulus; } NativeResult::Ok(vm.new_bigint(m)) } @@ -216,10 +196,7 @@ fn coerce_number_or_throw(vm: &mut H, value: JsValue) -> Result(vm: &mut H, args: &[u8]) -> Result { return Ok(0); } // ToIntegerOrInfinity 先截断(-0.9 → -0,不越界);再检查有限且 [0, 2^53-1]。 - let int = if n == 0.0 { 0.0 } else if n.is_infinite() { n } else { n.trunc() }; + let int = if n == 0.0 { + 0.0 + } else if n.is_infinite() { + n + } else { + n.trunc() + }; if !(0.0..=9007199254740991.0).contains(&int) { return Err(crate::error::create_range_error(vm, "Invalid index")); } @@ -256,21 +239,15 @@ fn to_bigint(vm: &mut H, val: JsValue) -> Result { return Ok(val); } if val.is_bool() { - return Ok(vm.new_bigint(if val.as_bool() { 1 } else { 0 })); + return Ok(vm.new_bigint(BigInt::from(if val.as_bool() { 1 } else { 0 }))); } if val.is_int() { // ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再 // NumberToBigInt;ToBigInt 直接拒绝 Number)。 - return Err(crate::error::create_type_error( - vm, - "Cannot convert a Number value to a BigInt", - )); + return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt")); } if val.is_double() { - return Err(crate::error::create_type_error( - vm, - "Cannot convert a Number value to a BigInt", - )); + return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt")); } if val.is_string() { let s = unsafe { oxide_runtime_api::string_data(val) }.to_string(); @@ -286,19 +263,13 @@ fn to_bigint(vm: &mut H, val: JsValue) -> Result { if let Some(exc) = vm.take_uncaught_value() { return Err(exc); } - return Err(crate::error::create_type_error( - vm, - "Cannot convert value to a BigInt", - )); + return Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt")); } }; return to_bigint(vm, prim); } // undefined / null / symbol。 - Err(crate::error::create_type_error( - vm, - "Cannot convert value to a BigInt", - )) + Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt")) } /// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; @@ -306,7 +277,7 @@ fn to_bigint(vm: &mut H, val: JsValue) -> Result { fn string_to_bigint(vm: &mut H, s: &str) -> Result { let trimmed = s.trim(); if trimmed.is_empty() { - return Ok(vm.new_bigint(0)); + return Ok(vm.new_bigint(BigInt::from(0))); } let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') { (true, r) @@ -350,35 +321,8 @@ fn string_to_bigint(vm: &mut H, s: &str) -> Result "Cannot convert string to BigInt: invalid integer literal", )); } - match i128::from_str_radix(digits, radix) { - Ok(m) => Ok(vm.new_bigint(if neg { -m } else { m })), - Err(_) => Err(crate::error::create_syntax_error( - vm, - "Cannot convert string to BigInt: invalid integer literal", - )), - } -} - -/// 把 i128 按 radix 转成字符串(含负号前缀)。 -fn i128_to_radix_string(value: i128, radix: u32) -> String { - let neg = value < 0; - let mut mag = value.unsigned_abs(); - let chars = b"0123456789abcdefghijklmnopqrstuvwxyz"; - let mut result = String::new(); - if mag == 0 { - result.push('0'); - } else { - let mut digits = Vec::new(); - while mag > 0 { - digits.push(chars[(mag % radix as u128) as usize] as char); - mag /= radix as u128; - } - for ch in digits.iter().rev() { - result.push(*ch); - } - } - if neg { - result.insert(0, '-'); - } - result + let m = BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| { + crate::error::create_syntax_error(vm, "Cannot convert string to BigInt: invalid integer literal") + })?; + Ok(vm.new_bigint(if neg { -m } else { m })) } diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 410cdc1f..d79b844b 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -1,5 +1,6 @@ use chrono::{Datelike, Days, NaiveDate, Utc}; +use num_traits::ToPrimitive; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api::{to_number, to_string, NativeResult, VmHost}; use oxide_types::object::JsObject; @@ -35,7 +36,7 @@ fn get_double_prop(obj: &JsObject, pos: usize) -> f64 { fn get_instant_epoch_ns(obj: &JsObject) -> Option { let value = obj.get_prop_at(0); if value.is_bigint() { - return Some(unsafe { oxide_runtime_api::bigint_data(value) }); + return Some(unsafe { oxide_runtime_api::bigint_data(value) }.to_i128().unwrap_or(i128::MAX)); } if value.is_int() || value.is_double() { return Some(to_number(value) as i128); @@ -136,7 +137,7 @@ fn is_ctor_call(vm: &mut H, args: &[u8], proto_ptr: *const JsObject) fn make_instant(vm: &mut H, epoch_ns: i128) -> NativeResult { let proto = JsValue::from_js_object(vm.session().builtin_world().instant_proto.as_ptr() as *mut JsObject); - let epoch_value = vm.new_bigint(epoch_ns); + let epoch_value = vm.new_bigint(num_bigint::BigInt::from(epoch_ns)); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); obj.type_tag = JsObject::OBJ_TYPE_INSTANT; obj.set_prop_at(0, epoch_value); @@ -145,7 +146,7 @@ fn make_instant(vm: &mut H, epoch_ns: i128) -> NativeResult { fn make_zoned_date_time(vm: &mut H, epoch_ns: i128, time_zone_id: &str) -> NativeResult { let proto = JsValue::from_js_object(vm.session().builtin_world().zoned_date_time_proto.as_ptr() as *mut JsObject); - let epoch_value = vm.new_bigint(epoch_ns); + let epoch_value = vm.new_bigint(num_bigint::BigInt::from(epoch_ns)); let time_zone_value = vm.new_string(time_zone_id); let calendar_value = vm.new_string("iso8601"); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); @@ -421,7 +422,7 @@ fn primitive_to_bigint(vm: &mut H, raw: JsValue) -> Result(vm: &mut H, args: &[u8]) -> NativeResult { if epoch_ns.unsigned_abs() > MAX_INSTANT_NS as u128 { return NativeResult::Err(crate::error::create_range_error(vm, "Instant outside supported range")); } - let epoch_value = vm.new_bigint(epoch_ns); + let epoch_value = vm.new_bigint(num_bigint::BigInt::from(epoch_ns)); initialize_temporal_receiver(vm, args, JsObject::OBJ_TYPE_INSTANT, [epoch_value]) } @@ -588,7 +589,7 @@ pub fn instant_epoch_nanoseconds(vm: &mut H, args: &[u8]) -> NativeRe NativeResult::Ok(value) } else { match get_instant_epoch_ns(obj) { - Some(epoch_ns) => NativeResult::Ok(vm.new_bigint(epoch_ns)), + Some(epoch_ns) => NativeResult::Ok(vm.new_bigint(num_bigint::BigInt::from(epoch_ns))), None => NativeResult::Err(crate::error::create_range_error(vm, "invalid Instant")), } } @@ -1290,7 +1291,7 @@ pub fn zoned_date_time_constructor(vm: &mut H, args: &[u8]) -> Native return NativeResult::Err(crate::error::create_range_error(vm, "invalid calendar")); } } - let epoch_value = vm.new_bigint(epoch_ns); + let epoch_value = vm.new_bigint(num_bigint::BigInt::from(epoch_ns)); let time_zone_value = vm.new_string(&time_zone_id); let calendar_value = vm.new_string("iso8601"); initialize_temporal_receiver( diff --git a/crates/oxide_bytecode/Cargo.toml b/crates/oxide_bytecode/Cargo.toml index 298cb98e..3f641159 100644 --- a/crates/oxide_bytecode/Cargo.toml +++ b/crates/oxide_bytecode/Cargo.toml @@ -8,3 +8,4 @@ description = "OxideJS - Shared bytecode protocol and compiled module ABI" [dependencies] oxide_log = { path = "../oxide_log" } +num-bigint = "0.4" diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index 65ea246e..057984f8 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -16,7 +16,7 @@ use crate::opcode::{self, OpCode}; pub enum Constant { Number(f64), Int(i32), - BigInt(i128), + BigInt(num_bigint::BigInt), String(String), Boolean(bool), Null, diff --git a/crates/oxide_emit/Cargo.toml b/crates/oxide_emit/Cargo.toml index 251d3053..30b938ee 100644 --- a/crates/oxide_emit/Cargo.toml +++ b/crates/oxide_emit/Cargo.toml @@ -13,4 +13,5 @@ oxide_ir = { path = "../oxide_ir" } oxide_log = { path = "../oxide_log" } regex.workspace = true regress.workspace = true +num-bigint = "0.4" diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 498641f4..a8cb5956 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -242,7 +242,7 @@ impl ParamSpec<'_> { enum ConstantKey { Number(u64), Int(i32), - BigInt(i128), + BigInt(num_bigint::BigInt), String(String), Boolean(bool), Null, @@ -643,7 +643,7 @@ impl ConstantKey { match value { Constant::Number(v) => Some(Self::Number(v.to_bits())), Constant::Int(v) => Some(Self::Int(*v)), - Constant::BigInt(v) => Some(Self::BigInt(*v)), + Constant::BigInt(v) => Some(Self::BigInt(v.clone())), Constant::String(v) => Some(Self::String(v.clone())), Constant::Boolean(v) => Some(Self::Boolean(*v)), Constant::Null => Some(Self::Null), @@ -673,12 +673,7 @@ impl Emitter { let msg_idx = ctx.add_constant(Constant::String(msg.to_string())); ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx)); let exc_reg = ctx.alloc_reg(); - ctx.inst(Inst::new_expression( - Operand::Reg(exc_reg), - Operand::Reg(ctor), - Operand::Reg(msg_reg), - 1, - )); + ctx.inst(Inst::new_expression(Operand::Reg(exc_reg), Operand::Reg(ctor), Operand::Reg(msg_reg), 1)); ctx.inst(Inst::new(OpCode::THROW, Operand::Reg(exc_reg), Operand::None, Operand::None)); let dummy = ctx.alloc_reg(); let undef_idx = ctx.add_constant(Constant::Undefined); diff --git a/crates/oxide_emit/src/expr/literal.rs b/crates/oxide_emit/src/expr/literal.rs index 5362cd4b..2df1145b 100644 --- a/crates/oxide_emit/src/expr/literal.rs +++ b/crates/oxide_emit/src/expr/literal.rs @@ -37,10 +37,12 @@ impl Emitter { fn emit_bigint_literal_expression( &self, b: &oxide_parser::BigIntLiteral, ctx: &mut CompileCtx, ) -> Result { - let value = b - .value - .parse::() - .map_err(|_| format!("BigInt literal out of range: {}", b.value))?; + let value = if let Ok(v) = b.value.parse::() { + num_bigint::BigInt::from(v) + } else { + num_bigint::BigInt::parse_bytes(b.value.as_bytes(), 10) + .ok_or_else(|| format!("BigInt literal out of range: {}", b.value))? + }; let idx = ctx.add_constant(Constant::BigInt(value)); let r = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(r), idx)); diff --git a/crates/oxide_runtime_api/Cargo.toml b/crates/oxide_runtime_api/Cargo.toml index 37e3c3fb..b6daae9a 100644 --- a/crates/oxide_runtime_api/Cargo.toml +++ b/crates/oxide_runtime_api/Cargo.toml @@ -8,6 +8,8 @@ description = "OxideJS - VmHost trait: the abstract Vm interface builtins are wr [dependencies] oxide_types = { path = "../oxide_types" } +num-bigint = "0.4" +num-traits = "0.2" oxide_kernel = { path = "../oxide_kernel" } oxide_log = { path = "../oxide_log" } ryu.workspace = true diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index b567606b..c3381f0a 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -15,6 +15,7 @@ mod runtime_api_log; use std::cmp::Ordering; use std::sync::Arc; +use num_traits::{ToPrimitive, Zero}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::mem::{Epoch, P}; use oxide_types::object::{JsObject, PropAttributes}; @@ -74,10 +75,10 @@ pub trait VmHost { // 对象分配 / 字符串创建 fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject; fn new_string(&mut self, s: &str) -> JsValue; - /// 分配 BigInt 值(`i128` box 登记到 VM,返回携带指针的 `JsValue`)。 - fn new_bigint(&mut self, v: i128) -> JsValue; + /// 分配 BigInt 值(num_bigint::BigInt box 登记到 VM,返回携带指针的 `JsValue`)。 + fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue; /// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。 - fn bigint_value(&mut self, val: JsValue) -> i128; + fn bigint_value(&mut self, val: JsValue) -> &num_bigint::BigInt; // 内核访问器 fn kernel_core(&self) -> &Arc; @@ -167,20 +168,29 @@ pub fn string_value_eq(a: JsValue, b: JsValue) -> bool { sa.data == sb.data } +/// BigInt 转 f64 的近似转换(连续整数用 to_u64,大数用 Display 解析)。 +#[inline] +pub fn bigint_to_f64(v: &num_bigint::BigInt) -> f64 { + if let Some(u) = v.to_u64() { + return u as f64; + } + v.to_string().parse::().unwrap_or(f64::INFINITY) +} + /// 借出 BigInt 值。 /// /// # Safety -/// `val` 必须是 BigInt `JsValue`,且其 `i128` box 存活(VM 在 full_reset 前保证)。 +/// `val` 必须是 BigInt `JsValue`,且其 `num_bigint::BigInt` box 存活(VM 在 full_reset 前保证)。 #[inline] -pub unsafe fn bigint_data(val: JsValue) -> i128 { - *val.as_bigint_ptr() +pub unsafe fn bigint_data(val: JsValue) -> &'static num_bigint::BigInt { + &*val.as_bigint_ptr() } -/// BigInt 的十进制字符串表示(`i128` → 十进制)。 +/// BigInt 的十进制字符串表示(num_bigint::BigInt → 十进制)。 /// /// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。 #[inline] -pub fn bigint_to_string(v: i128) -> String { +pub fn bigint_to_string(v: &num_bigint::BigInt) -> String { v.to_string() } @@ -211,7 +221,7 @@ pub fn to_number(val: JsValue) -> f64 { if val.is_bigint() { // BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。 // 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。 - return unsafe { bigint_data(val) } as f64; + return bigint_to_f64(unsafe { bigint_data(val) }); } if val.is_object() { return f64::NAN; @@ -443,7 +453,7 @@ pub fn to_string(val: JsValue) -> String { return unsafe { string_data(val) }.to_string(); } if val.is_bigint() { - return unsafe { bigint_data(val) }.to_string(); + return bigint_to_string(unsafe { bigint_data(val) }); } if val.is_object() { return "[object]".to_string(); @@ -470,7 +480,7 @@ pub fn to_boolean(val: JsValue) -> bool { return !unsafe { (*val.as_string_ptr()).is_empty() }; } if val.is_bigint() { - return unsafe { bigint_data(val) } != 0; + return !unsafe { bigint_data(val) }.is_zero(); } if val.is_object() { return true; @@ -529,19 +539,19 @@ pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul } // BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。 if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { - return Ok(unsafe { bigint_data(lhs) } as f64 == to_number(rhs)); + 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) == unsafe { bigint_data(rhs) } as f64); + 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(unsafe { bigint_data(lhs) } as f64 == r); + 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 == unsafe { bigint_data(rhs) } as f64); + return Ok(l == bigint_to_f64(unsafe { bigint_data(rhs) })); } // 步骤 5-6:Number 与 String。 if (lhs.is_int() || lhs.is_double()) && rhs.is_string() { @@ -685,14 +695,18 @@ fn to_f64(val: JsValue) -> f64 { /// BigInt 与 Number 的序比较:Number 为整数且落于 i128 范围时精确比较, /// 否则退化为 f64 近似比较(非有限/超出 i128 范围时)。 -fn bigint_cmp_number(big: i128, num: JsValue) -> Ordering { +fn bigint_cmp_number(big: &num_bigint::BigInt, num: JsValue) -> Ordering { number_cmp_bigint(num, big).reverse() } /// Number 与 BigInt 的序比较(`num` 与 `big` 的 `<`/`=`/`>`)。 -fn number_cmp_bigint(num: JsValue, big: i128) -> Ordering { +fn number_cmp_bigint(num: JsValue, big: &num_bigint::BigInt) -> Ordering { if num.is_int() { - return (num.as_int() as i128).cmp(&big); + return (num.as_int() as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { + i128::MIN + } else { + i128::MAX + })); } let d = num.as_double(); if d.is_nan() { @@ -703,9 +717,13 @@ fn number_cmp_bigint(num: JsValue, big: i128) -> Ordering { } let truncated = d.trunc(); if truncated != d || truncated.abs() > i128::MAX as f64 { - return (d).partial_cmp(&(big as f64)).unwrap_or(Ordering::Equal); + return (d).partial_cmp(&bigint_to_f64(big)).unwrap_or(Ordering::Equal); } - (truncated as i128).cmp(&big) + (truncated as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { + i128::MIN + } else { + i128::MAX + })) } /// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。 @@ -820,6 +838,8 @@ pub fn to_object(val: JsValue, host: &mut H) -> Result Self { + pub fn bigint(ptr: *const BigInt) -> Self { let addr = ptr as u64; debug_assert!(addr <= PTR_MASK, "bigint pointer must fit in 48 bits"); Self(make_tag(TAG_BIGINT) | addr) @@ -320,9 +321,9 @@ impl JsValue { } /// 解出 BigInt 底层指针;调用方须先保证 [`is_bigint`](JsValue::is_bigint)。 - pub fn as_bigint_ptr(&self) -> *const i128 { + pub fn as_bigint_ptr(&self) -> *const BigInt { debug_assert!(self.is_bigint()); - (self.0 & PTR_MASK) as *const i128 + (self.0 & PTR_MASK) as *const BigInt } } @@ -365,7 +366,7 @@ impl fmt::Display for JsValue { write!(f, "Symbol(idx={})", self.as_symbol_index()) } else if self.is_bigint() { // SAFETY: bigint 指针指向存活的 i128。 - write!(f, "BigInt({})", unsafe { *self.as_bigint_ptr() }) + write!(f, "BigInt({})", unsafe { &*self.as_bigint_ptr() }) } else { write!(f, "{{unknown}}") } @@ -397,7 +398,7 @@ impl fmt::Debug for JsValue { write!(f, "JsValue(Symbol(idx={}))", self.as_symbol_index()) } else if self.is_bigint() { // SAFETY: bigint 指针指向存活的 i128。 - write!(f, "JsValue(BigInt({}))", unsafe { *self.as_bigint_ptr() }) + write!(f, "JsValue(BigInt({}))", unsafe { &*self.as_bigint_ptr() }) } else { write!(f, "JsValue(Unknown)") } @@ -407,6 +408,7 @@ impl fmt::Debug for JsValue { #[cfg(test)] mod tests { use super::JsValue; + use num_bigint::BigInt; use proptest::prelude::*; use proptest::test_runner::TestRunner; @@ -534,27 +536,27 @@ mod tests { #[test] fn bigint_pointer_roundtrip() { - let boxed = Box::new(123_i128); - let ptr: *const i128 = &*boxed; + let boxed = Box::new(BigInt::from(123)); + let ptr: *const BigInt = &*boxed; let val = JsValue::bigint(ptr); assert!(val.is_bigint()); assert_eq!(val.as_bigint_ptr(), ptr); - assert_eq!(unsafe { *val.as_bigint_ptr() }, 123); + assert_eq!(unsafe { &*val.as_bigint_ptr() }, &BigInt::from(123)); assert!(!val.is_double()); assert!(!val.is_int()); } #[test] fn bigint_value_equality_by_value() { - let a = JsValue::bigint(Box::into_raw(Box::new(7_i128))); - let b = JsValue::bigint(Box::into_raw(Box::new(7_i128))); - let c = JsValue::bigint(Box::into_raw(Box::new(8_i128))); + let a = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7)))); + let b = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7)))); + let c = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(8)))); assert_eq!(a, b); assert_ne!(a, c); unsafe { - drop(Box::from_raw(a.as_bigint_ptr() as *mut i128)); - drop(Box::from_raw(b.as_bigint_ptr() as *mut i128)); - drop(Box::from_raw(c.as_bigint_ptr() as *mut i128)); + drop(Box::from_raw(a.as_bigint_ptr() as *mut BigInt)); + drop(Box::from_raw(b.as_bigint_ptr() as *mut BigInt)); + drop(Box::from_raw(c.as_bigint_ptr() as *mut BigInt)); } } } diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index 16f6c1f7..2a4cbdfd 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -26,6 +26,8 @@ indexmap.workspace = true smallvec.workspace = true unicode-normalization.workspace = true rustc-hash.workspace = true +num-bigint = "0.4" +num-traits = "0.2" [dev-dependencies] proptest.workspace = true diff --git a/crates/oxide_vm/src/dispatch/arithmetic.rs b/crates/oxide_vm/src/dispatch/arithmetic.rs index bf57a088..3a6b7734 100644 --- a/crates/oxide_vm/src/dispatch/arithmetic.rs +++ b/crates/oxide_vm/src/dispatch/arithmetic.rs @@ -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); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index ddf041a0..e9875e5c 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -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 { @@ -1983,11 +1984,11 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec NativeResult { NativeResult::Ok(JsValue::int(7)) diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index 593508e2..872dea4e 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -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`)追踪表。`RefCell` 使 `&self` 的 /// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与 /// mark/sweep 回收(值量少),只在 full_reset 统一释放。 - pub(crate) session_bigint_ptrs: RefCell>, + pub(crate) session_bigint_ptrs: RefCell>, pub(crate) session_bytes_allocated: usize, pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 39adf163..c7f2c06d 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -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]