/* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2023, Linus Groh * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace JS { // 2 ** 53 - 1 static constexpr double MAX_ARRAY_LIKE_INDEX = 9007199254740991.0; // Unique bit representation of negative zero (only sign bit set) static constexpr u64 NEGATIVE_ZERO_BITS = ((u64)1 << 63); // This leaves us 3 bits to tag the type of pointer: static constexpr u64 OBJECT_TAG = 0b001 | GC::IS_CELL_BIT; static constexpr u64 STRING_TAG = 0b010 | GC::IS_CELL_BIT; static constexpr u64 SYMBOL_TAG = 0b011 | GC::IS_CELL_BIT; static constexpr u64 ACCESSOR_TAG = 0b100 | GC::IS_CELL_BIT; static constexpr u64 BIGINT_TAG = 0b101 | GC::IS_CELL_BIT; // We can then by extracting the top 13 bits quickly check if a Value is // pointer backed. static_assert((OBJECT_TAG & GC::IS_CELL_PATTERN) == GC::IS_CELL_PATTERN); static_assert((STRING_TAG & GC::IS_CELL_PATTERN) == GC::IS_CELL_PATTERN); static_assert((GC::CANON_NAN_BITS & GC::IS_CELL_PATTERN) != GC::IS_CELL_PATTERN); static_assert((GC::NEGATIVE_INFINITY_BITS & GC::IS_CELL_PATTERN) != GC::IS_CELL_PATTERN); // Then for the non pointer backed types we don't set the sign bit and use the // three lower bits for tagging as well. static constexpr u64 UNDEFINED_TAG = 0b110 | GC::BASE_TAG; static constexpr u64 NULL_TAG = 0b111 | GC::BASE_TAG; static constexpr u64 BOOLEAN_TAG = 0b001 | GC::BASE_TAG; static constexpr u64 INT32_TAG = 0b010 | GC::BASE_TAG; static constexpr u64 EMPTY_TAG = 0b011 | GC::BASE_TAG; // Notice how only undefined and null have the top bit set, this mean we can // quickly check for nullish values by checking if the top and bottom bits are set // but the middle one isn't. static constexpr u64 IS_NULLISH_EXTRACT_PATTERN = 0xFFFEULL; static constexpr u64 IS_NULLISH_PATTERN = 0x7FFEULL; static_assert((UNDEFINED_TAG & IS_NULLISH_EXTRACT_PATTERN) == IS_NULLISH_PATTERN); static_assert((NULL_TAG & IS_NULLISH_EXTRACT_PATTERN) == IS_NULLISH_PATTERN); static_assert((BOOLEAN_TAG & IS_NULLISH_EXTRACT_PATTERN) != IS_NULLISH_PATTERN); static_assert((INT32_TAG & IS_NULLISH_EXTRACT_PATTERN) != IS_NULLISH_PATTERN); static_assert((EMPTY_TAG & IS_NULLISH_EXTRACT_PATTERN) != IS_NULLISH_PATTERN); // We also have the empty tag to represent array holes however since empty // values are not valid anywhere else we can use this "value" to our advantage // in Optional to represent the empty optional. static constexpr u64 SHIFTED_BOOLEAN_TAG = BOOLEAN_TAG << GC::TAG_SHIFT; static constexpr u64 SHIFTED_INT32_TAG = INT32_TAG << GC::TAG_SHIFT; // Summary: // To pack all the different value in to doubles we use the following schema: // s = sign, e = exponent, m = mantissa // The top part is the tag and the bottom the payload. // 0bseeeeeeeeeeemmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm // 0b0111111111111000 0... is the only real NaN // 0b1111111111111xxx yyy... xxx = pointer type, yyy = pointer value // 0b0111111111111xxx yyy... xxx = non-pointer type, yyy = value or 0 if just type // Future expansion: We are not fully utilizing all the possible bit patterns // yet, these choices were made to make it easy to implement and understand. // We can for example drop the always 1 top bit of the mantissa expanding our // options from 8 tags to 15 but since we currently only use 5 for both sign bits // this is not needed. class Value : public GC::NanBoxedValue { public: enum class PreferredType { Default, String, Number, }; [[nodiscard]] u16 tag() const { return m_value.tag; } bool is_special_empty_value() const { return m_value.encoded == (EMPTY_TAG << GC::TAG_SHIFT); } bool is_undefined() const { return m_value.encoded == (UNDEFINED_TAG << GC::TAG_SHIFT); } bool is_null() const { return m_value.encoded == (NULL_TAG << GC::TAG_SHIFT); } bool is_number() const { return is_double() || is_int32(); } bool is_string() const { return m_value.tag == STRING_TAG; } bool is_object() const { return m_value.tag == OBJECT_TAG; } bool is_boolean() const { return m_value.tag == BOOLEAN_TAG; } bool is_symbol() const { return m_value.tag == SYMBOL_TAG; } bool is_accessor() const { return m_value.tag == ACCESSOR_TAG; } bool is_bigint() const { return m_value.tag == BIGINT_TAG; } bool is_nullish() const { return (m_value.tag & IS_NULLISH_EXTRACT_PATTERN) == IS_NULLISH_PATTERN; } ThrowCompletionOr is_array(VM&) const; bool is_function() const; bool is_constructor() const; bool is_error() const; ThrowCompletionOr is_regexp(VM&) const; bool is_infinity() const { static_assert(GC::NEGATIVE_INFINITY_BITS == (0x1ULL << 63 | GC::POSITIVE_INFINITY_BITS)); return (0x1ULL << 63 | m_value.encoded) == GC::NEGATIVE_INFINITY_BITS; } bool is_positive_infinity() const { return m_value.encoded == GC::POSITIVE_INFINITY_BITS; } bool is_negative_infinity() const { return m_value.encoded == GC::NEGATIVE_INFINITY_BITS; } bool is_positive_zero() const { return m_value.encoded == 0 || (is_int32() && as_i32() == 0); } bool is_negative_zero() const { return m_value.encoded == NEGATIVE_ZERO_BITS; } bool is_integral_number() const { if (is_int32()) return true; return is_finite_number() && trunc(as_double()) == as_double(); } bool is_finite_number() const { if (!is_number()) return false; if (is_int32()) return true; return !is_nan() && !is_infinity(); } constexpr Value() : Value(UNDEFINED_TAG << GC::TAG_SHIFT, (u64)0) { } template requires(IsSameIgnoringCV) explicit Value(T value) : Value(BOOLEAN_TAG << GC::TAG_SHIFT, (u64)value) { } explicit Value(double value) { bool is_negative_zero = bit_cast(value) == NEGATIVE_ZERO_BITS; if (value >= NumericLimits::min() && value <= NumericLimits::max() && trunc(value) == value && !is_negative_zero) { ASSERT(!(SHIFTED_INT32_TAG & (static_cast(value) & 0xFFFFFFFFul))); m_value.encoded = SHIFTED_INT32_TAG | (static_cast(value) & 0xFFFFFFFFul); } else { if (isnan(value)) [[unlikely]] m_value.encoded = GC::CANON_NAN_BITS; else m_value.as_double = value; } } explicit Value(f16 value) : Value(static_cast(value)) { } // NOTE: A couple of integral types are excluded here: // - i32 has its own dedicated Value constructor // - i64 cannot safely be cast to a double // - bool isn't a number type and has its own dedicated Value constructor template requires(IsIntegral && !IsSameIgnoringCV && !IsSameIgnoringCV && !IsSameIgnoringCV) explicit Value(T value) { if (value > NumericLimits::max()) { m_value.as_double = static_cast(value); } else { ASSERT(!(SHIFTED_INT32_TAG & (static_cast(value) & 0xFFFFFFFFul))); m_value.encoded = SHIFTED_INT32_TAG | (static_cast(value) & 0xFFFFFFFFul); } } explicit Value(unsigned value) { if (value > NumericLimits::max()) { m_value.as_double = static_cast(value); } else { ASSERT(!(SHIFTED_INT32_TAG & (static_cast(value) & 0xFFFFFFFFul))); m_value.encoded = SHIFTED_INT32_TAG | (static_cast(value) & 0xFFFFFFFFul); } } explicit Value(i32 value) : Value(SHIFTED_INT32_TAG, (u32)value) { } Value(Cell const* cell) : Value(GC::IS_CELL_BIT << GC::TAG_SHIFT, reinterpret_cast(cell)) { } Value(Object const* object) : Value(OBJECT_TAG << GC::TAG_SHIFT, reinterpret_cast(object)) { } Value(PrimitiveString const* string) : Value(STRING_TAG << GC::TAG_SHIFT, reinterpret_cast(string)) { } Value(Symbol const* symbol) : Value(SYMBOL_TAG << GC::TAG_SHIFT, reinterpret_cast(symbol)) { } Value(Accessor const* accessor) : Value(ACCESSOR_TAG << GC::TAG_SHIFT, reinterpret_cast(accessor)) { } Value(BigInt const* bigint) : Value(BIGINT_TAG << GC::TAG_SHIFT, reinterpret_cast(bigint)) { } template Value(GC::Ptr ptr) : Value(ptr.ptr()) { } template Value(GC::Ref ptr) : Value(ptr.ptr()) { } template Value(GC::Root const& ptr) : Value(ptr.ptr()) { } Cell& as_cell() { VERIFY(is_cell()); return *extract_pointer(); } Cell& as_cell() const { VERIFY(is_cell()); return *extract_pointer(); } double as_double() const { VERIFY(is_number()); if (is_int32()) return as_i32(); return m_value.as_double; } bool as_bool() const { VERIFY(is_boolean()); return static_cast(m_value.encoded & 0x1); } Object& as_object() { VERIFY(is_object()); return *extract_pointer(); } Object const& as_object() const { VERIFY(is_object()); return *extract_pointer(); } PrimitiveString& as_string() { VERIFY(is_string()); return *extract_pointer(); } PrimitiveString const& as_string() const { VERIFY(is_string()); return *extract_pointer(); } Symbol& as_symbol() { VERIFY(is_symbol()); return *extract_pointer(); } Symbol const& as_symbol() const { VERIFY(is_symbol()); return *extract_pointer(); } Accessor& as_accessor() { VERIFY(is_accessor()); return *extract_pointer(); } BigInt const& as_bigint() const { VERIFY(is_bigint()); return *extract_pointer(); } BigInt& as_bigint() { VERIFY(is_bigint()); return *extract_pointer(); } Array& as_array(); FunctionObject& as_function(); FunctionObject const& as_function() const; u64 encoded() const { return m_value.encoded; } ThrowCompletionOr to_string(VM&) const; ThrowCompletionOr to_byte_string(VM&) const; ThrowCompletionOr to_utf16_string(VM&) const; ThrowCompletionOr to_well_formed_string(VM&) const; ThrowCompletionOr> to_primitive_string(VM&); ThrowCompletionOr to_primitive(VM&, PreferredType preferred_type = PreferredType::Default) const; ThrowCompletionOr> to_object(VM&) const; ThrowCompletionOr to_numeric(VM&) const; ThrowCompletionOr to_number(VM&) const; ThrowCompletionOr> to_bigint(VM&) const; ThrowCompletionOr to_bigint_int64(VM&) const; ThrowCompletionOr to_bigint_uint64(VM&) const; ThrowCompletionOr to_double(VM&) const; ThrowCompletionOr to_property_key(VM&) const; ThrowCompletionOr to_i32(VM&) const; ThrowCompletionOr to_u32(VM&) const; ThrowCompletionOr to_i16(VM&) const; ThrowCompletionOr to_u16(VM&) const; ThrowCompletionOr to_i8(VM&) const; ThrowCompletionOr to_u8(VM&) const; ThrowCompletionOr to_u8_clamp(VM&) const; ThrowCompletionOr to_length(VM&) const; ThrowCompletionOr to_index(VM&) const; ThrowCompletionOr to_integer_or_infinity(VM&) const; bool to_boolean() const; ThrowCompletionOr get(VM&, PropertyKey const&) const; ThrowCompletionOr> get_method(VM&, PropertyKey const&) const; [[nodiscard]] String to_string_without_side_effects() const; #if 0 Value value_or(Value fallback) const { if (is_special_empty_value()) return fallback; return *this; } #endif [[nodiscard]] GC::Ref typeof_(VM&) const; bool operator==(Value const&) const; template [[nodiscard]] ALWAYS_INLINE ThrowCompletionOr invoke(VM&, PropertyKey const& property_key, Args... args); // A double is any Value which does not have the full exponent and top mantissa bit set or has // exactly only those bits set. bool is_double() const { return (m_value.encoded & GC::CANON_NAN_BITS) != GC::CANON_NAN_BITS || (m_value.encoded == GC::CANON_NAN_BITS); } bool is_int32() const { return m_value.tag == INT32_TAG; } i32 as_i32() const { VERIFY(is_int32()); return static_cast(m_value.encoded & 0xFFFFFFFF); } i32 as_i32_clamped_integral_number() const { VERIFY(is_int32() || is_finite_number()); if (is_int32()) return as_i32(); double value = trunc(as_double()); if (value > INT32_MAX) return INT32_MAX; if (value < INT32_MIN) return INT32_MIN; return static_cast(value); } bool to_boolean_slow_case() const; private: ThrowCompletionOr to_number_slow_case(VM&) const; ThrowCompletionOr to_numeric_slow_case(VM&) const; ThrowCompletionOr to_primitive_slow_case(VM&, PreferredType) const; enum class EmptyTag { Empty }; constexpr Value(EmptyTag) : Value(EMPTY_TAG << GC::TAG_SHIFT, (u64)0) { } constexpr Value(u64 tag, u64 val) { ASSERT(!(tag & val)); m_value.encoded = tag | val; } template Value(u64 tag, PointerType const* ptr) { if (!ptr) { // Make sure all nullptrs are null m_value.tag = NULL_TAG; return; } ASSERT((tag & 0x8000000000000000ul) == 0x8000000000000000ul); if constexpr (sizeof(PointerType*) < sizeof(u64)) { m_value.encoded = tag | reinterpret_cast(ptr); } else { // NOTE: Pointers in x86-64 use just 48 bits however are supposed to be // sign extended up from the 47th bit. // This means that all bits above the 47th should be the same as // the 47th. When storing a pointer we thus drop the top 16 bits as // we can recover it when extracting the pointer again. // See also: NanBoxedValue::extract_pointer. m_value.encoded = tag | (reinterpret_cast(ptr) & 0x0000ffffffffffffULL); } } [[nodiscard]] ThrowCompletionOr invoke_internal(VM&, PropertyKey const&, Optional> arguments); ThrowCompletionOr to_i32_slow_case(VM&) const; friend constexpr Value js_undefined(); friend constexpr Value js_null(); friend constexpr Value js_special_empty_value(); friend ThrowCompletionOr greater_than(VM&, Value lhs, Value rhs); friend ThrowCompletionOr greater_than_equals(VM&, Value lhs, Value rhs); friend ThrowCompletionOr less_than(VM&, Value lhs, Value rhs); friend ThrowCompletionOr less_than_equals(VM&, Value lhs, Value rhs); friend ThrowCompletionOr add(VM&, Value lhs, Value rhs); friend bool same_value_non_number(Value lhs, Value rhs); }; inline constexpr Value js_undefined() { return Value(UNDEFINED_TAG << GC::TAG_SHIFT, (u64)0); } inline constexpr Value js_null() { return Value(NULL_TAG << GC::TAG_SHIFT, (u64)0); } inline constexpr Value js_special_empty_value() { return Value(Value::EmptyTag::Empty); } inline Value js_nan() { return Value(NAN); } inline Value js_infinity() { return Value(INFINITY); } inline Value js_negative_infinity() { return Value(-INFINITY); } ThrowCompletionOr greater_than(VM&, Value lhs, Value rhs); ThrowCompletionOr greater_than_equals(VM&, Value lhs, Value rhs); ThrowCompletionOr less_than(VM&, Value lhs, Value rhs); ThrowCompletionOr less_than_equals(VM&, Value lhs, Value rhs); ThrowCompletionOr bitwise_and(VM&, Value lhs, Value rhs); ThrowCompletionOr bitwise_or(VM&, Value lhs, Value rhs); ThrowCompletionOr bitwise_xor(VM&, Value lhs, Value rhs); ThrowCompletionOr bitwise_not(VM&, Value); ThrowCompletionOr unary_plus(VM&, Value); ThrowCompletionOr unary_minus(VM&, Value); ThrowCompletionOr left_shift(VM&, Value lhs, Value rhs); ThrowCompletionOr right_shift(VM&, Value lhs, Value rhs); ThrowCompletionOr unsigned_right_shift(VM&, Value lhs, Value rhs); ThrowCompletionOr add(VM&, Value lhs, Value rhs); ThrowCompletionOr sub(VM&, Value lhs, Value rhs); ThrowCompletionOr mul(VM&, Value lhs, Value rhs); ThrowCompletionOr div(VM&, Value lhs, Value rhs); ThrowCompletionOr mod(VM&, Value lhs, Value rhs); ThrowCompletionOr exp(VM&, Value lhs, Value rhs); ThrowCompletionOr in(VM&, Value lhs, Value rhs); ThrowCompletionOr instance_of(VM&, Value lhs, Value rhs); ThrowCompletionOr ordinary_has_instance(VM&, Value lhs, Value rhs); ThrowCompletionOr is_loosely_equal(VM&, Value lhs, Value rhs); bool is_strictly_equal(Value lhs, Value rhs); bool same_value(Value lhs, Value rhs); bool same_value_zero(Value lhs, Value rhs); bool same_value_non_number(Value lhs, Value rhs); ThrowCompletionOr is_less_than(VM&, Value lhs, Value rhs, bool left_first); double to_integer_or_infinity(double); enum class NumberToStringMode { WithExponent, WithoutExponent, }; [[nodiscard]] String number_to_string(double, NumberToStringMode = NumberToStringMode::WithExponent); [[nodiscard]] ByteString number_to_byte_string(double, NumberToStringMode = NumberToStringMode::WithExponent); double string_to_number(StringView); inline bool Value::operator==(Value const& value) const { return same_value(*this, value); } } namespace AK { static_assert(sizeof(JS::Value) == sizeof(double)); template<> class Optional : public OptionalBase { template friend class Optional; public: using ValueType = JS::Value; constexpr Optional() = default; template V> constexpr Optional(V) { } constexpr Optional(Optional const& other) { if (other.has_value()) m_value = other.m_value; } constexpr Optional(Optional&& other) : m_value(other.m_value) { } template requires(!IsSame>) explicit(!IsConvertible) constexpr Optional(U&& value) requires(!IsSame, Optional> && IsConstructible) : m_value(forward(value)) { } template V> constexpr Optional& operator=(V) { clear(); return *this; } constexpr Optional& operator=(Optional const& other) { if (this != &other) { clear(); m_value = other.m_value; } return *this; } constexpr Optional& operator=(Optional&& other) { if (this != &other) { clear(); m_value = other.m_value; } return *this; } constexpr void clear() { m_value = JS::js_special_empty_value(); } [[nodiscard]] constexpr bool has_value() const { return !m_value.is_special_empty_value(); } [[nodiscard]] constexpr JS::Value& value() & { VERIFY(has_value()); return m_value; } [[nodiscard]] constexpr JS::Value const& value() const& { VERIFY(has_value()); return m_value; } [[nodiscard]] constexpr JS::Value value() && { return release_value(); } [[nodiscard]] constexpr JS::Value release_value() { VERIFY(has_value()); JS::Value released_value = m_value; clear(); return released_value; } private: JS::Value m_value { JS::js_special_empty_value() }; }; } namespace GC { template<> class Root { public: Root() = default; static Root create(JS::Value value, SourceLocation location) { if (value.is_cell()) return Root(value, &value.as_cell(), location); return Root(value); } auto cell() { return m_handle.cell(); } auto cell() const { return m_handle.cell(); } auto value() const { return *m_value; } bool is_null() const { return m_handle.is_null() && !m_value.has_value(); } bool operator==(JS::Value const& value) const { return value == m_value; } bool operator==(Root const& other) const { return other.m_value == this->m_value; } private: explicit Root(JS::Value value) : m_value(value) { } explicit Root(JS::Value value, Cell* cell, SourceLocation location) : m_value(value) , m_handle(Root::create(cell, location)) { } Optional m_value; Root m_handle; }; inline Root make_root(JS::Value value, SourceLocation location = SourceLocation::current()) { return Root::create(value, location); } } namespace AK { template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, JS::Value value) { if (value.is_special_empty_value()) return Formatter::format(builder, ""sv); return Formatter::format(builder, value.to_string_without_side_effects()); } }; template<> struct Traits : DefaultTraits { static unsigned hash(JS::Value value) { return Traits::hash(value.encoded()); } static constexpr bool is_trivial() { return true; } }; template<> struct Traits> : public DefaultTraits> { static unsigned hash(GC::Root const& handle) { return Traits::hash(handle.value()); } }; }