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",
"chrono",
"criterion",
"dashmap",
"fast-float",
"float-cmp",
"icu_calendar",
@ -215,6 +216,7 @@ dependencies = [
"num-bigint",
"num-integer",
"num-traits",
"num_enum",
"once_cell",
"rand",
"regress",
@ -884,6 +886,19 @@ dependencies = [
"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]]
name = "databake"
version = "0.1.2"
@ -2334,6 +2349,27 @@ dependencies = [
"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]]
name = "num_threads"
version = "0.1.6"
@ -2636,6 +2672,17 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-error"
version = "1.0.4"

2
boa_engine/Cargo.toml

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

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

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError,
object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult,
};
use boa_gc::{Finalize, Trace};
@ -148,7 +148,7 @@ impl ArrayIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("Array Iterator")
.writable(false)

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

@ -32,7 +32,7 @@ use crate::{
FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
},
property::{Attribute, PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::{IntegerOrInfinity, JsValue},
Context, JsResult,
};
@ -48,8 +48,8 @@ impl BuiltIn for Array {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_iterator = WellKnownSymbols::iterator();
let symbol_unscopables = WellKnownSymbols::unscopables();
let symbol_iterator = JsSymbol::iterator();
let symbol_unscopables = JsSymbol::unscopables();
let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
@ -68,7 +68,7 @@ impl BuiltIn for Array {
.name(Self::NAME)
.length(Self::LENGTH)
.static_accessor(
WellKnownSymbols::species(),
JsSymbol::species(),
Some(get_species),
None,
Attribute::CONFIGURABLE,
@ -307,7 +307,7 @@ impl Array {
};
// 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).
if !spreadable.is_undefined() {
@ -366,7 +366,7 @@ impl Array {
// 5. If Type(C) is Object, then
let c = if let Some(c) = c.as_object() {
// 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.
if c.is_null_or_undefined() {
JsValue::undefined()
@ -428,7 +428,7 @@ impl Array {
};
// 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 {
// 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,
},
property::Attribute,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::{IntegerOrInfinity, Numeric},
Context, JsResult, JsValue,
};
@ -76,7 +76,7 @@ impl BuiltIn for ArrayBuffer {
.length(Self::LENGTH)
.accessor("byteLength", Some(get_byte_length), None, flag_attributes)
.static_accessor(
WellKnownSymbols::species(),
JsSymbol::species(),
Some(get_species),
None,
Attribute::CONFIGURABLE,
@ -84,7 +84,7 @@ impl BuiltIn for ArrayBuffer {
.static_method(Self::is_view, "isView", 1)
.method(Self::slice, "slice", 2)
.property(
WellKnownSymbols::to_string_tag(),
JsSymbol::to_string_tag(),
Self::NAME,
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,
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
@ -83,7 +83,7 @@ impl BuiltIn for AsyncFunction {
.configurable(true);
prototype
.borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property);
.insert(JsSymbol::to_string_tag(), property);
None
}

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

@ -14,7 +14,7 @@ use crate::{
native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
vm::GeneratorResumeKind,
Context, JsError, JsResult,
@ -93,7 +93,7 @@ impl BuiltIn for AsyncGenerator {
.name(Self::NAME)
.length(0)
.property(
WellKnownSymbols::to_string_tag(),
JsSymbol::to_string_tag(),
Self::NAME,
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,
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsResult,
};
@ -106,7 +106,7 @@ impl BuiltIn for AsyncGeneratorFunction {
.configurable(true);
prototype
.borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property);
.insert(JsSymbol::to_string_tag(), property);
None
}

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

@ -17,7 +17,7 @@ use crate::{
error::JsNativeError,
object::ConstructorBuilder,
property::Attribute,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::{IntegerOrInfinity, PreferredType},
Context, JsBigInt, JsResult, JsValue,
};
@ -38,7 +38,7 @@ impl BuiltIn for BigInt {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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(
context,

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

@ -17,7 +17,7 @@ use crate::{
FunctionObjectBuilder, JsObject, ObjectData,
},
property::Attribute,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsResult,
};
@ -84,7 +84,7 @@ impl BuiltIn for DataView {
.method(Self::set_uint16, "setUint16", 2)
.method(Self::set_uint32, "setUint32", 2)
.property(
WellKnownSymbols::to_string_tag(),
JsSymbol::to_string_tag(),
Self::NAME,
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,
},
string::utf16,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::{IntegerOrNan, JsValue, PreferredType},
Context, JsError, JsResult,
};
@ -165,7 +165,7 @@ impl BuiltIn for Date {
.method(Self::value_of, "valueOf", 0)
.method(
Self::to_primitive,
(WellKnownSymbols::to_primitive(), "[Symbol.toPrimitive]"),
(JsSymbol::to_primitive(), "[Symbol.toPrimitive]"),
1,
)
.build()

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

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

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

@ -23,7 +23,7 @@ use crate::{
object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement},
property::{Attribute, PropertyDescriptor, PropertyKey},
string::utf16,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
@ -879,7 +879,7 @@ impl BuiltIn for BuiltInFunctionObject {
.constructor(false)
.build_function_prototype(&function_prototype);
let symbol_has_instance = WellKnownSymbols::has_instance();
let symbol_has_instance = JsSymbol::has_instance();
let 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,
object::{ConstructorBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
vm::{CallFrame, GeneratorResumeKind, ReturnType},
Context, JsError, JsResult,
@ -81,7 +81,7 @@ impl BuiltIn for Generator {
.name(Self::NAME)
.length(Self::LENGTH)
.property(
WellKnownSymbols::to_string_tag(),
JsSymbol::to_string_tag(),
Self::NAME,
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,
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsResult,
};
@ -105,7 +105,7 @@ impl BuiltIn for GeneratorFunction {
.configurable(true);
prototype
.borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property);
.insert(JsSymbol::to_string_tag(), property);
None
}

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

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

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

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

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

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

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

@ -10,7 +10,7 @@ use crate::{
},
error::JsNativeError,
object::{JsObject, ObjectInitializer},
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult, JsValue,
};
use async_from_sync_iterator::create_async_from_sync_iterator_prototype;
@ -181,14 +181,12 @@ impl JsValue {
// a. If hint is async, then
if hint == IteratorHint::Async {
// i. Set method to ? GetMethod(obj, @@asyncIterator).
if let Some(method) =
self.get_method(WellKnownSymbols::async_iterator(), context)?
{
if let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? {
Some(method)
} else {
// ii. If method is undefined, then
// 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).
let sync_iterator_record =
@ -199,7 +197,7 @@ impl JsValue {
}
} else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)?
self.get_method(JsSymbol::iterator(), context)?
}
}
.ok_or_else(|| {
@ -239,7 +237,7 @@ impl JsValue {
fn create_iterator_prototype(context: &mut Context<'_>) -> JsObject {
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)
.function(
|v, _, _| Ok(v.clone()),
@ -563,7 +561,7 @@ pub(crate) use if_abrupt_close_iterator;
fn create_async_iterator_prototype(context: &mut Context<'_>) -> JsObject {
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)
.function(
|v, _, _| Ok(v.clone()),

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

@ -26,7 +26,7 @@ use crate::{
object::{JsObject, ObjectInitializer, RecursionLimiter},
property::{Attribute, PropertyNameKind},
string::{utf16, CodePoint},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
@ -141,7 +141,7 @@ impl BuiltIn for Json {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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;
ObjectInitializer::new(context)

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

@ -11,7 +11,7 @@ use crate::{
error::JsNativeError,
object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult,
};
use boa_gc::{Finalize, Trace};
@ -147,7 +147,7 @@ impl MapIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("Map Iterator")
.writable(false)

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

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

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

@ -13,8 +13,8 @@
use super::JsArgs;
use crate::{
builtins::BuiltIn, object::ObjectInitializer, property::Attribute, symbol::WellKnownSymbols,
Context, JsResult, JsValue,
builtins::BuiltIn, object::ObjectInitializer, property::Attribute, symbol::JsSymbol, Context,
JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
@ -33,7 +33,7 @@ impl BuiltIn for Math {
let _timer = Profiler::global().start_event(Self::NAME, "init");
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)
.property("E", std::f64::consts::E, 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},
property::PropertyDescriptor,
property::PropertyKey,
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -148,7 +148,7 @@ impl ForInIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("For In Iterator")
.writable(false)

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

@ -28,7 +28,7 @@ use crate::{
},
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
string::utf16,
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsResult, JsString,
};
@ -822,7 +822,7 @@ impl Object {
};
// 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.
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,
},
property::{Attribute, PropertyDescriptorBuilder},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsError, JsResult,
};
@ -274,7 +274,7 @@ impl BuiltIn for Promise {
.static_method(Self::reject, "reject", 1)
.static_method(Self::resolve, "resolve", 1)
.static_accessor(
WellKnownSymbols::species(),
JsSymbol::species(),
Some(get_species),
None,
Attribute::CONFIGURABLE,
@ -284,7 +284,7 @@ impl BuiltIn for Promise {
.method(Self::finally, "finally", 1)
// <https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag>
.property(
WellKnownSymbols::to_string_tag(),
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)

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

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

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

@ -25,7 +25,7 @@ use crate::{
},
property::{Attribute, PropertyDescriptorBuilder},
string::{utf16, CodePoint},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::JsValue,
Context, JsResult, JsString,
};
@ -115,7 +115,7 @@ impl BuiltIn for RegExp {
.name(Self::NAME)
.length(Self::LENGTH)
.static_accessor(
WellKnownSymbols::species(),
JsSymbol::species(),
Some(get_species),
None,
Attribute::CONFIGURABLE,
@ -124,31 +124,15 @@ impl BuiltIn for RegExp {
.method(Self::test, "test", 1)
.method(Self::exec, "exec", 1)
.method(Self::to_string, "toString", 0)
.method(
Self::r#match,
(WellKnownSymbols::r#match(), "[Symbol.match]"),
1,
)
.method(Self::r#match, (JsSymbol::r#match(), "[Symbol.match]"), 1)
.method(
Self::match_all,
(WellKnownSymbols::match_all(), "[Symbol.matchAll]"),
1,
)
.method(
Self::replace,
(WellKnownSymbols::replace(), "[Symbol.replace]"),
2,
)
.method(
Self::search,
(WellKnownSymbols::search(), "[Symbol.search]"),
(JsSymbol::match_all(), "[Symbol.matchAll]"),
1,
)
.method(
Self::split,
(WellKnownSymbols::split(), "[Symbol.split]"),
2,
)
.method(Self::replace, (JsSymbol::replace(), "[Symbol.replace]"), 2)
.method(Self::search, (JsSymbol::search(), "[Symbol.search]"), 1)
.method(Self::split, (JsSymbol::split(), "[Symbol.split]"), 2)
.accessor("hasIndices", Some(get_has_indices), None, flag_attributes)
.accessor("global", Some(get_global), 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,
object::{JsObject, ObjectData},
property::PropertyDescriptor,
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -172,7 +172,7 @@ impl RegExpStringIterator {
let result = JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("RegExp String Iterator")
.writable(false)

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

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

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

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError,
object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult,
};
use boa_gc::{Finalize, Trace};
@ -154,7 +154,7 @@ impl SetIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("Set Iterator")
.writable(false)

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

@ -25,7 +25,7 @@ use crate::{
property::{Attribute, PropertyDescriptor},
string::utf16,
string::{CodePoint, Utf16Trim},
symbol::WellKnownSymbols,
symbol::JsSymbol,
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
@ -69,7 +69,7 @@ impl BuiltIn for String {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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;
ConstructorBuilder::with_standard_constructor(
@ -930,7 +930,7 @@ impl String {
// 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() {
// 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
if let Some(replacer) = replacer {
@ -1053,7 +1053,7 @@ impl String {
}
// 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
if let Some(replacer) = replacer {
@ -1358,7 +1358,7 @@ impl String {
let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() {
// 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
if let Some(matcher) = matcher {
// i. Return ? Call(matcher, regexp, « O »).
@ -1373,7 +1373,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?;
// 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 )`.
@ -1828,7 +1828,7 @@ impl String {
// 2. If separator is neither undefined nor null, then
if !separator.is_null_or_undefined() {
// 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
if let Some(splitter) = splitter {
// i. Return ? Call(splitter, separator, « O, limit »).
@ -1986,7 +1986,7 @@ impl String {
}
}
// 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
if let Some(matcher) = matcher {
return matcher.call(regexp, &[o.clone()], context);
@ -2000,7 +2000,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::new(js_string!("g")), context)?;
// 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 ] )`
@ -2127,7 +2127,7 @@ impl String {
let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() {
// 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
if let Some(searcher) = searcher {
// i. Return ? Call(searcher, regexp, « O »).
@ -2142,7 +2142,7 @@ impl String {
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?;
// 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(
@ -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> {
// 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).
if !matcher.is_undefined() {

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

@ -10,7 +10,7 @@ use crate::{
error::JsNativeError,
object::{JsObject, ObjectData},
property::PropertyDescriptor,
symbol::WellKnownSymbols,
symbol::JsSymbol,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
@ -98,7 +98,7 @@ impl StringIterator {
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
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()
.value("String Iterator")
.writable(false)

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

@ -18,53 +18,67 @@
#[cfg(test)]
mod tests;
use std::hash::BuildHasherDefault;
use super::JsArgs;
use crate::{
builtins::BuiltIn,
error::JsNativeError,
js_string,
native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder},
property::Attribute,
symbol::{JsSymbol, WellKnownSymbols},
symbol::JsSymbol,
value::JsValue,
Context, JsResult, JsString,
};
use boa_profiler::Profiler;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use rustc_hash::FxHasher;
use tap::{Conv, Pipe};
thread_local! {
static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
}
static GLOBAL_SYMBOL_REGISTRY: Lazy<GlobalSymbolRegistry> = Lazy::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 {
keys: FxHashMap<JsString, JsSymbol>,
symbols: FxHashMap<JsSymbol, JsString>,
keys: FxDashMap<Box<[u16]>, JsSymbol>,
symbols: FxDashMap<JsSymbol, Box<[u16]>>,
}
impl GlobalSymbolRegistry {
fn new() -> Self {
Self {
keys: FxHashMap::default(),
symbols: FxHashMap::default(),
keys: FxDashMap::default(),
symbols: FxDashMap::default(),
}
}
fn get_or_insert_key(&mut self, key: JsString) -> JsSymbol {
if let Some(symbol) = self.keys.get(&key) {
return symbol.clone();
fn get_or_create_symbol(&self, key: &JsString) -> JsResult<JsSymbol> {
let slice = &**key;
if let Some(symbol) = self.keys.get(slice) {
return Ok(symbol.clone());
}
let symbol = JsSymbol::new(Some(key.clone()));
self.keys.insert(key.clone(), symbol.clone());
self.symbols.insert(symbol.clone(), key);
symbol
let symbol = JsSymbol::new(Some(key.clone())).ok_or_else(|| {
JsNativeError::range()
.with_message("reached the maximum number of symbols that can be created")
})?;
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) {
return Some(key.clone());
return Some(js_string!(&**key));
}
None
@ -81,19 +95,19 @@ impl BuiltIn for Symbol {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_async_iterator = WellKnownSymbols::async_iterator();
let symbol_has_instance = WellKnownSymbols::has_instance();
let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable();
let symbol_iterator = WellKnownSymbols::iterator();
let symbol_match = WellKnownSymbols::r#match();
let symbol_match_all = WellKnownSymbols::match_all();
let symbol_replace = WellKnownSymbols::replace();
let symbol_search = WellKnownSymbols::search();
let symbol_species = WellKnownSymbols::species();
let symbol_split = WellKnownSymbols::split();
let symbol_to_primitive = WellKnownSymbols::to_primitive();
let symbol_to_string_tag = WellKnownSymbols::to_string_tag();
let symbol_unscopables = WellKnownSymbols::unscopables();
let symbol_async_iterator = JsSymbol::async_iterator();
let symbol_has_instance = JsSymbol::has_instance();
let symbol_is_concat_spreadable = JsSymbol::is_concat_spreadable();
let symbol_iterator = JsSymbol::iterator();
let symbol_match = JsSymbol::r#match();
let symbol_match_all = JsSymbol::match_all();
let symbol_replace = JsSymbol::replace();
let symbol_search = JsSymbol::search();
let symbol_species = JsSymbol::species();
let symbol_split = JsSymbol::split();
let symbol_to_primitive = JsSymbol::to_primitive();
let symbol_to_string_tag = JsSymbol::to_string_tag();
let symbol_unscopables = JsSymbol::unscopables();
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.
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> {
@ -300,12 +319,9 @@ impl Symbol {
// 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.
// 6. Return newSymbol.
Ok(GLOBAL_SYMBOL_REGISTRY
.with(move |registry| {
let mut registry = registry.borrow_mut();
registry.get_or_insert_key(string_key)
})
.into())
GLOBAL_SYMBOL_REGISTRY
.get_or_create_symbol(&string_key)
.map(JsValue::from)
}
/// `Symbol.keyFor( sym )`
@ -318,24 +334,20 @@ impl Symbol {
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.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> {
let sym = args.get_or_undefined(0);
// 1. If Type(sym) is not Symbol, throw a TypeError exception.
if let Some(sym) = sym.as_symbol() {
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do
// 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.
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
let registry = registry.borrow();
registry.get_symbol(&sym)
});
Ok(symbol.map(JsValue::from).unwrap_or_default())
} else {
Err(JsNativeError::typ()
.with_message("Symbol.keyFor: sym is not a symbol")
.into())
}
let sym = args.get_or_undefined(0).as_symbol().ok_or_else(|| {
JsNativeError::typ().with_message("Symbol.keyFor: sym is not a symbol")
})?;
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do
// 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.
Ok(GLOBAL_SYMBOL_REGISTRY
.get_key(&sym)
.map(JsValue::from)
.unwrap_or_default())
}
/// `Symbol.prototype [ @@toPrimitive ]`

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

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

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

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

1
boa_engine/src/lib.rs

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

18
boa_engine/src/object/jsobject.rs

@ -840,10 +840,10 @@ impl Drop for RecursionLimiter {
fn drop(&mut self) {
if self.top_level {
// 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 {
// 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()
.insert(self.ptr, RecursionValueState::Visited)
});
@ -851,13 +851,13 @@ impl Drop for RecursionLimiter {
}
}
impl RecursionLimiter {
thread_local! {
/// 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`)
static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
}
thread_local! {
/// 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`)
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.
///
/// 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.
#[allow(trivial_casts)]
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 top_level = hm.is_empty();
let old_state = hm.insert(ptr, RecursionValueState::Live);

5
boa_engine/src/object/operations.rs

@ -4,9 +4,8 @@ use crate::{
error::JsNativeError,
object::{JsObject, PrivateElement},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::Type,
Context, JsResult, JsValue,
Context, JsResult, JsSymbol, JsValue,
};
use boa_ast::function::PrivateName;
@ -512,7 +511,7 @@ impl JsObject {
})?;
// 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.
if s.is_null_or_undefined() {

223
boa_engine/src/string/common.rs

@ -1,15 +1,145 @@
use std::hash::BuildHasherDefault;
use crate::tagged::Tagged;
use super::JsString;
use boa_macros::utf16;
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.
///
/// Any string defined here is used as a static [`JsString`] instead of allocating on the heap.
pub(super) const COMMON_STRINGS: &[&[u16]] = &[
// Empty string
/// Any strings defined here are used as a static [`JsString`] instead of allocating on the heap.
#[derive(Debug)]
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!(""),
// Misc
utf16!(","),
@ -80,7 +210,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("isArray"),
utf16!("of"),
utf16!("copyWithin"),
utf16!("entries"),
utf16!("every"),
utf16!("fill"),
utf16!("filter"),
@ -116,8 +245,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("endsWith"),
utf16!("fromCharCode"),
utf16!("fromCodePoint"),
utf16!("includes"),
utf16!("indexOf"),
utf16!("lastIndexOf"),
utf16!("match"),
utf16!("matchAll"),
@ -129,7 +256,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("replace"),
utf16!("replaceAll"),
utf16!("search"),
utf16!("slice"),
utf16!("split"),
utf16!("startsWith"),
utf16!("substr"),
@ -148,7 +274,6 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("parseFloat"),
utf16!("isFinite"),
utf16!("isNaN"),
utf16!("parseInt"),
utf16!("EPSILON"),
utf16!("MAX_SAFE_INTEGER"),
utf16!("MIN_SAFE_INTEGER"),
@ -196,19 +321,8 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("asyncIterator"),
utf16!("hasInstance"),
utf16!("species"),
utf16!("Symbol.species"),
utf16!("unscopables"),
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!("toPrimitive"),
utf16!("get description"),
@ -455,46 +569,31 @@ pub(super) const COMMON_STRINGS: &[&[u16]] = &[
utf16!("Z"),
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.
#![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};
pub use boa_macros::utf16;
@ -34,12 +38,13 @@ use std::{
convert::Infallible,
hash::{Hash, Hasher},
ops::{Deref, Index},
ptr::{self, NonNull},
process::abort,
ptr::{self, addr_of, addr_of_mut, NonNull},
slice::SliceIndex,
str::FromStr,
};
use self::common::{COMMON_STRINGS, COMMON_STRINGS_CACHE, MAX_COMMON_STRING_LENGTH};
use self::common::StaticJsStrings;
fn alloc_overflow() -> ! {
panic!("detected overflow during string allocation")
@ -185,98 +190,11 @@ struct 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.
///
/// This is pretty similar to a <code>[Rc][std::rc::Rc]\<[\[u16\]][slice]\></code>, but without
/// the length metadata associated with the `Rc` fat pointer. Instead, the length of
/// every string is stored on the heap, along with its reference counter and its data.
/// This is pretty similar to a <code>[Rc][std::rc::Rc]\<[\[u16\]][slice]\></code>, but without the
/// length metadata associated with the `Rc` fat pointer. Instead, the length of every string is
/// 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
/// 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.
#[derive(Finalize)]
pub struct JsString {
ptr: TaggedJsString,
ptr: Tagged<RawJsString>,
}
// JsString should always be pointer sized.
@ -327,7 +245,7 @@ impl JsString {
let string = {
// 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 {
let count = string.len();
// SAFETY:
@ -347,17 +265,11 @@ impl JsString {
}
Self {
// 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 {
if let Some(constant) = COMMON_STRINGS_CACHE.with(|c| c.get(&string[..]).cloned()) {
return constant;
}
}
string
StaticJsStrings::get_string(&string[..]).unwrap_or(string)
}
/// 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()?)
}
/// 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.
///
/// # Panics
@ -616,8 +515,10 @@ impl JsString {
// `[u16; str_len]`, the memory of the array must be in the `usize`
// range for the allocation to succeed.
unsafe {
let data = (*inner).data.as_ptr();
ptr::eq(inner.cast::<u8>().add(offset).cast(), data)
ptr::eq(
inner.cast::<u8>().add(offset).cast(),
(*inner).data.as_mut_ptr(),
)
}
});
@ -630,7 +531,7 @@ impl JsString {
let ptr = Self::allocate_inner(count);
// 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:
// - 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
@ -643,8 +544,8 @@ impl JsString {
ptr::copy_nonoverlapping(string.as_ptr(), data.cast(), count);
}
Self {
// Safety: We already know it's a valid heap pointer.
ptr: unsafe { TaggedJsString::new_heap(ptr) },
// Safety: `allocate_inner` guarantees `ptr` is a valid heap pointer.
ptr: Tagged::from_non_null(ptr),
}
}
}
@ -664,10 +565,14 @@ impl Borrow<[u16]> for JsString {
impl Clone for JsString {
#[inline]
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.
let inner = unsafe { &mut *inner.as_ptr() };
inner.refcount.set(inner.refcount.get() + 1);
let inner = unsafe { inner.as_ref() };
let strong = inner.refcount.get().wrapping_add(1);
if strong == 0 {
abort()
}
inner.refcount.set(strong);
}
Self { ptr: self.ptr }
}
@ -676,42 +581,37 @@ impl Clone for JsString {
impl Default for JsString {
#[inline]
fn default() -> Self {
sa::const_assert!(!COMMON_STRINGS.is_empty());
// 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),
}
}
StaticJsStrings::empty_string()
}
}
impl Drop for JsString {
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.
let inner = unsafe { &mut *raw.as_ptr() };
let inner = unsafe { raw.as_ref() };
inner.refcount.set(inner.refcount.get() - 1);
if inner.refcount.get() == 0 {
// SAFETY:
// 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 {
Layout::for_value(inner)
.extend(Layout::array::<u16>(inner.len).unwrap_unchecked())
.unwrap_unchecked()
.0
.pad_to_align()
};
// Safety:
// 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.
unsafe {
dealloc(raw.as_ptr().cast(), layout);
}
if inner.refcount.get() != 0 {
return;
}
// SAFETY:
// 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 {
Layout::for_value(inner)
.extend(Layout::array::<u16>(inner.len).unwrap_unchecked())
.unwrap_unchecked()
.0
.pad_to_align()
};
// Safety:
// 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.
unsafe {
dealloc(raw.as_ptr().cast(), layout);
}
}
}
@ -735,8 +635,8 @@ impl Deref for JsString {
type Target = [u16];
fn deref(&self) -> &Self::Target {
match self.ptr() {
JsStringPtrKind::Heap(h) => {
match self.ptr.unwrap() {
UnwrappedTagged::Ptr(h) => {
// SAFETY:
// - The `RawJsString` type has all the necessary information to reconstruct a valid
// 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
// by its signature, so this doesn't outlive `self`.
unsafe {
std::slice::from_raw_parts(
h.as_ptr().cast::<u8>().add(DATA_OFFSET).cast(),
h.as_ref().len,
)
let h = h.as_ptr();
std::slice::from_raw_parts(addr_of!((*h).data).cast(), (*h).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 {
fn from(s: &[u16]) -> Self {
if s.len() <= MAX_COMMON_STRING_LENGTH {
if let Some(constant) = COMMON_STRINGS_CACHE.with(|c| c.get(s).cloned()) {
return constant;
}
}
Self::from_slice_skip_interning(s)
StaticJsStrings::get_string(s).unwrap_or_else(|| Self::from_slice_skip_interning(s))
}
}
@ -822,10 +719,6 @@ impl Ord for JsString {
impl PartialEq for JsString {
fn eq(&self, other: &Self) -> bool {
if self.ptr == other.ptr {
return true;
}
self[..] == other[..]
}
}
@ -950,19 +843,21 @@ impl ToStringEscaped for [u16] {
}
#[cfg(test)]
mod tests {
use crate::tagged::UnwrappedTagged;
use super::utf16;
use super::{JsString, JsStringPtrKind};
use super::JsString;
impl JsString {
/// Gets the number of `JsString`s which point to this allocation.
fn refcount(&self) -> Option<usize> {
match self.ptr() {
JsStringPtrKind::Heap(inner) => {
match self.ptr.unwrap() {
UnwrappedTagged::Ptr(inner) => {
// SAFETY: The reference count of `JsString` guarantees that `inner` is always valid.
let inner = unsafe { inner.as_ref() };
Some(inner.refcount.get())
}
JsStringPtrKind::Static(_inner) => None,
UnwrappedTagged::Tag(_inner) => None,
}
}
}
@ -1016,13 +911,13 @@ mod tests {
let x = js_string!("Hello");
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");
assert_ne!(x.ptr, z.ptr);
assert_ne!(y.ptr, z.ptr);
assert_ne!(x.ptr.addr(), z.ptr.addr());
assert_ne!(y.ptr.addr(), z.ptr.addr());
}
#[test]
@ -1030,13 +925,13 @@ mod tests {
let x = js_string!();
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!();
assert_eq!(x.ptr, z.ptr);
assert_eq!(y.ptr, z.ptr);
assert_eq!(x.ptr.addr(), z.ptr.addr());
assert_eq!(y.ptr.addr(), z.ptr.addr());
}
#[test]

443
boa_engine/src/symbol.rs

@ -15,240 +15,97 @@
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [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 num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
cell::Cell,
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.
///
/// This is where the well known symbol live
/// and internal engine symbols.
const RESERVED_SYMBOL_HASHES: u64 = 128;
thread_local! {
/// Cached well known symbols
static WELL_KNOW_SYMBOLS: WellKnownSymbols = WellKnownSymbols::new();
const RESERVED_SYMBOL_HASHES: u64 = 127;
fn get_id() -> Option<u64> {
// Symbol hash.
//
// 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.
///
/// For now this is an incremented u64 number.
static SYMBOL_HASH_COUNT: Cell<u64> = Cell::new(RESERVED_SYMBOL_HASHES);
/// List of well known symbols.
#[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
enum WellKnown {
AsyncIterator,
HasInstance,
IsConcatSpreadable,
Iterator,
Match,
MatchAll,
Replace,
Search,
Species,
Split,
ToPrimitive,
ToStringTag,
Unscopables,
}
impl WellKnownSymbols {
/// Create the well known symbols.
fn new() -> Self {
let mut count = 0;
let async_iterator = JsSymbol::with_hash(count, Some("Symbol.asyncIterator".into()));
count += 1;
let has_instance = JsSymbol::with_hash(count, Some("Symbol.hasInstance".into()));
count += 1;
let is_concat_spreadable =
JsSymbol::with_hash(count, Some("Symbol.isConcatSpreadable".into()));
count += 1;
let iterator = JsSymbol::with_hash(count, Some("Symbol.iterator".into()));
count += 1;
let match_ = JsSymbol::with_hash(count, Some("Symbol.match".into()));
count += 1;
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,
impl WellKnown {
const fn description(self) -> JsString {
match self {
WellKnown::AsyncIterator => StaticJsStrings::symbol_async_iterator(),
WellKnown::HasInstance => StaticJsStrings::symbol_has_instance(),
WellKnown::IsConcatSpreadable => StaticJsStrings::symbol_is_concat_spreadable(),
WellKnown::Iterator => StaticJsStrings::symbol_iterator(),
WellKnown::Match => StaticJsStrings::symbol_match(),
WellKnown::MatchAll => StaticJsStrings::symbol_match_all(),
WellKnown::Replace => StaticJsStrings::symbol_replace(),
WellKnown::Search => StaticJsStrings::symbol_search(),
WellKnown::Species => StaticJsStrings::symbol_species(),
WellKnown::Split => StaticJsStrings::symbol_split(),
WellKnown::ToPrimitive => StaticJsStrings::symbol_to_primitive(),
WellKnown::ToStringTag => StaticJsStrings::symbol_to_string_tag(),
WellKnown::Unscopables => StaticJsStrings::symbol_unscopables(),
}
}
/// The `Symbol.asyncIterator` well known symbol.
///
/// 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())
const fn hash(self) -> u64 {
self as u64
}
/// The `Symbol.toStringTag` well known symbol.
///
/// 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())
const fn tag(self) -> usize {
self as usize
}
/// The `Symbol.unscopables` well known symbol.
///
/// 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())
fn from_tag(tag: usize) -> Option<Self> {
Self::try_from_primitive(u8::try_from(tag).ok()?).ok()
}
}
@ -260,46 +117,68 @@ struct Inner {
}
/// This represents a JavaScript symbol primitive.
#[derive(Debug, Clone, Finalize)]
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,
// so this is safe.
unsafe impl Trace for JsSymbol {
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 {
/// Create a new symbol.
/// Creates a new symbol.
///
/// Returns `None` if the maximum number of possible symbols has been reached (`u64::MAX`).
#[inline]
#[must_use]
pub fn new(description: Option<JsString>) -> Self {
let hash = SYMBOL_HASH_COUNT.with(|count| {
let hash = count.get();
count.set(hash + 1);
hash
});
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 }),
}
pub fn new(description: Option<JsString>) -> Option<Self> {
let hash = get_id()?;
let arc = Arc::new(Inner { hash, description });
Some(Self {
// SAFETY: Pointers returned by `Arc::into_raw` must be non-null.
repr: unsafe { Tagged::from_ptr(Arc::into_raw(arc).cast_mut()) },
})
}
/// Returns the `Symbol`s description.
#[inline]
#[must_use]
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.
@ -308,7 +187,18 @@ impl JsSymbol {
#[inline]
#[must_use]
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 )`
@ -319,17 +209,82 @@ impl JsSymbol {
/// [spec]: https://tc39.es/ecma262/#sec-symboldescriptivestring
#[must_use]
pub fn descriptive_string(&self) -> JsString {
self.inner.description.as_ref().map_or_else(
self.description().as_ref().map_or_else(
|| js_string!("Symbol()"),
|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 {
#[inline]
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()),
None => write!(f, "Symbol()"),
}
@ -341,26 +296,26 @@ impl Eq for JsSymbol {}
impl PartialEq for JsSymbol {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner.hash == other.inner.hash
self.hash() == other.hash()
}
}
impl PartialOrd for JsSymbol {
#[inline]
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 {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner.hash.cmp(&other.inner.hash)
self.hash().cmp(&other.hash())
}
}
impl Hash for JsSymbol {
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,
object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols},
symbol::JsSymbol,
Context, JsBigInt, JsResult, JsString,
};
use boa_gc::{custom_trace, Finalize, Trace};
@ -360,7 +360,7 @@ impl JsValue {
// 2. If Type(input) is Object, then
if let Some(input) = self.as_object() {
// 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
if let Some(exotic_to_prim) = exotic_to_prim {

4
boa_engine/src/value/operations.rs

@ -5,7 +5,7 @@ use crate::{
},
error::JsNativeError,
js_string,
value::{Numeric, PreferredType, WellKnownSymbols},
value::{JsSymbol, Numeric, PreferredType},
Context, JsBigInt, JsResult, JsValue,
};
@ -427,7 +427,7 @@ impl JsValue {
}
// 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
Some(instance_of_handler) => {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).

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

@ -1,6 +1,8 @@
/// The opcodes of the vm.
use crate::{vm::ShouldExit, Context, JsResult};
use num_enum::TryFromPrimitive;
// Operation modules
mod await_stm;
mod binary_ops;
@ -171,7 +173,7 @@ pub(crate) trait Operation {
}
generate_impl! {
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
#[repr(u8)]
pub enum Opcode {
/// 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.
///
/// This separate enum exists to make matching exhaustive where needed.

Loading…
Cancel
Save