Browse Source

Make `JsSymbol` thread-safe (#2539)

The section about `Symbol` on the [specification](https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type) says:

> The Symbol type is the set of all non-String values that may be used as the key of an Object property ([6.1.7](https://tc39.es/ecma262/#sec-object-type)).
Each possible Symbol value is unique and immutable.

Our previous implementation of `JsSymbol` used `Rc` and a thread local `Cell<usize>`. However, this meant that two different symbols in two different threads could share the same hash, making symbols not unique.
Also, the [GlobalSymbolRegistry](https://tc39.es/ecma262/#table-globalsymbolregistry-record-fields) is meant to be shared by all realms, including realms that are not in the same thread as the main one; this forces us to replace our current thread local global symbol registry with a thread-safe one that uses `DashMap` for concurrent access. However, the global symbol registry uses `JsString`s as keys and values, which forces us to either use `Vec<u16>` instead (wasteful and needs to allocate to convert to `JsString` on each access) or make `JsString` thread-safe with an atomic counter. For this reason, I implemented the second option.

This PR changes the following:

- Makes `JsSymbol` thread-safe by using Arc instead of Rc, and making `SYMBOL_HASH_COUNT` an `AtomicU64`.
- ~~Makes `JsString` thread-safe by using `AtomicUsize` instead of `Cell<usize>` for its ref count.~~ EDIT: Talked with @jasonwilliams and we decided to use `Box<[u16]>` for the global registry instead, because this won't penalize common usage of `JsString`, which is used a LOT more than `JsSymbol`.
- Makes the `GLOBAL_SYMBOL_REGISTRY` truly global, using `DashMap` as our global map that is shared by all threads.
- Replaces some thread locals with thread-safe alternatives, such as static arrays and static indices.
- Various improvements to all related code for this.
pull/2544/head
José Julián Espina 2 years ago
parent
commit
08e5e46117
  1. 47
      Cargo.lock
  2. 2
      boa_engine/Cargo.toml
  3. 4
      boa_engine/src/builtins/array/array_iterator.rs
  4. 14
      boa_engine/src/builtins/array/mod.rs
  5. 6
      boa_engine/src/builtins/array_buffer/mod.rs
  6. 4
      boa_engine/src/builtins/async_function/mod.rs
  7. 4
      boa_engine/src/builtins/async_generator/mod.rs
  8. 4
      boa_engine/src/builtins/async_generator_function/mod.rs
  9. 4
      boa_engine/src/builtins/bigint/mod.rs
  10. 4
      boa_engine/src/builtins/dataview/mod.rs
  11. 4
      boa_engine/src/builtins/date/mod.rs
  12. 6
      boa_engine/src/builtins/function/arguments.rs
  13. 4
      boa_engine/src/builtins/function/mod.rs
  14. 4
      boa_engine/src/builtins/generator/mod.rs
  15. 4
      boa_engine/src/builtins/generator_function/mod.rs
  16. 4
      boa_engine/src/builtins/intl/collator/mod.rs
  17. 4
      boa_engine/src/builtins/intl/list_format/mod.rs
  18. 4
      boa_engine/src/builtins/intl/locale/mod.rs
  19. 4
      boa_engine/src/builtins/intl/mod.rs
  20. 14
      boa_engine/src/builtins/iterable/mod.rs
  21. 4
      boa_engine/src/builtins/json/mod.rs
  22. 4
      boa_engine/src/builtins/map/map_iterator.rs
  23. 8
      boa_engine/src/builtins/map/mod.rs
  24. 6
      boa_engine/src/builtins/math/mod.rs
  25. 4
      boa_engine/src/builtins/object/for_in_iterator.rs
  26. 4
      boa_engine/src/builtins/object/mod.rs
  27. 6
      boa_engine/src/builtins/promise/mod.rs
  28. 4
      boa_engine/src/builtins/reflect/mod.rs
  29. 30
      boa_engine/src/builtins/regexp/mod.rs
  30. 4
      boa_engine/src/builtins/regexp/regexp_string_iterator.rs
  31. 8
      boa_engine/src/builtins/set/mod.rs
  32. 4
      boa_engine/src/builtins/set/set_iterator.rs
  33. 24
      boa_engine/src/builtins/string/mod.rs
  34. 4
      boa_engine/src/builtins/string/string_iterator.rs
  35. 124
      boa_engine/src/builtins/symbol/mod.rs
  36. 14
      boa_engine/src/builtins/typed_array/mod.rs
  37. 4
      boa_engine/src/builtins/weak/weak_ref.rs
  38. 1
      boa_engine/src/lib.rs
  39. 18
      boa_engine/src/object/jsobject.rs
  40. 5
      boa_engine/src/object/operations.rs
  41. 223
      boa_engine/src/string/common.rs
  42. 261
      boa_engine/src/string/mod.rs
  43. 443
      boa_engine/src/symbol.rs
  44. 109
      boa_engine/src/tagged.rs
  45. 4
      boa_engine/src/value/mod.rs
  46. 4
      boa_engine/src/value/operations.rs
  47. 33
      boa_engine/src/vm/opcode/mod.rs

47
Cargo.lock generated

@ -200,6 +200,7 @@ dependencies = [
"boa_profiler", "boa_profiler",
"chrono", "chrono",
"criterion", "criterion",
"dashmap",
"fast-float", "fast-float",
"float-cmp", "float-cmp",
"icu_calendar", "icu_calendar",
@ -215,6 +216,7 @@ dependencies = [
"num-bigint", "num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
"num_enum",
"once_cell", "once_cell",
"rand", "rand",
"regress", "regress",
@ -884,6 +886,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dashmap"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if 1.0.0",
"hashbrown 0.12.3",
"lock_api",
"once_cell",
"parking_lot_core 0.9.5",
]
[[package]] [[package]]
name = "databake" name = "databake"
version = "0.1.2" version = "0.1.2"
@ -2334,6 +2349,27 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "num_threads" name = "num_threads"
version = "0.1.6" version = "0.1.6"
@ -2636,6 +2672,17 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
dependencies = [
"once_cell",
"thiserror",
"toml",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"

2
boa_engine/Cargo.toml

@ -62,6 +62,8 @@ tap = "1.0.1"
sptr = "0.3.2" sptr = "0.3.2"
static_assertions = "1.1.0" static_assertions = "1.1.0"
thiserror = "1.0.38" thiserror = "1.0.38"
dashmap = "5.4.0"
num_enum = "0.5.7"
# intl deps # intl deps
boa_icu_provider = { workspace = true, optional = true } boa_icu_provider = { workspace = true, optional = true }

4
boa_engine/src/builtins/array/array_iterator.rs

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, Context, JsResult,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -148,7 +148,7 @@ impl ArrayIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &array_iterator, 0, context); make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("Array Iterator") .value("Array Iterator")
.writable(false) .writable(false)

14
boa_engine/src/builtins/array/mod.rs

@ -32,7 +32,7 @@ use crate::{
FunctionObjectBuilder, JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptor, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::{IntegerOrInfinity, JsValue}, value::{IntegerOrInfinity, JsValue},
Context, JsResult, Context, JsResult,
}; };
@ -48,8 +48,8 @@ impl BuiltIn for Array {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = JsSymbol::iterator();
let symbol_unscopables = WellKnownSymbols::unscopables(); let symbol_unscopables = JsSymbol::unscopables();
let get_species = let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
@ -68,7 +68,7 @@ impl BuiltIn for Array {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -307,7 +307,7 @@ impl Array {
}; };
// 2. Let spreadable be ? Get(O, @@isConcatSpreadable). // 2. Let spreadable be ? Get(O, @@isConcatSpreadable).
let spreadable = o.get(WellKnownSymbols::is_concat_spreadable(), context)?; let spreadable = o.get(JsSymbol::is_concat_spreadable(), context)?;
// 3. If spreadable is not undefined, return ! ToBoolean(spreadable). // 3. If spreadable is not undefined, return ! ToBoolean(spreadable).
if !spreadable.is_undefined() { if !spreadable.is_undefined() {
@ -366,7 +366,7 @@ impl Array {
// 5. If Type(C) is Object, then // 5. If Type(C) is Object, then
let c = if let Some(c) = c.as_object() { let c = if let Some(c) = c.as_object() {
// 5.a. Set C to ? Get(C, @@species). // 5.a. Set C to ? Get(C, @@species).
let c = c.get(WellKnownSymbols::species(), context)?; let c = c.get(JsSymbol::species(), context)?;
// 5.b. If C is null, set C to undefined. // 5.b. If C is null, set C to undefined.
if c.is_null_or_undefined() { if c.is_null_or_undefined() {
JsValue::undefined() JsValue::undefined()
@ -428,7 +428,7 @@ impl Array {
}; };
// 4. Let usingIterator be ? GetMethod(items, @@iterator). // 4. Let usingIterator be ? GetMethod(items, @@iterator).
let using_iterator = items.get_method(WellKnownSymbols::iterator(), context)?; let using_iterator = items.get_method(JsSymbol::iterator(), context)?;
let Some(using_iterator) = using_iterator else { let Some(using_iterator) = using_iterator else {
// 6. NOTE: items is not an Iterable so assume it is an array-like object. // 6. NOTE: items is not an Iterable so assume it is an array-like object.

6
boa_engine/src/builtins/array_buffer/mod.rs

@ -20,7 +20,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::{IntegerOrInfinity, Numeric}, value::{IntegerOrInfinity, Numeric},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -76,7 +76,7 @@ impl BuiltIn for ArrayBuffer {
.length(Self::LENGTH) .length(Self::LENGTH)
.accessor("byteLength", Some(get_byte_length), None, flag_attributes) .accessor("byteLength", Some(get_byte_length), None, flag_attributes)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -84,7 +84,7 @@ impl BuiltIn for ArrayBuffer {
.static_method(Self::is_view, "isView", 1) .static_method(Self::is_view, "isView", 1)
.method(Self::slice, "slice", 2) .method(Self::slice, "slice", 2)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/async_function/mod.rs

@ -15,7 +15,7 @@ use crate::{
native_function::NativeFunction, native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -83,7 +83,7 @@ impl BuiltIn for AsyncFunction {
.configurable(true); .configurable(true);
prototype prototype
.borrow_mut() .borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property); .insert(JsSymbol::to_string_tag(), property);
None None
} }

4
boa_engine/src/builtins/async_generator/mod.rs

@ -14,7 +14,7 @@ use crate::{
native_function::NativeFunction, native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData}, object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
vm::GeneratorResumeKind, vm::GeneratorResumeKind,
Context, JsError, JsResult, Context, JsError, JsResult,
@ -93,7 +93,7 @@ impl BuiltIn for AsyncGenerator {
.name(Self::NAME) .name(Self::NAME)
.length(0) .length(0)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/async_generator_function/mod.rs

@ -13,7 +13,7 @@ use crate::{
native_function::NativeFunction, native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, Context, JsResult,
}; };
@ -106,7 +106,7 @@ impl BuiltIn for AsyncGeneratorFunction {
.configurable(true); .configurable(true);
prototype prototype
.borrow_mut() .borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property); .insert(JsSymbol::to_string_tag(), property);
None None
} }

4
boa_engine/src/builtins/bigint/mod.rs

@ -17,7 +17,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::ConstructorBuilder, object::ConstructorBuilder,
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::{IntegerOrInfinity, PreferredType}, value::{IntegerOrInfinity, PreferredType},
Context, JsBigInt, JsResult, JsValue, Context, JsBigInt, JsResult, JsValue,
}; };
@ -38,7 +38,7 @@ impl BuiltIn for BigInt {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
ConstructorBuilder::with_standard_constructor( ConstructorBuilder::with_standard_constructor(
context, context,

4
boa_engine/src/builtins/dataview/mod.rs

@ -17,7 +17,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, Context, JsResult,
}; };
@ -84,7 +84,7 @@ impl BuiltIn for DataView {
.method(Self::set_uint16, "setUint16", 2) .method(Self::set_uint16, "setUint16", 2)
.method(Self::set_uint32, "setUint32", 2) .method(Self::set_uint32, "setUint32", 2)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/date/mod.rs

@ -23,7 +23,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
}, },
string::utf16, string::utf16,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::{IntegerOrNan, JsValue, PreferredType}, value::{IntegerOrNan, JsValue, PreferredType},
Context, JsError, JsResult, Context, JsError, JsResult,
}; };
@ -165,7 +165,7 @@ impl BuiltIn for Date {
.method(Self::value_of, "valueOf", 0) .method(Self::value_of, "valueOf", 0)
.method( .method(
Self::to_primitive, Self::to_primitive,
(WellKnownSymbols::to_primitive(), "[Symbol.toPrimitive]"), (JsSymbol::to_primitive(), "[Symbol.toPrimitive]"),
1, 1,
) )
.build() .build()

6
boa_engine/src/builtins/function/arguments.rs

@ -2,7 +2,7 @@ use crate::{
environments::DeclarativeEnvironment, environments::DeclarativeEnvironment,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::{self, WellKnownSymbols}, symbol::{self, JsSymbol},
Context, JsValue, Context, JsValue,
}; };
use boa_ast::{function::FormalParameterList, operations::bound_names}; use boa_ast::{function::FormalParameterList, operations::bound_names};
@ -113,7 +113,7 @@ impl Arguments {
// [[Configurable]]: true }). // [[Configurable]]: true }).
let values_function = context.intrinsics().objects().array_prototype_values(); let values_function = context.intrinsics().objects().array_prototype_values();
obj.define_property_or_throw( obj.define_property_or_throw(
symbol::WellKnownSymbols::iterator(), symbol::JsSymbol::iterator(),
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(values_function) .value(values_function)
.writable(true) .writable(true)
@ -264,7 +264,7 @@ impl Arguments {
// [[Configurable]]: true }). // [[Configurable]]: true }).
let values_function = context.intrinsics().objects().array_prototype_values(); let values_function = context.intrinsics().objects().array_prototype_values();
obj.define_property_or_throw( obj.define_property_or_throw(
WellKnownSymbols::iterator(), JsSymbol::iterator(),
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(values_function) .value(values_function)
.writable(true) .writable(true)

4
boa_engine/src/builtins/function/mod.rs

@ -23,7 +23,7 @@ use crate::{
object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement}, object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
string::utf16, string::utf16,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::IntegerOrInfinity, value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
@ -879,7 +879,7 @@ impl BuiltIn for BuiltInFunctionObject {
.constructor(false) .constructor(false)
.build_function_prototype(&function_prototype); .build_function_prototype(&function_prototype);
let symbol_has_instance = WellKnownSymbols::has_instance(); let symbol_has_instance = JsSymbol::has_instance();
let has_instance = let has_instance =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::has_instance)) FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::has_instance))

4
boa_engine/src/builtins/generator/mod.rs

@ -15,7 +15,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{ConstructorBuilder, JsObject, ObjectData}, object::{ConstructorBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
vm::{CallFrame, GeneratorResumeKind, ReturnType}, vm::{CallFrame, GeneratorResumeKind, ReturnType},
Context, JsError, JsResult, Context, JsError, JsResult,
@ -81,7 +81,7 @@ impl BuiltIn for Generator {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/generator_function/mod.rs

@ -18,7 +18,7 @@ use crate::{
native_function::NativeFunction, native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, Context, JsResult,
}; };
@ -105,7 +105,7 @@ impl BuiltIn for GeneratorFunction {
.configurable(true); .configurable(true);
prototype prototype
.borrow_mut() .borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property); .insert(JsSymbol::to_string_tag(), property);
None None
} }

4
boa_engine/src/builtins/intl/collator/mod.rs

@ -20,7 +20,7 @@ use crate::{
FunctionObjectBuilder, JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue,
}; };
@ -176,7 +176,7 @@ impl BuiltIn for Collator {
.length(Self::LENGTH) .length(Self::LENGTH)
.static_method(Self::supported_locales_of, "supportedLocalesOf", 1) .static_method(Self::supported_locales_of, "supportedLocalesOf", 1)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
"Intl.Collator", "Intl.Collator",
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/intl/list_format/mod.rs

@ -13,7 +13,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsNativeError, JsResult, JsString, JsValue, Context, JsNativeError, JsResult, JsString, JsValue,
}; };
@ -64,7 +64,7 @@ impl BuiltIn for ListFormat {
.length(Self::LENGTH) .length(Self::LENGTH)
.static_method(Self::supported_locales_of, "supportedLocalesOf", 1) .static_method(Self::supported_locales_of, "supportedLocalesOf", 1)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
"Intl.ListFormat", "Intl.ListFormat",
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/intl/locale/mod.rs

@ -26,7 +26,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsNativeError, JsResult, JsString, JsValue, Context, JsNativeError, JsResult, JsString, JsValue,
}; };
@ -109,7 +109,7 @@ impl BuiltIn for Locale {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
"Intl.Locale", "Intl.Locale",
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/intl/mod.rs

@ -16,7 +16,7 @@ use crate::{
context::BoaProvider, context::BoaProvider,
object::ObjectInitializer, object::ObjectInitializer,
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -57,7 +57,7 @@ impl BuiltIn for Intl {
ObjectInitializer::new(context) ObjectInitializer::new(context)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

14
boa_engine/src/builtins/iterable/mod.rs

@ -10,7 +10,7 @@ use crate::{
}, },
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectInitializer}, object::{JsObject, ObjectInitializer},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use async_from_sync_iterator::create_async_from_sync_iterator_prototype; use async_from_sync_iterator::create_async_from_sync_iterator_prototype;
@ -181,14 +181,12 @@ impl JsValue {
// a. If hint is async, then // a. If hint is async, then
if hint == IteratorHint::Async { if hint == IteratorHint::Async {
// i. Set method to ? GetMethod(obj, @@asyncIterator). // i. Set method to ? GetMethod(obj, @@asyncIterator).
if let Some(method) = if let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? {
self.get_method(WellKnownSymbols::async_iterator(), context)?
{
Some(method) Some(method)
} else { } else {
// ii. If method is undefined, then // ii. If method is undefined, then
// 1. Let syncMethod be ? GetMethod(obj, @@iterator). // 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?; let sync_method = self.get_method(JsSymbol::iterator(), context)?;
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let sync_iterator_record = let sync_iterator_record =
@ -199,7 +197,7 @@ impl JsValue {
} }
} else { } else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator). // b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)? self.get_method(JsSymbol::iterator(), context)?
} }
} }
.ok_or_else(|| { .ok_or_else(|| {
@ -239,7 +237,7 @@ impl JsValue {
fn create_iterator_prototype(context: &mut Context<'_>) -> JsObject { fn create_iterator_prototype(context: &mut Context<'_>) -> JsObject {
let _timer = Profiler::global().start_event("Iterator Prototype", "init"); let _timer = Profiler::global().start_event("Iterator Prototype", "init");
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = JsSymbol::iterator();
let iterator_prototype = ObjectInitializer::new(context) let iterator_prototype = ObjectInitializer::new(context)
.function( .function(
|v, _, _| Ok(v.clone()), |v, _, _| Ok(v.clone()),
@ -563,7 +561,7 @@ pub(crate) use if_abrupt_close_iterator;
fn create_async_iterator_prototype(context: &mut Context<'_>) -> JsObject { fn create_async_iterator_prototype(context: &mut Context<'_>) -> JsObject {
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init"); let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");
let symbol_iterator = WellKnownSymbols::async_iterator(); let symbol_iterator = JsSymbol::async_iterator();
let iterator_prototype = ObjectInitializer::new(context) let iterator_prototype = ObjectInitializer::new(context)
.function( .function(
|v, _, _| Ok(v.clone()), |v, _, _| Ok(v.clone()),

4
boa_engine/src/builtins/json/mod.rs

@ -26,7 +26,7 @@ use crate::{
object::{JsObject, ObjectInitializer, RecursionLimiter}, object::{JsObject, ObjectInitializer, RecursionLimiter},
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
string::{utf16, CodePoint}, string::{utf16, CodePoint},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::IntegerOrInfinity, value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
@ -141,7 +141,7 @@ impl BuiltIn for Json {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
ObjectInitializer::new(context) ObjectInitializer::new(context)

4
boa_engine/src/builtins/map/map_iterator.rs

@ -11,7 +11,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, Context, JsResult,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -147,7 +147,7 @@ impl MapIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &map_iterator, 0, context); make_builtin_fn(Self::next, "next", &map_iterator, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("Map Iterator") .value("Map Iterator")
.writable(false) .writable(false)

8
boa_engine/src/builtins/map/mod.rs

@ -22,7 +22,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -71,7 +71,7 @@ impl BuiltIn for Map {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -82,12 +82,12 @@ impl BuiltIn for Map {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )
.property( .property(
WellKnownSymbols::iterator(), JsSymbol::iterator(),
entries_function, entries_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

6
boa_engine/src/builtins/math/mod.rs

@ -13,8 +13,8 @@
use super::JsArgs; use super::JsArgs;
use crate::{ use crate::{
builtins::BuiltIn, object::ObjectInitializer, property::Attribute, symbol::WellKnownSymbols, builtins::BuiltIn, object::ObjectInitializer, property::Attribute, symbol::JsSymbol, Context,
Context, JsResult, JsValue, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
use tap::{Conv, Pipe}; use tap::{Conv, Pipe};
@ -33,7 +33,7 @@ impl BuiltIn for Math {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let string_tag = WellKnownSymbols::to_string_tag(); let string_tag = JsSymbol::to_string_tag();
ObjectInitializer::new(context) ObjectInitializer::new(context)
.property("E", std::f64::consts::E, attribute) .property("E", std::f64::consts::E, attribute)
.property("LN10", std::f64::consts::LN_10, attribute) .property("LN10", std::f64::consts::LN_10, attribute)

4
boa_engine/src/builtins/object/for_in_iterator.rs

@ -11,7 +11,7 @@ use crate::{
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
property::PropertyKey, property::PropertyKey,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -148,7 +148,7 @@ impl ForInIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context); make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("For In Iterator") .value("For In Iterator")
.writable(false) .writable(false)

4
boa_engine/src/builtins/object/mod.rs

@ -28,7 +28,7 @@ use crate::{
}, },
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
string::utf16, string::utf16,
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
@ -822,7 +822,7 @@ impl Object {
}; };
// 15. Let tag be ? Get(O, @@toStringTag). // 15. Let tag be ? Get(O, @@toStringTag).
let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; let tag = o.get(JsSymbol::to_string_tag(), context)?;
// 16. If Type(tag) is not String, set tag to builtinTag. // 16. If Type(tag) is not String, set tag to builtinTag.
let tag_str = tag.as_string().map_or(builtin_tag, JsString::deref); let tag_str = tag.as_string().map_or(builtin_tag, JsString::deref);

6
boa_engine/src/builtins/promise/mod.rs

@ -15,7 +15,7 @@ use crate::{
FunctionObjectBuilder, JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptorBuilder}, property::{Attribute, PropertyDescriptorBuilder},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsError, JsResult, Context, JsError, JsResult,
}; };
@ -274,7 +274,7 @@ impl BuiltIn for Promise {
.static_method(Self::reject, "reject", 1) .static_method(Self::reject, "reject", 1)
.static_method(Self::resolve, "resolve", 1) .static_method(Self::resolve, "resolve", 1)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -284,7 +284,7 @@ impl BuiltIn for Promise {
.method(Self::finally, "finally", 1) .method(Self::finally, "finally", 1)
// <https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag> // <https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag>
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Self::NAME, Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )

4
boa_engine/src/builtins/reflect/mod.rs

@ -16,7 +16,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::ObjectInitializer, object::ObjectInitializer,
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -35,7 +35,7 @@ impl BuiltIn for Reflect {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
ObjectInitializer::new(context) ObjectInitializer::new(context)
.function(Self::apply, "apply", 3) .function(Self::apply, "apply", 3)

30
boa_engine/src/builtins/regexp/mod.rs

@ -25,7 +25,7 @@ use crate::{
}, },
property::{Attribute, PropertyDescriptorBuilder}, property::{Attribute, PropertyDescriptorBuilder},
string::{utf16, CodePoint}, string::{utf16, CodePoint},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
@ -115,7 +115,7 @@ impl BuiltIn for RegExp {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -124,31 +124,15 @@ impl BuiltIn for RegExp {
.method(Self::test, "test", 1) .method(Self::test, "test", 1)
.method(Self::exec, "exec", 1) .method(Self::exec, "exec", 1)
.method(Self::to_string, "toString", 0) .method(Self::to_string, "toString", 0)
.method( .method(Self::r#match, (JsSymbol::r#match(), "[Symbol.match]"), 1)
Self::r#match,
(WellKnownSymbols::r#match(), "[Symbol.match]"),
1,
)
.method( .method(
Self::match_all, Self::match_all,
(WellKnownSymbols::match_all(), "[Symbol.matchAll]"), (JsSymbol::match_all(), "[Symbol.matchAll]"),
1,
)
.method(
Self::replace,
(WellKnownSymbols::replace(), "[Symbol.replace]"),
2,
)
.method(
Self::search,
(WellKnownSymbols::search(), "[Symbol.search]"),
1, 1,
) )
.method( .method(Self::replace, (JsSymbol::replace(), "[Symbol.replace]"), 2)
Self::split, .method(Self::search, (JsSymbol::search(), "[Symbol.search]"), 1)
(WellKnownSymbols::split(), "[Symbol.split]"), .method(Self::split, (JsSymbol::split(), "[Symbol.split]"), 2)
2,
)
.accessor("hasIndices", Some(get_has_indices), None, flag_attributes) .accessor("hasIndices", Some(get_has_indices), None, flag_attributes)
.accessor("global", Some(get_global), None, flag_attributes) .accessor("global", Some(get_global), None, flag_attributes)
.accessor("ignoreCase", Some(get_ignore_case), None, flag_attributes) .accessor("ignoreCase", Some(get_ignore_case), None, flag_attributes)

4
boa_engine/src/builtins/regexp/regexp_string_iterator.rs

@ -15,7 +15,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -172,7 +172,7 @@ impl RegExpStringIterator {
let result = JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); let result = JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &result, 0, context); make_builtin_fn(Self::next, "next", &result, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("RegExp String Iterator") .value("RegExp String Iterator")
.writable(false) .writable(false)

8
boa_engine/src/builtins/set/mod.rs

@ -22,7 +22,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -54,9 +54,9 @@ impl BuiltIn for Set {
.name("get size") .name("get size")
.build(); .build();
let iterator_symbol = WellKnownSymbols::iterator(); let iterator_symbol = JsSymbol::iterator();
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let values_function = let values_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values)) FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values))
@ -73,7 +73,7 @@ impl BuiltIn for Set {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,

4
boa_engine/src/builtins/set/set_iterator.rs

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, Context, JsResult,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -154,7 +154,7 @@ impl SetIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &set_iterator, 0, context); make_builtin_fn(Self::next, "next", &set_iterator, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("Set Iterator") .value("Set Iterator")
.writable(false) .writable(false)

24
boa_engine/src/builtins/string/mod.rs

@ -25,7 +25,7 @@ use crate::{
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
string::utf16, string::utf16,
string::{CodePoint, Utf16Trim}, string::{CodePoint, Utf16Trim},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::IntegerOrInfinity, value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
@ -69,7 +69,7 @@ impl BuiltIn for String {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = JsSymbol::iterator();
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
ConstructorBuilder::with_standard_constructor( ConstructorBuilder::with_standard_constructor(
@ -930,7 +930,7 @@ impl String {
// 2. If searchValue is neither undefined nor null, then // 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() { if !search_value.is_null_or_undefined() {
// a. Let replacer be ? GetMethod(searchValue, @@replace). // a. Let replacer be ? GetMethod(searchValue, @@replace).
let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; let replacer = search_value.get_method(JsSymbol::replace(), context)?;
// b. If replacer is not undefined, then // b. If replacer is not undefined, then
if let Some(replacer) = replacer { if let Some(replacer) = replacer {
@ -1053,7 +1053,7 @@ impl String {
} }
// c. Let replacer be ? GetMethod(searchValue, @@replace). // c. Let replacer be ? GetMethod(searchValue, @@replace).
let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; let replacer = search_value.get_method(JsSymbol::replace(), context)?;
// d. If replacer is not undefined, then // d. If replacer is not undefined, then
if let Some(replacer) = replacer { if let Some(replacer) = replacer {
@ -1358,7 +1358,7 @@ impl String {
let regexp = args.get_or_undefined(0); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let matcher be ? GetMethod(regexp, @@match). // a. Let matcher be ? GetMethod(regexp, @@match).
let matcher = regexp.get_method(WellKnownSymbols::r#match(), context)?; let matcher = regexp.get_method(JsSymbol::r#match(), context)?;
// b. If matcher is not undefined, then // b. If matcher is not undefined, then
if let Some(matcher) = matcher { if let Some(matcher) = matcher {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
@ -1373,7 +1373,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; let rx = RegExp::create(regexp, &JsValue::Undefined, context)?;
// 5. Return ? Invoke(rx, @@match, « S »). // 5. Return ? Invoke(rx, @@match, « S »).
rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) rx.invoke(JsSymbol::r#match(), &[JsValue::new(s)], context)
} }
/// Abstract operation `StringPad ( O, maxLength, fillString, placement )`. /// Abstract operation `StringPad ( O, maxLength, fillString, placement )`.
@ -1828,7 +1828,7 @@ impl String {
// 2. If separator is neither undefined nor null, then // 2. If separator is neither undefined nor null, then
if !separator.is_null_or_undefined() { if !separator.is_null_or_undefined() {
// a. Let splitter be ? GetMethod(separator, @@split). // a. Let splitter be ? GetMethod(separator, @@split).
let splitter = separator.get_method(WellKnownSymbols::split(), context)?; let splitter = separator.get_method(JsSymbol::split(), context)?;
// b. If splitter is not undefined, then // b. If splitter is not undefined, then
if let Some(splitter) = splitter { if let Some(splitter) = splitter {
// i. Return ? Call(splitter, separator, « O, limit »). // i. Return ? Call(splitter, separator, « O, limit »).
@ -1986,7 +1986,7 @@ impl String {
} }
} }
// c. Let matcher be ? GetMethod(regexp, @@matchAll). // c. Let matcher be ? GetMethod(regexp, @@matchAll).
let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; let matcher = regexp.get_method(JsSymbol::match_all(), context)?;
// d. If matcher is not undefined, then // d. If matcher is not undefined, then
if let Some(matcher) = matcher { if let Some(matcher) = matcher {
return matcher.call(regexp, &[o.clone()], context); return matcher.call(regexp, &[o.clone()], context);
@ -2000,7 +2000,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::new(js_string!("g")), context)?; let rx = RegExp::create(regexp, &JsValue::new(js_string!("g")), context)?;
// 5. Return ? Invoke(rx, @@matchAll, « S »). // 5. Return ? Invoke(rx, @@matchAll, « S »).
rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context) rx.invoke(JsSymbol::match_all(), &[JsValue::new(s)], context)
} }
/// `String.prototype.normalize( [ form ] )` /// `String.prototype.normalize( [ form ] )`
@ -2127,7 +2127,7 @@ impl String {
let regexp = args.get_or_undefined(0); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let searcher be ? GetMethod(regexp, @@search). // a. Let searcher be ? GetMethod(regexp, @@search).
let searcher = regexp.get_method(WellKnownSymbols::search(), context)?; let searcher = regexp.get_method(JsSymbol::search(), context)?;
// b. If searcher is not undefined, then // b. If searcher is not undefined, then
if let Some(searcher) = searcher { if let Some(searcher) = searcher {
// i. Return ? Call(searcher, regexp, « O »). // i. Return ? Call(searcher, regexp, « O »).
@ -2142,7 +2142,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; let rx = RegExp::create(regexp, &JsValue::Undefined, context)?;
// 5. Return ? Invoke(rx, @@search, « string »). // 5. Return ? Invoke(rx, @@search, « string »).
rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) rx.invoke(JsSymbol::search(), &[JsValue::new(string)], context)
} }
pub(crate) fn iterator( pub(crate) fn iterator(
@ -2364,7 +2364,7 @@ fn is_reg_exp(argument: &JsValue, context: &mut Context<'_>) -> JsResult<bool> {
} }
fn is_reg_exp_object(argument: &JsObject, context: &mut Context<'_>) -> JsResult<bool> { fn is_reg_exp_object(argument: &JsObject, context: &mut Context<'_>) -> JsResult<bool> {
// 2. Let matcher be ? Get(argument, @@match). // 2. Let matcher be ? Get(argument, @@match).
let matcher = argument.get(WellKnownSymbols::r#match(), context)?; let matcher = argument.get(JsSymbol::r#match(), context)?;
// 3. If matcher is not undefined, return ! ToBoolean(matcher). // 3. If matcher is not undefined, return ! ToBoolean(matcher).
if !matcher.is_undefined() { if !matcher.is_undefined() {

4
boa_engine/src/builtins/string/string_iterator.rs

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -98,7 +98,7 @@ impl StringIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary()); JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &array_iterator, 0, context); make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = JsSymbol::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()
.value("String Iterator") .value("String Iterator")
.writable(false) .writable(false)

124
boa_engine/src/builtins/symbol/mod.rs

@ -18,53 +18,67 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use std::hash::BuildHasherDefault;
use super::JsArgs; use super::JsArgs;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
error::JsNativeError, error::JsNativeError,
js_string,
native_function::NativeFunction, native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder}, object::{ConstructorBuilder, FunctionObjectBuilder},
property::Attribute, property::Attribute,
symbol::{JsSymbol, WellKnownSymbols}, symbol::JsSymbol,
value::JsValue, value::JsValue,
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
use rustc_hash::FxHashMap; use dashmap::DashMap;
use std::cell::RefCell; use once_cell::sync::Lazy;
use rustc_hash::FxHasher;
use tap::{Conv, Pipe}; use tap::{Conv, Pipe};
thread_local! { static GLOBAL_SYMBOL_REGISTRY: Lazy<GlobalSymbolRegistry> = Lazy::new(GlobalSymbolRegistry::new);
static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
} type FxDashMap<K, V> = DashMap<K, V, BuildHasherDefault<FxHasher>>;
// We previously used `JsString` instead of `Box<[u16]>` for this, but since the glocal symbol
// registry needed to be global, we had to either make `JsString` thread-safe or directly store
// its info into the registry. `JsSymbol` is already a pretty niche feature of JS, and we expect only
// advanced users to utilize it. On the other hand, almost every JS programmer uses `JsString`s, and
// the first option would impact performance for all `JsString`s in general. For those reasons, we
// opted for the second option, but we should try to optimize this in the future.
struct GlobalSymbolRegistry { struct GlobalSymbolRegistry {
keys: FxHashMap<JsString, JsSymbol>, keys: FxDashMap<Box<[u16]>, JsSymbol>,
symbols: FxHashMap<JsSymbol, JsString>, symbols: FxDashMap<JsSymbol, Box<[u16]>>,
} }
impl GlobalSymbolRegistry { impl GlobalSymbolRegistry {
fn new() -> Self { fn new() -> Self {
Self { Self {
keys: FxHashMap::default(), keys: FxDashMap::default(),
symbols: FxHashMap::default(), symbols: FxDashMap::default(),
} }
} }
fn get_or_insert_key(&mut self, key: JsString) -> JsSymbol { fn get_or_create_symbol(&self, key: &JsString) -> JsResult<JsSymbol> {
if let Some(symbol) = self.keys.get(&key) { let slice = &**key;
return symbol.clone(); if let Some(symbol) = self.keys.get(slice) {
return Ok(symbol.clone());
} }
let symbol = JsSymbol::new(Some(key.clone())); let symbol = JsSymbol::new(Some(key.clone())).ok_or_else(|| {
self.keys.insert(key.clone(), symbol.clone()); JsNativeError::range()
self.symbols.insert(symbol.clone(), key); .with_message("reached the maximum number of symbols that can be created")
symbol })?;
self.keys.insert(slice.into(), symbol.clone());
self.symbols.insert(symbol.clone(), slice.into());
Ok(symbol)
} }
fn get_symbol(&self, sym: &JsSymbol) -> Option<JsString> { fn get_key(&self, sym: &JsSymbol) -> Option<JsString> {
if let Some(key) = self.symbols.get(sym) { if let Some(key) = self.symbols.get(sym) {
return Some(key.clone()); return Some(js_string!(&**key));
} }
None None
@ -81,19 +95,19 @@ impl BuiltIn for Symbol {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_async_iterator = WellKnownSymbols::async_iterator(); let symbol_async_iterator = JsSymbol::async_iterator();
let symbol_has_instance = WellKnownSymbols::has_instance(); let symbol_has_instance = JsSymbol::has_instance();
let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable(); let symbol_is_concat_spreadable = JsSymbol::is_concat_spreadable();
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = JsSymbol::iterator();
let symbol_match = WellKnownSymbols::r#match(); let symbol_match = JsSymbol::r#match();
let symbol_match_all = WellKnownSymbols::match_all(); let symbol_match_all = JsSymbol::match_all();
let symbol_replace = WellKnownSymbols::replace(); let symbol_replace = JsSymbol::replace();
let symbol_search = WellKnownSymbols::search(); let symbol_search = JsSymbol::search();
let symbol_species = WellKnownSymbols::species(); let symbol_species = JsSymbol::species();
let symbol_split = WellKnownSymbols::split(); let symbol_split = JsSymbol::split();
let symbol_to_primitive = WellKnownSymbols::to_primitive(); let symbol_to_primitive = JsSymbol::to_primitive();
let symbol_to_string_tag = WellKnownSymbols::to_string_tag(); let symbol_to_string_tag = JsSymbol::to_string_tag();
let symbol_unscopables = WellKnownSymbols::unscopables(); let symbol_unscopables = JsSymbol::unscopables();
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
@ -193,7 +207,12 @@ impl Symbol {
}; };
// 4. Return a new unique Symbol value whose [[Description]] value is descString. // 4. Return a new unique Symbol value whose [[Description]] value is descString.
Ok(JsSymbol::new(description).into()) Ok(JsSymbol::new(description)
.ok_or_else(|| {
JsNativeError::range()
.with_message("reached the maximum number of symbols that can be created")
})?
.into())
} }
fn this_symbol_value(value: &JsValue) -> JsResult<JsSymbol> { fn this_symbol_value(value: &JsValue) -> JsResult<JsSymbol> {
@ -300,12 +319,9 @@ impl Symbol {
// 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey. // 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey.
// 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List. // 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List.
// 6. Return newSymbol. // 6. Return newSymbol.
Ok(GLOBAL_SYMBOL_REGISTRY GLOBAL_SYMBOL_REGISTRY
.with(move |registry| { .get_or_create_symbol(&string_key)
let mut registry = registry.borrow_mut(); .map(JsValue::from)
registry.get_or_insert_key(string_key)
})
.into())
} }
/// `Symbol.keyFor( sym )` /// `Symbol.keyFor( sym )`
@ -318,24 +334,20 @@ impl Symbol {
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.keyfor /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.keyfor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor
pub(crate) fn key_for(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> { pub(crate) fn key_for(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
let sym = args.get_or_undefined(0);
// 1. If Type(sym) is not Symbol, throw a TypeError exception. // 1. If Type(sym) is not Symbol, throw a TypeError exception.
if let Some(sym) = sym.as_symbol() { let sym = args.get_or_undefined(0).as_symbol().ok_or_else(|| {
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do JsNativeError::typ().with_message("Symbol.keyFor: sym is not a symbol")
// a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. })?;
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
// 4. Return undefined. // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]].
let registry = registry.borrow(); // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
registry.get_symbol(&sym) // 4. Return undefined.
});
Ok(GLOBAL_SYMBOL_REGISTRY
Ok(symbol.map(JsValue::from).unwrap_or_default()) .get_key(&sym)
} else { .map(JsValue::from)
Err(JsNativeError::typ() .unwrap_or_default())
.with_message("Symbol.keyFor: sym is not a symbol")
.into())
}
} }
/// `Symbol.prototype [ @@toPrimitive ]` /// `Symbol.prototype [ @@toPrimitive ]`

14
boa_engine/src/builtins/typed_array/mod.rs

@ -28,7 +28,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::JsSymbol,
value::{IntegerOrInfinity, JsValue}, value::{IntegerOrInfinity, JsValue},
Context, JsResult, Context, JsResult,
}; };
@ -87,7 +87,7 @@ macro_rules! typed_array {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -197,7 +197,7 @@ macro_rules! typed_array {
let first_argument_v = JsValue::from(first_argument.clone()); let first_argument_v = JsValue::from(first_argument.clone());
let using_iterator = let using_iterator =
first_argument_v.get_method(WellKnownSymbols::replace(), context)?; first_argument_v.get_method(JsSymbol::replace(), context)?;
// 3. If usingIterator is not undefined, then // 3. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator { if let Some(using_iterator) = using_iterator {
@ -305,7 +305,7 @@ impl BuiltIn for TypedArray {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.static_accessor( .static_accessor(
WellKnownSymbols::species(), JsSymbol::species(),
Some(get_species), Some(get_species),
None, None,
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
@ -316,7 +316,7 @@ impl BuiltIn for TypedArray {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
) )
.property( .property(
WellKnownSymbols::iterator(), JsSymbol::iterator(),
values_function, values_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )
@ -345,7 +345,7 @@ impl BuiltIn for TypedArray {
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
) )
.accessor( .accessor(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
Some(get_to_string_tag), Some(get_to_string_tag),
None, None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
@ -441,7 +441,7 @@ impl TypedArray {
// 5. Let usingIterator be ? GetMethod(source, @@iterator). // 5. Let usingIterator be ? GetMethod(source, @@iterator).
let source = args.get_or_undefined(0); let source = args.get_or_undefined(0);
let using_iterator = source.get_method(WellKnownSymbols::iterator(), context)?; let using_iterator = source.get_method(JsSymbol::iterator(), context)?;
let this_arg = args.get_or_undefined(2); let this_arg = args.get_or_undefined(2);

4
boa_engine/src/builtins/weak/weak_ref.rs

@ -9,7 +9,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::JsSymbol,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue,
}; };
@ -41,7 +41,7 @@ impl BuiltIn for WeakRef {
.name(Self::NAME) .name(Self::NAME)
.length(Self::LENGTH) .length(Self::LENGTH)
.property( .property(
WellKnownSymbols::to_string_tag(), JsSymbol::to_string_tag(),
"WeakRef", "WeakRef",
Attribute::CONFIGURABLE, Attribute::CONFIGURABLE,
) )

1
boa_engine/src/lib.rs

@ -124,6 +124,7 @@ pub mod symbol;
pub mod value; pub mod value;
pub mod vm; pub mod vm;
pub(crate) mod tagged;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

18
boa_engine/src/object/jsobject.rs

@ -840,10 +840,10 @@ impl Drop for RecursionLimiter {
fn drop(&mut self) { fn drop(&mut self) {
if self.top_level { if self.top_level {
// When the top level of the graph is dropped, we can free the entire map for the next traversal. // When the top level of the graph is dropped, we can free the entire map for the next traversal.
Self::SEEN.with(|hm| hm.borrow_mut().clear()); SEEN.with(|hm| hm.borrow_mut().clear());
} else if !self.live { } else if !self.live {
// This was the first RL for this object to become live, so it's no longer live now that it's dropped. // This was the first RL for this object to become live, so it's no longer live now that it's dropped.
Self::SEEN.with(|hm| { SEEN.with(|hm| {
hm.borrow_mut() hm.borrow_mut()
.insert(self.ptr, RecursionValueState::Visited) .insert(self.ptr, RecursionValueState::Visited)
}); });
@ -851,13 +851,13 @@ impl Drop for RecursionLimiter {
} }
} }
impl RecursionLimiter { thread_local! {
thread_local! { /// The map of pointers to `JsObject` that have been visited during the current `Debug::fmt` graph,
/// The map of pointers to `JsObject` that have been visited during the current `Debug::fmt` graph, /// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`)
/// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`) static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new()); }
}
impl RecursionLimiter {
/// Determines if the specified `JsObject` has been visited, and returns a struct that will free it when dropped. /// Determines if the specified `JsObject` has been visited, and returns a struct that will free it when dropped.
/// ///
/// This is done by maintaining a thread-local hashset containing the pointers of `JsObject` values that have been /// This is done by maintaining a thread-local hashset containing the pointers of `JsObject` values that have been
@ -867,7 +867,7 @@ impl RecursionLimiter {
// We shouldn't have to worry too much about this being moved during Debug::fmt. // We shouldn't have to worry too much about this being moved during Debug::fmt.
#[allow(trivial_casts)] #[allow(trivial_casts)]
let ptr = (o.as_ref() as *const _) as usize; let ptr = (o.as_ref() as *const _) as usize;
let (top_level, visited, live) = Self::SEEN.with(|hm| { let (top_level, visited, live) = SEEN.with(|hm| {
let mut hm = hm.borrow_mut(); let mut hm = hm.borrow_mut();
let top_level = hm.is_empty(); let top_level = hm.is_empty();
let old_state = hm.insert(ptr, RecursionValueState::Live); let old_state = hm.insert(ptr, RecursionValueState::Live);

5
boa_engine/src/object/operations.rs

@ -4,9 +4,8 @@ use crate::{
error::JsNativeError, error::JsNativeError,
object::{JsObject, PrivateElement}, object::{JsObject, PrivateElement},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::Type, value::Type,
Context, JsResult, JsValue, Context, JsResult, JsSymbol, JsValue,
}; };
use boa_ast::function::PrivateName; use boa_ast::function::PrivateName;
@ -512,7 +511,7 @@ impl JsObject {
})?; })?;
// 5. Let S be ? Get(C, @@species). // 5. Let S be ? Get(C, @@species).
let s = c.get(WellKnownSymbols::species(), context)?; let s = c.get(JsSymbol::species(), context)?;
// 6. If S is either undefined or null, return defaultConstructor. // 6. If S is either undefined or null, return defaultConstructor.
if s.is_null_or_undefined() { if s.is_null_or_undefined() {

223
boa_engine/src/string/common.rs

@ -1,15 +1,145 @@
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use crate::tagged::Tagged;
use super::JsString;
use boa_macros::utf16; use boa_macros::utf16;
use rustc_hash::{FxHashMap, FxHasher}; use rustc_hash::{FxHashMap, FxHasher};
use super::{JsString, TaggedJsString}; macro_rules! well_known_statics {
( $( $(#[$attr:meta])* ($name:ident, $string:literal) ),+$(,)? ) => {
$(
$(#[$attr])* pub(crate) const fn $name() -> JsString {
JsString {
ptr: Tagged::from_tag(
Self::find_index(utf16!($string)),
),
}
}
)+
};
}
/// List of commonly used strings in Javascript code. /// List of commonly used strings in Javascript code.
/// ///
/// Any string defined here is used as a static [`JsString`] instead of allocating on the heap. /// Any strings defined here are used as a static [`JsString`] instead of allocating on the heap.
pub(super) const COMMON_STRINGS: &[&[u16]] = &[ #[derive(Debug)]
// Empty string pub(crate) struct StaticJsStrings;
impl StaticJsStrings {
// useful to search at compile time a certain string in the array
const fn find_index(candidate: &[u16]) -> usize {
const fn const_eq(lhs: &[u16], rhs: &[u16]) -> bool {
if lhs.len() != rhs.len() {
return false;
}
let mut i = 0;
while i < lhs.len() {
if lhs[i] != rhs[i] {
return false;
}
i += 1;
}
true
}
let mut i = 0;
while i < RAW_STATICS.len() {
let s = RAW_STATICS[i];
if const_eq(s, candidate) {
return i;
}
i += 1;
}
panic!("couldn't find the required string on the common string array");
}
/// Gets the `JsString` corresponding to `string`, or `None` if the string
/// doesn't exist inside the static array.
pub(crate) fn get_string(string: &[u16]) -> Option<JsString> {
if string.len() > MAX_STATIC_LENGTH {
return None;
}
let index = RAW_STATICS_CACHE.with(|map| map.get(string).copied())?;
Some(JsString {
ptr: Tagged::from_tag(index),
})
}
/// Gets the `&[u16]` slice corresponding to the provided index, or `None` if the index
/// provided exceeds the size of the static array.
pub(crate) fn get(index: usize) -> Option<&'static [u16]> {
RAW_STATICS.get(index).copied()
}
well_known_statics! {
/// Gets the empty string (`""`) `JsString`.
(empty_string, ""),
/// Gets the static `JsString` for `"Symbol.asyncIterator"`.
(symbol_async_iterator, "Symbol.asyncIterator"),
/// Gets the static `JsString` for `"Symbol.hasInstance"`.
(symbol_has_instance, "Symbol.hasInstance"),
/// Gets the static `JsString` for `"Symbol.isConcatSpreadable"`.
(symbol_is_concat_spreadable, "Symbol.isConcatSpreadable"),
/// Gets the static `JsString` for `"Symbol.iterator"`.
(symbol_iterator, "Symbol.iterator"),
/// Gets the static `JsString` for `"Symbol.match"`.
(symbol_match, "Symbol.match"),
/// Gets the static `JsString` for `"Symbol.matchAll"`.
(symbol_match_all, "Symbol.matchAll"),
/// Gets the static `JsString` for `"Symbol.replace"`.
(symbol_replace, "Symbol.replace"),
/// Gets the static `JsString` for `"Symbol.search"`.
(symbol_search, "Symbol.search"),
/// Gets the static `JsString` for `"Symbol.species"`.
(symbol_species, "Symbol.species"),
/// Gets the static `JsString` for `"Symbol.split"`.
(symbol_split, "Symbol.split"),
/// Gets the static `JsString` for `"Symbol.toPrimitive"`.
(symbol_to_primitive, "Symbol.toPrimitive"),
/// Gets the static `JsString` for `"Symbol.toStringTag"`.
(symbol_to_string_tag, "Symbol.toStringTag"),
/// Gets the static `JsString` for `"Symbol.unscopables"`.
(symbol_unscopables, "Symbol.unscopables"),
}
}
static MAX_STATIC_LENGTH: usize = {
let mut max = 0;
let mut i = 0;
while i < RAW_STATICS.len() {
let len = RAW_STATICS[i].len();
if len > max {
max = len;
}
i += 1;
}
max
};
thread_local! {
/// Map from a string inside [`RAW_STATICS`] to its corresponding static index on `RAW_STATICS`.
static RAW_STATICS_CACHE: FxHashMap<&'static [u16], usize> = {
let mut constants = FxHashMap::with_capacity_and_hasher(
RAW_STATICS.len(),
BuildHasherDefault::<FxHasher>::default(),
);
for (idx, &s) in RAW_STATICS.iter().enumerate() {
constants.insert(s, idx);
}
constants
};
}
/// Array of raw static strings that aren't reference counted.
///
/// The macro `static_strings` automatically sorts the array of strings, making it faster
/// for searches by using `binary_search`.
const RAW_STATICS: &[&[u16]] = &[
utf16!(""), utf16!(""),
// Misc // Misc
utf16!(","), utf16!(","),
@ -80,7 +210,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("isArray"), utf16!("isArray"),
utf16!("of"), utf16!("of"),
utf16!("copyWithin"), utf16!("copyWithin"),
utf16!("entries"),
utf16!("every"), utf16!("every"),
utf16!("fill"), utf16!("fill"),
utf16!("filter"), utf16!("filter"),
@ -116,8 +245,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("endsWith"), utf16!("endsWith"),
utf16!("fromCharCode"), utf16!("fromCharCode"),
utf16!("fromCodePoint"), utf16!("fromCodePoint"),
utf16!("includes"),
utf16!("indexOf"),
utf16!("lastIndexOf"), utf16!("lastIndexOf"),
utf16!("match"), utf16!("match"),
utf16!("matchAll"), utf16!("matchAll"),
@ -129,7 +256,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("replace"), utf16!("replace"),
utf16!("replaceAll"), utf16!("replaceAll"),
utf16!("search"), utf16!("search"),
utf16!("slice"),
utf16!("split"), utf16!("split"),
utf16!("startsWith"), utf16!("startsWith"),
utf16!("substr"), utf16!("substr"),
@ -148,7 +274,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("parseFloat"), utf16!("parseFloat"),
utf16!("isFinite"), utf16!("isFinite"),
utf16!("isNaN"), utf16!("isNaN"),
utf16!("parseInt"),
utf16!("EPSILON"), utf16!("EPSILON"),
utf16!("MAX_SAFE_INTEGER"), utf16!("MAX_SAFE_INTEGER"),
utf16!("MIN_SAFE_INTEGER"), utf16!("MIN_SAFE_INTEGER"),
@ -196,19 +321,8 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("asyncIterator"), utf16!("asyncIterator"),
utf16!("hasInstance"), utf16!("hasInstance"),
utf16!("species"), utf16!("species"),
utf16!("Symbol.species"),
utf16!("unscopables"), utf16!("unscopables"),
utf16!("iterator"), utf16!("iterator"),
utf16!("Symbol.iterator"),
utf16!("Symbol.match"),
utf16!("[Symbol.match]"),
utf16!("Symbol.matchAll"),
utf16!("Symbol.replace"),
utf16!("[Symbol.replace]"),
utf16!("Symbol.search"),
utf16!("[Symbol.search]"),
utf16!("Symbol.split"),
utf16!("[Symbol.split]"),
utf16!("toStringTag"), utf16!("toStringTag"),
utf16!("toPrimitive"), utf16!("toPrimitive"),
utf16!("get description"), utf16!("get description"),
@ -455,46 +569,31 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("Z"), utf16!("Z"),
utf16!("_"), utf16!("_"),
utf16!("$"), utf16!("$"),
// Well known symbols
utf16!("Symbol.asyncIterator"),
utf16!("[Symbol.asyncIterator]"),
utf16!("Symbol.hasInstance"),
utf16!("[Symbol.hasInstance]"),
utf16!("Symbol.isConcatSpreadable"),
utf16!("[Symbol.isConcatSpreadable]"),
utf16!("Symbol.iterator"),
utf16!("[Symbol.iterator]"),
utf16!("Symbol.match"),
utf16!("[Symbol.match]"),
utf16!("Symbol.matchAll"),
utf16!("[Symbol.matchAll]"),
utf16!("Symbol.replace"),
utf16!("[Symbol.replace]"),
utf16!("Symbol.search"),
utf16!("[Symbol.search]"),
utf16!("Symbol.species"),
utf16!("[Symbol.species]"),
utf16!("Symbol.split"),
utf16!("[Symbol.split]"),
utf16!("Symbol.toPrimitive"),
utf16!("[Symbol.toPrimitive]"),
utf16!("Symbol.toStringTag"),
utf16!("[Symbol.toStringTag]"),
utf16!("Symbol.unscopables"),
utf16!("[Symbol.unscopables]"),
]; ];
/// The maximum length of a string within [`COMMON_STRINGS`].
///
/// This is useful to skip checks for strings with lengths > `MAX_COMMON_STRING_LENGTH` and directly
/// allocate on the heap.
pub(super) const MAX_COMMON_STRING_LENGTH: usize = {
let mut max = 0;
let mut i = 0;
while i < COMMON_STRINGS.len() {
let len = COMMON_STRINGS[i].len();
if len > max {
max = len;
}
i += 1;
}
max
};
thread_local! {
/// Map from a string inside [`COMMON_STRINGS`] to its corresponding static [`JsString`].
pub(super) static COMMON_STRINGS_CACHE: FxHashMap<&'static [u16], JsString> = {
let mut constants = FxHashMap::with_capacity_and_hasher(
COMMON_STRINGS.len(),
BuildHasherDefault::<FxHasher>::default(),
);
for (idx, &s) in COMMON_STRINGS.iter().enumerate() {
// Safety:
// As we're just building a cache of `JsString` indices to access the stored
// `COMMON_STRINGS`, this cannot generate invalid `TaggedJsString`s, since `idx` is
// always a valid index in `COMMON_STRINGS`.
let v = unsafe {
JsString {
ptr: TaggedJsString::new_static(idx),
}
};
constants.insert(s, v);
}
constants
};
}

261
boa_engine/src/string/mod.rs

@ -21,9 +21,13 @@
// the same names from the unstable functions of the `std::ptr` module. // the same names from the unstable functions of the `std::ptr` module.
#![allow(unstable_name_collisions)] #![allow(unstable_name_collisions)]
mod common; pub(crate) mod common;
use crate::{builtins::string::is_trimmable_whitespace, JsBigInt}; use crate::{
builtins::string::is_trimmable_whitespace,
tagged::{Tagged, UnwrappedTagged},
JsBigInt,
};
use boa_gc::{empty_trace, Finalize, Trace}; use boa_gc::{empty_trace, Finalize, Trace};
pub use boa_macros::utf16; pub use boa_macros::utf16;
@ -34,12 +38,13 @@ use std::{
convert::Infallible, convert::Infallible,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
ops::{Deref, Index}, ops::{Deref, Index},
ptr::{self, NonNull}, process::abort,
ptr::{self, addr_of, addr_of_mut, NonNull},
slice::SliceIndex, slice::SliceIndex,
str::FromStr, str::FromStr,
}; };
use self::common::{COMMON_STRINGS, COMMON_STRINGS_CACHE, MAX_COMMON_STRING_LENGTH}; use self::common::StaticJsStrings;
fn alloc_overflow() -> ! { fn alloc_overflow() -> ! {
panic!("detected overflow during string allocation") panic!("detected overflow during string allocation")
@ -185,98 +190,11 @@ struct RawJsString {
const DATA_OFFSET: usize = std::mem::size_of::<RawJsString>(); const DATA_OFFSET: usize = std::mem::size_of::<RawJsString>();
/// This struct uses a technique called tagged pointer to benefit from the fact that newly allocated
/// pointers are always word aligned on 64-bits platforms, making it impossible to have a LSB equal
/// to 1. More details about this technique on the article of Wikipedia about [tagged pointers][tagged_wp].
///
/// # Representation
///
/// If the LSB of the internal [`NonNull<RawJsString>`] is set (1), then the pointer address represents
/// an index value for [`COMMON_STRINGS`], where the remaining MSBs store the index.
/// Otherwise, the whole pointer represents the address of a heap allocated [`RawJsString`].
///
/// It uses [`NonNull`], which guarantees that [`TaggedJsString`] (and subsequently [`JsString`]) can
/// use the "null pointer optimization" to optimize the size of [`Option<TaggedJsString>`].
///
/// # Provenance
///
/// This struct stores a [`NonNull<RawJsString>`] instead of a [`NonZeroUsize`][std::num::NonZeroUsize]
/// in order to preserve the provenance of our valid heap pointers.
/// On the other hand, all index values are just casted to invalid pointers, because we don't need to
/// preserve the provenance of [`usize`] indices.
///
/// [tagged_wp]: https://en.wikipedia.org/wiki/Tagged_pointer
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct TaggedJsString(NonNull<RawJsString>);
impl TaggedJsString {
/// Creates a new [`TaggedJsString`] from a pointer to a valid [`RawJsString`].
///
/// # Safety
///
/// `inner` must point to a valid instance of [`RawJsString`], which should be deallocated only
/// by [`JsString`].
const unsafe fn new_heap(inner: NonNull<RawJsString>) -> Self {
Self(inner)
}
/// Creates a new static [`TaggedJsString`] from the index of an element inside
/// [`COMMON_STRINGS`].
///
/// # Safety
///
/// `idx` must be a valid index on [`COMMON_STRINGS`].
const unsafe fn new_static(idx: usize) -> Self {
// SAFETY:
// The operation `(idx << 1) | 1` sets the least significant bit to 1, meaning any pointer
// (valid or invalid) created using this address cannot be null.
unsafe { Self(NonNull::new_unchecked(sptr::invalid_mut((idx << 1) | 1))) }
}
/// Checks if [`TaggedJsString`] contains an index for [`COMMON_STRINGS`].
fn is_static(self) -> bool {
(self.0.as_ptr() as usize) & 1 == 1
}
/// Returns a reference to a string stored on the heap, without checking if its internal pointer
/// is valid.
///
/// # Safety
///
/// `self` must be a heap allocated [`RawJsString`].
const unsafe fn get_heap_unchecked(self) -> NonNull<RawJsString> {
self.0
}
/// Returns the string inside [`COMMON_STRINGS`] corresponding to the index inside
/// [`TaggedJsString`], without checking its validity.
///
/// # Safety
///
/// `self` must not be a pointer to a heap allocated [`RawJsString`], and it must be a valid
/// index inside [`COMMON_STRINGS`].
unsafe fn get_static_unchecked(self) -> &'static [u16] {
// SAFETY:
// The caller must ensure `self` is a valid index inside `COMMON_STRINGS`.
unsafe { COMMON_STRINGS.get_unchecked((self.0.as_ptr() as usize) >> 1) }
}
}
/// Enum representing either a pointer to a heap allocated [`RawJsString`] or a static reference to
/// a [`\[u16\]`][slice] inside [`COMMON_STRINGS`].
enum JsStringPtrKind {
// A string allocated on the heap.
Heap(NonNull<RawJsString>),
// A static string slice.
Static(&'static [u16]),
}
/// A UTF-16–encoded, reference counted, immutable string. /// A UTF-16–encoded, reference counted, immutable string.
/// ///
/// This is pretty similar to a <code>[Rc][std::rc::Rc]\<[\[u16\]][slice]\></code>, but without /// This is pretty similar to a <code>[Rc][std::rc::Rc]\<[\[u16\]][slice]\></code>, but without the
/// the length metadata associated with the `Rc` fat pointer. Instead, the length of /// length metadata associated with the `Rc` fat pointer. Instead, the length of every string is
/// every string is stored on the heap, along with its reference counter and its data. /// stored on the heap, along with its reference counter and its data.
/// ///
/// We define some commonly used string constants in an interner. For these strings, we don't allocate /// We define some commonly used string constants in an interner. For these strings, we don't allocate
/// memory on the heap to reduce the overhead of memory allocation and reference counting. /// memory on the heap to reduce the overhead of memory allocation and reference counting.
@ -287,7 +205,7 @@ enum JsStringPtrKind {
/// <code>\[u16\]</code>'s methods. /// <code>\[u16\]</code>'s methods.
#[derive(Finalize)] #[derive(Finalize)]
pub struct JsString { pub struct JsString {
ptr: TaggedJsString, ptr: Tagged<RawJsString>,
} }
// JsString should always be pointer sized. // JsString should always be pointer sized.
@ -327,7 +245,7 @@ impl JsString {
let string = { let string = {
// SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer. // SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer.
let mut data = unsafe { ptr.as_ptr().cast::<u8>().add(DATA_OFFSET).cast() }; let mut data = unsafe { addr_of_mut!((*ptr.as_ptr()).data).cast() };
for string in strings { for string in strings {
let count = string.len(); let count = string.len();
// SAFETY: // SAFETY:
@ -347,17 +265,11 @@ impl JsString {
} }
Self { Self {
// Safety: We already know it's a valid heap pointer. // Safety: We already know it's a valid heap pointer.
ptr: unsafe { TaggedJsString::new_heap(ptr) }, ptr: unsafe { Tagged::from_ptr(ptr.as_ptr()) },
} }
}; };
if string.len() <= MAX_COMMON_STRING_LENGTH { StaticJsStrings::get_string(&string[..]).unwrap_or(string)
if let Some(constant) = COMMON_STRINGS_CACHE.with(|c| c.get(&string[..]).cloned()) {
return constant;
}
}
string
} }
/// Decodes a [`JsString`] into a [`String`], replacing invalid data with its escaped representation /// Decodes a [`JsString`] into a [`String`], replacing invalid data with its escaped representation
@ -541,19 +453,6 @@ impl JsString {
JsBigInt::from_string(self.to_std_string().ok().as_ref()?) JsBigInt::from_string(self.to_std_string().ok().as_ref()?)
} }
/// Returns the inner pointer data, unwrapping its tagged data if the pointer contains a static
/// index for [`COMMON_STRINGS`].
fn ptr(&self) -> JsStringPtrKind {
// Check the first bit to 1.
if self.ptr.is_static() {
// Safety: We already checked.
JsStringPtrKind::Static(unsafe { self.ptr.get_static_unchecked() })
} else {
// Safety: We already checked.
JsStringPtrKind::Heap(unsafe { self.ptr.get_heap_unchecked() })
}
}
/// Allocates a new [`RawJsString`] with an internal capacity of `str_len` chars. /// Allocates a new [`RawJsString`] with an internal capacity of `str_len` chars.
/// ///
/// # Panics /// # Panics
@ -616,8 +515,10 @@ impl JsString {
// `[u16; str_len]`, the memory of the array must be in the `usize` // `[u16; str_len]`, the memory of the array must be in the `usize`
// range for the allocation to succeed. // range for the allocation to succeed.
unsafe { unsafe {
let data = (*inner).data.as_ptr(); ptr::eq(
ptr::eq(inner.cast::<u8>().add(offset).cast(), data) inner.cast::<u8>().add(offset).cast(),
(*inner).data.as_mut_ptr(),
)
} }
}); });
@ -630,7 +531,7 @@ impl JsString {
let ptr = Self::allocate_inner(count); let ptr = Self::allocate_inner(count);
// SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer. // SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer.
let data = unsafe { ptr.as_ptr().cast::<u8>().add(DATA_OFFSET) }; let data = unsafe { addr_of_mut!((*ptr.as_ptr()).data) };
// SAFETY: // SAFETY:
// - We read `count = data.len()` elements from `data`, which is within the bounds of the slice. // - We read `count = data.len()` elements from `data`, which is within the bounds of the slice.
// - `allocate_inner` must allocate at least `count` elements, which allows us to safely // - `allocate_inner` must allocate at least `count` elements, which allows us to safely
@ -643,8 +544,8 @@ impl JsString {
ptr::copy_nonoverlapping(string.as_ptr(), data.cast(), count); ptr::copy_nonoverlapping(string.as_ptr(), data.cast(), count);
} }
Self { Self {
// Safety: We already know it's a valid heap pointer. // Safety: `allocate_inner` guarantees `ptr` is a valid heap pointer.
ptr: unsafe { TaggedJsString::new_heap(ptr) }, ptr: Tagged::from_non_null(ptr),
} }
} }
} }
@ -664,10 +565,14 @@ impl Borrow<[u16]> for JsString {
impl Clone for JsString { impl Clone for JsString {
#[inline] #[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
if let JsStringPtrKind::Heap(inner) = self.ptr() { if let UnwrappedTagged::Ptr(inner) = self.ptr.unwrap() {
// SAFETY: The reference count of `JsString` guarantees that `raw` is always valid. // SAFETY: The reference count of `JsString` guarantees that `raw` is always valid.
let inner = unsafe { &mut *inner.as_ptr() }; let inner = unsafe { inner.as_ref() };
inner.refcount.set(inner.refcount.get() + 1); let strong = inner.refcount.get().wrapping_add(1);
if strong == 0 {
abort()
}
inner.refcount.set(strong);
} }
Self { ptr: self.ptr } Self { ptr: self.ptr }
} }
@ -676,42 +581,37 @@ impl Clone for JsString {
impl Default for JsString { impl Default for JsString {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
sa::const_assert!(!COMMON_STRINGS.is_empty()); StaticJsStrings::empty_string()
// Safety:
// `COMMON_STRINGS` must not be empty for this to be safe.
// The static assertion above verifies this.
unsafe {
Self {
ptr: TaggedJsString::new_static(0),
}
}
} }
} }
impl Drop for JsString { impl Drop for JsString {
fn drop(&mut self) { fn drop(&mut self) {
if let JsStringPtrKind::Heap(raw) = self.ptr() { if let UnwrappedTagged::Ptr(raw) = self.ptr.unwrap() {
// See https://doc.rust-lang.org/src/alloc/sync.rs.html#1672 for details.
// SAFETY: The reference count of `JsString` guarantees that `raw` is always valid. // SAFETY: The reference count of `JsString` guarantees that `raw` is always valid.
let inner = unsafe { &mut *raw.as_ptr() }; let inner = unsafe { raw.as_ref() };
inner.refcount.set(inner.refcount.get() - 1); inner.refcount.set(inner.refcount.get() - 1);
if inner.refcount.get() == 0 { if inner.refcount.get() != 0 {
// SAFETY: return;
// All the checks for the validity of the layout have already been made on `alloc_inner`, }
// so we can skip the unwrap.
let layout = unsafe { // SAFETY:
Layout::for_value(inner) // All the checks for the validity of the layout have already been made on `alloc_inner`,
.extend(Layout::array::<u16>(inner.len).unwrap_unchecked()) // so we can skip the unwrap.
.unwrap_unchecked() let layout = unsafe {
.0 Layout::for_value(inner)
.pad_to_align() .extend(Layout::array::<u16>(inner.len).unwrap_unchecked())
}; .unwrap_unchecked()
.0
// Safety: .pad_to_align()
// If refcount is 0 and we call drop, that means this is the last `JsString` which };
// points to this memory allocation, so deallocating it is safe. // Safety:
unsafe { // If refcount is 0 and we call drop, that means this is the last `JsString` which
dealloc(raw.as_ptr().cast(), layout); // points to this memory allocation, so deallocating it is safe.
} unsafe {
dealloc(raw.as_ptr().cast(), layout);
} }
} }
} }
@ -735,8 +635,8 @@ impl Deref for JsString {
type Target = [u16]; type Target = [u16];
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match self.ptr() { match self.ptr.unwrap() {
JsStringPtrKind::Heap(h) => { UnwrappedTagged::Ptr(h) => {
// SAFETY: // SAFETY:
// - The `RawJsString` type has all the necessary information to reconstruct a valid // - The `RawJsString` type has all the necessary information to reconstruct a valid
// slice (length and starting pointer). // slice (length and starting pointer).
@ -747,13 +647,15 @@ impl Deref for JsString {
// - The lifetime of `&Self::Target` is shorter than the lifetime of `self`, as seen // - The lifetime of `&Self::Target` is shorter than the lifetime of `self`, as seen
// by its signature, so this doesn't outlive `self`. // by its signature, so this doesn't outlive `self`.
unsafe { unsafe {
std::slice::from_raw_parts( let h = h.as_ptr();
h.as_ptr().cast::<u8>().add(DATA_OFFSET).cast(), std::slice::from_raw_parts(addr_of!((*h).data).cast(), (*h).len)
h.as_ref().len,
)
} }
} }
JsStringPtrKind::Static(s) => s, UnwrappedTagged::Tag(index) => {
// SAFETY: all static strings are valid indices on `STATIC_JS_STRINGS`, so `get` should always
// return `Some`.
unsafe { StaticJsStrings::get(index).unwrap_unchecked() }
}
} }
} }
} }
@ -762,12 +664,7 @@ impl Eq for JsString {}
impl From<&[u16]> for JsString { impl From<&[u16]> for JsString {
fn from(s: &[u16]) -> Self { fn from(s: &[u16]) -> Self {
if s.len() <= MAX_COMMON_STRING_LENGTH { StaticJsStrings::get_string(s).unwrap_or_else(|| Self::from_slice_skip_interning(s))
if let Some(constant) = COMMON_STRINGS_CACHE.with(|c| c.get(s).cloned()) {
return constant;
}
}
Self::from_slice_skip_interning(s)
} }
} }
@ -822,10 +719,6 @@ impl Ord for JsString {
impl PartialEq for JsString { impl PartialEq for JsString {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if self.ptr == other.ptr {
return true;
}
self[..] == other[..] self[..] == other[..]
} }
} }
@ -950,19 +843,21 @@ impl ToStringEscaped for [u16] {
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tagged::UnwrappedTagged;
use super::utf16; use super::utf16;
use super::{JsString, JsStringPtrKind}; use super::JsString;
impl JsString { impl JsString {
/// Gets the number of `JsString`s which point to this allocation. /// Gets the number of `JsString`s which point to this allocation.
fn refcount(&self) -> Option<usize> { fn refcount(&self) -> Option<usize> {
match self.ptr() { match self.ptr.unwrap() {
JsStringPtrKind::Heap(inner) => { UnwrappedTagged::Ptr(inner) => {
// SAFETY: The reference count of `JsString` guarantees that `inner` is always valid. // SAFETY: The reference count of `JsString` guarantees that `inner` is always valid.
let inner = unsafe { inner.as_ref() }; let inner = unsafe { inner.as_ref() };
Some(inner.refcount.get()) Some(inner.refcount.get())
} }
JsStringPtrKind::Static(_inner) => None, UnwrappedTagged::Tag(_inner) => None,
} }
} }
} }
@ -1016,13 +911,13 @@ mod tests {
let x = js_string!("Hello"); let x = js_string!("Hello");
let y = x.clone(); let y = x.clone();
assert!(!x.ptr.is_static()); assert!(!x.ptr.is_tagged());
assert_eq!(x.ptr, y.ptr); assert_eq!(x.ptr.addr(), y.ptr.addr());
let z = js_string!("Hello"); let z = js_string!("Hello");
assert_ne!(x.ptr, z.ptr); assert_ne!(x.ptr.addr(), z.ptr.addr());
assert_ne!(y.ptr, z.ptr); assert_ne!(y.ptr.addr(), z.ptr.addr());
} }
#[test] #[test]
@ -1030,13 +925,13 @@ mod tests {
let x = js_string!(); let x = js_string!();
let y = x.clone(); let y = x.clone();
assert!(x.ptr.is_static()); assert!(x.ptr.is_tagged());
assert_eq!(x.ptr, y.ptr); assert_eq!(x.ptr.addr(), y.ptr.addr());
let z = js_string!(); let z = js_string!();
assert_eq!(x.ptr, z.ptr); assert_eq!(x.ptr.addr(), z.ptr.addr());
assert_eq!(y.ptr, z.ptr); assert_eq!(y.ptr.addr(), z.ptr.addr());
} }
#[test] #[test]

443
boa_engine/src/symbol.rs

@ -15,240 +15,97 @@
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value //! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
use crate::{js_string, string::utf16, JsString}; #![deny(
unsafe_op_in_unsafe_fn,
clippy::undocumented_unsafe_blocks,
clippy::missing_safety_doc
)]
use crate::{
js_string,
string::{common::StaticJsStrings, utf16},
tagged::{Tagged, UnwrappedTagged},
JsString,
};
use boa_gc::{empty_trace, Finalize, Trace}; use boa_gc::{empty_trace, Finalize, Trace};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{ use std::{
cell::Cell,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
rc::Rc, sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
}; };
/// A structure that contains the JavaScript well known symbols.
///
/// # Examples
/// ```
/// # use boa_engine::symbol::WellKnownSymbols;
///
/// let iterator = WellKnownSymbols::iterator();
/// assert_eq!(iterator.description().unwrap().to_std_string_escaped(), "Symbol.iterator");
/// ```
/// This is equivalent to `let iterator = Symbol.iterator` in JavaScript.
#[derive(Debug, Clone)]
pub struct WellKnownSymbols {
async_iterator: JsSymbol,
has_instance: JsSymbol,
is_concat_spreadable: JsSymbol,
iterator: JsSymbol,
r#match: JsSymbol,
match_all: JsSymbol,
replace: JsSymbol,
search: JsSymbol,
species: JsSymbol,
split: JsSymbol,
to_primitive: JsSymbol,
to_string_tag: JsSymbol,
unscopables: JsSymbol,
}
/// Reserved number of symbols. /// Reserved number of symbols.
/// ///
/// This is where the well known symbol live /// This is where the well known symbol live
/// and internal engine symbols. /// and internal engine symbols.
const RESERVED_SYMBOL_HASHES: u64 = 128; const RESERVED_SYMBOL_HASHES: u64 = 127;
thread_local! { fn get_id() -> Option<u64> {
/// Cached well known symbols // Symbol hash.
static WELL_KNOW_SYMBOLS: WellKnownSymbols = WellKnownSymbols::new(); //
// For now this is an incremented u64 number.
static SYMBOL_HASH_COUNT: AtomicU64 = AtomicU64::new(RESERVED_SYMBOL_HASHES + 1);
SYMBOL_HASH_COUNT
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_add(1)
})
.ok()
}
/// Symbol hash. /// List of well known symbols.
/// #[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
/// For now this is an incremented u64 number. #[repr(u8)]
static SYMBOL_HASH_COUNT: Cell<u64> = Cell::new(RESERVED_SYMBOL_HASHES); enum WellKnown {
AsyncIterator,
HasInstance,
IsConcatSpreadable,
Iterator,
Match,
MatchAll,
Replace,
Search,
Species,
Split,
ToPrimitive,
ToStringTag,
Unscopables,
} }
impl WellKnownSymbols { impl WellKnown {
/// Create the well known symbols. const fn description(self) -> JsString {
fn new() -> Self { match self {
let mut count = 0; WellKnown::AsyncIterator => StaticJsStrings::symbol_async_iterator(),
WellKnown::HasInstance => StaticJsStrings::symbol_has_instance(),
let async_iterator = JsSymbol::with_hash(count, Some("Symbol.asyncIterator".into())); WellKnown::IsConcatSpreadable => StaticJsStrings::symbol_is_concat_spreadable(),
count += 1; WellKnown::Iterator => StaticJsStrings::symbol_iterator(),
let has_instance = JsSymbol::with_hash(count, Some("Symbol.hasInstance".into())); WellKnown::Match => StaticJsStrings::symbol_match(),
count += 1; WellKnown::MatchAll => StaticJsStrings::symbol_match_all(),
let is_concat_spreadable = WellKnown::Replace => StaticJsStrings::symbol_replace(),
JsSymbol::with_hash(count, Some("Symbol.isConcatSpreadable".into())); WellKnown::Search => StaticJsStrings::symbol_search(),
count += 1; WellKnown::Species => StaticJsStrings::symbol_species(),
let iterator = JsSymbol::with_hash(count, Some("Symbol.iterator".into())); WellKnown::Split => StaticJsStrings::symbol_split(),
count += 1; WellKnown::ToPrimitive => StaticJsStrings::symbol_to_primitive(),
let match_ = JsSymbol::with_hash(count, Some("Symbol.match".into())); WellKnown::ToStringTag => StaticJsStrings::symbol_to_string_tag(),
count += 1; WellKnown::Unscopables => StaticJsStrings::symbol_unscopables(),
let match_all = JsSymbol::with_hash(count, Some("Symbol.matchAll".into()));
count += 1;
let replace = JsSymbol::with_hash(count, Some("Symbol.replace".into()));
count += 1;
let search = JsSymbol::with_hash(count, Some("Symbol.search".into()));
count += 1;
let species = JsSymbol::with_hash(count, Some("Symbol.species".into()));
count += 1;
let split = JsSymbol::with_hash(count, Some("Symbol.split".into()));
count += 1;
let to_primitive = JsSymbol::with_hash(count, Some("Symbol.toPrimitive".into()));
count += 1;
let to_string_tag = JsSymbol::with_hash(count, Some("Symbol.toStringTag".into()));
count += 1;
let unscopables = JsSymbol::with_hash(count, Some("Symbol.unscopables".into()));
Self {
async_iterator,
has_instance,
is_concat_spreadable,
iterator,
r#match: match_,
match_all,
replace,
search,
species,
split,
to_primitive,
to_string_tag,
unscopables,
} }
} }
/// The `Symbol.asyncIterator` well known symbol. const fn hash(self) -> u64 {
/// self as u64
/// A method that returns the default `AsyncIterator` for an object.
/// Called by the semantics of the `for-await-of` statement.
#[inline]
#[must_use]
pub fn async_iterator() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.async_iterator.clone())
}
/// The `Symbol.hasInstance` well known symbol.
///
/// A method that determines if a `constructor` object
/// recognizes an object as one of the `constructor`'s instances.
/// Called by the semantics of the instanceof operator.
#[inline]
#[must_use]
pub fn has_instance() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.has_instance.clone())
}
/// The `Symbol.isConcatSpreadable` well known symbol.
///
/// A Boolean valued property that if `true` indicates that
/// an object should be flattened to its array elements
/// by `Array.prototype.concat`.
#[inline]
#[must_use]
pub fn is_concat_spreadable() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.is_concat_spreadable.clone())
}
/// The `Symbol.iterator` well known symbol.
///
/// A method that returns the default Iterator for an object.
/// Called by the semantics of the `for-of` statement.
#[inline]
#[must_use]
pub fn iterator() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.iterator.clone())
}
/// The `Symbol.match` well known symbol.
///
/// A regular expression method that matches the regular expression
/// against a string. Called by the `String.prototype.match` method.
#[inline]
#[must_use]
pub fn r#match() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.r#match.clone())
}
/// The `Symbol.matchAll` well known symbol.
///
/// A regular expression method that returns an iterator, that yields
/// matches of the regular expression against a string.
/// Called by the `String.prototype.matchAll` method.
#[inline]
#[must_use]
pub fn match_all() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_all.clone())
}
/// The `Symbol.replace` well known symbol.
///
/// A regular expression method that replaces matched substrings
/// of a string. Called by the `String.prototype.replace` method.
#[inline]
#[must_use]
pub fn replace() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.replace.clone())
}
/// The `Symbol.search` well known symbol.
///
/// A regular expression method that returns the index within a
/// string that matches the regular expression.
/// Called by the `String.prototype.search` method.
#[inline]
#[must_use]
pub fn search() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.search.clone())
}
/// The `Symbol.species` well known symbol.
///
/// A function valued property that is the `constructor` function
/// that is used to create derived objects.
#[inline]
#[must_use]
pub fn species() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.species.clone())
}
/// The `Symbol.split` well known symbol.
///
/// A regular expression method that splits a string at the indices
/// that match the regular expression.
/// Called by the `String.prototype.split` method.
#[inline]
#[must_use]
pub fn split() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.split.clone())
}
/// The `Symbol.toPrimitive` well known symbol.
///
/// A method that converts an object to a corresponding primitive value.
/// Called by the `ToPrimitive` (`Value::to_primitive`) abstract operation.
#[inline]
#[must_use]
pub fn to_primitive() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_primitive.clone())
} }
/// The `Symbol.toStringTag` well known symbol. const fn tag(self) -> usize {
/// self as usize
/// A String valued property that is used in the creation of the default
/// string description of an object.
/// Accessed by the built-in method `Object.prototype.toString`.
#[inline]
#[must_use]
pub fn to_string_tag() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_string_tag.clone())
} }
/// The `Symbol.unscopables` well known symbol. fn from_tag(tag: usize) -> Option<Self> {
/// Self::try_from_primitive(u8::try_from(tag).ok()?).ok()
/// An object valued property whose own and inherited property names are property
/// names that are excluded from the `with` environment bindings of the associated object.
#[inline]
#[must_use]
pub fn unscopables() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.unscopables.clone())
} }
} }
@ -260,46 +117,68 @@ struct Inner {
} }
/// This represents a JavaScript symbol primitive. /// This represents a JavaScript symbol primitive.
#[derive(Debug, Clone, Finalize)]
pub struct JsSymbol { pub struct JsSymbol {
inner: Rc<Inner>, repr: Tagged<Inner>,
} }
// SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe.
unsafe impl Send for JsSymbol {}
// SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe.
unsafe impl Sync for JsSymbol {}
impl Finalize for JsSymbol {}
// Safety: JsSymbol does not contain any objects which needs to be traced, // Safety: JsSymbol does not contain any objects which needs to be traced,
// so this is safe. // so this is safe.
unsafe impl Trace for JsSymbol { unsafe impl Trace for JsSymbol {
empty_trace!(); empty_trace!();
} }
macro_rules! well_known_symbols {
( $( $(#[$attr:meta])* ($name:ident, $variant:path) ),+$(,)? ) => {
$(
$(#[$attr])* pub(crate) const fn $name() -> JsSymbol {
JsSymbol {
repr: Tagged::from_tag($variant.tag()),
}
}
)+
};
}
impl JsSymbol { impl JsSymbol {
/// Create a new symbol. /// Creates a new symbol.
///
/// Returns `None` if the maximum number of possible symbols has been reached (`u64::MAX`).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new(description: Option<JsString>) -> Self { pub fn new(description: Option<JsString>) -> Option<Self> {
let hash = SYMBOL_HASH_COUNT.with(|count| { let hash = get_id()?;
let hash = count.get(); let arc = Arc::new(Inner { hash, description });
count.set(hash + 1);
hash Some(Self {
}); // SAFETY: Pointers returned by `Arc::into_raw` must be non-null.
repr: unsafe { Tagged::from_ptr(Arc::into_raw(arc).cast_mut()) },
Self { })
inner: Rc::new(Inner { hash, description }),
}
}
/// Create a new symbol with a specified hash and description.
#[inline]
fn with_hash(hash: u64, description: Option<JsString>) -> Self {
Self {
inner: Rc::new(Inner { hash, description }),
}
} }
/// Returns the `Symbol`s description. /// Returns the `Symbol`s description.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn description(&self) -> Option<JsString> { pub fn description(&self) -> Option<JsString> {
self.inner.description.clone() match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
// SAFETY: `ptr` comes from `Arc`, which ensures the validity of the pointer
// as long as we corrently call `Arc::from_raw` on `Drop`.
unsafe { ptr.as_ref().description.clone() }
}
UnwrappedTagged::Tag(tag) => {
// SAFETY: All tagged reprs always come from `WellKnown` itself, making
// this operation always safe.
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
Some(wk.description())
}
}
} }
/// Returns the `Symbol`s hash. /// Returns the `Symbol`s hash.
@ -308,7 +187,18 @@ impl JsSymbol {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn hash(&self) -> u64 { pub fn hash(&self) -> u64 {
self.inner.hash match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
// SAFETY: `ptr` comes from `Arc`, which ensures the validity of the pointer
// as long as we correctly call `Arc::from_raw` on `Drop`.
unsafe { ptr.as_ref().hash }
}
UnwrappedTagged::Tag(tag) => {
// SAFETY: All tagged reprs always come from `WellKnown` itself, making
// this operation always safe.
unsafe { WellKnown::from_tag(tag).unwrap_unchecked().hash() }
}
}
} }
/// Abstract operation `SymbolDescriptiveString ( sym )` /// Abstract operation `SymbolDescriptiveString ( sym )`
@ -319,17 +209,82 @@ impl JsSymbol {
/// [spec]: https://tc39.es/ecma262/#sec-symboldescriptivestring /// [spec]: https://tc39.es/ecma262/#sec-symboldescriptivestring
#[must_use] #[must_use]
pub fn descriptive_string(&self) -> JsString { pub fn descriptive_string(&self) -> JsString {
self.inner.description.as_ref().map_or_else( self.description().as_ref().map_or_else(
|| js_string!("Symbol()"), || js_string!("Symbol()"),
|desc| js_string!(utf16!("Symbol("), desc, utf16!(")")), |desc| js_string!(utf16!("Symbol("), desc, utf16!(")")),
) )
} }
well_known_symbols! {
/// Gets the static `JsSymbol` for `"Symbol.asyncIterator"`.
(async_iterator, WellKnown::AsyncIterator),
/// Gets the static `JsSymbol` for `"Symbol.hasInstance"`.
(has_instance, WellKnown::HasInstance),
/// Gets the static `JsSymbol` for `"Symbol.isConcatSpreadable"`.
(is_concat_spreadable, WellKnown::IsConcatSpreadable),
/// Gets the static `JsSymbol` for `"Symbol.iterator"`.
(iterator, WellKnown::Iterator),
/// Gets the static `JsSymbol` for `"Symbol.match"`.
(r#match, WellKnown::Match),
/// Gets the static `JsSymbol` for `"Symbol.matchAll"`.
(match_all, WellKnown::MatchAll),
/// Gets the static `JsSymbol` for `"Symbol.replace"`.
(replace, WellKnown::Replace),
/// Gets the static `JsSymbol` for `"Symbol.search"`.
(search, WellKnown::Search),
/// Gets the static `JsSymbol` for `"Symbol.species"`.
(species, WellKnown::Species),
/// Gets the static `JsSymbol` for `"Symbol.split"`.
(split, WellKnown::Split),
/// Gets the static `JsSymbol` for `"Symbol.toPrimitive"`.
(to_primitive, WellKnown::ToPrimitive),
/// Gets the static `JsSymbol` for `"Symbol.toStringTag"`.
(to_string_tag, WellKnown::ToStringTag),
/// Gets the static `JsSymbol` for `"Symbol.unscopables"`.
(unscopables, WellKnown::Unscopables),
}
}
impl Clone for JsSymbol {
fn clone(&self) -> Self {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
// SAFETY: the pointer returned by `self.repr` must be a valid pointer
// that came from an `Arc::into_raw` call.
unsafe {
let arc = Arc::from_raw(ptr.as_ptr().cast_const());
// Don't need the Arc since `self` is already a copyable pointer, just need to
// trigger the `clone` impl.
std::mem::forget(arc.clone());
std::mem::forget(arc);
}
}
Self { repr: self.repr }
}
}
impl Drop for JsSymbol {
fn drop(&mut self) {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
// SAFETY: the pointer returned by `self.repr` must be a valid pointer
// that came from an `Arc::into_raw` call.
unsafe { drop(Arc::from_raw(ptr.as_ptr().cast_const())) }
}
}
}
impl std::fmt::Debug for JsSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JsSymbol")
.field("hash", &self.hash())
.field("description", &self.description())
.finish()
}
} }
impl std::fmt::Display for JsSymbol { impl std::fmt::Display for JsSymbol {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner.description { match &self.description() {
Some(desc) => write!(f, "Symbol({})", desc.to_std_string_escaped()), Some(desc) => write!(f, "Symbol({})", desc.to_std_string_escaped()),
None => write!(f, "Symbol()"), None => write!(f, "Symbol()"),
} }
@ -341,26 +296,26 @@ impl Eq for JsSymbol {}
impl PartialEq for JsSymbol { impl PartialEq for JsSymbol {
#[inline] #[inline]
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.inner.hash == other.inner.hash self.hash() == other.hash()
} }
} }
impl PartialOrd for JsSymbol { impl PartialOrd for JsSymbol {
#[inline] #[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.inner.hash.partial_cmp(&other.inner.hash) self.hash().partial_cmp(&other.hash())
} }
} }
impl Ord for JsSymbol { impl Ord for JsSymbol {
#[inline] #[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner.hash.cmp(&other.inner.hash) self.hash().cmp(&other.hash())
} }
} }
impl Hash for JsSymbol { impl Hash for JsSymbol {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash.hash(state); self.hash().hash(state);
} }
} }

