Compare commits

...

3 commits

Author SHA1 Message Date
Andreas Kling
0e4450f4b3 LibJS: Avoid function call if @@hasInstance is default implementation
This makes the instanceof operator signficantly faster by avoiding a
generic function call to @@hasInstance unless it has been overridden.

1.15x speed-up on Octane/earley-boyer.js
2025-10-13 17:15:44 +02:00
Andreas Kling
5a7b0a07cb LibJS: Mark global function declarations as globals
This allows us to use the GetGlobal and SetGlobal bytecode instructions
for them, enabling cached accesses.

2.62x speed-up on this Fibonacci program:

    function fib(n) {
        return n < 2 ? n : fib(n - 1) + fib(n - 2);
    }
    for (let i = 0; i < 50_000; ++i)
        fib(10);
2025-10-13 17:15:44 +02:00
Rocco Corsi
44d2a74eeb LibJS: Increase the stack limit when ASAN enabled
Linux, x86_64, Sanitizer, GNU runners on GitHub Action fail randomly
with a stack overflow on recursive test called:
Libraries/LibJS/Tests/runtime-error-call-stack-size.js
2025-10-13 09:07:39 -04:00
7 changed files with 24 additions and 4 deletions

View file

@ -26,6 +26,7 @@ namespace JS::Bytecode {
O(MathSin, math_sin, Math, sin, 1) \ O(MathSin, math_sin, Math, sin, 1) \
O(MathCos, math_cos, Math, cos, 1) \ O(MathCos, math_cos, Math, cos, 1) \
O(MathTan, math_tan, Math, tan, 1) \ O(MathTan, math_tan, Math, tan, 1) \
O(OrdinaryHasInstance, ordinary_has_instance, InternalBuiltin, ordinary_has_instance, 1) \
O(ArrayIteratorPrototypeNext, array_iterator_prototype_next, ArrayIteratorPrototype, next, 0) \ O(ArrayIteratorPrototypeNext, array_iterator_prototype_next, ArrayIteratorPrototype, next, 0) \
O(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, next, 0) \ O(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, next, 0) \
O(SetIteratorPrototypeNext, set_iterator_prototype_next, SetIteratorPrototype, next, 0) \ O(SetIteratorPrototypeNext, set_iterator_prototype_next, SetIteratorPrototype, next, 0) \

View file

@ -2904,6 +2904,8 @@ static ThrowCompletionOr<Value> dispatch_builtin_call(Bytecode::Interpreter& int
case Builtin::SetIteratorPrototypeNext: case Builtin::SetIteratorPrototypeNext:
case Builtin::StringIteratorPrototypeNext: case Builtin::StringIteratorPrototypeNext:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
case Builtin::OrdinaryHasInstance:
VERIFY_NOT_REACHED();
case Bytecode::Builtin::__Count: case Bytecode::Builtin::__Count:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -327,7 +327,7 @@ public:
continue; continue;
} }
if (m_type == ScopeType::Function && m_bound_names.contains(identifier_group_name)) { if (m_type == ScopeType::Function && !m_is_function_declaration && m_bound_names.contains(identifier_group_name)) {
// NOTE: Currently parser can't determine that named function expression assignment creates scope with binding for function name so function names are not considered as candidates to be optimized in global variables access // NOTE: Currently parser can't determine that named function expression assignment creates scope with binding for function name so function names are not considered as candidates to be optimized in global variables access
identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment = true; identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment = true;
} }
@ -495,6 +495,11 @@ public:
m_is_arrow_function = true; m_is_arrow_function = true;
} }
void set_is_function_declaration()
{
m_is_function_declaration = true;
}
private: private:
void throw_identifier_declared(Utf16FlyString const& name, NonnullRefPtr<Declaration const> const& declaration) void throw_identifier_declared(Utf16FlyString const& name, NonnullRefPtr<Declaration const> const& declaration)
{ {
@ -544,6 +549,8 @@ private:
bool m_uses_this_from_environment { false }; bool m_uses_this_from_environment { false };
bool m_uses_this { false }; bool m_uses_this { false };
bool m_is_arrow_function { false }; bool m_is_arrow_function { false };
bool m_is_function_declaration { false };
}; };
class OperatorPrecedenceTable { class OperatorPrecedenceTable {
@ -2991,6 +2998,8 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u16 parse_options, O
FunctionParsingInsights parsing_insights; FunctionParsingInsights parsing_insights;
auto body = [&] { auto body = [&] {
ScopePusher function_scope = ScopePusher::function_scope(*this, name); ScopePusher function_scope = ScopePusher::function_scope(*this, name);
if constexpr (IsSame<FunctionNodeType, FunctionDeclaration>)
function_scope.set_is_function_declaration();
consume(TokenType::ParenOpen); consume(TokenType::ParenOpen);
parameters = parse_formal_parameters(function_length, parse_options); parameters = parse_formal_parameters(function_length, parse_options);

View file

@ -35,7 +35,7 @@ void FunctionPrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.bind, bind, 1, attr); define_native_function(realm, vm.names.bind, bind, 1, attr);
define_native_function(realm, vm.names.call, call, 1, attr); define_native_function(realm, vm.names.call, call, 1, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr); define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.well_known_symbol_has_instance(), symbol_has_instance, 1, 0); define_native_function(realm, vm.well_known_symbol_has_instance(), symbol_has_instance, 1, 0, Bytecode::Builtin::OrdinaryHasInstance);
define_direct_property(vm.names.length, Value(0), Attribute::Configurable); define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), Attribute::Configurable); define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), Attribute::Configurable);
} }

