feat: num-bigint 接入任意精度 BigInt(字面量、算术、toString、asIntN/asUintN)

This commit is contained in:
2026-08-12 03:15:18 +08:00
parent 4474702735
commit 1824ef0df1
18 changed files with 227 additions and 212 deletions
Generated
+9
View File
@@ -1101,6 +1101,8 @@ dependencies = [
"fast-float", "fast-float",
"indexmap", "indexmap",
"memchr", "memchr",
"num-bigint",
"num-traits",
"oxide_compiler", "oxide_compiler",
"oxide_kernel", "oxide_kernel",
"oxide_log", "oxide_log",
@@ -1119,6 +1121,7 @@ dependencies = [
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"num-bigint",
"oxide_log", "oxide_log",
] ]
@@ -1198,6 +1201,7 @@ dependencies = [
name = "oxide_emit" name = "oxide_emit"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"num-bigint",
"oxide_bytecode", "oxide_bytecode",
"oxide_ir", "oxide_ir",
"oxide_log", "oxide_log",
@@ -1285,6 +1289,8 @@ dependencies = [
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"num-bigint",
"num-traits",
"oxide_kernel", "oxide_kernel",
"oxide_log", "oxide_log",
"oxide_types", "oxide_types",
@@ -1313,6 +1319,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
"num-bigint",
"oxide_log", "oxide_log",
"proptest", "proptest",
"rustc-hash", "rustc-hash",
@@ -1328,6 +1335,8 @@ dependencies = [
"fast-float", "fast-float",
"indexmap", "indexmap",
"memchr", "memchr",
"num-bigint",
"num-traits",
"oxide_builtins", "oxide_builtins",
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
+2
View File
@@ -20,6 +20,8 @@ serde_json.workspace = true
chrono.workspace = true chrono.workspace = true
unicode-normalization.workspace = true unicode-normalization.workspace = true
indexmap.workspace = true indexmap.workspace = true
num-bigint = "0.4"
num-traits = "0.2"
[dev-dependencies] [dev-dependencies]
oxide_vm = { path = "../oxide_vm" } oxide_vm = { path = "../oxide_vm" }
+37 -93
View File
@@ -1,3 +1,4 @@
use num_bigint::BigInt;
use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost}; use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost};
use oxide_types::value::JsValue; 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]); let this_val = vm.reg(args[0]);
if this_val.is_object() { if this_val.is_object() {
// new 语义:BigInt 不可 new,抛 TypeError。 // new 语义:BigInt 不可 new,抛 TypeError。
return NativeResult::Err(crate::error::create_type_error( return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor"));
vm,
"BigInt is not a constructor",
));
} }
} }
// 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。 // 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。
if args.len() <= 1 { 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]); let val = vm.reg(args[1]);
// ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString // 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() { if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc); return NativeResult::Err(exc);
} }
return NativeResult::Err(crate::error::create_type_error( return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt"));
vm,
"Cannot convert value to a BigInt",
));
} }
}; };
// 步骤 3:Type(prim) 为 Number → NumberToBigInt。 // 步骤 3:Type(prim) 为 Number → NumberToBigInt。
if prim.is_int() { 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() { if prim.is_double() {
let d = prim.as_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", "The number cannot be converted to a BigInt because it is not an integer",
)); ));
} }
// i128 暂存上限:超出范围的整数后续由任意精度表示补齐。 return NativeResult::Ok(vm.new_bigint(BigInt::from(d.trunc() as 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));
} }
// 步骤 4:其它原始类型走 ToBigInt。 // 步骤 4:其它原始类型走 ToBigInt。
match to_bigint(vm, prim) { match to_bigint(vm, prim) {
@@ -99,7 +87,7 @@ pub fn bigint_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else { } else {
10u32 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)) 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); let v = vm.bigint_value(val);
if bits == 0 { if bits == 0 {
return NativeResult::Ok(vm.new_bigint(0)); return NativeResult::Ok(vm.new_bigint(BigInt::from(0)));
} }
if bits >= 127 { let modulus = BigInt::from(1) << bits;
// i128 表示范围内 `2^bits` 恒大于 |v|:取模后符号位折叠回原值。 let half = BigInt::from(1) << (bits - 1);
return NativeResult::Ok(val); let mut m = v % &modulus;
} if m.sign() == num_bigint::Sign::Minus {
let modulus = 1i128 << bits; m += &modulus;
let half = 1i128 << (bits - 1);
let mut m = v % modulus;
if m < 0 {
m += modulus;
} }
if m >= half { if m >= half {
m -= modulus; return NativeResult::Ok(vm.new_bigint(m - modulus));
} }
NativeResult::Ok(vm.new_bigint(m)) 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); let v = vm.bigint_value(val);
if bits == 0 { if bits == 0 {
return NativeResult::Ok(vm.new_bigint(0)); return NativeResult::Ok(vm.new_bigint(BigInt::from(0)));
} }
if bits >= 128 { let modulus = BigInt::from(1) << bits;
// i128 上限:非负值取模不变;负值结果超出表示范围,待任意精度补齐。 let mut m = v % &modulus;
return NativeResult::Ok(val); if m.sign() == num_bigint::Sign::Minus {
} m += &modulus;
let modulus = 1i128 << bits;
let mut m = v % modulus;
if m < 0 {
m += modulus;
} }
NativeResult::Ok(vm.new_bigint(m)) 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() { if prim.is_symbol() || prim.is_bigint() {
return Err(crate::error::create_type_error( return Err(crate::error::create_type_error(vm, "Cannot convert a Symbol or BigInt value to a number"));
vm,
"Cannot convert a Symbol or BigInt value to a number",
));
} }
Ok(oxide_runtime_api::to_number(prim)) 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); return Ok(0);
} }
// ToIntegerOrInfinity 先截断(-0.9 → -0,不越界);再检查有限且 [0, 2^53-1]。 // 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) { if !(0.0..=9007199254740991.0).contains(&int) {
return Err(crate::error::create_range_error(vm, "Invalid index")); 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); return Ok(val);
} }
if val.is_bool() { 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() { if val.is_int() {
// ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再 // ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再
// NumberToBigInt;ToBigInt 直接拒绝 Number)。 // NumberToBigInt;ToBigInt 直接拒绝 Number)。
return Err(crate::error::create_type_error( return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt"));
vm,
"Cannot convert a Number value to a BigInt",
));
} }
if val.is_double() { if val.is_double() {
return Err(crate::error::create_type_error( return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt"));
vm,
"Cannot convert a Number value to a BigInt",
));
} }
if val.is_string() { if val.is_string() {
let s = unsafe { oxide_runtime_api::string_data(val) }.to_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() { if let Some(exc) = vm.take_uncaught_value() {
return Err(exc); return Err(exc);
} }
return Err(crate::error::create_type_error( return Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt"));
vm,
"Cannot convert value to a BigInt",
));
} }
}; };
return to_bigint(vm, prim); return to_bigint(vm, prim);
} }
// undefined / null / symbol。 // undefined / null / symbol。
Err(crate::error::create_type_error( Err(crate::error::create_type_error(vm, "Cannot convert value to a BigInt"))
vm,
"Cannot convert value to a BigInt",
))
} }
/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; /// 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> { fn string_to_bigint<H: VmHost>(vm: &mut H, s: &str) -> Result<JsValue, JsValue> {
let trimmed = s.trim(); let trimmed = s.trim();
if trimmed.is_empty() { 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('-') { let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') {
(true, r) (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", "Cannot convert string to BigInt: invalid integer literal",
)); ));
} }
match i128::from_str_radix(digits, radix) { let m = BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| {
Ok(m) => Ok(vm.new_bigint(if neg { -m } else { m })), crate::error::create_syntax_error(vm, "Cannot convert string to BigInt: invalid integer literal")
Err(_) => Err(crate::error::create_syntax_error( })?;
vm, Ok(vm.new_bigint(if neg { -m } else { m }))
"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
} }
+8 -7
View File
@@ -1,5 +1,6 @@
use chrono::{Datelike, Days, NaiveDate, Utc}; use chrono::{Datelike, Days, NaiveDate, Utc};
use num_traits::ToPrimitive;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{to_number, to_string, NativeResult, VmHost}; use oxide_runtime_api::{to_number, to_string, NativeResult, VmHost};
use oxide_types::object::JsObject; 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> { fn get_instant_epoch_ns(obj: &JsObject) -> Option<i128> {
let value = obj.get_prop_at(0); let value = obj.get_prop_at(0);
if value.is_bigint() { 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() { if value.is_int() || value.is_double() {
return Some(to_number(value) as i128); 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 { 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 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); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
obj.type_tag = JsObject::OBJ_TYPE_INSTANT; obj.type_tag = JsObject::OBJ_TYPE_INSTANT;
obj.set_prop_at(0, epoch_value); 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 { 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 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 time_zone_value = vm.new_string(time_zone_id);
let calendar_value = vm.new_string("iso8601"); let calendar_value = vm.new_string("iso8601");
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); 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) let primitive = oxide_runtime_api::to_primitive(raw, oxide_runtime_api::ToPrimitiveHint::Number, vm)
.map_err(|error| native_engine_error(vm, &error))?; .map_err(|error| native_engine_error(vm, &error))?;
if primitive.is_bigint() { 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() { if primitive.is_bool() {
return Ok(i128::from(primitive.as_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 { if epoch_ns.unsigned_abs() > MAX_INSTANT_NS as u128 {
return NativeResult::Err(crate::error::create_range_error(vm, "Instant outside supported range")); 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]) 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) NativeResult::Ok(value)
} else { } else {
match get_instant_epoch_ns(obj) { 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")), 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")); 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 time_zone_value = vm.new_string(&time_zone_id);
let calendar_value = vm.new_string("iso8601"); let calendar_value = vm.new_string("iso8601");
initialize_temporal_receiver( initialize_temporal_receiver(
+1
View File
@@ -8,3 +8,4 @@ description = "OxideJS - Shared bytecode protocol and compiled module ABI"
[dependencies] [dependencies]
oxide_log = { path = "../oxide_log" } oxide_log = { path = "../oxide_log" }
num-bigint = "0.4"
+1 -1
View File
@@ -16,7 +16,7 @@ use crate::opcode::{self, OpCode};
pub enum Constant { pub enum Constant {
Number(f64), Number(f64),
Int(i32), Int(i32),
BigInt(i128), BigInt(num_bigint::BigInt),
String(String), String(String),
Boolean(bool), Boolean(bool),
Null, Null,
+1
View File
@@ -13,4 +13,5 @@ oxide_ir = { path = "../oxide_ir" }
oxide_log = { path = "../oxide_log" } oxide_log = { path = "../oxide_log" }
regex.workspace = true regex.workspace = true
regress.workspace = true regress.workspace = true
num-bigint = "0.4"
+3 -8
View File
@@ -242,7 +242,7 @@ impl ParamSpec<'_> {
enum ConstantKey { enum ConstantKey {
Number(u64), Number(u64),
Int(i32), Int(i32),
BigInt(i128), BigInt(num_bigint::BigInt),
String(String), String(String),
Boolean(bool), Boolean(bool),
Null, Null,
@@ -643,7 +643,7 @@ impl ConstantKey {
match value { match value {
Constant::Number(v) => Some(Self::Number(v.to_bits())), Constant::Number(v) => Some(Self::Number(v.to_bits())),
Constant::Int(v) => Some(Self::Int(*v)), 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::String(v) => Some(Self::String(v.clone())),
Constant::Boolean(v) => Some(Self::Boolean(*v)), Constant::Boolean(v) => Some(Self::Boolean(*v)),
Constant::Null => Some(Self::Null), Constant::Null => Some(Self::Null),
@@ -673,12 +673,7 @@ impl Emitter {
let msg_idx = ctx.add_constant(Constant::String(msg.to_string())); let msg_idx = ctx.add_constant(Constant::String(msg.to_string()));
ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx)); ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx));
let exc_reg = ctx.alloc_reg(); let exc_reg = ctx.alloc_reg();
ctx.inst(Inst::new_expression( ctx.inst(Inst::new_expression(Operand::Reg(exc_reg), Operand::Reg(ctor), Operand::Reg(msg_reg), 1));
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)); ctx.inst(Inst::new(OpCode::THROW, Operand::Reg(exc_reg), Operand::None, Operand::None));
let dummy = ctx.alloc_reg(); let dummy = ctx.alloc_reg();
let undef_idx = ctx.add_constant(Constant::Undefined); let undef_idx = ctx.add_constant(Constant::Undefined);
+6 -4
View File
@@ -37,10 +37,12 @@ impl Emitter {
fn emit_bigint_literal_expression( fn emit_bigint_literal_expression(
&self, b: &oxide_parser::BigIntLiteral, ctx: &mut CompileCtx, &self, b: &oxide_parser::BigIntLiteral, ctx: &mut CompileCtx,
) -> Result<u32, String> { ) -> Result<u32, String> {
let value = b let value = if let Ok(v) = b.value.parse::<i128>() {
.value num_bigint::BigInt::from(v)
.parse::<i128>() } else {
.map_err(|_| format!("BigInt literal out of range: {}", b.value))?; 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 idx = ctx.add_constant(Constant::BigInt(value));
let r = ctx.alloc_reg(); let r = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(r), idx)); ctx.inst(Inst::load_const(Operand::Reg(r), idx));
+2
View File
@@ -8,6 +8,8 @@ description = "OxideJS - VmHost trait: the abstract Vm interface builtins are wr
[dependencies] [dependencies]
oxide_types = { path = "../oxide_types" } oxide_types = { path = "../oxide_types" }
num-bigint = "0.4"
num-traits = "0.2"
oxide_kernel = { path = "../oxide_kernel" } oxide_kernel = { path = "../oxide_kernel" }
oxide_log = { path = "../oxide_log" } oxide_log = { path = "../oxide_log" }
ryu.workspace = true ryu.workspace = true
+40 -20
View File
@@ -15,6 +15,7 @@ mod runtime_api_log;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::sync::Arc; use std::sync::Arc;
use num_traits::{ToPrimitive, Zero};
use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::mem::{Epoch, P}; use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::object::{JsObject, PropAttributes};
@@ -74,10 +75,10 @@ pub trait VmHost {
// 对象分配 / 字符串创建 // 对象分配 / 字符串创建
fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject; fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject;
fn new_string(&mut self, s: &str) -> JsValue; fn new_string(&mut self, s: &str) -> JsValue;
/// 分配 BigInt 值(`i128` box 登记到 VM,返回携带指针的 `JsValue`)。 /// 分配 BigInt 值(num_bigint::BigInt box 登记到 VM,返回携带指针的 `JsValue`)。
fn new_bigint(&mut self, v: i128) -> JsValue; fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue;
/// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。 /// 读取 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>; fn kernel_core(&self) -> &Arc<KernelCore>;
@@ -167,20 +168,29 @@ pub fn string_value_eq(a: JsValue, b: JsValue) -> bool {
sa.data == sb.data 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 值。 /// 借出 BigInt 值。
/// ///
/// # Safety /// # Safety
/// `val` 必须是 BigInt `JsValue`,且其 `i128` box 存活(VM 在 full_reset 前保证)。 /// `val` 必须是 BigInt `JsValue`,且其 `num_bigint::BigInt` box 存活(VM 在 full_reset 前保证)。
#[inline] #[inline]
pub unsafe fn bigint_data(val: JsValue) -> i128 { pub unsafe fn bigint_data(val: JsValue) -> &'static num_bigint::BigInt {
*val.as_bigint_ptr() &*val.as_bigint_ptr()
} }
/// BigInt 的十进制字符串表示(`i128` → 十进制)。 /// BigInt 的十进制字符串表示(num_bigint::BigInt → 十进制)。
/// ///
/// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。 /// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。
#[inline] #[inline]
pub fn bigint_to_string(v: i128) -> String { pub fn bigint_to_string(v: &num_bigint::BigInt) -> String {
v.to_string() v.to_string()
} }
@@ -211,7 +221,7 @@ pub fn to_number(val: JsValue) -> f64 {
if val.is_bigint() { if val.is_bigint() {
// BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。 // BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。
// 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。 // 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。
return unsafe { bigint_data(val) } as f64; return bigint_to_f64(unsafe { bigint_data(val) });
} }
if val.is_object() { if val.is_object() {
return f64::NAN; return f64::NAN;
@@ -443,7 +453,7 @@ pub fn to_string(val: JsValue) -> String {
return unsafe { string_data(val) }.to_string(); return unsafe { string_data(val) }.to_string();
} }
if val.is_bigint() { if val.is_bigint() {
return unsafe { bigint_data(val) }.to_string(); return bigint_to_string(unsafe { bigint_data(val) });
} }
if val.is_object() { if val.is_object() {
return "[object]".to_string(); return "[object]".to_string();
@@ -470,7 +480,7 @@ pub fn to_boolean(val: JsValue) -> bool {
return !unsafe { (*val.as_string_ptr()).is_empty() }; return !unsafe { (*val.as_string_ptr()).is_empty() };
} }
if val.is_bigint() { if val.is_bigint() {
return unsafe { bigint_data(val) } != 0; return !unsafe { bigint_data(val) }.is_zero();
} }
if val.is_object() { if val.is_object() {
return true; return true;
@@ -529,19 +539,19 @@ pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul
} }
// BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。 // BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。
if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { 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() { 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:字符串解析为数字后比较。 // BigInt 与 String:字符串解析为数字后比较。
if lhs.is_bigint() && rhs.is_string() { if lhs.is_bigint() && rhs.is_string() {
let r = parse_js_number(unsafe { string_data(rhs) }); 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() { if lhs.is_string() && rhs.is_bigint() {
let l = parse_js_number(unsafe { string_data(lhs) }); 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。 // 步骤 5-6:Number 与 String。
if (lhs.is_int() || lhs.is_double()) && rhs.is_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 范围时精确比较, /// BigInt 与 Number 的序比较:Number 为整数且落于 i128 范围时精确比较,
/// 否则退化为 f64 近似比较(非有限/超出 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_cmp_bigint(num, big).reverse()
} }
/// Number 与 BigInt 的序比较(`num` 与 `big` 的 `<`/`=`/`>`)。 /// 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() { 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(); let d = num.as_double();
if d.is_nan() { if d.is_nan() {
@@ -703,9 +717,13 @@ fn number_cmp_bigint(num: JsValue, big: i128) -> Ordering {
} }
let truncated = d.trunc(); let truncated = d.trunc();
if truncated != d || truncated.abs() > i128::MAX as f64 { 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 视为不同。 /// 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) (P::as_ptr(&world.boolean_proto) as *mut JsObject, JsObject::OBJ_TYPE_BOOLEAN_OBJ)
} else if val.is_bigint() { } else if val.is_bigint() {
(P::as_ptr(&world.bigint_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) (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 { } else {
(P::as_ptr(&world.object_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) (P::as_ptr(&world.object_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN)
}; };
+1
View File
@@ -11,6 +11,7 @@ bumpalo.workspace = true
hashbrown.workspace = true hashbrown.workspace = true
oxide_log = { path = "../oxide_log" } oxide_log = { path = "../oxide_log" }
rustc-hash.workspace = true rustc-hash.workspace = true
num-bigint = "0.4"
[dev-dependencies] [dev-dependencies]
proptest.workspace = true proptest.workspace = true
+16 -14
View File
@@ -8,6 +8,7 @@
use std::fmt; use std::fmt;
use crate::object::{JsObject, JsString}; use crate::object::{JsObject, JsString};
use num_bigint::BigInt;
/// 静默 NaN 前缀——bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1) /// 静默 NaN 前缀——bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1)
const QNAN_PREFIX: u64 = 0xFFF8_0000_0000_0000; const QNAN_PREFIX: u64 = 0xFFF8_0000_0000_0000;
@@ -308,7 +309,7 @@ impl JsValue {
/// 与 JsString 同机制:i128 本体存于 VM 管理的堆 box,这里携带指针。 /// 与 JsString 同机制:i128 本体存于 VM 管理的堆 box,这里携带指针。
/// tag 7 原为 NaN 规范化编码,NaN 已改用普通 quiet NaN 位模式,故 tag 7 /// tag 7 原为 NaN 规范化编码,NaN 已改用普通 quiet NaN 位模式,故 tag 7
/// 空出给 BigInt。 /// 空出给 BigInt。
pub fn bigint(ptr: *const i128) -> Self { pub fn bigint(ptr: *const BigInt) -> Self {
let addr = ptr as u64; let addr = ptr as u64;
debug_assert!(addr <= PTR_MASK, "bigint pointer must fit in 48 bits"); debug_assert!(addr <= PTR_MASK, "bigint pointer must fit in 48 bits");
Self(make_tag(TAG_BIGINT) | addr) Self(make_tag(TAG_BIGINT) | addr)
@@ -320,9 +321,9 @@ impl JsValue {
} }
/// 解出 BigInt 底层指针;调用方须先保证 [`is_bigint`](JsValue::is_bigint)。 /// 解出 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()); 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()) write!(f, "Symbol(idx={})", self.as_symbol_index())
} else if self.is_bigint() { } else if self.is_bigint() {
// SAFETY: bigint 指针指向存活的 i128。 // SAFETY: bigint 指针指向存活的 i128。
write!(f, "BigInt({})", unsafe { *self.as_bigint_ptr() }) write!(f, "BigInt({})", unsafe { &*self.as_bigint_ptr() })
} else { } else {
write!(f, "{{unknown}}") write!(f, "{{unknown}}")
} }
@@ -397,7 +398,7 @@ impl fmt::Debug for JsValue {
write!(f, "JsValue(Symbol(idx={}))", self.as_symbol_index()) write!(f, "JsValue(Symbol(idx={}))", self.as_symbol_index())
} else if self.is_bigint() { } else if self.is_bigint() {
// SAFETY: bigint 指针指向存活的 i128。 // SAFETY: bigint 指针指向存活的 i128。
write!(f, "JsValue(BigInt({}))", unsafe { *self.as_bigint_ptr() }) write!(f, "JsValue(BigInt({}))", unsafe { &*self.as_bigint_ptr() })
} else { } else {
write!(f, "JsValue(Unknown)") write!(f, "JsValue(Unknown)")
} }
@@ -407,6 +408,7 @@ impl fmt::Debug for JsValue {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::JsValue; use super::JsValue;
use num_bigint::BigInt;
use proptest::prelude::*; use proptest::prelude::*;
use proptest::test_runner::TestRunner; use proptest::test_runner::TestRunner;
@@ -534,27 +536,27 @@ mod tests {
#[test] #[test]
fn bigint_pointer_roundtrip() { fn bigint_pointer_roundtrip() {
let boxed = Box::new(123_i128); let boxed = Box::new(BigInt::from(123));
let ptr: *const i128 = &*boxed; let ptr: *const BigInt = &*boxed;
let val = JsValue::bigint(ptr); let val = JsValue::bigint(ptr);
assert!(val.is_bigint()); assert!(val.is_bigint());
assert_eq!(val.as_bigint_ptr(), ptr); 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_double());
assert!(!val.is_int()); assert!(!val.is_int());
} }
#[test] #[test]
fn bigint_value_equality_by_value() { fn bigint_value_equality_by_value() {
let a = JsValue::bigint(Box::into_raw(Box::new(7_i128))); let a = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
let b = JsValue::bigint(Box::into_raw(Box::new(7_i128))); let b = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
let c = JsValue::bigint(Box::into_raw(Box::new(8_i128))); let c = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(8))));
assert_eq!(a, b); assert_eq!(a, b);
assert_ne!(a, c); assert_ne!(a, c);
unsafe { unsafe {
drop(Box::from_raw(a.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 i128)); drop(Box::from_raw(b.as_bigint_ptr() as *mut BigInt));
drop(Box::from_raw(c.as_bigint_ptr() as *mut i128)); drop(Box::from_raw(c.as_bigint_ptr() as *mut BigInt));
} }
} }
} }
+2
View File
@@ -26,6 +26,8 @@ indexmap.workspace = true
smallvec.workspace = true smallvec.workspace = true
unicode-normalization.workspace = true unicode-normalization.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
num-bigint = "0.4"
num-traits = "0.2"
[dev-dependencies] [dev-dependencies]
proptest.workspace = true proptest.workspace = true
+77 -45
View File
@@ -3,6 +3,11 @@ use crate::vm_trace;
use oxide_runtime_api as coercion; use oxide_runtime_api as coercion;
use oxide_types::value::JsValue; 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 { impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
@@ -22,36 +27,43 @@ impl Vm {
// 必须放在 coerce 之后。 // 必须放在 coerce 之后。
let lhs = self.coerce_primitive_bounded(lv, false)?; let lhs = self.coerce_primitive_bounded(lv, false)?;
let rhs = self.coerce_primitive_bounded(rv, 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() { 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)); self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
return Ok(()); return Ok(());
} }
if lhs.is_bigint() != rhs.is_bigint() { if lhs.is_bigint() != rhs.is_bigint() {
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 操作数混合加法必须抛 // 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 非字符串操作数混合加法
// TypeError(规范不允许 BigInt 与 Number/String 相加)。 // 必须抛 TypeError(规范只允许 String + BigInt 走字符串拼接)。
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
} }
if lhs.is_string() || rhs.is_string() { let ln = coercion::to_number(lhs);
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 }; let rn = coercion::to_number(rhs);
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 }; self.regs[rd] = JsValue::float(ln + rn);
// 预分配精确容量:字符串操作数 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);
}
Ok(()) 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)] #[inline(always)]
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]); 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> { 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]); vm_trace!("UNARY_PLUS rd={} r{}={:?}", rd, a, self.regs[a]);
let v = self.regs[a]; let v = self.regs[a];
if v.is_bigint() { // ToPrimitive 先解盒(BigInt 包装对象如 Object(1n) 的 valueOf 返回 BigInt),
// 一元 + 对 BigInt 必须抛 TypeError(ToNumber(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"); return self.raise_type_error("Cannot convert a BigInt value to a number");
} }
let v = self.coerce_number_bounded(v)?; let n = coercion::to_number(prim);
self.regs[rd] = JsValue::float(v); self.regs[rd] = JsValue::float(n);
Ok(()) Ok(())
} }
@@ -92,30 +106,22 @@ impl Vm {
} }
let lhs = self.coerce_primitive_bounded(lv, false)?; let lhs = self.coerce_primitive_bounded(lv, false)?;
let rhs = self.coerce_primitive_bounded(rv, 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() { if lhs.is_bigint() && rhs.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs)); self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
return Ok(()); return Ok(());
} }
if lhs.is_bigint() != rhs.is_bigint() { 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"); return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
} }
if lhs.is_string() || rhs.is_string() { let ln = coercion::to_number(lhs);
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 }; let rn = coercion::to_number(rhs);
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 }; self.regs[rd] = JsValue::float(ln + rn);
// 预分配精确容量:字符串操作数 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);
}
Ok(()) Ok(())
} }
@@ -186,7 +192,7 @@ impl Vm {
} }
if lv.is_bigint() && rv.is_bigint() { if lv.is_bigint() && rv.is_bigint() {
let r = self.bigint_value(rv); let r = self.bigint_value(rv);
if r == 0 { if bigint_is_zero(r) {
return self.raise_error_kind("RangeError", "Division by zero"); return self.raise_error_kind("RangeError", "Division by zero");
} }
self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r); 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)?; let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r); let rv = self.bigint_value(r);
if rv == 0 { if bigint_is_zero(rv) {
return self.raise_error_kind("RangeError", "Division by zero"); return self.raise_error_kind("RangeError", "Division by zero");
} }
self.regs[rd] = self.new_bigint(self.bigint_value(l) / rv); self.regs[rd] = self.new_bigint(self.bigint_value(l) / rv);
@@ -222,7 +228,7 @@ impl Vm {
} }
if lv.is_bigint() && rv.is_bigint() { if lv.is_bigint() && rv.is_bigint() {
let r = self.bigint_value(rv); let r = self.bigint_value(rv);
if r == 0 { if bigint_is_zero(r) {
return self.raise_error_kind("RangeError", "Division by zero"); return self.raise_error_kind("RangeError", "Division by zero");
} }
self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r); 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)?; let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r); let rv = self.bigint_value(r);
if rv == 0 { if bigint_is_zero(rv) {
return self.raise_error_kind("RangeError", "Division by zero"); return self.raise_error_kind("RangeError", "Division by zero");
} }
self.regs[rd] = self.new_bigint(self.bigint_value(l) % rv); self.regs[rd] = self.new_bigint(self.bigint_value(l) % rv);
@@ -265,6 +271,13 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_inc_pre(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_inc_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("INC_PRE rd={} a={}", rd, a); 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 n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n + 1.0); let result = JsValue::float(n + 1.0);
self.regs[rd] = result; self.regs[rd] = result;
@@ -275,6 +288,12 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_inc_post(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_inc_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("INC_POST rd={} a={}", rd, a); 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])?; let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n); self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n + 1.0); self.regs[rd] = JsValue::float(n + 1.0);
@@ -284,6 +303,13 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_dec_pre(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_dec_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("DEC_PRE rd={} a={}", rd, a); 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 n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n - 1.0); let result = JsValue::float(n - 1.0);
self.regs[rd] = result; self.regs[rd] = result;
@@ -294,6 +320,12 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_dec_post(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_dec_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("DEC_POST rd={} a={}", rd, a); 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])?; let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n); self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n - 1.0); self.regs[rd] = JsValue::float(n - 1.0);
+10 -9
View File
@@ -3,6 +3,7 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use num_traits::Zero;
use oxide_bytecode::module::{CompiledModule, Constant}; use oxide_bytecode::module::{CompiledModule, Constant};
use oxide_bytecode::opcode::{self, OpCode}; use oxide_bytecode::opcode::{self, OpCode};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -85,9 +86,9 @@ macro_rules! binary_arith {
if lv.is_int() && rv.is_int() { if lv.is_int() && rv.is_int() {
$self.regs[$rd] = JsValue::float(lv.as_int() as f64 $op rv.as_int() as f64); $self.regs[$rd] = JsValue::float(lv.as_int() as f64 $op rv.as_int() as f64);
} else if lv.is_bigint() && rv.is_bigint() { } else if lv.is_bigint() && rv.is_bigint() {
let l = $self.bigint_value(lv); let l = $self.bigint_value(lv).clone();
let r = $self.bigint_value(rv); let r = $self.bigint_value(rv).clone();
if r == 0 { if r.is_zero() {
// BigInt 除/模零:规范要求 RangeError(Number 路径走 f64 inf/NaN)。 // BigInt 除/模零:规范要求 RangeError(Number 路径走 f64 inf/NaN)。
$self.raise_error_kind("RangeError", "Division by zero")?; $self.raise_error_kind("RangeError", "Division by zero")?;
$self.regs[$rd] = JsValue::undefined(); $self.regs[$rd] = JsValue::undefined();
@@ -101,9 +102,9 @@ macro_rules! binary_arith {
let r = $self.coerce_primitive_bounded(rv, false)?; let r = $self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) / 2n)。 // 包装对象 coerce 后暴露双 BigInt(如 Object(2n) / 2n)。
let lv = $self.bigint_value(l); let lv = $self.bigint_value(l).clone();
let rv = $self.bigint_value(r); let rv = $self.bigint_value(r).clone();
if rv == 0 { if rv.is_zero() {
$self.raise_error_kind("RangeError", "Division by zero")?; $self.raise_error_kind("RangeError", "Division by zero")?;
$self.regs[$rd] = JsValue::undefined(); $self.regs[$rd] = JsValue::undefined();
} else { } else {
@@ -1781,10 +1782,10 @@ impl oxide_runtime_api::VmHost for Vm {
fn new_string(&mut self, s: &str) -> JsValue { fn new_string(&mut self, s: &str) -> JsValue {
self.new_string(s) 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) 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) Vm::bigint_value(self, val)
} }
fn kernel_core(&self) -> &Arc<KernelCore> { fn kernel_core(&self) -> &Arc<KernelCore> {
@@ -1983,11 +1984,11 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec<Arc<Compiled
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{opcode, JsValue, TryHandler, Vm}; use super::{opcode, JsValue, TryHandler, Vm};
use std::sync::Arc;
use oxide_bytecode::module::CompiledModule; use oxide_bytecode::module::CompiledModule;
use oxide_runtime_api::NativeResult; use oxide_runtime_api::NativeResult;
use oxide_types::object::NativeFnPtr; use oxide_types::object::NativeFnPtr;
use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::object::{JsObject, PropAttributes};
use std::sync::Arc;
fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult { fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::int(7)) NativeResult::Ok(JsValue::int(7))
+3 -3
View File
@@ -13,10 +13,10 @@ use std::collections::HashMap;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use oxide_types::object::{JsObject, JsString};
use oxide_types::value::JsValue;
use crate::session_gc::SessionGc; use crate::session_gc::SessionGc;
use crate::vm::ForInIter; use crate::vm::ForInIter;
use oxide_types::object::{JsObject, JsString};
use oxide_types::value::JsValue;
/// session arena 与 GC 簿记。 /// session arena 与 GC 簿记。
/// ///
@@ -32,7 +32,7 @@ pub(crate) struct GcState {
/// BigInt 堆 box(`Box<i128>`)追踪表。`RefCell` 使 `&self` 的 /// BigInt 堆 box(`Box<i128>`)追踪表。`RefCell` 使 `&self` 的
/// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与 /// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。 /// 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) session_bytes_allocated: usize,
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
} }
+8 -8
View File
@@ -484,23 +484,23 @@ impl Vm {
/// ///
/// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。 /// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。
/// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。 /// `&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)); let ptr = Box::into_raw(Box::new(v));
self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr); self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr);
JsValue::bigint(ptr) JsValue::bigint(ptr)
} }
/// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。 /// 读取 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。 // 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 对象/寄存器会引用它们。 /// 此时没有存活的 session 对象/寄存器会引用它们。
fn free_session_bigint_heap_data(&mut self) { fn free_session_bigint_heap_data(&mut self) {
for ptr in self.gc_state.session_bigint_ptrs.borrow_mut().drain(..) { 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 { unsafe {
drop(Box::from_raw(ptr)); drop(Box::from_raw(ptr));
@@ -512,7 +512,7 @@ impl Vm {
match constant { match constant {
Constant::Number(v) => JsValue::float(*v), Constant::Number(v) => JsValue::float(*v),
Constant::Int(v) => JsValue::int(*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::String(s) => self.perm_string(s),
Constant::Boolean(b) => JsValue::bool(*b), Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(), Constant::Null => JsValue::null(),
@@ -835,10 +835,10 @@ mod tests {
let mut vm = Vm::new(); let mut vm = Vm::new();
let result = run_source(&mut vm, "100n + 23n"); let result = run_source(&mut vm, "100n + 23n");
assert!(result.is_bigint()); assert!(result.is_bigint());
assert_eq!(vm.bigint_value(result), 123); assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
vm.reset(); vm.reset();
// reset 保留 session 字符串/bigint box:值仍可读。 // reset 保留 session 字符串/bigint box:值仍可读。
assert_eq!(vm.bigint_value(result), 123); assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
} }
#[test] #[test]