Skip to content

Commit

Permalink
LibJS: Make typeof a lot faster by caching all possible results
Browse files Browse the repository at this point in the history
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;
    }
  • Loading branch information
awesomekling committed Jul 23, 2024
1 parent 835986d commit f3ec215
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 16 deletions.
8 changes: 4 additions & 4 deletions Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) \
Expand Down Expand Up @@ -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 = {};
Expand All @@ -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 {};
}

Expand Down
20 changes: 20 additions & 0 deletions Userland/Libraries/LibJS/Runtime/VM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions Userland/Libraries/LibJS/Runtime/VM.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,16 @@ class VM : public RefCounted<VM> {
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*);
Expand Down
22 changes: 11 additions & 11 deletions Userland/Libraries/LibJS/Runtime/Value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibJS/Runtime/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ class Value {
return *this;
}

StringView typeof() const;
[[nodiscard]] NonnullGCPtr<PrimitiveString> typeof(VM&) const;

bool operator==(Value const&) const;

Expand Down

0 comments on commit f3ec215

Please sign in to comment.