LibJS: Make typeof a lot faster by caching all possible results

The typeof operator has a very small set of possible resulting strings,
so let's make it much faster by caching those strings on the VM.

~8x speed-up on this microbenchmark:

    for (let i = 0; i < 10_000_000; ++i) {
        typeof i;
    }
This commit is contained in:
Andreas Kling 2024-07-23 09:42:00 +02:00 committed by Andreas Kling
commit d0b11af387
Notes: github-actions[bot] 2024-07-23 09:48:32 +00:00
5 changed files with 46 additions and 16 deletions

View file

@ -1193,7 +1193,7 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
if (!succeeded && vm.in_strict_mode()) { if (!succeeded && vm.in_strict_mode()) {
if (base.is_object()) if (base.is_object())
return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, base.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, base.to_string_without_side_effects());
return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof(), base.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof(vm)->utf8_string(), base.to_string_without_side_effects());
} }
break; break;
} }
@ -2018,7 +2018,7 @@ static ThrowCompletionOr<Value> not_(VM&, Value value)
static ThrowCompletionOr<Value> typeof_(VM& vm, Value value) static ThrowCompletionOr<Value> typeof_(VM& vm, Value value)
{ {
return PrimitiveString::create(vm, value.typeof()); return value.typeof(vm);
} }
#define JS_DEFINE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \ #define JS_DEFINE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \
@ -2873,7 +2873,7 @@ ThrowCompletionOr<void> TypeofBinding::execute_impl(Bytecode::Interpreter& inter
environment = environment->outer_environment(); environment = environment->outer_environment();
if (!environment->is_permanently_screwed_by_eval()) { if (!environment->is_permanently_screwed_by_eval()) {
auto value = TRY(static_cast<DeclarativeEnvironment const&>(*environment).get_binding_value_direct(vm, m_cache.index)); auto value = TRY(static_cast<DeclarativeEnvironment const&>(*environment).get_binding_value_direct(vm, m_cache.index));
interpreter.set(dst(), PrimitiveString::create(vm, value.typeof())); interpreter.set(dst(), value.typeof(vm));
return {}; return {};
} }
m_cache = {}; m_cache = {};
@ -2897,7 +2897,7 @@ ThrowCompletionOr<void> TypeofBinding::execute_impl(Bytecode::Interpreter& inter
// 4. NOTE: This step is replaced in section B.3.6.3. // 4. NOTE: This step is replaced in section B.3.6.3.
// 5. Return a String according to Table 41. // 5. Return a String according to Table 41.
interpreter.set(dst(), PrimitiveString::create(vm, value.typeof())); interpreter.set(dst(), value.typeof(vm));
return {}; return {};
} }

View file

@ -70,6 +70,17 @@ VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages)
m_empty_string = m_heap.allocate_without_realm<PrimitiveString>(String {}); m_empty_string = m_heap.allocate_without_realm<PrimitiveString>(String {});
typeof_strings = {
.number = m_heap.allocate_without_realm<PrimitiveString>("number"),
.undefined = m_heap.allocate_without_realm<PrimitiveString>("undefined"),
.object = m_heap.allocate_without_realm<PrimitiveString>("object"),
.string = m_heap.allocate_without_realm<PrimitiveString>("string"),
.symbol = m_heap.allocate_without_realm<PrimitiveString>("symbol"),
.boolean = m_heap.allocate_without_realm<PrimitiveString>("boolean"),
.bigint = m_heap.allocate_without_realm<PrimitiveString>("bigint"),
.function = m_heap.allocate_without_realm<PrimitiveString>("function"),
};
for (size_t i = 0; i < single_ascii_character_strings.size(); ++i) for (size_t i = 0; i < single_ascii_character_strings.size(); ++i)
m_single_ascii_character_strings[i] = m_heap.allocate_without_realm<PrimitiveString>(single_ascii_character_strings[i]); m_single_ascii_character_strings[i] = m_heap.allocate_without_realm<PrimitiveString>(single_ascii_character_strings[i]);
@ -192,6 +203,15 @@ void VM::gather_roots(HashMap<Cell*, HeapRoot>& roots)
for (auto string : m_single_ascii_character_strings) for (auto string : m_single_ascii_character_strings)
roots.set(string, HeapRoot { .type = HeapRoot::Type::VM }); roots.set(string, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.number, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.undefined, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.object, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.string, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.symbol, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.boolean, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.bigint, HeapRoot { .type = HeapRoot::Type::VM });
roots.set(typeof_strings.function, HeapRoot { .type = HeapRoot::Type::VM });
#define __JS_ENUMERATE(SymbolName, snake_name) \ #define __JS_ENUMERATE(SymbolName, snake_name) \
roots.set(m_well_known_symbols.snake_name, HeapRoot { .type = HeapRoot::Type::VM }); roots.set(m_well_known_symbols.snake_name, HeapRoot { .type = HeapRoot::Type::VM });
JS_ENUMERATE_WELL_KNOWN_SYMBOLS JS_ENUMERATE_WELL_KNOWN_SYMBOLS

