Skip to content

Commit

Permalink
feat(gc): Symbol and Primitive lifetime (#479)
Browse files Browse the repository at this point in the history
  • Loading branch information
aapoalas authored Dec 6, 2024
1 parent cb2e8c3 commit 81314f7
Show file tree
Hide file tree
Showing 19 changed files with 267 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! ## [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::ecmascript::types::{Numeric, Primitive};
use crate::engine::context::{GcScope, NoGcScope};
use crate::{
ecmascript::{
Expand Down Expand Up @@ -272,30 +272,71 @@ pub(crate) fn is_less_than<const LEFT_FIRST: bool>(
x: impl Into<Value> + Copy,
y: impl Into<Value> + Copy,
) -> JsResult<Option<bool>> {
// 1. If LeftFirst is true, then
let (px, py) = if LEFT_FIRST {
// a. Let px be ? ToPrimitive(x, NUMBER).
let px = to_primitive(agent, gc.reborrow(), x.into(), Some(PreferredType::Number))?;

// b. Let py be ? ToPrimitive(y, NUMBER).
let py = to_primitive(agent, gc.reborrow(), y.into(), Some(PreferredType::Number))?;

(px, py)
}
// 2. Else,
else {
// a. NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
// b. Let py be ? ToPrimitive(y, NUMBER).
let py = to_primitive(agent, gc.reborrow(), y.into(), Some(PreferredType::Number))?;

// c. Let px be ? ToPrimitive(x, NUMBER).
let px = to_primitive(agent, gc.reborrow(), x.into(), Some(PreferredType::Number))?;

(px, py)
let (px, py, gc) = match (Primitive::try_from(x.into()), Primitive::try_from(y.into())) {
(Ok(px), Ok(py)) => {
let gc = gc.into_nogc();
(px.bind(gc), py.bind(gc), gc)
}
(Ok(px), Err(_)) => {
let px = px.scope(agent, gc.nogc());
let py =
to_primitive(agent, gc.reborrow(), y.into(), Some(PreferredType::Number))?.unbind();
let gc = gc.into_nogc();
let px = px.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
(Err(_), Ok(py)) => {
let py = py.scope(agent, gc.nogc());
let px =
to_primitive(agent, gc.reborrow(), x.into(), Some(PreferredType::Number))?.unbind();
let gc = gc.into_nogc();
let py = py.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
(Err(_), Err(_)) => {
if LEFT_FIRST {
// 1. If LeftFirst is true, then
// a. Let px be ? ToPrimitive(x, NUMBER).
// b. Let py be ? ToPrimitive(y, NUMBER).
let y: Value = y.into();
let y = y.scope(agent, gc.nogc());
let px = to_primitive(agent, gc.reborrow(), x.into(), Some(PreferredType::Number))?
.unbind()
.scope(agent, gc.nogc());
let py = to_primitive(
agent,
gc.reborrow(),
y.get(agent),
Some(PreferredType::Number),
)?
.unbind();
let gc = gc.into_nogc();
let px = px.get(agent);
(px.bind(gc), py.bind(gc), gc)
} else {
// 2. Else,
// a. NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
// b. Let py be ? ToPrimitive(y, NUMBER).
// c. Let px be ? ToPrimitive(x, NUMBER).
let x: Value = x.into();
let x = x.scope(agent, gc.nogc());
let py = to_primitive(agent, gc.reborrow(), y.into(), Some(PreferredType::Number))?
.unbind()
.scope(agent, gc.nogc());
let px = to_primitive(
agent,
gc.reborrow(),
x.get(agent),
Some(PreferredType::Number),
)?
.unbind();
let gc = gc.into_nogc();
let py = py.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
}
};

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 @@ -506,14 +547,16 @@ pub(crate) fn is_loosely_equal(

// 11. If x is either a String, a Number, a BigInt, or a Symbol and y is an Object, return ! IsLooselyEqual(x, ? ToPrimitive(y)).
if (x.is_string() || x.is_number() || x.is_bigint() || x.is_symbol()) && y.is_object() {
let y = to_primitive(agent, gc.reborrow(), y, None)?;
return Ok(is_loosely_equal(agent, gc, x, y).unwrap());
let x = x.scope(agent, gc.nogc());
let y = to_primitive(agent, gc.reborrow(), y, None)?.unbind();
return Ok(is_loosely_equal(agent, gc, x.get(agent), y).unwrap());
}

// 12. If x is an Object and y is either a String, a Number, a BigInt, or a Symbol, return ! IsLooselyEqual(? ToPrimitive(x), y).
if x.is_object() && (y.is_string() || y.is_number() || y.is_bigint() || y.is_symbol()) {
let x = to_primitive(agent, gc.reborrow(), x, None)?;
return Ok(is_loosely_equal(agent, gc, x, y).unwrap());
let y = y.scope(agent, gc.nogc());
let x = to_primitive(agent, gc.reborrow(), x, None)?.unbind();
return Ok(is_loosely_equal(agent, gc, x, y.get(agent)).unwrap());
}

// 13. If x is a BigInt and y is a Number, or if x is a Number and y is a BigInt, then
Expand Down
56 changes: 34 additions & 22 deletions nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ pub enum PreferredType {
/// > this specification only Dates (see 21.4.4.45) and Symbol objects (see
/// > 20.4.3.5) over-ride the default ToPrimitive behaviour. Dates treat the
/// > absence of a hint as if the hint were STRING.
pub(crate) fn to_primitive(
pub(crate) fn to_primitive<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'a, '_>,
input: impl IntoValue,
preferred_type: Option<PreferredType>,
) -> JsResult<Primitive> {
) -> JsResult<Primitive<'a>> {
let input = input.into_value();
// 1. If input is an Object, then
if let Ok(input) = Object::try_from(input) {
Expand Down Expand Up @@ -124,12 +124,12 @@ pub(crate) fn to_primitive(
}
}

pub(crate) fn to_primitive_object(
pub(crate) fn to_primitive_object<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'a, '_>,
input: impl IntoObject,
preferred_type: Option<PreferredType>,
) -> JsResult<Primitive> {
) -> JsResult<Primitive<'a>> {
let input = input.into_object();
// a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
let exotic_to_prim = get_method(
Expand Down Expand Up @@ -186,12 +186,12 @@ pub(crate) fn to_primitive_object(
/// The abstract operation OrdinaryToPrimitive takes arguments O (an Object)
/// and hint (STRING or NUMBER) and returns either a normal completion
/// containing an ECMAScript language value or a throw completion.
pub(crate) fn ordinary_to_primitive(
pub(crate) fn ordinary_to_primitive<'a>(
agent: &mut Agent,
mut gc: GcScope<'_, '_>,
mut gc: GcScope<'a, '_>,
o: Object,
hint: PreferredType,
) -> JsResult<Primitive> {
) -> JsResult<Primitive<'a>> {
let to_string_key = PropertyKey::from(BUILTIN_STRING_MEMORY.toString);
let value_of_key = PropertyKey::from(BUILTIN_STRING_MEMORY.valueOf);
let method_names = match hint {
Expand Down Expand Up @@ -260,15 +260,18 @@ pub(crate) fn to_numeric<'a>(
value: impl IntoValue,
) -> JsResult<Numeric<'a>> {
// 1. Let primValue be ? ToPrimitive(value, number).
let prim_value = to_primitive(agent, gc.reborrow(), value, Some(PreferredType::Number))?;
let prim_value =
to_primitive(agent, gc.reborrow(), value, Some(PreferredType::Number))?.unbind();
let gc = gc.into_nogc();
let prim_value = prim_value.bind(gc);

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

pub(crate) fn to_numeric_primitive<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
prim_value: impl IntoPrimitive,
prim_value: impl IntoPrimitive<'a>,
) -> JsResult<Numeric<'a>> {
let prim_value = prim_value.into_primitive();
// 2. If primValue is a BigInt, return primValue.
Expand Down Expand Up @@ -307,18 +310,21 @@ pub(crate) fn to_number<'gc>(
let argument = Object::try_from(argument).unwrap();
// 8. Let primValue be ? ToPrimitive(argument, number).
let prim_value =
to_primitive_object(agent, gc.reborrow(), argument, Some(PreferredType::Number))?;
to_primitive_object(agent, gc.reborrow(), argument, Some(PreferredType::Number))?
.unbind();
let gc = gc.into_nogc();
let prim_value = prim_value.bind(gc);
// 9. Assert: primValue is not an Object.
// 10. Return ? ToNumber(primValue).
to_number(agent, gc, prim_value)
to_number_primitive(agent, gc, prim_value)
}
}

/// ### [7.1.4 ToNumber ( argument )](https://tc39.es/ecma262/#sec-tonumber)
pub(crate) fn to_number_primitive<'gc>(
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
argument: Primitive,
argument: Primitive<'gc>,
) -> JsResult<Number<'gc>> {
match argument {
// 3. If argument is undefined, return NaN.
Expand Down Expand Up @@ -861,9 +867,10 @@ pub(crate) fn to_big_int<'a>(
argument: Value,
) -> JsResult<BigInt<'a>> {
// 1. Let prim be ? ToPrimitive(argument, number).
let prim = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::Number))?;

let prim = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::Number))?.unbind();
let gc = gc.into_nogc();
let prim = prim.bind(gc);

// 2. Return the value that prim corresponds to in Table 12.
match prim {
Primitive::Undefined => Err(agent.throw_exception_with_static_message(
Expand Down Expand Up @@ -1059,18 +1066,21 @@ pub(crate) fn to_string<'gc>(
// 9. Assert: argument is an Object.
assert!(Object::try_from(argument).is_ok());
// 10. Let primValue be ? ToPrimitive(argument, string).
let prim_value = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::String))?;
let prim_value =
to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::String))?.unbind();
let gc = gc.into_nogc();
let prim_value = prim_value.bind(gc);
// 11. Assert: primValue is not an Object.
// 12. Return ? ToString(primValue).
to_string(agent, gc, prim_value)
to_string_primitive(agent, gc, prim_value)
}
}

/// ### [7.1.17 ToString ( argument )](https://tc39.es/ecma262/#sec-tostring)
pub(crate) fn to_string_primitive<'gc>(
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
argument: Primitive,
argument: Primitive<'gc>,
) -> JsResult<String<'gc>> {
// 1. If argument is a String, return argument.
match argument {
Expand Down Expand Up @@ -1209,7 +1219,9 @@ pub(crate) fn to_property_key(
// We call ToPrimitive in case we're dealing with an object.

// 1. Let key be ? ToPrimitive(argument, hint String).
let key = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::String))?;
let key = to_primitive(agent, gc.reborrow(), argument, Some(PreferredType::String))?.unbind();
let gc = gc.into_nogc();
let key = key.bind(gc);

