diff --git a/Libraries/LibJS/Bytecode/Builtins.h b/Libraries/LibJS/Bytecode/Builtins.h index d590ff7a30..c484728cad 100644 --- a/Libraries/LibJS/Bytecode/Builtins.h +++ b/Libraries/LibJS/Bytecode/Builtins.h @@ -26,6 +26,9 @@ 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(RegExpPrototypeExec, regexp_prototype_exec, RegExpPrototype, exec, 1) \ + O(RegExpPrototypeReplace, regexp_prototype_replace, RegExpPrototype, replace, 2) \ + O(RegExpPrototypeSplit, regexp_prototype_split, RegExpPrototype, split, 2) \ 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) \ diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index ebbbbf0b29..88592ce7e8 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -2417,6 +2417,9 @@ static ThrowCompletionOr dispatch_builtin_call(Bytecode::Interpreter& int return TRY(MathObject::cos_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathTan: return TRY(MathObject::tan_impl(interpreter.vm(), interpreter.get(arguments[0]))); + case Builtin::RegExpPrototypeExec: + case Builtin::RegExpPrototypeReplace: + case Builtin::RegExpPrototypeSplit: case Builtin::ArrayIteratorPrototypeNext: case Builtin::MapIteratorPrototypeNext: case Builtin::SetIteratorPrototypeNext: diff --git a/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Libraries/LibJS/Runtime/RegExpPrototype.cpp index 6741373c40..1f4a08aa98 100644 --- a/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -39,14 +39,14 @@ void RegExpPrototype::initialize(Realm& realm) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(realm, vm.names.toString, to_string, 0, attr); define_native_function(realm, vm.names.test, test, 1, attr); - define_native_function(realm, vm.names.exec, exec, 1, attr); + define_native_function(realm, vm.names.exec, exec, 1, attr, Bytecode::Builtin::RegExpPrototypeExec); define_native_function(realm, vm.names.compile, compile, 2, attr); define_native_function(realm, vm.well_known_symbol_match(), symbol_match, 1, attr); define_native_function(realm, vm.well_known_symbol_match_all(), symbol_match_all, 1, attr); - define_native_function(realm, vm.well_known_symbol_replace(), symbol_replace, 2, attr); + define_native_function(realm, vm.well_known_symbol_replace(), symbol_replace, 2, attr, Bytecode::Builtin::RegExpPrototypeReplace); define_native_function(realm, vm.well_known_symbol_search(), symbol_search, 1, attr); - define_native_function(realm, vm.well_known_symbol_split(), symbol_split, 2, attr); + define_native_function(realm, vm.well_known_symbol_split(), symbol_split, 2, attr, Bytecode::Builtin::RegExpPrototypeSplit); define_native_accessor(realm, vm.names.flags, flags, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.source, source, {}, Attribute::Configurable); @@ -459,8 +459,12 @@ ThrowCompletionOr regexp_exec(VM& vm, Object& regexp_object, GC::Refget_builtin_value(Bytecode::Builtin::RegExpPrototypeExec)) + return regexp_builtin_exec(vm, static_cast(regexp_object), string); + // a. Let result be ? Call(exec, R, « S »). - auto result = TRY(call(vm, exec.as_function(), ®exp_object, string)); + auto result = TRY(call(vm, exec_function, ®exp_object, string)); // b. If Type(result) is neither Object nor Null, throw a TypeError exception. if (!result.is_object() && !result.is_null()) @@ -718,6 +722,11 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace) // 3. Let S be ? ToString(string). auto string = TRY(string_value.to_primitive_string(vm)); + return symbol_replace_impl(vm, *regexp_object, string, replace_value); +} + +ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& regexp_object, GC::Ref string, Value replace_value) +{ // 4. Let lengthS be the number of code unit elements in S. // 5. Let functionalReplace be IsCallable(replaceValue). @@ -730,7 +739,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace) // 7. Let flags be ? ToString(? Get(rx, "flags")). static Bytecode::PropertyLookupCache cache; - auto flags_value = TRY(regexp_object->get(vm.names.flags, cache)); + auto flags_value = TRY(regexp_object.get(vm.names.flags, cache)); auto flags = TRY(flags_value.to_string(vm)); // 8. If flags contains "g", let global be true. Otherwise, let global be false. @@ -740,7 +749,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace) if (global) { // a. Perform ? Set(rx, "lastIndex", +0𝔽, true). static Bytecode::PropertyLookupCache cache2; - TRY(regexp_object->set(vm.names.lastIndex, Value(0), cache2)); + TRY(regexp_object.set(vm.names.lastIndex, Value(0), cache2)); } // 10. Let results be a new empty List. @@ -969,8 +978,6 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::source) // 22.2.6.14 RegExp.prototype [ @@split ] ( string, limit ), https://tc39.es/ecma262/#sec-regexp.prototype-@@split JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split) { - auto& realm = *vm.current_realm(); - // 1. Let rx be the this value. // 2. If Type(rx) is not Object, throw a TypeError exception. auto regexp_object = TRY(this_object(vm)); @@ -978,12 +985,19 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split) // 3. Let S be ? ToString(string). auto string = TRY(vm.argument(0).to_primitive_string(vm)); + return symbol_split_impl(vm, *regexp_object, string, vm.argument(1)); +} + +ThrowCompletionOr RegExpPrototype::symbol_split_impl(VM& vm, Object& regexp_object, GC::Ref string, Value limit_value) +{ + auto& realm = *vm.current_realm(); + // 4. Let C be ? SpeciesConstructor(rx, %RegExp%). auto* constructor = TRY(species_constructor(vm, regexp_object, realm.intrinsics().regexp_constructor())); // 5. Let flags be ? ToString(? Get(rx, "flags")). static Bytecode::PropertyLookupCache cache; - auto flags_value = TRY(regexp_object->get(vm.names.flags, cache)); + auto flags_value = TRY(regexp_object.get(vm.names.flags, cache)); auto flags = TRY(flags_value.to_string(vm)); // 6. If flags contains "u" or flags contains "v", let unicodeMatching be true. @@ -995,7 +1009,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split) auto new_flags = flags.bytes_as_string_view().find('y').has_value() ? move(flags) : MUST(String::formatted("{}y", flags)); // 10. Let splitter be ? Construct(C, « rx, newFlags »). - auto splitter = TRY(construct(vm, *constructor, regexp_object, PrimitiveString::create(vm, move(new_flags)))); + auto splitter = TRY(construct(vm, *constructor, ®exp_object, PrimitiveString::create(vm, move(new_flags)))); // 11. Let A be ! ArrayCreate(0). auto array = MUST(Array::create(realm, 0)); @@ -1005,8 +1019,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split) // 13. If limit is undefined, let lim be 2^32 - 1; else let lim be ℝ(? ToUint32(limit)). auto limit = NumericLimits::max(); - if (!vm.argument(1).is_undefined()) - limit = TRY(vm.argument(1).to_u32(vm)); + if (!limit_value.is_undefined()) + limit = TRY(limit_value.to_u32(vm)); // 14. If lim is 0, return A. if (limit == 0) diff --git a/Libraries/LibJS/Runtime/RegExpPrototype.h b/Libraries/LibJS/Runtime/RegExpPrototype.h index e753841d89..750dc33fdb 100644 --- a/Libraries/LibJS/Runtime/RegExpPrototype.h +++ b/Libraries/LibJS/Runtime/RegExpPrototype.h @@ -22,6 +22,9 @@ public: virtual void initialize(Realm&) override; virtual ~RegExpPrototype() override = default; + static ThrowCompletionOr symbol_replace_impl(VM&, Object& regexp_object, GC::Ref string, Value replace_value); + static ThrowCompletionOr symbol_split_impl(VM&, Object& regexp_object, GC::Ref string, Value limit_value); + private: explicit RegExpPrototype(Realm&); diff --git a/Libraries/LibJS/Runtime/StringPrototype.cpp b/Libraries/LibJS/Runtime/StringPrototype.cpp index 8cdc4ea73b..df9db51f5b 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -876,6 +877,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) // b. If replacer is not undefined, then if (replacer) { + if (replacer == vm.current_realm()->get_builtin_value(Bytecode::Builtin::RegExpPrototypeReplace)) { + // OPTIMIZATION: The common case of RegExp.prototype[@@replace] + auto& rx = search_value.as_object(); + auto string = TRY(this_object.to_primitive_string(vm)); + return TRY(RegExpPrototype::symbol_replace_impl(vm, rx, *string, replace_value)); + } // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return TRY(call(vm, *replacer, search_value, this_object, replace_value)); } @@ -972,6 +979,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all) // d. If replacer is not undefined, then if (replacer) { + if (replacer == vm.current_realm()->get_builtin_value(Bytecode::Builtin::RegExpPrototypeReplace)) { + // OPTIMIZATION: The common case of RegExp.prototype[@@replace] + auto& rx = search_value.as_object(); + auto string = TRY(this_object.to_primitive_string(vm)); + return TRY(RegExpPrototype::symbol_replace_impl(vm, rx, *string, replace_value)); + } // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return TRY(call(vm, *replacer, search_value, this_object, replace_value)); } @@ -1154,6 +1167,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) auto splitter = TRY(separator_argument.get_method(vm, vm.well_known_symbol_split(), cache)); // b. If splitter is not undefined, then if (splitter) { + if (splitter == realm.get_builtin_value(Bytecode::Builtin::RegExpPrototypeSplit)) { + // OPTIMIZATION: The common case of RegExp.prototype[@@split] + auto& rx = separator_argument.as_object(); + auto string = TRY(object.to_primitive_string(vm)); + return TRY(RegExpPrototype::symbol_split_impl(vm, rx, *string, limit_argument)); + } // i. Return ? Call(splitter, separator, « O, limit »). return TRY(call(vm, *splitter, separator_argument, object, limit_argument)); }