View file

@ -217,6 +217,16 @@ public:
Object& get_global_object(); Object& get_global_object();
CommonPropertyNames names; CommonPropertyNames names;
struct {
GCPtr<PrimitiveString> number;
GCPtr<PrimitiveString> undefined;
GCPtr<PrimitiveString> object;
GCPtr<PrimitiveString> string;
GCPtr<PrimitiveString> symbol;
GCPtr<PrimitiveString> boolean;
GCPtr<PrimitiveString> bigint;
GCPtr<PrimitiveString> function;
} typeof_strings;
void run_queued_promise_jobs(); void run_queued_promise_jobs();
void enqueue_promise_job(NonnullGCPtr<HeapFunction<ThrowCompletionOr<Value>()>> job, Realm*); void enqueue_promise_job(NonnullGCPtr<HeapFunction<ThrowCompletionOr<Value>()>> job, Realm*);

View file

@ -313,42 +313,42 @@ ThrowCompletionOr<bool> Value::is_regexp(VM& vm) const
} }
// 13.5.3 The typeof Operator, https://tc39.es/ecma262/#sec-typeof-operator // 13.5.3 The typeof Operator, https://tc39.es/ecma262/#sec-typeof-operator
StringView Value::typeof() const NonnullGCPtr<PrimitiveString> Value::typeof(VM& vm) const
{ {
// 9. If val is a Number, return "number". // 9. If val is a Number, return "number".
if (is_number()) if (is_number())
return "number"sv; return *vm.typeof_strings.number;
switch (m_value.tag) { switch (m_value.tag) {
// 4. If val is undefined, return "undefined". // 4. If val is undefined, return "undefined".
case UNDEFINED_TAG: case UNDEFINED_TAG:
return "undefined"sv; return *vm.typeof_strings.undefined;
// 5. If val is null, return "object". // 5. If val is null, return "object".
case NULL_TAG: case NULL_TAG:
return "object"sv; return *vm.typeof_strings.object;
// 6. If val is a String, return "string". // 6. If val is a String, return "string".
case STRING_TAG: case STRING_TAG:
return "string"sv; return *vm.typeof_strings.string;
// 7. If val is a Symbol, return "symbol". // 7. If val is a Symbol, return "symbol".
case SYMBOL_TAG: case SYMBOL_TAG:
return "symbol"sv; return *vm.typeof_strings.symbol;
// 8. If val is a Boolean, return "boolean". // 8. If val is a Boolean, return "boolean".
case BOOLEAN_TAG: case BOOLEAN_TAG:
return "boolean"sv; return *vm.typeof_strings.boolean;
// 10. If val is a BigInt, return "bigint". // 10. If val is a BigInt, return "bigint".
case BIGINT_TAG: case BIGINT_TAG:
return "bigint"sv; return *vm.typeof_strings.bigint;
// 11. Assert: val is an Object. // 11. Assert: val is an Object.
case OBJECT_TAG: case OBJECT_TAG:
// B.3.6.3 Changes to the typeof Operator, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-typeof // B.3.6.3 Changes to the typeof Operator, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-typeof
// 12. If val has an [[IsHTMLDDA]] internal slot, return "undefined". // 12. If val has an [[IsHTMLDDA]] internal slot, return "undefined".
if (as_object().is_htmldda()) if (as_object().is_htmldda())
return "undefined"sv; return *vm.typeof_strings.undefined;
// 13. If val has a [[Call]] internal slot, return "function". // 13. If val has a [[Call]] internal slot, return "function".
if (is_function()) if (is_function())
return "function"sv; return *vm.typeof_strings.function;
// 14. Return "object". // 14. Return "object".
return "object"sv; return *vm.typeof_strings.object;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -418,7 +418,7 @@ public:
return *this; return *this;
} }
StringView typeof() const; [[nodiscard]] NonnullGCPtr<PrimitiveString> typeof(VM&) const;
bool operator==(Value const&) const; bool operator==(Value const&) const;