// 2. If Type(key) is Symbol, then
// a. Return key.
Expand All @@ -1224,7 +1236,7 @@ pub(crate) fn to_property_key(
// stringifying.

// 3. Return ! ToString(key).
to_string(agent, gc, key).unwrap().into()
to_string_primitive(agent, gc, key).unwrap().into()
}))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ impl SymbolPrototype {
}

#[inline(always)]
fn this_symbol_value(agent: &mut Agent, gc: NoGcScope, value: Value) -> JsResult<Symbol> {
fn this_symbol_value<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
value: Value,
) -> JsResult<Symbol<'a>> {
match value {
Value::Symbol(symbol) => Ok(symbol),
Value::PrimitiveObject(object) if object.is_symbol_object(agent) => {
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 @@ -438,7 +438,7 @@ pub(crate) enum PrimitiveObjectData {
Boolean(bool) = BOOLEAN_DISCRIMINANT,
String(HeapString<'static>) = STRING_DISCRIMINANT,
SmallString(SmallString) = SMALL_STRING_DISCRIMINANT,
Symbol(Symbol) = SYMBOL_DISCRIMINANT,
Symbol(Symbol<'static>) = SYMBOL_DISCRIMINANT,
Number(HeapNumber<'static>) = NUMBER_DISCRIMINANT,
Integer(SmallInteger) = INTEGER_DISCRIMINANT,
Float(SmallF64) = FLOAT_DISCRIMINANT,
Expand Down Expand Up @@ -483,7 +483,7 @@ impl TryFrom<PrimitiveObjectData> for String<'static> {
}
}

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

fn try_from(value: PrimitiveObjectData) -> Result<Self, Self::Error> {
Expand Down Expand Up @@ -545,7 +545,7 @@ impl PrimitiveObjectHeapData {
pub(crate) fn new_symbol_object(symbol: Symbol) -> Self {
Self {
object_index: None,
data: PrimitiveObjectData::Symbol(symbol),
data: PrimitiveObjectData::Symbol(symbol.unbind()),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/execution/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ pub struct Agent {
pub(crate) heap: Heap,
pub(crate) options: Options,
pub(crate) symbol_id: usize,
pub(crate) global_symbol_registry: AHashMap<&'static str, Symbol>,
pub(crate) global_symbol_registry: AHashMap<&'static str, Symbol<'static>>,
pub(crate) host_hooks: &'static dyn HostHooks,
pub(crate) execution_context_stack: Vec<ExecutionContext>,
/// Temporary storage for on-stack heap roots.
Expand Down
20 changes: 10 additions & 10 deletions nova_vm/src/ecmascript/types/language/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ impl IntoValue for BigInt<'_> {
}
}

impl IntoPrimitive for BigInt<'_> {
fn into_primitive(self) -> Primitive {
impl<'a> IntoPrimitive<'a> for BigInt<'a> {
fn into_primitive(self) -> Primitive<'a> {
self.into()
}
}
Expand Down Expand Up @@ -126,8 +126,8 @@ impl IntoValue for HeapBigInt<'_> {
}
}

impl IntoPrimitive for HeapBigInt<'_> {
fn into_primitive(self) -> Primitive {
impl<'a> IntoPrimitive<'a> for HeapBigInt<'a> {
fn into_primitive(self) -> Primitive<'a> {
Primitive::BigInt(self.unbind())
}
}
Expand All @@ -144,10 +144,10 @@ impl TryFrom<Value> for HeapBigInt<'_> {
}
}

impl TryFrom<Primitive> for HeapBigInt<'_> {
impl<'a> TryFrom<Primitive<'a>> for HeapBigInt<'a> {
type Error = ();

fn try_from(value: Primitive) -> Result<Self, Self::Error> {
fn try_from(value: Primitive<'a>) -> Result<Self, Self::Error> {
if let Primitive::BigInt(x) = value {
Ok(x)
} else {
Expand Down Expand Up @@ -682,9 +682,9 @@ impl TryFrom<Value> for BigInt<'_> {
}
}

impl TryFrom<Primitive> for BigInt<'_> {
impl<'a> TryFrom<Primitive<'a>> for BigInt<'a> {
type Error = ();
fn try_from(value: Primitive) -> Result<Self, Self::Error> {
fn try_from(value: Primitive<'a>) -> Result<Self, Self::Error> {
match value {
Primitive::BigInt(x) => Ok(BigInt::BigInt(x)),
Primitive::SmallBigInt(x) => Ok(BigInt::SmallBigInt(x)),
Expand Down Expand Up @@ -713,8 +713,8 @@ impl From<BigInt<'_>> for Value {
}
}

impl From<BigInt<'_>> for Primitive {
fn from(value: BigInt<'_>) -> Primitive {
impl<'a> From<BigInt<'a>> for Primitive<'a> {
fn from(value: BigInt<'a>) -> Primitive<'a> {
match value {
BigInt::BigInt(x) => Primitive::BigInt(x.unbind()),
BigInt::SmallBigInt(x) => Primitive::SmallBigInt(x),
Expand Down
4 changes: 2 additions & 2 deletions nova_vm/src/ecmascript/types/language/into_primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

use super::Primitive;

pub trait IntoPrimitive
pub trait IntoPrimitive<'a>
where
Self: Sized + Copy,
{
fn into_primitive(self) -> Primitive;
fn into_primitive(self) -> Primitive<'a>;
}
Loading

0 comments on commit 81314f7

Please sign in to comment.