109
boa_engine/src/tagged.rs

@ -0,0 +1,109 @@
// Remove when/if https://github.com/rust-lang/rust/issues/95228 stabilizes.
// Right now this allows us to use the stable polyfill from the `sptr` crate, which uses
// the same names from the unstable functions of the `std::ptr` module.
#![allow(unstable_name_collisions)]
use sptr::Strict;
use std::ptr::NonNull;
/// A pointer that can be tagged with an `usize`.
///
/// Only pointers with a minimum alignment of 2-bytes are valid, and the tag must have its most
/// significant bit (MSB) unset. In other words, the tag must fit inside `usize::BITS - 1` bits.
///
/// # Representation
///
/// If the least significant bit (LSB) of the internal [`NonNull`] is set (1), then the pointer
/// address represents a tag where the remaining bits store the tag. Otherwise, the whole pointer
/// represents the pointer itself.
///
/// It uses [`NonNull`], which guarantees that [`Tagged`] can use the "null pointer optimization"
/// to optimize the size of [`Option<Tagged>`].
///
/// # Provenance
///
/// This struct stores a [`NonNull<T>`] instead of a [`NonZeroUsize`][std::num::NonZeroUsize]
/// in order to preserve the provenance of our valid heap pointers.
/// On the other hand, all index values are just casted to invalid pointers, because we don't need to
/// preserve the provenance of [`usize`] indices.
///
/// [tagged_wp]: https://en.wikipedia.org/wiki/Tagged_pointer
#[derive(Debug)]
pub(crate) struct Tagged<T>(NonNull<T>);
impl<T> Clone for Tagged<T> {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<T> Copy for Tagged<T> {}
impl<T> Tagged<T> {
/// Creates a new, tagged `Tagged` pointer from an integer.
///
/// # Requirements
///
/// - `T` must have an alignment of at least 2.
/// - `tag` must fit inside `usize::BITS - 1` bits
pub(crate) const fn from_tag(tag: usize) -> Tagged<T> {
debug_assert!(std::mem::align_of::<T>() >= 2);
let addr = (tag << 1) | 1;
// SAFETY: `addr` is never zero, since we always set its LSB to 1
unsafe { Tagged(NonNull::new_unchecked(sptr::invalid_mut(addr))) }
}
/// Creates a new `Tagged` pointer from a raw pointer.
///
/// # Requirements
///
/// - `T` must have an alignment of at least 2.
///
/// # Safety
///
/// - `T` must be non null.
pub(crate) const unsafe fn from_ptr(ptr: *mut T) -> Tagged<T> {
debug_assert!(std::mem::align_of::<T>() >= 2);
// SAFETY: the caller must ensure the invariants hold.
unsafe { Tagged(NonNull::new_unchecked(ptr)) }
}
/// Creates a new `Tagged` pointer from a `NonNull` pointer.
///
/// # Requirements
///
/// - `T` must have an alignment of at least 2.
pub(crate) const fn from_non_null(ptr: NonNull<T>) -> Tagged<T> {
debug_assert!(std::mem::align_of::<T>() >= 2);
Tagged(ptr)
}
/// Unwraps the `Tagged` pointer.
pub(crate) fn unwrap(self) -> UnwrappedTagged<T> {
let addr = self.0.as_ptr().addr();
if addr & 1 == 0 {
UnwrappedTagged::Ptr(self.0)
} else {
UnwrappedTagged::Tag(addr >> 1)
}
}
/// Gets the address of the inner pointer.
#[allow(unused)]
pub(crate) fn addr(self) -> usize {
self.0.as_ptr().addr()
}
/// Returns `true` if `self ` is a tagged pointer.
#[allow(unused)]
pub(crate) fn is_tagged(self) -> bool {
self.0.as_ptr().addr() & 1 > 0
}
}
/// The unwrapped value of a [`Tagged`] pointer.
#[derive(Debug, Clone, Copy)]
pub(crate) enum UnwrappedTagged<T> {
Ptr(NonNull<T>),
Tag(usize),
}

4
boa_engine/src/value/mod.rs

@ -24,7 +24,7 @@ use crate::{
js_string, js_string,
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols}, symbol::JsSymbol,
Context, JsBigInt, JsResult, JsString, Context, JsBigInt, JsResult, JsString,
}; };
use boa_gc::{custom_trace, Finalize, Trace}; use boa_gc::{custom_trace, Finalize, Trace};
@ -360,7 +360,7 @@ impl JsValue {
// 2. If Type(input) is Object, then // 2. If Type(input) is Object, then
if let Some(input) = self.as_object() { if let Some(input) = self.as_object() {
// a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
let exotic_to_prim = input.get_method(WellKnownSymbols::to_primitive(), context)?; let exotic_to_prim = input.get_method(JsSymbol::to_primitive(), context)?;
// b. If exoticToPrim is not undefined, then // b. If exoticToPrim is not undefined, then
if let Some(exotic_to_prim) = exotic_to_prim { if let Some(exotic_to_prim) = exotic_to_prim {

4
boa_engine/src/value/operations.rs

@ -5,7 +5,7 @@ use crate::{
}, },
error::JsNativeError, error::JsNativeError,
js_string, js_string,
value::{Numeric, PreferredType, WellKnownSymbols}, value::{JsSymbol, Numeric, PreferredType},
Context, JsBigInt, JsResult, JsValue, Context, JsBigInt, JsResult, JsValue,
}; };
@ -427,7 +427,7 @@ impl JsValue {
} }
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
match target.get_method(WellKnownSymbols::has_instance(), context)? { match target.get_method(JsSymbol::has_instance(), context)? {
// 3. If instOfHandler is not undefined, then // 3. If instOfHandler is not undefined, then
Some(instance_of_handler) => { Some(instance_of_handler) => {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)). // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).

33
boa_engine/src/vm/opcode/mod.rs

@ -1,6 +1,8 @@
/// The opcodes of the vm. /// The opcodes of the vm.
use crate::{vm::ShouldExit, Context, JsResult}; use crate::{vm::ShouldExit, Context, JsResult};
use num_enum::TryFromPrimitive;
// Operation modules // Operation modules
mod await_stm; mod await_stm;
mod binary_ops; mod binary_ops;
@ -171,7 +173,7 @@ pub(crate) trait Operation {
} }
generate_impl! { generate_impl! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, TryFromPrimitive)]
#[repr(u8)] #[repr(u8)]
pub enum Opcode { pub enum Opcode {
/// Pop the top value from the stack. /// Pop the top value from the stack.
@ -1506,35 +1508,6 @@ generate_impl! {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidOpcodeError {
value: u8,
}
impl std::fmt::Display for InvalidOpcodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid opcode: {:#04x}", self.value)
}
}
impl std::error::Error for InvalidOpcodeError {}
impl TryFrom<u8> for Opcode {
type Error = InvalidOpcodeError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value > Self::Nop as u8 {
return Err(InvalidOpcodeError { value });
}
// Safety: we already checked if it is in the Opcode range,
// so this is safe.
let opcode = unsafe { Self::from_raw(value) };
Ok(opcode)
}
}
/// Specific opcodes for bindings. /// Specific opcodes for bindings.
/// ///
/// This separate enum exists to make matching exhaustive where needed. /// This separate enum exists to make matching exhaustive where needed.

Loading…
Cancel
Save