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 (base.is_object())
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;
}
@ -2018,7 +2018,7 @@ static ThrowCompletionOr<Value> not_(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) \
@ -2873,7 +2873,7 @@ ThrowCompletionOr<void> TypeofBinding::execute_impl(Bytecode::Interpreter& inter
environment = environment->outer_environment();
if (!environment->is_permanently_screwed_by_eval()) {
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 {};
}
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.
// 5. Return a String according to Table 41.
interpreter.set(dst(), PrimitiveString::create(vm, value.typeof()));
interpreter.set(dst(), value.typeof(vm));
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 {});
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)
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)
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) \
roots.set(m_well_known_symbols.snake_name, HeapRoot { .type = HeapRoot::Type::VM });
JS_ENUMERATE_WELL_KNOWN_SYMBOLS

View file

@ -217,6 +217,16 @@ public:
Object& get_global_object();
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 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
StringView Value::typeof() const
NonnullGCPtr<PrimitiveString> Value::typeof(VM& vm) const
{
// 9. If val is a Number, return "number".
if (is_number())
return "number"sv;
return *vm.typeof_strings.number;
switch (m_value.tag) {
// 4. If val is undefined, return "undefined".
case UNDEFINED_TAG:
return "undefined"sv;
return *vm.typeof_strings.undefined;
// 5. If val is null, return "object".
case NULL_TAG:
return "object"sv;
return *vm.typeof_strings.object;
// 6. If val is a String, return "string".
case STRING_TAG:
return "string"sv;
return *vm.typeof_strings.string;
// 7. If val is a Symbol, return "symbol".
case SYMBOL_TAG:
return "symbol"sv;
return *vm.typeof_strings.symbol;
// 8. If val is a Boolean, return "boolean".
case BOOLEAN_TAG:
return "boolean"sv;
return *vm.typeof_strings.boolean;
// 10. If val is a BigInt, return "bigint".
case BIGINT_TAG:
return "bigint"sv;
return *vm.typeof_strings.bigint;
// 11. Assert: val is an Object.
case OBJECT_TAG:
// 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".
if (as_object().is_htmldda())
return "undefined"sv;
return *vm.typeof_strings.undefined;
// 13. If val has a [[Call]] internal slot, return "function".
if (is_function())
return "function"sv;
return *vm.typeof_strings.function;
// 14. Return "object".
return "object"sv;
return *vm.typeof_strings.object;
default:
VERIFY_NOT_REACHED();
}

View file

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