diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs new file mode 100644 index 0000000..3e606d3 --- /dev/null +++ b/crates/oxide_builtins/src/bigint.rs @@ -0,0 +1,131 @@ +use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_types::value::JsValue; + +/// JS `BigInt()` 构造逻辑:把参数转成 BigInt 值。 +/// +/// 普通调用返回原始 BigInt;`new BigInt()` 抛 TypeError(BigInt 不可构造)。 +/// 参数转换规则(最小子集): +/// - 无参数 → `0n` +/// - BigInt → 原样返回 +/// - Number → 截断为整数(非整数抛 RangeError) +/// - String → 解析十进制整数 +/// - Boolean → `0n`/`1n` +/// - 其它类型 → TypeError +pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + if args.len() > 1 { + 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", + )); + } + } + let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + match to_bigint_value(vm, val) { + Ok(v) => NativeResult::Ok(v), + Err(err) => NativeResult::Err(err), + } +} + +/// `BigInt.prototype.toString`:返回 BigInt 的十进制字符串。 +pub fn bigint_to_string(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + if !this_val.is_bigint() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "BigInt.prototype.toString called on incompatible receiver", + )); + } + let v = unsafe { oxide_runtime_api::bigint_data(this_val) }; + NativeResult::Ok(vm.new_string(&oxide_runtime_api::bigint_to_string(v))) +} + +/// `BigInt.prototype.valueOf`:返回包装的 BigInt 原始值。 +pub fn bigint_value_of(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + if this_val.is_bigint() { + return NativeResult::Ok(this_val); + } + if this_val.is_object() { + let ptr = this_val.as_js_object_ptr(); + if !ptr.is_null() { + let obj = unsafe { &*ptr }; + if obj.proto().is_object() { + let proto_ptr = obj.proto().as_js_object_ptr(); + let bigint_proto = vm.session().builtin_world().bigint_proto.as_ptr() as *mut oxide_types::object::JsObject; + if !proto_ptr.is_null() && std::ptr::eq(proto_ptr, bigint_proto) { + let v = obj.get_prop_at(0); + if v.is_bigint() { + return NativeResult::Ok(v); + } + } + } + } + } + NativeResult::Err(crate::error::create_type_error( + vm, + "BigInt.prototype.valueOf called on incompatible receiver", + )) +} + +/// 把原始值转为 BigInt 值(`BigInt(value)` 的参数转换)。 +fn to_bigint_value(vm: &mut H, val: JsValue) -> Result { + if val.is_bigint() { + return Ok(val); + } + if val.is_undefined() { + return Ok(vm.new_bigint(0)); + } + if val.is_bool() { + return Ok(vm.new_bigint(if val.as_bool() { 1 } else { 0 })); + } + if val.is_int() { + return Ok(vm.new_bigint(val.as_int() as i128)); + } + if val.is_double() { + let d = val.as_double(); + if d.is_nan() || d.is_infinite() { + return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer")); + } + if d.trunc() != d { + return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer")); + } + return Ok(vm.new_bigint(d.trunc() as i128)); + } + if val.is_string() { + let s = unsafe { oxide_runtime_api::string_data(val) }.to_string(); + let s = s.trim(); + let sign = if let Some(rest) = s.strip_prefix('-') { + (true, rest) + } else if let Some(rest) = s.strip_prefix('+') { + (false, rest) + } else { + (false, s) + }; + let (radix, digits) = if let Some(hex) = sign.1.strip_prefix("0x").or_else(|| sign.1.strip_prefix("0X")) { + (16u32, hex) + } else if let Some(oct) = sign.1.strip_prefix("0o").or_else(|| sign.1.strip_prefix("0O")) { + (8u32, oct) + } else if let Some(bin) = sign.1.strip_prefix("0b").or_else(|| sign.1.strip_prefix("0B")) { + (2u32, bin) + } else { + (10u32, sign.1) + }; + let magnitude = match i128::from_str_radix(digits, radix) { + Ok(m) => m, + Err(_) => { + return Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )) + } + }; + return Ok(vm.new_bigint(if sign.0 { -magnitude } else { magnitude })); + } + Err(crate::error::create_type_error( + vm, + "Cannot convert value to a BigInt", + )) +} diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index 3c5b849..107fc90 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -13,6 +13,8 @@ pub mod array; pub mod array_buffer; /// Boolean 内置对象实现(constructor 与 prototype 的 valueOf/toString)。 pub mod boolean; +/// BigInt 内置对象实现(constructor 与 prototype 的 toString)。 +pub mod bigint; /// builtins 层日志宏(`builtins_error`/`builtins_info` 等),target 为 `oxide::builtins`。 pub mod builtins_log; /// DataView 内置对象实现(对 ArrayBuffer 的定点读写视图)。 diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 0f62bcd..7f5d4b3 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -310,6 +310,13 @@ pub fn object_constructor(vm: &mut H, args: &[u8]) -> NativeResult { obj_ref.set_prop_at(0, val); return NativeResult::Ok(JsValue::from_js_object(obj)); } + if val.is_bigint() { + let proto = vm.session().builtin_world().bigint_proto.as_ptr() as *mut JsObject; + let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto))); + let obj_ref = unsafe { &mut *obj }; + obj_ref.set_prop_at(0, val); + return NativeResult::Ok(JsValue::from_js_object(obj)); + } let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); NativeResult::Ok(JsValue::from_js_object(obj)) } diff --git a/crates/oxide_builtins/src/stubs.rs b/crates/oxide_builtins/src/stubs.rs index e24cb39..4de8493 100644 --- a/crates/oxide_builtins/src/stubs.rs +++ b/crates/oxide_builtins/src/stubs.rs @@ -9,10 +9,6 @@ fn stub_error(vm: &mut H, name: &str) -> NativeResult { pub fn proxy_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "Proxy") } -/// `BigInt` 全局占位:始终抛 TypeError。BigInt 按设计延后实现。 -pub fn bigint_stub(vm: &mut H, _args: &[u8]) -> NativeResult { - stub_error(vm, "BigInt") -} /// `WeakMap` 全局占位:始终抛 TypeError。WeakMap 按设计延后实现。 pub fn weakmap_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "WeakMap") diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index 543d0df..65ea246 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -16,6 +16,7 @@ use crate::opcode::{self, OpCode}; pub enum Constant { Number(f64), Int(i32), + BigInt(i128), String(String), Boolean(bool), Null, diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 13064d3..14cd37e 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -197,6 +197,8 @@ fn format_js_value( // SAFETY: val 已确认是字符串值。 let s = unsafe { (*val.as_string_ptr()).data.clone() }; format!("\"{s}\"") + } else if val.is_bigint() { + format!("{}", vm.bigint_value(val)) } else if val.is_object() { let obj = unsafe { &*val.as_js_object_ptr() }; if obj.is_promise_obj() { diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 79fc310..a456fe4 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -242,6 +242,7 @@ impl ParamSpec<'_> { enum ConstantKey { Number(u64), Int(i32), + BigInt(i128), String(String), Boolean(bool), Null, @@ -642,6 +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::String(v) => Some(Self::String(v.clone())), Constant::Boolean(v) => Some(Self::Boolean(*v)), Constant::Null => Some(Self::Null), @@ -1255,6 +1257,7 @@ impl Emitter { pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { match expr { Expression::NumericLiteral(_) + | Expression::BigIntLiteral(_) | Expression::StringLiteral(_) | Expression::BooleanLiteral(_) | Expression::NullLiteral(_) diff --git a/crates/oxide_emit/src/expr/literal.rs b/crates/oxide_emit/src/expr/literal.rs index 2e1011c..5362cd4 100644 --- a/crates/oxide_emit/src/expr/literal.rs +++ b/crates/oxide_emit/src/expr/literal.rs @@ -12,6 +12,7 @@ impl Emitter { pub(crate) fn emit_literal(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { match expr { Expression::NumericLiteral(n) => self.emit_numeric_literal_expression(n, ctx), + Expression::BigIntLiteral(b) => self.emit_bigint_literal_expression(b, ctx), Expression::StringLiteral(s) => self.emit_string_literal_expression(s, ctx), Expression::BooleanLiteral(b) => self.emit_boolean_literal_expression(b, ctx), Expression::NullLiteral(_) => self.emit_null_literal_expression(ctx), @@ -33,6 +34,19 @@ impl Emitter { Ok(r) } + fn emit_bigint_literal_expression( + &self, b: &oxide_parser::BigIntLiteral, ctx: &mut CompileCtx, + ) -> Result { + let value = b + .value + .parse::() + .map_err(|_| format!("BigInt literal out of range: {}", b.value))?; + let idx = ctx.add_constant(Constant::BigInt(value)); + let r = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(r), idx)); + Ok(r) + } + fn emit_string_literal_expression( &self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx, ) -> Result { diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 1365630..8a045e4 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -251,6 +251,8 @@ pub struct BuiltinWorld { pub plain_date_proto: P, pub plain_time_constructor: P, pub plain_time_proto: P, + pub bigint_constructor: P, + pub bigint_proto: P, pub stub_objects: Vec>, } @@ -502,9 +504,10 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { wire_ctor_proto(&world.instant_constructor, &world.instant_proto); wire_ctor_proto(&world.plain_date_constructor, &world.plain_date_proto); wire_ctor_proto(&world.plain_time_constructor, &world.plain_time_proto); + wire_ctor_proto(&world.bigint_constructor, &world.bigint_proto); let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject); - let non_object_protos: [&P; 17] = [ + let non_object_protos: [&P; 18] = [ &world.array_proto, &world.function_proto, &world.string_proto, @@ -522,6 +525,7 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { &world.instant_proto, &world.plain_date_proto, &world.plain_time_proto, + &world.bigint_proto, ]; for proto in &non_object_protos { set_proto_if_changed(proto, obj_proto_val); @@ -656,6 +660,8 @@ impl BuiltinWorld { BuiltinId::PlainDateProto => &self.plain_date_proto, BuiltinId::PlainTimeConstructor => &self.plain_time_constructor, BuiltinId::PlainTimeProto => &self.plain_time_proto, + BuiltinId::BigIntConstructor => &self.bigint_constructor, + BuiltinId::BigIntProto => &self.bigint_proto, } } @@ -704,6 +710,7 @@ impl BuiltinWorld { make_named_pair(string_forge, shape_forge, labels, "PlainDate"); let (plain_time_proto, plain_time_constructor) = make_named_pair(string_forge, shape_forge, labels, "PlainTime"); + let (bigint_proto, bigint_constructor) = make_named_pair(string_forge, shape_forge, labels, "BigInt"); let stub_objects = Vec::new(); let world = Self { @@ -784,6 +791,8 @@ impl BuiltinWorld { plain_date_proto, plain_time_constructor, plain_time_proto, + bigint_constructor, + bigint_proto, stub_objects, }; wire_builtin_world_links(&world); @@ -987,6 +996,11 @@ impl BuiltinWorld { current.plain_time_constructor.clone(), ) }; + let (bigint_proto, bigint_constructor) = if dirty.stubs { + make_named_pair(string_forge, shape_forge, labels, "BigInt") + } else { + (current.bigint_proto.clone(), current.bigint_constructor.clone()) + }; let stub_objects = if dirty.stubs { Vec::new() } else { current.stub_objects.clone() }; let world = BuiltinWorld { @@ -1067,6 +1081,8 @@ impl BuiltinWorld { plain_date_proto, plain_time_constructor, plain_time_proto, + bigint_constructor, + bigint_proto, stub_objects, }; wire_builtin_world_links(&world); diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 49391f5..21f6c6f 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -225,7 +225,7 @@ pub struct KernelSession { /// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 /// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 /// builtin 家族。 -pub const NUM_BUILTINS: usize = 76; +pub const NUM_BUILTINS: usize = 78; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 /// @@ -310,6 +310,8 @@ pub enum BuiltinId { PlainDateProto = 73, PlainTimeConstructor = 74, PlainTimeProto = 75, + BigIntConstructor = 76, + BigIntProto = 77, } impl BuiltinId { @@ -391,6 +393,8 @@ impl BuiltinId { BuiltinId::PlainDateProto, BuiltinId::PlainTimeConstructor, BuiltinId::PlainTimeProto, + BuiltinId::BigIntConstructor, + BuiltinId::BigIntProto, ]; } @@ -624,7 +628,10 @@ impl KernelSession { || gen(BuiltinId::PlainDateProto) != snap(BuiltinId::PlainDateProto) || gen(BuiltinId::PlainTimeConstructor) != snap(BuiltinId::PlainTimeConstructor) || gen(BuiltinId::PlainTimeProto) != snap(BuiltinId::PlainTimeProto), - stubs: world.stub_objects.len() != snapshot.stub_objects_len || stub_generations_dirty, + stubs: world.stub_objects.len() != snapshot.stub_objects_len + || stub_generations_dirty + || gen(BuiltinId::BigIntConstructor) != snap(BuiltinId::BigIntConstructor) + || gen(BuiltinId::BigIntProto) != snap(BuiltinId::BigIntProto), global: BuiltinSnapshot::gen(&self.global_object) != snapshot.global_object_generation, } } diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index c8ef301..b567606 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -12,6 +12,7 @@ mod runtime_api_log; +use std::cmp::Ordering; use std::sync::Arc; use oxide_kernel::kernel::{KernelCore, KernelSession}; @@ -73,6 +74,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 值;调用方须保证 `val.is_bigint()`。 + fn bigint_value(&mut self, val: JsValue) -> i128; // 内核访问器 fn kernel_core(&self) -> &Arc; @@ -162,6 +167,23 @@ pub fn string_value_eq(a: JsValue, b: JsValue) -> bool { sa.data == sb.data } +/// 借出 BigInt 值。 +/// +/// # Safety +/// `val` 必须是 BigInt `JsValue`,且其 `i128` box 存活(VM 在 full_reset 前保证)。 +#[inline] +pub unsafe fn bigint_data(val: JsValue) -> i128 { + *val.as_bigint_ptr() +} + +/// BigInt 的十进制字符串表示(`i128` → 十进制)。 +/// +/// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。 +#[inline] +pub fn bigint_to_string(v: i128) -> String { + v.to_string() +} + /// 对原始值执行 ECMAScript ToNumber 的快速路径(不触发对象 coercion)。 /// /// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`; @@ -186,6 +208,11 @@ pub fn to_number(val: JsValue) -> f64 { let s = unsafe { string_data(val) }; return parse_js_number(s); } + if val.is_bigint() { + // BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。 + // 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。 + return unsafe { bigint_data(val) } as f64; + } if val.is_object() { return f64::NAN; } @@ -382,6 +409,11 @@ pub fn push_to_string(val: JsValue, buf: &mut String) { unsafe { buf.push_str(string_data(val)) }; return; } + if val.is_bigint() { + use std::fmt::Write; + let _ = write!(buf, "{}", unsafe { bigint_data(val) }); + return; + } if val.is_object() { buf.push_str("[object]"); } @@ -410,6 +442,9 @@ pub fn to_string(val: JsValue) -> String { if val.is_string() { return unsafe { string_data(val) }.to_string(); } + if val.is_bigint() { + return unsafe { bigint_data(val) }.to_string(); + } if val.is_object() { return "[object]".to_string(); } @@ -434,6 +469,9 @@ pub fn to_boolean(val: JsValue) -> bool { if val.is_string() { return !unsafe { (*val.as_string_ptr()).is_empty() }; } + if val.is_bigint() { + return unsafe { bigint_data(val) } != 0; + } if val.is_object() { return true; } @@ -467,6 +505,9 @@ fn same_type(a: JsValue, b: JsValue) -> bool { if a.is_symbol() && b.is_symbol() { return true; } + if a.is_bigint() && b.is_bigint() { + return true; + } false } @@ -486,6 +527,22 @@ pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul if (lhs.is_null() && rhs.is_undefined()) || (lhs.is_undefined() && rhs.is_null()) { return Ok(true); } + // BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。 + if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { + return Ok(unsafe { bigint_data(lhs) } as f64 == to_number(rhs)); + } + if (lhs.is_int() || lhs.is_double()) && rhs.is_bigint() { + return Ok(to_number(lhs) == unsafe { bigint_data(rhs) } as f64); + } + // 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); + } + 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); + } // 步骤 5-6:Number 与 String。 if (lhs.is_int() || lhs.is_double()) && rhs.is_string() { return Ok(strict_double_eq(to_number(lhs), to_number(rhs))); @@ -547,6 +604,9 @@ pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool { if lhs.is_symbol() && rhs.is_symbol() { return lhs.as_symbol_index() == rhs.as_symbol_index(); } + if lhs.is_bigint() && rhs.is_bigint() { + return unsafe { bigint_data(lhs) } == unsafe { bigint_data(rhs) }; + } false } @@ -566,6 +626,37 @@ pub fn relational_compare(lhs: JsValue, rhs: JsValue) -> Option { let rs = unsafe { string_data(rhs) }; return Some(ls < rs); } + if lhs.is_bigint() && rhs.is_bigint() { + let l = unsafe { bigint_data(lhs) }; + let r = unsafe { bigint_data(rhs) }; + return Some(l < r); + } + // BigInt 与 Number 混合:Number 为整数且落在 i128 范围时转 i128 精确比较, + // 否则(非整数/超出范围/NaN/±Infinity)转 f64 比较(NaN 结果为未定义)。 + if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { + if rhs.is_double() && rhs.as_double().is_nan() { + return None; + } + let l = unsafe { bigint_data(lhs) }; + return Some(bigint_cmp_number(l, rhs) == Ordering::Less); + } + if (lhs.is_int() || lhs.is_double()) && rhs.is_bigint() { + if lhs.is_double() && lhs.as_double().is_nan() { + return None; + } + let r = unsafe { bigint_data(rhs) }; + // lhs(Number) < rhs(BigInt) ⇔ 反向比较(lhs 作为"number 侧")。 + return Some(number_cmp_bigint(lhs, r) == Ordering::Less); + } + // BigInt 与 String/Boolean 混合:转 Number 后比较。 + if lhs.is_bigint() || rhs.is_bigint() { + let l = to_number(lhs); + let r = to_number(rhs); + if l.is_nan() || r.is_nan() { + return None; + } + return l.partial_cmp(&r).map(|o| o.is_lt()); + } let l = to_number(lhs); let r = to_number(rhs); if l.is_nan() || r.is_nan() { @@ -592,6 +683,31 @@ fn to_f64(val: JsValue) -> f64 { } } +/// BigInt 与 Number 的序比较:Number 为整数且落于 i128 范围时精确比较, +/// 否则退化为 f64 近似比较(非有限/超出 i128 范围时)。 +fn bigint_cmp_number(big: i128, num: JsValue) -> Ordering { + number_cmp_bigint(num, big).reverse() +} + +/// Number 与 BigInt 的序比较(`num` 与 `big` 的 `<`/`=`/`>`)。 +fn number_cmp_bigint(num: JsValue, big: i128) -> Ordering { + if num.is_int() { + return (num.as_int() as i128).cmp(&big); + } + let d = num.as_double(); + if d.is_nan() { + return Ordering::Greater; + } + if d.is_infinite() { + return if d.is_sign_positive() { Ordering::Greater } else { Ordering::Less }; + } + let truncated = d.trunc(); + if truncated != d || truncated.abs() > i128::MAX as f64 { + return (d).partial_cmp(&(big as f64)).unwrap_or(Ordering::Equal); + } + (truncated as i128).cmp(&big) +} + /// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。 pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool { if lhs.is_double() && rhs.is_double() { @@ -639,6 +755,9 @@ pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool { if lhs.is_symbol() && rhs.is_symbol() { return lhs.as_symbol_index() == rhs.as_symbol_index(); } + if lhs.is_bigint() && rhs.is_bigint() { + return unsafe { bigint_data(lhs) } == unsafe { bigint_data(rhs) }; + } false } @@ -699,6 +818,8 @@ pub fn to_object(val: JsValue, host: &mut H) -> Result Self { let bits = v.to_bits(); if is_nan_bits(bits) { - Self(QNAN_PREFIX | (7u64 << TAG_SHIFT) | 1) + // 指数全 1、尾数非 0 的 quiet NaN,非 NaN-box 前缀(0xFFF8..=0xFFFF)。 + Self(0x7FF8_0000_0000_0000) } else { Self(bits) } @@ -291,6 +302,28 @@ impl JsValue { debug_assert!(self.is_symbol()); (self.0 & INT_MASK) as u32 } + + /// 构造 BigInt 值(payload 为堆分配 `i128` 的 48 位指针)。 + /// + /// 与 JsString 同机制:i128 本体存于 VM 管理的堆 box,这里携带指针。 + /// tag 7 原为 NaN 规范化编码,NaN 已改用普通 quiet NaN 位模式,故 tag 7 + /// 空出给 BigInt。 + pub fn bigint(ptr: *const i128) -> Self { + let addr = ptr as u64; + debug_assert!(addr <= PTR_MASK, "bigint pointer must fit in 48 bits"); + Self(make_tag(TAG_BIGINT) | addr) + } + + /// 是否为 BigInt。 + pub fn is_bigint(&self) -> bool { + is_nan_boxed(self.0) && get_tag(self.0) == TAG_BIGINT + } + + /// 解出 BigInt 底层指针;调用方须先保证 [`is_bigint`](JsValue::is_bigint)。 + pub fn as_bigint_ptr(&self) -> *const i128 { + debug_assert!(self.is_bigint()); + (self.0 & PTR_MASK) as *const i128 + } } fn is_nan_bits(bits: u64) -> bool { @@ -298,7 +331,7 @@ fn is_nan_bits(bits: u64) -> bool { } fn is_nan_boxed(bits: u64) -> bool { - (0xFFF8..=0xFFFE).contains(&((bits >> 48) as u16)) + (0xFFF8..=0xFFFF).contains(&((bits >> 48) as u16)) } impl fmt::Display for JsValue { @@ -330,6 +363,9 @@ impl fmt::Display for JsValue { write!(f, "{{string}}") } else if self.is_symbol() { write!(f, "Symbol(idx={})", self.as_symbol_index()) + } else if self.is_bigint() { + // SAFETY: bigint 指针指向存活的 i128。 + write!(f, "BigInt({})", unsafe { *self.as_bigint_ptr() }) } else { write!(f, "{{unknown}}") } @@ -359,6 +395,9 @@ impl fmt::Debug for JsValue { write!(f, "JsValue(String({:p}))", self.as_string_ptr()) } else if self.is_symbol() { 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() }) } else { write!(f, "JsValue(Unknown)") } @@ -427,6 +466,7 @@ mod tests { val.is_object(), val.is_string(), val.is_symbol(), + val.is_bigint(), ]; let count = matched.iter().filter(|&&x| x).count(); assert_eq!(count, 1, "bits={bits:#018x} matched {count} types"); @@ -491,4 +531,30 @@ mod tests { assert_eq!(va, JsValue::string(&*a)); assert_eq!(unsafe { (*va.as_string_ptr()).as_str() }, unsafe { (*vb.as_string_ptr()).as_str() }); } + + #[test] + fn bigint_pointer_roundtrip() { + let boxed = Box::new(123_i128); + let ptr: *const i128 = &*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!(!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))); + 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)); + } + } } diff --git a/crates/oxide_vm/src/bindings/bind_bigint.rs b/crates/oxide_vm/src/bindings/bind_bigint.rs new file mode 100644 index 0000000..a384b63 --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_bigint.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use crate::bindings::{apply_binding_table, bind_global_value, configure_native_constructor}; +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +/// 把 BigInt 构造器与原型方法绑定到 global。 +/// +/// 构造器/原型对象由 `BuiltinWorld` 创建(含 name/prototype/constructor 互指), +/// 这里只配置 native 实现并挂方法。`BigInt(value)` 当函数调用时返回 BigInt 值; +/// `new BigInt()` 抛 TypeError(BigInt 不可 new)。 +pub fn bind_bigint(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let world = session.builtin_world(); + let ctor_ptr = world.bigint_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; + let proto_ptr = world.bigint_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + + configure_native_constructor(ctor, oxide_builtins::bigint::bigint_constructor:: as *const (), 1); + + apply_binding_table( + world, + proto, + core, + &[ + ("toString", oxide_builtins::bigint::bigint_to_string:: as *const (), 0), + ("valueOf", oxide_builtins::bigint::bigint_value_of:: as *const (), 0), + ], + ); + + bind_global_value(core, global, "BigInt", JsValue::from_js_object(ctor_ptr)); +} diff --git a/crates/oxide_vm/src/bindings/bind_stubs.rs b/crates/oxide_vm/src/bindings/bind_stubs.rs index c3e7daa..ef683bb 100644 --- a/crates/oxide_vm/src/bindings/bind_stubs.rs +++ b/crates/oxide_vm/src/bindings/bind_stubs.rs @@ -8,9 +8,8 @@ use oxide_types::value::JsValue; use crate::bindings::{bind_global_value, configure_native_constructor}; -const STUBS: [(&str, *const (), u8); 8] = [ +const STUBS: [(&str, *const (), u8); 7] = [ ("Proxy", oxide_builtins::stubs::proxy_stub:: as *const (), 2), - ("BigInt", oxide_builtins::stubs::bigint_stub:: as *const (), 1), ("WeakMap", oxide_builtins::stubs::weakmap_stub:: as *const (), 0), ("WeakSet", oxide_builtins::stubs::weakset_stub:: as *const (), 0), ("WeakRef", oxide_builtins::stubs::weakref_stub:: as *const (), 1), @@ -27,7 +26,7 @@ const STUBS: [(&str, *const (), u8); 8] = [ ("Atomics", oxide_builtins::stubs::atomics_stub:: as *const (), 0), ]; -/// 把未实现内置(Proxy/BigInt/WeakMap/WeakSet/WeakRef/FinalizationRegistry/SharedArrayBuffer/Atomics) +/// 把未实现内置(Proxy/WeakMap/WeakSet/WeakRef/FinalizationRegistry/SharedArrayBuffer/Atomics) /// 的 stub 构造器绑定到 global,并登记到 `stub_objects` 供快照跟踪。 pub fn bind_stubs(core: &Arc, session: &mut KernelSession, global: &mut JsObject) { let builtin_world = Arc::get_mut(&mut session.builtin_world) diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index c2a9dc8..b4666fb 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -6,6 +6,8 @@ pub mod bind_array; /// ArrayBuffer 构造器与原型的 native 方法绑定。 pub mod bind_array_buffer; +/// BigInt 构造器与原型的 native 方法绑定。 +pub mod bind_bigint; /// Boolean 构造器与原型的 native 方法绑定。 pub mod bind_boolean; /// DataView 构造器与原型的 native 方法绑定。 @@ -371,7 +373,6 @@ fn bind_iterator_global(core: &Arc, session: &KernelSession, global: fn bind_stub_globals(core: &Arc, session: &KernelSession, global: &mut JsObject) { for (index, (name, native_fn, arg_count)) in [ ("Proxy", oxide_builtins::stubs::proxy_stub:: as *const (), 2), - ("BigInt", oxide_builtins::stubs::bigint_stub:: as *const (), 1), ("WeakMap", oxide_builtins::stubs::weakmap_stub:: as *const (), 0), ("WeakSet", oxide_builtins::stubs::weakset_stub:: as *const (), 0), ("WeakRef", oxide_builtins::stubs::weakref_stub:: as *const (), 1), @@ -603,6 +604,7 @@ pub fn bind_global_builtin_slots(core: &Arc, session: &KernelSession bind_reflect_global(core, session, global); bind_iterator_global(core, session, global); bind_stub_globals(core, session, global); + bind_bigint::bind_bigint(core, session, global); bind_global_functions(core, session, global); let global_this = JsValue::from_js_object(global as *mut JsObject); bind_existing_global(core, global, "globalThis", global_this); @@ -674,6 +676,9 @@ pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession if dirty.map_or(true, |d| d.stubs) { bind_stubs::bind_stubs(core, session, global); } + if dirty.map_or(true, |d| d.stubs) { + bind_bigint::bind_bigint(core, session, global); + } } /// 完整初始化一个 session 的内置对象(全量绑定 + `globalThis` + 快照记录)。 diff --git a/crates/oxide_vm/src/dispatch/arithmetic.rs b/crates/oxide_vm/src/dispatch/arithmetic.rs index a20082c..bf57a08 100644 --- a/crates/oxide_vm/src/dispatch/arithmetic.rs +++ b/crates/oxide_vm/src/dispatch/arithmetic.rs @@ -13,8 +13,25 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 + rv.as_int() as f64); return Ok(()); } + if lv.is_bigint() && rv.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv)); + return Ok(()); + } + // 注意:不能在此前置检查 BigInt 混合(`lv.is_bigint() || rv.is_bigint()`)—— + // 对象操作数(如 `{valueOf: () => 2n}`)需先 ToPrimitive 再判定,混合检查 + // 必须放在 coerce 之后。 let lhs = self.coerce_primitive_bounded(lv, false)?; let rhs = self.coerce_primitive_bounded(rv, false)?; + 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 相加)。 + 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 }; @@ -38,7 +55,12 @@ impl Vm { #[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]); - let v = self.coerce_number_bounded(self.regs[a])?; + let v = self.regs[a]; + if v.is_bigint() { + self.regs[rd] = self.new_bigint(-self.bigint_value(v)); + return Ok(()); + } + let v = self.coerce_number_bounded(v)?; self.regs[rd] = JsValue::float(-v); Ok(()) } @@ -46,7 +68,12 @@ impl Vm { #[inline(always)] 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.coerce_number_bounded(self.regs[a])?; + let v = self.regs[a]; + if v.is_bigint() { + // 一元 + 对 BigInt 必须抛 TypeError(ToNumber(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); Ok(()) } @@ -59,8 +86,19 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 + rv.as_int() as f64); return Ok(()); } + if lv.is_bigint() && rv.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv)); + return Ok(()); + } let lhs = self.coerce_primitive_bounded(lv, false)?; let rhs = self.coerce_primitive_bounded(rv, false)?; + 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() { + 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 }; @@ -90,9 +128,22 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 - rv.as_int() as f64); return Ok(()); } - let l = self.coerce_number_bounded(lv)?; - let r = self.coerce_number_bounded(rv)?; - self.regs[rd] = JsValue::float(l - r); + if lv.is_bigint() && rv.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(lv) - self.bigint_value(rv)); + return Ok(()); + } + let l = self.coerce_primitive_bounded(lv, false)?; + let r = self.coerce_primitive_bounded(rv, false)?; + if l.is_bigint() && r.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(l) - self.bigint_value(r)); + return Ok(()); + } + if l.is_bigint() != r.is_bigint() { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } + let ln = coercion::to_number(l); + let rn = coercion::to_number(r); + self.regs[rd] = JsValue::float(ln - rn); Ok(()) } @@ -105,9 +156,22 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 * rv.as_int() as f64); return Ok(()); } - let l = self.coerce_number_bounded(lv)?; - let r = self.coerce_number_bounded(rv)?; - self.regs[rd] = JsValue::float(l * r); + if lv.is_bigint() && rv.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(lv) * self.bigint_value(rv)); + return Ok(()); + } + let l = self.coerce_primitive_bounded(lv, false)?; + let r = self.coerce_primitive_bounded(rv, false)?; + if l.is_bigint() && r.is_bigint() { + self.regs[rd] = self.new_bigint(self.bigint_value(l) * self.bigint_value(r)); + return Ok(()); + } + if l.is_bigint() != r.is_bigint() { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } + let ln = coercion::to_number(l); + let rn = coercion::to_number(r); + self.regs[rd] = JsValue::float(ln * rn); Ok(()) } @@ -120,9 +184,30 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 / rv.as_int() as f64); return Ok(()); } - let l = self.coerce_number_bounded(lv)?; - let r = self.coerce_number_bounded(rv)?; - self.regs[rd] = JsValue::float(l / r); + if lv.is_bigint() && rv.is_bigint() { + let r = self.bigint_value(rv); + if r == 0 { + return self.raise_error_kind("RangeError", "Division by zero"); + } + self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r); + return Ok(()); + } + let l = self.coerce_primitive_bounded(lv, false)?; + let r = self.coerce_primitive_bounded(rv, false)?; + if l.is_bigint() && r.is_bigint() { + let rv = self.bigint_value(r); + if rv == 0 { + return self.raise_error_kind("RangeError", "Division by zero"); + } + self.regs[rd] = self.new_bigint(self.bigint_value(l) / rv); + return Ok(()); + } + if l.is_bigint() != r.is_bigint() { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } + let ln = coercion::to_number(l); + let rn = coercion::to_number(r); + self.regs[rd] = JsValue::float(ln / rn); Ok(()) } @@ -135,9 +220,30 @@ impl Vm { self.regs[rd] = JsValue::float(lv.as_int() as f64 % rv.as_int() as f64); return Ok(()); } - let l = self.coerce_number_bounded(lv)?; - let r = self.coerce_number_bounded(rv)?; - self.regs[rd] = JsValue::float(l % r); + if lv.is_bigint() && rv.is_bigint() { + let r = self.bigint_value(rv); + if r == 0 { + return self.raise_error_kind("RangeError", "Division by zero"); + } + self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r); + return Ok(()); + } + let l = self.coerce_primitive_bounded(lv, false)?; + let r = self.coerce_primitive_bounded(rv, false)?; + if l.is_bigint() && r.is_bigint() { + let rv = self.bigint_value(r); + if rv == 0 { + return self.raise_error_kind("RangeError", "Division by zero"); + } + self.regs[rd] = self.new_bigint(self.bigint_value(l) % rv); + return Ok(()); + } + if l.is_bigint() != r.is_bigint() { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } + let ln = coercion::to_number(l); + let rn = coercion::to_number(r); + self.regs[rd] = JsValue::float(ln % rn); Ok(()) } diff --git a/crates/oxide_vm/src/dispatch/comparison.rs b/crates/oxide_vm/src/dispatch/comparison.rs index dc83171..915ffde 100644 --- a/crates/oxide_vm/src/dispatch/comparison.rs +++ b/crates/oxide_vm/src/dispatch/comparison.rs @@ -29,6 +29,9 @@ impl Vm { vm_trace!("LT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); let va = self.coerce_primitive_bounded(self.regs[a], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], false)?; + if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } match coercion::relational_compare(va, vb) { Some(r) => self.regs[rd] = JsValue::bool(r), None => { @@ -44,6 +47,9 @@ impl Vm { vm_trace!("GT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); let va = self.coerce_primitive_bounded(self.regs[a], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], false)?; + if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } match coercion::relational_compare(vb, va) { Some(r) => self.regs[rd] = JsValue::bool(r), None => { @@ -59,6 +65,9 @@ impl Vm { vm_trace!("LTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); let va = self.coerce_primitive_bounded(self.regs[a], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], false)?; + if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } match coercion::relational_compare(vb, va) { Some(r) => self.regs[rd] = JsValue::bool(!r), None => { @@ -74,6 +83,9 @@ impl Vm { vm_trace!("GTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); let va = self.coerce_primitive_bounded(self.regs[a], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], false)?; + if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) { + return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions"); + } match coercion::relational_compare(va, vb) { Some(r) => self.regs[rd] = JsValue::bool(!r), None => { diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 99f3fee..3bcb076 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -36,6 +36,8 @@ impl Vm { "string" } else if val.is_symbol() { "symbol" + } else if val.is_bigint() { + "bigint" } else if val.is_object() { let obj = unsafe { &*val.as_js_object_ptr() }; if obj.is_function() { diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index a091861..7e9e405 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -65,6 +65,11 @@ impl Vm { let proto = unsafe { &*proto_ptr }; return self.ordinary_get(proto, prop_name_si, val).map(Some); } + if val.is_bigint() { + let proto_ptr = self.session.builtin_world().bigint_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &*proto_ptr }; + return self.ordinary_get(proto, prop_name_si, val).map(Some); + } Ok(None) } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 8842a98..bb655af 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -84,10 +84,40 @@ macro_rules! binary_arith { let rv = $self.regs[$b]; 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 { + // BigInt 除/模零:规范要求 RangeError(Number 路径走 f64 inf/NaN)。 + $self.raise_error_kind("RangeError", "Division by zero")?; + $self.regs[$rd] = JsValue::undefined(); + } else { + $self.regs[$rd] = $self.new_bigint(l $op r); + } } else { - let l = $self.coerce_number_bounded(lv)?; - let r = $self.coerce_number_bounded(rv)?; - $self.regs[$rd] = JsValue::float(l $op r); + // 注意:混合 BigInt/Number 检查不能在此前置(对象操作数如 + // `{valueOf: () => 2n}` 需先 ToPrimitive 再判定),统一放 coerce 之后。 + let l = $self.coerce_primitive_bounded(lv, false)?; + 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 { + $self.raise_error_kind("RangeError", "Division by zero")?; + $self.regs[$rd] = JsValue::undefined(); + } else { + $self.regs[$rd] = $self.new_bigint(lv $op rv); + } + } else if l.is_bigint() != r.is_bigint() { + // 包装对象 coerce 后暴露 BigInt 混合(如 Object(1n) - 1)。 + $self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions")?; + $self.regs[$rd] = JsValue::undefined(); + } else { + let ln = coercion::to_number(l); + let rn = coercion::to_number(r); + $self.regs[$rd] = JsValue::float(ln $op rn); + } } }} } @@ -1745,6 +1775,12 @@ 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 { + Vm::new_bigint(self, v) + } + fn bigint_value(&mut self, val: JsValue) -> i128 { + Vm::bigint_value(self, val) + } fn kernel_core(&self) -> &Arc { self.kernel_core() } diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index ab00611..593508e 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -8,14 +8,13 @@ //! //! 本文件只定义字段;方法随 `Vm` 存放。 -use std::cell::Cell; +use std::cell::{Cell, RefCell}; 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; @@ -30,6 +29,10 @@ pub(crate) struct GcState { pub(crate) epoch_object_ptrs: Vec<*mut JsObject>, pub(crate) session_object_ptrs: Vec<*mut JsObject>, pub(crate) session_string_ptrs: Vec<*mut JsString>, + /// BigInt 堆 box(`Box`)追踪表。`RefCell` 使 `&self` 的 + /// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与 + /// mark/sweep 回收(值量少),只在 full_reset 统一释放。 + pub(crate) session_bigint_ptrs: RefCell>, pub(crate) session_bytes_allocated: usize, pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index d5a9f4d..39adf16 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -79,6 +79,7 @@ impl Vm { epoch_object_ptrs: Vec::new(), session_object_ptrs: Vec::new(), session_string_ptrs: Vec::new(), + session_bigint_ptrs: std::cell::RefCell::new(Vec::new()), session_bytes_allocated: 0, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, @@ -171,6 +172,7 @@ impl Vm { epoch_object_ptrs: Vec::new(), session_object_ptrs: Vec::new(), session_string_ptrs: Vec::new(), + session_bigint_ptrs: std::cell::RefCell::new(Vec::new()), session_bytes_allocated: 0, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, @@ -275,6 +277,7 @@ impl Vm { self.gc_state.session_bytes_allocated = 0; self.gc_state.session_gc = crate::session_gc::SessionGc::new(); self.free_session_string_heap_data(); + self.free_session_bigint_heap_data(); self.symbols.reset(); self.root_reg_limit = 0; self.active_reg_limit = 0; @@ -477,10 +480,39 @@ impl Vm { format!("{val}") } + /// 分配一个 BigInt 值:把 `i128` 堆分配为 box 并返回携带指针的 `JsValue`。 + /// + /// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。 + /// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。 + pub fn new_bigint(&self, v: i128) -> JsValue { + let ptr = Box::into_raw(Box::new(v)); + self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr); + JsValue::bigint(ptr) + } + + /// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。 + pub fn bigint_value(&self, val: JsValue) -> i128 { + // SAFETY: bigint 指针由 new_bigint 经 Box::into_raw 产生,存活至 full_reset。 + unsafe { *val.as_bigint_ptr() } + } + + /// 释放全部 session 堆 `i128` box。仅在完全隔离重置(`full_reset`)时调用, + /// 此时没有存活的 session 对象/寄存器会引用它们。 + fn free_session_bigint_heap_data(&mut self) { + for ptr in self.gc_state.session_bigint_ptrs.borrow_mut().drain(..) { + // SAFETY: 每个指针来自 new_bigint 的 Box::into_raw(Box::new(i128)), + // 且只在这里恰好释放一次。 + unsafe { + drop(Box::from_raw(ptr)); + } + } + } + fn convert_constant(&self, constant: &Constant) -> JsValue { match constant { Constant::Number(v) => JsValue::float(*v), Constant::Int(v) => JsValue::int(*v), + Constant::BigInt(v) => self.new_bigint(*v), Constant::String(s) => self.perm_string(s), Constant::Boolean(b) => JsValue::bool(*b), Constant::Null => JsValue::null(), @@ -752,4 +784,79 @@ mod tests { )); assert!(!vm.session.is_dirty_since_snapshot()); } + + #[test] + fn bigint_literal_arithmetic_and_comparison() { + let mut vm = Vm::new(); + assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n")); + assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n")); + assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n")); + assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n")); + assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n")); + assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n")); + assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false)); + assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false)); + let typeof_result = run_source(&mut vm, "typeof 123n"); + assert!(typeof_result.is_string()); + assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint")); + } + + #[test] + fn bigint_constructor_and_string() { + let mut vm = Vm::new(); + assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n")); + assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n")); + assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n")); + let s = run_source(&mut vm, "String(123n)"); + assert!(s.is_string()); + assert_eq!(vm.lookup_str(s).as_deref(), Some("123")); + let ts = run_source(&mut vm, "(123n).toString()"); + assert!(ts.is_string()); + assert_eq!(vm.lookup_str(ts).as_deref(), Some("123")); + assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5)); + } + + #[test] + fn bigint_mixed_type_throws() { + let mut vm = Vm::new(); + let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); + let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError")); + let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError")); + } + + #[test] + fn bigint_survives_reset_and_gc() { + let mut vm = Vm::new(); + let result = run_source(&mut vm, "100n + 23n"); + assert!(result.is_bigint()); + assert_eq!(vm.bigint_value(result), 123); + vm.reset(); + // reset 保留 session 字符串/bigint box:值仍可读。 + assert_eq!(vm.bigint_value(result), 123); + } + + #[test] + fn bigint_wrapped_and_number_comparison() { + let mut vm = Vm::new(); + // 包装对象 coerce 后双 BigInt 运算。 + assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n")); + assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n")); + assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n")); + // BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。 + assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true)); + assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true)); + // NaN 关系比较为 false。 + assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false)); + // 混合算术抛 TypeError。 + let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }"); + assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError")); + } }