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
This commit is contained in:
Andreas Kling 2025-10-13 12:21:59 +02:00 committed by Andreas Kling
parent 5a7b0a07cb
commit 0e4450f4b3
5 changed files with 12 additions and 1 deletions

View File

@ -26,6 +26,7 @@ namespace JS::Bytecode {
O(MathSin, math_sin, Math, sin, 1) \
O(MathCos, math_cos, Math, cos, 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(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, 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::StringIteratorPrototypeNext:
VERIFY_NOT_REACHED();
case Builtin::OrdinaryHasInstance:
VERIFY_NOT_REACHED();
case Bytecode::Builtin::__Count:
VERIFY_NOT_REACHED();
}

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.call, call, 1, 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.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_string_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::StringIteratorPrototypeNext; }
Optional<Bytecode::Builtin> builtin() const { return m_builtin; }
protected:
NativeFunction(Utf16FlyString name, Object& prototype);
NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)>, Object* prototype, Realm& realm, Optional<Bytecode::Builtin> builtin);

View File

@ -2172,6 +2172,12 @@ ThrowCompletionOr<Value> instance_of(VM& vm, Value value, Value target)
// 3. If instOfHandler is not undefined, then
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 »)).
return Value(TRY(call(vm, *instance_of_handler, target, value)).to_boolean());
}