From 81314f73f1f05657204e89e232b92da338aa33cb Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 6 Dec 2024 18:35:04 +0200 Subject: [PATCH] feat(gc): Symbol and Primitive lifetime (#479) --- .../testing_and_comparison.rs | 97 +++++++++++++------ .../abstract_operations/type_conversion.rs | 56 ++++++----- .../symbol_objects/symbol_prototype.rs | 6 +- .../ecmascript/builtins/primitive_objects.rs | 6 +- nova_vm/src/ecmascript/execution/agent.rs | 2 +- .../src/ecmascript/types/language/bigint.rs | 20 ++-- .../types/language/into_primitive.rs | 4 +- .../src/ecmascript/types/language/number.rs | 12 +-- .../src/ecmascript/types/language/numeric.rs | 12 +-- .../types/language/object/property_key.rs | 6 +- .../ecmascript/types/language/primitive.rs | 56 ++++++----- .../src/ecmascript/types/language/string.rs | 18 ++-- .../src/ecmascript/types/language/symbol.rs | 71 ++++++++------ .../src/ecmascript/types/language/value.rs | 2 +- nova_vm/src/engine/bytecode/vm.rs | 54 +++++++---- nova_vm/src/engine/rootable.rs | 6 +- nova_vm/src/heap/heap_bits.rs | 2 +- nova_vm/src/heap/heap_constants.rs | 4 +- nova_vm/src/heap/indexes.rs | 2 +- 19 files changed, 267 insertions(+), 169 deletions(-) diff --git a/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs index 6c940c3ff..c4c699abe 100644 --- a/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs +++ b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs @@ -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::{ @@ -272,30 +272,71 @@ pub(crate) fn is_less_than( x: impl Into + Copy, y: impl Into + Copy, ) -> JsResult> { - // 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. @@ -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 diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index 28ffdfb16..bf1fb4e7d 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -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, -) -> JsResult { +) -> JsResult> { let input = input.into_value(); // 1. If input is an Object, then if let Ok(input) = Object::try_from(input) { @@ -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, -) -> JsResult { +) -> JsResult> { let input = input.into_object(); // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). let exotic_to_prim = get_method( @@ -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 { +) -> JsResult> { 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 { @@ -260,15 +260,18 @@ pub(crate) fn to_numeric<'a>( value: impl IntoValue, ) -> JsResult> { // 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> { let prim_value = prim_value.into_primitive(); // 2. If primValue is a BigInt, return primValue. @@ -307,10 +310,13 @@ 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) } } @@ -318,7 +324,7 @@ pub(crate) fn to_number<'gc>( pub(crate) fn to_number_primitive<'gc>( agent: &mut Agent, gc: NoGcScope<'gc, '_>, - argument: Primitive, + argument: Primitive<'gc>, ) -> JsResult> { match argument { // 3. If argument is undefined, return NaN. @@ -861,9 +867,10 @@ pub(crate) fn to_big_int<'a>( argument: Value, ) -> JsResult> { // 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( @@ -1059,10 +1066,13 @@ 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) } } @@ -1070,7 +1080,7 @@ pub(crate) fn to_string<'gc>( pub(crate) fn to_string_primitive<'gc>( agent: &mut Agent, gc: NoGcScope<'gc, '_>, - argument: Primitive, + argument: Primitive<'gc>, ) -> JsResult> { // 1. If argument is a String, return argument. match argument { @@ -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. @@ -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() })) } diff --git a/nova_vm/src/ecmascript/builtins/fundamental_objects/symbol_objects/symbol_prototype.rs b/nova_vm/src/ecmascript/builtins/fundamental_objects/symbol_objects/symbol_prototype.rs index 9331697da..e8da82ddb 100644 --- a/nova_vm/src/ecmascript/builtins/fundamental_objects/symbol_objects/symbol_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/fundamental_objects/symbol_objects/symbol_prototype.rs @@ -174,7 +174,11 @@ impl SymbolPrototype { } #[inline(always)] -fn this_symbol_value(agent: &mut Agent, gc: NoGcScope, value: Value) -> JsResult { +fn this_symbol_value<'a>( + agent: &mut Agent, + gc: NoGcScope<'a, '_>, + value: Value, +) -> JsResult> { match value { Value::Symbol(symbol) => Ok(symbol), Value::PrimitiveObject(object) if object.is_symbol_object(agent) => { diff --git a/nova_vm/src/ecmascript/builtins/primitive_objects.rs b/nova_vm/src/ecmascript/builtins/primitive_objects.rs index e72381352..770abbe6f 100644 --- a/nova_vm/src/ecmascript/builtins/primitive_objects.rs +++ b/nova_vm/src/ecmascript/builtins/primitive_objects.rs @@ -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, @@ -483,7 +483,7 @@ impl TryFrom for String<'static> { } } -impl TryFrom for Symbol { +impl TryFrom for Symbol<'_> { type Error = (); fn try_from(value: PrimitiveObjectData) -> Result { @@ -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()), } } } diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index a41c514bf..55cf31f42 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -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, /// Temporary storage for on-stack heap roots. diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index 41e34a529..331450ee7 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -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() } } @@ -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()) } } @@ -144,10 +144,10 @@ impl TryFrom for HeapBigInt<'_> { } } -impl TryFrom for HeapBigInt<'_> { +impl<'a> TryFrom> for HeapBigInt<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { if let Primitive::BigInt(x) = value { Ok(x) } else { @@ -682,9 +682,9 @@ impl TryFrom for BigInt<'_> { } } -impl TryFrom for BigInt<'_> { +impl<'a> TryFrom> for BigInt<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { match value { Primitive::BigInt(x) => Ok(BigInt::BigInt(x)), Primitive::SmallBigInt(x) => Ok(BigInt::SmallBigInt(x)), @@ -713,8 +713,8 @@ impl From> for Value { } } -impl From> for Primitive { - fn from(value: BigInt<'_>) -> Primitive { +impl<'a> From> 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), diff --git a/nova_vm/src/ecmascript/types/language/into_primitive.rs b/nova_vm/src/ecmascript/types/language/into_primitive.rs index 42a921835..83829d4bb 100644 --- a/nova_vm/src/ecmascript/types/language/into_primitive.rs +++ b/nova_vm/src/ecmascript/types/language/into_primitive.rs @@ -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>; } diff --git a/nova_vm/src/ecmascript/types/language/number.rs b/nova_vm/src/ecmascript/types/language/number.rs index 2727de769..65ae50982 100644 --- a/nova_vm/src/ecmascript/types/language/number.rs +++ b/nova_vm/src/ecmascript/types/language/number.rs @@ -91,8 +91,8 @@ impl IntoValue for HeapNumber<'_> { } } -impl IntoPrimitive for HeapNumber<'_> { - fn into_primitive(self) -> Primitive { +impl<'a> IntoPrimitive<'a> for HeapNumber<'a> { + fn into_primitive(self) -> Primitive<'a> { Primitive::Number(self.unbind()) } } @@ -125,8 +125,8 @@ impl TryFrom for HeapNumber<'_> { } } -impl IntoPrimitive for Number<'_> { - fn into_primitive(self) -> Primitive { +impl<'a> IntoPrimitive<'a> for Number<'a> { + fn into_primitive(self) -> Primitive<'a> { match self { Number::Number(idx) => Primitive::Number(idx.unbind()), Number::Integer(data) => Primitive::Integer(data), @@ -241,9 +241,9 @@ impl TryFrom for Number<'_> { } } -impl TryFrom for Number<'_> { +impl<'a> TryFrom> for Number<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { match value { Primitive::Number(data) => Ok(Number::Number(data)), Primitive::Integer(data) => Ok(Number::Integer(data)), diff --git a/nova_vm/src/ecmascript/types/language/numeric.rs b/nova_vm/src/ecmascript/types/language/numeric.rs index 022f3b93d..e437e5d80 100644 --- a/nova_vm/src/ecmascript/types/language/numeric.rs +++ b/nova_vm/src/ecmascript/types/language/numeric.rs @@ -122,8 +122,8 @@ impl IntoValue for Numeric<'_> { } } -impl IntoPrimitive for Numeric<'_> { - fn into_primitive(self) -> Primitive { +impl<'a> IntoPrimitive<'a> for Numeric<'a> { + fn into_primitive(self) -> Primitive<'a> { match self { Numeric::Number(data) => Primitive::Number(data.unbind()), Numeric::Integer(data) => Primitive::Integer(data), @@ -140,8 +140,8 @@ impl From> for Value { } } -impl From> for Primitive { - fn from(value: Numeric<'_>) -> Self { +impl<'a> From> for Primitive<'a> { + fn from(value: Numeric<'a>) -> Self { value.into_primitive() } } @@ -161,10 +161,10 @@ impl TryFrom for Numeric<'_> { } } -impl TryFrom for Numeric<'_> { +impl<'a> TryFrom> for Numeric<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { match value { Primitive::Number(data) => Ok(Numeric::Number(data)), Primitive::Integer(data) => Ok(Numeric::Integer(data)), diff --git a/nova_vm/src/ecmascript/types/language/object/property_key.rs b/nova_vm/src/ecmascript/types/language/object/property_key.rs index 4d883595b..e5751e782 100644 --- a/nova_vm/src/ecmascript/types/language/object/property_key.rs +++ b/nova_vm/src/ecmascript/types/language/object/property_key.rs @@ -28,7 +28,7 @@ pub enum PropertyKey { Integer(SmallInteger) = INTEGER_DISCRIMINANT, SmallString(SmallString) = SMALL_STRING_DISCRIMINANT, String(HeapString<'static>) = STRING_DISCRIMINANT, - Symbol(Symbol) = SYMBOL_DISCRIMINANT, + Symbol(Symbol<'static>) = SYMBOL_DISCRIMINANT, // TODO: PrivateKey } @@ -172,9 +172,9 @@ impl From for PropertyKey { } } -impl From for PropertyKey { +impl From> for PropertyKey { fn from(value: Symbol) -> Self { - PropertyKey::Symbol(value) + PropertyKey::Symbol(value.unbind()) } } diff --git a/nova_vm/src/ecmascript/types/language/primitive.rs b/nova_vm/src/ecmascript/types/language/primitive.rs index 5ed7ca185..21b2556ff 100644 --- a/nova_vm/src/ecmascript/types/language/primitive.rs +++ b/nova_vm/src/ecmascript/types/language/primitive.rs @@ -5,10 +5,12 @@ use small_string::SmallString; use crate::{ + ecmascript::execution::Agent, engine::{ context::NoGcScope, rootable::{HeapRootData, HeapRootRef, Rootable}, small_f64::SmallF64, + Scoped, }, SmallInteger, }; @@ -28,7 +30,7 @@ use super::{ #[derive(Debug, Clone, Copy, PartialEq)] #[repr(u8)] -pub enum Primitive { +pub enum Primitive<'a> { /// ### [6.1.1 The Undefined Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type) Undefined = UNDEFINED_DISCRIMINANT, /// ### [6.1.2 The Null Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type) @@ -40,7 +42,7 @@ pub enum Primitive { /// UTF-8 string on the heap. Accessing the data must be done through the /// Agent. ECMAScript specification compliant UTF-16 indexing is /// implemented through an index mapping. - String(HeapString<'static>) = STRING_DISCRIMINANT, + String(HeapString<'a>) = STRING_DISCRIMINANT, /// ### [6.1.4 The String Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type) /// /// 7-byte UTF-8 string on the stack. End of the string is determined by @@ -48,11 +50,11 @@ pub enum Primitive { /// demand from the data. SmallString(SmallString) = SMALL_STRING_DISCRIMINANT, /// ### [6.1.5 The Symbol Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type) - Symbol(Symbol) = SYMBOL_DISCRIMINANT, + Symbol(Symbol<'a>) = SYMBOL_DISCRIMINANT, /// ### [6.1.6.1 The Number Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type) /// /// f64 on the heap. Accessing the data must be done through the Agent. - Number(HeapNumber<'static>) = NUMBER_DISCRIMINANT, + Number(HeapNumber<'a>) = NUMBER_DISCRIMINANT, /// ### [6.1.6.1 The Number Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type) /// /// 53-bit signed integer on the stack. @@ -66,7 +68,7 @@ pub enum Primitive { /// /// Unlimited size integer data on the heap. Accessing the data must be /// done through the Agent. - BigInt(HeapBigInt<'static>) = BIGINT_DISCRIMINANT, + BigInt(HeapBigInt<'a>) = BIGINT_DISCRIMINANT, /// ### [6.1.6.2 The BigInt Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type) /// /// 56-bit signed integer on the stack. @@ -134,30 +136,30 @@ impl TryFrom for HeapPrimitive { } } -impl IntoValue for Primitive { +impl<'a> IntoValue for Primitive<'a> { fn into_value(self) -> super::Value { match self { Primitive::Undefined => Value::Undefined, Primitive::Null => Value::Null, Primitive::Boolean(data) => Value::Boolean(data), - Primitive::String(data) => Value::String(data), + Primitive::String(data) => Value::String(data.unbind()), Primitive::SmallString(data) => Value::SmallString(data), - Primitive::Symbol(data) => Value::Symbol(data), - Primitive::Number(data) => Value::Number(data), + Primitive::Symbol(data) => Value::Symbol(data.unbind()), + Primitive::Number(data) => Value::Number(data.unbind()), Primitive::Integer(data) => Value::Integer(data), Primitive::SmallF64(data) => Value::SmallF64(data), - Primitive::BigInt(data) => Value::BigInt(data), + Primitive::BigInt(data) => Value::BigInt(data.unbind()), Primitive::SmallBigInt(data) => Value::SmallBigInt(data), } } } -impl Primitive { +impl<'a> Primitive<'a> { /// Unbind this Primitive from its current lifetime. This is necessary to /// use the Primitive as a parameter in a call that can perform garbage /// collection. - pub fn unbind(self) -> Self { - self + pub fn unbind(self) -> Primitive<'static> { + unsafe { std::mem::transmute::>(self) } } // Bind this Primitive to the garbage collection lifetime. This enables @@ -169,8 +171,16 @@ impl Primitive { // let primitive = primitive.bind(&gc); // ``` // to make sure that the unbound Primitive cannot be used after binding. - pub const fn bind(self, _: NoGcScope<'_, '_>) -> Self { - self + pub const fn bind(self, _: NoGcScope<'a, '_>) -> Self { + unsafe { std::mem::transmute::, Self>(self) } + } + + pub fn scope<'scope>( + self, + agent: &mut Agent, + gc: NoGcScope<'_, 'scope>, + ) -> Scoped<'scope, Primitive<'static>> { + Scoped::new(agent, gc, self.unbind()) } pub fn is_boolean(self) -> bool { @@ -201,20 +211,20 @@ impl Primitive { } } -impl From for Value { +impl From> for Value { fn from(value: Primitive) -> Self { value.into_value() } } -impl IntoPrimitive for Primitive { +impl<'a> IntoPrimitive<'a> for Primitive<'a> { #[inline(always)] fn into_primitive(self) -> Self { self } } -impl TryFrom for Primitive { +impl TryFrom for Primitive<'_> { type Error = (); fn try_from(value: Value) -> Result { @@ -235,7 +245,7 @@ impl TryFrom for Primitive { } } -impl Rootable for Primitive { +impl Rootable for Primitive<'_> { type RootRepr = PrimitiveRootRepr; #[inline] @@ -244,13 +254,13 @@ impl Rootable for Primitive { Self::Undefined => Ok(Self::RootRepr::Undefined), Self::Null => Ok(Self::RootRepr::Null), Self::Boolean(bool) => Ok(Self::RootRepr::Boolean(bool)), - Self::String(heap_string) => Err(HeapRootData::String(heap_string)), + Self::String(heap_string) => Err(HeapRootData::String(heap_string.unbind())), Self::SmallString(small_string) => Ok(Self::RootRepr::SmallString(small_string)), - Self::Symbol(symbol) => Err(HeapRootData::Symbol(symbol)), - Self::Number(heap_number) => Err(HeapRootData::Number(heap_number)), + Self::Symbol(symbol) => Err(HeapRootData::Symbol(symbol.unbind())), + Self::Number(heap_number) => Err(HeapRootData::Number(heap_number.unbind())), Self::Integer(integer) => Ok(Self::RootRepr::Integer(integer)), Self::SmallF64(small_f64) => Ok(Self::RootRepr::SmallF64(small_f64)), - Self::BigInt(heap_big_int) => Err(HeapRootData::BigInt(heap_big_int)), + Self::BigInt(heap_big_int) => Err(HeapRootData::BigInt(heap_big_int.unbind())), Self::SmallBigInt(small_big_int) => Ok(Self::RootRepr::SmallBigInt(small_big_int)), } } diff --git a/nova_vm/src/ecmascript/types/language/string.rs b/nova_vm/src/ecmascript/types/language/string.rs index 36d609251..7bedbc026 100644 --- a/nova_vm/src/ecmascript/types/language/string.rs +++ b/nova_vm/src/ecmascript/types/language/string.rs @@ -153,8 +153,8 @@ impl IntoValue for String<'_> { } } -impl IntoPrimitive for String<'_> { - fn into_primitive(self) -> Primitive { +impl<'a> IntoPrimitive<'a> for String<'a> { + fn into_primitive(self) -> Primitive<'a> { match self { String::String(idx) => Primitive::String(idx.unbind()), String::SmallString(data) => Primitive::SmallString(data), @@ -168,8 +168,8 @@ impl<'a> From> for String<'a> { } } -impl From> for Primitive { - fn from(value: HeapString<'_>) -> Self { +impl<'a> From> for Primitive<'a> { + fn from(value: HeapString<'a>) -> Self { Self::String(value.unbind()) } } @@ -192,9 +192,9 @@ impl TryFrom for String<'_> { } } -impl TryFrom for String<'_> { +impl<'a> TryFrom> for String<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { match value { Primitive::String(x) => Ok(String::String(x)), Primitive::SmallString(x) => Ok(String::SmallString(x)), @@ -230,14 +230,14 @@ impl IntoValue for SmallString { } } -impl From for Primitive { +impl From for Primitive<'static> { fn from(value: SmallString) -> Self { Self::SmallString(value) } } -impl IntoPrimitive for SmallString { - fn into_primitive(self) -> Primitive { +impl IntoPrimitive<'static> for SmallString { + fn into_primitive(self) -> Primitive<'static> { self.into() } } diff --git a/nova_vm/src/ecmascript/types/language/symbol.rs b/nova_vm/src/ecmascript/types/language/symbol.rs index eccec0f4a..caa59e21b 100644 --- a/nova_vm/src/ecmascript/types/language/symbol.rs +++ b/nova_vm/src/ecmascript/types/language/symbol.rs @@ -13,6 +13,7 @@ use crate::{ engine::{ context::NoGcScope, rootable::{HeapRootData, HeapRootRef, Rootable}, + Scoped, }, heap::{ indexes::SymbolIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, @@ -24,7 +25,7 @@ use super::{IntoPrimitive, IntoValue, Primitive, Value, BUILTIN_STRING_MEMORY}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Symbol(pub(crate) SymbolIndex); +pub struct Symbol<'a>(pub(crate) SymbolIndex<'a>); /// Inner root repr type to hide WellKnownSymbolIndexes. #[derive(Debug, Clone, Copy)] @@ -38,12 +39,12 @@ enum SymbolRootReprInner { #[derive(Debug, Clone, Copy)] pub struct SymbolRootRepr(SymbolRootReprInner); -impl Symbol { +impl<'a> Symbol<'a> { /// Unbind this Symbol from its current lifetime. This is necessary to use /// the Symbol as a parameter in a call that can perform garbage /// collection. - pub fn unbind(self) -> Self { - self + pub fn unbind(self) -> Symbol<'static> { + unsafe { std::mem::transmute::>(self) } } // Bind this Symbol to the garbage collection lifetime. This enables Rust's @@ -55,8 +56,16 @@ impl Symbol { // let symbol = symbol.bind(&gc); // ``` // to make sure that the unbound Symbol cannot be used after binding. - pub const fn bind(self, _: NoGcScope<'_, '_>) -> Self { - self + pub const fn bind(self, _: NoGcScope<'a, '_>) -> Self { + unsafe { std::mem::transmute::, Self>(self) } + } + + pub fn scope<'scope>( + self, + agent: &mut Agent, + gc: NoGcScope<'_, 'scope>, + ) -> Scoped<'scope, Symbol<'static>> { + Scoped::new(agent, gc, self.unbind()) } pub(crate) const fn _def() -> Self { @@ -68,7 +77,7 @@ impl Symbol { } /// ### [20.4.3.3.1 SymbolDescriptiveString ( sym )](https://tc39.es/ecma262/#sec-symboldescriptivestring) - pub fn descriptive_string<'gc>(self, agent: &mut Agent, gc: NoGcScope<'gc, '_>) -> String<'gc> { + pub fn descriptive_string(self, agent: &mut Agent, gc: NoGcScope<'a, '_>) -> String<'a> { if let Some(descriptor) = agent[self].descriptor { String::concat( agent, @@ -85,31 +94,31 @@ impl Symbol { } } -impl IntoValue for Symbol { +impl IntoValue for Symbol<'_> { fn into_value(self) -> Value { - Value::Symbol(self) + Value::Symbol(self.unbind()) } } -impl IntoPrimitive for Symbol { - fn into_primitive(self) -> Primitive { - Primitive::Symbol(self) +impl<'a> IntoPrimitive<'a> for Symbol<'a> { + fn into_primitive(self) -> Primitive<'a> { + Primitive::Symbol(self.unbind()) } } -impl From for Value { +impl From> for Value { fn from(value: Symbol) -> Self { value.into_value() } } -impl From for Primitive { - fn from(value: Symbol) -> Self { +impl<'a> From> for Primitive<'a> { + fn from(value: Symbol<'a>) -> Self { value.into_primitive() } } -impl TryFrom for Symbol { +impl TryFrom for Symbol<'_> { type Error = (); fn try_from(value: Value) -> Result { @@ -120,10 +129,10 @@ impl TryFrom for Symbol { } } -impl TryFrom for Symbol { +impl<'a> TryFrom> for Symbol<'a> { type Error = (); - fn try_from(value: Primitive) -> Result { + fn try_from(value: Primitive<'a>) -> Result { match value { Primitive::Symbol(idx) => Ok(idx), _ => Err(()), @@ -131,24 +140,24 @@ impl TryFrom for Symbol { } } -impl Index for Agent { +impl Index> for Agent { type Output = SymbolHeapData; - fn index(&self, index: Symbol) -> &Self::Output { + fn index(&self, index: Symbol<'_>) -> &Self::Output { &self.heap.symbols[index] } } -impl IndexMut for Agent { - fn index_mut(&mut self, index: Symbol) -> &mut Self::Output { +impl IndexMut> for Agent { + fn index_mut(&mut self, index: Symbol<'_>) -> &mut Self::Output { &mut self.heap.symbols[index] } } -impl Index for Vec> { +impl Index> for Vec> { type Output = SymbolHeapData; - fn index(&self, index: Symbol) -> &Self::Output { + fn index(&self, index: Symbol<'_>) -> &Self::Output { self.get(index.get_index()) .expect("Symbol out of bounds") .as_ref() @@ -156,8 +165,8 @@ impl Index for Vec> { } } -impl IndexMut for Vec> { - fn index_mut(&mut self, index: Symbol) -> &mut Self::Output { +impl IndexMut> for Vec> { + fn index_mut(&mut self, index: Symbol<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) .expect("Symbol out of bounds") .as_mut() @@ -165,7 +174,7 @@ impl IndexMut for Vec> { } } -impl HeapMarkAndSweep for Symbol { +impl HeapMarkAndSweep for Symbol<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.symbols.push(*self); } @@ -175,14 +184,14 @@ impl HeapMarkAndSweep for Symbol { } } -impl CreateHeapData for Heap { - fn create(&mut self, data: SymbolHeapData) -> Symbol { +impl CreateHeapData> for Heap { + fn create(&mut self, data: SymbolHeapData) -> Symbol<'static> { self.symbols.push(Some(data)); Symbol(SymbolIndex::last(&self.symbols)) } } -impl Rootable for Symbol { +impl Rootable for Symbol<'_> { type RootRepr = SymbolRootRepr; #[inline] @@ -195,7 +204,7 @@ impl Rootable for Symbol { }, ))) } else { - Err(HeapRootData::Symbol(value)) + Err(HeapRootData::Symbol(value.unbind())) } } diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 4ca723eeb..9d7104784 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -96,7 +96,7 @@ pub enum Value { SmallString(SmallString), /// ### [6.1.5 The Symbol Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type) - Symbol(Symbol), + Symbol(Symbol<'static>), /// ### [6.1.6.1 The Number Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type) /// diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 424ce5afd..62075c219 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -26,7 +26,7 @@ use crate::{ }, type_conversion::{ to_boolean, to_number, to_numeric, to_numeric_primitive, to_object, to_primitive, - to_property_key, to_string, + to_property_key, to_string, to_string_primitive, }, }, builtins::{ @@ -45,8 +45,8 @@ use crate::{ types::{ get_this_value, get_value, initialize_referenced_binding, is_private_reference, is_super_reference, put_value, Base, BigInt, Function, InternalMethods, IntoFunction, - IntoObject, IntoValue, Number, Numeric, Object, PropertyDescriptor, PropertyKey, - Reference, String, Value, BUILTIN_STRING_MEMORY, + IntoObject, IntoValue, Number, Numeric, Object, Primitive, PropertyDescriptor, + PropertyKey, Reference, String, Value, BUILTIN_STRING_MEMORY, }, }, engine::context::GcScope, @@ -2231,41 +2231,61 @@ fn apply_string_or_numeric_binary_operator( // 1. If opText is +, then let gc = if op_text == BinaryOperator::Addition { // a. Let lprim be ? ToPrimitive(lval). - let lprim = to_primitive(agent, gc.reborrow(), lval, None)?; - // b. Let rprim be ? ToPrimitive(rval). - let rprim = to_primitive(agent, gc.reborrow(), rval, None)?; + let (lprim, rprim, gc) = match (Primitive::try_from(lval), Primitive::try_from(rval)) { + (Ok(lprim), Ok(rprim)) => { + let gc = gc.into_nogc(); + (lprim.bind(gc), rprim.bind(gc), gc) + } + (Ok(lprim), Err(_)) => { + let lprim = lprim.scope(agent, gc.nogc()); + let rprim = to_primitive(agent, gc.reborrow(), rval, None)?.unbind(); + let gc = gc.into_nogc(); + let lprim = lprim.get(agent); + (lprim.bind(gc), rprim.bind(gc), gc) + } + (Err(_), Ok(rprim)) => { + let rprim = rprim.scope(agent, gc.nogc()); + let lprim = to_primitive(agent, gc.reborrow(), lval, None)?.unbind(); + let gc = gc.into_nogc(); + let rprim = rprim.get(agent); + (lprim.bind(gc), rprim.bind(gc), gc) + } + (Err(_), Err(_)) => { + let rval = rval.scope(agent, gc.nogc()); + let lprim = to_primitive(agent, gc.reborrow(), lval, None)? + .unbind() + .scope(agent, gc.nogc()); + let rprim = to_primitive(agent, gc.reborrow(), rval.get(agent), None)?.unbind(); + let gc = gc.into_nogc(); + let lprim = lprim.get(agent); + (lprim.bind(gc), rprim.bind(gc), gc) + } + }; // c. If lprim is a String or rprim is a String, then match (String::try_from(lprim), String::try_from(rprim)) { (Ok(lstr), Ok(rstr)) => { - let gc = gc.into_nogc(); // iii. Return the string-concatenation of lstr and rstr. return Ok(String::concat(agent, gc, [lstr, rstr]).into_value()); } (Ok(lstr), Err(_)) => { - let lstr = lstr.bind(gc.nogc()).scope(agent, gc.nogc()); + let lstr = lstr.bind(gc).scope(agent, gc); // ii. Let rstr be ? ToString(rprim). - let rstr = to_string(agent, gc.reborrow(), rprim)?.unbind(); - let gc = gc.into_nogc(); - let rstr = rstr.bind(gc); + let rstr = to_string_primitive(agent, gc, rprim)?; // iii. Return the string-concatenation of lstr and rstr. return Ok(String::concat(agent, gc, [lstr.get(agent).bind(gc), rstr]).into_value()); } (Err(_), Ok(rstr)) => { - let rstr = rstr.bind(gc.nogc()).scope(agent, gc.nogc()); + let rstr = rstr.bind(gc).scope(agent, gc); // i. Let lstr be ? ToString(lprim). - let lstr = to_string(agent, gc.reborrow(), lprim)?.unbind(); - let gc = gc.into_nogc(); - let lstr = lstr.bind(gc); + let lstr = to_string_primitive(agent, gc, lprim)?; // iii. Return the string-concatenation of lstr and rstr. return Ok(String::concat(agent, gc, [lstr, rstr.get(agent).bind(gc)]).into_value()); } (Err(_), Err(_)) => {} } - let gc = gc.into_nogc(); - // d. Set lval to lprim. // e. Set rval to rprim. // 2. NOTE: At this point, it must be a numeric operation. diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index 0d6e1e220..5c2873d49 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -140,7 +140,7 @@ mod private { impl RootableSealed for Numeric<'_> {} impl RootableSealed for Object {} impl RootableSealed for OrdinaryObject {} - impl RootableSealed for Primitive {} + impl RootableSealed for Primitive<'_> {} impl RootableSealed for PrimitiveObject {} impl RootableSealed for Promise {} impl RootableSealed for Proxy {} @@ -150,7 +150,7 @@ mod private { #[cfg(feature = "shared-array-buffer")] impl RootableSealed for SharedArrayBuffer {} impl RootableSealed for String<'_> {} - impl RootableSealed for Symbol {} + impl RootableSealed for Symbol<'_> {} #[cfg(feature = "array-buffer")] impl RootableSealed for TypedArray {} impl RootableSealed for Value {} @@ -205,7 +205,7 @@ pub enum HeapRootData { // First the Value variants: This list should match 1-to-1 the list in // value.rs, but with the String(HeapString<'static>) = STRING_DISCRIMINANT, - Symbol(Symbol) = SYMBOL_DISCRIMINANT, + Symbol(Symbol<'static>) = SYMBOL_DISCRIMINANT, Number(HeapNumber<'static>) = NUMBER_DISCRIMINANT, BigInt(HeapBigInt<'static>) = BIGINT_DISCRIMINANT, Object(OrdinaryObject) = OBJECT_DISCRIMINANT, diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 7edec3b75..3ce50ea41 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -178,7 +178,7 @@ pub(crate) struct WorkQueues { #[cfg(feature = "shared-array-buffer")] pub shared_array_buffers: Vec, pub strings: Vec>, - pub symbols: Vec, + pub symbols: Vec>, #[cfg(feature = "array-buffer")] pub typed_arrays: Vec, #[cfg(feature = "weak-refs")] diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index f1101425e..cc4ca3d07 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -368,13 +368,13 @@ impl WellKnownSymbolIndexes { } } -impl From for SymbolIndex { +impl From for SymbolIndex<'static> { fn from(value: WellKnownSymbolIndexes) -> Self { SymbolIndex::from_u32_index(value as u32) } } -impl From for Symbol { +impl From for Symbol<'static> { fn from(value: WellKnownSymbolIndexes) -> Self { Symbol(value.into()) } diff --git a/nova_vm/src/heap/indexes.rs b/nova_vm/src/heap/indexes.rs index c6d265f9d..d95f6679a 100644 --- a/nova_vm/src/heap/indexes.rs +++ b/nova_vm/src/heap/indexes.rs @@ -218,7 +218,7 @@ pub type SetIteratorIndex = BaseIndex<'static, SetIteratorHeapData>; #[cfg(feature = "shared-array-buffer")] pub type SharedArrayBufferIndex = BaseIndex<'static, SharedArrayBufferHeapData>; pub type StringIndex<'a> = BaseIndex<'a, StringHeapData>; -pub type SymbolIndex = BaseIndex<'static, SymbolHeapData>; +pub type SymbolIndex<'a> = BaseIndex<'a, SymbolHeapData>; #[cfg(feature = "array-buffer")] pub type TypedArrayIndex = BaseIndex<'static, TypedArrayHeapData>; #[cfg(feature = "weak-refs")]