View file

@ -49,6 +49,8 @@ public:
bool is_set_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::SetIteratorPrototypeNext; } bool is_set_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::SetIteratorPrototypeNext; }
bool is_string_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::StringIteratorPrototypeNext; } bool is_string_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::StringIteratorPrototypeNext; }
Optional<Bytecode::Builtin> builtin() const { return m_builtin; }
protected: protected:
NativeFunction(Utf16FlyString name, Object& prototype); NativeFunction(Utf16FlyString name, Object& prototype);
NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)>, Object* prototype, Realm& realm, Optional<Bytecode::Builtin> builtin); NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)>, Object* prototype, Realm& realm, Optional<Bytecode::Builtin> builtin);

View file

@ -108,8 +108,8 @@ public:
bool did_reach_stack_space_limit() const bool did_reach_stack_space_limit() const
{ {
#if defined(AK_OS_MACOS) && defined(HAS_ADDRESS_SANITIZER) #if defined(HAS_ADDRESS_SANITIZER)
// We hit stack limits sooner on macOS 14 arm64 with ASAN enabled. // We hit stack limits sooner with ASAN enabled.
return m_stack_info.size_free() < 96 * KiB; return m_stack_info.size_free() < 96 * KiB;
#else #else
return m_stack_info.size_free() < 32 * KiB; return m_stack_info.size_free() < 32 * KiB;

View file

@ -2172,6 +2172,12 @@ ThrowCompletionOr<Value> instance_of(VM& vm, Value value, Value target)
// 3. If instOfHandler is not undefined, then // 3. If instOfHandler is not undefined, then
if (instance_of_handler) { if (instance_of_handler) {
// OPTIMIZATION: If the handler is the default OrdinaryHasInstance, we can skip doing a generic call.
if (auto* native_function = as_if<NativeFunction>(*instance_of_handler)) {
if (native_function->builtin() == Bytecode::Builtin::OrdinaryHasInstance) {
return ordinary_has_instance(vm, value, target);
}
}
// a. Return ToBoolean(? Call(instOfHandler, target, « V »)). // a. Return ToBoolean(? Call(instOfHandler, target, « V »)).
return Value(TRY(call(vm, *instance_of_handler, target, value)).to_boolean()); return Value(TRY(call(vm, *instance_of_handler, target, value)).to_boolean());
} }