diff --git a/Userland/Applications/Spreadsheet/JSIntegration.cpp b/Userland/Applications/Spreadsheet/JSIntegration.cpp index 2512f8b324..dcd67770e5 100644 --- a/Userland/Applications/Spreadsheet/JSIntegration.cpp +++ b/Userland/Applications/Spreadsheet/JSIntegration.cpp @@ -324,7 +324,7 @@ JS_DEFINE_NATIVE_FUNCTION(SheetGlobalObject::column_arithmetic) auto& column_name_str = column_name.as_string().string(); auto offset = TRY(vm.argument(1).to_number(global_object)); - auto offset_number = offset.as_i32(); + auto offset_number = static_cast(offset.as_double()); auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 63a2b11dad..c6f1fdd106 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -356,7 +356,7 @@ static ThrowCompletionOr argument_list_evaluation(Interpreter& interpreter for (auto& argument : arguments) { auto value = TRY(argument.value->execute(interpreter, global_object)).release_value(); if (argument.is_spread) { - auto result = TRY(get_iterator_values(global_object, value, [&](Value iterator_value) -> Optional { + TRY(get_iterator_values(global_object, value, [&](Value iterator_value) -> Optional { list.append(iterator_value); return {}; })); diff --git a/Userland/Libraries/LibJS/Heap/Heap.cpp b/Userland/Libraries/LibJS/Heap/Heap.cpp index ca50bc28d0..1fef87d439 100644 --- a/Userland/Libraries/LibJS/Heap/Heap.cpp +++ b/Userland/Libraries/LibJS/Heap/Heap.cpp @@ -136,15 +136,32 @@ __attribute__((no_sanitize("address"))) void Heap::gather_conservative_roots(Has auto* raw_jmp_buf = reinterpret_cast(buf); + auto add_possible_value = [&](FlatPtr data) { + if constexpr (sizeof(FlatPtr*) == sizeof(Value)) { + // Because Value stores pointers in non-canonical form we have to check if the top bytes + // match any pointer-backed tag, in that case we have to extract the pointer to its + // canonical form and add that as a possible pointer. + if ((data & SHIFTED_IS_CELL_PATTERN) == SHIFTED_IS_CELL_PATTERN) + possible_pointers.set((u64)(((i64)data << 16) >> 16)); + else + possible_pointers.set(data); + } else { + static_assert((sizeof(Value) % sizeof(FlatPtr*)) == 0); + // In the 32-bit case we will look at the top and bottom part of Value separately we just + // add both the upper and lower bytes as possible pointers. + possible_pointers.set(data); + } + }; + for (size_t i = 0; i < ((size_t)sizeof(buf)) / sizeof(FlatPtr); i += sizeof(FlatPtr)) - possible_pointers.set(raw_jmp_buf[i]); + add_possible_value(raw_jmp_buf[i]); auto stack_reference = bit_cast(&dummy); auto& stack_info = m_vm.stack_info(); for (FlatPtr stack_address = stack_reference; stack_address < stack_info.top(); stack_address += sizeof(FlatPtr)) { auto data = *reinterpret_cast(stack_address); - possible_pointers.set(data); + add_possible_value(data); } HashTable all_live_heap_blocks; diff --git a/Userland/Libraries/LibJS/Runtime/PropertyKey.h b/Userland/Libraries/LibJS/Runtime/PropertyKey.h index a32c01dfc6..f4df2ca70f 100644 --- a/Userland/Libraries/LibJS/Runtime/PropertyKey.h +++ b/Userland/Libraries/LibJS/Runtime/PropertyKey.h @@ -33,7 +33,7 @@ public: if (value.is_symbol()) return PropertyKey { value.as_symbol() }; if (value.is_integral_number() && value.as_double() >= 0 && value.as_double() < NumericLimits::max()) - return value.as_u32(); + return static_cast(value.as_double()); return TRY(value.to_string(global_object)); } diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp index f210760c6a..77fc975649 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.cpp +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -41,10 +41,16 @@ namespace JS { static inline bool same_type_for_equality(Value const& lhs, Value const& rhs) { - if (lhs.type() == rhs.type()) + // If the top two bytes are identical then either: + // both are NaN boxed Values with the same type + // or they are doubles which happen to have the same top bytes. + if ((lhs.encoded() & TAG_EXTRACTION) == (rhs.encoded() & TAG_EXTRACTION)) return true; + if (lhs.is_number() && rhs.is_number()) return true; + + // One of the Values is not a number and they do not have the same tag return false; } @@ -210,7 +216,7 @@ ThrowCompletionOr Value::is_array(GlobalObject& global_object) const Array& Value::as_array() { VERIFY(is_object() && is(as_object())); - return static_cast(*m_value.as_object); + return static_cast(as_object()); } // 7.2.3 IsCallable ( argument ), https://tc39.es/ecma262/#sec-iscallable @@ -263,28 +269,28 @@ ThrowCompletionOr Value::is_regexp(GlobalObject& global_object) const // 13.5.3 The typeof Operator, https://tc39.es/ecma262/#sec-typeof-operator String Value::typeof() const { - switch (m_type) { - case Value::Type::Undefined: - return "undefined"; - case Value::Type::Null: - return "object"; - case Value::Type::Int32: - case Value::Type::Double: + if (is_number()) return "number"; - case Value::Type::String: + + switch (m_value.tag) { + case UNDEFINED_TAG: + return "undefined"; + case NULL_TAG: + return "object"; + case STRING_TAG: return "string"; - case Value::Type::Object: + case OBJECT_TAG: // B.3.7.3 Changes to the typeof Operator, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-typeof if (as_object().is_htmldda()) return "undefined"; if (is_function()) return "function"; return "object"; - case Value::Type::Boolean: + case BOOLEAN_TAG: return "boolean"; - case Value::Type::Symbol: + case SYMBOL_TAG: return "symbol"; - case Value::Type::BigInt: + case BIGINT_TAG: return "bigint"; default: VERIFY_NOT_REACHED(); @@ -293,26 +299,27 @@ String Value::typeof() const String Value::to_string_without_side_effects() const { - switch (m_type) { - case Type::Undefined: - return "undefined"; - case Type::Null: - return "null"; - case Type::Boolean: - return m_value.as_bool ? "true" : "false"; - case Type::Int32: - return String::number(m_value.as_i32); - case Type::Double: + if (is_double()) return double_to_string(m_value.as_double); - case Type::String: - return m_value.as_string->string(); - case Type::Symbol: - return m_value.as_symbol->to_string(); - case Type::BigInt: - return m_value.as_bigint->to_string(); - case Type::Object: + + switch (m_value.tag) { + case UNDEFINED_TAG: + return "undefined"; + case NULL_TAG: + return "null"; + case BOOLEAN_TAG: + return as_bool() ? "true" : "false"; + case INT32_TAG: + return String::number(as_i32()); + case STRING_TAG: + return as_string().string(); + case SYMBOL_TAG: + return as_symbol().to_string(); + case BIGINT_TAG: + return as_bigint().to_string(); + case OBJECT_TAG: return String::formatted("[object {}]", as_object().class_name()); - case Type::Accessor: + case ACCESSOR_TAG: return ""; default: VERIFY_NOT_REACHED(); @@ -330,25 +337,26 @@ ThrowCompletionOr Value::to_primitive_string(GlobalObject& glo // 7.1.17 ToString ( argument ), https://tc39.es/ecma262/#sec-tostring ThrowCompletionOr Value::to_string(GlobalObject& global_object) const { - auto& vm = global_object.vm(); - switch (m_type) { - case Type::Undefined: - return "undefined"sv; - case Type::Null: - return "null"sv; - case Type::Boolean: - return m_value.as_bool ? "true"sv : "false"sv; - case Type::Int32: - return String::number(m_value.as_i32); - case Type::Double: + if (is_double()) return double_to_string(m_value.as_double); - case Type::String: - return m_value.as_string->string(); - case Type::Symbol: + + auto& vm = global_object.vm(); + switch (m_value.tag) { + case UNDEFINED_TAG: + return "undefined"sv; + case NULL_TAG: + return "null"sv; + case BOOLEAN_TAG: + return as_bool() ? "true"sv : "false"sv; + case INT32_TAG: + return String::number(as_i32()); + case STRING_TAG: + return as_string().string(); + case SYMBOL_TAG: return vm.throw_completion(global_object, ErrorType::Convert, "symbol", "string"); - case Type::BigInt: - return m_value.as_bigint->big_integer().to_base(10); - case Type::Object: { + case BIGINT_TAG: + return as_bigint().big_integer().to_base(10); + case OBJECT_TAG: { auto primitive_value = TRY(to_primitive(global_object, PreferredType::String)); return primitive_value.to_string(global_object); } @@ -359,8 +367,8 @@ ThrowCompletionOr Value::to_string(GlobalObject& global_object) const ThrowCompletionOr Value::to_utf16_string(GlobalObject& global_object) const { - if (m_type == Type::String) - return m_value.as_string->utf16_string(); + if (is_string()) + return as_string().utf16_string(); auto utf8_string = TRY(to_string(global_object)); return Utf16String(utf8_string); @@ -369,27 +377,29 @@ ThrowCompletionOr Value::to_utf16_string(GlobalObject& global_objec // 7.1.2 ToBoolean ( argument ), https://tc39.es/ecma262/#sec-toboolean bool Value::to_boolean() const { - switch (m_type) { - case Type::Undefined: - case Type::Null: - return false; - case Type::Boolean: - return m_value.as_bool; - case Type::Int32: - return m_value.as_i32 != 0; - case Type::Double: + if (is_double()) { if (is_nan()) return false; return m_value.as_double != 0; - case Type::String: - return !m_value.as_string->is_empty(); - case Type::Symbol: + } + + switch (m_value.tag) { + case UNDEFINED_TAG: + case NULL_TAG: + return false; + case BOOLEAN_TAG: + return as_bool(); + case INT32_TAG: + return as_i32() != 0; + case STRING_TAG: + return !as_string().is_empty(); + case SYMBOL_TAG: return true; - case Type::BigInt: - return m_value.as_bigint->big_integer() != BIGINT_ZERO; - case Type::Object: + case BIGINT_TAG: + return as_bigint().big_integer() != BIGINT_ZERO; + case OBJECT_TAG: // B.3.7.1 Changes to ToBoolean, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-to-boolean - if (m_value.as_object->is_htmldda()) + if (as_object().is_htmldda()) return false; return true; default: @@ -432,22 +442,23 @@ ThrowCompletionOr Value::to_primitive(GlobalObject& global_object, Prefer // 7.1.18 ToObject ( argument ), https://tc39.es/ecma262/#sec-toobject ThrowCompletionOr Value::to_object(GlobalObject& global_object) const { - switch (m_type) { - case Type::Undefined: - case Type::Null: - return global_object.vm().throw_completion(global_object, ErrorType::ToObjectNullOrUndefined); - case Type::Boolean: - return BooleanObject::create(global_object, m_value.as_bool); - case Type::Int32: - case Type::Double: + VERIFY(!is_empty()); + if (is_number()) return NumberObject::create(global_object, as_double()); - case Type::String: - return StringObject::create(global_object, *m_value.as_string, *global_object.string_prototype()); - case Type::Symbol: - return SymbolObject::create(global_object, *m_value.as_symbol); - case Type::BigInt: - return BigIntObject::create(global_object, *m_value.as_bigint); - case Type::Object: + + switch (m_value.tag) { + case UNDEFINED_TAG: + case NULL_TAG: + return global_object.vm().throw_completion(global_object, ErrorType::ToObjectNullOrUndefined); + case BOOLEAN_TAG: + return BooleanObject::create(global_object, as_bool()); + case STRING_TAG: + return StringObject::create(global_object, const_cast(as_string()), *global_object.string_prototype()); + case SYMBOL_TAG: + return SymbolObject::create(global_object, const_cast(as_symbol())); + case BIGINT_TAG: + return BigIntObject::create(global_object, const_cast(as_bigint())); + case OBJECT_TAG: return &const_cast(as_object()); default: VERIFY_NOT_REACHED(); @@ -466,17 +477,18 @@ FLATTEN ThrowCompletionOr Value::to_numeric(GlobalObject& global_object) // 7.1.4 ToNumber ( argument ), https://tc39.es/ecma262/#sec-tonumber ThrowCompletionOr Value::to_number(GlobalObject& global_object) const { - switch (m_type) { - case Type::Undefined: - return js_nan(); - case Type::Null: - return Value(0); - case Type::Boolean: - return Value(m_value.as_bool ? 1 : 0); - case Type::Int32: - case Type::Double: + VERIFY(!is_empty()); + if (is_number()) return *this; - case Type::String: { + + switch (m_value.tag) { + case UNDEFINED_TAG: + return js_nan(); + case NULL_TAG: + return Value(0); + case BOOLEAN_TAG: + return Value(as_bool() ? 1 : 0); + case STRING_TAG: { String string = Utf8View(as_string().string()).trim(whitespace_characters, AK::TrimMode::Both).as_string(); if (string.is_empty()) return Value(0); @@ -490,11 +502,11 @@ ThrowCompletionOr Value::to_number(GlobalObject& global_object) const return js_nan(); return Value(parsed_double); } - case Type::Symbol: + case SYMBOL_TAG: return global_object.vm().throw_completion(global_object, ErrorType::Convert, "symbol", "number"); - case Type::BigInt: + case BIGINT_TAG: return global_object.vm().throw_completion(global_object, ErrorType::Convert, "BigInt", "number"); - case Type::Object: { + case OBJECT_TAG: { auto primitive = TRY(to_primitive(global_object, PreferredType::Number)); return primitive.to_number(global_object); } @@ -508,22 +520,24 @@ ThrowCompletionOr Value::to_bigint(GlobalObject& global_object) const { auto& vm = global_object.vm(); auto primitive = TRY(to_primitive(global_object, PreferredType::Number)); - switch (primitive.type()) { - case Type::Undefined: + + VERIFY(!primitive.is_empty()); + if (primitive.is_number()) + return vm.throw_completion(global_object, ErrorType::Convert, "number", "BigInt"); + + switch (primitive.m_value.tag) { + case UNDEFINED_TAG: return vm.throw_completion(global_object, ErrorType::Convert, "undefined", "BigInt"); - case Type::Null: + case NULL_TAG: return vm.throw_completion(global_object, ErrorType::Convert, "null", "BigInt"); - case Type::Boolean: { + case BOOLEAN_TAG: { auto value = primitive.as_bool() ? 1 : 0; return js_bigint(vm, Crypto::SignedBigInteger { value }); } - case Type::BigInt: + case BIGINT_TAG: return &primitive.as_bigint(); - case Type::Int32: - case Type::Double: - return vm.throw_completion(global_object, ErrorType::Convert, "number", "BigInt"); - case Type::String: { - // 1. Let n be StringToBigInt(prim). + case STRING_TAG: { + // 1. Let n be ! StringToBigInt(prim). auto bigint = primitive.string_to_bigint(global_object); // 2. If n is undefined, throw a SyntaxError exception. @@ -533,7 +547,7 @@ ThrowCompletionOr Value::to_bigint(GlobalObject& global_object) const // 3. Return n. return bigint.release_value(); } - case Type::Symbol: + case SYMBOL_TAG: return vm.throw_completion(global_object, ErrorType::Convert, "symbol", "BigInt"); default: VERIFY_NOT_REACHED(); @@ -632,7 +646,7 @@ ThrowCompletionOr Value::to_double(GlobalObject& global_object) const // 7.1.19 ToPropertyKey ( argument ), https://tc39.es/ecma262/#sec-topropertykey ThrowCompletionOr Value::to_property_key(GlobalObject& global_object) const { - if (type() == Type::Int32 && as_i32() >= 0) + if (is_int32() && as_i32() >= 0) return PropertyKey { as_i32() }; auto key = TRY(to_primitive(global_object, PreferredType::String)); if (key.is_symbol()) @@ -642,7 +656,7 @@ ThrowCompletionOr Value::to_property_key(GlobalObject& global_objec ThrowCompletionOr Value::to_i32_slow_case(GlobalObject& global_object) const { - VERIFY(type() != Type::Int32); + VERIFY(!is_int32()); double value = TRY(to_number(global_object)).as_double(); if (!isfinite(value) || value == 0) return 0; @@ -659,8 +673,8 @@ ThrowCompletionOr Value::to_i32_slow_case(GlobalObject& global_object) cons ThrowCompletionOr Value::to_i32(GlobalObject& global_object) const { - if (m_type == Type::Int32) - return m_value.as_i32; + if (is_int32()) + return as_i32(); return to_i32_slow_case(global_object); } @@ -872,7 +886,7 @@ ThrowCompletionOr Value::get_method(GlobalObject& global_object // 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators ThrowCompletionOr greater_than(GlobalObject& global_object, Value lhs, Value rhs) { - if (lhs.type() == Value::Type::Int32 && rhs.type() == Value::Type::Int32) + if (lhs.is_int32() && rhs.is_int32()) return lhs.as_i32() > rhs.as_i32(); TriState relation = TRY(is_less_than(global_object, lhs, rhs, false)); @@ -884,7 +898,7 @@ ThrowCompletionOr greater_than(GlobalObject& global_object, Value lhs, Va // 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators ThrowCompletionOr greater_than_equals(GlobalObject& global_object, Value lhs, Value rhs) { - if (lhs.type() == Value::Type::Int32 && rhs.type() == Value::Type::Int32) + if (lhs.is_int32() && rhs.is_int32()) return lhs.as_i32() >= rhs.as_i32(); TriState relation = TRY(is_less_than(global_object, lhs, rhs, true)); @@ -896,7 +910,7 @@ ThrowCompletionOr greater_than_equals(GlobalObject& global_object, Value // 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators ThrowCompletionOr less_than(GlobalObject& global_object, Value lhs, Value rhs) { - if (lhs.type() == Value::Type::Int32 && rhs.type() == Value::Type::Int32) + if (lhs.is_int32() && rhs.is_int32()) return lhs.as_i32() < rhs.as_i32(); TriState relation = TRY(is_less_than(global_object, lhs, rhs, true)); @@ -908,7 +922,7 @@ ThrowCompletionOr less_than(GlobalObject& global_object, Value lhs, Value // 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators ThrowCompletionOr less_than_equals(GlobalObject& global_object, Value lhs, Value rhs) { - if (lhs.type() == Value::Type::Int32 && rhs.type() == Value::Type::Int32) + if (lhs.is_int32() && rhs.is_int32()) return lhs.as_i32() <= rhs.as_i32(); TriState relation = TRY(is_less_than(global_object, lhs, rhs, false)); @@ -1078,7 +1092,7 @@ ThrowCompletionOr unsigned_right_shift(GlobalObject& global_object, Value ThrowCompletionOr add(GlobalObject& global_object, Value lhs, Value rhs) { if (both_number(lhs, rhs)) { - if (lhs.type() == Value::Type::Int32 && rhs.type() == Value::Type::Int32) { + if (lhs.is_int32() && rhs.is_int32()) { Checked result; result = MUST(lhs.to_i32(global_object)); result += MUST(rhs.to_i32(global_object)); @@ -1112,8 +1126,12 @@ ThrowCompletionOr sub(GlobalObject& global_object, Value lhs, Value rhs) auto& vm = global_object.vm(); auto lhs_numeric = TRY(lhs.to_numeric(global_object)); auto rhs_numeric = TRY(rhs.to_numeric(global_object)); - if (both_number(lhs_numeric, rhs_numeric)) - return Value(lhs_numeric.as_double() - rhs_numeric.as_double()); + if (both_number(lhs_numeric, rhs_numeric)) { + double lhsd = lhs_numeric.as_double(); + double rhsd = rhs_numeric.as_double(); + double interm = lhsd - rhsd; + return Value(interm); + } if (both_bigint(lhs_numeric, rhs_numeric)) return Value(js_bigint(vm, lhs_numeric.as_bigint().big_integer().minus(rhs_numeric.as_bigint().big_integer()))); return vm.throw_completion(global_object, ErrorType::BigIntBadOperatorOtherType, "subtraction"); @@ -1182,7 +1200,7 @@ static Value exp_double(Value base, Value exponent) if (base.is_positive_infinity()) return exponent.as_double() > 0 ? js_infinity() : Value(0); if (base.is_negative_infinity()) { - auto is_odd_integral_number = exponent.is_integral_number() && (exponent.as_i32() % 2 != 0); + auto is_odd_integral_number = exponent.is_integral_number() && (static_cast(exponent.as_double()) % 2 != 0); if (exponent.as_double() > 0) return is_odd_integral_number ? js_negative_infinity() : js_infinity(); else @@ -1191,7 +1209,7 @@ static Value exp_double(Value base, Value exponent) if (base.is_positive_zero()) return exponent.as_double() > 0 ? Value(0) : js_infinity(); if (base.is_negative_zero()) { - auto is_odd_integral_number = exponent.is_integral_number() && (exponent.as_i32() % 2 != 0); + auto is_odd_integral_number = exponent.is_integral_number() && (static_cast(exponent.as_double()) % 2 != 0); if (exponent.as_double() > 0) return is_odd_integral_number ? Value(-0.0) : Value(0); else @@ -1342,21 +1360,10 @@ bool same_value_non_numeric(Value lhs, Value rhs) VERIFY(!lhs.is_number() && !lhs.is_bigint()); VERIFY(same_type_for_equality(lhs, rhs)); - switch (lhs.type()) { - case Value::Type::Undefined: - case Value::Type::Null: - return true; - case Value::Type::String: + if (lhs.is_string()) return lhs.as_string().string() == rhs.as_string().string(); - case Value::Type::Symbol: - return &lhs.as_symbol() == &rhs.as_symbol(); - case Value::Type::Boolean: - return lhs.as_bool() == rhs.as_bool(); - case Value::Type::Object: - return &lhs.as_object() == &rhs.as_object(); - default: - VERIFY_NOT_REACHED(); - } + + return lhs.m_value.encoded == rhs.m_value.encoded; } // 7.2.15 IsStrictlyEqual ( x, y ), https://tc39.es/ecma262/#sec-isstrictlyequal diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h index b23aa93eb0..47beb74ac4 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.h +++ b/Userland/Libraries/LibJS/Runtime/Value.h @@ -28,40 +28,119 @@ static constexpr u64 NEGATIVE_ZERO_BITS = ((u64)1 << 63); namespace JS { +static_assert(sizeof(double) == 8); +static_assert(sizeof(void*) == sizeof(double) || sizeof(void*) == sizeof(u32)); +// To make our Value representation compact we can use the fact that IEEE +// doubles have a lot (2^52 - 2) of NaN bit patterns. The canonical form being +// just 0x7FF8000000000000 i.e. sign = 0 exponent is all ones and the top most +// bit of the mantissa set. +static constexpr u64 CANON_NAN_BITS = bit_cast(__builtin_nan("")); +static_assert(CANON_NAN_BITS == 0x7FF8000000000000); +// (Unfortunately all the other values are valid so we have to convert any +// incoming NaNs to this pattern although in practice it seems only the negative +// version of these CANON_NAN_BITS) +// +/- Infinity are represented by a full exponent but without any bits of the +// mantissa set. +static constexpr u64 POSITIVE_INFINITY_BITS = bit_cast(__builtin_huge_val()); +static constexpr u64 NEGATIVE_INFINITY_BITS = bit_cast(-__builtin_huge_val()); +static_assert(POSITIVE_INFINITY_BITS == 0x7FF0000000000000); +static_assert(NEGATIVE_INFINITY_BITS == 0xFFF0000000000000); +// However as long as any bit is set in the mantissa with the exponent of all +// ones this value is a NaN, and it even ignores the sign bit. +static_assert(isnan(bit_cast(0x7FF0000000000001))); +static_assert(isnan(bit_cast(0xFFF0000000040000))); +// This means we can use all of these NaNs to store all other options for Value. +// To make sure all of these other representations we use 0x7FF8 as the base top +// 2 bytes which ensures the value is always a NaN. +static constexpr u64 BASE_TAG = 0x7FF8; +// This leaves the sign bit and the three lower bits for tagging a value and then +// 48 bits of potential payload. +// First the pointer backed types (Object, String etc.), to signify this category +// and make stack scanning easier we use the sign bit (top most bit) of 1 to +// signify that it is a pointer backed type. +static constexpr u64 IS_CELL_BIT = 0x8000 | BASE_TAG; +// On all current 64-bit systems this code runs pointer actually only use the +// lowest 6 bytes which fits neatly into our NaN payload with the top two bytes +// left over for marking it as a NaN and tagging the type. +// Note that we do need to take care when extracting the pointer value but this +// is explained in the extract_pointer method. + +// This leaves us 3 bits to tag the type of pointer: +static constexpr u64 OBJECT_TAG = 0b001 | IS_CELL_BIT; +static constexpr u64 STRING_TAG = 0b010 | IS_CELL_BIT; +static constexpr u64 SYMBOL_TAG = 0b011 | IS_CELL_BIT; +static constexpr u64 ACCESSOR_TAG = 0b100 | IS_CELL_BIT; +static constexpr u64 BIGINT_TAG = 0b101 | IS_CELL_BIT; + +// We can then by extracting the top 13 bits quickly check if a Value is +// pointer backed. +static constexpr u64 IS_CELL_PATTERN = 0xFFF8ULL; +static_assert((OBJECT_TAG & IS_CELL_PATTERN) == IS_CELL_PATTERN); +static_assert((STRING_TAG & IS_CELL_PATTERN) == IS_CELL_PATTERN); +static_assert((CANON_NAN_BITS & IS_CELL_PATTERN) != IS_CELL_PATTERN); +static_assert((NEGATIVE_INFINITY_BITS & IS_CELL_PATTERN) != 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 | BASE_TAG; +static constexpr u64 NULL_TAG = 0b111 | BASE_TAG; +static constexpr u64 BOOLEAN_TAG = 0b001 | BASE_TAG; +static constexpr u64 INT32_TAG = 0b010 | BASE_TAG; +static constexpr u64 EMPTY_TAG = 0b011 | 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 TAG_EXTRACTION = 0xFFFF000000000000; +static constexpr u64 TAG_SHIFT = 48; +static constexpr u64 SHIFTED_INT32_TAG = INT32_TAG << TAG_SHIFT; +static constexpr u64 SHIFTED_IS_CELL_PATTERN = IS_CELL_PATTERN << 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: - enum class Type { - Empty, - Undefined, - Null, - Int32, - Double, - String, - Object, - Boolean, - Symbol, - Accessor, - BigInt, - }; - enum class PreferredType { Default, String, Number, }; - bool is_empty() const { return m_type == Type::Empty; } - bool is_undefined() const { return m_type == Type::Undefined; } - bool is_null() const { return m_type == Type::Null; } - bool is_number() const { return m_type == Type::Int32 || m_type == Type::Double; } - bool is_string() const { return m_type == Type::String; } - bool is_object() const { return m_type == Type::Object; } - bool is_boolean() const { return m_type == Type::Boolean; } - bool is_symbol() const { return m_type == Type::Symbol; } - bool is_accessor() const { return m_type == Type::Accessor; }; - bool is_bigint() const { return m_type == Type::BigInt; }; - bool is_nullish() const { return is_null() || is_undefined(); } - bool is_cell() const { return is_string() || is_accessor() || is_object() || is_bigint() || is_symbol(); } + bool is_empty() const { return m_value.tag == EMPTY_TAG; } + bool is_undefined() const { return m_value.tag == UNDEFINED_TAG; } + bool is_null() const { return m_value.tag == NULL_TAG; } + 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; } + bool is_cell() const { return (m_value.tag & IS_CELL_PATTERN) == IS_CELL_PATTERN; } ThrowCompletionOr is_array(GlobalObject&) const; bool is_function() const; bool is_constructor() const; @@ -69,84 +148,73 @@ public: bool is_nan() const { - if (type() == Type::Int32) - return false; - return is_number() && __builtin_isnan(as_double()); + return m_value.encoded == CANON_NAN_BITS; } bool is_infinity() const { - if (type() == Type::Int32) - return false; - return is_number() && __builtin_isinf(as_double()); + static_assert(NEGATIVE_INFINITY_BITS == (0x1ULL << 63 | POSITIVE_INFINITY_BITS)); + return (0x1ULL << 63 | m_value.encoded) == NEGATIVE_INFINITY_BITS; } bool is_positive_infinity() const { - if (type() == Type::Int32) - return false; - return is_number() && __builtin_isinf_sign(as_double()) > 0; + return m_value.encoded == POSITIVE_INFINITY_BITS; } bool is_negative_infinity() const { - if (type() == Type::Int32) - return false; - return is_number() && __builtin_isinf_sign(as_double()) < 0; + return m_value.encoded == NEGATIVE_INFINITY_BITS; } bool is_positive_zero() const { - if (type() == Type::Int32) - return as_i32() == 0; - return is_number() && bit_cast(as_double()) == 0; + return m_value.encoded == 0 || (is_int32() && as_i32() == 0); } bool is_negative_zero() const { - if (type() == Type::Int32) - return false; - return is_number() && bit_cast(as_double()) == NEGATIVE_ZERO_BITS; + return m_value.encoded == NEGATIVE_ZERO_BITS; } bool is_integral_number() const { - if (type() == Type::Int32) + if (is_int32()) return true; return is_finite_number() && trunc(as_double()) == as_double(); } bool is_finite_number() const { - if (type() == Type::Int32) - return true; if (!is_number()) return false; - auto number = as_double(); - return !__builtin_isnan(number) && !__builtin_isinf(number); + if (is_int32()) + return true; + return !is_nan() && !is_infinity(); } Value() - : m_type(Type::Empty) + : Value(EMPTY_TAG << TAG_SHIFT, (u64)0) { } template requires(IsSameIgnoringCV) explicit Value(T value) - : m_type(Type::Boolean) + : Value(BOOLEAN_TAG << TAG_SHIFT, (u64)value) { - m_value.as_bool = 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) { - m_type = Type::Int32; - m_value.as_i32 = static_cast(value); + VERIFY(!(SHIFTED_INT32_TAG & (static_cast(value) & 0xFFFFFFFFul))); + m_value.encoded = SHIFTED_INT32_TAG | (static_cast(value) & 0xFFFFFFFFul); } else { - m_type = Type::Double; - m_value.as_double = value; + if (isnan(value)) [[unlikely]] + m_value.encoded = CANON_NAN_BITS; + else + m_value.as_double = value; } } @@ -159,143 +227,130 @@ public: { if (value > NumericLimits::max()) { m_value.as_double = static_cast(value); - m_type = Type::Double; } else { - m_value.as_i32 = static_cast(value); - m_type = Type::Int32; + VERIFY(!(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 { + VERIFY(!(SHIFTED_INT32_TAG & (static_cast(value) & 0xFFFFFFFFul))); + m_value.encoded = SHIFTED_INT32_TAG | (static_cast(value) & 0xFFFFFFFFul); } } explicit Value(i32 value) - : m_type(Type::Int32) + : Value(SHIFTED_INT32_TAG, (u32)value) { - m_value.as_i32 = value; } Value(Object const* object) - : m_type(object ? Type::Object : Type::Null) + : Value(object ? (OBJECT_TAG << TAG_SHIFT) : (NULL_TAG << TAG_SHIFT), reinterpret_cast(object)) { - m_value.as_object = const_cast(object); } Value(PrimitiveString const* string) - : m_type(Type::String) + : Value(STRING_TAG << TAG_SHIFT, reinterpret_cast(string)) { - m_value.as_string = const_cast(string); } Value(Symbol const* symbol) - : m_type(Type::Symbol) + : Value(SYMBOL_TAG << TAG_SHIFT, reinterpret_cast(symbol)) { - m_value.as_symbol = const_cast(symbol); } Value(Accessor const* accessor) - : m_type(Type::Accessor) + : Value(ACCESSOR_TAG << TAG_SHIFT, reinterpret_cast(accessor)) { - m_value.as_accessor = const_cast(accessor); } Value(BigInt const* bigint) - : m_type(Type::BigInt) - { - m_value.as_bigint = const_cast(bigint); - } - - explicit Value(Type type) - : m_type(type) + : Value(BIGINT_TAG << TAG_SHIFT, reinterpret_cast(bigint)) { } - Type type() const { return m_type; } - double as_double() const { VERIFY(is_number()); - if (m_type == Type::Int32) - return m_value.as_i32; + if (is_int32()) + return static_cast(m_value.encoded); return m_value.as_double; } bool as_bool() const { - VERIFY(type() == Type::Boolean); - return m_value.as_bool; + VERIFY(is_boolean()); + return static_cast(m_value.encoded & 0x1); } Object& as_object() { - VERIFY(type() == Type::Object); - return *m_value.as_object; + VERIFY(is_object()); + return *extract_pointer(); } Object const& as_object() const { - VERIFY(type() == Type::Object); - return *m_value.as_object; + VERIFY(is_object()); + return *extract_pointer(); } PrimitiveString& as_string() { VERIFY(is_string()); - return *m_value.as_string; + return *extract_pointer(); } PrimitiveString const& as_string() const { VERIFY(is_string()); - return *m_value.as_string; + return *extract_pointer(); } Symbol& as_symbol() { VERIFY(is_symbol()); - return *m_value.as_symbol; + return *extract_pointer(); } Symbol const& as_symbol() const { VERIFY(is_symbol()); - return *m_value.as_symbol; + return *extract_pointer(); } Cell& as_cell() { VERIFY(is_cell()); - return *m_value.as_cell; + return *extract_pointer(); } Accessor& as_accessor() { VERIFY(is_accessor()); - return *m_value.as_accessor; + return *extract_pointer(); + } + + BigInt const& as_bigint() const + { + VERIFY(is_bigint()); + return *extract_pointer(); } BigInt& as_bigint() { VERIFY(is_bigint()); - return *m_value.as_bigint; + return *extract_pointer(); } Array& as_array(); FunctionObject& as_function(); FunctionObject const& as_function() const; - // FIXME: These two conversions are wrong for JS, and seem likely to be footguns - i32 as_i32() const - { - if (m_type == Type::Int32) - return m_value.as_i32; - return static_cast(as_double()); - } - u32 as_u32() const - { - if (m_type == Type::Int32 && m_value.as_i32 >= 0) - return m_value.as_i32; - VERIFY(as_double() >= 0); - return (u32)min(as_double(), (double)NumericLimits::max()); - } - u64 encoded() const { return m_value.encoded; } ThrowCompletionOr to_string(GlobalObject&) const; @@ -343,35 +398,86 @@ public: [[nodiscard]] ALWAYS_INLINE ThrowCompletionOr invoke(GlobalObject& global_object, PropertyKey const& property_key, Args... args); private: - Type m_type { Type::Empty }; + Value(u64 tag, u64 val) + { + VERIFY((tag & ~TAG_EXTRACTION) == 0); + VERIFY(!(tag & val)); + m_value.encoded = tag | val; + } + + template + Value(u64 tag, PointerType const* ptr) + { + VERIFY((tag & ~TAG_EXTRACTION) == 0); + VERIFY((tag & TAG_EXTRACTION) != 0); + // Cell tag bit must be set or this is a nullptr + VERIFY((tag & 0x8000000000000000ul) == 0x8000000000000000ul || !ptr); + + if constexpr (sizeof(PointerType*) < sizeof(u64)) { + m_value.encoded = tag | reinterpret_cast(ptr); + } else { + VERIFY(!(reinterpret_cast(ptr) & TAG_EXTRACTION)); + m_value.encoded = tag | reinterpret_cast(ptr); + } + } + + // 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 & CANON_NAN_BITS) != CANON_NAN_BITS || (m_value.encoded == 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); + } + + template + PointerType* extract_pointer() const + { + VERIFY(is_cell()); + + // For 32-bit system the pointer fully fits so we can just return it directly. + if constexpr (sizeof(PointerType*) < sizeof(u64)) + return reinterpret_cast(static_cast(m_value.encoded & 0xffffffff)); + + // For x86_64 the top 16 bits should be sign extending the "real" top bit (47th). + // So first shift the top 16 bits away then using the right shift it sign extends the top 16 bits. + u64 ptr_val = (u64)(((i64)(m_value.encoded << 16)) >> 16); + return reinterpret_cast(ptr_val); + } [[nodiscard]] ThrowCompletionOr invoke_internal(GlobalObject& global_object, PropertyKey const&, Optional> arguments); ThrowCompletionOr to_i32_slow_case(GlobalObject&) const; union { - bool as_bool; - i32 as_i32; double as_double; - PrimitiveString* as_string; - Symbol* as_symbol; - Object* as_object; - Cell* as_cell; - Accessor* as_accessor; - BigInt* as_bigint; - + struct { + u64 payload : 48; + u64 tag : 16; + }; u64 encoded; } m_value { .encoded = 0 }; + + friend Value js_undefined(); + friend Value js_null(); + friend ThrowCompletionOr greater_than(GlobalObject&, Value lhs, Value rhs); + friend ThrowCompletionOr greater_than_equals(GlobalObject&, Value lhs, Value rhs); + friend ThrowCompletionOr less_than(GlobalObject&, Value lhs, Value rhs); + friend ThrowCompletionOr less_than_equals(GlobalObject&, Value lhs, Value rhs); + friend ThrowCompletionOr add(GlobalObject&, Value lhs, Value rhs); + friend bool same_value_non_numeric(Value lhs, Value rhs); }; inline Value js_undefined() { - return Value(Value::Type::Undefined); + return Value(UNDEFINED_TAG << TAG_SHIFT, (u64)0); } inline Value js_null() { - return Value(Value::Type::Null); + return Value(NULL_TAG << TAG_SHIFT, (u64)0); } inline Value js_nan() @@ -433,6 +539,8 @@ inline bool Value::operator==(Value const& value) const { return same_value(*thi namespace AK { +static_assert(sizeof(JS::Value) == sizeof(double)); + template<> class Optional { template diff --git a/Userland/Libraries/LibWeb/Bindings/IDLAbstractOperations.cpp b/Userland/Libraries/LibWeb/Bindings/IDLAbstractOperations.cpp index 054cca4816..28a106cb52 100644 --- a/Userland/Libraries/LibWeb/Bindings/IDLAbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/Bindings/IDLAbstractOperations.cpp @@ -74,7 +74,7 @@ ErrorOr get_buffer_source_copy(JS::Object const& buffer_source) // 9. For i in the range offset to offset + length − 1, inclusive, set bytes[i − offset] to ! GetValueFromBuffer(esArrayBuffer, i, Uint8, true, Unordered). for (u64 i = offset; i <= offset + length - 1; ++i) { auto value = es_array_buffer->get_value(i, true, JS::ArrayBuffer::Unordered); - bytes[i - offset] = (u8)value.as_u32(); + bytes[i - offset] = static_cast(value.as_double()); } // 10. Return bytes.