Skip to content

Commit

Permalink
feat(gc): BigInt and Numeric lifetime (#477)
Browse files Browse the repository at this point in the history
* feat(gc): BigInt lifetime

* feat(gc): Numeric lifetime

* chore(test262): Update expectations
  • Loading branch information
aapoalas authored Dec 5, 2024
1 parent 64d16ba commit 797cba3
Show file tree
Hide file tree
Showing 23 changed files with 494 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

//! ## [7.2 Testing and Comparison Operations](https://tc39.es/ecma262/#sec-testing-and-comparison-operations)
use crate::ecmascript::abstract_operations::type_conversion::to_numeric_primitive;
use crate::ecmascript::types::Numeric;
use crate::engine::context::{GcScope, NoGcScope};
use crate::{
ecmascript::{
abstract_operations::type_conversion::to_numeric,
execution::{agent::ExceptionType, Agent, JsResult},
types::{
bigint::BigInt, Function, InternalMethods, IntoValue, Number, Object, String, Value,
Expand Down Expand Up @@ -293,6 +294,8 @@ pub(crate) fn is_less_than<const LEFT_FIRST: bool>(
(px, py)
};

let gc = gc.into_nogc();

// 3. If px is a String and py is a String, then
if px.is_string() && py.is_string() {
// a. Let lx be the length of px.
Expand Down Expand Up @@ -331,10 +334,10 @@ pub(crate) fn is_less_than<const LEFT_FIRST: bool>(

// c. NOTE: Because px and py are primitive values, evaluation order is not important.
// d. Let nx be ? ToNumeric(px).
let nx = to_numeric(agent, gc.reborrow(), px)?;
let nx = to_numeric_primitive(agent, gc, px)?;

// e. Let ny be ? ToNumeric(py).
let ny = to_numeric(agent, gc.reborrow(), py)?;
let ny = to_numeric_primitive(agent, gc, py)?;

// f. If Type(nx) is Type(ny), then
if is_same_type(nx, ny) {
Expand Down Expand Up @@ -374,9 +377,33 @@ pub(crate) fn is_less_than<const LEFT_FIRST: bool>(
}

// k. If ℝ(nx) < ℝ(ny), return true; otherwise return false.
let rnx = nx.to_real(agent, gc.reborrow())?;
let rny = nx.to_real(agent, gc)?;
Ok(Some(rnx < rny))
Ok(Some(match (nx, ny) {
(Numeric::Number(x), Numeric::Number(y)) => x != y && agent[x] < agent[y],
(Numeric::Number(x), Numeric::Integer(y)) => agent[x] < y.into_i64() as f64,
(Numeric::Number(x), Numeric::SmallF64(y)) => agent[x] < y.into_f64(),
(Numeric::Integer(x), Numeric::Number(y)) => (x.into_i64() as f64) < agent[y],
(Numeric::Integer(x), Numeric::Integer(y)) => x.into_i64() < y.into_i64(),
(Numeric::Number(x), Numeric::BigInt(y)) => agent[y].ge(&agent[x]),
(Numeric::Number(x), Numeric::SmallBigInt(y)) => agent[x] < y.into_i64() as f64,
(Numeric::Integer(x), Numeric::SmallF64(y)) => (x.into_i64() as f64) < y.into_f64(),
(Numeric::Integer(x), Numeric::BigInt(y)) => agent[y].ge(&x.into_i64()),
(Numeric::Integer(x), Numeric::SmallBigInt(y)) => x.into_i64() < y.into_i64(),
(Numeric::SmallF64(x), Numeric::Number(y)) => x.into_f64() < agent[y],
(Numeric::SmallF64(x), Numeric::Integer(y)) => x.into_f64() < y.into_i64() as f64,
(Numeric::SmallF64(x), Numeric::SmallF64(y)) => x.into_f64() < y.into_f64(),
(Numeric::SmallF64(x), Numeric::BigInt(y)) => agent[y].ge(&x.into_f64()),
(Numeric::SmallF64(x), Numeric::SmallBigInt(y)) => x.into_f64() < y.into_i64() as f64,
(Numeric::BigInt(x), Numeric::Number(y)) => agent[x].le(&agent[y]),
(Numeric::BigInt(x), Numeric::Integer(y)) => agent[x].le(&y.into_i64()),
(Numeric::BigInt(x), Numeric::SmallF64(y)) => agent[x].le(&y.into_f64()),
(Numeric::BigInt(x), Numeric::BigInt(y)) => agent[x].data < agent[y].data,
(Numeric::BigInt(x), Numeric::SmallBigInt(y)) => agent[x].le(&y.into_i64()),
(Numeric::SmallBigInt(x), Numeric::Number(y)) => (x.into_i64() as f64) < agent[y],
(Numeric::SmallBigInt(x), Numeric::Integer(y)) => x.into_i64() < y.into_i64(),
(Numeric::SmallBigInt(x), Numeric::SmallF64(y)) => (x.into_i64() as f64) < y.into_f64(),
(Numeric::SmallBigInt(x), Numeric::BigInt(y)) => agent[y].ge(&x.into_i64()),
(Numeric::SmallBigInt(x), Numeric::SmallBigInt(y)) => x.into_i64() < y.into_i64(),
}))
}
}

Expand Down
44 changes: 27 additions & 17 deletions nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,21 +254,30 @@ pub(crate) fn to_boolean(agent: &Agent, argument: Value) -> bool {
}

/// ### [7.1.3 ToNumeric ( value )](https://tc39.es/ecma262/#sec-tonumeric)
pub(crate) fn to_numeric(
pub(crate) fn to_numeric<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'a, '_>,
value: impl IntoValue,
) -> JsResult<Numeric> {
) -> JsResult<Numeric<'a>> {
// 1. Let primValue be ? ToPrimitive(value, number).
let prim_value = to_primitive(agent, gc.reborrow(), value, Some(PreferredType::Number))?;

to_numeric_primitive(agent, gc.into_nogc(), prim_value)
}

pub(crate) fn to_numeric_primitive<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
prim_value: impl IntoPrimitive,
) -> JsResult<Numeric<'a>> {
let prim_value = prim_value.into_primitive();
// 2. If primValue is a BigInt, return primValue.
if let Ok(prim_value) = BigInt::try_from(prim_value) {
return Ok(prim_value.into_numeric());
}

// 3. Return ? ToNumber(primValue).
to_number_primitive(agent, gc.nogc(), prim_value).map(|n| n.into_numeric())
to_number_primitive(agent, gc, prim_value).map(|n| n.into_numeric())
}

pub(crate) fn try_to_number<'gc>(
Expand Down Expand Up @@ -846,23 +855,24 @@ pub(crate) fn to_uint8_clamp_number(agent: &mut Agent, number: Number) -> u8 {

/// ### [7.1.13 ToBigInt ( argument )](https://tc39.es/ecma262/#sec-tobigint)
#[inline(always)]
pub(crate) fn to_big_int(
pub(crate) fn to_big_int<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'a, '_>,
argument: Value,
) -> JsResult<BigInt> {
) -> JsResult<BigInt<'a>> {
// 1. Let prim be ? ToPrimitive(argument, number).
let prim = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::Number))?;

let gc = gc.into_nogc();
// 2. Return the value that prim corresponds to in Table 12.
match prim {
Primitive::Undefined => Err(agent.throw_exception_with_static_message(
gc.nogc(),
gc,
ExceptionType::TypeError,
"Invalid primitive 'undefined'",
)),
Primitive::Null => Err(agent.throw_exception_with_static_message(
gc.nogc(),
gc,
ExceptionType::TypeError,
"Invalid primitive 'null'",
)),
Expand All @@ -873,16 +883,16 @@ pub(crate) fn to_big_int(
Ok(BigInt::from(0))
}
}
Primitive::String(idx) => string_to_big_int(agent, gc.nogc(), idx.into()),
Primitive::SmallString(data) => string_to_big_int(agent, gc.nogc(), data.into()),
Primitive::String(idx) => string_to_big_int(agent, gc, idx.into()),
Primitive::SmallString(data) => string_to_big_int(agent, gc, data.into()),
Primitive::Symbol(_) => Err(agent.throw_exception_with_static_message(
gc.nogc(),
gc,
ExceptionType::TypeError,
"Cannot convert Symbol to BigInt",
)),
Primitive::Number(_) | Primitive::Integer(_) | Primitive::SmallF64(_) => Err(agent
.throw_exception_with_static_message(
gc.nogc(),
gc,
ExceptionType::TypeError,
"Cannot convert Number to BigInt",
)),
Expand All @@ -892,11 +902,11 @@ pub(crate) fn to_big_int(
}

/// ### [7.1.14 StringToBigInt ( str )](https://tc39.es/ecma262/#sec-stringtobigint)
pub(crate) fn string_to_big_int(
pub(crate) fn string_to_big_int<'a>(
agent: &mut Agent,
nogc: NoGcScope<'_, '_>,
argument: String,
) -> JsResult<BigInt> {
nogc: NoGcScope<'a, '_>,
argument: String<'a>,
) -> JsResult<BigInt<'a>> {
// 1. Let text be StringToCodePoints(str).
// 2. Let literal be ParseText(text, StringIntegerLiteral).
// 3. If literal is a List of errors, return undefined.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,12 @@ pub(crate) const fn is_no_tear_configuration(r#type: (), order: Ordering) -> boo
/// The abstract operation RawBytesToNumeric takes arguments type (a
/// TypedArray element type), rawBytes (a List of byte values), and
/// isLittleEndian (a Boolean) and returns a Number or a BigInt.
pub(crate) fn raw_bytes_to_numeric<T: Viewable>(
pub(crate) fn raw_bytes_to_numeric<'a, T: Viewable>(
agent: &mut Agent,
gc: NoGcScope,
gc: NoGcScope<'a, '_>,
raw_bytes: T,
is_little_endian: bool,
) -> Numeric {
) -> Numeric<'a> {
// 1. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
// 2. If isLittleEndian is false, reverse the order of the elements of rawBytes.
// 3. If type is FLOAT32, then
Expand Down Expand Up @@ -383,15 +383,15 @@ pub(crate) fn get_raw_bytes_from_shared_block(
/// integer), type (a TypedArray element type), isTypedArray (a Boolean),
/// and order (SEQ-CST or UNORDERED) and optional argument isLittleEndian
/// (a Boolean) and returns a Number or a BigInt.
pub(crate) fn get_value_from_buffer<T: Viewable>(
pub(crate) fn get_value_from_buffer<'a, T: Viewable>(
agent: &mut Agent,
gc: NoGcScope,
gc: NoGcScope<'a, '_>,
array_buffer: ArrayBuffer,
byte_index: usize,
_is_typed_array: bool,
_order: Ordering,
is_little_endian: Option<bool>,
) -> Numeric {
) -> Numeric<'a> {
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
debug_assert!(!array_buffer.is_detached(agent));
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,14 @@ pub(crate) fn is_view_out_of_bounds(
/// returns either a normal completion containing either a Number or a BigInt,
/// or a throw completion. It is used by functions on DataView instances to
/// retrieve values from the view's buffer.
pub(crate) fn get_view_value<T: Viewable>(
pub(crate) fn get_view_value<'gc, T: Viewable>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'gc, '_>,
view: Value,
request_index: Value,
// 4. Set isLittleEndian to ToBoolean(isLittleEndian).
is_little_endian: bool,
) -> JsResult<Numeric> {
) -> JsResult<Numeric<'gc>> {
// 1. Perform ? RequireInternalSlot(view, [[DataView]]).
// 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
let view = require_internal_slot_data_view(agent, gc.nogc(), view)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,11 @@ impl BigIntConstructor {
}
}

fn number_to_big_int(
fn number_to_big_int<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
value: Number,
) -> JsResult<BigInt> {
mut gc: GcScope<'a, '_>,
value: Number<'a>,
) -> JsResult<BigInt<'a>> {
if !is_integral_number(agent, gc.reborrow(), value) {
Err(agent.throw_exception_with_static_message(
gc.nogc(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ impl BigIntPrototype {
/// The abstract operation ThisBigIntValue takes argument value (an ECMAScript
/// language value) and returns either a normal completion containing a BigInt
/// or a throw completion.
fn this_big_int_value(agent: &mut Agent, gc: NoGcScope, value: Value) -> JsResult<BigInt> {
fn this_big_int_value<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
value: Value,
) -> JsResult<BigInt<'a>> {
match value {
// 1. If value is a BigInt, return value.
Value::BigInt(value) => Ok(value.into()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::ecmascript::execution::Agent;
use crate::ecmascript::execution::JsResult;
use crate::ecmascript::execution::ProtoIntrinsics;
use crate::ecmascript::execution::RealmIdentifier;
use crate::ecmascript::types::bigint::BigIntMathematicalValue;
use crate::ecmascript::types::Function;
use crate::ecmascript::types::IntoNumeric;
use crate::ecmascript::types::IntoObject;
use crate::ecmascript::types::IntoValue;
use crate::ecmascript::types::Number;
Expand Down Expand Up @@ -82,28 +82,42 @@ impl NumberConstructor {
// 1. If value is present, then
let n = if !value.is_undefined() {
// a. Let prim be ? ToNumeric(value).
let prim = value.to_numeric(agent, gc.reborrow())?;
let prim = value
.to_numeric(agent, gc.reborrow())?
.unbind()
.bind(gc.nogc());

// b. If prim is a BigInt, let n be 𝔽(ℝ(prim)).
if prim.is_bigint() {
todo!()
match prim {
Numeric::BigInt(b) => {
let b = b.mathematical_value(agent, gc.nogc());
match b {
BigIntMathematicalValue::Integer(i) => {
Number::from_i64(agent, gc.nogc(), i)
}
BigIntMathematicalValue::Number(f) => Number::from_f64(agent, gc.nogc(), f),
}
}
Numeric::SmallBigInt(b) => Number::from_i64(agent, gc.nogc(), b.into_i64()),
Numeric::Number(n) => n.into(),
Numeric::Integer(i) => i.into(),
Numeric::SmallF64(f) => f.into(),
}
// c. Otherwise, let n be prim.
else {
prim
}
}
// 2. Else,
else {
// a. Let n be +0𝔽.
Number::from(0).into_numeric()
Number::from(0)
};

// 3. If NewTarget is undefined, return n.
let Some(new_target) = new_target else {
return Ok(n.into_value());
};

let n = n.scope(agent, gc.nogc());

let new_target = Function::try_from(new_target).unwrap();

// 4. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Number.prototype%", « [[NumberData]] »).
Expand All @@ -114,12 +128,12 @@ impl NumberConstructor {
ProtoIntrinsics::Number,
)?)
.unwrap();
let n = n.get(agent).unbind();
// 5. Set O.[[NumberData]] to n.
agent[o].data = match n {
Numeric::Number(d) => PrimitiveObjectData::Number(d),
Numeric::Integer(d) => PrimitiveObjectData::Integer(d),
Numeric::SmallF64(d) => PrimitiveObjectData::Float(d),
_ => unreachable!(),
Number::Number(d) => PrimitiveObjectData::Number(d),
Number::Integer(d) => PrimitiveObjectData::Integer(d),
Number::SmallF64(d) => PrimitiveObjectData::Float(d),
};
// 6. Return O.
Ok(o.into_value())
Expand Down
6 changes: 3 additions & 3 deletions nova_vm/src/ecmascript/builtins/primitive_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,11 @@ pub(crate) enum PrimitiveObjectData {
Number(HeapNumber<'static>) = NUMBER_DISCRIMINANT,
Integer(SmallInteger) = INTEGER_DISCRIMINANT,
Float(SmallF64) = FLOAT_DISCRIMINANT,
BigInt(HeapBigInt) = BIGINT_DISCRIMINANT,
BigInt(HeapBigInt<'static>) = BIGINT_DISCRIMINANT,
SmallBigInt(SmallBigInt) = SMALL_BIGINT_DISCRIMINANT,
}

impl TryFrom<PrimitiveObjectData> for BigInt {
impl TryFrom<PrimitiveObjectData> for BigInt<'_> {
type Error = ();

fn try_from(value: PrimitiveObjectData) -> Result<Self, Self::Error> {
Expand Down Expand Up @@ -503,7 +503,7 @@ pub struct PrimitiveObjectHeapData {
impl PrimitiveObjectHeapData {
pub(crate) fn new_big_int_object(big_int: BigInt) -> Self {
let data = match big_int {
BigInt::BigInt(data) => PrimitiveObjectData::BigInt(data),
BigInt::BigInt(data) => PrimitiveObjectData::BigInt(data.unbind()),
BigInt::SmallBigInt(data) => PrimitiveObjectData::SmallBigInt(data),
};
Self {
Expand Down
Loading

0 comments on commit 797cba3

Please sign in to comment.