feat: arbitrary-precision BigInt via num-bigint (literals, arithmetic, toString, asIntN/asUintN)
This commit is contained in:
Generated
+9
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<f64,
|
||||
}
|
||||
};
|
||||
if prim.is_symbol() || prim.is_bigint() {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Cannot convert a Symbol or BigInt value to a number",
|
||||
));
|
||||
return Err(crate::error::create_type_error(vm, "Cannot convert a Symbol or BigInt value to a number"));
|
||||
}
|
||||
Ok(oxide_runtime_api::to_number(prim))
|
||||
}
|
||||
@@ -233,7 +210,13 @@ fn to_index<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<usize, JsValue> {
|
||||
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<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
|
||||
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<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
|
||||
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<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
|
||||
fn string_to_bigint<H: VmHost>(vm: &mut H, s: &str) -> Result<JsValue, JsValue> {
|
||||
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<H: VmHost>(vm: &mut H, s: &str) -> Result<JsValue, JsValue>
|
||||
"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 }))
|
||||
}
|
||||
|
||||
@@ -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<i128> {
|
||||
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<H: VmHost>(vm: &mut H, args: &[u8], proto_ptr: *const JsObject)
|
||||
|
||||
fn make_instant<H: VmHost>(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<H: VmHost>(vm: &mut H, epoch_ns: i128) -> NativeResult {
|
||||
|
||||
fn make_zoned_date_time<H: VmHost>(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<H: VmHost>(vm: &mut H, raw: JsValue) -> Result<i128, JsVa
|
||||
let primitive = oxide_runtime_api::to_primitive(raw, oxide_runtime_api::ToPrimitiveHint::Number, vm)
|
||||
.map_err(|error| native_engine_error(vm, &error))?;
|
||||
if primitive.is_bigint() {
|
||||
return Ok(vm.bigint_value(primitive));
|
||||
return Ok(vm.bigint_value(primitive).to_i128().unwrap_or(i128::MAX));
|
||||
}
|
||||
if primitive.is_bool() {
|
||||
return Ok(i128::from(primitive.as_bool()));
|
||||
@@ -483,7 +484,7 @@ pub fn instant_constructor<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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(
|
||||
|
||||
@@ -8,3 +8,4 @@ description = "OxideJS - Shared bytecode protocol and compiled module ABI"
|
||||
|
||||
[dependencies]
|
||||
oxide_log = { path = "../oxide_log" }
|
||||
num-bigint = "0.4"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,4 +13,5 @@ oxide_ir = { path = "../oxide_ir" }
|
||||
oxide_log = { path = "../oxide_log" }
|
||||
regex.workspace = true
|
||||
regress.workspace = true
|
||||
num-bigint = "0.4"
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -37,10 +37,12 @@ impl Emitter {
|
||||
fn emit_bigint_literal_expression(
|
||||
&self, b: &oxide_parser::BigIntLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
let value = b
|
||||
.value
|
||||
.parse::<i128>()
|
||||
.map_err(|_| format!("BigInt literal out of range: {}", b.value))?;
|
||||
let value = if let Ok(v) = b.value.parse::<i128>() {
|
||||
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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<KernelCore>;
|
||||
@@ -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::<f64>().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<H: VmHost>(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<H: VmHost>(val: JsValue, host: &mut H) -> Result<JsValue, Strin
|
||||
(P::as_ptr(&world.boolean_proto) as *mut JsObject, JsObject::OBJ_TYPE_BOOLEAN_OBJ)
|
||||
} else if val.is_bigint() {
|
||||
(P::as_ptr(&world.bigint_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN)
|
||||
} else if val.is_symbol() {
|
||||
(P::as_ptr(&world.symbol_proto) as *mut JsObject, JsObject::OBJ_TYPE_SYMBOL_OBJ)
|
||||
} else {
|
||||
(P::as_ptr(&world.object_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN)
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ bumpalo.workspace = true
|
||||
hashbrown.workspace = true
|
||||
oxide_log = { path = "../oxide_log" }
|
||||
rustc-hash.workspace = true
|
||||
num-bigint = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest.workspace = true
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::object::{JsObject, JsString};
|
||||
use num_bigint::BigInt;
|
||||
|
||||
/// 静默 NaN 前缀——bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1)
|
||||
const QNAN_PREFIX: u64 = 0xFFF8_0000_0000_0000;
|
||||
@@ -308,7 +309,7 @@ impl JsValue {
|
||||
/// 与 JsString 同机制:i128 本体存于 VM 管理的堆 box,这里携带指针。
|
||||
/// tag 7 原为 NaN 规范化编码,NaN 已改用普通 quiet NaN 位模式,故 tag 7
|
||||
/// 空出给 BigInt。
|
||||
pub fn bigint(ptr: *const i128) -> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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