From e443b89f238cf97b7d33eb54ca34fc19ef44f52f Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Mon, 23 Feb 2026 09:54:47 -0800 Subject: [PATCH] [vm] Update Irregexp to V8 commit 254cc758346f10be2a7e22e55d90d4defe9cad74. Includes support for modifier spans and duplicate named capture groups. Drops the flow graph implementation to ease maintenance. TEST=corelib/regexp Bug: https://github.com/dart-lang/sdk/issues/56573 Bug: https://github.com/dart-lang/sdk/issues/61337 Bug: https://github.com/dart-lang/sdk/issues/62349 Bug: https://github.com/dart-lang/sdk/issues/62708 Change-Id: I05640ba945a4fa5476e7ad463738f4f39d842c14 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480121 Reviewed-by: Lasse Nielsen Commit-Queue: Ryan Macnak --- CHANGELOG.md | 7 + build/config/BUILDCONFIG.gn | 2 +- build/config/compiler/BUILD.gn | 7 +- .../lib/runtime/vm_offsets.g.dart | 52 +- runtime/PRESUBMIT.py | 5 + runtime/lib/regexp.cc | 54 +- runtime/platform/globals.h | 2 + runtime/platform/unicode.h | 27 + runtime/platform/utils.h | 4 + .../vm/dart/regress_big_regexp_test.dart | 2 +- runtime/tools/run_clang_tidy.dart | 45 + runtime/vm/BUILD.gn | 24 + runtime/vm/app_snapshot.cc | 4 +- runtime/vm/canonical_tables.cc | 5 +- runtime/vm/canonical_tables.h | 2 +- runtime/vm/compiler/aot/precompiler.cc | 2 - runtime/vm/compiler/asm_intrinsifier.cc | 12 - runtime/vm/compiler/asm_intrinsifier.h | 4 - runtime/vm/compiler/asm_intrinsifier_arm.cc | 33 - runtime/vm/compiler/asm_intrinsifier_arm64.cc | 41 - runtime/vm/compiler/asm_intrinsifier_ia32.cc | 32 - runtime/vm/compiler/asm_intrinsifier_riscv.cc | 35 - runtime/vm/compiler/asm_intrinsifier_x64.cc | 42 - .../compiler/backend/constant_propagator.cc | 5 - runtime/vm/compiler/backend/il.cc | 6 - runtime/vm/compiler/backend/il.h | 62 - runtime/vm/compiler/backend/il_arm.cc | 22 - runtime/vm/compiler/backend/il_arm64.cc | 22 - runtime/vm/compiler/backend/il_ia32.cc | 25 - runtime/vm/compiler/backend/il_riscv.cc | 34 - runtime/vm/compiler/backend/il_x64.cc | 22 - .../vm/compiler/backend/type_propagator.cc | 22 - runtime/vm/compiler/jit/compiler.cc | 66 - runtime/vm/compiler/recognized_methods_list.h | 3 - .../vm/compiler/runtime_offsets_extracted.h | 750 +-- runtime/vm/dart_api_impl.cc | 49 - runtime/vm/flag_list.h | 1 - runtime/vm/object.cc | 83 +- runtime/vm/object.h | 175 +- runtime/vm/object_service.cc | 36 +- runtime/vm/parser.cc | 14 - runtime/vm/parser.h | 1 - runtime/vm/raw_object.h | 17 +- runtime/vm/regexp/README.md | 29 + runtime/vm/regexp/base.h | 214 + runtime/vm/regexp/char-predicates-inl.h | 187 + runtime/vm/regexp/char-predicates.cc | 38 + runtime/vm/regexp/char-predicates.h | 69 + runtime/vm/regexp/flags.h | 138 + runtime/vm/regexp/gen-regexp-special-case.cc | 166 + runtime/vm/regexp/label.h | 107 + runtime/vm/regexp/memcopy.h | 69 + runtime/vm/regexp/regexp-ast.cc | 269 + runtime/vm/regexp/regexp-ast.h | 785 +++ .../vm/regexp/regexp-bytecode-generator-inl.h | 95 + .../vm/regexp/regexp-bytecode-generator.cc | 652 ++ runtime/vm/regexp/regexp-bytecode-generator.h | 272 + runtime/vm/regexp/regexp-bytecodes-inl.h | 366 + runtime/vm/regexp/regexp-bytecodes.h | 339 + runtime/vm/regexp/regexp-compiler-tonode.cc | 2359 +++++++ runtime/vm/regexp/regexp-compiler.cc | 4225 ++++++++++++ runtime/vm/regexp/regexp-compiler.h | 713 ++ runtime/vm/regexp/regexp-error.cc | 22 + runtime/vm/regexp/regexp-error.h | 65 + runtime/vm/regexp/regexp-flags.h | 84 + runtime/vm/regexp/regexp-interpreter.cc | 1269 ++++ runtime/vm/regexp/regexp-interpreter.h | 83 + runtime/vm/regexp/regexp-macro-assembler.cc | 585 ++ runtime/vm/regexp/regexp-macro-assembler.h | 463 ++ runtime/vm/regexp/regexp-nodes.h | 906 +++ runtime/vm/regexp/regexp-parser.cc | 3379 ++++++++++ runtime/vm/regexp/regexp-parser.h | 39 + runtime/vm/regexp/regexp.cc | 5968 ++--------------- runtime/vm/regexp/regexp.h | 1674 +---- runtime/vm/regexp/regexp_assembler.cc | 129 - runtime/vm/regexp/regexp_assembler.h | 269 - .../vm/regexp/regexp_assembler_bytecode.cc | 562 -- runtime/vm/regexp/regexp_assembler_bytecode.h | 146 - .../vm/regexp/regexp_assembler_bytecode_inl.h | 54 - runtime/vm/regexp/regexp_assembler_ir.cc | 1740 ----- runtime/vm/regexp/regexp_assembler_ir.h | 447 -- runtime/vm/regexp/regexp_ast.cc | 313 - runtime/vm/regexp/regexp_ast.h | 448 -- runtime/vm/regexp/regexp_bytecodes.h | 86 - runtime/vm/regexp/regexp_interpreter.cc | 713 -- runtime/vm/regexp/regexp_interpreter.h | 29 - runtime/vm/regexp/regexp_parser.cc | 1989 ------ runtime/vm/regexp/regexp_parser.h | 267 - runtime/vm/regexp/regexp_sources.gni | 48 +- runtime/vm/regexp/regexp_test.cc | 44 +- runtime/vm/regexp/small-vector.h | 387 ++ runtime/vm/regexp/special-case.cc | 90 + runtime/vm/regexp/special-case.h | 117 + runtime/vm/regexp/unibrow-inl.h | 10 +- runtime/vm/regexp/unibrow.cc | 36 +- runtime/vm/regexp/unibrow.h | 49 +- runtime/vm/regexp/vector.h | 197 + runtime/vm/regexp/zone-containers.h | 716 ++ runtime/vm/regexp/zone-list-inl.h | 160 + runtime/vm/regexp/zone-list.h | 193 + runtime/vm/runtime_entry.cc | 10 + runtime/vm/runtime_entry.h | 10 - runtime/vm/runtime_entry_list.h | 4 - runtime/vm/symbols.h | 4 - runtime/vm/zone.h | 6 + sdk/lib/_internal/vm/lib/regexp_patch.dart | 143 +- .../duplicate_named_capture_group_test.dart | 34 + tests/corelib/regexp/group_modifier_test.dart | 19 + .../corelib/regexp/named_captures_2_test.dart | 12 +- tests/standalone/regress_52691_test.dart | 2 +- 110 files changed, 21314 insertions(+), 15656 deletions(-) create mode 100644 runtime/vm/regexp/README.md create mode 100644 runtime/vm/regexp/base.h create mode 100644 runtime/vm/regexp/char-predicates-inl.h create mode 100644 runtime/vm/regexp/char-predicates.cc create mode 100644 runtime/vm/regexp/char-predicates.h create mode 100644 runtime/vm/regexp/flags.h create mode 100644 runtime/vm/regexp/gen-regexp-special-case.cc create mode 100644 runtime/vm/regexp/label.h create mode 100644 runtime/vm/regexp/memcopy.h create mode 100644 runtime/vm/regexp/regexp-ast.cc create mode 100644 runtime/vm/regexp/regexp-ast.h create mode 100644 runtime/vm/regexp/regexp-bytecode-generator-inl.h create mode 100644 runtime/vm/regexp/regexp-bytecode-generator.cc create mode 100644 runtime/vm/regexp/regexp-bytecode-generator.h create mode 100644 runtime/vm/regexp/regexp-bytecodes-inl.h create mode 100644 runtime/vm/regexp/regexp-bytecodes.h create mode 100644 runtime/vm/regexp/regexp-compiler-tonode.cc create mode 100644 runtime/vm/regexp/regexp-compiler.cc create mode 100644 runtime/vm/regexp/regexp-compiler.h create mode 100644 runtime/vm/regexp/regexp-error.cc create mode 100644 runtime/vm/regexp/regexp-error.h create mode 100644 runtime/vm/regexp/regexp-flags.h create mode 100644 runtime/vm/regexp/regexp-interpreter.cc create mode 100644 runtime/vm/regexp/regexp-interpreter.h create mode 100644 runtime/vm/regexp/regexp-macro-assembler.cc create mode 100644 runtime/vm/regexp/regexp-macro-assembler.h create mode 100644 runtime/vm/regexp/regexp-nodes.h create mode 100644 runtime/vm/regexp/regexp-parser.cc create mode 100644 runtime/vm/regexp/regexp-parser.h delete mode 100644 runtime/vm/regexp/regexp_assembler.cc delete mode 100644 runtime/vm/regexp/regexp_assembler.h delete mode 100644 runtime/vm/regexp/regexp_assembler_bytecode.cc delete mode 100644 runtime/vm/regexp/regexp_assembler_bytecode.h delete mode 100644 runtime/vm/regexp/regexp_assembler_bytecode_inl.h delete mode 100644 runtime/vm/regexp/regexp_assembler_ir.cc delete mode 100644 runtime/vm/regexp/regexp_assembler_ir.h delete mode 100644 runtime/vm/regexp/regexp_ast.cc delete mode 100644 runtime/vm/regexp/regexp_ast.h delete mode 100644 runtime/vm/regexp/regexp_bytecodes.h delete mode 100644 runtime/vm/regexp/regexp_interpreter.cc delete mode 100644 runtime/vm/regexp/regexp_interpreter.h delete mode 100644 runtime/vm/regexp/regexp_parser.cc delete mode 100644 runtime/vm/regexp/regexp_parser.h create mode 100644 runtime/vm/regexp/small-vector.h create mode 100644 runtime/vm/regexp/special-case.cc create mode 100644 runtime/vm/regexp/special-case.h create mode 100644 runtime/vm/regexp/vector.h create mode 100644 runtime/vm/regexp/zone-containers.h create mode 100644 runtime/vm/regexp/zone-list-inl.h create mode 100644 runtime/vm/regexp/zone-list.h create mode 100644 tests/corelib/regexp/duplicate_named_capture_group_test.dart create mode 100644 tests/corelib/regexp/group_modifier_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb0680470e..4808613ed87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,13 @@ main() { } ``` +### Libraries + +#### `dart:core` + +- The Dart VM's implementation of `RegExp` has been updated to include support + for modifier spans and duplicate named capture groups. + ### Tools #### Pub diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn index 7dbc6232a24..cfb06870be9 100644 --- a/build/config/BUILDCONFIG.gn +++ b/build/config/BUILDCONFIG.gn @@ -257,7 +257,7 @@ if (current_os == "win") { # ============================================================================= use_flutter_cxx = is_clang && (((is_asan || is_ubsan) && is_mac) || is_msan || - is_tsan || is_ios) + is_tsan || is_ios || is_android) using_sanitizer = !is_win && (is_asan || is_hwasan || is_lsan || is_msan || is_tsan || is_ubsan) diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 696cbc7e5d0..7a1b0723840 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -620,10 +620,9 @@ config("runtime_library") { } else if (is_android) { # Android standard library setup. - ldflags += [ - "-Wl,--warn-shared-textrel", - "-static-libstdc++", - ] + if (!use_flutter_cxx) { + ldflags += [ "-static-libstdc++" ] + } libs += [ "c", diff --git a/pkg/native_compiler/lib/runtime/vm_offsets.g.dart b/pkg/native_compiler/lib/runtime/vm_offsets.g.dart index 566baabdd9d..6537c76cb26 100644 --- a/pkg/native_compiler/lib/runtime/vm_offsets.g.dart +++ b/pkg/native_compiler/lib/runtime/vm_offsets.g.dart @@ -912,7 +912,7 @@ final class Arm64VMOffsets extends VMOffsets { @override int get Thread_allocate_object_slow_entry_point_offset => 0x230; @override - int get Thread_api_top_scope_offset => 0x898; + int get Thread_api_top_scope_offset => 0x888; @override int get Thread_async_exception_handler_stub_offset => 0x160; @override @@ -928,15 +928,15 @@ final class Arm64VMOffsets extends VMOffsets { @override int get Thread_call_to_runtime_stub_offset => 0xd8; @override - int get Thread_dart_stream_offset => 0x8f0; + int get Thread_dart_stream_offset => 0x8e0; @override int get Thread_dispatch_table_array_offset => 0x68; @override - int get Thread_double_truncate_round_supported_offset => 0x8a0; + int get Thread_double_truncate_round_supported_offset => 0x890; @override - int get Thread_service_extension_stream_offset => 0x8f8; + int get Thread_service_extension_stream_offset => 0x8e8; @override - int get Thread_thread_locals_offset => 0x900; + int get Thread_thread_locals_offset => 0x8f0; @override int get Thread_optimize_entry_offset => 0x258; @override @@ -1062,7 +1062,7 @@ final class Arm64VMOffsets extends VMOffsets { @override int get Thread_shared_field_table_values_offset => 0x78; @override - int get Thread_single_step_offset => 0x8d0; + int get Thread_single_step_offset => 0x8c0; @override int get Thread_slow_type_test_stub_offset => 0x1d0; @override @@ -1117,7 +1117,7 @@ final class Arm64VMOffsets extends VMOffsets { @override int get Thread_top_resource_offset => 0x20; @override - int get Thread_unboxed_runtime_arg_offset => 0x8a8; + int get Thread_unboxed_runtime_arg_offset => 0x898; @override int get Thread_vm_tag_offset => 0x6c0; @override @@ -1125,19 +1125,19 @@ final class Arm64VMOffsets extends VMOffsets { @override int get Thread_write_barrier_mask_offset => 0x50; @override - int get Thread_next_task_id_offset => 0x8b8; + int get Thread_next_task_id_offset => 0x8a8; @override - int get Thread_random_offset => 0x8c0; + int get Thread_random_offset => 0x8b0; @override int get Thread_jump_to_frame_entry_point_offset => 0x270; @override - int get Thread_tsan_utils_offset => 0x8c8; + int get Thread_tsan_utils_offset => 0x8b8; @override - int get Thread_current_tag_offset => 0x8e0; + int get Thread_current_tag_offset => 0x8d0; @override - int get Thread_default_tag_offset => 0x8e8; + int get Thread_default_tag_offset => 0x8d8; @override - int get Thread_user_tag_offset => 0x8d8; + int get Thread_user_tag_offset => 0x8c8; @override int get TsanUtils_setjmp_function_offset => 0x0; @override @@ -1823,7 +1823,7 @@ final class Arm64ProductVMOffsets extends VMOffsets { @override int get Thread_allocate_object_slow_entry_point_offset => 0x230; @override - int get Thread_api_top_scope_offset => 0x898; + int get Thread_api_top_scope_offset => 0x888; @override int get Thread_async_exception_handler_stub_offset => 0x160; @override @@ -1839,15 +1839,15 @@ final class Arm64ProductVMOffsets extends VMOffsets { @override int get Thread_call_to_runtime_stub_offset => 0xd8; @override - int get Thread_dart_stream_offset => 0x8f0; + int get Thread_dart_stream_offset => 0x8e0; @override int get Thread_dispatch_table_array_offset => 0x68; @override - int get Thread_double_truncate_round_supported_offset => 0x8a0; + int get Thread_double_truncate_round_supported_offset => 0x890; @override - int get Thread_service_extension_stream_offset => 0x8f8; + int get Thread_service_extension_stream_offset => 0x8e8; @override - int get Thread_thread_locals_offset => 0x900; + int get Thread_thread_locals_offset => 0x8f0; @override int get Thread_optimize_entry_offset => 0x258; @override @@ -2026,7 +2026,7 @@ final class Arm64ProductVMOffsets extends VMOffsets { @override int get Thread_top_resource_offset => 0x20; @override - int get Thread_unboxed_runtime_arg_offset => 0x8a8; + int get Thread_unboxed_runtime_arg_offset => 0x898; @override int get Thread_vm_tag_offset => 0x6c0; @override @@ -2034,19 +2034,19 @@ final class Arm64ProductVMOffsets extends VMOffsets { @override int get Thread_write_barrier_mask_offset => 0x50; @override - int get Thread_next_task_id_offset => 0x8b8; + int get Thread_next_task_id_offset => 0x8a8; @override - int get Thread_random_offset => 0x8c0; + int get Thread_random_offset => 0x8b0; @override int get Thread_jump_to_frame_entry_point_offset => 0x270; @override - int get Thread_tsan_utils_offset => 0x8c8; + int get Thread_tsan_utils_offset => 0x8b8; @override - int get Thread_current_tag_offset => 0x8e0; + int get Thread_current_tag_offset => 0x8d0; @override - int get Thread_default_tag_offset => 0x8e8; + int get Thread_default_tag_offset => 0x8d8; @override - int get Thread_user_tag_offset => 0x8d8; + int get Thread_user_tag_offset => 0x8c8; @override int get TsanUtils_setjmp_function_offset => 0x0; @override @@ -2626,8 +2626,6 @@ enum LeafRuntimeEntry { LibcAtan2, LibcExp, LibcLog, - CaseInsensitiveCompareUCS2, - CaseInsensitiveCompareUTF16, EnterSafepoint, ExitSafepoint, EnterHandleScope, diff --git a/runtime/PRESUBMIT.py b/runtime/PRESUBMIT.py index bee8c74dbe6..83ff9e31dd4 100644 --- a/runtime/PRESUBMIT.py +++ b/runtime/PRESUBMIT.py @@ -52,6 +52,11 @@ def RunLint(input_api, output_api): # Find all .cc and .h files in the change list. for git_file in input_api.AffectedTextFiles(): filename = git_file.AbsoluteLocalPath() + + # Don't lint V8 sources. + if 'runtime/vm/regexp/' in filename: + continue + if filename.endswith('.cc') or ( # cpplint complains about the style of #ifndefs in our .pbzero.h # files, but they are generated by the protozero compiler, so we diff --git a/runtime/lib/regexp.cc b/runtime/lib/regexp.cc index 649b080394b..a6e38cd8949 100644 --- a/runtime/lib/regexp.cc +++ b/runtime/lib/regexp.cc @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +#include "vm/regexp/regexp.h" #include "platform/assert.h" #include "vm/bootstrap_natives.h" #include "vm/canonical_tables.h" @@ -9,16 +10,11 @@ #include "vm/native_entry.h" #include "vm/object.h" #include "vm/object_store.h" -#include "vm/regexp/regexp_assembler_bytecode.h" -#include "vm/regexp/regexp_parser.h" +#include "vm/regexp/regexp-parser.h" #include "vm/reusable_handles.h" #include "vm/symbols.h" #include "vm/thread.h" -#if !defined(DART_PRECOMPILED_RUNTIME) -#include "vm/regexp/regexp_assembler_ir.h" -#endif // !defined(DART_PRECOMPILED_RUNTIME) - namespace dart { DEFINE_NATIVE_ENTRY(RegExp_factory, 0, 6) { @@ -32,11 +28,11 @@ DEFINE_NATIVE_ENTRY(RegExp_factory, 0, 6) { bool dot_all = arguments->NativeArgAt(5) == Bool::True().ptr(); RegExpFlags flags; - flags.SetGlobal(); // All dart regexps are global. - if (ignore_case) flags.SetIgnoreCase(); - if (multi_line) flags.SetMultiLine(); - if (unicode) flags.SetUnicode(); - if (dot_all) flags.SetDotAll(); + flags |= RegExpFlag::kGlobal; // All dart regexps are global. + if (ignore_case) flags |= RegExpFlag::kIgnoreCase; + if (multi_line) flags |= RegExpFlag::kMultiline; + if (unicode) flags |= RegExpFlag::kUnicode; + if (dot_all) flags |= RegExpFlag::kDotAll; RegExpKey lookup_key(pattern, flags); RegExp& regexp = RegExp::Handle(thread->zone()); @@ -60,7 +56,12 @@ DEFINE_NATIVE_ENTRY(RegExp_factory, 0, 6) { // the factory constructor. It is parsed again upon compilation. RegExpCompileData compileData; // Throws an exception on parsing failure. - RegExpParser::ParseRegExp(pattern, flags, &compileData); + if (!RegExpParser::ParseRegExpFromHeapString(isolate, zone, pattern, flags, + &compileData)) { + USE(RegExpStatics::ThrowRegExpException(isolate, flags, pattern, + compileData.error)); + UNREACHABLE(); + } { RegExpKey lookup_symbol_key(String::Handle(Symbols::New(thread, pattern)), @@ -86,31 +87,31 @@ DEFINE_NATIVE_ENTRY(RegExp_getPattern, 0, 1) { DEFINE_NATIVE_ENTRY(RegExp_getIsMultiLine, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - return Bool::Get(regexp.flags().IsMultiLine()).ptr(); + return Bool::Get(IsMultiline(regexp.flags())).ptr(); } DEFINE_NATIVE_ENTRY(RegExp_getIsUnicode, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - return Bool::Get(regexp.flags().IsUnicode()).ptr(); + return Bool::Get(IsUnicode(regexp.flags())).ptr(); } DEFINE_NATIVE_ENTRY(RegExp_getIsDotAll, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - return Bool::Get(regexp.flags().IsDotAll()).ptr(); + return Bool::Get(IsDotAll(regexp.flags())).ptr(); } DEFINE_NATIVE_ENTRY(RegExp_getIsCaseSensitive, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - return Bool::Get(!regexp.flags().IgnoreCase()).ptr(); + return Bool::Get(!IsIgnoreCase(regexp.flags())).ptr(); } DEFINE_NATIVE_ENTRY(RegExp_getGroupCount, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - if (regexp.is_initialized()) { + if (regexp.num_bracket_expressions() != -1) { return Smi::New(regexp.num_bracket_expressions()); } const String& pattern = String::Handle(regexp.pattern()); @@ -126,7 +127,7 @@ DEFINE_NATIVE_ENTRY(RegExp_getGroupCount, 0, 1) { DEFINE_NATIVE_ENTRY(RegExp_getGroupNameMap, 0, 1) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); ASSERT(!regexp.IsNull()); - if (regexp.is_initialized()) { + if (regexp.num_bracket_expressions() != -1) { return regexp.capture_name_map(); } const String& pattern = String::Handle(regexp.pattern()); @@ -139,7 +140,8 @@ DEFINE_NATIVE_ENTRY(RegExp_getGroupNameMap, 0, 1) { return Object::null(); } -static ObjectPtr ExecuteMatch(Zone* zone, +static ObjectPtr ExecuteMatch(Thread* thread, + Zone* zone, NativeArguments* arguments, bool sticky) { const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0)); @@ -160,24 +162,18 @@ static ObjectPtr ExecuteMatch(Zone* zone, kMinInt32, kMaxInt32); } -#if !defined(DART_PRECOMPILED_RUNTIME) - if (!FLAG_interpret_irregexp) { - return IRRegExpMacroAssembler::Execute(regexp, subject, start_index, - /*sticky=*/sticky, zone); - } -#endif - return BytecodeRegExpMacroAssembler::Interpret(regexp, subject, start_index, - /*is_sticky=*/sticky, zone); + return RegExpStatics::Interpret(thread, regexp, subject, start_index.Value(), + sticky); } DEFINE_NATIVE_ENTRY(RegExp_ExecuteMatch, 0, 3) { // This function is intrinsified. See Intrinsifier::RegExp_ExecuteMatch. - return ExecuteMatch(zone, arguments, /*sticky=*/false); + return ExecuteMatch(thread, zone, arguments, /*sticky=*/false); } DEFINE_NATIVE_ENTRY(RegExp_ExecuteMatchSticky, 0, 3) { // This function is intrinsified. See Intrinsifier::RegExp_ExecuteMatchSticky. - return ExecuteMatch(zone, arguments, /*sticky=*/true); + return ExecuteMatch(thread, zone, arguments, /*sticky=*/true); } } // namespace dart diff --git a/runtime/platform/globals.h b/runtime/platform/globals.h index 09e2087b062..9078987dba9 100644 --- a/runtime/platform/globals.h +++ b/runtime/platform/globals.h @@ -466,6 +466,8 @@ constexpr intptr_t kInt64SizeLog2 = 3; constexpr intptr_t kInt64Size = 1 << kInt64SizeLog2; static_assert(kInt64Size == sizeof(int64_t), "Mismatched int64 size constant"); +constexpr int kUInt32Size = sizeof(uint32_t); + constexpr intptr_t kDoubleSize = sizeof(double); constexpr intptr_t kFloatSize = sizeof(float); constexpr intptr_t kQuadSize = 4 * kFloatSize; diff --git a/runtime/platform/unicode.h b/runtime/platform/unicode.h index 1ad3dd6fb3c..5ced725b00f 100644 --- a/runtime/platform/unicode.h +++ b/runtime/platform/unicode.h @@ -132,6 +132,26 @@ class Utf16 : AllStatic { return (ch & 0xFFFFFC00) == 0xDC00; } + static uint32_t CombineSurrogatePair(uint32_t lead, uint32_t trail) { + return 0x10000 + ((lead & 0x3ff) << 10) + (trail & 0x3ff); + } + static const uint32_t kMaxNonSurrogateCharCode = 0xffff; + // Encoding a single UTF-16 code unit will produce 1, 2 or 3 bytes + // of UTF-8 data. The special case where the unit is a surrogate + // trail produces 1 byte net, because the encoding of the pair is + // 4 bytes and the 3 bytes that were used to encode the lead surrogate + // can be reclaimed. + static const int kMaxExtraUtf8BytesForOneUtf16CodeUnit = 3; + // One UTF-16 surrogate is encoded (illegally) as 3 UTF-8 bytes. + // The illegality stems from the surrogate not being part of a pair. + static const int kUtf8BytesToCodeASurrogate = 3; + static inline uint16_t LeadSurrogate(uint32_t char_code) { + return 0xd800 + (((char_code - 0x10000) >> 10) & 0x3ff); + } + static inline uint16_t TrailSurrogate(uint32_t char_code) { + return 0xdc00 + (char_code & 0x3ff); + } + // Returns the character at i and advances i to the next character // boundary. static int32_t Next(const uint16_t* characters, intptr_t* i, intptr_t len) { @@ -250,6 +270,13 @@ class Latin1 { } }; +// LineTerminator: 'JS_Line_Terminator' in point.properties +// ES#sec-line-terminators lists exactly 4 code points: +// LF (U+000A), CR (U+000D), LS(U+2028), PS(U+2029) +inline bool IsLineTerminator(uint32_t c) { + return c == 0x000A || c == 0x000D || c == 0x2028 || c == 0x2029; +} + } // namespace dart #endif // RUNTIME_PLATFORM_UNICODE_H_ diff --git a/runtime/platform/utils.h b/runtime/platform/utils.h index fa7899b8c1f..bb6896ae8ab 100644 --- a/runtime/platform/utils.h +++ b/runtime/platform/utils.h @@ -141,6 +141,10 @@ class Utils { static constexpr int CountOneBits64(uint64_t x) { return std::popcount(x); } static constexpr int CountOneBits32(uint32_t x) { return std::popcount(x); } static constexpr int CountOneBitsWord(uword x) { return std::popcount(x); } + template + static constexpr int CountOneBits(T x) { + return std::popcount(x); + } // TODO(koda): Compare to flsll call/intrinsic. static constexpr size_t HighestBit(int64_t v) { diff --git a/runtime/tests/vm/dart/regress_big_regexp_test.dart b/runtime/tests/vm/dart/regress_big_regexp_test.dart index 9f032959b89..efc2194b82b 100644 --- a/runtime/tests/vm/dart/regress_big_regexp_test.dart +++ b/runtime/tests/vm/dart/regress_big_regexp_test.dart @@ -15,7 +15,7 @@ void testBigRegExp(String source) { Expect.isTrue(re.hasMatch(source)); } catch (e) { // May throw a compile-time error, but shouldn't crash. - Expect.isTrue(e.toString().contains('RegExp too big')); + Expect.isTrue(e.toString().contains('Regular expression too large')); } } diff --git a/runtime/tools/run_clang_tidy.dart b/runtime/tools/run_clang_tidy.dart index b3cf7d17cdb..0033b9b2918 100644 --- a/runtime/tools/run_clang_tidy.dart +++ b/runtime/tools/run_clang_tidy.dart @@ -151,6 +151,51 @@ final Set excludedFiles = Set.from([ 'runtime/bin/utils_win.h', 'runtime/vm/compiler/backend/locations_helpers_arm.h', 'runtime/vm/compiler/ffi/unit_test_custom_zone.cc', + + // V8 sources + 'runtime/vm/regexp/base.h', + 'runtime/vm/regexp/char-predicates-inl.h', + 'runtime/vm/regexp/char-predicates.cc', + 'runtime/vm/regexp/char-predicates.h', + 'runtime/vm/regexp/flags.h', + 'runtime/vm/regexp/gen-regexp-special-case.cc', + 'runtime/vm/regexp/label.h', + 'runtime/vm/regexp/memcopy.h', + 'runtime/vm/regexp/regexp-ast.cc', + 'runtime/vm/regexp/regexp-ast.h', + 'runtime/vm/regexp/regexp-bytecode-generator-inl.h', + 'runtime/vm/regexp/regexp-bytecode-generator.cc', + 'runtime/vm/regexp/regexp-bytecode-generator.h', + 'runtime/vm/regexp/regexp-bytecodes-inl.h', + 'runtime/vm/regexp/regexp-bytecodes.h', + 'runtime/vm/regexp/regexp-compiler-tonode.cc', + 'runtime/vm/regexp/regexp-compiler.cc', + 'runtime/vm/regexp/regexp-compiler.h', + 'runtime/vm/regexp/regexp-error.cc', + 'runtime/vm/regexp/regexp-error.h', + 'runtime/vm/regexp/regexp-flags.h', + 'runtime/vm/regexp/regexp-interpreter.cc', + 'runtime/vm/regexp/regexp-interpreter.h', + 'runtime/vm/regexp/regexp-macro-assembler.cc', + 'runtime/vm/regexp/regexp-macro-assembler.h', + 'runtime/vm/regexp/regexp-nodes.h', + 'runtime/vm/regexp/regexp-parser.cc', + 'runtime/vm/regexp/regexp-parser.h', + 'runtime/vm/regexp/regexp-test.cc', + 'runtime/vm/regexp/regexp-utils.cc', + 'runtime/vm/regexp/regexp-utils.h', + 'runtime/vm/regexp/regexp.cc', + 'runtime/vm/regexp/regexp.h', + 'runtime/vm/regexp/small-vector.h', + 'runtime/vm/regexp/special-case.cc', + 'runtime/vm/regexp/special-case.h', + 'runtime/vm/regexp/unibrow-inl.h', + 'runtime/vm/regexp/unibrow.cc', + 'runtime/vm/regexp/unibrow.h', + 'runtime/vm/regexp/vector.h', + 'runtime/vm/regexp/zone-containers.h', + 'runtime/vm/regexp/zone-list-inl.h', + 'runtime/vm/regexp/zone-list.h', ]); final defineSets = [ diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn index b7aa432bcc8..dc4f61add3a 100644 --- a/runtime/vm/BUILD.gn +++ b/runtime/vm/BUILD.gn @@ -255,3 +255,27 @@ executable("offsets_extractor_aotruntime") { sources = [ "compiler/offsets_extractor.cc" ] include_dirs = [ ".." ] } + +executable("gen_regexp_special_case") { + # The timeline cannot be accessed from the generated executable, so we define + # DART_DISABLE_TIMELINE to strip out the timeline source code. The precise + # reason why we do this is to avoid missing header errors, as the Perfetto + # proto headers are not built as a dependency of this target, but are + # transitively included in this target when DART_DISABLE_TIMELINE is not + # defined. + defines = [ "DART_DISABLE_TIMELINE" ] + configs += [ + "..:dart_arch_config", + "..:dart_config", + "..:dart_aotruntime_config", + "..:dart_maybe_product_config", + ":libdart_vm_config", + ] + deps = [ + "../platform:libdart_platform_jit", + "//third_party/icu:icui18n", + "//third_party/icu:icuuc", + ] + sources = [ "regexp/gen_regexp_special_case.cc" ] + include_dirs = [ ".." ] +} diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc index ebf07360563..69bebed6f3e 100644 --- a/runtime/vm/app_snapshot.cc +++ b/runtime/vm/app_snapshot.cc @@ -6188,7 +6188,7 @@ class RegExpSerializationCluster : public SerializationCluster { WriteFromTo(regexp); s->Write(regexp->untag()->num_one_byte_registers_); s->Write(regexp->untag()->num_two_byte_registers_); - s->Write(regexp->untag()->type_flags_); + s->Write(regexp->untag()->flags_); } } @@ -6220,7 +6220,7 @@ class RegExpDeserializationCluster : public DeserializationCluster { d.ReadFromTo(regexp); regexp->untag()->num_one_byte_registers_ = d.Read(); regexp->untag()->num_two_byte_registers_ = d.Read(); - regexp->untag()->type_flags_ = d.Read(); + regexp->untag()->flags_ = d.Read(); } } }; diff --git a/runtime/vm/canonical_tables.cc b/runtime/vm/canonical_tables.cc index 245f02555ef..d9728bea5fb 100644 --- a/runtime/vm/canonical_tables.cc +++ b/runtime/vm/canonical_tables.cc @@ -4,8 +4,6 @@ #include "vm/canonical_tables.h" -#include "vm/regexp/regexp.h" - namespace dart { bool MetadataMapTraits::IsMatch(const Object& a, const Object& b) { @@ -120,8 +118,7 @@ ObjectPtr CanonicalInstanceTraits::NewKey(const CanonicalInstanceKey& obj) { } ObjectPtr CanonicalRegExpTraits::NewKey(const RegExpKey& key) { - return RegExpEngine::CreateRegExp(Thread::Current(), key.pattern_, - key.flags_); + return RegExp::New(key.pattern_, key.flags_); } } // namespace dart diff --git a/runtime/vm/canonical_tables.h b/runtime/vm/canonical_tables.h index 0b0f5ef4117..b8f03434d1a 100644 --- a/runtime/vm/canonical_tables.h +++ b/runtime/vm/canonical_tables.h @@ -444,7 +444,7 @@ class RegExpKey { } uword Hash() const { // Must agree with RegExp::CanonicalizeHash. - return CombineHashes(pattern_.Hash(), flags_.value()); + return CombineHashes(pattern_.Hash(), flags_); } const String& pattern_; diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index d3437fc02ca..23f2fe152cb 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -47,8 +47,6 @@ #include "vm/os.h" #include "vm/parser.h" #include "vm/program_visitor.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/regexp/regexp_parser.h" #include "vm/resolver.h" #include "vm/runtime_entry.h" #include "vm/stack_trace.h" diff --git a/runtime/vm/compiler/asm_intrinsifier.cc b/runtime/vm/compiler/asm_intrinsifier.cc index 64da178689b..25175ebd020 100644 --- a/runtime/vm/compiler/asm_intrinsifier.cc +++ b/runtime/vm/compiler/asm_intrinsifier.cc @@ -14,18 +14,6 @@ void AsmIntrinsifier::String_identityHash(Assembler* assembler, String_getHashCode(assembler, normal_ir_body); } -void AsmIntrinsifier::RegExp_ExecuteMatch(Assembler* assembler, - Label* normal_ir_body) { - AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(assembler, normal_ir_body, - /*sticky=*/false); -} - -void AsmIntrinsifier::RegExp_ExecuteMatchSticky(Assembler* assembler, - Label* normal_ir_body) { - AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(assembler, normal_ir_body, - /*sticky=*/true); -} - #define __ assembler-> // TODO(srdjan): Add combinations (one-byte/two-byte/external strings). diff --git a/runtime/vm/compiler/asm_intrinsifier.h b/runtime/vm/compiler/asm_intrinsifier.h index 5d3ca3409cc..c2da79fb7e9 100644 --- a/runtime/vm/compiler/asm_intrinsifier.h +++ b/runtime/vm/compiler/asm_intrinsifier.h @@ -38,10 +38,6 @@ class AsmIntrinsifier : public AllStatic { #undef DECLARE_FUNCTION - static void IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky); - static void StringEquality(Assembler* assembler, Register obj1, Register obj2, diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc index dd412e75254..5e0049f5b31 100644 --- a/runtime/vm/compiler/asm_intrinsifier_arm.cc +++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc @@ -1705,39 +1705,6 @@ void AsmIntrinsifier::TwoByteString_equality(Assembler* assembler, kTwoByteStringCid); } -void AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky) { - if (FLAG_interpret_irregexp) return; - - const intptr_t kRegExpParamOffset = 2 * target::kWordSize; - const intptr_t kStringParamOffset = 1 * target::kWordSize; - // start_index smi is located at offset 0. - - // Incoming registers: - // R0: Function. (Will be reloaded with the specialized matcher function.) - // R4: Arguments descriptor. (Will be preserved.) - // R9: Unknown. (Must be GC safe on tail call.) - - // Load the specialized function pointer into R0. Leverage the fact the - // string CIDs as well as stored function pointers are in sequence. - __ ldr(R2, Address(SP, kRegExpParamOffset)); - __ ldr(R1, Address(SP, kStringParamOffset)); - __ LoadClassId(R1, R1); - __ AddImmediate(R1, -kOneByteStringCid); - __ add(R1, R2, Operand(R1, LSL, target::kWordSizeLog2)); - __ ldr(FUNCTION_REG, FieldAddress(R1, target::RegExp::function_offset( - kOneByteStringCid, sticky))); - - // Registers are now set up for the lazy compile stub. It expects the function - // in R0, the argument descriptor in R4, and IC-Data in R9. - __ eor(R9, R9, Operand(R9)); - - // Tail-call the function. - __ ldr(CODE_REG, FieldAddress(FUNCTION_REG, target::Function::code_offset())); - __ Branch(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); -} - void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler, Label* normal_ir_body) { #if !defined(SUPPORT_TIMELINE) diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc index 777d1445528..1d65e3b6570 100644 --- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc +++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc @@ -1938,47 +1938,6 @@ void AsmIntrinsifier::TwoByteString_equality(Assembler* assembler, kTwoByteStringCid); } -void AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky) { - if (FLAG_interpret_irregexp) return; - - const intptr_t kRegExpParamOffset = 2 * target::kWordSize; - const intptr_t kStringParamOffset = 1 * target::kWordSize; - // start_index smi is located at offset 0. - - // Incoming registers: - // R0: Function. (Will be reloaded with the specialized matcher function.) - // R4: Arguments descriptor. (Will be preserved.) - // R5: Unknown. (Must be GC safe on tail call.) - - // Load the specialized function pointer into R0. Leverage the fact the - // string CIDs as well as stored function pointers are in sequence. - __ ldr(R2, Address(SP, kRegExpParamOffset)); - __ ldr(R1, Address(SP, kStringParamOffset)); - __ LoadClassId(R1, R1); - __ AddImmediate(R1, -kOneByteStringCid); -#if !defined(DART_COMPRESSED_POINTERS) - __ add(R1, R2, Operand(R1, LSL, target::kWordSizeLog2)); -#else - __ add(R1, R2, Operand(R1, LSL, target::kWordSizeLog2 - 1)); -#endif - __ LoadCompressed(FUNCTION_REG, - FieldAddress(R1, target::RegExp::function_offset( - kOneByteStringCid, sticky))); - - // Registers are now set up for the lazy compile stub. It expects the function - // in R0, the argument descriptor in R4, and IC-Data in R5. - __ eor(R5, R5, Operand(R5)); - - // Tail-call the function. - __ LoadCompressed( - CODE_REG, FieldAddress(FUNCTION_REG, target::Function::code_offset())); - __ ldr(R1, - FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); - __ br(R1); -} - void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler, Label* normal_ir_body) { #if !defined(SUPPORT_TIMELINE) diff --git a/runtime/vm/compiler/asm_intrinsifier_ia32.cc b/runtime/vm/compiler/asm_intrinsifier_ia32.cc index d9143c059bb..c24432436ab 100644 --- a/runtime/vm/compiler/asm_intrinsifier_ia32.cc +++ b/runtime/vm/compiler/asm_intrinsifier_ia32.cc @@ -1743,38 +1743,6 @@ void AsmIntrinsifier::TwoByteString_equality(Assembler* assembler, kTwoByteStringCid); } -void AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky) { - if (FLAG_interpret_irregexp) return; - - const intptr_t kRegExpParamOffset = 3 * target::kWordSize; - const intptr_t kStringParamOffset = 2 * target::kWordSize; - // start_index smi is located at offset 1. - - // Incoming registers: - // EAX: Function. (Will be loaded with the specialized matcher function.) - // ECX: Unknown. (Must be GC safe on tail call.) - // EDX: Arguments descriptor. (Will be preserved.) - - // Load the specialized function pointer into EAX. Leverage the fact the - // string CIDs as well as stored function pointers are in sequence. - __ movl(EBX, Address(ESP, kRegExpParamOffset)); - __ movl(EDI, Address(ESP, kStringParamOffset)); - __ LoadClassId(EDI, EDI); - __ SubImmediate(EDI, Immediate(kOneByteStringCid)); - __ movl(FUNCTION_REG, FieldAddress(EBX, EDI, TIMES_4, - target::RegExp::function_offset( - kOneByteStringCid, sticky))); - - // Registers are now set up for the lazy compile stub. It expects the function - // in EAX, the argument descriptor in EDX, and IC-Data in ECX. - __ xorl(ECX, ECX); - - // Tail-call the function. - __ jmp(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); -} - void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler, Label* normal_ir_body) { #if !defined(SUPPORT_TIMELINE) diff --git a/runtime/vm/compiler/asm_intrinsifier_riscv.cc b/runtime/vm/compiler/asm_intrinsifier_riscv.cc index 2d45d8b4892..f1f08c16614 100644 --- a/runtime/vm/compiler/asm_intrinsifier_riscv.cc +++ b/runtime/vm/compiler/asm_intrinsifier_riscv.cc @@ -1960,41 +1960,6 @@ void AsmIntrinsifier::TwoByteString_equality(Assembler* assembler, kTwoByteStringCid); } -void AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky) { - if (FLAG_interpret_irregexp) return; - - const intptr_t kRegExpParamOffset = 2 * target::kWordSize; - const intptr_t kStringParamOffset = 1 * target::kWordSize; - // start_index smi is located at offset 0. - - // Incoming registers: - // T0: Function. (Will be reloaded with the specialized matcher function.) - // S4: Arguments descriptor. (Will be preserved.) - // S5: Unknown. (Must be GC safe on tail call.) - - // Load the specialized function pointer into T0. Leverage the fact the - // string CIDs as well as stored function pointers are in sequence. - __ lx(T2, Address(SP, kRegExpParamOffset)); - __ lx(T1, Address(SP, kStringParamOffset)); - __ LoadClassId(T1, T1); - __ AddImmediate(T1, -kOneByteStringCid); - __ slli(T1, T1, target::kWordSizeLog2); - __ add(T1, T1, T2); - __ lx(FUNCTION_REG, FieldAddress(T1, target::RegExp::function_offset( - kOneByteStringCid, sticky))); - - // Registers are now set up for the lazy compile stub. It expects the function - // in T0, the argument descriptor in S4, and IC-Data in S5. - __ li(S5, 0); - - // Tail-call the function. - __ lx(CODE_REG, FieldAddress(FUNCTION_REG, target::Function::code_offset())); - __ lx(T1, FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); - __ jr(T1); -} - void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler, Label* normal_ir_body) { #if !defined(SUPPORT_TIMELINE) diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc index a9ccb01e399..3428c6dbc4c 100644 --- a/runtime/vm/compiler/asm_intrinsifier_x64.cc +++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc @@ -1833,48 +1833,6 @@ void AsmIntrinsifier::TwoByteString_equality(Assembler* assembler, kTwoByteStringCid); } -void AsmIntrinsifier::IntrinsifyRegExpExecuteMatch(Assembler* assembler, - Label* normal_ir_body, - bool sticky) { - if (FLAG_interpret_irregexp) return; - - const intptr_t kRegExpParamOffset = 3 * target::kWordSize; - const intptr_t kStringParamOffset = 2 * target::kWordSize; - // start_index smi is located at offset 1. - - // Incoming registers: - // RAX: Function. (Will be loaded with the specialized matcher function.) - // RCX: Unknown. (Must be GC safe on tail call.) - // R10: Arguments descriptor. (Will be preserved.) - - // Load the specialized function pointer into RAX. Leverage the fact the - // string CIDs as well as stored function pointers are in sequence. - __ movq(RBX, Address(RSP, kRegExpParamOffset)); - __ movq(RDI, Address(RSP, kStringParamOffset)); - __ LoadClassId(RDI, RDI); - __ SubImmediate(RDI, Immediate(kOneByteStringCid)); -#if !defined(DART_COMPRESSED_POINTERS) - __ movq(FUNCTION_REG, FieldAddress(RBX, RDI, TIMES_8, - target::RegExp::function_offset( - kOneByteStringCid, sticky))); -#else - __ LoadCompressed(FUNCTION_REG, FieldAddress(RBX, RDI, TIMES_4, - target::RegExp::function_offset( - kOneByteStringCid, sticky))); -#endif - - // Registers are now set up for the lazy compile stub. It expects the function - // in RAX, the argument descriptor in R10, and IC-Data in RCX. - __ xorq(RCX, RCX); - - // Tail-call the function. - __ LoadCompressed( - CODE_REG, FieldAddress(FUNCTION_REG, target::Function::code_offset())); - __ movq(RDI, - FieldAddress(FUNCTION_REG, target::Function::entry_point_offset())); - __ jmp(RDI); -} - void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler, Label* normal_ir_body) { #if !defined(SUPPORT_TIMELINE) diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc index 44f3838561f..65a8fb0941a 100644 --- a/runtime/vm/compiler/backend/constant_propagator.cc +++ b/runtime/vm/compiler/backend/constant_propagator.cc @@ -1489,11 +1489,6 @@ void ConstantPropagator::VisitMathMinMax(MathMinMaxInstr* instr) { SetValue(instr, non_constant_); } -void ConstantPropagator::VisitCaseInsensitiveCompare( - CaseInsensitiveCompareInstr* instr) { - SetValue(instr, non_constant_); -} - void ConstantPropagator::VisitUnbox(UnboxInstr* instr) { const Object& value = instr->value()->definition()->constant_value(); if (IsUnknown(value)) { diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc index b1a00d7a6d9..2dfaa27f027 100644 --- a/runtime/vm/compiler/backend/il.cc +++ b/runtime/vm/compiler/backend/il.cc @@ -37,7 +37,6 @@ #include "vm/object.h" #include "vm/object_store.h" #include "vm/os.h" -#include "vm/regexp/regexp_assembler_ir.h" #include "vm/resolver.h" #include "vm/runtime_entry.h" #include "vm/scopes.h" @@ -1099,11 +1098,6 @@ bool StrictCompareInstr::AttributesEqual(const Instruction& other) const { (needs_number_check() == other_op->needs_number_check()); } -const RuntimeEntry& CaseInsensitiveCompareInstr::TargetFunction() const { - return handle_surrogates_ ? kCaseInsensitiveCompareUTF16RuntimeEntry - : kCaseInsensitiveCompareUCS2RuntimeEntry; -} - bool MathMinMaxInstr::AttributesEqual(const Instruction& other) const { auto const other_op = other.AsMathMinMax(); ASSERT(other_op != nullptr); diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h index 34712ab6611..14231d10ddd 100644 --- a/runtime/vm/compiler/backend/il.h +++ b/runtime/vm/compiler/backend/il.h @@ -501,7 +501,6 @@ struct InstrAttrs { M(MathMinMax, kNoGC) \ M(BoxInt64, _) \ M(UnboxInt64, kNoGC) \ - M(CaseInsensitiveCompare, kNoGC) \ M(BinaryInt64Op, kNoGC) \ M(UnaryInt64Op, kNoGC) \ M(CheckArrayBound, kNoGC) \ @@ -8933,67 +8932,6 @@ bool Definition::IsInt64Definition() { IsUnaryInt64Op() || IsBoxInt64() || IsUnboxInt64(); } -// Calls into the runtime and performs a case-insensitive comparison of the -// UTF16 strings (i.e. TwoByteString) located at -// str[lhs_index:lhs_index + length] and str[rhs_index:rhs_index + length]. -// Depending on [handle_surrogates], we will treat the strings as either -// UCS2 (no surrogate handling) or UTF16 (surrogates handled appropriately). -class CaseInsensitiveCompareInstr - : public TemplateDefinition<4, NoThrow, Pure> { - public: - CaseInsensitiveCompareInstr(Value* str, - Value* lhs_index, - Value* rhs_index, - Value* length, - bool handle_surrogates, - intptr_t cid) - : handle_surrogates_(handle_surrogates), cid_(cid) { - ASSERT(cid == kTwoByteStringCid); - ASSERT(index_scale() == 2); - SetInputAt(0, str); - SetInputAt(1, lhs_index); - SetInputAt(2, rhs_index); - SetInputAt(3, length); - } - - Value* str() const { return inputs_[0]; } - Value* lhs_index() const { return inputs_[1]; } - Value* rhs_index() const { return inputs_[2]; } - Value* length() const { return inputs_[3]; } - - const RuntimeEntry& TargetFunction() const; - intptr_t class_id() const { return cid_; } - - intptr_t index_scale() const { - return compiler::target::Instance::ElementSizeFor(cid_); - } - - virtual bool ComputeCanDeoptimize() const { return false; } - - virtual Representation representation() const { return kTagged; } - - DECLARE_INSTRUCTION(CaseInsensitiveCompare) - virtual CompileType ComputeType() const; - - virtual bool AttributesEqual(const Instruction& other) const { - const auto* other_compare = other.AsCaseInsensitiveCompare(); - return (other_compare->handle_surrogates_ == handle_surrogates_) && - (other_compare->cid_ == cid_); - } - -#define FIELD_LIST(F) \ - F(const bool, handle_surrogates_) \ - F(const intptr_t, cid_) - - DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(CaseInsensitiveCompareInstr, - TemplateDefinition, - FIELD_LIST) -#undef FIELD_LIST - - private: - DISALLOW_COPY_AND_ASSIGN(CaseInsensitiveCompareInstr); -}; - // Represents Math's static min and max functions. class MathMinMaxInstr : public TemplateDefinition<2, NoThrow, Pure> { public: diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc index 83c05e86c7d..bef19b22278 100644 --- a/runtime/vm/compiler/backend/il_arm.cc +++ b/runtime/vm/compiler/backend/il_arm.cc @@ -5338,28 +5338,6 @@ void SimdOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) { #undef DEFINE_EMIT -LocationSummary* CaseInsensitiveCompareInstr::MakeLocationSummary( - Zone* zone, - bool opt) const { - const intptr_t kNumTemps = 0; - LocationSummary* summary = new (zone) LocationSummary( - zone, InputCount(), kNumTemps, LocationSummary::kNativeLeafCall); - summary->set_in(0, Location::RegisterLocation(R0)); - summary->set_in(1, Location::RegisterLocation(R1)); - summary->set_in(2, Location::RegisterLocation(R2)); - summary->set_in(3, Location::RegisterLocation(R3)); - summary->set_out(0, Location::RegisterLocation(R0)); - return summary; -} - -void CaseInsensitiveCompareInstr::EmitNativeCode(FlowGraphCompiler* compiler) { - compiler::LeafRuntimeScope rt(compiler->assembler(), - /*frame_size=*/0, - /*preserve_registers=*/false); - // Call the function. Parameters are already in their correct spots. - rt.Call(TargetFunction(), TargetFunction().argument_count()); -} - LocationSummary* MathMinMaxInstr::MakeLocationSummary(Zone* zone, bool opt) const { if (representation() == kUnboxedDouble) { diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc index e504ce6fcd1..ba50347edaf 100644 --- a/runtime/vm/compiler/backend/il_arm64.cc +++ b/runtime/vm/compiler/backend/il_arm64.cc @@ -4442,28 +4442,6 @@ void SimdOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) { #undef DEFINE_EMIT -LocationSummary* CaseInsensitiveCompareInstr::MakeLocationSummary( - Zone* zone, - bool opt) const { - const intptr_t kNumTemps = 0; - LocationSummary* summary = new (zone) LocationSummary( - zone, InputCount(), kNumTemps, LocationSummary::kNativeLeafCall); - summary->set_in(0, Location::RegisterLocation(R0)); - summary->set_in(1, Location::RegisterLocation(R1)); - summary->set_in(2, Location::RegisterLocation(R2)); - summary->set_in(3, Location::RegisterLocation(R3)); - summary->set_out(0, Location::RegisterLocation(R0)); - return summary; -} - -void CaseInsensitiveCompareInstr::EmitNativeCode(FlowGraphCompiler* compiler) { - compiler::LeafRuntimeScope rt(compiler->assembler(), - /*frame_size=*/0, - /*preserve_registers=*/false); - // Call the function. Parameters are already in their correct spots. - rt.Call(TargetFunction(), TargetFunction().argument_count()); -} - LocationSummary* MathMinMaxInstr::MakeLocationSummary(Zone* zone, bool opt) const { if (representation() == kUnboxedDouble) { diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc index 481d46e94a9..a5b32102b38 100644 --- a/runtime/vm/compiler/backend/il_ia32.cc +++ b/runtime/vm/compiler/backend/il_ia32.cc @@ -4419,31 +4419,6 @@ void SimdOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) { #undef DEFINE_EMIT -LocationSummary* CaseInsensitiveCompareInstr::MakeLocationSummary( - Zone* zone, - bool opt) const { - const intptr_t kNumTemps = 0; - LocationSummary* summary = new (zone) LocationSummary( - zone, InputCount(), kNumTemps, LocationSummary::kNativeLeafCall); - summary->set_in(0, Location::RegisterLocation(EAX)); - summary->set_in(1, Location::RegisterLocation(ECX)); - summary->set_in(2, Location::RegisterLocation(EDX)); - summary->set_in(3, Location::RegisterLocation(EBX)); - summary->set_out(0, Location::RegisterLocation(EAX)); - return summary; -} - -void CaseInsensitiveCompareInstr::EmitNativeCode(FlowGraphCompiler* compiler) { - compiler::LeafRuntimeScope rt(compiler->assembler(), - /*frame_size=*/4 * compiler::target::kWordSize, - /*preserve_registers=*/false); - __ movl(compiler::Address(ESP, +0 * kWordSize), locs()->in(0).reg()); - __ movl(compiler::Address(ESP, +1 * kWordSize), locs()->in(1).reg()); - __ movl(compiler::Address(ESP, +2 * kWordSize), locs()->in(2).reg()); - __ movl(compiler::Address(ESP, +3 * kWordSize), locs()->in(3).reg()); - rt.Call(TargetFunction(), 4); -} - LocationSummary* MathMinMaxInstr::MakeLocationSummary(Zone* zone, bool opt) const { if (representation() == kUnboxedDouble) { diff --git a/runtime/vm/compiler/backend/il_riscv.cc b/runtime/vm/compiler/backend/il_riscv.cc index 05f8c0fb139..84e17950db8 100644 --- a/runtime/vm/compiler/backend/il_riscv.cc +++ b/runtime/vm/compiler/backend/il_riscv.cc @@ -4372,40 +4372,6 @@ void SimdOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } -LocationSummary* CaseInsensitiveCompareInstr::MakeLocationSummary( - Zone* zone, - bool opt) const { - const intptr_t kNumTemps = 0; - LocationSummary* summary = new (zone) LocationSummary( - zone, InputCount(), kNumTemps, LocationSummary::kNativeLeafCall); - summary->set_in(0, Location::RegisterLocation(A0)); - summary->set_in(1, Location::RegisterLocation(A1)); - summary->set_in(2, Location::RegisterLocation(A2)); - // Can't specify A3 because it is blocked in register allocation as TMP. - summary->set_in(3, Location::Any()); - summary->set_out(0, Location::RegisterLocation(A0)); - return summary; -} - -void CaseInsensitiveCompareInstr::EmitNativeCode(FlowGraphCompiler* compiler) { - if (compiler->intrinsic_mode()) { - // Would also need to preserve CODE_REG and ARGS_DESC_REG. - UNIMPLEMENTED(); - } - - compiler::LeafRuntimeScope rt(compiler->assembler(), - /*frame_size=*/0, - /*preserve_registers=*/false); - if (locs()->in(3).IsRegister()) { - __ mv(A3, locs()->in(3).reg()); - } else if (locs()->in(3).IsStackSlot()) { - __ lx(A3, LocationToStackSlotAddress(locs()->in(3))); - } else { - UNIMPLEMENTED(); - } - rt.Call(TargetFunction(), TargetFunction().argument_count()); -} - LocationSummary* MathMinMaxInstr::MakeLocationSummary(Zone* zone, bool opt) const { if (representation() == kUnboxedDouble) { diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc index 0ffc45d7b48..d35cf8b7641 100644 --- a/runtime/vm/compiler/backend/il_x64.cc +++ b/runtime/vm/compiler/backend/il_x64.cc @@ -4640,28 +4640,6 @@ void SimdOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) { #undef DEFINE_EMIT -LocationSummary* CaseInsensitiveCompareInstr::MakeLocationSummary( - Zone* zone, - bool opt) const { - const intptr_t kNumTemps = 0; - LocationSummary* summary = new (zone) LocationSummary( - zone, InputCount(), kNumTemps, LocationSummary::kNativeLeafCall); - summary->set_in(0, Location::RegisterLocation(CallingConventions::kArg1Reg)); - summary->set_in(1, Location::RegisterLocation(CallingConventions::kArg2Reg)); - summary->set_in(2, Location::RegisterLocation(CallingConventions::kArg3Reg)); - summary->set_in(3, Location::RegisterLocation(CallingConventions::kArg4Reg)); - summary->set_out(0, Location::RegisterLocation(RAX)); - return summary; -} - -void CaseInsensitiveCompareInstr::EmitNativeCode(FlowGraphCompiler* compiler) { - compiler::LeafRuntimeScope rt(compiler->assembler(), - /*frame_size=*/0, - /*preserve_registers=*/false); - // Call the function. Parameters are already in their correct spots. - rt.Call(TargetFunction(), TargetFunction().argument_count()); -} - LocationSummary* UnarySmiOpInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index da1e9543f28..92dd13db25e 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -9,7 +9,6 @@ #include "vm/bit_vector.h" #include "vm/compiler/compiler_state.h" #include "vm/object_store.h" -#include "vm/regexp/regexp_assembler.h" #include "vm/resolver.h" #include "vm/timeline.h" @@ -1210,23 +1209,6 @@ CompileType ParameterInstr::ComputeType() const { const ParsedFunction& pf = graph_entry->parsed_function(); const Function& function = pf.function(); - if (function.IsIrregexpFunction()) { - // In irregexp functions, types of input parameters are known and immutable. - // Set parameter types here in order to prevent unnecessary CheckClassInstr - // from being generated. - switch (env_index()) { - case RegExpMacroAssembler::kParamRegExpIndex: - return CompileType::FromCid(kRegExpCid); - case RegExpMacroAssembler::kParamStringIndex: - return CompileType::FromCid(function.string_specialization_cid()); - case RegExpMacroAssembler::kParamStartOffsetIndex: - return CompileType::FromCid(kSmiCid); - default: - UNREACHABLE(); - } - UNREACHABLE(); - return CompileType::Dynamic(); - } const intptr_t param_index = this->param_index(); if (param_index >= 0) { @@ -1775,10 +1757,6 @@ CompileType MathMinMaxInstr::ComputeType() const { return CompileType::FromUnboxedRepresentation(representation()); } -CompileType CaseInsensitiveCompareInstr::ComputeType() const { - return CompileType::FromCid(kBoolCid); -} - CompileType BoxInstr::ComputeType() const { return CompileType::FromUnboxedRepresentation(from_representation()); } diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc index de5e974a676..14b98c6a5b5 100644 --- a/runtime/vm/compiler/jit/compiler.cc +++ b/runtime/vm/compiler/jit/compiler.cc @@ -37,8 +37,6 @@ #include "vm/object_store.h" #include "vm/os.h" #include "vm/parser.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/regexp/regexp_parser.h" #include "vm/runtime_entry.h" #include "vm/symbols.h" #include "vm/tags.h" @@ -96,7 +94,6 @@ static void PrecompilationModeHandler(bool value) { FLAG_background_compilation = false; FLAG_enable_mirrors = false; - FLAG_interpret_irregexp = true; FLAG_link_natives_lazily = true; FLAG_optimization_counter_threshold = -1; FLAG_polymorphic_with_deopt = false; @@ -121,74 +118,12 @@ DEFINE_FLAG_HANDLER(PrecompilationModeHandler, #ifndef DART_PRECOMPILED_RUNTIME -static FlowGraph* BuildIrregexpFunctionFlowGraph( - Zone* zone, - ParsedFunction* parsed_function, - ZoneGrowableArray* ic_data_array, - intptr_t osr_id, - bool optimized) { - if (parsed_function->regexp_compile_data() == nullptr) { - VMTagScope tagScope(parsed_function->thread(), - VMTag::kCompileParseRegExpTagId); - RegExp& regexp = RegExp::Handle(parsed_function->function().regexp()); - - const String& pattern = String::Handle(regexp.pattern()); - - RegExpCompileData* compile_data = new (zone) RegExpCompileData(); - // Parsing failures are handled in the RegExp factory constructor. - RegExpParser::ParseRegExp(pattern, regexp.flags(), compile_data); - - regexp.set_num_bracket_expressions(compile_data->capture_count); - regexp.set_capture_name_map(compile_data->capture_name_map); - if (compile_data->simple) { - regexp.set_is_simple(); - } else { - regexp.set_is_complex(); - } - - parsed_function->SetRegExpCompileData(compile_data); - - // Variables are allocated after compilation. - } - - // Compile to the dart IR. - RegExpEngine::CompilationResult result = - RegExpEngine::CompileIR(parsed_function->regexp_compile_data(), - parsed_function, *ic_data_array, osr_id); - if (result.error_message != nullptr) { - Report::LongJump(LanguageError::Handle( - LanguageError::New(String::Handle(String::New(result.error_message))))); - } - - // Allocate variables now that we know the number of locals. - parsed_function->AllocateIrregexpVariables(result.num_stack_locals); - - // When compiling for OSR, use a depth first search to find the OSR - // entry and make graph entry jump to it instead of normal entry. - // Catch entries are always considered reachable, even if they - // become unreachable after OSR. - if (osr_id != Compiler::kNoOSRDeoptId) { - auto osr_result = result.graph_entry->FindOsrEntry(zone, result.num_blocks); - // No try-catch in irregexps, so we can pass nullptr as flow_graph_builder. - ASSERT(osr_result->try_entries_length() == 0); - kernel::FlowGraphBuilder::RelinkToOsrEntry(/*builder=*/nullptr, osr_result); - } - PrologueInfo prologue_info(-1, -1); - return new (zone) - FlowGraph(*parsed_function, result.graph_entry, result.num_blocks, - prologue_info, FlowGraph::CompilationModeFrom(optimized)); -} - FlowGraph* Compiler::BuildFlowGraph( Zone* zone, ParsedFunction* parsed_function, ZoneGrowableArray* ic_data_array, intptr_t osr_id, bool optimized) { - if (parsed_function->function().IsIrregexpFunction()) { - return BuildIrregexpFunctionFlowGraph(zone, parsed_function, ic_data_array, - osr_id, optimized); - } kernel::FlowGraphBuilder builder(parsed_function, ic_data_array, /* not building var desc */ nullptr, /* not inlining */ nullptr, optimized, @@ -615,7 +550,6 @@ CodePtr CompileParsedFunctionHelper::Compile() { } else { // We bailed out or we encountered an error. const Error& error = Error::Handle(thread()->StealStickyError()); - if (error.ptr() == Object::branch_offset_error().ptr()) { // Compilation failed due to an out of range branch offset in the // assembler. We try again (done = false) with far branches enabled. diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index 6795f67f91d..acc216892b9 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -560,9 +560,6 @@ namespace dart { V(CoreLibrary, _Double, get:isNegative, Double_getIsNegative, 0xd45438d1) \ V(CoreLibrary, _Double, _mulFromInteger, Double_mulFromInteger, 0xecd1beaf) \ V(CoreLibrary, _Double, .fromInteger, DoubleFromInteger, 0x7cf2c1d9) \ - V(CoreLibrary, _RegExp, _ExecuteMatch, RegExp_ExecuteMatch, 0x98f4bd89) \ - V(CoreLibrary, _RegExp, _ExecuteMatchSticky, RegExp_ExecuteMatchSticky, \ - 0x91c0704f) \ V(CoreLibrary, Object, ==, ObjectEquals, 0x463b5870) \ V(CoreLibrary, Object, get:runtimeType, ObjectRuntimeType, 0x0364b091) \ V(CoreLibrary, Object, _haveSameRuntimeType, ObjectHaveSameRuntimeType, \ diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h index 7e09937e69f..b03a06d1d02 100644 --- a/runtime/vm/compiler/runtime_offsets_extracted.h +++ b/runtime/vm/compiler/runtime_offsets_extracted.h @@ -346,7 +346,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x41c; + 0x414; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -359,15 +359,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x45c; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x454; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x420; + Thread_double_truncate_round_supported_offset = 0x418; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x460; + Thread_service_extension_stream_offset = 0x458; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x464; + 0x45c; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -483,7 +483,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x34c; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x3c; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x44c; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x444; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0xe8; static constexpr dart::compiler::target::word @@ -530,21 +530,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x428; + Thread_unboxed_runtime_arg_offset = 0x420; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x330; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x438; -static constexpr dart::compiler::target::word Thread_random_offset = 0x440; + 0x430; +static constexpr dart::compiler::target::word Thread_random_offset = 0x438; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x448; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x454; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x458; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x450; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x440; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x44c; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x450; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x448; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -1071,7 +1071,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x850; + 0x840; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -1084,15 +1084,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8a8; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x898; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x858; + Thread_double_truncate_round_supported_offset = 0x848; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8b0; + Thread_service_extension_stream_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -1208,7 +1208,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x6b0; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x78; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x888; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x878; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -1255,21 +1255,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x860; + Thread_unboxed_runtime_arg_offset = 0x850; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x678; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x870; -static constexpr dart::compiler::target::word Thread_random_offset = 0x878; + 0x860; +static constexpr dart::compiler::target::word Thread_random_offset = 0x868; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x880; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x898; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8a0; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x870; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x888; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x880; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -1796,7 +1796,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x410; + 0x408; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -1809,15 +1809,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x44c; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x444; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x414; + Thread_double_truncate_round_supported_offset = 0x40c; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x450; + Thread_service_extension_stream_offset = 0x448; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x454; + 0x44c; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -1933,7 +1933,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x340; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x3c; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x43c; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x434; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0xe8; static constexpr dart::compiler::target::word @@ -1980,21 +1980,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x418; + Thread_unboxed_runtime_arg_offset = 0x410; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x324; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x428; -static constexpr dart::compiler::target::word Thread_random_offset = 0x430; + 0x420; +static constexpr dart::compiler::target::word Thread_random_offset = 0x428; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x438; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x444; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x448; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x440; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x430; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x43c; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x440; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x438; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -2520,7 +2520,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -2533,15 +2533,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8f0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x8a0; + Thread_double_truncate_round_supported_offset = 0x890; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8f8; + Thread_service_extension_stream_offset = 0x8e8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x900; + 0x8f0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -2657,7 +2657,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x6f8; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x78; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8d0; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8c0; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -2704,21 +2704,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x8a8; + Thread_unboxed_runtime_arg_offset = 0x898; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6c0; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8b8; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8c0; + 0x8a8; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8b0; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8c8; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8e0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8e8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8b8; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d0; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8c8; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -3249,7 +3249,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x858; + 0x848; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -3262,15 +3262,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x210; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xe0; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8b0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x860; + Thread_double_truncate_round_supported_offset = 0x850; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8b8; + Thread_service_extension_stream_offset = 0x8a8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8c0; + 0x8b0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -3386,7 +3386,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x6b8; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x80; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x890; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x880; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0x1d8; static constexpr dart::compiler::target::word @@ -3433,7 +3433,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x60; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x868; + Thread_unboxed_runtime_arg_offset = 0x858; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x680; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x200; @@ -3441,14 +3441,14 @@ static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x878; -static constexpr dart::compiler::target::word Thread_random_offset = 0x880; + 0x868; +static constexpr dart::compiler::target::word Thread_random_offset = 0x870; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x278; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x888; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8a0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8a8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x898; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x878; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x898; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x888; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -3975,7 +3975,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -3988,15 +3988,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x210; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xe0; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8f8; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e8; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x8a8; + Thread_double_truncate_round_supported_offset = 0x898; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x900; + Thread_service_extension_stream_offset = 0x8f0; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x908; + 0x8f8; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -4112,7 +4112,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x700; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x80; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8c8; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0x1d8; static constexpr dart::compiler::target::word @@ -4159,7 +4159,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x60; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x8b0; + Thread_unboxed_runtime_arg_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6c8; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x200; @@ -4167,14 +4167,14 @@ static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8c0; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8c8; + 0x8b0; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8b8; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x278; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8d0; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8e8; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8f0; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8c0; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8d0; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -4701,7 +4701,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x444; + 0x43c; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -4714,15 +4714,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x484; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x47c; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x448; + Thread_double_truncate_round_supported_offset = 0x440; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x488; + Thread_service_extension_stream_offset = 0x480; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x48c; + 0x484; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -4838,7 +4838,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x374; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x3c; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x474; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x46c; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0xe8; static constexpr dart::compiler::target::word @@ -4885,21 +4885,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x450; + Thread_unboxed_runtime_arg_offset = 0x448; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x358; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x460; -static constexpr dart::compiler::target::word Thread_random_offset = 0x468; + 0x458; +static constexpr dart::compiler::target::word Thread_random_offset = 0x460; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x470; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x47c; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x480; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x478; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x468; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x474; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x478; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x470; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -5427,7 +5427,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -5440,15 +5440,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8d0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x890; + Thread_double_truncate_round_supported_offset = 0x880; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8e8; + Thread_service_extension_stream_offset = 0x8d8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -5564,7 +5564,7 @@ static constexpr dart::compiler::target::word Thread_safepoint_state_offset = 0x6e8; static constexpr dart::compiler::target::word Thread_shared_field_table_values_offset = 0x78; -static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8c0; +static constexpr dart::compiler::target::word Thread_single_step_offset = 0x8b0; static constexpr dart::compiler::target::word Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -5611,21 +5611,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x898; + Thread_unboxed_runtime_arg_offset = 0x888; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6b0; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8a8; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8b0; + 0x898; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8b8; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8d8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8a8; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8c0; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8b8; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -6146,7 +6146,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x41c; + 0x414; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -6159,15 +6159,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x45c; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x454; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x420; + Thread_double_truncate_round_supported_offset = 0x418; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x460; + Thread_service_extension_stream_offset = 0x458; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x464; + 0x45c; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -6329,21 +6329,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x428; + Thread_unboxed_runtime_arg_offset = 0x420; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x330; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x438; -static constexpr dart::compiler::target::word Thread_random_offset = 0x440; + 0x430; +static constexpr dart::compiler::target::word Thread_random_offset = 0x438; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x448; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x454; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x458; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x450; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x440; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x44c; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x450; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x448; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -6863,7 +6863,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x850; + 0x840; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -6876,15 +6876,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8a8; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x898; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x858; + Thread_double_truncate_round_supported_offset = 0x848; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8b0; + Thread_service_extension_stream_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -7046,21 +7046,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x860; + Thread_unboxed_runtime_arg_offset = 0x850; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x678; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x870; -static constexpr dart::compiler::target::word Thread_random_offset = 0x878; + 0x860; +static constexpr dart::compiler::target::word Thread_random_offset = 0x868; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x880; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x898; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8a0; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x870; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x888; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x880; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -7580,7 +7580,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x410; + 0x408; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -7593,15 +7593,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x44c; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x444; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x414; + Thread_double_truncate_round_supported_offset = 0x40c; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x450; + Thread_service_extension_stream_offset = 0x448; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x454; + 0x44c; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -7763,21 +7763,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x418; + Thread_unboxed_runtime_arg_offset = 0x410; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x324; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x428; -static constexpr dart::compiler::target::word Thread_random_offset = 0x430; + 0x420; +static constexpr dart::compiler::target::word Thread_random_offset = 0x428; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x438; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x444; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x448; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x440; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x430; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x43c; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x440; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x438; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -8296,7 +8296,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -8309,15 +8309,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8f0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x8a0; + Thread_double_truncate_round_supported_offset = 0x890; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8f8; + Thread_service_extension_stream_offset = 0x8e8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x900; + 0x8f0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -8479,21 +8479,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x8a8; + Thread_unboxed_runtime_arg_offset = 0x898; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6c0; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8b8; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8c0; + 0x8a8; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8b0; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8c8; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8e0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8e8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8b8; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d0; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8c8; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -9017,7 +9017,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x858; + 0x848; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -9030,15 +9030,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x210; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xe0; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8b0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x860; + Thread_double_truncate_round_supported_offset = 0x850; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8b8; + Thread_service_extension_stream_offset = 0x8a8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8c0; + 0x8b0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -9200,7 +9200,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x60; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x868; + Thread_unboxed_runtime_arg_offset = 0x858; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x680; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x200; @@ -9208,14 +9208,14 @@ static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x878; -static constexpr dart::compiler::target::word Thread_random_offset = 0x880; + 0x868; +static constexpr dart::compiler::target::word Thread_random_offset = 0x870; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x278; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x888; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8a0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8a8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x898; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x878; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x890; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x898; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x888; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -9735,7 +9735,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -9748,15 +9748,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x210; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xe0; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8f8; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e8; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x8a8; + Thread_double_truncate_round_supported_offset = 0x898; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x900; + Thread_service_extension_stream_offset = 0x8f0; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x908; + 0x8f8; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -9918,7 +9918,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x60; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x8b0; + Thread_unboxed_runtime_arg_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6c8; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x200; @@ -9926,14 +9926,14 @@ static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8c0; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8c8; + 0x8b0; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8b8; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x278; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8d0; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8e8; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8f0; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8c0; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8d0; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -10453,7 +10453,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x444; + 0x43c; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -10466,15 +10466,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x104; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0x6c; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x484; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x47c; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x448; + Thread_double_truncate_round_supported_offset = 0x440; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x488; + Thread_service_extension_stream_offset = 0x480; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x48c; + 0x484; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -10636,21 +10636,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x450; + Thread_unboxed_runtime_arg_offset = 0x448; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x358; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x460; -static constexpr dart::compiler::target::word Thread_random_offset = 0x468; + 0x458; +static constexpr dart::compiler::target::word Thread_random_offset = 0x460; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x138; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x470; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x47c; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x480; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x478; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x468; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x474; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x478; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x470; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -11171,7 +11171,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word Thread_api_top_scope_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -11184,15 +11184,15 @@ static constexpr dart::compiler::target::word Thread_call_to_runtime_entry_point_offset = 0x208; static constexpr dart::compiler::target::word Thread_call_to_runtime_stub_offset = 0xd8; -static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8e0; +static constexpr dart::compiler::target::word Thread_dart_stream_offset = 0x8d0; static constexpr dart::compiler::target::word Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - Thread_double_truncate_round_supported_offset = 0x890; + Thread_double_truncate_round_supported_offset = 0x880; static constexpr dart::compiler::target::word - Thread_service_extension_stream_offset = 0x8e8; + Thread_service_extension_stream_offset = 0x8d8; static constexpr dart::compiler::target::word Thread_thread_locals_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word Thread_optimize_stub_offset = @@ -11354,21 +11354,21 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word Thread_top_offset = 0x58; static constexpr dart::compiler::target::word Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - Thread_unboxed_runtime_arg_offset = 0x898; + Thread_unboxed_runtime_arg_offset = 0x888; static constexpr dart::compiler::target::word Thread_vm_tag_offset = 0x6b0; static constexpr dart::compiler::target::word Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word Thread_next_task_id_offset = - 0x8a8; -static constexpr dart::compiler::target::word Thread_random_offset = 0x8b0; + 0x898; +static constexpr dart::compiler::target::word Thread_random_offset = 0x8a0; static constexpr dart::compiler::target::word Thread_jump_to_frame_entry_point_offset = 0x270; -static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8b8; -static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8d0; -static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8d8; -static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word Thread_tsan_utils_offset = 0x8a8; +static constexpr dart::compiler::target::word Thread_current_tag_offset = 0x8c0; +static constexpr dart::compiler::target::word Thread_default_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word Thread_user_tag_offset = 0x8b8; static constexpr dart::compiler::target::word TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word TsanUtils_setjmp_buffer_offset = @@ -11925,7 +11925,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x41c; + 0x414; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -11941,15 +11941,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0x6c; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x45c; + 0x454; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x420; + AOT_Thread_double_truncate_round_supported_offset = 0x418; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x460; + AOT_Thread_service_extension_stream_offset = 0x458; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x464; + 0x45c; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -12071,7 +12071,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x3c; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x44c; + 0x444; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0xe8; static constexpr dart::compiler::target::word @@ -12122,25 +12122,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x428; + AOT_Thread_unboxed_runtime_arg_offset = 0x420; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x330; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x438; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x440; + 0x430; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x438; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x138; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x448; + 0x440; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x454; + 0x44c; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x458; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x450; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x448; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -12729,7 +12729,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x850; + 0x840; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -12745,15 +12745,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8a8; + 0x898; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x858; + AOT_Thread_double_truncate_round_supported_offset = 0x848; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8b0; + AOT_Thread_service_extension_stream_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -12875,7 +12875,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x78; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -12926,25 +12926,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x860; + AOT_Thread_unboxed_runtime_arg_offset = 0x850; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x678; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x870; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x878; + 0x860; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x868; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x880; + 0x870; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8a0; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x890; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x880; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -13540,7 +13540,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -13556,15 +13556,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x8a0; + AOT_Thread_double_truncate_round_supported_offset = 0x890; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8f8; + AOT_Thread_service_extension_stream_offset = 0x8e8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x900; + 0x8f0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -13686,7 +13686,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x78; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x8d0; + 0x8c0; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -13737,25 +13737,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x8a8; + AOT_Thread_unboxed_runtime_arg_offset = 0x898; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6c0; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8b8; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8c0; + 0x8a8; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b0; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8c8; + 0x8b8; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8e0; + 0x8d0; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8e8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8c8; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -14347,7 +14347,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x858; + 0x848; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -14363,15 +14363,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xe0; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8b0; + 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x860; + AOT_Thread_double_truncate_round_supported_offset = 0x850; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8b8; + AOT_Thread_service_extension_stream_offset = 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8c0; + 0x8b0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -14493,7 +14493,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x80; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x890; + 0x880; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0x1d8; static constexpr dart::compiler::target::word @@ -14544,7 +14544,7 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x60; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x868; + AOT_Thread_unboxed_runtime_arg_offset = 0x858; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x680; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x200; @@ -14553,18 +14553,18 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x878; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x880; + 0x868; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x870; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x278; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8a8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x898; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x888; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -15154,7 +15154,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -15170,15 +15170,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xe0; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8f8; + 0x8e8; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x8a8; + AOT_Thread_double_truncate_round_supported_offset = 0x898; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x900; + AOT_Thread_service_extension_stream_offset = 0x8f0; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x908; + 0x8f8; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -15300,7 +15300,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x80; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x8d8; + 0x8c8; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0x1d8; static constexpr dart::compiler::target::word @@ -15351,7 +15351,7 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x60; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x8b0; + AOT_Thread_unboxed_runtime_arg_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6c8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x200; @@ -15360,18 +15360,18 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8c0; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8c8; + 0x8b0; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b8; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x278; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8d0; + 0x8c0; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8e8; + 0x8d8; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8f0; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8d0; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -15963,7 +15963,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x444; + 0x43c; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -15979,15 +15979,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0x6c; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x484; + 0x47c; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x448; + AOT_Thread_double_truncate_round_supported_offset = 0x440; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x488; + AOT_Thread_service_extension_stream_offset = 0x480; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x48c; + 0x484; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -16109,7 +16109,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x3c; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x474; + 0x46c; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0xe8; static constexpr dart::compiler::target::word @@ -16160,25 +16160,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x450; + AOT_Thread_unboxed_runtime_arg_offset = 0x448; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x358; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x460; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x468; + 0x458; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x460; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x138; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x470; + 0x468; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x47c; + 0x474; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x480; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x478; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x470; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -16768,7 +16768,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -16784,15 +16784,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8e0; + 0x8d0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x890; + AOT_Thread_double_truncate_round_supported_offset = 0x880; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8e8; + AOT_Thread_service_extension_stream_offset = 0x8d8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -16914,7 +16914,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_shared_field_table_values_offset = 0x78; static constexpr dart::compiler::target::word AOT_Thread_single_step_offset = - 0x8c0; + 0x8b0; static constexpr dart::compiler::target::word AOT_Thread_slow_type_test_stub_offset = 0x1d0; static constexpr dart::compiler::target::word @@ -16965,25 +16965,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x898; + AOT_Thread_unboxed_runtime_arg_offset = 0x888; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6b0; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8a8; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b0; + 0x898; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8d0; + 0x8c0; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8d8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8b8; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -17567,7 +17567,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x41c; + 0x414; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -17583,15 +17583,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0x6c; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x45c; + 0x454; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x420; + AOT_Thread_double_truncate_round_supported_offset = 0x418; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x460; + AOT_Thread_service_extension_stream_offset = 0x458; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x464; + 0x45c; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -17762,25 +17762,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x428; + AOT_Thread_unboxed_runtime_arg_offset = 0x420; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x330; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x438; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x440; + 0x430; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x438; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x138; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x448; + 0x440; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x454; + 0x44c; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x458; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x450; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x448; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -18362,7 +18362,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x850; + 0x840; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -18378,15 +18378,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8a8; + 0x898; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x858; + AOT_Thread_double_truncate_round_supported_offset = 0x848; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8b0; + AOT_Thread_service_extension_stream_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -18557,25 +18557,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x860; + AOT_Thread_unboxed_runtime_arg_offset = 0x850; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x678; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x870; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x878; + 0x860; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x868; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x880; + 0x870; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8a0; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x890; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x880; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -19164,7 +19164,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x898; + 0x888; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -19180,15 +19180,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x8a0; + AOT_Thread_double_truncate_round_supported_offset = 0x890; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8f8; + AOT_Thread_service_extension_stream_offset = 0x8e8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x900; + 0x8f0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -19359,25 +19359,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x8a8; + AOT_Thread_unboxed_runtime_arg_offset = 0x898; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6c0; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8b8; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8c0; + 0x8a8; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b0; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8c8; + 0x8b8; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8e0; + 0x8d0; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8e8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8d8; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8c8; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -19962,7 +19962,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x858; + 0x848; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -19978,15 +19978,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xe0; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8b0; + 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x860; + AOT_Thread_double_truncate_round_supported_offset = 0x850; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8b8; + AOT_Thread_service_extension_stream_offset = 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8c0; + 0x8b0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -20157,7 +20157,7 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x60; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x868; + AOT_Thread_unboxed_runtime_arg_offset = 0x858; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x680; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x200; @@ -20166,18 +20166,18 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x878; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x880; + 0x868; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x870; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x278; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8a8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x898; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x888; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -20760,7 +20760,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x238; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x8a0; + 0x890; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x168; static constexpr dart::compiler::target::word @@ -20776,15 +20776,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xe0; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8f8; + 0x8e8; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x70; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x8a8; + AOT_Thread_double_truncate_round_supported_offset = 0x898; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x900; + AOT_Thread_service_extension_stream_offset = 0x8f0; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x908; + 0x8f8; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x260; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -20955,7 +20955,7 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x60; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x8b0; + AOT_Thread_unboxed_runtime_arg_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6c8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x200; @@ -20964,18 +20964,18 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_heap_base_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8c0; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8c8; + 0x8b0; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b8; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x278; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8d0; + 0x8c0; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8e8; + 0x8d8; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8f0; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8e0; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8d0; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -21560,7 +21560,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x118; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x444; + 0x43c; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0xb0; static constexpr dart::compiler::target::word @@ -21576,15 +21576,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0x6c; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x484; + 0x47c; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x34; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x448; + AOT_Thread_double_truncate_round_supported_offset = 0x440; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x488; + AOT_Thread_service_extension_stream_offset = 0x480; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x48c; + 0x484; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x12c; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -21755,25 +21755,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x2c; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x10; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x450; + AOT_Thread_unboxed_runtime_arg_offset = 0x448; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x358; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0xfc; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x28; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x460; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x468; + 0x458; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x460; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x138; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x470; + 0x468; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x47c; + 0x474; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x480; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x478; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x470; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word @@ -22356,7 +22356,7 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_allocate_object_slow_entry_point_offset = 0x230; static constexpr dart::compiler::target::word AOT_Thread_api_top_scope_offset = - 0x888; + 0x878; static constexpr dart::compiler::target::word AOT_Thread_async_exception_handler_stub_offset = 0x160; static constexpr dart::compiler::target::word @@ -22372,15 +22372,15 @@ static constexpr dart::compiler::target::word static constexpr dart::compiler::target::word AOT_Thread_call_to_runtime_stub_offset = 0xd8; static constexpr dart::compiler::target::word AOT_Thread_dart_stream_offset = - 0x8e0; + 0x8d0; static constexpr dart::compiler::target::word AOT_Thread_dispatch_table_array_offset = 0x68; static constexpr dart::compiler::target::word - AOT_Thread_double_truncate_round_supported_offset = 0x890; + AOT_Thread_double_truncate_round_supported_offset = 0x880; static constexpr dart::compiler::target::word - AOT_Thread_service_extension_stream_offset = 0x8e8; + AOT_Thread_service_extension_stream_offset = 0x8d8; static constexpr dart::compiler::target::word AOT_Thread_thread_locals_offset = - 0x8f0; + 0x8e0; static constexpr dart::compiler::target::word AOT_Thread_optimize_entry_offset = 0x258; static constexpr dart::compiler::target::word AOT_Thread_optimize_stub_offset = @@ -22551,25 +22551,25 @@ static constexpr dart::compiler::target::word AOT_Thread_top_offset = 0x58; static constexpr dart::compiler::target::word AOT_Thread_top_resource_offset = 0x20; static constexpr dart::compiler::target::word - AOT_Thread_unboxed_runtime_arg_offset = 0x898; + AOT_Thread_unboxed_runtime_arg_offset = 0x888; static constexpr dart::compiler::target::word AOT_Thread_vm_tag_offset = 0x6b0; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_entry_point_offset = 0x1f8; static constexpr dart::compiler::target::word AOT_Thread_write_barrier_mask_offset = 0x50; static constexpr dart::compiler::target::word AOT_Thread_next_task_id_offset = - 0x8a8; -static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8b0; + 0x898; +static constexpr dart::compiler::target::word AOT_Thread_random_offset = 0x8a0; static constexpr dart::compiler::target::word AOT_Thread_jump_to_frame_entry_point_offset = 0x270; static constexpr dart::compiler::target::word AOT_Thread_tsan_utils_offset = - 0x8b8; + 0x8a8; static constexpr dart::compiler::target::word AOT_Thread_current_tag_offset = - 0x8d0; + 0x8c0; static constexpr dart::compiler::target::word AOT_Thread_default_tag_offset = - 0x8d8; -static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = 0x8c8; +static constexpr dart::compiler::target::word AOT_Thread_user_tag_offset = + 0x8b8; static constexpr dart::compiler::target::word AOT_TsanUtils_setjmp_function_offset = 0x0; static constexpr dart::compiler::target::word diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 24c0eb33718..7b32dbcfdb7 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -7080,54 +7080,6 @@ DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id) { #endif } -#if (!defined(TARGET_ARCH_IA32) && !defined(DART_PRECOMPILED_RUNTIME)) - -// Any flag that affects how we compile code might cause a problem when the -// snapshot writer generates code with one value of the flag and the snapshot -// reader expects code to behave according to another value of the flag. -// Normally, we add these flags to Dart::FeaturesString and refuse to run the -// snapshot it they don't match, but since --interpret-irregexp affects only -// 2 functions we choose to remove the code instead. See issue #34422. -static void DropRegExpMatchCode(Zone* zone) { - const String& execute_match_name = - String::Handle(zone, String::New("_ExecuteMatch")); - const String& execute_match_sticky_name = - String::Handle(zone, String::New("_ExecuteMatchSticky")); - - const Library& core_lib = Library::Handle(zone, Library::CoreLibrary()); - const Class& reg_exp_class = - Class::Handle(zone, core_lib.LookupClassAllowPrivate(Symbols::_RegExp())); - ASSERT(!reg_exp_class.IsNull()); - - auto thread = Thread::Current(); - Function& func = Function::Handle( - zone, reg_exp_class.LookupFunctionAllowPrivate(execute_match_name)); - ASSERT(!func.IsNull()); - Code& code = Code::Handle(zone); - SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); - if (func.HasCode()) { - code = func.CurrentCode(); - ASSERT(!code.IsNull()); - code.DisableDartCode(); - } - func.ClearCode(); - func.ClearICDataArray(); - ASSERT(!func.HasCode()); - - func = reg_exp_class.LookupFunctionAllowPrivate(execute_match_sticky_name); - ASSERT(!func.IsNull()); - if (func.HasCode()) { - code = func.CurrentCode(); - ASSERT(!code.IsNull()); - code.DisableDartCode(); - } - func.ClearCode(); - func.ClearICDataArray(); - ASSERT(!func.HasCode()); -} - -#endif // (!defined(TARGET_ARCH_IA32) && !defined(DART_PRECOMPILED_RUNTIME)) - #if !defined(TARGET_ARCH_IA32) && !defined(DART_PRECOMPILED_RUNTIME) static void KillNonMainIsolatesSlow(Thread* thread, Isolate* main_isolate) { auto group = main_isolate->group(); @@ -7181,7 +7133,6 @@ Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer, KillNonMainIsolatesSlow(T, I); NoBackgroundCompilerScope no_bg_compiler(T); - DropRegExpMatchCode(Z); ProgramVisitor::Dedup(T); diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h index e3f48642aaa..4e612b0fdc9 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -130,7 +130,6 @@ constexpr bool FLAG_support_il_printer = false; "Consider thread pool isolates for idle tasks after this long.") \ P(idle_duration_micros, int, kMaxInt32, \ "Allow idle tasks to run for this long.") \ - P(interpret_irregexp, bool, false, "Use irregexp bytecode interpreter") \ C(interpreter, false, false, bool, false, "Use bytecode interpreter") \ P(link_natives_lazily, bool, false, "Link native calls lazily") \ R(log_marker_tasks, false, bool, false, \ diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index d294371c082..a699abae15e 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -59,7 +59,6 @@ #include "vm/os.h" #include "vm/parser.h" #include "vm/profiler.h" -#include "vm/regexp/regexp.h" #include "vm/resolver.h" #include "vm/reusable_handles.h" #include "vm/reverse_pc_lookup_cache.h" @@ -27535,26 +27534,6 @@ void RegExp::set_pattern(const String& pattern) const { untag()->set_pattern(pattern.ptr()); } -void RegExp::set_function(intptr_t cid, - bool sticky, - const Function& value) const { - if (sticky) { - switch (cid) { - case kOneByteStringCid: - return untag()->set_one_byte_sticky(value.ptr()); - case kTwoByteStringCid: - return untag()->set_two_byte_sticky(value.ptr()); - } - } else { - switch (cid) { - case kOneByteStringCid: - return untag()->set_one_byte(value.ptr()); - case kTwoByteStringCid: - return untag()->set_two_byte(value.ptr()); - } - } -} - void RegExp::set_bytecode(bool is_one_byte, bool sticky, const TypedData& bytecode) const { @@ -27581,61 +27560,49 @@ void RegExp::set_capture_name_map(const Array& array) const { untag()->set_capture_name_map(array.ptr()); } -RegExpPtr RegExp::New(Zone* zone, Heap::Space space) { - const auto& result = RegExp::Handle(Object::Allocate(space)); - ASSERT_EQUAL(result.type(), kUninitialized); - ASSERT(result.flags() == RegExpFlags()); +RegExpPtr RegExp::New(const String& pattern, RegExpFlags flags) { + const auto& result = RegExp::Handle(Object::Allocate(Heap::kNew)); + result.set_pattern(pattern); + result.set_flags(flags); result.set_num_bracket_expressions(-1); result.set_num_registers(/*is_one_byte=*/false, -1); result.set_num_registers(/*is_one_byte=*/true, -1); - - if (!FLAG_interpret_irregexp) { - auto thread = Thread::Current(); - const Library& lib = Library::Handle(zone, Library::CoreLibrary()); - const Class& owner = - Class::Handle(zone, lib.LookupClass(Symbols::RegExp())); - - for (intptr_t cid = kOneByteStringCid; cid <= kTwoByteStringCid; cid++) { - CreateSpecializedFunction(thread, zone, result, cid, /*sticky=*/false, - owner); - CreateSpecializedFunction(thread, zone, result, cid, /*sticky=*/true, - owner); - } - } return result.ptr(); } -const char* RegExpFlags::ToCString() const { - switch (value_ & ~kGlobal) { - case kIgnoreCase | kMultiLine | kDotAll | kUnicode: +const char* FlagsToCString(RegExpFlags flags) { + switch (flags & ~RegExpFlag::kGlobal) { + case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | + RegExpFlag::kDotAll | RegExpFlag::kUnicode: return "imsu"; - case kIgnoreCase | kMultiLine | kDotAll: + case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | RegExpFlag::kDotAll: return "ims"; - case kIgnoreCase | kMultiLine | kUnicode: + case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | + RegExpFlag::kUnicode: return "imu"; - case kIgnoreCase | kUnicode | kDotAll: + case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode | RegExpFlag::kDotAll: return "ius"; - case kMultiLine | kDotAll | kUnicode: + case RegExpFlag::kMultiline | RegExpFlag::kDotAll | RegExpFlag::kUnicode: return "msu"; - case kIgnoreCase | kMultiLine: + case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline: return "im"; - case kIgnoreCase | kDotAll: + case RegExpFlag::kIgnoreCase | RegExpFlag::kDotAll: return "is"; - case kIgnoreCase | kUnicode: + case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode: return "iu"; - case kMultiLine | kDotAll: + case RegExpFlag::kMultiline | RegExpFlag::kDotAll: return "ms"; - case kMultiLine | kUnicode: + case RegExpFlag::kMultiline | RegExpFlag::kUnicode: return "mu"; - case kDotAll | kUnicode: + case RegExpFlag::kDotAll | RegExpFlag::kUnicode: return "su"; - case kIgnoreCase: + case RegExpFlags(RegExpFlag::kIgnoreCase): return "i"; - case kMultiLine: + case RegExpFlags(RegExpFlag::kMultiline): return "m"; - case kDotAll: + case RegExpFlags(RegExpFlag::kDotAll): return "s"; - case kUnicode: + case RegExpFlags(RegExpFlag::kUnicode): return "u"; default: break; @@ -27666,13 +27633,13 @@ bool RegExp::CanonicalizeEquals(const Instance& other) const { uint32_t RegExp::CanonicalizeHash() const { // Must agree with RegExpKey::Hash. - return CombineHashes(String::Hash(pattern()), flags().value()); + return CombineHashes(String::Hash(pattern()), flags()); } const char* RegExp::ToCString() const { const String& str = String::Handle(pattern()); return OS::SCreate(Thread::Current()->zone(), "RegExp: pattern=%s flags=%s", - str.ToCString(), flags().ToCString()); + str.ToCString(), FlagsToCString(flags())); } WeakPropertyPtr WeakProperty::New(Heap::Space space) { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index a1983d9b81b..feb1f299cda 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -34,6 +34,7 @@ #include "vm/json_stream.h" #include "vm/os.h" #include "vm/raw_object.h" +#include "vm/regexp/regexp-flags.h" #include "vm/report.h" #include "vm/static_type_exactness_state.h" #include "vm/thread.h" @@ -10466,6 +10467,12 @@ class String : public Instance { static constexpr intptr_t kOneByteChar = 1; static constexpr intptr_t kTwoByteChar = 2; + static const int32_t kMaxOneByteCharCode = 0xff; + static const uint32_t kMaxOneByteCharCodeU = 0xff; + static const int kMaxUtf16CodeUnit = 0xffff; + static const uint32_t kMaxUtf16CodeUnitU = kMaxUtf16CodeUnit; + static const uint32_t kMaxCodePoint = 0x10ffff; + // All strings share the same maximum element count to keep things // simple. We choose a value that will prevent integer overflow for // 2 byte strings, since it is the worst case. @@ -10929,6 +10936,11 @@ class OneByteString : public AllStatic { return static_cast(Object::null()); } + static uint8_t* DataStart(const String& str) { + ASSERT(str.IsOneByteString()); + return &str.UnsafeMutableNonPointer(untag(str)->data())[0]; + } + private: static OneByteStringPtr raw(const String& str) { return static_cast(str.ptr()); @@ -10944,11 +10956,6 @@ class OneByteString : public AllStatic { return &str.UnsafeMutableNonPointer(untag(str)->data())[index]; } - static uint8_t* DataStart(const String& str) { - ASSERT(str.IsOneByteString()); - return &str.UnsafeMutableNonPointer(untag(str)->data())[0]; - } - ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(OneByteString, String); friend class Class; @@ -11050,6 +11057,13 @@ class TwoByteString : public AllStatic { static const ClassId kClassId = kTwoByteStringCid; + // Use this instead of CharAddr(0). It will not assert that the index is < + // length. + static uint16_t* DataStart(const String& str) { + ASSERT(str.IsTwoByteString()); + return &str.UnsafeMutableNonPointer(untag(str)->data())[0]; + } + private: static TwoByteStringPtr raw(const String& str) { return static_cast(str.ptr()); @@ -11065,13 +11079,6 @@ class TwoByteString : public AllStatic { return &str.UnsafeMutableNonPointer(untag(str)->data())[index]; } - // Use this instead of CharAddr(0). It will not assert that the index is < - // length. - static uint16_t* DataStart(const String& str) { - ASSERT(str.IsTwoByteString()); - return &str.UnsafeMutableNonPointer(untag(str)->data())[0]; - } - ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(TwoByteString, String); friend class Class; @@ -13023,92 +13030,9 @@ class SuspendState : public Instance { friend class Interpreter; }; -class RegExpFlags { - public: - // Flags are passed to a regex object as follows: - // 'i': ignore case, 'g': do global matches, 'm': pattern is multi line, - // 'u': pattern is full Unicode, not just BMP, 's': '.' in pattern matches - // all characters including line terminators. - enum Flags { - kNone = 0, - kGlobal = 1, - kIgnoreCase = 2, - kMultiLine = 4, - kUnicode = 8, - kDotAll = 16, - }; - - static constexpr int kDefaultFlags = 0; - - RegExpFlags() : value_(kDefaultFlags) {} - explicit RegExpFlags(int value) : value_(value) {} - - inline bool IsGlobal() const { return (value_ & kGlobal) != 0; } - inline bool IgnoreCase() const { return (value_ & kIgnoreCase) != 0; } - inline bool IsMultiLine() const { return (value_ & kMultiLine) != 0; } - inline bool IsUnicode() const { return (value_ & kUnicode) != 0; } - inline bool IsDotAll() const { return (value_ & kDotAll) != 0; } - - inline bool NeedsUnicodeCaseEquivalents() { - // Both unicode and ignore_case flags are set. We need to use ICU to find - // the closure over case equivalents. - return IsUnicode() && IgnoreCase(); - } - - void SetGlobal() { value_ |= kGlobal; } - void SetIgnoreCase() { value_ |= kIgnoreCase; } - void SetMultiLine() { value_ |= kMultiLine; } - void SetUnicode() { value_ |= kUnicode; } - void SetDotAll() { value_ |= kDotAll; } - - const char* ToCString() const; - - int value() const { return value_; } - - bool operator==(const RegExpFlags& other) const { - return value_ == other.value_; - } - bool operator!=(const RegExpFlags& other) const { - return value_ != other.value_; - } - - private: - int value_; -}; - // Internal JavaScript regular expression object. class RegExp : public Instance { public: - // Meaning of RegExType: - // kUninitialized: the type of th regexp has not been initialized yet. - // kSimple: A simple pattern to match against, using string indexOf operation. - // kComplex: A complex pattern to match. - enum RegExType { - kUninitialized = 0, - kSimple = 1, - kComplex = 2, - }; - - using TypeBits = BitField; - - // Must be kept in sync with RegExFlags::Flags. - using GlobalBit = BitField; - using IgnoreCaseBit = BitField; - using MultiLineBit = BitField; - using UnicodeBit = BitField; - using DotAllBit = BitField; - - // The portion of the bitfield container that contains all the above - // bool bits, which is passed to the constructor for RegExFlags. - using FlagsBits = BitField; - - bool is_initialized() const { return (type() != kUninitialized); } - bool is_simple() const { return (type() == kSimple); } - bool is_complex() const { return (type() == kComplex); } - intptr_t num_registers(bool is_one_byte) const { return LoadNonPointer( is_one_byte ? &untag()->num_one_byte_registers_ @@ -13125,13 +13049,29 @@ class RegExp : public Instance { TypedDataPtr bytecode(bool is_one_byte, bool sticky) const { if (sticky) { - return TypedData::RawCast( - is_one_byte ? untag()->one_byte_sticky() - : untag()->two_byte_sticky()); + return is_one_byte + ? untag()->one_byte_sticky() + : untag()->two_byte_sticky(); } else { - return TypedData::RawCast( - is_one_byte ? untag()->one_byte() - : untag()->two_byte()); + return is_one_byte ? untag()->one_byte() + : untag()->two_byte(); + } + } + bool has_bytecode(bool is_one_byte, bool sticky) const { + if (sticky) { + if (is_one_byte) { + return Object::null() != + untag()->one_byte_sticky(); + } else { + return Object::null() != + untag()->two_byte_sticky(); + } + } else { + if (is_one_byte) { + return Object::null() != untag()->one_byte(); + } else { + return Object::null() != untag()->two_byte(); + } } } @@ -13178,7 +13118,6 @@ class RegExp : public Instance { } void set_pattern(const String& pattern) const; - void set_function(intptr_t cid, bool sticky, const Function& value) const; void set_bytecode(bool is_one_byte, bool sticky, const TypedData& bytecode) const; @@ -13187,23 +13126,6 @@ class RegExp : public Instance { void set_num_bracket_expressions(const Smi& value) const; void set_num_bracket_expressions(intptr_t value) const; void set_capture_name_map(const Array& array) const; - void set_is_global() const { - untag()->type_flags_.UpdateBool(true); - } - void set_is_ignore_case() const { - untag()->type_flags_.UpdateBool(true); - } - void set_is_multi_line() const { - untag()->type_flags_.UpdateBool(true); - } - void set_is_unicode() const { - untag()->type_flags_.UpdateBool(true); - } - void set_is_dot_all() const { - untag()->type_flags_.UpdateBool(true); - } - void set_is_simple() const { set_type(kSimple); } - void set_is_complex() const { set_type(kComplex); } void set_num_registers(bool is_one_byte, intptr_t value) const { StoreNonPointer( is_one_byte ? &untag()->num_one_byte_registers_ @@ -13211,12 +13133,8 @@ class RegExp : public Instance { value); } - RegExpFlags flags() const { - return RegExpFlags(untag()->type_flags_.Read()); - } - void set_flags(RegExpFlags flags) const { - untag()->type_flags_.Update(flags.value()); - } + RegExpFlags flags() const { return RegExpFlags(untag()->flags_); } + void set_flags(RegExpFlags flags) const { untag()->flags_ = flags; } virtual bool CanonicalizeEquals(const Instance& other) const; virtual uint32_t CanonicalizeHash() const; @@ -13225,14 +13143,9 @@ class RegExp : public Instance { return RoundedAllocationSize(sizeof(UntaggedRegExp)); } - static RegExpPtr New(Zone* zone, Heap::Space space = Heap::kNew); + static RegExpPtr New(const String& pattern, RegExpFlags flags); private: - void set_type(RegExType type) const { - untag()->type_flags_.Update(type); - } - RegExType type() const { return untag()->type_flags_.Read(); } - FINAL_HEAP_OBJECT_IMPLEMENTATION(RegExp, Instance); friend class Class; }; diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc index 836dd0c9fb5..e62706f4e05 100644 --- a/runtime/vm/object_service.cc +++ b/runtime/vm/object_service.cc @@ -2004,30 +2004,20 @@ void RegExp::PrintJSONImpl(JSONStream* stream, bool ref) const { return; } - jsobj.AddProperty("isCaseSensitive", !flags().IgnoreCase()); - jsobj.AddProperty("isMultiLine", flags().IsMultiLine()); + jsobj.AddProperty("isCaseSensitive", !IsIgnoreCase(flags())); + jsobj.AddProperty("isMultiLine", IsMultiline(flags())); + jsobj.AddProperty("isUnicode", IsUnicode(flags())); + jsobj.AddProperty("isDotAll", IsDotAll(flags())); - if (!FLAG_interpret_irregexp) { - Function& func = Function::Handle(); - func = function(kOneByteStringCid, /*sticky=*/false); - jsobj.AddProperty("_oneByteFunction", func); - func = function(kTwoByteStringCid, /*sticky=*/false); - jsobj.AddProperty("_twoByteFunction", func); - func = function(kOneByteStringCid, /*sticky=*/true); - jsobj.AddProperty("_oneByteFunctionSticky", func); - func = function(kTwoByteStringCid, /*sticky=*/true); - jsobj.AddProperty("_twoByteFunctionSticky", func); - } else { - TypedData& bc = TypedData::Handle(); - bc = bytecode(/*is_one_byte=*/true, /*sticky=*/false); - jsobj.AddProperty("_oneByteBytecode", bc); - bc = bytecode(/*is_one_byte=*/false, /*sticky=*/false); - jsobj.AddProperty("_twoByteBytecode", bc); - bc = bytecode(/*is_one_byte=*/true, /*sticky=*/true); - jsobj.AddProperty("_oneByteBytecodeSticky", bc); - bc = bytecode(/*is_one_byte=*/false, /*sticky=*/true); - jsobj.AddProperty("_twoByteBytecodeSticky", bc); - } + TypedData& bc = TypedData::Handle(); + bc = bytecode(/*is_one_byte=*/true, /*sticky=*/false); + jsobj.AddProperty("_oneByteBytecode", bc); + bc = bytecode(/*is_one_byte=*/false, /*sticky=*/false); + jsobj.AddProperty("_twoByteBytecode", bc); + bc = bytecode(/*is_one_byte=*/true, /*sticky=*/true); + jsobj.AddProperty("_oneByteBytecodeSticky", bc); + bc = bytecode(/*is_one_byte=*/false, /*sticky=*/true); + jsobj.AddProperty("_twoByteBytecodeSticky", bc); } void RegExp::PrintImplementationFieldsImpl( diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc index 381ffdfac07..bb3d997ce8b 100644 --- a/runtime/vm/parser.cc +++ b/runtime/vm/parser.cc @@ -28,7 +28,6 @@ #include "vm/object.h" #include "vm/object_store.h" #include "vm/os.h" -#include "vm/regexp/regexp_assembler.h" #include "vm/resolver.h" #include "vm/scopes.h" #include "vm/stack_frame.h" @@ -256,19 +255,6 @@ void ParsedFunction::AllocateVariables() { num_stack_locals_ = -next_free_index.value(); } -void ParsedFunction::AllocateIrregexpVariables(intptr_t num_stack_locals) { - ASSERT(function().IsIrregexpFunction()); - ASSERT(function().NumOptionalParameters() == 0); - const intptr_t num_params = function().num_fixed_parameters(); - ASSERT(num_params == RegExpMacroAssembler::kParamCount); - // Compute start indices to parameters and locals, and the number of - // parameters to copy. - first_parameter_index_ = VariableIndex(num_params); - - // Frame indices are relative to the frame pointer and are decreasing. - num_stack_locals_ = num_stack_locals; -} - void ParsedFunction::SetGenericCovariantImplParameters( const BitVector* generic_covariant_impl_parameters) { ASSERT(generic_covariant_impl_parameters_ == nullptr); diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h index 36a9202668a..7f1ddf9ff90 100644 --- a/runtime/vm/parser.h +++ b/runtime/vm/parser.h @@ -180,7 +180,6 @@ class ParsedFunction : public ZoneObject { int num_stack_locals() const { return num_stack_locals_; } void AllocateVariables(); - void AllocateIrregexpVariables(intptr_t num_stack_locals); void record_await() { have_seen_await_expr_ = true; } bool have_seen_await() const { return have_seen_await_expr_; } diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 325f6202201..0660de715bc 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -3719,10 +3719,10 @@ class UntaggedRegExp : public UntaggedInstance { VISIT_FROM(capture_name_map) // Pattern to be used for matching. COMPRESSED_POINTER_FIELD(StringPtr, pattern) - COMPRESSED_POINTER_FIELD(ObjectPtr, one_byte) // FunctionPtr or TypedDataPtr - COMPRESSED_POINTER_FIELD(ObjectPtr, two_byte) - COMPRESSED_POINTER_FIELD(ObjectPtr, one_byte_sticky) - COMPRESSED_POINTER_FIELD(ObjectPtr, two_byte_sticky) + COMPRESSED_POINTER_FIELD(TypedDataPtr, one_byte) + COMPRESSED_POINTER_FIELD(TypedDataPtr, two_byte) + COMPRESSED_POINTER_FIELD(TypedDataPtr, one_byte_sticky) + COMPRESSED_POINTER_FIELD(TypedDataPtr, two_byte_sticky) VISIT_TO(two_byte_sticky) CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) { return to(); } @@ -3741,13 +3741,8 @@ class UntaggedRegExp : public UntaggedInstance { intptr_t num_one_byte_registers_; intptr_t num_two_byte_registers_; - // A bitfield with two fields: - // type: Uninitialized, simple or complex. - // flags: Represents global/local, case insensitive, multiline, unicode, - // dotAll. - // It is possible multiple compilers race to update the flags concurrently. - // That should be safe since all updates update to the same values.. - AtomicBitFieldContainer type_flags_; + // RegExpFlags + uint32_t flags_; }; class UntaggedWeakProperty : public UntaggedInstance { diff --git a/runtime/vm/regexp/README.md b/runtime/vm/regexp/README.md new file mode 100644 index 00000000000..82fd555b97f --- /dev/null +++ b/runtime/vm/regexp/README.md @@ -0,0 +1,29 @@ +# RegExp + +Dart RegExp is defined to have the same behavior as JS RegExp so that the JS implementations of Dart can directly use the host JS RegExp engine. The Dart VM's implementation is taken from V8, which is called [Irregexp](https://blog.chromium.org/2009/02/irregexp-google-chromes-new-regexp.html). + +The following are disabled + + - the atom matching optimization + - the [experimental](https://v8.dev/blog/non-backtracking-regexp) implementation + - the bytecode peephole optimization + - the machine code implementations + - tiering up and statistics counters + - caching of matches + - caching of regexp (though we do this in the VM at an earlier place) + +To update + - copy the files from v8/src/{regexp,base,zone,strings}/* to runtime/vm/regexp + - most of these will become unused + - update the includes to account for the new location + - add includes of vm/regexp/base.h to get shims mapping many V8-isms to Dart-isms + - remove or comment-out anything listed as disabled above + - hopefully these will mostly be obvious from the diff from the previous port + - map any remaining compile time errors from V8 things to Dart things + - Handle -> String& + - Handle -> RegExp& + - Handle -> TypedData& + +Note that all Dart strings are what V8 calls "flat". We have no special String representations that delay concatenation or taking substrings. All Dart RegExp are also "unmodified": users can't add/remove slots or replace methods. + +The most recent update used v8 commit 254cc758346f10be2a7e22e55d90d4defe9cad74, which might be helpful for looking at a diff on the V8 side. diff --git a/runtime/vm/regexp/base.h b/runtime/vm/regexp/base.h new file mode 100644 index 00000000000..b0273d8846c --- /dev/null +++ b/runtime/vm/regexp/base.h @@ -0,0 +1,214 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_VM_REGEXP_BASE_H_ +#define RUNTIME_VM_REGEXP_BASE_H_ + +#include +#include +#include + +#include "platform/assert.h" +#include "platform/globals.h" +#include "platform/unicode.h" + +#define DCHECK(x) DEBUG_ASSERT(x) +#define DCHECK_NULL(x) DEBUG_ASSERT((x) == nullptr) +#define DCHECK_NOT_NULL(x) DEBUG_ASSERT((x) != nullptr) +#define DCHECK_EQ(a, b) DEBUG_ASSERT((a) == (b)) +#define DCHECK_NE(a, b) DEBUG_ASSERT((a) != (b)) +#define DCHECK_LT(a, b) DEBUG_ASSERT((a) < (b)) +#define DCHECK_GT(a, b) DEBUG_ASSERT((a) > (b)) +#define DCHECK_LE(a, b) DEBUG_ASSERT((a) <= (b)) +#define DCHECK_GE(a, b) DEBUG_ASSERT((a) >= (b)) +#define DCHECK_IMPLIES(a, b) DEBUG_ASSERT(!(a) || (b)) + +#define CHECK(x) RELEASE_ASSERT(x) +#define CHECK_EQ(a, b) RELEASE_ASSERT((a) == (b)) +#define CHECK_NE(a, b) RELEASE_ASSERT((a) != (b)) +#define CHECK_LT(a, b) RELEASE_ASSERT((a) < (b)) +#define CHECK_GT(a, b) RELEASE_ASSERT((a) > (b)) +#define CHECK_LE(a, b) RELEASE_ASSERT((a) <= (b)) +#define CHECK_GE(a, b) RELEASE_ASSERT((a) >= (b)) +#define CHECK_IMPLIES(a, b) RELEASE_ASSERT(!(a) || (b)) + +#define SBXCHECK(x) RELEASE_ASSERT(x) +#define SBXCHECK_LT(a, b) RELEASE_ASSERT((a) < (b)) +#define SBXCHECK_GT(a, b) RELEASE_ASSERT((a) > (b)) +#define SBXCHECK_LE(a, b) RELEASE_ASSERT((a) <= (b)) +#define SBXCHECK_GE(a, b) RELEASE_ASSERT((a) >= (b)) + +#define V8_INLINE DART_FORCE_INLINE +#define V8_NOINLINE DART_NOINLINE +#define V8_NOEXCEPT +#define V8_PRESERVE_MOST +#define V8_LIKELY LIKELY +#define V8_UNLIKELY UNLIKELY +#define V8_ASSUME(x) +#define V8_NODISCARD [[nodiscard]] +#define V8_INTL_SUPPORT 1 +#define V8_ALLOW_UNUSED DART_UNUSED +#define V8_WARN_UNUSED_RESULT DART_WARN_UNUSED_RESULT +#define COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER 1 + +#ifdef DART_HAS_COMPUTED_GOTO +#define V8_HAS_COMPUTED_GOTO 1 +#define V8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH 1 +#endif + +#define CONCAT_(a, ...) a##__VA_ARGS__ +#define CONCAT(a, ...) CONCAT_(a, __VA_ARGS__) + +// COUNT_MACRO_ARGS(...) returns the number of arguments passed. Currently, up +// to 8 arguments are supported. +#define COUNT_MACRO_ARGS(...) \ + EXPAND(COUNT_MACRO_ARGS_IMPL(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)) +#define COUNT_MACRO_ARGS_IMPL(_8, _7, _6, _5, _4, _3, _2, _1, N, ...) N +// GET_NTH_ARG(N, ...) returns the Nth argument in the list of arguments +// following. Currently, up to N=8 is supported. +#define GET_NTH_ARG(N, ...) CONCAT(GET_NTH_ARG_IMPL_, N)(__VA_ARGS__) +#define GET_NTH_ARG_IMPL_0(_0, ...) _0 +#define GET_NTH_ARG_IMPL_1(_0, _1, ...) _1 +#define GET_NTH_ARG_IMPL_2(_0, _1, _2, ...) _2 +#define GET_NTH_ARG_IMPL_3(_0, _1, _2, _3, ...) _3 +#define GET_NTH_ARG_IMPL_4(_0, _1, _2, _3, _4, ...) _4 +#define GET_NTH_ARG_IMPL_5(_0, _1, _2, _3, _4, _5, ...) _5 +#define GET_NTH_ARG_IMPL_6(_0, _1, _2, _3, _4, _5, _6, ...) _6 +#define GET_NTH_ARG_IMPL_7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7 + +// Expands to true if __VA_ARGS__ is empty, false otherwise. +#define IS_VA_EMPTY(...) GET_NTH_ARG(0, __VA_OPT__(false, ) true) + +// UNPAREN(x) removes a layer of nested parentheses on x, if any. This means +// that both UNPAREN(x) and UNPAREN((x)) expand to x. This is helpful for macros +// that want to support multi argument templates with commas, e.g. +// +// #define FOO(Type, Name) UNPAREN(Type) Name; +// +// will work with both +// +// FOO(int, x); +// FOO((Foo), x); +#define UNPAREN(X) CONCAT(DROP_, UNPAREN_ X) +#define UNPAREN_(...) UNPAREN_ __VA_ARGS__ +#define DROP_UNPAREN_ + +// clang-format off +#define INT_0_TO_127_LIST(V) \ +V(0) V(1) V(2) V(3) V(4) V(5) V(6) V(7) V(8) V(9) \ +V(10) V(11) V(12) V(13) V(14) V(15) V(16) V(17) V(18) V(19) \ +V(20) V(21) V(22) V(23) V(24) V(25) V(26) V(27) V(28) V(29) \ +V(30) V(31) V(32) V(33) V(34) V(35) V(36) V(37) V(38) V(39) \ +V(40) V(41) V(42) V(43) V(44) V(45) V(46) V(47) V(48) V(49) \ +V(50) V(51) V(52) V(53) V(54) V(55) V(56) V(57) V(58) V(59) \ +V(60) V(61) V(62) V(63) V(64) V(65) V(66) V(67) V(68) V(69) \ +V(70) V(71) V(72) V(73) V(74) V(75) V(76) V(77) V(78) V(79) \ +V(80) V(81) V(82) V(83) V(84) V(85) V(86) V(87) V(88) V(89) \ +V(90) V(91) V(92) V(93) V(94) V(95) V(96) V(97) V(98) V(99) \ +V(100) V(101) V(102) V(103) V(104) V(105) V(106) V(107) V(108) V(109) \ +V(110) V(111) V(112) V(113) V(114) V(115) V(116) V(117) V(118) V(119) \ +V(120) V(121) V(122) V(123) V(124) V(125) V(126) V(127) +// clang-format on + +namespace base { + +using uc16 = uint16_t; +using uc32 = uint32_t; +constexpr int kUC16Size = sizeof(uc16); + +// Returns the value (0 .. 15) of a hexadecimal character c. +// If c is not a legal hexadecimal character, returns a value < 0. +inline int HexValue(uc32 c) { + c -= '0'; + if (static_cast(c) <= 9) return c; + c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. + if (static_cast(c) <= 5) return c + 10; + return -1; +} + +template +D saturated_cast(S in) { + if (in < std::numeric_limits::min()) { + return std::numeric_limits::min(); + } + if (in > std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + return static_cast(in); +} + +// Checks if value is in range [lower_limit, higher_limit] using a single +// branch. +template + requires((std::is_integral_v || std::is_enum_v) && + (std::is_integral_v || std::is_enum_v)) && + (sizeof(U) <= sizeof(T)) +inline constexpr bool IsInRange(T value, U lower_limit, U higher_limit) { + ASSERT(lower_limit <= higher_limit); + using unsigned_T = std::make_unsigned_t; + // Use static_cast to support enum classes. + return static_cast(static_cast(value) - + static_cast(lower_limit)) <= + static_cast(static_cast(higher_limit) - + static_cast(lower_limit)); +} + +}; // namespace base + +namespace dart { + +using Address = uintptr_t; + +constexpr bool FLAG_correctness_fuzzer_suppressions = false; +constexpr bool FLAG_regexp_possessive_quantifier = false; +constexpr bool FLAG_js_regexp_modifiers = true; +constexpr bool FLAG_js_regexp_duplicate_named_groups = true; +constexpr bool FLAG_trace_regexp_parser = false; +constexpr bool FLAG_regexp_unroll = false; +constexpr bool FLAG_regexp_optimization = false; +constexpr bool FLAG_regexp_quick_check = true; +constexpr bool FLAG_regexp_tier_up = false; +constexpr bool FLAG_regexp_peephole_optimization = false; + +class JSRegExp { + public: + static constexpr uint32_t kNoBacktrackLimit = 0; + static constexpr int RegistersForCaptureCount(int count) { + return (count + 1) * 2; + } +}; + +class DisallowGarbageCollection {}; + +// Compare 8bit/16bit chars to 8bit/16bit chars. +template +inline bool CompareCharsEqualUnsigned(const lchar* lhs, + const rchar* rhs, + size_t chars) { + static_assert(std::is_unsigned_v); + static_assert(std::is_unsigned_v); + if constexpr (sizeof(*lhs) == sizeof(*rhs)) { + // memcmp compares byte-by-byte, but for equality it doesn't matter whether + // two-byte char comparison is little- or big-endian. + return memcmp(lhs, rhs, chars * sizeof(*lhs)) == 0; + } + for (const lchar* limit = lhs + chars; lhs < limit; ++lhs, ++rhs) { + if (*lhs != *rhs) return false; + } + return true; +} + +template +inline bool CompareCharsEqual(const lchar* lhs, + const rchar* rhs, + size_t chars) { + using ulchar = std::make_unsigned_t; + using urchar = std::make_unsigned_t; + return CompareCharsEqualUnsigned(reinterpret_cast(lhs), + reinterpret_cast(rhs), chars); +} + +} // namespace dart + +#endif // RUNTIME_VM_REGEXP_BASE_H_ diff --git a/runtime/vm/regexp/char-predicates-inl.h b/runtime/vm/regexp/char-predicates-inl.h new file mode 100644 index 00000000000..b5ef1b65eed --- /dev/null +++ b/runtime/vm/regexp/char-predicates-inl.h @@ -0,0 +1,187 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_STRINGS_CHAR_PREDICATES_INL_H_ +#define V8_STRINGS_CHAR_PREDICATES_INL_H_ + +#include "vm/regexp/char-predicates.h" +// Include the non-inl header before the rest of the headers. + +namespace dart { + +// If c is in 'A'-'Z' or 'a'-'z', return its lower-case. +// Else, return something outside of 'A'-'Z' and 'a'-'z'. +// Note: it ignores LOCALE. +inline constexpr int AsciiAlphaToLower(base::uc32 c) { + return c | 0x20; +} + +inline constexpr bool IsCarriageReturn(base::uc32 c) { + return c == 0x000D; +} + +inline constexpr bool IsLineFeed(base::uc32 c) { + return c == 0x000A; +} + +inline constexpr bool IsAsciiIdentifier(base::uc32 c) { + return IsAlphaNumeric(c) || c == '$' || c == '_'; +} + +inline constexpr bool IsAlphaNumeric(base::uc32 c) { + return base::IsInRange(AsciiAlphaToLower(c), 'a', 'z') || IsDecimalDigit(c); +} + +inline constexpr bool IsDecimalDigit(base::uc32 c) { + // ECMA-262, 3rd, 7.8.3 (p 16) + return base::IsInRange(c, '0', '9'); +} + +inline constexpr bool IsHexDigit(base::uc32 c) { + // ECMA-262, 3rd, 7.6 (p 15) + return IsDecimalDigit(c) || base::IsInRange(AsciiAlphaToLower(c), 'a', 'f'); +} + +inline constexpr bool IsOctalDigit(base::uc32 c) { + // ECMA-262, 6th, 7.8.3 + return base::IsInRange(c, '0', '7'); +} + +inline constexpr bool IsNonOctalDecimalDigit(base::uc32 c) { + return base::IsInRange(c, '8', '9'); +} + +inline constexpr bool IsBinaryDigit(base::uc32 c) { + // ECMA-262, 6th, 7.8.3 + return c == '0' || c == '1'; +} + +inline constexpr bool IsAscii(base::uc32 c) { + return !(c & ~0x7F); +} + +template + requires(std::integral && + std::numeric_limits::max() <= std::numeric_limits::max()) +inline constexpr bool IsAsciiLower(Char c) { + return base::IsInRange(c, 'a', 'z'); +} + +template + requires(std::integral && + std::numeric_limits::max() <= std::numeric_limits::max()) +inline constexpr bool IsAsciiUpper(Char c) { + return base::IsInRange(c, 'A', 'Z'); +} + +inline constexpr base::uc32 ToAsciiUpper(base::uc32 c) { + return c & ~(IsAsciiLower(c) << 5); +} + +inline constexpr base::uc32 ToAsciiLower(base::uc32 c) { + return c | (IsAsciiUpper(c) << 5); +} + +inline constexpr bool IsRegExpWord(base::uc32 c) { + return IsAlphaNumeric(c) || c == '_'; +} + +// Constexpr cache table for character flags. +enum OneByteCharFlags { + kIsIdentifierStart = 1 << 0, + kIsIdentifierPart = 1 << 1, + kIsWhiteSpace = 1 << 2, + kIsWhiteSpaceOrLineTerminator = 1 << 3, + kMaybeLineEnd = 1 << 4 +}; + +// See http://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt +// ID_Start. Additionally includes '_' and '$'. +constexpr bool IsOneByteIDStart(base::uc32 c) { + return c == 0x0024 || (c >= 0x0041 && c <= 0x005A) || c == 0x005F || + (c >= 0x0061 && c <= 0x007A) || c == 0x00AA || c == 0x00B5 || + c == 0x00BA || (c >= 0x00C0 && c <= 0x00D6) || + (c >= 0x00D8 && c <= 0x00F6) || (c >= 0x00F8 && c <= 0x00FF); +} + +// See http://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt +// ID_Continue. Additionally includes '_' and '$'. +constexpr bool IsOneByteIDContinue(base::uc32 c) { + return c == 0x0024 || (c >= 0x0030 && c <= 0x0039) || c == 0x005F || + (c >= 0x0041 && c <= 0x005A) || (c >= 0x0061 && c <= 0x007A) || + c == 0x00AA || c == 0x00B5 || c == 0x00B7 || c == 0x00BA || + (c >= 0x00C0 && c <= 0x00D6) || (c >= 0x00D8 && c <= 0x00F6) || + (c >= 0x00F8 && c <= 0x00FF); +} + +constexpr bool IsOneByteWhitespace(base::uc32 c) { + return c == '\t' || c == '\v' || c == '\f' || c == ' ' || c == u'\xa0'; +} + +constexpr uint8_t BuildOneByteCharFlags(base::uc32 c) { + uint8_t result = 0; + if (IsOneByteIDStart(c) || c == '\\') result |= kIsIdentifierStart; + if (IsOneByteIDContinue(c) || c == '\\') result |= kIsIdentifierPart; + if (IsOneByteWhitespace(c)) { + result |= kIsWhiteSpace | kIsWhiteSpaceOrLineTerminator; + } + if (c == '\r' || c == '\n') { + result |= kIsWhiteSpaceOrLineTerminator | kMaybeLineEnd; + } + // Add markers to identify 0x2028 and 0x2029. + if (c == static_cast(0x2028) || c == static_cast(0x2029)) { + result |= kMaybeLineEnd; + } + return result; +} +const constexpr uint8_t kOneByteCharFlags[256] = { +#define BUILD_CHAR_FLAGS(N) BuildOneByteCharFlags(N), + INT_0_TO_127_LIST(BUILD_CHAR_FLAGS) +#undef BUILD_CHAR_FLAGS +#define BUILD_CHAR_FLAGS(N) BuildOneByteCharFlags(N + 128), + INT_0_TO_127_LIST(BUILD_CHAR_FLAGS) +#undef BUILD_CHAR_FLAGS +}; + +bool IsIdentifierStart(base::uc32 c) { + if (!base::IsInRange(c, 0, 255)) return IsIdentifierStartSlow(c); + DCHECK_EQ(IsIdentifierStartSlow(c), + static_cast(kOneByteCharFlags[c] & kIsIdentifierStart)); + return kOneByteCharFlags[c] & kIsIdentifierStart; +} + +bool IsIdentifierPart(base::uc32 c) { + if (!base::IsInRange(c, 0, 255)) return IsIdentifierPartSlow(c); + DCHECK_EQ(IsIdentifierPartSlow(c), + static_cast(kOneByteCharFlags[c] & kIsIdentifierPart)); + return kOneByteCharFlags[c] & kIsIdentifierPart; +} + +bool IsWhiteSpace(base::uc32 c) { + if (!base::IsInRange(c, 0, 255)) return IsWhiteSpaceSlow(c); + DCHECK_EQ(IsWhiteSpaceSlow(c), + static_cast(kOneByteCharFlags[c] & kIsWhiteSpace)); + return kOneByteCharFlags[c] & kIsWhiteSpace; +} + +bool IsWhiteSpaceOrLineTerminator(base::uc32 c) { + if (!base::IsInRange(c, 0, 255)) return IsWhiteSpaceOrLineTerminatorSlow(c); + DCHECK_EQ( + IsWhiteSpaceOrLineTerminatorSlow(c), + static_cast(kOneByteCharFlags[c] & kIsWhiteSpaceOrLineTerminator)); + return kOneByteCharFlags[c] & kIsWhiteSpaceOrLineTerminator; +} + +bool IsLineTerminatorSequence(base::uc32 c, base::uc32 next) { + if (kOneByteCharFlags[static_cast(c)] & kMaybeLineEnd) { + if (c == '\n') return true; + if (c == '\r') return next != '\n'; + return base::IsInRange(static_cast(c), 0x2028u, 0x2029u); + } + return false; +} + +} // namespace dart + +#endif // V8_STRINGS_CHAR_PREDICATES_INL_H_ diff --git a/runtime/vm/regexp/char-predicates.cc b/runtime/vm/regexp/char-predicates.cc new file mode 100644 index 00000000000..ed8514acecc --- /dev/null +++ b/runtime/vm/regexp/char-predicates.cc @@ -0,0 +1,38 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/char-predicates.h" + +#include "unicode/uchar.h" +#include "unicode/urename.h" + +namespace dart { + +// ES#sec-names-and-keywords Names and Keywords +// UnicodeIDStart, '$', '_' and '\' +bool IsIdentifierStartSlow(base::uc32 c) { + // cannot use u_isIDStart because it does not work for + // Other_ID_Start characters. + return u_hasBinaryProperty(c, UCHAR_ID_START) || + (c < 0x60 && (c == '$' || c == '\\' || c == '_')); +} + +// ES#sec-names-and-keywords Names and Keywords +// UnicodeIDContinue, '$', '_', '\', ZWJ, and ZWNJ +bool IsIdentifierPartSlow(base::uc32 c) { + // Can't use u_isIDPart because it does not work for + // Other_ID_Continue characters. + return u_hasBinaryProperty(c, UCHAR_ID_CONTINUE) || + (c < 0x60 && (c == '$' || c == '\\' || c == '_')) || c == 0x200C || + c == 0x200D; +} + +// ES#sec-white-space White Space +// gC=Zs, U+0009, U+000B, U+000C, U+FEFF +bool IsWhiteSpaceSlow(base::uc32 c) { + return (u_charType(c) == U_SPACE_SEPARATOR) || + (c < 0x0D && (c == 0x09 || c == 0x0B || c == 0x0C)) || c == 0xFEFF; +} + +} // namespace dart diff --git a/runtime/vm/regexp/char-predicates.h b/runtime/vm/regexp/char-predicates.h new file mode 100644 index 00000000000..9209ef4b41d --- /dev/null +++ b/runtime/vm/regexp/char-predicates.h @@ -0,0 +1,69 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_STRINGS_CHAR_PREDICATES_H_ +#define V8_STRINGS_CHAR_PREDICATES_H_ + +#include "platform/unicode.h" +#include "vm/regexp/base.h" + +namespace dart { + +// Unicode character predicates as defined by ECMA-262, 3rd, +// used for lexical analysis. + +inline constexpr int AsciiAlphaToLower(base::uc32 c); +inline constexpr bool IsCarriageReturn(base::uc32 c); +inline constexpr bool IsLineFeed(base::uc32 c); +inline constexpr bool IsAsciiIdentifier(base::uc32 c); +inline constexpr bool IsAlphaNumeric(base::uc32 c); +inline constexpr bool IsDecimalDigit(base::uc32 c); +inline constexpr bool IsHexDigit(base::uc32 c); +inline constexpr bool IsOctalDigit(base::uc32 c); +inline constexpr bool IsBinaryDigit(base::uc32 c); +inline constexpr bool IsRegExpWord(base::uc32 c); + +template +inline constexpr bool IsAsciiLower(Char ch); +template +inline constexpr bool IsAsciiUpper(Char ch); + +inline constexpr base::uc32 ToAsciiUpper(base::uc32 ch); +inline constexpr base::uc32 ToAsciiLower(base::uc32 ch); + +// ES#sec-names-and-keywords +// This includes '_', '$' and '\', and ID_Start according to +// http://www.unicode.org/reports/tr31/, which consists of categories +// 'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl', but excluding properties +// 'Pattern_Syntax' or 'Pattern_White_Space'. +inline bool IsIdentifierStart(base::uc32 c); +bool IsIdentifierStartSlow(base::uc32 c); + +// ES#sec-names-and-keywords +// This includes \u200c and \u200d, and ID_Continue according to +// http://www.unicode.org/reports/tr31/, which consists of ID_Start, +// the categories 'Mn', 'Mc', 'Nd', 'Pc', but excluding properties +// 'Pattern_Syntax' or 'Pattern_White_Space'. +inline bool IsIdentifierPart(base::uc32 c); +bool IsIdentifierPartSlow(base::uc32 c); + +// ES6 draft section 11.2 +// This includes all code points of Unicode category 'Zs'. +// Further included are \u0009, \u000b, \u000c, and \ufeff. +inline bool IsWhiteSpace(base::uc32 c); +bool IsWhiteSpaceSlow(base::uc32 c); + +// WhiteSpace and LineTerminator according to ES6 draft section 11.2 and 11.3 +// This includes all the characters with Unicode category 'Z' (= Zs+Zl+Zp) +// as well as \u0009 - \u000d and \ufeff. +inline bool IsWhiteSpaceOrLineTerminator(base::uc32 c); +inline bool IsWhiteSpaceOrLineTerminatorSlow(base::uc32 c) { + return IsWhiteSpaceSlow(c) || IsLineTerminator(c); +} + +inline bool IsLineTerminatorSequence(base::uc32 c, base::uc32 next); + +} // namespace dart + +#endif // V8_STRINGS_CHAR_PREDICATES_H_ diff --git a/runtime/vm/regexp/flags.h b/runtime/vm/regexp/flags.h new file mode 100644 index 00000000000..0e8a43d3b7d --- /dev/null +++ b/runtime/vm/regexp/flags.h @@ -0,0 +1,138 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_BASE_FLAGS_H_ +#define V8_BASE_FLAGS_H_ + +#include + +#include "vm/regexp/base.h" + +namespace base { + +// The Flags class provides a type-safe way of storing OR-combinations of enum +// values. +// +// The traditional C++ approach for storing OR-combinations of enum values is to +// use an int or unsigned int variable. The inconvenience with this approach is +// that there's no type checking at all; any enum value can be OR'd with any +// other enum value and passed on to a function that takes an int or unsigned +// int. +template +class Flags final { + public: + static_assert(sizeof(BitfieldStorageT) >= sizeof(BitfieldT)); + using flag_type = EnumT; + using mask_type = BitfieldT; + + constexpr Flags() : mask_(0) {} + constexpr Flags(flag_type flag) // NOLINT(runtime/explicit) + : mask_(static_cast(flag)) {} + constexpr explicit Flags(mask_type mask) + : mask_(static_cast(mask)) {} + + constexpr bool operator==(flag_type flag) const { + return mask_ == static_cast(flag); + } + + Flags& operator&=(const Flags& flags) { + mask_ &= flags.mask_; + return *this; + } + Flags& operator|=(const Flags& flags) { + mask_ |= flags.mask_; + return *this; + } + Flags& operator^=(const Flags& flags) { + mask_ ^= flags.mask_; + return *this; + } + + constexpr Flags operator&(const Flags& flags) const { + return Flags(mask_ & flags.mask_); + } + constexpr Flags operator|(const Flags& flags) const { + return Flags(mask_ | flags.mask_); + } + constexpr Flags operator^(const Flags& flags) const { + return Flags(mask_ ^ flags.mask_); + } + + Flags& operator&=(flag_type flag) { return operator&=(Flags(flag)); } + Flags& operator|=(flag_type flag) { return operator|=(Flags(flag)); } + Flags& operator^=(flag_type flag) { return operator^=(Flags(flag)); } + + // Sets or clears given flag. + Flags& set(flag_type flag, bool value) { + if (value) return operator|=(Flags(flag)); + return operator&=(~Flags(flag)); + } + + constexpr Flags operator&(flag_type flag) const { + return operator&(Flags(flag)); + } + constexpr Flags operator|(flag_type flag) const { + return operator|(Flags(flag)); + } + constexpr Flags operator^(flag_type flag) const { + return operator^(Flags(flag)); + } + + constexpr Flags operator~() const { return Flags(~mask_); } + + constexpr operator mask_type() const { return mask_; } + constexpr bool operator!() const { return !mask_; } + + constexpr bool contains(const Flags& flags) const { + return (mask_ & flags.mask_) == flags.mask_; + } + + Flags without(flag_type flag) const { return *this & (~Flags(flag)); } + + friend size_t hash_value(const Flags& flags) { return flags.mask_; } + + private: + BitfieldStorageT mask_; +}; + +#define DEFINE_OPERATORS_FOR_FLAGS(Type) \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator&( \ + Type::flag_type lhs, Type::flag_type rhs) { \ + return Type(lhs) & rhs; \ + } \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator&( \ + Type::flag_type lhs, const Type& rhs) { \ + return rhs & lhs; \ + } \ + V8_ALLOW_UNUSED inline void operator&(Type::flag_type lhs, \ + Type::mask_type rhs) {} \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator|( \ + Type::flag_type lhs, Type::flag_type rhs) { \ + return Type(lhs) | rhs; \ + } \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator|( \ + Type::flag_type lhs, const Type& rhs) { \ + return rhs | lhs; \ + } \ + V8_ALLOW_UNUSED inline void operator|(Type::flag_type lhs, \ + Type::mask_type rhs) {} \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator^( \ + Type::flag_type lhs, Type::flag_type rhs) { \ + return Type(lhs) ^ rhs; \ + } \ + V8_ALLOW_UNUSED V8_WARN_UNUSED_RESULT inline constexpr Type operator^( \ + Type::flag_type lhs, const Type& rhs) { \ + return rhs ^ lhs; \ + } \ + V8_ALLOW_UNUSED inline void operator^(Type::flag_type lhs, \ + Type::mask_type rhs) {} \ + V8_ALLOW_UNUSED inline constexpr Type operator~(Type::flag_type val) { \ + return ~Type(val); \ + } + +} // namespace base + +#endif // V8_BASE_FLAGS_H_ diff --git a/runtime/vm/regexp/gen-regexp-special-case.cc b/runtime/vm/regexp/gen-regexp-special-case.cc new file mode 100644 index 00000000000..7c3da60f75d --- /dev/null +++ b/runtime/vm/regexp/gen-regexp-special-case.cc @@ -0,0 +1,166 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "vm/regexp/special-case.h" + +namespace dart { + +static const base::uc32 kSurrogateStart = 0xd800; +static const base::uc32 kSurrogateEnd = 0xdfff; +static const base::uc32 kNonBmpStart = 0x10000; + +// The following code generates "src/regexp/special-case.cc". +void PrintSet(std::ofstream& out, + const char* name, + const icu::UnicodeSet& set) { + out << "icu::UnicodeSet Build" << name << "() {\n" + << " icu::UnicodeSet set;\n"; + for (int32_t i = 0; i < set.getRangeCount(); i++) { + if (set.getRangeStart(i) == set.getRangeEnd(i)) { + out << " set.add(0x" << set.getRangeStart(i) << ");\n"; + } else { + out << " set.add(0x" << set.getRangeStart(i) << ", 0x" + << set.getRangeEnd(i) << ");\n"; + } + } + out << " set.freeze();\n" + << " return set;\n" + << "}\n\n"; + + out << "struct " << name << "Data {\n" + << " " << name << "Data() : set(Build" << name << "()) {}\n" + << " const icu::UnicodeSet set;\n" + << "};\n\n"; + + out << "//static\n" + << "const icu::UnicodeSet& RegExpCaseFolding::" << name << "() {\n" + << " static base::LazyInstance<" << name << "Data>::type set =\n" + << " LAZY_INSTANCE_INITIALIZER;\n" + << " return set.Pointer()->set;\n" + << "}\n\n"; +} + +void PrintSpecial(std::ofstream& out) { + icu::UnicodeSet current; + icu::UnicodeSet special_add; + icu::UnicodeSet ignore; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeSet upper("[\\p{Lu}]", status); + CHECK(U_SUCCESS(status)); + + // Iterate through all chars in BMP except surrogates. + for (UChar32 i = 0; i < static_cast(kNonBmpStart); i++) { + if (i >= static_cast(kSurrogateStart) && + i <= static_cast(kSurrogateEnd)) { + continue; // Ignore surrogate range + } + current.set(i, i); + current.closeOver(USET_CASE_INSENSITIVE); + + // Check to see if all characters in the case-folding equivalence + // class as defined by UnicodeSet::closeOver all map to the same + // canonical value. + UChar32 canonical = RegExpCaseFolding::Canonicalize(i); + bool class_has_matching_canonical_char = false; + bool class_has_non_matching_canonical_char = false; + for (int32_t j = 0; j < current.getRangeCount(); j++) { + for (UChar32 c = current.getRangeStart(j); c <= current.getRangeEnd(j); + c++) { + if (c == i) { + continue; + } + UChar32 other_canonical = RegExpCaseFolding::Canonicalize(c); + if (canonical == other_canonical) { + class_has_matching_canonical_char = true; + } else { + class_has_non_matching_canonical_char = true; + } + } + } + // If any other character in i's equivalence class has a + // different canonical value, then i needs special handling. If + // no other character shares a canonical value with i, we can + // ignore i when adding alternatives for case-independent + // comparison. If at least one other character shares a + // canonical value, then i needs special handling. + if (class_has_non_matching_canonical_char) { + if (class_has_matching_canonical_char) { + special_add.add(i); + } else { + ignore.add(i); + } + } + } + + // Verify that no Unicode equivalence class contains two non-trivial + // JS equivalence classes. Every character in SpecialAddSet has the + // same canonical value as every other non-IgnoreSet character in + // its Unicode equivalence class. Therefore, if we call closeOver on + // a set containing no IgnoreSet characters, the only characters + // that must be removed from the result are in IgnoreSet. This fact + // is used in CharacterRange::AddCaseEquivalents. + for (int32_t i = 0; i < special_add.getRangeCount(); i++) { + for (UChar32 c = special_add.getRangeStart(i); + c <= special_add.getRangeEnd(i); c++) { + UChar32 canonical = RegExpCaseFolding::Canonicalize(c); + current.set(c, c); + current.closeOver(USET_CASE_INSENSITIVE); + current.removeAll(ignore); + for (int32_t j = 0; j < current.getRangeCount(); j++) { + for (UChar32 c2 = current.getRangeStart(j); + c2 <= current.getRangeEnd(j); c2++) { + CHECK_EQ(canonical, RegExpCaseFolding::Canonicalize(c2)); + } + } + } + } + + PrintSet(out, "IgnoreSet", ignore); + PrintSet(out, "SpecialAddSet", special_add); +} + +void WriteHeader(const char* header_filename) { + std::ofstream out(header_filename); + out << std::hex << std::setfill('0') << std::setw(4); + out << "// Copyright 2020 the V8 project authors. All rights reserved.\n" + << "// Use of this source code is governed by a BSD-style license that\n" + << "// can be found in the LICENSE file.\n\n" + << "// Automatically generated by regexp/gen-regexp-special-case.cc\n\n" + << "// The following functions are used to build UnicodeSets\n" + << "// for special cases where the case-folding algorithm used by\n" + << "// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) does not match\n" + << "// the algorithm defined in ECMAScript 2020 21.2.2.8.2 (Runtime\n" + << "// Semantics: Canonicalize) step 3.\n\n" + << "#ifdef V8_INTL_SUPPORT\n" + << "#include \"src/base/lazy-instance.h\"\n\n" + << "#include \"src/regexp/special-case.h\"\n\n" + << "#include \"unicode/uniset.h\"\n" + << "namespace dart {\n\n"; + + PrintSpecial(out); + + out << "\n" + << "} // namespace dart\n" + << "#endif // V8_INTL_SUPPORT\n"; +} + +} // namespace dart + +extern "C" void Dart_DumpNativeStackTrace(void*) {} +extern "C" void Dart_PrepareToAbort() {} + +int main(int argc, const char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " \n"; + std::exit(1); + } + dart::WriteHeader(argv[1]); + + return 0; +} diff --git a/runtime/vm/regexp/label.h b/runtime/vm/regexp/label.h new file mode 100644 index 00000000000..6a8bfdb2654 --- /dev/null +++ b/runtime/vm/regexp/label.h @@ -0,0 +1,107 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CODEGEN_LABEL_H_ +#define V8_CODEGEN_LABEL_H_ + +#include "vm/object.h" +#include "vm/regexp/base.h" + +namespace dart { + +class V8Label { + public: + enum Distance { + kNear, // near jump: 8 bit displacement (signed) + kFar // far jump: 32 bit displacement (signed) + }; + + V8Label() = default; + + // Disallow copy construction and assignment, but allow move construction and + // move assignment on selected platforms (see below). + V8Label(const V8Label&) = delete; + V8Label& operator=(const V8Label&) = delete; + +// On ARM64, the Assembler keeps track of pointers to V8Labels to resolve +// branches to distant targets. Copying labels would confuse the Assembler. +// On other platforms, allow move construction. +#if !V8_TARGET_ARCH_ARM64 +// In debug builds, the old V8Label has to be cleared in order to avoid a DCHECK +// failure in it's destructor. +#ifdef DEBUG + V8Label(V8Label&& other) V8_NOEXCEPT { *this = std::move(other); } + V8Label& operator=(V8Label&& other) V8_NOEXCEPT { + pos_ = other.pos_; + near_link_pos_ = other.near_link_pos_; + other.Unuse(); + other.UnuseNear(); + return *this; + } +#else + V8Label(V8Label&&) V8_NOEXCEPT = default; + V8Label& operator=(V8Label&&) V8_NOEXCEPT = default; +#endif +#endif + +#ifdef DEBUG + V8_INLINE ~V8Label() { + DCHECK(!is_linked()); + DCHECK(!is_near_linked()); + } +#endif + + V8_INLINE void Unuse() { pos_ = 0; } + V8_INLINE void UnuseNear() { near_link_pos_ = 0; } + + V8_INLINE bool is_bound() const { return pos_ < 0; } + V8_INLINE bool is_unused() const { return pos_ == 0 && near_link_pos_ == 0; } + V8_INLINE bool is_linked() const { return pos_ > 0; } + V8_INLINE bool is_near_linked() const { return near_link_pos_ > 0; } + + // Returns the position of bound or linked labels. Cannot be used + // for unused labels. + int pos() const { + if (pos_ < 0) return -pos_ - 1; + if (pos_ > 0) return pos_ - 1; + UNREACHABLE(); + } + + int near_link_pos() const { return near_link_pos_ - 1; } + + private: + // pos_ encodes both the binding state (via its sign) + // and the binding position (via its value) of a label. + // + // pos_ < 0 bound label, pos() returns the jump target position + // pos_ == 0 unused label + // pos_ > 0 linked label, pos() returns the last reference position + int pos_ = 0; + + // Behaves like |pos_| in the "> 0" case, but for near jumps to this label. + int near_link_pos_ = 0; + + void bind_to(int pos) { + pos_ = -pos - 1; + DCHECK(is_bound()); + } + void link_to(int pos, Distance distance = kFar) { + if (distance == kNear) { + near_link_pos_ = pos + 1; + DCHECK(is_near_linked()); + } else { + pos_ = pos + 1; + DCHECK(is_linked()); + } + } + + friend class Assembler; + friend class Displacement; + friend class RegExpBytecodeGenerator; + friend class RegExpBytecodeWriter; +}; + +} // namespace dart + +#endif // V8_CODEGEN_LABEL_H_ diff --git a/runtime/vm/regexp/memcopy.h b/runtime/vm/regexp/memcopy.h new file mode 100644 index 00000000000..200f63381d0 --- /dev/null +++ b/runtime/vm/regexp/memcopy.h @@ -0,0 +1,69 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_BASE_MEMCOPY_H_ +#define V8_BASE_MEMCOPY_H_ + +#include + +#include + +#include "vm/regexp/base.h" + +namespace base { + +// Copy memory area to disjoint memory area. +inline void MemCopy(void* dest, const void* src, size_t size) { + memcpy(dest, src, size); // NOLINT +} + +inline void MemMove(void* dest, const void* src, size_t size) { + memmove(dest, src, size); // NOLINT +} + +template +V8_INLINE bool TryTrivialCopy(const T* src_begin, const T* src_end, T* dest) { + DCHECK_LE(src_begin, src_end); + if constexpr (std::is_trivially_copyable_v) { + const size_t count = src_end - src_begin; + base::MemCopy(dest, src_begin, count * sizeof(T)); + return true; + } + return false; +} + +template +V8_INLINE bool TryTrivialMove(const T* src_begin, const T* src_end, T* dest) { + DCHECK_LE(src_begin, src_end); + if constexpr (std::is_trivially_copyable_v) { + const size_t count = src_end - src_begin; + base::MemMove(dest, src_begin, count * sizeof(T)); + return true; + } + return false; +} + +// Fills `destination` with `count` `value`s. +template +constexpr void Memset(T* destination, U value, size_t count) + requires std::is_trivially_assignable_v +{ + for (size_t i = 0; i < count; i++) { + destination[i] = value; + } +} + +// Fills `destination` with `count` `value`s. +template +inline void Relaxed_Memset(T* destination, T value, size_t count) + requires std::is_integral_v +{ + for (size_t i = 0; i < count; i++) { + std::atomic_ref(destination[i]).store(value, std::memory_order_relaxed); + } +} + +} // namespace base + +#endif // V8_BASE_MEMCOPY_H_ diff --git a/runtime/vm/regexp/regexp-ast.cc b/runtime/vm/regexp/regexp-ast.cc new file mode 100644 index 00000000000..41f7cf4422a --- /dev/null +++ b/runtime/vm/regexp/regexp-ast.cc @@ -0,0 +1,269 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-ast.h" + +#include +#include + +#include "platform/utils.h" +#include "vm/os.h" + +namespace dart { + +#define MAKE_ACCEPT(Name) \ + void* RegExp##Name::Accept(RegExpVisitor* visitor, void* data) { \ + return visitor->Visit##Name(this, data); \ + } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ACCEPT) +#undef MAKE_ACCEPT + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExpTree::As##Name() { \ + return nullptr; \ + } \ + bool RegExpTree::Is##Name() { \ + return false; \ + } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExp##Name::As##Name() { \ + return this; \ + } \ + bool RegExp##Name::Is##Name() { \ + return true; \ + } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +namespace { + +Interval ListCaptureRegisters(ZoneList* children) { + Interval result = Interval::Empty(); + for (int i = 0; i < children->length(); i++) + result = result.Union(children->at(i)->CaptureRegisters()); + return result; +} + +} // namespace + +Interval RegExpAlternative::CaptureRegisters() { + return ListCaptureRegisters(nodes()); +} + +Interval RegExpDisjunction::CaptureRegisters() { + return ListCaptureRegisters(alternatives()); +} + +Interval RegExpLookaround::CaptureRegisters() { + return body()->CaptureRegisters(); +} + +Interval RegExpCapture::CaptureRegisters() { + Interval self(StartRegister(index()), EndRegister(index())); + return self.Union(body()->CaptureRegisters()); +} + +Interval RegExpQuantifier::CaptureRegisters() { + return body()->CaptureRegisters(); +} + +bool RegExpAssertion::IsAnchoredAtStart() { + return assertion_type() == RegExpAssertion::Type::START_OF_INPUT; +} + +bool RegExpAssertion::IsAnchoredAtEnd() { + return assertion_type() == RegExpAssertion::Type::END_OF_INPUT; +} + +bool RegExpAlternative::IsAnchoredAtStart() { + ZoneList* nodes = this->nodes(); + for (int i = 0; i < nodes->length(); i++) { + RegExpTree* node = nodes->at(i); + if (node->IsAnchoredAtStart()) { + return true; + } + if (node->max_match() > 0) { + return false; + } + } + return false; +} + +bool RegExpAlternative::IsAnchoredAtEnd() { + ZoneList* nodes = this->nodes(); + for (int i = nodes->length() - 1; i >= 0; i--) { + RegExpTree* node = nodes->at(i); + if (node->IsAnchoredAtEnd()) { + return true; + } + if (node->max_match() > 0) { + return false; + } + } + return false; +} + +bool RegExpDisjunction::IsAnchoredAtStart() { + ZoneList* alternatives = this->alternatives(); + for (int i = 0; i < alternatives->length(); i++) { + if (!alternatives->at(i)->IsAnchoredAtStart()) return false; + } + return true; +} + +bool RegExpDisjunction::IsAnchoredAtEnd() { + ZoneList* alternatives = this->alternatives(); + for (int i = 0; i < alternatives->length(); i++) { + if (!alternatives->at(i)->IsAnchoredAtEnd()) return false; + } + return true; +} + +bool RegExpLookaround::IsAnchoredAtStart() { + return is_positive() && type() == LOOKAHEAD && body()->IsAnchoredAtStart(); +} + +bool RegExpCapture::IsAnchoredAtStart() { + return body()->IsAnchoredAtStart(); +} + +bool RegExpCapture::IsAnchoredAtEnd() { + return body()->IsAnchoredAtEnd(); +} + +RegExpDisjunction::RegExpDisjunction(ZoneList* alternatives) + : alternatives_(alternatives) { + ASSERT(1 < alternatives->length()); + RegExpTree* first_alternative = alternatives->at(0); + min_match_ = first_alternative->min_match(); + max_match_ = first_alternative->max_match(); + for (int i = 1; i < alternatives->length(); i++) { + RegExpTree* alternative = alternatives->at(i); + min_match_ = std::min(min_match_, alternative->min_match()); + max_match_ = std::max(max_match_, alternative->max_match()); + } +} + +namespace { + +int IncreaseBy(int previous, int increase) { + if (RegExpTree::kInfinity - previous < increase) { + return RegExpTree::kInfinity; + } else { + return previous + increase; + } +} + +} // namespace + +RegExpAlternative::RegExpAlternative(ZoneList* nodes) + : nodes_(nodes) { + ASSERT(1 < nodes->length()); + min_match_ = 0; + max_match_ = 0; + for (int i = 0; i < nodes->length(); i++) { + RegExpTree* node = nodes->at(i); + int node_min_match = node->min_match(); + min_match_ = IncreaseBy(min_match_, node_min_match); + int node_max_match = node->max_match(); + max_match_ = IncreaseBy(max_match_, node_max_match); + } +} + +RegExpClassSetOperand::RegExpClassSetOperand(ZoneList* ranges, + CharacterClassStrings* strings) + : ranges_(ranges), strings_(strings) { + ASSERT(ranges != nullptr); + min_match_ = 0; + max_match_ = 0; + if (!ranges->is_empty()) { + min_match_ = 1; + max_match_ = 2; + } + if (has_strings()) { + for (auto string : *strings) { + min_match_ = std::min(min_match_, string.second->min_match()); + max_match_ = std::max(max_match_, string.second->max_match()); + } + } +} + +RegExpClassSetExpression::RegExpClassSetExpression( + OperationType op, + bool is_negated, + bool may_contain_strings, + ZoneList* operands) + : operation_(op), + is_negated_(is_negated), + may_contain_strings_(may_contain_strings), + operands_(operands) { + ASSERT(operands != nullptr); + if (is_negated) { + ASSERT(!may_contain_strings_); + // We don't know anything about max matches for negated classes. + // As there are no strings involved, assume that we can match a unicode + // character (2 code points). + max_match_ = 2; + } else { + max_match_ = 0; + for (auto operand : *operands) { + max_match_ = std::max(max_match_, operand->max_match()); + } + } +} + +// static +RegExpClassSetExpression* RegExpClassSetExpression::Empty(Zone* zone, + bool is_negated) { + ZoneList* ranges = + zone->template New>(0, zone); + RegExpClassSetOperand* op = + zone->template New(ranges, nullptr); + ZoneList* operands = + zone->template New>(1, zone); + operands->Add(op, zone); + return zone->template New( + RegExpClassSetExpression::OperationType::kUnion, is_negated, false, + operands); +} + +bool RegExpText::StartsWithAtom() const { + if (elements_.length() == 0) return false; + return elements_.at(0).text_type() == TextElement::ATOM; +} + +RegExpAtom* RegExpText::FirstAtom() const { + return elements_.at(0).atom(); +} + +RegExpClassRanges::RegExpClassRanges( + Zone* zone, + ZoneList* ranges, + RegExpClassRanges::ClassRangesFlags class_ranges_flags) + : set_(ranges), class_ranges_flags_(class_ranges_flags) { + // Convert the empty set of ranges to the negated Everything() range. + if (ranges->is_empty()) { + ranges->Add(CharacterRange::Everything(), zone); + class_ranges_flags_ ^= NEGATED; + } + if (!is_negated() && !is_certainly_two_code_points() && + no_case_folding_needed()) { + // Perhaps we can detect that it is always two code points. + bool found_basic_plane = false; + for (int i = 0; i < ranges->length(); i++) { + if (ranges->at(i).from() < 0x10000) { + found_basic_plane = true; + break; + } + } + if (!found_basic_plane) { + class_ranges_flags_ |= IS_CERTAINLY_TWO_CODE_POINTS; + } + } +} + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-ast.h b/runtime/vm/regexp/regexp-ast.h new file mode 100644 index 00000000000..7562e244672 --- /dev/null +++ b/runtime/vm/regexp/regexp-ast.h @@ -0,0 +1,785 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_AST_H_ +#define V8_REGEXP_REGEXP_AST_H_ + +#include + +#include "platform/globals.h" +#include "platform/utils.h" +#include "vm/allocation.h" +#include "vm/regexp/regexp-flags.h" +#include "vm/regexp/vector.h" +#include "vm/regexp/zone-containers.h" +#include "vm/regexp/zone-list-inl.h" + +namespace dart { + +class Isolate; + +#define FOR_EACH_REG_EXP_TREE_TYPE(VISIT) \ + VISIT(Disjunction) \ + VISIT(Alternative) \ + VISIT(Assertion) \ + VISIT(ClassRanges) \ + VISIT(ClassSetOperand) \ + VISIT(ClassSetExpression) \ + VISIT(Atom) \ + VISIT(Quantifier) \ + VISIT(Capture) \ + VISIT(Group) \ + VISIT(Lookaround) \ + VISIT(BackReference) \ + VISIT(Empty) \ + VISIT(Text) + +#define FORWARD_DECLARE(Name) class RegExp##Name; +FOR_EACH_REG_EXP_TREE_TYPE(FORWARD_DECLARE) +#undef FORWARD_DECLARE + +class RegExpCompiler; +class RegExpNode; +class RegExpTree; + +class RegExpVisitor { + public: + virtual ~RegExpVisitor() = default; +#define MAKE_CASE(Name) \ + virtual void* Visit##Name(RegExp##Name*, void* data) = 0; + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) +#undef MAKE_CASE +}; + +// A simple closed interval. +class Interval { + public: + Interval() : from_(kNone), to_(kNone - 1) {} // '- 1' for branchless size(). + Interval(int from, int to) : from_(from), to_(to) {} + Interval Union(Interval that) { + if (that.from_ == kNone) return *this; + if (from_ == kNone) return that; + return Interval(std::min(from_, that.from_), std::max(to_, that.to_)); + } + + static Interval Empty() { return Interval(); } + + bool Contains(int value) const { return (from_ <= value) && (value <= to_); } + bool is_empty() const { return from_ == kNone; } + int from() const { return from_; } + int to() const { return to_; } + int size() const { return to_ - from_ + 1; } + + static constexpr int kNone = -1; + + private: + int from_; + int to_; +}; + +// Named standard character sets. +enum class StandardCharacterSet : char { + kWhitespace = 's', // Like /\s/. + kNotWhitespace = 'S', // Like /\S/. + kWord = 'w', // Like /\w/. + kNotWord = 'W', // Like /\W/. + kDigit = 'd', // Like /\d/. + kNotDigit = 'D', // Like /\D/. + kLineTerminator = 'n', // The inverse of /./. + kNotLineTerminator = '.', // Like /./. + kEverything = '*', // Matches every character, like /./s. +}; + +// Represents code points (with values up to 0x10FFFF) in the range from from_ +// to to_, both ends are inclusive. +class CharacterRange { + public: + CharacterRange() = default; + // For compatibility with the CHECK_OK macro. + CharacterRange(void* null) { ASSERT(null); } // NOLINT + + static inline CharacterRange Singleton(uint32_t value) { + return CharacterRange(value, value); + } + static inline CharacterRange Range(uint32_t from, uint32_t to) { + // Werror: 0 <= unsigned always true. + // ASSERT(0 <= from && to <= kMaxCodePoint); + ASSERT(static_cast(from) <= static_cast(to)); + return CharacterRange(from, to); + } + static inline CharacterRange Everything() { + return CharacterRange(0, kMaxCodePoint); + } + + static inline ZoneList* List(Zone* zone, + CharacterRange range) { + ZoneList* list = + zone->New>(1, zone); + list->Add(range, zone); + return list; + } + + // Add class escapes. Add case equivalent closure for \w and \W if necessary. + static void AddClassEscape(StandardCharacterSet standard_character_set, + ZoneList* ranges, + bool add_unicode_case_equivalents, + Zone* zone); + // Add case equivalents to ranges. Only used for /i, not for /ui or /vi, as + // the semantics for unicode mode are slightly different. + // See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch Note 4. + static void AddCaseEquivalents(Isolate* isolate, + Zone* zone, + ZoneList* ranges, + bool is_one_byte); + // Add case equivalent code points to ranges. Only used for /ui and /vi, not + // for /i, as the semantics for non-unicode mode are slightly different. + // See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch Note 4. + static void AddUnicodeCaseEquivalents(ZoneList* ranges, + Zone* zone); + + bool Contains(uint32_t i) const { return from_ <= i && i <= to_; } + uint32_t from() const { return from_; } + uint32_t to() const { return to_; } + bool IsEverything(uint32_t max) const { return from_ == 0 && to_ >= max; } + bool IsSingleton() const { return from_ == to_; } + + // Whether a range list is in canonical form: Ranges ordered by from value, + // and ranges non-overlapping and non-adjacent. + static bool IsCanonical(const ZoneList* ranges); + // Convert range list to canonical form. The characters covered by the ranges + // will still be the same, but no character is in more than one range, and + // adjacent ranges are merged. The resulting list may be shorter than the + // original, but cannot be longer. + static void Canonicalize(ZoneList* ranges); + // Negate the contents of a character range in canonical form. + static void Negate(const ZoneList* src, + ZoneList* dst, + Zone* zone); + // Intersect the contents of two character ranges in canonical form. + static void Intersect(const ZoneList* lhs, + const ZoneList* rhs, + ZoneList* dst, + Zone* zone); + // Subtract the contents of |to_remove| from the contents of |src|. + static void Subtract(const ZoneList* src, + const ZoneList* to_remove, + ZoneList* dst, + Zone* zone); + // Remove all ranges outside the one-byte range. + static void ClampToOneByte(ZoneList* ranges); + // Checks if two ranges (both need to be canonical) are equal. + static bool Equals(const ZoneList* lhs, + const ZoneList* rhs); + + private: + CharacterRange(uint32_t from, uint32_t to) : from_(from), to_(to) {} + + static constexpr int kMaxCodePoint = 0x10ffff; + + uint32_t from_ = 0; + uint32_t to_ = 0; +}; + +inline bool operator==(const CharacterRange& lhs, const CharacterRange& rhs) { + return lhs.from() == rhs.from() && lhs.to() == rhs.to(); +} +inline bool operator!=(const CharacterRange& lhs, const CharacterRange& rhs) { + return !operator==(lhs, rhs); +} + +#define DECL_BOILERPLATE(Name) \ + void* Accept(RegExpVisitor* visitor, void* data) override; \ + RegExpNode* ToNodeImpl(RegExpCompiler* compiler, RegExpNode* on_success) \ + override; \ + RegExp##Name* As##Name() override; \ + bool Is##Name() override + +class RegExpTree : public ZoneObject { + public: + static const int kInfinity = kMaxInt; + virtual ~RegExpTree() = default; + virtual void* Accept(RegExpVisitor* visitor, void* data) = 0; + RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); + virtual RegExpNode* ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) = 0; + virtual bool IsTextElement() { return false; } + virtual bool IsAnchoredAtStart() { return false; } + virtual bool IsAnchoredAtEnd() { return false; } + virtual int min_match() = 0; + virtual int max_match() = 0; + // Returns the interval of registers used for captures within this + // expression. + virtual Interval CaptureRegisters() { return Interval::Empty(); } + virtual void AppendToText(RegExpText* text, Zone* zone); +#define MAKE_ASTYPE(Name) \ + virtual RegExp##Name* As##Name(); \ + virtual bool Is##Name(); + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE) +#undef MAKE_ASTYPE +}; + +class RegExpDisjunction final : public RegExpTree { + public: + explicit RegExpDisjunction(ZoneList* alternatives); + + DECL_BOILERPLATE(Disjunction); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + ZoneList* alternatives() const { return alternatives_; } + + private: + bool SortConsecutiveAtoms(RegExpCompiler* compiler); + void RationalizeConsecutiveAtoms(RegExpCompiler* compiler); + void FixSingleCharacterDisjunctions(RegExpCompiler* compiler); + ZoneList* alternatives_; + int min_match_; + int max_match_; +}; + +class RegExpAlternative final : public RegExpTree { + public: + explicit RegExpAlternative(ZoneList* nodes); + + DECL_BOILERPLATE(Alternative); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + ZoneList* nodes() const { return nodes_; } + + private: + ZoneList* nodes_; + int min_match_; + int max_match_; +}; + +class RegExpAssertion final : public RegExpTree { + public: + enum class Type { + START_OF_LINE = 0, + START_OF_INPUT = 1, + END_OF_LINE = 2, + END_OF_INPUT = 3, + BOUNDARY = 4, + NON_BOUNDARY = 5, + LAST_ASSERTION_TYPE = NON_BOUNDARY, + }; + explicit RegExpAssertion(Type type) : assertion_type_(type) {} + + DECL_BOILERPLATE(Assertion); + + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return 0; } + int max_match() override { return 0; } + Type assertion_type() const { return assertion_type_; } + + private: + const Type assertion_type_; +}; + +class CharacterSet final { + public: + explicit CharacterSet(StandardCharacterSet standard_set_type) + : standard_set_type_(standard_set_type) {} + explicit CharacterSet(ZoneList* ranges) : ranges_(ranges) {} + + ZoneList* ranges(Zone* zone); + StandardCharacterSet standard_set_type() const { + return standard_set_type_.value(); + } + void set_standard_set_type(StandardCharacterSet standard_set_type) { + standard_set_type_ = standard_set_type; + } + bool is_standard() const { return standard_set_type_.has_value(); } + void Canonicalize(); + + private: + ZoneList* ranges_ = nullptr; + std::optional standard_set_type_; +}; + +class RegExpClassRanges final : public RegExpTree { + public: + // NEGATED: The character class is negated and should match everything but + // the specified ranges. + // CONTAINS_SPLIT_SURROGATE: The character class contains part of a split + // surrogate and should not be unicode-desugared (crbug.com/641091). + // NO_CASE_FOLDING_NEEDED: If case folding is required (/i), it was already + // performed on individual ranges and should not be applied again. + enum Flag { + NEGATED = 1 << 0, + CONTAINS_SPLIT_SURROGATE = 1 << 1, + NO_CASE_FOLDING_NEEDED = 1 << 2, + IS_CERTAINLY_ONE_CODE_POINT = 1 << 3, + IS_CERTAINLY_TWO_CODE_POINTS = 1 << 4, + }; + using ClassRangesFlags = base::Flags; + + RegExpClassRanges(Zone* zone, + ZoneList* ranges, + ClassRangesFlags class_ranges_flags = ClassRangesFlags()); + explicit RegExpClassRanges(StandardCharacterSet standard_set_type) + : set_(standard_set_type), class_ranges_flags_() {} + + DECL_BOILERPLATE(ClassRanges); + + bool IsTextElement() override { return true; } + int min_match() override { + if (is_certainly_two_code_points()) { + return 2; + } + return 1; + } + // The character class may match two code units for unicode regexps. + // TODO(yangguo): we should split this class for usage in TextElement, and + // make max_match() dependent on the character class content. + int max_match() override { + if (is_certainly_one_code_point()) { + return 1; + } + return 2; + } + + void AppendToText(RegExpText* text, Zone* zone) override; + + // TODO(lrn): Remove need for complex version if is_standard that + // recognizes a mangled standard set and just do { return set_.is_special(); } + bool is_standard(Zone* zone); + // Returns a value representing the standard character set if is_standard() + // returns true. + StandardCharacterSet standard_type() const { + return set_.standard_set_type(); + } + + CharacterSet character_set() const { return set_; } + ZoneList* ranges(Zone* zone) { return set_.ranges(zone); } + + bool is_negated() const { return (class_ranges_flags_ & NEGATED) != 0; } + bool contains_split_surrogate() const { + return (class_ranges_flags_ & CONTAINS_SPLIT_SURROGATE) != 0; + } + bool no_case_folding_needed() const { + return (class_ranges_flags_ & NO_CASE_FOLDING_NEEDED) != 0; + } + bool is_certainly_one_code_point() const { + return (class_ranges_flags_ & IS_CERTAINLY_ONE_CODE_POINT) != 0; + } + bool is_certainly_two_code_points() const { + return (class_ranges_flags_ & IS_CERTAINLY_TWO_CODE_POINTS) != 0; + } + + private: + CharacterSet set_; + ClassRangesFlags class_ranges_flags_; +}; + +struct CharacterClassStringLess { + bool operator()(base::Vector lhs, + base::Vector rhs) const { + // Longer strings first so we generate matches for the largest string + // possible. + if (lhs.length() != rhs.length()) { + return lhs.length() > rhs.length(); + } + for (int i = 0; i < lhs.length(); i++) { + if (lhs[i] != rhs[i]) { + return lhs[i] < rhs[i]; + } + } + return false; + } +}; + +// A type used for strings as part of character classes (only possible in +// unicode sets mode). +// We use a ZoneMap instead of an UnorderedZoneMap because we need to match +// the longest alternatives first. By using a ZoneMap with the custom comparator +// we can avoid sorting before assembling the code. +// Strings are likely short (the largest string in current unicode properties +// consists of 10 code points). +using CharacterClassStrings = ZoneMap, + RegExpTree*, + CharacterClassStringLess>; + +// TODO(pthier): If we are sure we don't want to use icu::UnicodeSets +// (performance evaluation pending), this class can be merged with +// RegExpClassRanges. +class RegExpClassSetOperand final : public RegExpTree { + public: + RegExpClassSetOperand(ZoneList* ranges, + CharacterClassStrings* strings); + + DECL_BOILERPLATE(ClassSetOperand); + + bool IsTextElement() override { return true; } + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + + void Union(RegExpClassSetOperand* other, Zone* zone); + void Intersect(RegExpClassSetOperand* other, + ZoneList* temp_ranges, + Zone* zone); + void Subtract(RegExpClassSetOperand* other, + ZoneList* temp_ranges, + Zone* zone); + + bool has_strings() const { return strings_ != nullptr && !strings_->empty(); } + ZoneList* ranges() { return ranges_; } + CharacterClassStrings* strings() { + ASSERT(strings_ != nullptr); + return strings_; + } + + private: + ZoneList* ranges_; + CharacterClassStrings* strings_; + int min_match_; + int max_match_; +}; + +class RegExpClassSetExpression final : public RegExpTree { + public: + enum class OperationType { kUnion, kIntersection, kSubtraction }; + + RegExpClassSetExpression(OperationType op, + bool is_negated, + bool may_contain_strings, + ZoneList* operands); + + DECL_BOILERPLATE(ClassSetExpression); + + // Create an empty class set expression (matches everything if |is_negated|, + // nothing otherwise). + static RegExpClassSetExpression* Empty(Zone* zone, bool is_negated); + + bool IsTextElement() override { return true; } + int min_match() override { return 0; } + int max_match() override { return max_match_; } + + OperationType operation() const { return operation_; } + bool is_negated() const { return is_negated_; } + bool may_contain_strings() const { return may_contain_strings_; } + const ZoneList* operands() const { return operands_; } + ZoneList* operands() { return operands_; } + + private: + // Recursively evaluates the tree rooted at |root|, computing the valid + // CharacterRanges and strings after applying all set operations. + // The original tree will be modified by this method, so don't store pointers + // to inner nodes of the tree somewhere else! + // Modifying the tree in-place saves memory and speeds up multiple calls of + // the method (e.g. when unrolling quantifiers). + // |temp_ranges| is used for intermediate results, passed as parameter to + // avoid allocating new lists all the time. + static RegExpClassSetOperand* ComputeExpression( + RegExpTree* root, + ZoneList* temp_ranges, + Zone* zone); + + const OperationType operation_; + bool is_negated_; + const bool may_contain_strings_; + ZoneList* operands_ = nullptr; + int max_match_; +}; + +class RegExpAtom final : public RegExpTree { + public: + explicit RegExpAtom(base::Vector data) : data_(data) {} + + DECL_BOILERPLATE(Atom); + + bool IsTextElement() override { return true; } + int min_match() override { return data_.length(); } + int max_match() override { return data_.length(); } + void AppendToText(RegExpText* text, Zone* zone) override; + + base::Vector data() const { return data_; } + int length() const { return data_.length(); } + + private: + base::Vector data_; +}; + +class TextElement final { + public: + enum TextType { ATOM, CLASS_RANGES }; + + static TextElement Atom(RegExpAtom* atom); + static TextElement ClassRanges(RegExpClassRanges* class_ranges); + + int cp_offset() const { return cp_offset_; } + void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; } + int length() const; + + TextType text_type() const { return text_type_; } + + RegExpTree* tree() const { return tree_; } + + RegExpAtom* atom() const { + ASSERT(text_type() == ATOM); + return reinterpret_cast(tree()); + } + + RegExpClassRanges* class_ranges() const { + ASSERT(text_type() == CLASS_RANGES); + return reinterpret_cast(tree()); + } + + private: + TextElement(TextType text_type, RegExpTree* tree) + : cp_offset_(-1), text_type_(text_type), tree_(tree) {} + + int cp_offset_; + TextType text_type_; + RegExpTree* tree_; +}; + +class RegExpText final : public RegExpTree { + public: + explicit RegExpText(Zone* zone) : elements_(2, zone) {} + + DECL_BOILERPLATE(Text); + + bool IsTextElement() override { return true; } + int min_match() override { return length_; } + int max_match() override { return length_; } + void AppendToText(RegExpText* text, Zone* zone) override; + void AddElement(TextElement elm, Zone* zone) { + elements_.Add(elm, zone); + length_ += elm.length(); + } + ZoneList* elements() { return &elements_; } + + bool StartsWithAtom() const; + RegExpAtom* FirstAtom() const; + + private: + ZoneList elements_; + int length_ = 0; +}; + +class RegExpQuantifier final : public RegExpTree { + public: + enum QuantifierType { GREEDY, NON_GREEDY, POSSESSIVE }; + RegExpQuantifier(int min, + int max, + QuantifierType type, + int index, + RegExpTree* body) + : body_(body), + min_(min), + max_(max), + quantifier_type_(type), + index_(index) { + if (min > 0 && body->min_match() > kInfinity / min) { + min_match_ = kInfinity; + } else { + min_match_ = min * body->min_match(); + } + if (max > 0 && body->max_match() > kInfinity / max) { + max_match_ = kInfinity; + } else { + max_match_ = max * body->max_match(); + } + } + + DECL_BOILERPLATE(Quantifier); + + static RegExpNode* ToNode(int min, + int max, + bool is_greedy, + RegExpTree* body, + RegExpCompiler* compiler, + RegExpNode* on_success, + bool not_at_start = false); + Interval CaptureRegisters() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + int min() const { return min_; } + int max() const { return max_; } + QuantifierType quantifier_type() const { return quantifier_type_; } + int index() const { return index_; } + bool is_possessive() const { return quantifier_type_ == POSSESSIVE; } + bool is_non_greedy() const { return quantifier_type_ == NON_GREEDY; } + bool is_greedy() const { return quantifier_type_ == GREEDY; } + RegExpTree* body() const { return body_; } + + private: + RegExpTree* body_; + int min_; + int max_; + int min_match_; + int max_match_; + QuantifierType quantifier_type_; + int index_; +}; + +class RegExpCapture final : public RegExpTree { + public: + explicit RegExpCapture(int index) + : body_(nullptr), + index_(index), + min_match_(0), + max_match_(0), + name_(nullptr) {} + + DECL_BOILERPLATE(Capture); + + static RegExpNode* ToNode(RegExpTree* body, + int index, + RegExpCompiler* compiler, + RegExpNode* on_success); + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + Interval CaptureRegisters() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + RegExpTree* body() { return body_; } + void set_body(RegExpTree* body) { + body_ = body; + min_match_ = body->min_match(); + max_match_ = body->max_match(); + } + int index() const { return index_; } + const ZoneVector* name() const { return name_; } + void set_name(const ZoneVector* name) { name_ = name; } + static int StartRegister(int index) { return index * 2; } + static int EndRegister(int index) { return index * 2 + 1; } + + private: + RegExpTree* body_ = nullptr; + int index_; + int min_match_ = 0; + int max_match_ = 0; + const ZoneVector* name_ = nullptr; +}; + +class RegExpGroup final : public RegExpTree { + public: + explicit RegExpGroup(RegExpTree* body, RegExpFlags flags) + : body_(body), + flags_(flags), + min_match_(body->min_match()), + max_match_(body->max_match()) {} + + DECL_BOILERPLATE(Group); + + bool IsAnchoredAtStart() override { return body_->IsAnchoredAtStart(); } + bool IsAnchoredAtEnd() override { return body_->IsAnchoredAtEnd(); } + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + Interval CaptureRegisters() override { return body_->CaptureRegisters(); } + RegExpTree* body() const { return body_; } + RegExpFlags flags() const { return flags_; } + + private: + RegExpTree* body_; + const RegExpFlags flags_; + int min_match_; + int max_match_; +}; + +class RegExpLookaround final : public RegExpTree { + public: + enum Type { LOOKAHEAD, LOOKBEHIND }; + + RegExpLookaround(RegExpTree* body, + bool is_positive, + int capture_count, + int capture_from, + Type type, + int index) + : body_(body), + is_positive_(is_positive), + capture_count_(capture_count), + capture_from_(capture_from), + type_(type), + index_(index) {} + + DECL_BOILERPLATE(Lookaround); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + int min_match() override { return 0; } + int max_match() override { return 0; } + RegExpTree* body() const { return body_; } + bool is_positive() const { return is_positive_; } + int capture_count() const { return capture_count_; } + int capture_from() const { return capture_from_; } + Type type() const { return type_; } + int index() const { return index_; } + + class Builder { + public: + Builder(bool is_positive, + RegExpNode* on_success, + RegExpCompiler* compiler, + int stack_pointer_register, + int position_register, + int capture_register_count = 0, + int capture_register_start = 0); + RegExpNode* on_match_success() const { return on_match_success_; } + RegExpNode* ForMatch(RegExpCompiler*, RegExpNode* match); + + private: + bool is_positive_; + RegExpNode* on_match_success_; + RegExpNode* on_success_; + int stack_pointer_register_; + int position_register_; + }; + + private: + RegExpTree* body_; + bool is_positive_; + int capture_count_; + int capture_from_; + Type type_; + int index_; +}; + +class RegExpBackReference final : public RegExpTree { + public: + explicit RegExpBackReference(Zone* zone) : captures_(1, zone) {} + explicit RegExpBackReference(RegExpCapture* capture, Zone* zone) + : captures_(1, zone) { + captures_.Add(capture, zone); + } + + DECL_BOILERPLATE(BackReference); + + int min_match() override { return 0; } + // The back reference may be recursive, e.g. /(\2)(\1)/. To avoid infinite + // recursion, we give up. Ignorance is bliss. + int max_match() override { return kInfinity; } + const ZoneList* captures() const { return &captures_; } + void add_capture(RegExpCapture* capture, Zone* zone) { + captures_.Add(capture, zone); + } + const ZoneVector* name() const { return name_; } + void set_name(const ZoneVector* name) { name_ = name; } + + private: + ZoneList captures_; + const ZoneVector* name_ = nullptr; +}; + +class RegExpEmpty final : public RegExpTree { + public: + DECL_BOILERPLATE(Empty); + int min_match() override { return 0; } + int max_match() override { return 0; } +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_AST_H_ diff --git a/runtime/vm/regexp/regexp-bytecode-generator-inl.h b/runtime/vm/regexp/regexp-bytecode-generator-inl.h new file mode 100644 index 00000000000..2940d09c322 --- /dev/null +++ b/runtime/vm/regexp/regexp-bytecode-generator-inl.h @@ -0,0 +1,95 @@ +// Copyright 2008-2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ +#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ + +#include "vm/regexp/regexp-bytecode-generator.h" +// Include the non-inl header before the rest of the headers. + +#include "vm/regexp/regexp-bytecodes-inl.h" + +namespace dart { + +template +void RegExpBytecodeWriter::Emit(T value, int offset) { + const int new_pc_within_bc = pc_ + offset; + DCHECK(base::IsInRange(new_pc_within_bc, pc_within_bc_, end_of_bc_)); + DCHECK_LE(new_pc_within_bc + sizeof(T), buffer_.size()); + // Dart: V8 has mixed sign comparison + // DCHECK_LE(new_pc_within_bc + sizeof(T), end_of_bc_); + DCHECK(Utils::IsAligned(new_pc_within_bc, sizeof(T))); + EMIT_PADDING(offset); + *reinterpret_cast(buffer_.data() + new_pc_within_bc) = value; +#ifdef DEBUG + pc_within_bc_ = new_pc_within_bc + sizeof(T); +#endif +} + +template +void RegExpBytecodeWriter::OverwriteValue(T value, int absolute_offset) { + // TODO(jgruber): Consider specializing this function; there should be very + // few uses (updating jump offsets). + ASSERT(Utils::IsAligned(absolute_offset, sizeof(T))); + DCHECK_LE(absolute_offset + sizeof(T), buffer_.size()); + *reinterpret_cast(buffer_.data() + absolute_offset) = value; +} + +void RegExpBytecodeWriter::EmitBytecode(RegExpBytecode bc) { + DCHECK_EQ(pc_, end_of_bc_); + DCHECK_EQ(pc_within_bc_, end_of_bc_); +#ifdef DEBUG + end_of_bc_ = pc_ + RegExpBytecodes::Size(bc); + pc_within_bc_ = pc_; +#endif + EnsureCapacity(RegExpBytecodes::Size(bc)); + Emit(RegExpBytecodes::ToByte(bc), 0); +} + +void RegExpBytecodeWriter::EnsureCapacity(size_t size_delta) { + const size_t required_size = pc_ + size_delta; + size_t size = buffer_.size(); + if (LIKELY(size >= required_size)) return; + if (required_size < kInitialBufferSizeInBytes) { + size = kInitialBufferSizeInBytes; + } else if (required_size <= kMaxBufferGrowthInBytes) { + // We use a doubling strategy until hitting the limit. + size = Utils::RoundUpToPowerOfTwo(required_size); + } else { + // .. and kMaxBufferGrowthInBytes chunks afterwards. + size = Utils::RoundUp(required_size, kMaxBufferGrowthInBytes); + } + ExpandBuffer(size); + DCHECK_LE(required_size, buffer_.size()); +} + +void RegExpBytecodeWriter::ResetPc(int new_pc) { + // Resetting is only allowed at the beginning of a bytecode. + DCHECK_EQ(pc_, pc_within_bc_); + DCHECK_LE(new_pc, pc_); + pc_ = new_pc; +#ifdef DEBUG + pc_within_bc_ = pc_; + end_of_bc_ = pc_; +#endif +} + +#ifdef DEBUG +void RegExpBytecodeWriter::EmitPadding(int offset) { + const int padding_to = pc_ + offset; + // Dart: V8 has mixed sign comparison + // DCHECK_LE(padding_to, buffer_.size()); + // DCHECK_LE(padding_to, end_of_bc_); + // DCHECK_GE(padding_to, pc_within_bc_); + static constexpr uint8_t kPaddingByte = 0x0; + for (int i = pc_within_bc_; i < padding_to; ++i) { + buffer_[i] = kPaddingByte; + } + pc_within_bc_ = padding_to; +} +#endif + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ diff --git a/runtime/vm/regexp/regexp-bytecode-generator.cc b/runtime/vm/regexp/regexp-bytecode-generator.cc new file mode 100644 index 00000000000..b6450a24784 --- /dev/null +++ b/runtime/vm/regexp/regexp-bytecode-generator.cc @@ -0,0 +1,652 @@ +// Copyright 2008-2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-bytecode-generator.h" + +#include +#include +#include + +#include "vm/regexp/regexp-bytecode-generator-inl.h" +#include "vm/regexp/regexp-bytecodes-inl.h" +#include "vm/regexp/regexp-macro-assembler.h" +#include "vm/regexp/regexp.h" + +namespace dart { + +// Used to decide whether we use the `Char` or `4Chars` variant of a bytecode. +static constexpr int kMaxSingleCharValue = + RegExpOperandTypeTraits::kMaxValue; + +// TODO(jgruber): Move all Writer methods before Generator methods. + +RegExpBytecodeWriter::RegExpBytecodeWriter(Zone* zone) + : buffer_(zone), + pc_(0), + jump_edges_(zone) +#ifdef DEBUG + , + end_of_bc_(0), + pc_within_bc_(0) +#endif +{ +} + +void RegExpBytecodeWriter::ExpandBuffer(size_t new_size) { + // TODO(jgruber): It's not necessary to default-initialize new elements. + buffer_.resize(new_size); +} + +void RegExpBytecodeWriter::Reset() { + // We keep the buffer_ storage; the next pass will overwrite its contents. + jump_edges_.clear(); + ResetPc(0); +} + +void RegExpBytecodeWriter::EmitRawBytecodeStream(const uint8_t* data, int len) { + EnsureCapacity(len); + // Must start at a bytecode boundary. + DCHECK_EQ(pc_within_bc_, end_of_bc_); + // We cannot check whether we also end at a boundary since we don't know what + // data contains. Let's at least verify alignment. + // TODO(jgruber): We could use RegExpBytecodeIterator to verify in DEBUG. + ASSERT(Utils::IsAligned(len, kBytecodeAlignment)); + memcpy(buffer_.data() + pc_, data, len); + // End at a bytecode boundary, update bookkeeping. + pc_ += len; +#ifdef DEBUG + pc_within_bc_ = pc_; + end_of_bc_ = pc_; +#endif +} + +void RegExpBytecodeWriter::EmitRawBytecodeStream( + const RegExpBytecodeWriter* src_writer, + int src_offset, + int length) { + const int start_pc = pc_; + + EmitRawBytecodeStream(src_writer->buffer().data() + src_offset, length); + + // Copy jumps in range. + const auto& src_edges = src_writer->jump_edges(); + auto jump_iter = src_edges.lower_bound(src_offset); + // Iterate over all jumps that start in the copied range. + while (jump_iter != src_edges.end() && + jump_iter->first < src_offset + length) { + int old_source = jump_iter->first; + int old_target = jump_iter->second; + int new_source = start_pc + (old_source - src_offset); + jump_edges_.emplace(new_source, old_target); + jump_iter++; + } +} + +void RegExpBytecodeWriter::Finalize(RegExpBytecode bc) { + int size = RegExpBytecodes::Size(bc); + EMIT_PADDING(size); + pc_ += size; +#ifdef DEBUG + DCHECK_EQ(pc_within_bc_, end_of_bc_); + pc_within_bc_ = pc_; + end_of_bc_ = pc_; +#endif +} + +RegExpBytecodeGenerator::RegExpBytecodeGenerator(Isolate* isolate, + Zone* zone, + Mode mode) + : RegExpMacroAssembler(isolate, zone, mode), + RegExpBytecodeWriter(zone), + isolate_(isolate) {} + +RegExpBytecodeGenerator::~RegExpBytecodeGenerator() { + if (backtrack_.is_linked()) backtrack_.Unuse(); +} + +RegExpBytecodeGenerator::IrregexpImplementation +RegExpBytecodeGenerator::Implementation() { + return kBytecodeImplementation; +} + +template +void RegExpBytecodeWriter::Emit(Args... args) { + using Operands = RegExpBytecodeOperands; + static_assert(sizeof...(Args) == Operands::kCount, + "Wrong number of operands"); + + auto arguments_tuple = std::make_tuple(args...); + EmitBytecode(bytecode); + Operands::ForEachOperandWithIndex([&]() { + constexpr RegExpBytecodeOperandType type = Operands::Type(op); + constexpr int offset = Operands::Offset(op); + auto value = std::get(arguments_tuple); + EmitOperand(value, offset); + }); + Finalize(bytecode); +} + +namespace { + +// Helper to get the underlying type of an enum, or the type itself if it isn't +// an enum. +template +struct get_underlying_or_self { + using type = T; +}; + +template + requires std::is_enum_v +struct get_underlying_or_self { + using type = std::underlying_type_t; +}; + +} // namespace + +template +auto RegExpBytecodeWriter::GetCheckedBasicOperandValue(T value) { + static_assert(RegExpOperandTypeTraits::kIsBasic); + using Traits = RegExpOperandTypeTraits; + using EnumOrCType = Traits::kCType; + using CType = get_underlying_or_self::type; + if constexpr (std::is_enum_v) { + static_assert(std::is_same_v); + } else { + static_assert(std::is_convertible_v); + } + DCHECK_GE(value, Traits::kMinValue); + DCHECK_LE(value, Traits::kMaxValue); + return static_cast(value); +} + +template +void RegExpBytecodeWriter::EmitOperand(T value, int offset) { + if constexpr (OperandType == RegExpBytecodeOperandType::kJumpTarget) { + jump_edges_.emplace(pc_ + offset, static_cast(value)); + } + Emit(GetCheckedBasicOperandValue(value), offset); +} + +void RegExpBytecodeWriter::PatchJump(int target, int absolute_offset) { + ASSERT(jump_edges_.contains(absolute_offset)); + OverwriteValue(target, absolute_offset); + jump_edges_[absolute_offset] = target; +} + +template +void RegExpBytecodeWriter::EmitOperand(RegExpBytecodeOperandType type, + T value, + int offset) { + switch (type) { +#define CASE(Name, ...) \ + case RegExpBytecodeOperandType::k##Name: \ + return EmitOperand(value, offset); + BYTECODE_OPERAND_TYPE_LIST(CASE) +#undef CASE + default: + UNREACHABLE(); + } +} + +template <> +void RegExpBytecodeWriter::EmitOperand(V8Label* label, + int offset) { + DCHECK_NOT_NULL(label); + const int current_pc = pc_ + offset; + int pos = 0; + if (label->is_bound()) { + pos = label->pos(); + jump_edges_.emplace(current_pc, pos); + } else { + if (label->is_linked()) { + pos = label->pos(); + } + label->link_to(current_pc); + } + Emit(pos, offset); +} + +template <> +void RegExpBytecodeWriter::EmitOperand( + const TypedData* table, + int offset) { + for (int i = 0; i < RegExpMacroAssembler::kTableSize; i += kBitsPerByte) { + uint8_t byte = 0; + for (int j = 0; j < kBitsPerByte; j++) { + if (table->GetUint8(i + j) != 0) byte |= 1 << j; + } + Emit(byte, offset + i / kBitsPerByte); + } +} + +template <> +void RegExpBytecodeWriter::EmitOperand( + const uint8_t* src, + int offset) { + // The emitted table operand is 16 bytes long. + static_assert(RegExpMacroAssembler::kTableSize / kBitsPerByte == 16); + const uint32_t* cursor = reinterpret_cast(src); + static constexpr int kWordCount = + (RegExpMacroAssembler::kTableSize / (kBitsPerByte * kInt32Size)); + for (int i = 0; i < kWordCount; i++) { + Emit(cursor[i], offset + i * kInt32Size); + } +} + +template +void RegExpBytecodeGenerator::Emit(Args... args) { + // Converts nullptr labels into our internal backtrack_ label. + DART_UNUSED auto fix_label = [this](auto arg) { + if constexpr (std::is_convertible_v) { + V8Label* l = static_cast(arg); + return l ? l : &backtrack_; + } else { + return arg; + } + }; + RegExpBytecodeWriter::Emit(fix_label(args)...); +} + +void RegExpBytecodeGenerator::Bind(V8Label* l) { + ASSERT(!l->is_bound()); + if (l->is_linked()) { + int pos = l->pos(); + while (pos != 0) { + int fixup = pos; + pos = *reinterpret_cast(buffer_.data() + fixup); + OverwriteValue(pc_, fixup); + jump_edges().emplace(fixup, pc_); + } + } + l->bind_to(pc_); +} + +void RegExpBytecodeGenerator::PopRegister(int register_index) { + Emit(register_index); +} + +void RegExpBytecodeGenerator::PushRegister(int register_index, + StackCheckFlag check_stack_limit) { + Emit(register_index, check_stack_limit); +} + +void RegExpBytecodeGenerator::WriteCurrentPositionToRegister(int register_index, + int cp_offset) { + Emit(register_index, + cp_offset); +} + +void RegExpBytecodeGenerator::ClearRegisters(int reg_from, int reg_to) { + DCHECK_LE(reg_from, reg_to); + Emit(reg_from, reg_to); +} + +void RegExpBytecodeGenerator::ReadCurrentPositionFromRegister( + int register_index) { + Emit(register_index); +} + +void RegExpBytecodeGenerator::WriteStackPointerToRegister(int register_index) { + Emit(register_index); +} + +void RegExpBytecodeGenerator::ReadStackPointerFromRegister(int register_index) { + Emit(register_index); +} + +void RegExpBytecodeGenerator::SetCurrentPositionFromEnd(int by) { + Emit(by); +} + +void RegExpBytecodeGenerator::SetRegister(int register_index, int to) { + Emit(register_index, to); +} + +void RegExpBytecodeGenerator::AdvanceRegister(int register_index, int by) { + Emit(register_index, by); +} + +void RegExpBytecodeGenerator::PopCurrentPosition() { + Emit(); +} + +void RegExpBytecodeGenerator::PushCurrentPosition() { + Emit(); +} + +void RegExpBytecodeGenerator::Backtrack() { + int error_code = can_fallback() ? RegExpStatics::RE_FALLBACK_TO_EXPERIMENTAL + : RegExpStatics::RE_FAILURE; + Emit(error_code); +} + +void RegExpBytecodeGenerator::GoTo(V8Label* label) { + Emit(label); +} + +void RegExpBytecodeGenerator::PushBacktrack(V8Label* label) { + Emit(label); +} + +bool RegExpBytecodeGenerator::Succeed() { + Emit(); + return false; // Restart matching for global regexp not supported. +} + +void RegExpBytecodeGenerator::Fail() { + Emit(); +} + +void RegExpBytecodeGenerator::AdvanceCurrentPosition(int by) { + Emit(by); +} + +void RegExpBytecodeGenerator::CheckFixedLengthLoop( + V8Label* on_tos_equals_current_position) { + Emit(on_tos_equals_current_position); +} + +void RegExpBytecodeGenerator::CheckPosition(int cp_offset, + V8Label* on_outside_input) { + Emit(cp_offset, on_outside_input); +} + +void RegExpBytecodeGenerator::CheckSpecialClassRanges(StandardCharacterSet type, + V8Label* on_no_match) { + ASSERT(CanOptimizeSpecialClassRanges(type)); + Emit(type, on_no_match); +} + +void RegExpBytecodeGenerator::LoadCurrentCharacterImpl(int cp_offset, + V8Label* on_failure, + bool check_bounds, + int characters, + int eats_at_least) { + DCHECK_GE(eats_at_least, characters); + if (eats_at_least > characters && check_bounds) { + Emit(cp_offset + eats_at_least - 1, + on_failure); + check_bounds = false; // Load below doesn't need to check. + } + + CHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); + if (check_bounds) { + if (characters == 4) { + Emit(cp_offset, on_failure); + } else if (characters == 2) { + Emit(cp_offset, on_failure); + } else { + DCHECK_EQ(1, characters); + Emit(cp_offset, on_failure); + } + } else { + if (characters == 4) { + Emit(cp_offset); + } else if (characters == 2) { + Emit(cp_offset); + } else { + DCHECK_EQ(1, characters); + Emit(cp_offset); + } + } +} + +void RegExpBytecodeGenerator::CheckCharacterLT(uint16_t limit, + V8Label* on_less) { + Emit(limit, on_less); +} + +void RegExpBytecodeGenerator::CheckCharacterGT(uint16_t limit, + V8Label* on_greater) { + Emit(limit, on_greater); +} + +void RegExpBytecodeGenerator::CheckCharacter(uint32_t c, V8Label* on_equal) { + if (c > kMaxSingleCharValue) { + Emit(c, on_equal); + } else { + Emit(c, on_equal); + } +} + +void RegExpBytecodeGenerator::CheckAtStart(int cp_offset, + V8Label* on_at_start) { + Emit(cp_offset, on_at_start); +} + +void RegExpBytecodeGenerator::CheckNotAtStart(int cp_offset, + V8Label* on_not_at_start) { + Emit(cp_offset, on_not_at_start); +} + +void RegExpBytecodeGenerator::CheckNotCharacter(uint32_t c, + V8Label* on_not_equal) { + if (c > kMaxSingleCharValue) { + Emit(c, on_not_equal); + } else { + Emit(c, on_not_equal); + } +} + +void RegExpBytecodeGenerator::CheckCharacterAfterAnd(uint32_t c, + uint32_t mask, + V8Label* on_equal) { + // TODO(pthier): This is super hacky. We could still check for 4 characters + // (with the last 2 being 0 after masking them), but not emit AndCheck4Chars. + // This is rather confusing and should be changed. + if (c > kMaxSingleCharValue) { + Emit(c, mask, on_equal); + } else { + Emit(c, mask, on_equal); + } +} + +void RegExpBytecodeGenerator::CheckNotCharacterAfterAnd(uint32_t c, + uint32_t mask, + V8Label* on_not_equal) { + // TODO(pthier): This is super hacky. We could still check for 4 characters + // (with the last 2 being 0 after masking them), but not emit AndCheck4Chars. + // This is rather confusing and should be changed. + if (c > kMaxSingleCharValue) { + Emit(c, mask, on_not_equal); + } else { + Emit(c, mask, on_not_equal); + } +} + +void RegExpBytecodeGenerator::CheckNotCharacterAfterMinusAnd( + uint16_t c, + uint16_t minus, + uint16_t mask, + V8Label* on_not_equal) { + Emit(c, minus, mask, + on_not_equal); +} + +void RegExpBytecodeGenerator::CheckCharacterInRange(uint16_t from, + uint16_t to, + V8Label* on_in_range) { + Emit(from, to, on_in_range); +} + +void RegExpBytecodeGenerator::CheckCharacterNotInRange( + uint16_t from, + uint16_t to, + V8Label* on_not_in_range) { + Emit(from, to, on_not_in_range); +} + +void RegExpBytecodeGenerator::CheckBitInTable(const TypedData& table, + V8Label* on_bit_set) { + Emit(on_bit_set, &table); +} + +void RegExpBytecodeGenerator::SkipUntilBitInTable(int cp_offset, + const TypedData& table, + const TypedData& nibble_table, + int advance_by, + V8Label* on_match, + V8Label* on_no_match) { + Emit(cp_offset, advance_by, &table, + on_match, on_no_match); +} + +void RegExpBytecodeGenerator::SkipUntilCharAnd(int cp_offset, + int advance_by, + unsigned character, + unsigned mask, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) { + Emit(cp_offset, advance_by, character, + mask, eats_at_least, on_match, + on_no_match); +} + +void RegExpBytecodeGenerator::SkipUntilChar(int cp_offset, + int advance_by, + unsigned character, + V8Label* on_match, + V8Label* on_no_match) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::SkipUntilCharPosChecked(int cp_offset, + int advance_by, + unsigned character, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::SkipUntilCharOrChar(int cp_offset, + int advance_by, + unsigned char1, + unsigned char2, + V8Label* on_match, + V8Label* on_no_match) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::SkipUntilGtOrNotBitInTable(int cp_offset, + int advance_by, + unsigned character, + const TypedData& table, + V8Label* on_match, + V8Label* on_no_match) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::SkipUntilOneOfMasked(int cp_offset, + int advance_by, + unsigned both_chars, + unsigned both_mask, + int max_offset, + unsigned chars1, + unsigned mask1, + unsigned chars2, + unsigned mask2, + V8Label* on_match1, + V8Label* on_match2, + V8Label* on_failure) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::SkipUntilOneOfMasked3( + const SkipUntilOneOfMasked3Args& args) { + // Only generated by peephole optimization. + UNREACHABLE(); +} + +void RegExpBytecodeGenerator::CheckNotBackReference(int start_reg, + bool read_backward, + V8Label* on_not_equal) { + if (read_backward) { + Emit(start_reg, on_not_equal); + } else { + Emit(start_reg, on_not_equal); + } +} + +void RegExpBytecodeGenerator::CheckNotBackReferenceIgnoreCase( + int start_reg, + bool read_backward, + bool unicode, + V8Label* on_not_equal) { + if (read_backward) { + if (unicode) { + Emit(start_reg, + on_not_equal); + } else { + Emit(start_reg, + on_not_equal); + } + } else { + if (unicode) { + Emit(start_reg, + on_not_equal); + } else { + Emit(start_reg, on_not_equal); + } + } +} + +void RegExpBytecodeGenerator::IfRegisterLT(int register_index, + int comparand, + V8Label* on_less_than) { + Emit(register_index, comparand, on_less_than); +} + +void RegExpBytecodeGenerator::IfRegisterGE(int register_index, + int comparand, + V8Label* on_greater_or_equal) { + Emit(register_index, comparand, + on_greater_or_equal); +} + +void RegExpBytecodeGenerator::IfRegisterEqPos(int register_index, + V8Label* on_equal) { + Emit(register_index, on_equal); +} + +ObjectPtr RegExpBytecodeGenerator::GetCode(const String& source, + RegExpFlags flags) { + Bind(&backtrack_); + Backtrack(); + + if (FLAG_regexp_peephole_optimization) { + UNIMPLEMENTED(); + // return RegExpBytecodePeepholeOptimization::OptimizeBytecode( + // isolate_, zone(), source, this); + return TypedData::null(); + } else { + const TypedData& array = + TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, length())); + NoSafepointScope no_safepoint; + CopyBufferTo((uint8_t*)array.DataAddr(0)); + return array.ptr(); + } +} + +void RegExpBytecodeWriter::CopyBufferTo(uint8_t* a) const { + base::MemCopy(a, buffer_.data(), length()); +} + +// Instantiate template methods. +#define CASE(Name, ...) \ + template void \ + RegExpBytecodeWriter::EmitOperand( \ + RegExpOperandTypeTraits::kCType, \ + int); +BASIC_BYTECODE_OPERAND_TYPE_LIST(CASE) +BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(CASE) +#undef CASE + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-bytecode-generator.h b/runtime/vm/regexp/regexp-bytecode-generator.h new file mode 100644 index 00000000000..1540c473aed --- /dev/null +++ b/runtime/vm/regexp/regexp-bytecode-generator.h @@ -0,0 +1,272 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ +#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ + +#include "vm/regexp/label.h" +#include "vm/regexp/regexp-bytecodes.h" +#include "vm/regexp/regexp-macro-assembler.h" + +namespace dart { + +class RegExpBytecodeWriter { + public: + explicit RegExpBytecodeWriter(Zone* zone); + virtual ~RegExpBytecodeWriter() = default; + + // Helpers for peephole optimization. + template + void OverwriteValue(T value, int absolute_offset); + // MUST start and end at a bytecode boundary. + void EmitRawBytecodeStream(const uint8_t* data, int length); + void EmitRawBytecodeStream(const RegExpBytecodeWriter* src_writer, + int src_offset, + int length); + void Finalize(RegExpBytecode bc); + + // Bytecode buffer access. + // TODO(jgruber): Remove access to details, at least the non-const accessors. + int pc() const { return pc_; } + ZoneVector& buffer() { return buffer_; } + const ZoneVector& buffer() const { return buffer_; } + + // Code and bitmap emission. + template + inline void Emit(T value, int offset); + inline void EmitBytecode(RegExpBytecode bc); + + // Update bookkeeping at bytecode boundaries. + inline void ResetPc(int new_pc); + // Reset all state. + void Reset(); + + // Templated code emission. + template + void Emit(Args... args); + template + void EmitOperand(T value, int offset); + template + auto GetCheckedBasicOperandValue(T value); + + // Runtime versions. + template + void EmitOperand(RegExpBytecodeOperandType type, T value, int offset); + + int length() const { return pc_; } + void CopyBufferTo(uint8_t* a) const; + + ZoneMap& jump_edges() { return jump_edges_; } + const ZoneMap& jump_edges() const { return jump_edges_; } + + void PatchJump(int target, int absolute_offset); + +#ifdef DEBUG + // Emit padding from start (inclusive) to end (exclusive) + inline void EmitPadding(int offset); +#define EMIT_PADDING(offset) EmitPadding(offset) +#else +#define EMIT_PADDING(offset) ((void)0) +#endif + + protected: + // The buffer into which code and relocation info are generated. + static constexpr int kInitialBufferSizeInBytes = 1 * KB; + static constexpr size_t kMaxBufferGrowthInBytes = 1 * MB; + ZoneVector buffer_; + + // The program counter. Always points at the beginning of a bytecode while + // we generate the ByteArray. Points to the end when we are done. + int pc_; + + private: + // Stores jump edges emitted for the bytecode (used by + // RegExpBytecodePeepholeOptimization). + // Key: jump source (offset in buffer_ where jump destination is stored). + // Value: jump destination (offset in buffer_ to jump to). + ZoneMap jump_edges_; + +#ifdef DEBUG + // End of the bytecode we are currently emitting (exclusive). Absolute value + // greater than `pc_`. + int end_of_bc_; + // Position (absolute) within the current bytecode. This value is updated with + // every operand and is guaranteed to be between `pc_` and `end_of_bc_`. + int pc_within_bc_; +#endif + + // TODO(jgruber): Reasonable protected/private organisation once the dust has + // settled. + inline void EnsureCapacity(size_t size); + void ExpandBuffer(size_t new_size); +}; + +// An assembler/generator for the Irregexp byte code. +class RegExpBytecodeGenerator : public RegExpMacroAssembler, + public RegExpBytecodeWriter { + public: + // Create an assembler. Instructions and relocation information are emitted + // into a buffer, with the instructions starting from the beginning and the + // relocation information starting from the end of the buffer. See CodeDesc + // for a detailed comment on the layout (globals.h). + // + // The assembler allocates and grows its own buffer, and buffer_size + // determines the initial buffer size. The buffer is owned by the assembler + // and deallocated upon destruction of the assembler. + RegExpBytecodeGenerator(Isolate* isolate, Zone* zone, Mode mode); + ~RegExpBytecodeGenerator() override; + void Bind(V8Label* label) override; + void AdvanceCurrentPosition(int by) override; // Signed cp change. + void PopCurrentPosition() override; + void PushCurrentPosition() override; + void Backtrack() override; + void GoTo(V8Label* label) override; + void PushBacktrack(V8Label* label) override; + bool Succeed() override; + void Fail() override; + void PopRegister(int register_index) override; + void PushRegister(int register_index, + StackCheckFlag check_stack_limit) override; + void AdvanceRegister(int register_index, int by) override; // r[reg] += by. + void SetCurrentPositionFromEnd(int by) override; + void SetRegister(int register_index, int to) override; + void WriteCurrentPositionToRegister(int register_index, + int cp_offset) override; + void ClearRegisters(int reg_from, int reg_to) override; + void ReadCurrentPositionFromRegister(int reg) override; + void WriteStackPointerToRegister(int register_index) override; + void ReadStackPointerFromRegister(int register_index) override; + void CheckPosition(int cp_offset, V8Label* on_outside_input) override; + void CheckSpecialClassRanges(StandardCharacterSet type, + V8Label* on_no_match) override; + void LoadCurrentCharacterImpl(int cp_offset, + V8Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) override; + void CheckCharacter(unsigned c, V8Label* on_equal) override; + void CheckCharacterAfterAnd(unsigned c, + unsigned mask, + V8Label* on_equal) override; + void CheckCharacterGT(uint16_t limit, V8Label* on_greater) override; + void CheckCharacterLT(uint16_t limit, V8Label* on_less) override; + void CheckFixedLengthLoop(V8Label* on_tos_equals_current_position) override; + void CheckAtStart(int cp_offset, V8Label* on_at_start) override; + void CheckNotAtStart(int cp_offset, V8Label* on_not_at_start) override; + void CheckNotCharacter(unsigned c, V8Label* on_not_equal) override; + void CheckNotCharacterAfterAnd(unsigned c, + unsigned mask, + V8Label* on_not_equal) override; + void CheckNotCharacterAfterMinusAnd(uint16_t c, + uint16_t minus, + uint16_t mask, + V8Label* on_not_equal) override; + void CheckCharacterInRange(uint16_t from, + uint16_t to, + V8Label* on_in_range) override; + void CheckCharacterNotInRange(uint16_t from, + uint16_t to, + V8Label* on_not_in_range) override; + bool CheckCharacterInRangeArray(const ZoneList* ranges, + V8Label* on_in_range) override { + // Disabled in the interpreter, because 1) there is no constant pool that + // could store the ByteArray pointer, 2) bytecode size limits are not as + // restrictive as code (e.g. branch distances on arm), 3) bytecode for + // large character classes is already quite compact. + // TODO(jgruber): Consider using BytecodeArrays (with a constant pool) + // instead of plain ByteArrays; then we could implement + // CheckCharacterInRangeArray in the interpreter. + return false; + } + bool CheckCharacterNotInRangeArray(const ZoneList* ranges, + V8Label* on_not_in_range) override { + return false; + } + void CheckBitInTable(const TypedData& table, V8Label* on_bit_set) override; + void SkipUntilBitInTable(int cp_offset, + const TypedData& table, + const TypedData& nibble_table, + int advance_by, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilCharAnd(int cp_offset, + int advance_by, + unsigned character, + unsigned mask, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilChar(int cp_offset, + int advance_by, + unsigned character, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilCharPosChecked(int cp_offset, + int advance_by, + unsigned character, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilCharOrChar(int cp_offset, + int advance_by, + unsigned char1, + unsigned char2, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilGtOrNotBitInTable(int cp_offset, + int advance_by, + unsigned character, + const TypedData& table, + V8Label* on_match, + V8Label* on_no_match) override; + void SkipUntilOneOfMasked(int cp_offset, + int advance_by, + unsigned both_chars, + unsigned both_mask, + int max_offset, + unsigned chars1, + unsigned mask1, + unsigned chars2, + unsigned mask2, + V8Label* on_match1, + V8Label* on_match2, + V8Label* on_failure) override; + void SkipUntilOneOfMasked3(const SkipUntilOneOfMasked3Args& args) override; + void CheckNotBackReference(int start_reg, + bool read_backward, + V8Label* on_no_match) override; + void CheckNotBackReferenceIgnoreCase(int start_reg, + bool read_backward, + bool unicode, + V8Label* on_no_match) override; + void IfRegisterLT(int register_index, + int comparand, + V8Label* on_less_than) override; + void IfRegisterGE(int register_index, + int comparand, + V8Label* on_greater_or_equal) override; + void IfRegisterEqPos(int register_index, V8Label* on_equal) override; + void RecordComment(std::string_view comment) override {} + // MacroAssembler* masm() override { return nullptr; } + + IrregexpImplementation Implementation() override; + ObjectPtr GetCode(const String& source, RegExpFlags flags) override; + + private: + template + void Emit(Args... args); + using RegExpBytecodeWriter::Emit; + + void EmitSkipTable(const TypedData& table); + + V8Label backtrack_; + + Isolate* isolate_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpBytecodeGenerator); +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ diff --git a/runtime/vm/regexp/regexp-bytecodes-inl.h b/runtime/vm/regexp/regexp-bytecodes-inl.h new file mode 100644 index 00000000000..40b8ab89540 --- /dev/null +++ b/runtime/vm/regexp/regexp-bytecodes-inl.h @@ -0,0 +1,366 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_BYTECODES_INL_H_ +#define V8_REGEXP_REGEXP_BYTECODES_INL_H_ + +#include "vm/regexp/regexp-bytecodes.h" +// Include the non-inl header before the rest of the headers. + +#include +#include +#include +#include + +#include "vm/regexp/regexp-macro-assembler.h" // For StackCheckFlag + +namespace dart { + +template +struct RegExpOperandTypeTraits; + +#define DECLARE_BASIC_OPERAND_TYPE_TRAITS(Name, CType) \ + template <> \ + struct RegExpOperandTypeTraits { \ + static_assert(!std::is_pointer_v); \ + static constexpr uint8_t kSize = sizeof(CType); \ + using kCType = CType; \ + static constexpr bool kIsBasic = true; \ + static constexpr kCType kMinValue = std::numeric_limits::min(); \ + static constexpr kCType kMaxValue = std::numeric_limits::max(); \ + static constexpr size_t kAlignment = kSize; \ + }; +BASIC_BYTECODE_OPERAND_TYPE_LIST(DECLARE_BASIC_OPERAND_TYPE_TRAITS) +#undef DECLARE_OPERAND_TYPE_TRAITS + +#define DECLARE_BASIC_OPERAND_TYPE_LIMITS_TRAITS(Name, CType, MinValue, \ + MaxValue) \ + template <> \ + struct RegExpOperandTypeTraits { \ + static_assert(!std::is_pointer_v); \ + static constexpr uint8_t kSize = sizeof(CType); \ + using kCType = CType; \ + static constexpr bool kIsBasic = true; \ + static_assert(std::is_enum_v || \ + MinValue >= std::numeric_limits::min()); \ + static_assert(std::is_enum_v || \ + MaxValue <= std::numeric_limits::max()); \ + static constexpr kCType kMinValue = MinValue; \ + static constexpr kCType kMaxValue = MaxValue; \ + static constexpr size_t kAlignment = kSize; \ + }; +BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST( + DECLARE_BASIC_OPERAND_TYPE_LIMITS_TRAITS) +#undef DECLARE_OPERAND_TYPE_LIMITS_TRAITS + +#define DECLARE_SPECIAL_OPERAND_TYPE_TRAITS(Name, Size, Alignment) \ + template <> \ + struct RegExpOperandTypeTraits { \ + static constexpr uint8_t kSize = Size; \ + static constexpr bool kIsBasic = false; \ + static constexpr size_t kAlignment = Alignment; \ + static_assert(Utils::IsAligned(kSize, kAlignment)); \ + }; +SPECIAL_BYTECODE_OPERAND_TYPE_LIST(DECLARE_SPECIAL_OPERAND_TYPE_TRAITS) +#undef DECLARE_OPERAND_TYPE_TRAITS + +namespace detail { + +template +constexpr int CountOf() { + return sizeof...(Args); +} + +template +consteval std::array SplitNames(const char* raw_names) { + std::array result; + std::string_view names(raw_names); + + // Remove '(' and ')'. + DCHECK_EQ(names.front(), '('); + DCHECK_EQ(names.back(), ')'); + size_t start = 1; + size_t names_size = names.size() - 1; + + for (size_t i = 0; i < N; ++i) { + size_t comma = names.find(',', start); + // DCHECK_EQ(i == N - 1, comma == std::string_view::npos); + + // Trim whitespace. + start = names.find_first_not_of(" ", start); + size_t end = (comma == std::string_view::npos) ? names_size : comma; + end = names.find_last_not_of(" ,)", end) + 1; + result[i] = names.substr(start, end - start); + + start = comma + 1; + } + + return result; +} + +// Calculates packed offsets for each Bytecode operand. +// All operands are aligned to their own size. +template +consteval auto CalculateAlignedOffsets() { + constexpr int N = sizeof...(operand_types); + constexpr std::array kOperandSizes = { + RegExpOperandTypeTraits::kSize...}; + constexpr std::array kOperandAlignments = { + RegExpOperandTypeTraits::kAlignment...}; + + std::array offsets{}; + int first_offset = sizeof(RegExpBytecode); + int offset = first_offset; + + for (size_t i = 0; i < N; ++i) { + uint8_t operand_size = kOperandSizes[i]; + size_t operand_alignment = kOperandAlignments[i]; + + offset = Utils::RoundUp(offset, operand_alignment); + + // If the operand doesn't fit into the current 4-byte block, start a new + // 4-byte block. + if ((offset % kBytecodeAlignment) + operand_size > kBytecodeAlignment) { + offset = Utils::RoundUp(offset, kBytecodeAlignment); + } + + offsets[i] = offset; + offset += operand_size; + } + + return offsets; +} + +template +struct RegExpBytecodeOperandsTraits { + static constexpr int kOperandCount = sizeof...(ops); + static constexpr std::array + kOperandTypes = {ops...}; + static constexpr std::array kOperandSizes = { + RegExpOperandTypeTraits::kSize...}; + static constexpr std::array kOperandAlignments = { + RegExpOperandTypeTraits::kAlignment...}; + static constexpr std::array kOperandOffsets = + CalculateAlignedOffsets(); + static constexpr int kSize = Utils::RoundUp( + kOperandCount == 0 ? sizeof(RegExpBytecode) + : kOperandOffsets.back() + kOperandSizes.back(), + kBytecodeAlignment); +}; + +template +struct RegExpBytecodeOperandNames; + +#define DECLARE_OPERAND_NAMES(CamelName, OpNames, OpTypes) \ + template <> \ + struct RegExpBytecodeOperandNames { \ + enum class Operand { UNPAREN(OpNames) }; \ + using enum Operand; \ + static constexpr size_t kCount = detail::CountOf(); \ + static constexpr auto kNames = detail::SplitNames(#OpNames); \ + static /*constexpr*/ std::string_view Name(Operand op) { \ + return kNames[static_cast(op)]; \ + } \ + }; +REGEXP_BYTECODE_LIST(DECLARE_OPERAND_NAMES) +#undef DECLARE_OPERAND_NAMES + +template +class RegExpBytecodeOperandsBase { + public: + static constexpr RegExpBytecode kBytecode = bc; + using Operand = RegExpBytecodeOperandNames::Operand; + using Traits = RegExpBytecodeOperandsTraits; + static constexpr int kCount = Traits::kOperandCount; + static constexpr int kTotalSize = Traits::kSize; + static constexpr int Index(Operand op) { return static_cast(op); } + static constexpr int Size(Operand op) { + return Traits::kOperandSizes[Index(op)]; + } + static constexpr int Offset(Operand op) { + return Traits::kOperandOffsets[Index(op)]; + } + static constexpr RegExpBytecodeOperandType Type(Operand op) { + return Traits::kOperandTypes[Index(op)]; + } + + static constexpr std::string_view Name(Operand op) { + return RegExpBytecodeOperandNames::Name(op); + } + + // Returns a tuple of all operands. + static consteval auto GetOperandsTuple() { + return [](std::index_sequence) { + return std::tuple_cat([]() { + constexpr auto id = static_cast(I); + return std::tuple(std::integral_constant{}); + }.template operator()()...); + }(std::make_index_sequence{}); + } + + // Calls |f| templatized by Operand for each Operand in the Operands list. + // Example: + // using Operands = RegExpBytecodeOperands; + // size_t op_sizes = 0; + // Operands::ForEachOperand([]() { + // op_sizes += Operands::Size(op); + // }); + // Note that this gets evaluated at compile time, so op_sizes in the example + // above is essentially a constant. + template + static constexpr void ForEachOperand(Func&& f) { + constexpr auto filtered_ops = GetOperandsTuple(); + std::apply([&](auto... ops) { (..., f.template operator()()); }, + filtered_ops); + } + + // Similar to ForEachOperand, but additionally provides the current index as + // a template argument. The index is a sequential index of operands. + template + static constexpr void ForEachOperandWithIndex(Func&& f) { + constexpr auto filtered_ops = GetOperandsTuple(); + [&](std::index_sequence) { + (..., + f.template operator()< + std::tuple_element_t::value /* Operand */, + I /* Index */>()); + }(std::make_index_sequence>{}); + } + + // Similar to above, but calls |f| only for operands of a given type. + template + static constexpr void ForEachOperandOfType(Func&& f) { + ForEachOperand([&]() { + if constexpr (Type(operand) == OpType) { + f.template operator()(); + } + }); + } + + public: + template + requires(RegExpOperandTypeTraits::kIsBasic) + static auto Get(const uint8_t* pc, const DisallowGarbageCollection& no_gc) { + DCHECK_EQ(RegExpBytecodes::FromPtr(pc), bc); + constexpr RegExpBytecodeOperandType OperandType = Type(op); + constexpr int offset = Offset(op); + using CType = RegExpOperandTypeTraits::kCType; + ASSERT(Utils::IsAligned(offset, sizeof(CType))); + return *reinterpret_cast(pc + offset); + } + + template + requires(RegExpOperandTypeTraits::kIsBasic) + static auto Get(const TypedData& bytecode, int offset, Zone* zone) { + // Basic operand types won't allocate, so we can always fallback to the + // GC-unsafe version. + DisallowGarbageCollection no_gc; + //return Get(bytecode->begin() + offset); + return Get((uint8_t*)bytecode.DataAddr(offset), no_gc); + } + + template + requires(Type(op) == RegExpBytecodeOperandType::kBitTable) + static auto Get(const uint8_t* pc, DisallowGarbageCollection no_gc) { + static_assert(Size(op) == RegExpMacroAssembler::kTableSize / kBitsPerByte); + DCHECK_EQ(RegExpBytecodes::FromPtr(pc), bc); + constexpr int offset = Offset(op); + return pc + offset; + } + + template + requires(Type(op) == RegExpBytecodeOperandType::kBitTable) + static auto Get(const TypedData& bytecode, int offset, Zone* zone) { + static_assert(Size(op) == RegExpMacroAssembler::kTableSize / kBitsPerByte); + // DCHECK_EQ(RegExpBytecodes::FromPtr(bytecode->begin() + offset), bc); + constexpr int op_offset = Offset(op); + const uint8_t* start = (uint8_t*)bytecode.DataAddr(0) + offset + op_offset; + const uint8_t* end = start + Size(op); + return ZoneVector(start, end, zone); + } +}; + +} // namespace detail + +#define PACK_OPTIONAL(x, ...) x __VA_OPT__(, ) __VA_ARGS__ + +#define DECLARE_OPERANDS(CamelName, OpNames, OpTypes) \ + template <> \ + class RegExpBytecodeOperands final \ + : public detail::RegExpBytecodeOperandsBase, \ + public AllStatic { \ + public: \ + enum class Operand { UNPAREN(OpNames) }; \ + using enum Operand; \ + }; + +REGEXP_BYTECODE_LIST(DECLARE_OPERANDS) +#undef DECLARE_OPERANDS + +namespace detail { + +#define DECLARE_BYTECODE_NAMES(CamelName, ...) #CamelName, +static constexpr const char* kBytecodeNames[] = { + REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_NAMES)}; +#undef DECLARE_BYTECODE_NAMES + +#define DECLARE_BYTECODE_SIZES(CamelName, ...) \ + RegExpBytecodeOperands::kTotalSize, +static constexpr uint8_t kBytecodeSizes[] = { + REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_SIZES)}; +#undef DECLARE_BYTECODE_SIZES + +#define DECLARE_OPERAND_TYPE_SIZE(Name, ...) \ + RegExpOperandTypeTraits::kSize, +static constexpr uint8_t kOperandTypeSizes[] = { + BYTECODE_OPERAND_TYPE_LIST(DECLARE_OPERAND_TYPE_SIZE)}; +#undef DECLARE_OPERAND_TYPE_SIZE + +} // namespace detail + +// static +template +decltype(auto) RegExpBytecodes::DispatchOnBytecode(RegExpBytecode bytecode, + Func&& f) { + switch (bytecode) { +#define CASE(CamelName, ...) \ + case RegExpBytecode::k##CamelName: \ + return f.template operator()(); + REGEXP_BYTECODE_LIST(CASE) +#undef CASE + } + UNREACHABLE(); +} + +// static +constexpr const char* RegExpBytecodes::Name(RegExpBytecode bytecode) { + return Name(ToByte(bytecode)); +} + +// static +constexpr const char* RegExpBytecodes::Name(uint8_t bytecode) { + DCHECK_LT(bytecode, kCount); + return detail::kBytecodeNames[bytecode]; +} + +// static +constexpr uint8_t RegExpBytecodes::Size(RegExpBytecode bytecode) { + return Size(ToByte(bytecode)); +} + +// static +constexpr uint8_t RegExpBytecodes::Size(uint8_t bytecode) { + DCHECK_LT(bytecode, kCount); + return detail::kBytecodeSizes[bytecode]; +} + +// static +constexpr uint8_t RegExpBytecodes::Size(RegExpBytecodeOperandType type) { + return detail::kOperandTypeSizes[static_cast(type)]; +} + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_BYTECODES_INL_H_ diff --git a/runtime/vm/regexp/regexp-bytecodes.h b/runtime/vm/regexp/regexp-bytecodes.h new file mode 100644 index 00000000000..76efef2886b --- /dev/null +++ b/runtime/vm/regexp/regexp-bytecodes.h @@ -0,0 +1,339 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_BYTECODES_H_ +#define V8_REGEXP_REGEXP_BYTECODES_H_ + +#include "platform/globals.h" +#include "platform/utils.h" +#include "vm/regexp/base.h" + +namespace dart { + +// Basic operand types that have a direct mapping to a C-type. +// Getters/Setters for these are fully auto-generated. +// Format: V(Name, C type) +#define BASIC_BYTECODE_OPERAND_TYPE_LIST(V) \ + V(Int16, int16_t) \ + V(Int32, int32_t) \ + V(Uint32, uint32_t) \ + V(Char, uint16_t) \ + V(JumpTarget, uint32_t) + +#define BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(V) \ + V(Offset, int16_t, RegExpMacroAssembler::kMinCPOffset, \ + RegExpMacroAssembler::kMaxCPOffset) \ + V(Register, uint16_t, 0, RegExpMacroAssembler::kMaxRegister) \ + V(StackCheckFlag, RegExpMacroAssembler::StackCheckFlag, \ + RegExpMacroAssembler::StackCheckFlag::kNoStackLimitCheck, \ + RegExpMacroAssembler::StackCheckFlag::kCheckStackLimit) \ + V(StandardCharacterSet, StandardCharacterSet, \ + StandardCharacterSet::kEverything, StandardCharacterSet::kWord) + +// Special operand types that don't have a direct mapping to a C-type. +// Getters/Setters for these types need to be specialized manually. +// Format: V(Name, Size in bytes, Alignment in bytes) +#define SPECIAL_BYTECODE_OPERAND_TYPE_LIST(V) V(BitTable, 16, 1) + +#define BYTECODE_OPERAND_TYPE_LIST(V) \ + BASIC_BYTECODE_OPERAND_TYPE_LIST(V) \ + BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(V) \ + SPECIAL_BYTECODE_OPERAND_TYPE_LIST(V) + +enum class RegExpBytecodeOperandType : uint8_t { +#define DECLARE_OPERAND(Name, ...) k##Name, + BYTECODE_OPERAND_TYPE_LIST(DECLARE_OPERAND) +#undef DECLARE_OPERAND +}; + +using ReBcOpType = RegExpBytecodeOperandType; + +// Bytecodes that indicate something is invalid. These don't have a direct +// equivalent in RegExpMacroAssembler. +// It's a requirement that BREAK has an enum value of 0 (as e.g. jumps to offset +// 0 are considered invalid). +// Format: V(CamelName, (OperandNames...), (OperandTypes...)) +#define INVALID_BYTECODE_LIST(V) V(Break, (), ()) + +// Format: V(CamelName, (OperandNames...), (OperandTypes...)) +#define BASIC_BYTECODE_LIST(V) \ + V(PushCurrentPosition, (), ()) \ + V(PushBacktrack, (label), (ReBcOpType::kJumpTarget)) \ + V(WriteCurrentPositionToRegister, (register_index, cp_offset), \ + (ReBcOpType::kRegister, ReBcOpType::kOffset)) \ + V(ReadCurrentPositionFromRegister, (register_index), \ + (ReBcOpType::kRegister)) \ + V(WriteStackPointerToRegister, (register_index), (ReBcOpType::kRegister)) \ + V(ReadStackPointerFromRegister, (register_index), (ReBcOpType::kRegister)) \ + V(SetRegister, (register_index, value), \ + (ReBcOpType::kRegister, ReBcOpType::kInt32)) \ + /* Clear registers in the range from_register to to_register (inclusive) */ \ + V(ClearRegisters, (from_register, to_register), \ + (ReBcOpType::kRegister, ReBcOpType::kRegister)) \ + V(AdvanceRegister, (register_index, by), \ + (ReBcOpType::kRegister, ReBcOpType::kOffset)) \ + V(PopCurrentPosition, (), ()) \ + /* TODO(pthier): PushRegister fits into 4 byte once the restrictions due */ \ + /* to the old layout are lifted */ \ + V(PushRegister, (register_index, stack_check), \ + (ReBcOpType::kRegister, ReBcOpType::kStackCheckFlag)) \ + V(PopRegister, (register_index), (ReBcOpType::kRegister)) \ + V(Fail, (), ()) \ + V(Succeed, (), ()) \ + V(AdvanceCurrentPosition, (by), (ReBcOpType::kOffset)) \ + /* Jump to another bytecode given its offset. */ \ + V(GoTo, (label), (ReBcOpType::kJumpTarget)) \ + /* Check if offset is in range and load character at given offset. */ \ + V(LoadCurrentCharacter, (cp_offset, on_failure), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + /* Checks if current position + given offset is in range. */ \ + /* I.e. jumps to |on_failure| if current pos + |cp_offset| >= subject len */ \ + V(CheckPosition, (cp_offset, on_failure), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + V(CheckSpecialClassRanges, (character_set, on_no_match), \ + (ReBcOpType::kStandardCharacterSet, ReBcOpType::kJumpTarget)) \ + /* Check if current character is equal to a given character */ \ + V(CheckCharacter, (character, on_equal), \ + (ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + V(CheckNotCharacter, (character, on_not_equal), \ + (ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + /* Checks if the current character combined with mask (bitwise and) */ \ + /* matches a character (e.g. used when two characters in a disjunction */ \ + /* differ by only a single bit */ \ + /* TODO(pthier): mask should be kChar */ \ + V(CheckCharacterAfterAnd, (character, mask, on_equal), \ + (ReBcOpType::kChar, ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + /* TODO(pthier): mask should be kChar */ \ + V(CheckNotCharacterAfterAnd, (character, mask, on_not_equal), \ + (ReBcOpType::kChar, ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + V(CheckNotCharacterAfterMinusAnd, (character, minus, mask, on_not_equal), \ + (ReBcOpType::kChar, ReBcOpType::kChar, ReBcOpType::kChar, \ + ReBcOpType::kJumpTarget)) \ + V(CheckCharacterInRange, (from, to, on_in_range), \ + (ReBcOpType::kChar, ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + V(CheckCharacterNotInRange, (from, to, on_not_in_range), \ + (ReBcOpType::kChar, ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + V(CheckCharacterLT, (limit, on_less), \ + (ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + V(CheckCharacterGT, (limit, on_greater), \ + (ReBcOpType::kChar, ReBcOpType::kJumpTarget)) \ + V(IfRegisterLT, (register_index, comparand, on_less_than), \ + (ReBcOpType::kRegister, ReBcOpType::kInt32, ReBcOpType::kJumpTarget)) \ + V(IfRegisterGE, (register_index, comparand, on_greater_or_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kInt32, ReBcOpType::kJumpTarget)) \ + V(IfRegisterEqPos, (register_index, on_eq), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckAtStart, (cp_offset, on_at_start), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + V(CheckNotAtStart, (cp_offset, on_not_at_start), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + /* Checks if the current position matches top of backtrack stack */ \ + V(CheckFixedLengthLoop, (on_tos_equals_current_position), \ + (ReBcOpType::kJumpTarget)) \ + /* Advance character pointer by given offset and jump to another bytecode.*/ \ + V(SetCurrentPositionFromEnd, (by), (ReBcOpType::kOffset)) + +// Bytecodes dealing with multiple characters, introduced due to special logic +// in the bytecode-generator or requiring additional logic when assembling; +// e.g. they have arguments only used in the interpreter, different +// MacroAssembler names, non-default MacroAssembler arguments that need to be +// provided, etc. +// These share a method with Basic Bytecodes in RegExpMacroAssembler. +// Format: V(CamelName, (OperandNames...), (OperandTypes...)) +#define SPECIAL_BYTECODE_LIST(V) \ + V(Backtrack, (return_code), (ReBcOpType::kInt16)) \ + /* Load character at given offset without range checks. */ \ + V(LoadCurrentCharacterUnchecked, (cp_offset), (ReBcOpType::kOffset)) \ + /* Checks if the current character matches any of the characters encoded */ \ + /* in a bit table. Similar to/inspired by boyer moore string search */ \ + /* Todo(pthier): Change order to (table, label) and move to Basic */ \ + V(CheckBitInTable, (on_bit_set, table), \ + (ReBcOpType::kJumpTarget, ReBcOpType::kBitTable)) \ + V(Load2CurrentChars, (cp_offset, on_failure), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + V(Load2CurrentCharsUnchecked, (cp_offset), (ReBcOpType::kOffset)) \ + V(Load4CurrentChars, (cp_offset, on_failure), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + V(Load4CurrentCharsUnchecked, (cp_offset), (ReBcOpType::kOffset)) \ + V(Check4Chars, (characters, on_equal), \ + (ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + V(CheckNot4Chars, (characters, on_not_equal), \ + (ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + V(AndCheck4Chars, (characters, mask, on_equal), \ + (ReBcOpType::kUint32, ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + V(AndCheckNot4Chars, (characters, mask, on_not_equal), \ + (ReBcOpType::kUint32, ReBcOpType::kUint32, ReBcOpType::kJumpTarget)) \ + V(AdvanceCpAndGoto, (by, on_goto), \ + (ReBcOpType::kOffset, ReBcOpType::kJumpTarget)) \ + /* TODO(pthier): CheckNotBackRef variants could be merged into a single */ \ + /* Bytecode without increasing the size */ \ + V(CheckNotBackRef, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckNotBackRefNoCase, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckNotBackRefNoCaseUnicode, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckNotBackRefBackward, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckNotBackRefNoCaseBackward, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) \ + V(CheckNotBackRefNoCaseUnicodeBackward, (start_reg, on_not_equal), \ + (ReBcOpType::kRegister, ReBcOpType::kJumpTarget)) + +// Bytecodes generated by peephole optimization. These don't have a direct +// equivalent in the RegExpMacroAssembler. +// All peephole generated bytecodes should have a default implementation in +// RegExpMacroAssembler, that maps the optimized sequence back to the basic +// sequence they were created from. +// Format: V(CamelName, (OperandNames...), (OperandTypes...)) +#define PEEPHOLE_BYTECODE_LIST(V) \ + /* Combination of: */ \ + /* LoadCurrentCharacter, CheckBitInTable and AdvanceCpAndGoto */ \ + V(SkipUntilBitInTable, \ + (cp_offset, advance_by, table, on_match, on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kBitTable, \ + ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* CheckPosition, LoadCurrentCharacterUnchecked, CheckCharacterAfterAnd */ \ + /* and AdvanceCpAndGoto */ \ + /* TODO(pthier): mask should be kChar */ \ + /* TODO(pthier): eats_at_least should be Offset */ \ + V(SkipUntilCharAnd, \ + (cp_offset, advance_by, character, mask, eats_at_least, on_match, \ + on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kChar, \ + ReBcOpType::kUint32, ReBcOpType::kOffset, ReBcOpType::kJumpTarget, \ + ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* LoadCurrentCharacter, CheckCharacter and AdvanceCpAndGoto */ \ + V(SkipUntilChar, (cp_offset, advance_by, character, on_match, on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kChar, \ + ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* CheckPosition, LoadCurrentCharacterUnchecked, CheckCharacter */ \ + /* and AdvanceCpAndGoto */ \ + V(SkipUntilCharPosChecked, \ + (cp_offset, advance_by, character, eats_at_least, on_match, on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kChar, \ + ReBcOpType::kOffset, ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget)) \ + /* TODO(pthier): eats_at_least should be Offset instead of Uint32 */ \ + /* Combination of: */ \ + /* LoadCurrentCharacter, CheckCharacter, CheckCharacter and */ \ + /* AdvanceCpAndGoto */ \ + V(SkipUntilCharOrChar, \ + (cp_offset, advance_by, char1, char2, on_match, on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kChar, \ + ReBcOpType::kChar, ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* LoadCurrentCharacter, CheckCharacterGT, CheckBitInTable, GoTo and */ \ + /* AdvanceCpAndGoto */ \ + V(SkipUntilGtOrNotBitInTable, \ + (cp_offset, advance_by, character, table, on_match, on_no_match), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kChar, \ + ReBcOpType::kBitTable, ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* CheckPosition, Load4CurrentCharsUnchecked, AndCheck4Chars, */ \ + /* AdvanceCpAndGoto, AndCheck4Chars, AndCheckNot4Chars */ \ + /* This pattern is common for finding a match from an alternative with */ \ + /* few different characters. E.g. /[ab]bbbc|[de]eeef/. */ \ + V(SkipUntilOneOfMasked, \ + (cp_offset, advance_by, both_chars, both_mask, max_offset, chars1, mask1, \ + chars2, mask2, on_match1, on_match2, on_failure), \ + (ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kUint32, \ + ReBcOpType::kUint32, ReBcOpType::kOffset, ReBcOpType::kUint32, \ + ReBcOpType::kUint32, ReBcOpType::kUint32, ReBcOpType::kUint32, \ + ReBcOpType::kJumpTarget, ReBcOpType::kJumpTarget, \ + ReBcOpType::kJumpTarget)) \ + /* Combination of: */ \ + /* SkipUntilBitInTable, CheckPosition, LoadCurrentCharacter, */ \ + /* CheckCharacterAfterAnd, AdvanceCurrentPosition, LoadCurrentCharacter, */ \ + /* CheckCharacterAfterAnd, CheckCharacterAfterAnd, */ \ + /* CheckNotCharacterAfterAnd. */ \ + /* This pattern is common for finding a match from an alternative, e.g.: */ \ + /* / +class RegExpBytecodeOperands; + +class RegExpBytecodes final : public AllStatic { + public: + static constexpr int kCount = static_cast(RegExpBytecode::kLast) + 1; + static constexpr uint8_t ToByte(RegExpBytecode bc) { + return static_cast(bc); + } + static constexpr RegExpBytecode FromByte(uint8_t byte) { + ASSERT(IsValid(byte)); + return static_cast(byte); + } + // Extract the bytecode from the given `ptr`, which must point at the + // word32-aligned region containing the bytecode. Endian-ness independent. + static constexpr RegExpBytecode FromPtr(const void* ptr) { + if (!std::is_constant_evaluated()) { + ASSERT( + Utils::IsAligned(reinterpret_cast(ptr), kBytecodeAlignment)); + } + return FromByte(*static_cast(ptr)); + } + static constexpr bool IsValid(uint8_t byte) { return byte < kCount; } + static constexpr bool IsValidJumpTarget(uint8_t byte) { + return IsValid(byte) && FromByte(byte) != RegExpBytecode::kBreak; + } + + // Calls |f| templatized by RegExpBytecode. This allows the usage of the + // functions template argument in other templates. + // Example: + // RegExpBytecode bc = ; + // DispatchOnBytecode(bc, []() { DoFancyStuff(); }); + template + static decltype(auto) DispatchOnBytecode(RegExpBytecode bytecode, Func&& f); + + static constexpr const char* Name(RegExpBytecode bytecode); + static constexpr const char* Name(uint8_t bytecode); + + static constexpr uint8_t Size(RegExpBytecode bytecode); + static constexpr uint8_t Size(uint8_t bytecode); + static constexpr uint8_t Size(RegExpBytecodeOperandType type); +}; + +void RegExpBytecodeDisassembleSingle(const uint8_t* code_base, + const uint8_t* pc); +void RegExpBytecodeDisassemble(const uint8_t* code_base, + int length, + const char* pattern); + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_BYTECODES_H_ diff --git a/runtime/vm/regexp/regexp-compiler-tonode.cc b/runtime/vm/regexp/regexp-compiler-tonode.cc new file mode 100644 index 00000000000..93511315c3c --- /dev/null +++ b/runtime/vm/regexp/regexp-compiler-tonode.cc @@ -0,0 +1,2359 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "platform/unicode.h" +#include "vm/regexp/regexp-compiler.h" +#include "vm/regexp/regexp.h" +#include "vm/regexp/unibrow-inl.h" +#include "vm/regexp/unibrow.h" +#include "vm/regexp/zone-list-inl.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/locid.h" +#include "unicode/uniset.h" +#include "unicode/utypes.h" +#include "vm/regexp/special-case.h" +#endif // V8_INTL_SUPPORT + +namespace dart { + +using namespace regexp_compiler_constants; // NOLINT(build/namespaces) + +constexpr uint32_t kMaxCodePoint = 0x10ffff; +constexpr int kMaxUtf16CodeUnit = 0xffff; +constexpr uint32_t kMaxUtf16CodeUnitU = 0xffff; + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS +#define TRACE(msg) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + std::ostream& os = compiler->diagnostics()->trace_tree_scope()->os(); \ + os << msg << std::endl; \ + } \ + } while (false) +#define TRACE_WITH_NODE(msg, node) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + std::ostream& os = compiler->diagnostics()->trace_tree_scope()->os(); \ + os << msg; \ + compiler->diagnostics()->ast_printer()->Print(node); \ + os << std::endl; \ + } \ + } while (false) +#define REGISTER_NODE(node) \ + do { \ + if (UNLIKELY(!!compiler->diagnostics() && \ + compiler->diagnostics()->has_graph_labeller())) { \ + compiler->diagnostics()->graph_labeller()->RegisterNode(node); \ + } \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + compiler->diagnostics()->trace_tree_scope()->os() << "+ "; \ + compiler->diagnostics()->graph_printer()->PrintNode(node); \ + } \ + } while (false) +#else +#define TRACE(msg) (void(0)) +#define TRACE_WITH_NODE(msg, node) (void(0)) +#define REGISTER_NODE(node) (void(0)) +#endif + +// ------------------------------------------------------------------- +// Tree to graph conversion + +RegExpNode* RegExpTree::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + TraceRegExpTreeScope trace_tree_scope(compiler->diagnostics()); + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { + trace_tree_scope.PrintTree(this); + } +#endif + // We try to remove entire subbranches of the node structure that can't + // succeed by returning backtrack nodes instead of nodes that first match + // something and then inevitably backtrack. + if (on_success->IsBacktrack()) return on_success; + compiler->ToNodeMaybeCheckForStackOverflow(); + if (compiler->IsRegExpTooBig()) { + // We can always return this even though it may not be the expected + // subclass because all call sites already have to check for this case. + Zone* zone = compiler->zone(); + return zone->New(EndNode::BACKTRACK, zone); + } + return ToNodeImpl(compiler, on_success); +} + +RegExpNode* RegExpAtom::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + ZoneList* elms = zone->New>(1, zone); + elms->Add(TextElement::Atom(this), zone); + TextNode* result = + zone->New(elms, compiler->read_backward(), on_success); + if (compiler->one_byte() && !result->CanMatchLatin1(compiler)) { + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; + } + + REGISTER_NODE(result); + return result; +} + +RegExpNode* RegExpText::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + TextNode* result = + zone->New(elements(), compiler->read_backward(), on_success); + if (compiler->one_byte() && !result->CanMatchLatin1(compiler)) { + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; + } + + REGISTER_NODE(result); + return result; +} + +namespace { + +bool CompareInverseRanges(ZoneList* ranges, + const int* special_class, + int length) { + length--; // Remove final marker. + + DCHECK_EQ(kRangeEndMarker, special_class[length]); + DCHECK_NE(0, ranges->length()); + DCHECK_NE(0, length); + DCHECK_NE(0, special_class[0]); + + if (ranges->length() != (length >> 1) + 1) return false; + + CharacterRange range = ranges->at(0); + if (range.from() != 0) return false; + + for (int i = 0; i < length; i += 2) { + if (static_cast(special_class[i]) != (range.to() + 1)) { + return false; + } + range = ranges->at((i >> 1) + 1); + if (static_cast(special_class[i + 1]) != range.from()) { + return false; + } + } + + return range.to() == kMaxCodePoint; +} + +bool CompareRanges(ZoneList* ranges, + const int* special_class, + int length) { + length--; // Remove final marker. + + DCHECK_EQ(kRangeEndMarker, special_class[length]); + if (ranges->length() * 2 != length) return false; + + for (int i = 0; i < length; i += 2) { + CharacterRange range = ranges->at(i >> 1); + if (range.from() != static_cast(special_class[i]) || + range.to() != static_cast(special_class[i + 1] - 1)) { + return false; + } + } + return true; +} + +} // namespace + +bool RegExpClassRanges::is_standard(Zone* zone) { + // TODO(lrn): Remove need for this function, by not throwing away information + // along the way. + if (is_negated()) { + return false; + } + if (set_.is_standard()) { + return true; + } + if (CompareRanges(set_.ranges(zone), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kWhitespace); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotWhitespace); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotLineTerminator); + return true; + } + if (CompareRanges(set_.ranges(zone), kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kLineTerminator); + return true; + } + if (CompareRanges(set_.ranges(zone), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kWord); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotWord); + return true; + } + return false; +} + +UnicodeRangeSplitter::UnicodeRangeSplitter(ZoneList* base) { + // The unicode range splitter categorizes given character ranges into: + // - Code points from the BMP representable by one code unit. + // - Code points outside the BMP that need to be split into + // surrogate pairs. + // - Lone lead surrogates. + // - Lone trail surrogates. + // Lone surrogates are valid code points, even though no actual characters. + // They require special matching to make sure we do not split surrogate pairs. + + for (int i = 0; i < base->length(); i++) + AddRange(base->at(i)); +} + +void UnicodeRangeSplitter::AddRange(CharacterRange range) { + static constexpr uint32_t kBmp1Start = 0; + static constexpr uint32_t kBmp1End = kLeadSurrogateStart - 1; + static constexpr uint32_t kBmp2Start = kTrailSurrogateEnd + 1; + static constexpr uint32_t kBmp2End = kNonBmpStart - 1; + + // Ends are all inclusive. + static_assert(kBmp1Start == 0); + static_assert(kBmp1Start < kBmp1End); + static_assert(kBmp1End + 1 == kLeadSurrogateStart); + static_assert(kLeadSurrogateStart < kLeadSurrogateEnd); + static_assert(kLeadSurrogateEnd + 1 == kTrailSurrogateStart); + static_assert(kTrailSurrogateStart < kTrailSurrogateEnd); + static_assert(kTrailSurrogateEnd + 1 == kBmp2Start); + static_assert(kBmp2Start < kBmp2End); + static_assert(kBmp2End + 1 == kNonBmpStart); + static_assert(kNonBmpStart < kNonBmpEnd); + + static constexpr uint32_t kStarts[] = { + kBmp1Start, kLeadSurrogateStart, kTrailSurrogateStart, + kBmp2Start, kNonBmpStart, + }; + + static constexpr uint32_t kEnds[] = { + kBmp1End, kLeadSurrogateEnd, kTrailSurrogateEnd, kBmp2End, kNonBmpEnd, + }; + + CharacterRangeVector* const kTargets[] = { + &bmp_, &lead_surrogates_, &trail_surrogates_, &bmp_, &non_bmp_, + }; + + static constexpr int kCount = ARRAY_SIZE(kStarts); + static_assert(kCount == ARRAY_SIZE(kEnds)); + static_assert(kCount == ARRAY_SIZE(kTargets)); + + for (int i = 0; i < kCount; i++) { + if (kStarts[i] > range.to()) break; + const uint32_t from = std::max(kStarts[i], range.from()); + const uint32_t to = std::min(kEnds[i], range.to()); + if (from > to) continue; + kTargets[i]->emplace_back(CharacterRange::Range(from, to)); + } +} + +namespace { + +// Translates between new and old V8-isms (SmallVector, ZoneList). +ZoneList* ToCanonicalZoneList( + const UnicodeRangeSplitter::CharacterRangeVector* v, + Zone* zone) { + if (v->empty()) return nullptr; + + ZoneList* result = + zone->New>(static_cast(v->size()), zone); + for (size_t i = 0; i < v->size(); i++) { + result->Add(v->at(i), zone); + } + + CharacterRange::Canonicalize(result); + return result; +} + +void AddBmpCharacters(RegExpCompiler* compiler, + ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + TRACE("* Add BMP Characters"); + ZoneList* bmp = + ToCanonicalZoneList(splitter->bmp(), compiler->zone()); + if (bmp == nullptr) return; + RegExpNode* node = TextNode::CreateForCharacterRanges( + compiler->zone(), bmp, compiler->read_backward(), on_success); + REGISTER_NODE(node); + result->AddAlternative(GuardedAlternative(node)); +} + +using UC16Range = uint32_t; // {from, to} packed into one uint32_t. +constexpr UC16Range ToUC16Range(uint16_t from, uint16_t to) { + return (static_cast(from) << 16) | to; +} +constexpr uint16_t ExtractFrom(UC16Range r) { + return static_cast(r >> 16); +} +constexpr uint16_t ExtractTo(UC16Range r) { + return static_cast(r); +} + +void AddNonBmpSurrogatePairs(RegExpCompiler* compiler, + ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + ASSERT(!compiler->one_byte()); + Zone* const zone = compiler->zone(); + ZoneList* non_bmp = + ToCanonicalZoneList(splitter->non_bmp(), zone); + if (non_bmp == nullptr) return; + + // Translate each 32-bit code point range into the corresponding 16-bit code + // unit representation consisting of the lead- and trail surrogate. + // + // The generated alternatives are grouped by the leading surrogate to avoid + // emitting excessive code. For example, for + // + // { \ud800[\udc00-\udc01] + // , \ud800[\udc05-\udc06] + // } + // + // there's no need to emit matching code for the leading surrogate \ud800 + // twice. We also create a dedicated grouping for full trailing ranges, i.e. + // [dc00-dfff]. + TRACE("* Add Non-BMP Surrogate Pairs"); + ZoneUnorderedMap*> grouped_by_leading( + zone); + ZoneList* leading_with_full_trailing_range = + zone->New>(1, zone); + const auto AddRange = [&](uint16_t from_l, uint16_t to_l, uint16_t from_t, + uint16_t to_t) { + const UC16Range leading_range = ToUC16Range(from_l, to_l); + if (grouped_by_leading.count(leading_range) == 0) { + if (from_t == kTrailSurrogateStart && to_t == kTrailSurrogateEnd) { + leading_with_full_trailing_range->Add( + CharacterRange::Range(from_l, to_l), zone); + return; + } + grouped_by_leading[leading_range] = + zone->New>(2, zone); + } + grouped_by_leading[leading_range]->Add(CharacterRange::Range(from_t, to_t), + zone); + }; + + // First, create the grouped ranges. + CharacterRange::Canonicalize(non_bmp); + for (int i = 0; i < non_bmp->length(); i++) { + // Match surrogate pair. + // E.g. [\u10005-\u11005] becomes + // \ud800[\udc05-\udfff]| + // [\ud801-\ud803][\udc00-\udfff]| + // \ud804[\udc00-\udc05] + uint32_t from = non_bmp->at(i).from(); + uint32_t to = non_bmp->at(i).to(); + uint16_t from_l = Utf16::LeadSurrogate(from); + uint16_t from_t = Utf16::TrailSurrogate(from); + uint16_t to_l = Utf16::LeadSurrogate(to); + uint16_t to_t = Utf16::TrailSurrogate(to); + + if (from_l == to_l) { + // The lead surrogate is the same. + AddRange(from_l, to_l, from_t, to_t); + continue; + } + + if (from_t != kTrailSurrogateStart) { + // Add [from_l][from_t-\udfff]. + AddRange(from_l, from_l, from_t, kTrailSurrogateEnd); + from_l++; + } + if (to_t != kTrailSurrogateEnd) { + // Add [to_l][\udc00-to_t]. + AddRange(to_l, to_l, kTrailSurrogateStart, to_t); + to_l--; + } + if (from_l <= to_l) { + // Add [from_l-to_l][\udc00-\udfff]. + AddRange(from_l, to_l, kTrailSurrogateStart, kTrailSurrogateEnd); + } + } + + // Create the actual TextNode now that ranges are fully grouped. + if (!leading_with_full_trailing_range->is_empty()) { + CharacterRange::Canonicalize(leading_with_full_trailing_range); + RegExpNode* node = TextNode::CreateForSurrogatePair( + zone, leading_with_full_trailing_range, + CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd), + compiler->read_backward(), on_success); + REGISTER_NODE(node); + result->AddAlternative(GuardedAlternative(node)); + } + for (const auto& it : grouped_by_leading) { + CharacterRange leading_range = + CharacterRange::Range(ExtractFrom(it.first), ExtractTo(it.first)); + ZoneList* trailing_ranges = it.second; + CharacterRange::Canonicalize(trailing_ranges); + RegExpNode* node = + TextNode::CreateForSurrogatePair(zone, leading_range, trailing_ranges, + compiler->read_backward(), on_success); + REGISTER_NODE(node); + result->AddAlternative(GuardedAlternative(node)); + } +} + +RegExpNode* NegativeLookaroundAgainstReadDirectionAndMatch( + RegExpCompiler* compiler, + ZoneList* lookbehind, + ZoneList* match, + RegExpNode* on_success, + bool read_backward) { + Zone* zone = compiler->zone(); + RegExpNode* match_node = TextNode::CreateForCharacterRanges( + zone, match, read_backward, on_success); + REGISTER_NODE(match_node); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + RegExpLookaround::Builder lookaround(false, match_node, compiler, + stack_register, position_register); + RegExpNode* negative_match = TextNode::CreateForCharacterRanges( + zone, lookbehind, !read_backward, lookaround.on_match_success()); + REGISTER_NODE(negative_match); + return lookaround.ForMatch(compiler, negative_match); +} + +RegExpNode* MatchAndNegativeLookaroundInReadDirection( + RegExpCompiler* compiler, + ZoneList* match, + ZoneList* lookahead, + RegExpNode* on_success, + bool read_backward) { + Zone* zone = compiler->zone(); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + RegExpLookaround::Builder lookaround(false, on_success, compiler, + stack_register, position_register); + RegExpNode* negative_match = TextNode::CreateForCharacterRanges( + zone, lookahead, read_backward, lookaround.on_match_success()); + REGISTER_NODE(negative_match); + RegExpNode* node = TextNode::CreateForCharacterRanges( + zone, match, read_backward, + lookaround.ForMatch(compiler, negative_match)); + REGISTER_NODE(node); + return node; +} + +void AddLoneLeadSurrogates(RegExpCompiler* compiler, + ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + ZoneList* lead_surrogates = + ToCanonicalZoneList(splitter->lead_surrogates(), compiler->zone()); + if (lead_surrogates == nullptr) return; + TRACE("* Add Lone Lead Surrogates"); + Zone* zone = compiler->zone(); + // E.g. \ud801 becomes \ud801(?![\udc00-\udfff]). + ZoneList* trail_surrogates = CharacterRange::List( + zone, CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd)); + + RegExpNode* match; + if (compiler->read_backward()) { + // Reading backward. Assert that reading forward, there is no trail + // surrogate, and then backward match the lead surrogate. + match = NegativeLookaroundAgainstReadDirectionAndMatch( + compiler, trail_surrogates, lead_surrogates, on_success, true); + } else { + // Reading forward. Forward match the lead surrogate and assert that + // no trail surrogate follows. + match = MatchAndNegativeLookaroundInReadDirection( + compiler, lead_surrogates, trail_surrogates, on_success, false); + } + result->AddAlternative(GuardedAlternative(match)); +} + +void AddLoneTrailSurrogates(RegExpCompiler* compiler, + ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + ZoneList* trail_surrogates = + ToCanonicalZoneList(splitter->trail_surrogates(), compiler->zone()); + if (trail_surrogates == nullptr) return; + TRACE("* Add Lone Trail Surrogates"); + Zone* zone = compiler->zone(); + // E.g. \udc01 becomes (?* lead_surrogates = CharacterRange::List( + zone, CharacterRange::Range(kLeadSurrogateStart, kLeadSurrogateEnd)); + + RegExpNode* match; + if (compiler->read_backward()) { + // Reading backward. Backward match the trail surrogate and assert that no + // lead surrogate precedes it. + match = MatchAndNegativeLookaroundInReadDirection( + compiler, trail_surrogates, lead_surrogates, on_success, true); + } else { + // Reading forward. Assert that reading backward, there is no lead + // surrogate, and then forward match the trail surrogate. + match = NegativeLookaroundAgainstReadDirectionAndMatch( + compiler, lead_surrogates, trail_surrogates, on_success, false); + } + result->AddAlternative(GuardedAlternative(match)); +} + +RegExpNode* UnanchoredAdvance(RegExpCompiler* compiler, + RegExpNode* on_success) { + // This implements ES2015 21.2.5.2.3, AdvanceStringIndex. + ASSERT(!compiler->read_backward()); + Zone* zone = compiler->zone(); + // Advance any character. If the character happens to be a lead surrogate and + // we advanced into the middle of a surrogate pair, it will work out, as + // nothing will match from there. We will have to advance again, consuming + // the associated trail surrogate. + ZoneList* range = + CharacterRange::List(zone, CharacterRange::Range(0, kMaxUtf16CodeUnit)); + RegExpNode* node = + TextNode::CreateForCharacterRanges(zone, range, false, on_success); + REGISTER_NODE(node); + return node; +} + +} // namespace + +// static +// Only for /ui and /vi, not for /i regexps. +void CharacterRange::AddUnicodeCaseEquivalents(ZoneList* ranges, + Zone* zone) { +#ifdef V8_INTL_SUPPORT + ASSERT(IsCanonical(ranges)); + + // Micro-optimization to avoid passing large ranges to UnicodeSet::closeOver. + // See also https://crbug.com/v8/6727. + // TODO(jgruber): This only covers the special case of the {0,0x10FFFF} range, + // which we use frequently internally. But large ranges can also easily be + // created by the user. We might want to have a more general caching mechanism + // for such ranges. + if (ranges->length() == 1 && ranges->at(0).IsEverything(kNonBmpEnd)) return; + + // Use ICU to compute the case fold closure over the ranges. + icu::UnicodeSet set; + for (int i = 0; i < ranges->length(); i++) { + set.add(ranges->at(i).from(), ranges->at(i).to()); + } + // Clear the ranges list without freeing the backing store. + ranges->Rewind(0); + set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); + for (int i = 0; i < set.getRangeCount(); i++) { + ranges->Add(Range(set.getRangeStart(i), set.getRangeEnd(i)), zone); + } + // No errors and everything we collected have been ranges. + Canonicalize(ranges); +#endif // V8_INTL_SUPPORT +} + +RegExpNode* RegExpClassRanges::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + set_.Canonicalize(); + TRACE_WITH_NODE("* After canonicalization: ", this); + Zone* const zone = compiler->zone(); + ZoneList* ranges = this->ranges(zone); + + const bool needs_case_folding = + NeedsUnicodeCaseEquivalents(compiler->flags()) && + !no_case_folding_needed(); + if (needs_case_folding) { + CharacterRange::AddUnicodeCaseEquivalents(ranges, zone); + TRACE_WITH_NODE("* After case folding: ", this); + } + + if (!IsEitherUnicode(compiler->flags()) || compiler->one_byte() || + contains_split_surrogate()) { + TextNode* result = + zone->New(this, compiler->read_backward(), on_success); + if (compiler->one_byte() && !result->CanMatchLatin1(compiler)) { + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; + } + + REGISTER_NODE(result); + return result; + } + + if (is_negated()) { + // With /v, character classes are never negated. + // https://tc39.es/ecma262/#sec-compileatom + // Atom :: CharacterClass + // 4. Assert: cc.[[Invert]] is false. + // Instead the complement is created when evaluating the class set. + // The only exception is the "nothing range" (negated everything), which is + // internally created for an empty set. + DCHECK_IMPLIES( + IsUnicodeSets(compiler->flags()), + ranges->length() == 1 && ranges->first().IsEverything(kMaxCodePoint)); + ZoneList* negated = + zone->New>(2, zone); + CharacterRange::Negate(ranges, negated, zone); + ranges = negated; +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { + std::ostream& os = compiler->diagnostics()->trace_tree_scope()->os(); + os << "* After negation: ["; + for (int i = 0; i < ranges->length(); i++) { + const CharacterRange& range = ranges->at(i); + os << " " << AsUC32(range.from()); + if (!range.IsSingleton()) { + os << "-" << AsUC32(range.to()); + } + } + os << "]" << std::endl; + } +#endif + } + + if (ranges->length() == 0) { + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; + } + + if (set_.is_standard() && + standard_type() == StandardCharacterSet::kEverything) { + return UnanchoredAdvance(compiler, on_success); + } + + // Split ranges in order to handle surrogates correctly: + // - Surrogate pairs: translate the 32-bit code point into two uc16 code + // units (irregexp operates only on code units). + // - Lone surrogates: these require lookarounds to ensure we don't match in + // the middle of a surrogate pair. + ChoiceNode* result = zone->New(2, zone); + UnicodeRangeSplitter splitter(ranges); + AddBmpCharacters(compiler, result, on_success, &splitter); + AddNonBmpSurrogatePairs(compiler, result, on_success, &splitter); + AddLoneLeadSurrogates(compiler, result, on_success, &splitter); + AddLoneTrailSurrogates(compiler, result, on_success, &splitter); + + static constexpr int kMaxRangesToInline = 32; // Arbitrary. + if (ranges->length() > kMaxRangesToInline) result->SetDoNotInline(); + + if (result->alternatives()->length() == 1) { + return result->alternatives()->at(0).node(); + } + + REGISTER_NODE(result); + return result; +} + +RegExpNode* RegExpClassSetOperand::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + const int size = (has_strings() ? static_cast(strings()->size()) : 0) + + (ranges()->is_empty() ? 0 : 1); + if (size == 0) { + // If neither ranges nor strings are present, the operand is equal to an + // empty range (matching nothing). + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; + } + ZoneList* alternatives = + zone->New>(size, zone); + // Strings are sorted by length first (larger strings before shorter ones). + // See the comment on CharacterClassStrings. + // Empty strings (if present) are added after character ranges. + RegExpTree* empty_string = nullptr; + if (has_strings()) { + for (auto string : *strings()) { + if (string.second->IsEmpty()) { + empty_string = string.second; + } else { + alternatives->Add(string.second, zone); + } + } + } + if (!ranges()->is_empty()) { + // In unicode sets mode case folding has to be done at precise locations + // (e.g. before building complements). + // It is therefore the parsers responsibility to case fold (sub-) ranges + // before creating ClassSetOperands. + alternatives->Add( + zone->New(zone, ranges(), + RegExpClassRanges::NO_CASE_FOLDING_NEEDED), + zone); + } + if (empty_string != nullptr) { + alternatives->Add(empty_string, zone); + } + + RegExpTree* tree = nullptr; + if (size == 1) { + DCHECK_EQ(alternatives->length(), 1); + tree = alternatives->first(); + } else { + tree = zone->New(alternatives); + } + RegExpNode* node = tree->ToNode(compiler, on_success); + REGISTER_NODE(node); + return node; +} + +RegExpNode* RegExpClassSetExpression::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + ZoneList* temp_ranges = + zone->New>(4, zone); + RegExpClassSetOperand* root = ComputeExpression(this, temp_ranges, zone); + RegExpNode* node = root->ToNode(compiler, on_success); + REGISTER_NODE(node); + return node; +} + +void RegExpClassSetOperand::Union(RegExpClassSetOperand* other, Zone* zone) { + ranges()->AddAll(*other->ranges(), zone); + if (other->has_strings()) { + if (strings_ == nullptr) { + strings_ = zone->New(zone); + } + strings()->insert(other->strings()->begin(), other->strings()->end()); + } +} + +void RegExpClassSetOperand::Intersect(RegExpClassSetOperand* other, + ZoneList* temp_ranges, + Zone* zone) { + CharacterRange::Intersect(ranges(), other->ranges(), temp_ranges, zone); + std::swap(*ranges(), *temp_ranges); + temp_ranges->Rewind(0); + if (has_strings()) { + if (!other->has_strings()) { + strings()->clear(); + } else { + for (auto iter = strings()->begin(); iter != strings()->end();) { + if (other->strings()->find(iter->first) == other->strings()->end()) { + iter = strings()->erase(iter); + } else { + iter++; + } + } + } + } +} + +void RegExpClassSetOperand::Subtract(RegExpClassSetOperand* other, + ZoneList* temp_ranges, + Zone* zone) { + CharacterRange::Subtract(ranges(), other->ranges(), temp_ranges, zone); + std::swap(*ranges(), *temp_ranges); + temp_ranges->Rewind(0); + if (has_strings() && other->has_strings()) { + for (auto iter = strings()->begin(); iter != strings()->end();) { + if (other->strings()->find(iter->first) != other->strings()->end()) { + iter = strings()->erase(iter); + } else { + iter++; + } + } + } +} + +// static +RegExpClassSetOperand* RegExpClassSetExpression::ComputeExpression( + RegExpTree* root, + ZoneList* temp_ranges, + Zone* zone) { + ASSERT(temp_ranges->is_empty()); + if (root->IsClassSetOperand()) { + return root->AsClassSetOperand(); + } + ASSERT(root->IsClassSetExpression()); + RegExpClassSetExpression* node = root->AsClassSetExpression(); + RegExpClassSetOperand* result = + ComputeExpression(node->operands()->at(0), temp_ranges, zone); + switch (node->operation()) { + case OperationType::kUnion: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Union(op, zone); + } + CharacterRange::Canonicalize(result->ranges()); + break; + } + case OperationType::kIntersection: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Intersect(op, temp_ranges, zone); + } + break; + } + case OperationType::kSubtraction: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Subtract(op, temp_ranges, zone); + } + break; + } + } + if (node->is_negated()) { + ASSERT(!result->has_strings()); + CharacterRange::Negate(result->ranges(), temp_ranges, zone); + std::swap(*result->ranges(), *temp_ranges); + temp_ranges->Rewind(0); + node->is_negated_ = false; + } + // Store the result as single operand of the current node. + node->operands()->Set(0, result); + node->operands()->Rewind(1); + + return result; +} + +namespace { + +bool StartsWithAtom(RegExpTree* tree) { + if (tree->IsAtom()) return true; + return tree->IsText() && tree->AsText()->StartsWithAtom(); +} + +RegExpAtom* FirstAtom(RegExpTree* tree) { + if (tree->IsAtom()) return tree->AsAtom(); + return tree->AsText()->FirstAtom(); +} + +int CompareFirstChar(RegExpTree* const* a, RegExpTree* const* b) { + RegExpAtom* atom1 = FirstAtom(*a); + RegExpAtom* atom2 = FirstAtom(*b); + uint16_t character1 = atom1->data().at(0); + uint16_t character2 = atom2->data().at(0); + if (character1 < character2) return -1; + if (character1 > character2) return 1; + return 0; +} + +#ifdef V8_INTL_SUPPORT + +int CompareCaseInsensitive(const icu::UnicodeString& a, + const icu::UnicodeString& b) { + return a.caseCompare(b, U_FOLD_CASE_DEFAULT); +} + +int CompareFirstCharCaseInsensitive(RegExpTree* const* a, + RegExpTree* const* b) { + RegExpAtom* atom1 = FirstAtom(*a); + RegExpAtom* atom2 = FirstAtom(*b); + return CompareCaseInsensitive(icu::UnicodeString{atom1->data().at(0)}, + icu::UnicodeString{atom2->data().at(0)}); +} + +bool Equals(bool ignore_case, + const icu::UnicodeString& a, + const icu::UnicodeString& b) { + if (a == b) return true; + if (ignore_case) return CompareCaseInsensitive(a, b) == 0; + return false; // Case-sensitive equality already checked above. +} + +bool CharAtEquals(bool ignore_case, + int index, + const RegExpAtom* a, + const RegExpAtom* b) { + return Equals(ignore_case, icu::UnicodeString{a->data().at(index)}, + icu::UnicodeString{b->data().at(index)}); +} + +#else + +unibrow::uchar Canonical( + unibrow::Mapping* canonicalize, + unibrow::uchar c) { + unibrow::uchar chars[unibrow::Ecma262Canonicalize::kMaxWidth]; + int length = canonicalize->get(c, '\0', chars); + DCHECK_LE(length, 1); + unibrow::uchar canonical = c; + if (length == 1) canonical = chars[0]; + return canonical; +} + +int CompareCaseInsensitive( + unibrow::Mapping* canonicalize, + unibrow::uchar a, + unibrow::uchar b) { + if (a == b) return 0; + if (a >= 'a' || b >= 'a') { + a = Canonical(canonicalize, a); + b = Canonical(canonicalize, b); + } + return static_cast(a) - static_cast(b); +} + +int CompareFirstCharCaseInsensitive( + unibrow::Mapping* canonicalize, + RegExpTree* const* a, + RegExpTree* const* b) { + RegExpAtom* atom1 = FirstAtom(*a); + RegExpAtom* atom2 = FirstAtom(*b); + return CompareCaseInsensitive(canonicalize, atom1->data().at(0), + atom2->data().at(0)); +} + +bool Equals(bool ignore_case, + unibrow::Mapping* canonicalize, + unibrow::uchar a, + unibrow::uchar b) { + if (a == b) return true; + if (ignore_case) { + return CompareCaseInsensitive(canonicalize, a, b) == 0; + } + return false; // Case-sensitive equality already checked above. +} + +bool CharAtEquals(bool ignore_case, + unibrow::Mapping* canonicalize, + int index, + const RegExpAtom* a, + const RegExpAtom* b) { + return Equals(ignore_case, canonicalize, a->data().at(index), + b->data().at(index)); +} + +#endif // V8_INTL_SUPPORT + +} // namespace + +// We can stable sort runs of atoms, since the order does not matter if they +// start with different characters. +// Returns true if any consecutive atoms were found. +bool RegExpDisjunction::SortConsecutiveAtoms(RegExpCompiler* compiler) { + ZoneList* alternatives = this->alternatives(); + int length = alternatives->length(); + bool found_consecutive_atoms = false; + for (int i = 0; i < length; i++) { + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (StartsWithAtom(alternative)) break; + i++; + } + // i is length or it is the index of an atom. + if (i == length) break; + int first_atom = i; + i++; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!StartsWithAtom(alternative)) break; + i++; + } + // Sort atoms to get ones with common prefixes together. + // This step is more tricky if we are in a case-independent regexp, + // because it would change /is|I/ to /I|is/, and order matters when + // the regexp parts don't match only disjoint starting points. To fix + // this we have a version of CompareFirstChar that uses case- + // independent character classes for comparison. + DCHECK_LT(first_atom, alternatives->length()); + DCHECK_LE(i, alternatives->length()); + DCHECK_LE(first_atom, i); + if (IsIgnoreCase(compiler->flags())) { +#ifdef V8_INTL_SUPPORT + alternatives->StableSort(CompareFirstCharCaseInsensitive, first_atom, + i - first_atom); +#else + unibrow::Mapping* canonicalize = + compiler->isolate()->regexp_macro_assembler_canonicalize(); + auto compare_closure = [canonicalize](RegExpTree* const* a, + RegExpTree* const* b) { + return CompareFirstCharCaseInsensitive(canonicalize, a, b); + }; + alternatives->StableSort(compare_closure, first_atom, i - first_atom); +#endif // V8_INTL_SUPPORT + } else { + alternatives->StableSort(CompareFirstChar, first_atom, i - first_atom); + } + if (i - first_atom > 1) found_consecutive_atoms = true; + } + return found_consecutive_atoms; +} + +// Optimizes ab|ac|az to a(?:b|c|d). +void RegExpDisjunction::RationalizeConsecutiveAtoms(RegExpCompiler* compiler) { + Zone* zone = compiler->zone(); + ZoneList* alternatives = this->alternatives(); + int length = alternatives->length(); + const bool ignore_case = IsIgnoreCase(compiler->flags()); + + int write_posn = 0; + int i = 0; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!StartsWithAtom(alternative)) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + RegExpAtom* const atom = FirstAtom(alternative); + +#ifdef V8_INTL_SUPPORT + icu::UnicodeString common_prefix(atom->data().at(0)); +#else + unibrow::Mapping* const canonicalize = + compiler->isolate()->regexp_macro_assembler_canonicalize(); + unibrow::uchar common_prefix = atom->data().at(0); + if (ignore_case) { + common_prefix = Canonical(canonicalize, common_prefix); + } +#endif // V8_INTL_SUPPORT + int first_with_prefix = i; + int prefix_length = atom->length(); + i++; + while (i < length) { + alternative = alternatives->at(i); + if (!StartsWithAtom(alternative)) break; + RegExpAtom* const alt_atom = FirstAtom(alternative); +#ifdef V8_INTL_SUPPORT + icu::UnicodeString new_prefix(alt_atom->data().at(0)); + if (!Equals(ignore_case, new_prefix, common_prefix)) break; +#else + unibrow::uchar new_prefix = alt_atom->data().at(0); + if (!Equals(ignore_case, canonicalize, new_prefix, common_prefix)) break; +#endif // V8_INTL_SUPPORT + prefix_length = std::min(prefix_length, alt_atom->length()); + i++; + } + if (i > first_with_prefix + 2) { + // Found worthwhile run of alternatives with common prefix of at least one + // character. The sorting function above did not sort on more than one + // character for reasons of correctness, but there may still be a longer + // common prefix if the terms were similar or presorted in the input. + // Find out how long the common prefix is. + int run_length = i - first_with_prefix; + RegExpAtom* const alt_atom = + FirstAtom(alternatives->at(first_with_prefix)); + alternatives->at(first_with_prefix)->AsAtom(); + for (int j = 1; j < run_length && prefix_length > 1; j++) { + RegExpAtom* old_atom = + FirstAtom(alternatives->at(j + first_with_prefix)); + for (int k = 1; k < prefix_length; k++) { +#ifdef V8_INTL_SUPPORT + if (!CharAtEquals(ignore_case, k, alt_atom, old_atom)) { +#else + if (!CharAtEquals(ignore_case, canonicalize, k, alt_atom, old_atom)) { +#endif // V8_INTL_SUPPORT + prefix_length = k; + break; + } + } + } + RegExpAtom* prefix = + zone->New(alt_atom->data().SubVector(0, prefix_length)); + TRACE_WITH_NODE("* Found common prefix: ", prefix); + + ZoneList* pair = zone->New>(2, zone); + pair->Add(prefix, zone); + ZoneList* suffixes = + zone->New>(run_length, zone); + for (int j = 0; j < run_length; j++) { + if (alternatives->at(j + first_with_prefix)->IsAtom()) { + RegExpAtom* old_atom = + alternatives->at(j + first_with_prefix)->AsAtom(); + int len = old_atom->length(); + if (len == prefix_length) { + suffixes->Add(zone->New(), zone); + } else { + RegExpTree* suffix = zone->New( + old_atom->data().SubVector(prefix_length, len)); + suffixes->Add(suffix, zone); + } + } else { + RegExpText* new_text = zone->New(zone); + RegExpText* old_text = + alternatives->at(j + first_with_prefix)->AsText(); + RegExpAtom* old_atom = old_text->FirstAtom(); + int len = old_atom->length(); + if (len != prefix_length) { + RegExpAtom* suffix = zone->New( + old_atom->data().SubVector(prefix_length, len)); + new_text->AddElement(TextElement::Atom(suffix), zone); + } + for (int k = 1; k < old_text->elements()->length(); k++) { + new_text->AddElement(old_text->elements()->at(k), zone); + } + if (new_text->elements()->length() != 0) { + suffixes->Add(new_text, zone); + } else { + suffixes->Add(zone->New(), zone); + } + } + } + pair->Add(zone->New(suffixes), zone); + alternatives->at(write_posn++) = zone->New(pair); + } else { + // Just copy any non-worthwhile alternatives. + for (int j = first_with_prefix; j < i; j++) { + alternatives->at(write_posn++) = alternatives->at(j); + } + } + } + alternatives->Rewind(write_posn); // Trim end of array. +} + +// Optimizes b|c|z to [bcz]. +void RegExpDisjunction::FixSingleCharacterDisjunctions( + RegExpCompiler* compiler) { + Zone* zone = compiler->zone(); + ZoneList* alternatives = this->alternatives(); + int length = alternatives->length(); + + int write_posn = 0; + int i = 0; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!alternative->IsAtom()) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + RegExpAtom* const atom = alternative->AsAtom(); + if (atom->length() != 1) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + const RegExpFlags flags = compiler->flags(); + DCHECK_IMPLIES(IsEitherUnicode(flags), + !Utf16::IsLeadSurrogate(atom->data().at(0))); + bool contains_trail_surrogate = Utf16::IsTrailSurrogate(atom->data().at(0)); + int first_in_run = i; + i++; + // Find a run of single-character atom alternatives that have identical + // flags (case independence and unicode-ness). + while (i < length) { + alternative = alternatives->at(i); + if (!alternative->IsAtom()) break; + RegExpAtom* const alt_atom = alternative->AsAtom(); + if (alt_atom->length() != 1) break; + DCHECK_IMPLIES(IsEitherUnicode(flags), + !Utf16::IsLeadSurrogate(alt_atom->data().at(0))); + contains_trail_surrogate |= + Utf16::IsTrailSurrogate(alt_atom->data().at(0)); + i++; + } + if (i > first_in_run + 1) { + // Found non-trivial run of single-character alternatives. + int run_length = i - first_in_run; + ZoneList* ranges = + zone->New>(2, zone); + for (int j = 0; j < run_length; j++) { + RegExpAtom* old_atom = alternatives->at(j + first_in_run)->AsAtom(); + DCHECK_EQ(old_atom->length(), 1); + ranges->Add(CharacterRange::Singleton(old_atom->data().at(0)), zone); + } + RegExpClassRanges::ClassRangesFlags class_ranges_flags; + if (IsEitherUnicode(flags) && contains_trail_surrogate) { + class_ranges_flags = RegExpClassRanges::CONTAINS_SPLIT_SURROGATE; + } + alternatives->at(write_posn++) = + zone->New(zone, ranges, class_ranges_flags); + } else { + // Just copy any trivial alternatives. + for (int j = first_in_run; j < i; j++) { + alternatives->at(write_posn++) = alternatives->at(j); + } + } + } + alternatives->Rewind(write_posn); // Trim end of array. +} + +RegExpNode* RegExpDisjunction::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + ZoneList* alternatives = this->alternatives(); + + if (alternatives->length() > 2) { + bool found_consecutive_atoms = SortConsecutiveAtoms(compiler); + if (found_consecutive_atoms) RationalizeConsecutiveAtoms(compiler); + TRACE_WITH_NODE("* After rationalizing consecutive atoms: ", this); + FixSingleCharacterDisjunctions(compiler); + TRACE_WITH_NODE("* After fixing single character disjunctions: ", this); + if (alternatives->length() == 1) { + return alternatives->at(0)->ToNode(compiler, on_success); + } + } + + int length = alternatives->length(); + + ChoiceNode* result = + compiler->zone()->New(length, compiler->zone()); + for (int i = 0; i < length; i++) { + GuardedAlternative alternative( + alternatives->at(i)->ToNode(compiler, on_success)); + if (!alternative.node()->IsBacktrack()) { + result->AddAlternative(alternative); + } + } + REGISTER_NODE(result); + int node_length = result->alternatives()->length(); + if (node_length >= 2) return result; + if (node_length == 1) return result->alternatives()->at(0).node(); + Zone* zone = on_success->zone(); + RegExpNode* backtrack = zone->New(EndNode::BACKTRACK, zone); + REGISTER_NODE(backtrack); + return backtrack; +} + +RegExpNode* RegExpQuantifier::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + return ToNode(min(), max(), is_greedy(), body(), compiler, on_success); +} + +namespace { +// Desugar \b to (?<=\w)(?=\W)|(?<=\W)(?=\w) and +// \B to (?<=\w)(?=\w)|(?<=\W)(?=\W) +RegExpNode* BoundaryAssertionAsLookaround(RegExpCompiler* compiler, + RegExpNode* on_success, + RegExpAssertion::Type type) { + CHECK(NeedsUnicodeCaseEquivalents(compiler->flags())); + Zone* zone = compiler->zone(); + ZoneList* word_range = + zone->New>(2, zone); + CharacterRange::AddClassEscape(StandardCharacterSet::kWord, word_range, true, + zone); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + ChoiceNode* result = zone->New(2, zone); + // Add two choices. The (non-)boundary could start with a word or + // a non-word-character. + for (int i = 0; i < 2; i++) { + bool lookbehind_for_word = i == 0; + TRACE("* Creating " << (lookbehind_for_word ? "lookbehind" : "lookahead") + << " for word boundary"); + bool lookahead_for_word = + (type == RegExpAssertion::Type::BOUNDARY) ^ lookbehind_for_word; + // Look to the left. + RegExpLookaround::Builder lookbehind(lookbehind_for_word, on_success, + compiler, stack_register, + position_register); + RegExpNode* backward = TextNode::CreateForCharacterRanges( + zone, word_range, true, lookbehind.on_match_success()); + REGISTER_NODE(backward); + // Look to the right. + RegExpLookaround::Builder lookahead( + lookahead_for_word, lookbehind.ForMatch(compiler, backward), compiler, + stack_register, position_register); + RegExpNode* forward = TextNode::CreateForCharacterRanges( + zone, word_range, false, lookahead.on_match_success()); + REGISTER_NODE(forward); + result->AddAlternative( + GuardedAlternative(lookahead.ForMatch(compiler, forward))); + } + REGISTER_NODE(result); + return result; +} +} // anonymous namespace + +RegExpNode* RegExpAssertion::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + NodeInfo info; + Zone* zone = compiler->zone(); + + switch (assertion_type()) { + case Type::START_OF_LINE: { + RegExpNode* node = AssertionNode::AfterNewline(on_success); + REGISTER_NODE(node); + return node; + } + case Type::START_OF_INPUT: { + RegExpNode* node = AssertionNode::AtStart(on_success); + REGISTER_NODE(node); + return node; + } + case Type::BOUNDARY: { + RegExpNode* node = NeedsUnicodeCaseEquivalents(compiler->flags()) + ? BoundaryAssertionAsLookaround( + compiler, on_success, Type::BOUNDARY) + : AssertionNode::AtBoundary(on_success); + REGISTER_NODE(node); + return node; + } + case Type::NON_BOUNDARY: { + RegExpNode* node = NeedsUnicodeCaseEquivalents(compiler->flags()) + ? BoundaryAssertionAsLookaround( + compiler, on_success, Type::NON_BOUNDARY) + : AssertionNode::AtNonBoundary(on_success); + REGISTER_NODE(node); + return node; + } + case Type::END_OF_INPUT: { + RegExpNode* node = AssertionNode::AtEnd(on_success); + REGISTER_NODE(node); + return node; + } + case Type::END_OF_LINE: { + // Compile $ in multiline regexps as an alternation with a positive + // lookahead in one side and an end-of-input on the other side. + // We need two registers for the lookahead. + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + // The ChoiceNode to distinguish between a newline and end-of-input. + ChoiceNode* result = zone->New(2, zone); + // Create a newline atom. + ZoneList* newline_ranges = + zone->New>(3, zone); + CharacterRange::AddClassEscape(StandardCharacterSet::kLineTerminator, + newline_ranges, false, zone); + ActionNode* submatch_success = ActionNode::PositiveSubmatchSuccess( + stack_pointer_register, position_register, + 0, // No captures inside. + -1, // Ignored if no captures. + on_success); + REGISTER_NODE(submatch_success); + RegExpClassRanges* newline_atom = + zone->New(StandardCharacterSet::kLineTerminator); + TextNode* newline_matcher = + zone->New(newline_atom, false, submatch_success); + REGISTER_NODE(newline_matcher); + // Create an end-of-input matcher. + RegExpNode* end_of_line = ActionNode::BeginPositiveSubmatch( + stack_pointer_register, position_register, newline_matcher, + submatch_success); + REGISTER_NODE(end_of_line); + // Add the two alternatives to the ChoiceNode. + GuardedAlternative eol_alternative(end_of_line); + result->AddAlternative(eol_alternative); + GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success)); + result->AddAlternative(end_alternative); + REGISTER_NODE(result); + return result; + } + default: + UNREACHABLE(); + } +} + +RegExpNode* RegExpBackReference::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + RegExpNode* backref_node = on_success; + // Only one of the captures in the list can actually match. Since + // back-references to unmatched captures are treated as empty, we can simply + // create back-references to all possible captures. + for (auto capture : *captures()) { + backref_node = compiler->zone()->New( + RegExpCapture::StartRegister(capture->index()), + RegExpCapture::EndRegister(capture->index()), compiler->read_backward(), + backref_node); + REGISTER_NODE(backref_node); + } + return backref_node; +} + +RegExpNode* RegExpEmpty::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + return on_success; +} + +namespace { + +class V8_NODISCARD ModifiersScope { + public: + ModifiersScope(RegExpCompiler* compiler, RegExpFlags flags) + : compiler_(compiler), previous_flags_(compiler->flags()) { + compiler->set_flags(flags); + } + ~ModifiersScope() { compiler_->set_flags(previous_flags_); } + + private: + RegExpCompiler* compiler_; + const RegExpFlags previous_flags_; +}; + +} // namespace + +RegExpNode* RegExpGroup::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + // If no flags are modified, simply convert and return the body. + if (flags() == compiler->flags()) { + return body_->ToNode(compiler, on_success); + } + // Reset flags for successor node. + const RegExpFlags old_flags = compiler->flags(); + on_success = ActionNode::ModifyFlags(old_flags, on_success); + + // Convert body using modifier. + ModifiersScope modifiers_scope(compiler, flags()); + RegExpNode* body = body_->ToNode(compiler, on_success); + if (body->IsBacktrack()) return body; + + // Wrap body into modifier node. + RegExpNode* modified_body = ActionNode::ModifyFlags(flags(), body); + return modified_body; +} + +RegExpLookaround::Builder::Builder(bool is_positive, + RegExpNode* on_success, + RegExpCompiler* compiler, + int stack_pointer_register, + int position_register, + int capture_register_count, + int capture_register_start) + : is_positive_(is_positive), + on_success_(on_success), + stack_pointer_register_(stack_pointer_register), + position_register_(position_register) { + if (is_positive_) { + on_match_success_ = ActionNode::PositiveSubmatchSuccess( + stack_pointer_register, position_register, capture_register_count, + capture_register_start, on_success_); + } else { + Zone* zone = on_success_->zone(); + on_match_success_ = zone->New( + stack_pointer_register, position_register, capture_register_count, + capture_register_start, zone); + } + REGISTER_NODE(on_match_success_); +} + +RegExpNode* RegExpLookaround::Builder::ForMatch(RegExpCompiler* compiler, + RegExpNode* match) { + if (is_positive_) { + ActionNode* on_match_success = on_match_success_->AsActionNode(); + RegExpNode* node = ActionNode::BeginPositiveSubmatch( + stack_pointer_register_, position_register_, match, on_match_success); + REGISTER_NODE(node); + return node; + } else { + Zone* zone = on_success_->zone(); + // We use a ChoiceNode to represent the negative lookaround. The first + // alternative is the negative match. On success, the end node backtracks. + // On failure, the second alternative is tried and leads to success. + // NegativeLookaroundChoiceNode is a special ChoiceNode that ignores the + // first exit when calculating quick checks. + ChoiceNode* choice_node = zone->New( + GuardedAlternative(match), GuardedAlternative(on_success_), zone); + REGISTER_NODE(choice_node); + RegExpNode* node = ActionNode::BeginNegativeSubmatch( + stack_pointer_register_, position_register_, choice_node); + REGISTER_NODE(node); + return node; + } +} + +RegExpNode* RegExpLookaround::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + + const int registers_per_capture = 2; + const int register_of_first_capture = 2; + int register_count = capture_count_ * registers_per_capture; + int register_start = + register_of_first_capture + capture_from_ * registers_per_capture; + + RegExpNode* result; + bool was_reading_backward = compiler->read_backward(); + compiler->set_read_backward(type() == LOOKBEHIND); + Builder builder(is_positive(), on_success, compiler, stack_pointer_register, + position_register, register_count, register_start); + RegExpNode* match = body_->ToNode(compiler, builder.on_match_success()); + if (match->IsBacktrack() && (is_positive() || compiler->IsRegExpTooBig())) { + compiler->set_read_backward(was_reading_backward); + return match; + } + result = builder.ForMatch(compiler, match); + compiler->set_read_backward(was_reading_backward); + return result; +} + +RegExpNode* RegExpCapture::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + return ToNode(body(), index(), compiler, on_success); +} + +// static +RegExpNode* RegExpCapture::ToNode(RegExpTree* body, + int index, + RegExpCompiler* compiler, + RegExpNode* on_success) { + DCHECK_NOT_NULL(body); + int start_reg = RegExpCapture::StartRegister(index); + int end_reg = RegExpCapture::EndRegister(index); + if (compiler->read_backward()) std::swap(start_reg, end_reg); + RegExpNode* store_end = ActionNode::StorePosition(end_reg, on_success); + REGISTER_NODE(store_end); + RegExpNode* body_node = body->ToNode(compiler, store_end); + if (body_node->IsBacktrack()) return body_node; + RegExpNode* node = ActionNode::StorePosition(start_reg, body_node); + REGISTER_NODE(node); + return node; +} + +namespace { + +class AssertionSequenceRewriter final { + public: + // TODO(jgruber): Consider moving this to a separate AST tree rewriter pass + // instead of sprinkling rewrites into the AST->Node conversion process. + static void MaybeRewrite(ZoneList* terms, Zone* zone) { + AssertionSequenceRewriter rewriter(terms, zone); + + static constexpr int kNoIndex = -1; + int from = kNoIndex; + + for (int i = 0; i < terms->length(); i++) { + RegExpTree* t = terms->at(i); + if (from == kNoIndex && t->IsAssertion()) { + from = i; // Start a sequence. + } else if (from != kNoIndex && !t->IsAssertion()) { + // Terminate and process the sequence. + if (i - from > 1) rewriter.Rewrite(from, i); + from = kNoIndex; + } + } + + if (from != kNoIndex && terms->length() - from > 1) { + rewriter.Rewrite(from, terms->length()); + } + } + + // All assertions are zero width. A consecutive sequence of assertions is + // order-independent. There's two ways we can optimize here: + // 1. fold all identical assertions. + // 2. if any assertion combinations are known to fail (e.g. \b\B), the entire + // sequence fails. + void Rewrite(int from, int to) { + DCHECK_GT(to, from + 1); + + // Bitfield of all seen assertions. + uint32_t seen_assertions = 0; + static_assert(static_cast(RegExpAssertion::Type::LAST_ASSERTION_TYPE) < + kUInt32Size * kBitsPerByte); + + for (int i = from; i < to; i++) { + RegExpAssertion* t = terms_->at(i)->AsAssertion(); + const uint32_t bit = 1 << static_cast(t->assertion_type()); + + if (seen_assertions & bit) { + // Fold duplicates. + terms_->Set(i, zone_->New()); + } + + seen_assertions |= bit; + } + + // Collapse failures. + const uint32_t always_fails_mask = + 1 << static_cast(RegExpAssertion::Type::BOUNDARY) | + 1 << static_cast(RegExpAssertion::Type::NON_BOUNDARY); + if ((seen_assertions & always_fails_mask) == always_fails_mask) { + ReplaceSequenceWithFailure(from, to); + } + } + + void ReplaceSequenceWithFailure(int from, int to) { + // Replace the entire sequence with a single node that always fails. + // TODO(jgruber): Consider adding an explicit Fail kind. Until then, the + // negated '*' (everything) range serves the purpose. + ZoneList* ranges = + zone_->New>(0, zone_); + RegExpClassRanges* cc = zone_->New(zone_, ranges); + terms_->Set(from, cc); + + // Zero out the rest. + RegExpEmpty* empty = zone_->New(); + for (int i = from + 1; i < to; i++) + terms_->Set(i, empty); + } + + private: + AssertionSequenceRewriter(ZoneList* terms, Zone* zone) + : zone_(zone), terms_(terms) {} + + Zone* zone_; + ZoneList* terms_; +}; + +} // namespace + +RegExpNode* RegExpAlternative::ToNodeImpl(RegExpCompiler* compiler, + RegExpNode* on_success) { + ZoneList* children = nodes(); + + AssertionSequenceRewriter::MaybeRewrite(children, compiler->zone()); + TRACE_WITH_NODE("* After assertion sequence rewrite: ", this); + + RegExpNode* current = on_success; + if (compiler->read_backward()) { + for (int i = 0; i < children->length(); i++) { + current = children->at(i)->ToNode(compiler, current); + } + } else { + for (int i = children->length() - 1; i >= 0; i--) { + current = children->at(i)->ToNode(compiler, current); + } + } + return current; +} + +namespace { + +void AddClass(const int* elmv, + int elmc, + ZoneList* ranges, + Zone* zone) { + elmc--; + DCHECK_EQ(kRangeEndMarker, elmv[elmc]); + for (int i = 0; i < elmc; i += 2) { + ASSERT(elmv[i] < elmv[i + 1]); + ranges->Add(CharacterRange::Range(elmv[i], elmv[i + 1] - 1), zone); + } +} + +void AddClassNegated(const int* elmv, + int elmc, + ZoneList* ranges, + Zone* zone) { + elmc--; + DCHECK_EQ(kRangeEndMarker, elmv[elmc]); + DCHECK_NE(0x0000, elmv[0]); + DCHECK_NE(kMaxCodePoint, elmv[elmc - 1]); + uint16_t last = 0x0000; + for (int i = 0; i < elmc; i += 2) { + ASSERT(last <= elmv[i] - 1); + ASSERT(elmv[i] < elmv[i + 1]); + ranges->Add(CharacterRange::Range(last, elmv[i] - 1), zone); + last = elmv[i + 1]; + } + ranges->Add(CharacterRange::Range(last, kMaxCodePoint), zone); +} + +} // namespace + +void CharacterRange::AddClassEscape(StandardCharacterSet standard_character_set, + ZoneList* ranges, + bool add_unicode_case_equivalents, + Zone* zone) { + if (add_unicode_case_equivalents && + (standard_character_set == StandardCharacterSet::kWord || + standard_character_set == StandardCharacterSet::kNotWord)) { + // See #sec-runtime-semantics-wordcharacters-abstract-operation + // In case of unicode and ignore_case, we need to create the closure over + // case equivalent characters before negating. + ZoneList* new_ranges = + zone->New>(2, zone); + AddClass(kWordRanges, kWordRangeCount, new_ranges, zone); + AddUnicodeCaseEquivalents(new_ranges, zone); + if (standard_character_set == StandardCharacterSet::kNotWord) { + ZoneList* negated = + zone->New>(2, zone); + CharacterRange::Negate(new_ranges, negated, zone); + new_ranges = negated; + } + ranges->AddAll(*new_ranges, zone); + return; + } + + switch (standard_character_set) { + case StandardCharacterSet::kWhitespace: + AddClass(kSpaceRanges, kSpaceRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotWhitespace: + AddClassNegated(kSpaceRanges, kSpaceRangeCount, ranges, zone); + break; + case StandardCharacterSet::kWord: + AddClass(kWordRanges, kWordRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotWord: + AddClassNegated(kWordRanges, kWordRangeCount, ranges, zone); + break; + case StandardCharacterSet::kDigit: + AddClass(kDigitRanges, kDigitRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotDigit: + AddClassNegated(kDigitRanges, kDigitRangeCount, ranges, zone); + break; + // This is the set of characters matched by the $ and ^ symbols + // in multiline mode. + case StandardCharacterSet::kLineTerminator: + AddClass(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotLineTerminator: + AddClassNegated(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges, + zone); + break; + // This is not a character range as defined by the spec but a + // convenient shorthand for a character class that matches any + // character. + case StandardCharacterSet::kEverything: + ranges->Add(CharacterRange::Everything(), zone); + break; + } +} + +// static +// Only for /i, not for /ui or /vi. +void CharacterRange::AddCaseEquivalents(Isolate* isolate, + Zone* zone, + ZoneList* ranges, + bool is_one_byte) { + CharacterRange::Canonicalize(ranges); + int range_count = ranges->length(); +#ifdef V8_INTL_SUPPORT + icu::UnicodeSet others; + for (int i = 0; i < range_count; i++) { + CharacterRange range = ranges->at(i); + uint32_t from = range.from(); + if (from > kMaxUtf16CodeUnit) continue; + uint32_t to = std::min({range.to(), kMaxUtf16CodeUnitU}); + // Nothing to be done for surrogates. + if (from >= kLeadSurrogateStart && to <= kTrailSurrogateEnd) continue; + if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { + if (from > String::kMaxOneByteCharCode) continue; + if (to > String::kMaxOneByteCharCode) to = String::kMaxOneByteCharCode; + } + others.add(from, to); + } + + // Compute the set of additional characters that should be added, + // using UnicodeSet::closeOver. ECMA 262 defines slightly different + // case-folding rules than Unicode, so some characters that are + // added by closeOver do not match anything other than themselves in + // JS. For example, 'ſ' (U+017F LATIN SMALL LETTER LONG S) is the + // same case-insensitive character as 's' or 'S' according to + // Unicode, but does not match any other character in JS. To handle + // this case, we add such characters to the IgnoreSet and filter + // them out. We filter twice: once before calling closeOver (to + // prevent 'ſ' from adding 's'), and once after calling closeOver + // (to prevent 's' from adding 'ſ'). See regexp/special-case.h for + // more information. + icu::UnicodeSet already_added(others); + others.removeAll(RegExpCaseFolding::IgnoreSet()); + others.closeOver(USET_CASE_INSENSITIVE); + others.removeAll(RegExpCaseFolding::IgnoreSet()); + others.removeAll(already_added); + + // Add others to the ranges + for (int32_t i = 0; i < others.getRangeCount(); i++) { + UChar32 from = others.getRangeStart(i); + UChar32 to = others.getRangeEnd(i); + if (from == to) { + ranges->Add(CharacterRange::Singleton(from), zone); + } else { + ranges->Add(CharacterRange::Range(from, to), zone); + } + } +#else + for (int i = 0; i < range_count; i++) { + CharacterRange range = ranges->at(i); + uint32_t bottom = range.from(); + if (bottom > kMaxUtf16CodeUnit) continue; + uint32_t top = std::min({range.to(), kMaxUtf16CodeUnitU}); + // Nothing to be done for surrogates. + if (bottom >= kLeadSurrogateStart && top <= kTrailSurrogateEnd) continue; + if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { + if (bottom > String::kMaxOneByteCharCode) continue; + if (top > String::kMaxOneByteCharCode) top = String::kMaxOneByteCharCode; + } + unibrow::uchar chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + if (top == bottom) { + // If this is a singleton we just expand the one character. + int length = isolate->jsregexp_uncanonicalize()->get(bottom, '\0', chars); + for (int j = 0; j < length; j++) { + uint32_t chr = chars[j]; + if (chr != bottom) { + ranges->Add(CharacterRange::Singleton(chars[j]), zone); + } + } + } else { + // If this is a range we expand the characters block by block, expanding + // contiguous subranges (blocks) one at a time. The approach is as + // follows. For a given start character we look up the remainder of the + // block that contains it (represented by the end point), for instance we + // find 'z' if the character is 'c'. A block is characterized by the + // property that all characters uncanonicalize in the same way, except + // that each entry in the result is incremented by the distance from the + // first element. So a-z is a block because 'a' uncanonicalizes to ['a', + // 'A'] and the k'th letter uncanonicalizes to ['a' + k, 'A' + k]. Once + // we've found the end point we look up its uncanonicalization and + // produce a range for each element. For instance for [c-f] we look up + // ['z', 'Z'] and produce [c-f] and [C-F]. We then only add a range if + // it is not already contained in the input, so [c-f] will be skipped but + // [C-F] will be added. If this range is not completely contained in a + // block we do this for all the blocks covered by the range (handling + // characters that is not in a block as a "singleton block"). + unibrow::uchar equivalents[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + uint32_t pos = bottom; + while (pos <= top) { + int length = + isolate->jsregexp_canonrange()->get(pos, '\0', equivalents); + uint32_t block_end; + if (length == 0) { + block_end = pos; + } else { + DCHECK_EQ(1, length); + block_end = equivalents[0]; + } + int end = (block_end > top) ? top : block_end; + length = isolate->jsregexp_uncanonicalize()->get(block_end, '\0', + equivalents); + for (int j = 0; j < length; j++) { + uint32_t c = equivalents[j]; + uint32_t range_from = c - (block_end - pos); + uint32_t range_to = c - (block_end - end); + if (!(bottom <= range_from && range_to <= top)) { + ranges->Add(CharacterRange::Range(range_from, range_to), zone); + } + } + pos = end + 1; + } + } + } +#endif // V8_INTL_SUPPORT +} + +bool CharacterRange::IsCanonical(const ZoneList* ranges) { + DCHECK_NOT_NULL(ranges); + int n = ranges->length(); + if (n <= 1) return true; + uint32_t max = ranges->at(0).to(); + for (int i = 1; i < n; i++) { + CharacterRange next_range = ranges->at(i); + if (next_range.from() <= max + 1) return false; + max = next_range.to(); + } + return true; +} + +ZoneList* CharacterSet::ranges(Zone* zone) { + if (ranges_ == nullptr) { + ranges_ = zone->New>(2, zone); + CharacterRange::AddClassEscape(standard_set_type_.value(), ranges_, false, + zone); + } + return ranges_; +} + +namespace { + +// Move a number of elements in a zonelist to another position +// in the same list. Handles overlapping source and target areas. +void MoveRanges(ZoneList* list, int from, int to, int count) { + // Ranges are potentially overlapping. + if (from < to) { + for (int i = count - 1; i >= 0; i--) { + list->at(to + i) = list->at(from + i); + } + } else { + for (int i = 0; i < count; i++) { + list->at(to + i) = list->at(from + i); + } + } +} + +int InsertRangeInCanonicalList(ZoneList* list, + int count, + CharacterRange insert) { + // Inserts a range into list[0..count[, which must be sorted + // by from value and non-overlapping and non-adjacent, using at most + // list[0..count] for the result. Returns the number of resulting + // canonicalized ranges. Inserting a range may collapse existing ranges into + // fewer ranges, so the return value can be anything in the range 1..count+1. + uint32_t from = insert.from(); + uint32_t to = insert.to(); + int start_pos = 0; + int end_pos = count; + for (int i = count - 1; i >= 0; i--) { + CharacterRange current = list->at(i); + if (current.from() > to + 1) { + end_pos = i; + } else if (current.to() + 1 < from) { + start_pos = i + 1; + break; + } + } + + // Inserted range overlaps, or is adjacent to, ranges at positions + // [start_pos..end_pos[. Ranges before start_pos or at or after end_pos are + // not affected by the insertion. + // If start_pos == end_pos, the range must be inserted before start_pos. + // if start_pos < end_pos, the entire range from start_pos to end_pos + // must be merged with the insert range. + + if (start_pos == end_pos) { + // Insert between existing ranges at position start_pos. + if (start_pos < count) { + MoveRanges(list, start_pos, start_pos + 1, count - start_pos); + } + list->at(start_pos) = insert; + return count + 1; + } + if (start_pos + 1 == end_pos) { + // Replace single existing range at position start_pos. + CharacterRange to_replace = list->at(start_pos); + int new_from = std::min(to_replace.from(), from); + int new_to = std::max(to_replace.to(), to); + list->at(start_pos) = CharacterRange::Range(new_from, new_to); + return count; + } + // Replace a number of existing ranges from start_pos to end_pos - 1. + // Move the remaining ranges down. + + int new_from = std::min(list->at(start_pos).from(), from); + int new_to = std::max(list->at(end_pos - 1).to(), to); + if (end_pos < count) { + MoveRanges(list, end_pos, start_pos + 1, count - end_pos); + } + list->at(start_pos) = CharacterRange::Range(new_from, new_to); + return count - (end_pos - start_pos) + 1; +} + +} // namespace + +void CharacterSet::Canonicalize() { + // Special/default classes are always considered canonical. The result + // of calling ranges() will be sorted. + if (ranges_ == nullptr) return; + CharacterRange::Canonicalize(ranges_); +} + +// static +void CharacterRange::Canonicalize(ZoneList* character_ranges) { + if (character_ranges->length() <= 1) return; + // Check whether ranges are already canonical (increasing, non-overlapping, + // non-adjacent). + int n = character_ranges->length(); + uint32_t max = character_ranges->at(0).to(); + int i = 1; + while (i < n) { + CharacterRange current = character_ranges->at(i); + if (current.from() <= max + 1) { + break; + } + max = current.to(); + i++; + } + // Canonical until the i'th range. If that's all of them, we are done. + if (i == n) return; + + // The ranges at index i and forward are not canonicalized. Make them so by + // doing the equivalent of insertion sort (inserting each into the previous + // list, in order). + // Notice that inserting a range can reduce the number of ranges in the + // result due to combining of adjacent and overlapping ranges. + int read = i; // Range to insert. + int num_canonical = i; // Length of canonicalized part of list. + do { + num_canonical = InsertRangeInCanonicalList(character_ranges, num_canonical, + character_ranges->at(read)); + read++; + } while (read < n); + character_ranges->Rewind(num_canonical); + + ASSERT(CharacterRange::IsCanonical(character_ranges)); +} + +// static +void CharacterRange::Negate(const ZoneList* ranges, + ZoneList* negated_ranges, + Zone* zone) { + ASSERT(CharacterRange::IsCanonical(ranges)); + DCHECK_EQ(0, negated_ranges->length()); + int range_count = ranges->length(); + uint32_t from = 0; + int i = 0; + if (range_count > 0 && ranges->at(0).from() == 0) { + from = ranges->at(0).to() + 1; + i = 1; + } + while (i < range_count) { + CharacterRange range = ranges->at(i); + negated_ranges->Add(CharacterRange::Range(from, range.from() - 1), zone); + from = range.to() + 1; + i++; + } + if (from < kMaxCodePoint) { + negated_ranges->Add(CharacterRange::Range(from, kMaxCodePoint), zone); + } +} + +// static +void CharacterRange::Intersect(const ZoneList* lhs, + const ZoneList* rhs, + ZoneList* intersection, + Zone* zone) { + ASSERT(CharacterRange::IsCanonical(lhs)); + ASSERT(CharacterRange::IsCanonical(rhs)); + DCHECK_EQ(0, intersection->length()); + int lhs_index = 0; + int rhs_index = 0; + while (lhs_index < lhs->length() && rhs_index < rhs->length()) { + // Skip non-overlapping ranges. + if (lhs->at(lhs_index).to() < rhs->at(rhs_index).from()) { + lhs_index++; + continue; + } + if (rhs->at(rhs_index).to() < lhs->at(lhs_index).from()) { + rhs_index++; + continue; + } + + uint32_t from = + std::max(lhs->at(lhs_index).from(), rhs->at(rhs_index).from()); + uint32_t to = std::min(lhs->at(lhs_index).to(), rhs->at(rhs_index).to()); + intersection->Add(CharacterRange::Range(from, to), zone); + if (to == lhs->at(lhs_index).to()) { + lhs_index++; + } else { + rhs_index++; + } + } + + ASSERT(IsCanonical(intersection)); +} + +namespace { + +// Advance |index| and set |from| and |to| to the new range, if not out of +// bounds of |range|, otherwise |from| is set to a code point beyond the legal +// unicode character range. +void SafeAdvanceRange(const ZoneList* range, + int* index, + uint32_t* from, + uint32_t* to) { + ++(*index); + if (*index < range->length()) { + *from = range->at(*index).from(); + *to = range->at(*index).to(); + } else { + *from = kMaxCodePoint + 1; + } +} + +} // namespace + +// static +void CharacterRange::Subtract(const ZoneList* src, + const ZoneList* to_remove, + ZoneList* result, + Zone* zone) { + ASSERT(CharacterRange::IsCanonical(src)); + ASSERT(CharacterRange::IsCanonical(to_remove)); + DCHECK_EQ(0, result->length()); + + if (src->is_empty()) return; + + int src_index = 0; + int to_remove_index = 0; + uint32_t from = src->at(src_index).from(); + uint32_t to = src->at(src_index).to(); + while (src_index < src->length() && to_remove_index < to_remove->length()) { + CharacterRange remove_range = to_remove->at(to_remove_index); + if (remove_range.to() < from) { + // (a) Non-overlapping case, ignore current to_remove range. + // |-------| + // |-------| + to_remove_index++; + } else if (to < remove_range.from()) { + // (b) Non-overlapping case, add full current range to result. + // |-------| + // |-------| + result->Add(CharacterRange::Range(from, to), zone); + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (from >= remove_range.from() && to <= remove_range.to()) { + // (c) Current to_remove range fully covers current range. + // |---| + // |-------| + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (from < remove_range.from() && to > remove_range.to()) { + // (d) Split current range. + // |-------| + // |---| + result->Add(CharacterRange::Range(from, remove_range.from() - 1), zone); + from = remove_range.to() + 1; + to_remove_index++; + } else if (from < remove_range.from()) { + // (e) End current range. + // |-------| + // |-------| + to = remove_range.from() - 1; + result->Add(CharacterRange::Range(from, to), zone); + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (to > remove_range.to()) { + // (f) Modify start of current range. + // |-------| + // |-------| + from = remove_range.to() + 1; + to_remove_index++; + } else { + UNREACHABLE(); + } + } + // The last range needs special treatment after |to_remove| is exhausted, as + // |from| might have been modified by the last |to_remove| range and |to| was + // not yet known (i.e. cases d and f). + if (from <= to) { + result->Add(CharacterRange::Range(from, to), zone); + } + src_index++; + + // Add remaining ranges after |to_remove| is exhausted. + for (; src_index < src->length(); src_index++) { + result->Add(src->at(src_index), zone); + } + + ASSERT(IsCanonical(result)); +} + +// static +void CharacterRange::ClampToOneByte(ZoneList* ranges) { + ASSERT(IsCanonical(ranges)); + + // Drop all ranges that don't contain one-byte code units, and clamp the last + // range s.t. it likewise only contains one-byte code units. Note this relies + // on `ranges` being canonicalized, i.e. sorted and non-overlapping. + + static constexpr uint32_t max_char = String::kMaxOneByteCharCodeU; + int n = ranges->length(); + for (; n > 0; n--) { + CharacterRange& r = ranges->at(n - 1); + if (r.from() <= max_char) { + r.to_ = std::min(r.to_, max_char); + break; + } + } + + ranges->Rewind(n); +} + +// static +bool CharacterRange::Equals(const ZoneList* lhs, + const ZoneList* rhs) { + ASSERT(IsCanonical(lhs)); + ASSERT(IsCanonical(rhs)); + if (lhs->length() != rhs->length()) return false; + + for (int i = 0; i < lhs->length(); i++) { + if (lhs->at(i) != rhs->at(i)) return false; + } + + return true; +} + +namespace { + +// Scoped object to keep track of how much we unroll quantifier loops in the +// regexp graph generator. +class RegExpExpansionLimiter { + public: + static const int kMaxExpansionFactor = 6; + RegExpExpansionLimiter(RegExpCompiler* compiler, int factor) + : compiler_(compiler), + saved_expansion_factor_(compiler->current_expansion_factor()), + ok_to_expand_(saved_expansion_factor_ <= kMaxExpansionFactor) { + DCHECK_LT(0, factor); + if (ok_to_expand_) { + if (factor > kMaxExpansionFactor) { + // Avoid integer overflow of the current expansion factor. + ok_to_expand_ = false; + compiler->set_current_expansion_factor(kMaxExpansionFactor + 1); + } else { + int new_factor = saved_expansion_factor_ * factor; + ok_to_expand_ = (new_factor <= kMaxExpansionFactor); + compiler->set_current_expansion_factor(new_factor); + } + } + } + + ~RegExpExpansionLimiter() { + compiler_->set_current_expansion_factor(saved_expansion_factor_); + } + + bool ok_to_expand() { return ok_to_expand_; } + + private: + RegExpCompiler* compiler_; + int saved_expansion_factor_; + bool ok_to_expand_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpExpansionLimiter); +}; + +} // namespace + +// static +RegExpNode* RegExpQuantifier::ToNode(int min, + int max, + bool is_greedy, + RegExpTree* body, + RegExpCompiler* compiler, + RegExpNode* on_success, + bool not_at_start) { +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + TraceRegExpTreeScope trace_tree_scope(compiler->diagnostics()); +#endif + TRACE("* Handling quantifier {" + << min << "," << (max == kInfinity ? "∞" : std::to_string(max)) << "}"); + // x{f, t} becomes this: + // + // (r++)<-. + // | ` + // | (x) + // v ^ + // (r=0)-->(?)---/ [if r < t] + // | + // [if r >= f] \----> ... + // + + // 15.10.2.5 RepeatMatcher algorithm. + // The parser has already eliminated the case where max is 0. In the case + // where max_match is zero the parser has removed the quantifier if min was + // > 0 and removed the atom if min was 0. See AddQuantifierToAtom. + + // If we know that we cannot match zero length then things are a little + // simpler since we don't need to make the special zero length match check + // from step 2.1. If the min and max are small we can unroll a little in + // this case. + static const int kMaxUnrolledMinMatches = 3; // Unroll (foo)+ and (foo){3,} + static const int kMaxUnrolledMaxMatches = 3; // Unroll (foo)? and (foo){x,3} + if (max == 0) return on_success; // This can happen due to recursion. + bool body_can_be_empty = (body->min_match() == 0); + int body_start_reg = RegExpCompiler::kNoRegister; + Interval capture_registers = body->CaptureRegisters(); + // At the start of the next iteration of a quantifier the captures must be + // cleared, so that /(?:x(.)?z){2}/ when applied to "xyzxz" captures "" + // (rather than "y" from the first repeat). However, if the max number of + // iterations is 1 then there is no 'next repeat' so we don't need to do this. + bool needs_capture_clearing = !capture_registers.is_empty() && max != 1; + Zone* zone = compiler->zone(); + + bool want_unroll = compiler->optimize() && FLAG_regexp_unroll; + if (body_can_be_empty) { + body_start_reg = compiler->AllocateRegister(); + } else if (want_unroll && !needs_capture_clearing) { + // Only unroll if there are no captures and the body can't be + // empty. + { + RegExpExpansionLimiter limiter(compiler, min + ((max != min) ? 1 : 0)); + if (min > 0 && min <= kMaxUnrolledMinMatches && limiter.ok_to_expand()) { + TRACE("* Recurse for remainder after unrolling (unrolling " + << min << " times)"); + int new_max = (max == kInfinity) ? max : max - min; + // Recurse once to get the loop or optional matches after the fixed + // ones. + RegExpNode* answer = + ToNode(0, new_max, is_greedy, body, compiler, on_success, true); + // Unroll the forced matches from 0 to min. This can cause chains of + // TextNodes (which the parser does not generate). These should be + // combined if it turns out they hinder good code generation. + TRACE("* Unrolling loop " << min << " time(s) for min matches"); + for (int i = 0; i < min; i++) { + TRACE("* Iteration " << i + 1 << " / " << min); + answer = body->ToNode(compiler, answer); + } + return answer; + } + } + if (max <= kMaxUnrolledMaxMatches && min == 0) { + DCHECK_LT(0, max); // Due to the 'if' above. + RegExpExpansionLimiter limiter(compiler, max); + if (limiter.ok_to_expand()) { + TRACE("* Unrolling loop " << max << " times for max matches"); + // Unroll the optional matches up to max. + RegExpNode* answer = on_success; + for (int i = 0; i < max; i++) { + TRACE("* Iteration " << i + 1 << " / " << max); + ChoiceNode* alternation = zone->New(2, zone); + if (is_greedy) { + alternation->AddAlternative( + GuardedAlternative(body->ToNode(compiler, answer))); + alternation->AddAlternative(GuardedAlternative(on_success)); + } else { + alternation->AddAlternative(GuardedAlternative(on_success)); + alternation->AddAlternative( + GuardedAlternative(body->ToNode(compiler, answer))); + } + answer = alternation; + if (not_at_start && !compiler->read_backward()) { + alternation->set_not_at_start(); + } + REGISTER_NODE(alternation); + } + return answer; + } + } + } + bool has_min = min > 0; + bool has_max = max < RegExpTree::kInfinity; + bool needs_counter = has_min || has_max; + int reg_ctr = needs_counter ? compiler->AllocateRegister() + : RegExpCompiler::kNoRegister; + LoopChoiceNode* center = zone->New( + body->min_match() == 0, compiler->read_backward(), zone); + if (not_at_start && !compiler->read_backward()) center->set_not_at_start(); + RegExpNode* loop_return = center; + if (needs_counter) { + loop_return = ActionNode::IncrementRegister(reg_ctr, loop_return); + REGISTER_NODE(loop_return); + } + if (body_can_be_empty) { + // If the body can be empty we need to check if it was and then + // backtrack. + loop_return = + ActionNode::EmptyMatchCheck(body_start_reg, reg_ctr, min, loop_return); + REGISTER_NODE(loop_return); + } + RegExpNode* body_node = body->ToNode(compiler, loop_return); + if (body_node->IsBacktrack()) { + // Body can never match. If there is a minimum number of iterations that + // means this whole part of the regexp can't match, so we just return the + // never-match (backtrack) node. + if (has_min) return body_node; + // Since there is no minimum number of iterations and the body can't match + // we can go straight to whatever comes after the quantifier. + return on_success; + } + if (body_can_be_empty) { + // If the body can be empty we need to store the start position + // so we can bail out if it was empty. + body_node = ActionNode::RestorePosition(body_start_reg, body_node); + REGISTER_NODE(body_node); + } + if (needs_capture_clearing) { + // Before entering the body of this loop we need to clear captures. + body_node = ActionNode::ClearCaptures(capture_registers, body_node); + REGISTER_NODE(body_node); + } + GuardedAlternative body_alt(body_node); + if (has_max) { + Guard* body_guard = zone->New(reg_ctr, Guard::LT, max); + body_alt.AddGuard(body_guard, zone); + } + GuardedAlternative rest_alt(on_success); + if (has_min) { + Guard* rest_guard = compiler->zone()->New(reg_ctr, Guard::GEQ, min); + rest_alt.AddGuard(rest_guard, zone); + } + if (is_greedy) { + center->AddLoopAlternative(body_alt); + center->AddContinueAlternative(rest_alt); + } else { + center->AddContinueAlternative(rest_alt); + center->AddLoopAlternative(body_alt); + } + REGISTER_NODE(center); + RegExpNode* result = center; + if (min > 0 && body->min_match() > 0 && !compiler->read_backward()) { + uint8_t eats = base::saturated_cast( + std::min(256, min) * std::min(256, body->min_match())); + result = ActionNode::EatsAtLeast(eats, result); + REGISTER_NODE(result); + } + if (needs_counter) { + result = ActionNode::SetRegisterForLoop(reg_ctr, 0, result); + REGISTER_NODE(result); + } + return result; +} + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-compiler.cc b/runtime/vm/regexp/regexp-compiler.cc new file mode 100644 index 00000000000..dc304d55ffa --- /dev/null +++ b/runtime/vm/regexp/regexp-compiler.cc @@ -0,0 +1,4225 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-compiler.h" + +#include +#include +#include +#include +#include + +#include "vm/regexp/regexp-macro-assembler.h" +#include "vm/regexp/unibrow.h" +#include "vm/regexp/zone-list-inl.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/locid.h" +#include "unicode/uniset.h" +#include "unicode/utypes.h" +#include "vm/regexp/special-case.h" +#endif // V8_INTL_SUPPORT + +namespace dart { + +using namespace regexp_compiler_constants; // NOLINT(build/namespaces) + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS +#define TRACE_COMPILER(compiler, msg) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { \ + compiler->diagnostics()->os() << msg << std::endl; \ + } \ + } while (false) +#define TRACE(msg) TRACE_COMPILER(compiler, msg) +#define TRACE_WITH_NODE(compiler, msg, node) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { \ + RegExpGraphPrinter* printer = compiler->diagnostics()->graph_printer(); \ + std::ostream& os = compiler->diagnostics()->os(); \ + os << msg; \ + printer->PrintNode(node); \ + } \ + } while (false) +#define TRACE_WITH_NODE_AND_TRACE(compiler, msg, node, trace) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { \ + RegExpGraphPrinter* printer = compiler->diagnostics()->graph_printer(); \ + std::ostream& os = compiler->diagnostics()->os(); \ + os << msg; \ + printer->PrintNodeNoNewline(node); \ + if (trace != nullptr) { \ + os << " "; \ + printer->PrintTrace(trace); \ + } \ + os << std::endl; \ + } \ + } while (false) +#define TRACE_EMIT(name) \ + TRACE_WITH_NODE_AND_TRACE(compiler, "* Assembling " << name << ": ", this, \ + trace) +#define TRACE_GRAPH(msg) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + diagnostics()->os() << msg << std::endl; \ + } \ + } while (false) +#define TRACE_GRAPH_WITH_NODE(msg, node) \ + do { \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + std::ostream& os = diagnostics()->trace_tree_scope()->os(); \ + os << msg; \ + diagnostics()->ast_printer()->Print(node); \ + os << std::endl; \ + } \ + } while (false) +#define REGISTER_NODE(node) \ + do { \ + if (UNLIKELY(!!diagnostics() && diagnostics()->has_graph_labeller())) { \ + diagnostics()->graph_labeller()->RegisterNode(node); \ + } \ + if (UNLIKELY(v8_flags.trace_regexp_graph_building)) { \ + diagnostics()->trace_tree_scope()->os() << "+ "; \ + diagnostics()->graph_printer()->PrintNode(node); \ + } \ + } while (false) +#else +#define TRACE_COMPILER(compiler, msg) (void(0)) +#define TRACE(x) (void(0)) +#define TRACE_WITH_NODE(compiler, msg, node) (void(0)) +#define TRACE_WITH_NODE_AND_TRACE(compiler, msg, node, trace) (void(0)) +#define TRACE_EMIT(name) (void(0)) +#define TRACE_GRAPH(msg) (void(0)) +#define TRACE_GRAPH_WITH_NODE(msg, node) (void(0)) +#define REGISTER_NODE(node) (void(0)) +#endif + +// ------------------------------------------------------------------- +// Implementation of the Irregexp regular expression engine. +// +// The Irregexp regular expression engine is intended to be a complete +// implementation of ECMAScript regular expressions. It generates either +// bytecodes or native code. + +// The Irregexp regexp engine is structured in three steps. +// 1) The parser generates an abstract syntax tree. See ast.cc. +// 2) From the AST a node network is created. The nodes are all +// subclasses of RegExpNode. The nodes represent states when +// executing a regular expression. Several optimizations are +// performed on the node network. +// 3) From the nodes we generate either byte codes or native code +// that can actually execute the regular expression (perform +// the search). The code generation step is described in more +// detail below. + +// Code generation. +// +// The nodes are divided into four main categories. +// * Choice nodes +// These represent places where the regular expression can +// match in more than one way. For example on entry to an +// alternation (foo|bar) or a repetition (*, +, ? or {}). +// * Action nodes +// These represent places where some action should be +// performed. Examples include recording the current position +// in the input string to a register (in order to implement +// captures) or other actions on register for example in order +// to implement the counters needed for {} repetitions. +// * Matching nodes +// These attempt to match some element part of the input string. +// Examples of elements include character classes, plain strings +// or back references. +// * End nodes +// These are used to implement the actions required on finding +// a successful match or failing to find a match. +// +// The code generated (whether as byte codes or native code) maintains +// some state as it runs. This consists of the following elements: +// +// * The capture registers. Used for string captures. +// * Other registers. Used for counters etc. +// * The current position. +// * The stack of backtracking information. Used when a matching node +// fails to find a match and needs to try an alternative. +// +// Conceptual regular expression execution model: +// +// There is a simple conceptual model of regular expression execution +// which will be presented first. The actual code generated is a more +// efficient simulation of the simple conceptual model: +// +// * Choice nodes are implemented as follows: +// For each choice except the last { +// push current position +// push backtrack code location +// +// backtrack code location: +// pop current position +// } +// +// +// * Actions nodes are generated as follows +// +// +// push backtrack code location +// +// backtrack code location: +// +// +// +// * Matching nodes are generated as follows: +// if input string matches at current position +// update current position +// +// else +// +// +// Thus it can be seen that the current position is saved and restored +// by the choice nodes, whereas the registers are saved and restored by +// by the action nodes that manipulate them. +// +// The other interesting aspect of this model is that nodes are generated +// at the point where they are needed by a recursive call to Emit(). If +// the node has already been code generated then the Emit() call will +// generate a jump to the previously generated code instead. In order to +// limit recursion it is possible for the Emit() function to put the node +// on a work list for later generation and instead generate a jump. The +// destination of the jump is resolved later when the code is generated. +// +// Actual regular expression code generation. +// +// Code generation is actually more complicated than the above. In order to +// improve the efficiency of the generated code some optimizations are +// performed +// +// * Choice nodes have 1-character lookahead. +// A choice node looks at the following character and eliminates some of +// the choices immediately based on that character. This is not yet +// implemented. +// * Simple greedy loops store reduced backtracking information. We call +// these fixed length loops +// A quantifier like /.*foo/m will greedily match the whole input. It will +// then need to backtrack to a point where it can match "foo". The naive +// implementation of this would push each character position onto the +// backtracking stack, then pop them off one by one. This would use space +// proportional to the length of the input string. However since the "." +// can only match in one way and always has a constant length (in this case +// of 1) it suffices to store the current position on the top of the stack +// once. Matching now becomes merely incrementing the current position and +// backtracking becomes decrementing the current position and checking the +// result against the stored current position. This is faster and saves +// space. +// * The current state is virtualized. +// This is used to defer expensive operations until it is clear that they +// are needed and to generate code for a node more than once, allowing +// specialized an efficient versions of the code to be created. This is +// explained in the section below. +// +// Execution state virtualization. +// +// Instead of emitting code, nodes that manipulate the state can record their +// manipulation in an object called the Trace. The Trace object can record a +// current position offset, an optional backtrack code location on the top of +// the virtualized backtrack stack and some register changes. When a node is +// to be emitted it can flush the Trace or update it. Flushing the Trace +// will emit code to bring the actual state into line with the virtual state. +// Avoiding flushing the state can postpone some work (e.g. updates of capture +// registers). Postponing work can save time when executing the regular +// expression since it may be found that the work never has to be done as a +// failure to match can occur. In addition it is much faster to jump to a +// known backtrack code location than it is to pop an unknown backtrack +// location from the stack and jump there. +// +// The virtual state found in the Trace affects code generation. For example +// the virtual state contains the difference between the actual current +// position and the virtual current position, and matching code needs to use +// this offset to attempt a match in the correct location of the input +// string. Therefore code generated for a non-trivial trace is specialized +// to that trace. The code generator therefore has the ability to generate +// code for each node several times. In order to limit the size of the +// generated code there is an arbitrary limit on how many specialized sets of +// code may be generated for a given node. If the limit is reached, the +// trace is flushed and a generic version of the code for a node is emitted. +// This is subsequently used for that node. The code emitted for non-generic +// trace is not recorded in the node and so it cannot currently be reused in +// the event that code generation is requested for an identical trace. + +namespace { + +constexpr uint32_t MaxCodeUnit(const bool one_byte) { + static_assert(String::kMaxOneByteCharCodeU <= + std::numeric_limits::max()); + static_assert(String::kMaxUtf16CodeUnitU <= + std::numeric_limits::max()); + return one_byte ? String::kMaxOneByteCharCodeU : String::kMaxUtf16CodeUnitU; +} + +constexpr uint32_t CharMask(const bool one_byte) { + static_assert(Utils::IsPowerOfTwo(String::kMaxOneByteCharCodeU + 1)); + static_assert(Utils::IsPowerOfTwo(String::kMaxUtf16CodeUnitU + 1)); + return MaxCodeUnit(one_byte); +} + +} // namespace + +void RegExpTree::AppendToText(RegExpText* text, Zone* zone) { + UNREACHABLE(); +} + +void RegExpAtom::AppendToText(RegExpText* text, Zone* zone) { + text->AddElement(TextElement::Atom(this), zone); +} + +void RegExpClassRanges::AppendToText(RegExpText* text, Zone* zone) { + text->AddElement(TextElement::ClassRanges(this), zone); +} + +void RegExpText::AppendToText(RegExpText* text, Zone* zone) { + for (int i = 0; i < elements()->length(); i++) + text->AddElement(elements()->at(i), zone); +} + +TextElement TextElement::Atom(RegExpAtom* atom) { + return TextElement(ATOM, atom); +} + +TextElement TextElement::ClassRanges(RegExpClassRanges* class_ranges) { + return TextElement(CLASS_RANGES, class_ranges); +} + +int TextElement::length() const { + switch (text_type()) { + case ATOM: + return atom()->length(); + + case CLASS_RANGES: + return 1; + } + UNREACHABLE(); +} + +class RecursionCheck { + public: + explicit RecursionCheck(RegExpCompiler* compiler) : compiler_(compiler) { + compiler->IncrementRecursionDepth(); + } + ~RecursionCheck() { compiler_->DecrementRecursionDepth(); } + + private: + RegExpCompiler* compiler_; +}; + +// Attempts to compile the regexp using an Irregexp code generator. Returns +// a fixed array or a null handle depending on whether it succeeded. +RegExpCompiler::RegExpCompiler(Isolate* isolate, + Zone* zone, + int capture_count, + RegExpFlags flags, + bool one_byte) + : next_register_(JSRegExp::RegistersForCaptureCount(capture_count)), + unicode_lookaround_stack_register_(kNoRegister), + unicode_lookaround_position_register_(kNoRegister), + work_list_(nullptr), + recursion_depth_(0), + flags_(flags), + one_byte_(one_byte), + reg_exp_too_big_(false), + limiting_recursion_(false), + optimize_(FLAG_regexp_optimization), + read_backward_(false), + current_expansion_factor_(1), + frequency_collator_(), + isolate_(isolate), + zone_(zone) { + accept_ = zone->New(EndNode::ACCEPT, zone); + DCHECK_GE(RegExpMacroAssembler::kMaxRegister, next_register_ - 1); +} + +RegExpCompiler::CompilationResult RegExpCompiler::Assemble( + Isolate* isolate, + RegExpMacroAssembler* macro_assembler, + RegExpNode* start, + int capture_count, + const String& pattern) { + macro_assembler_ = macro_assembler; + + auto ReportError = [this]() { + if (FLAG_correctness_fuzzer_suppressions) { + FATAL("Aborting on excess zone allocation"); + } + macro_assembler_->AbortedCodeGeneration(); + return CompilationResult::RegExpTooBig(); + }; + + ZoneVector work_list(zone()); + work_list_ = &work_list; + V8Label fail; + macro_assembler_->PushBacktrack(&fail); + Trace new_trace; + if (start->Emit(this, &new_trace).IsError()) { + return ReportError(); + } + macro_assembler_->BindJumpTarget(&fail); + macro_assembler_->Fail(); + while (!work_list.empty()) { + RegExpNode* node = work_list.back(); + TRACE_WITH_NODE(this, "Popping from worklist ", node); + work_list.pop_back(); + node->set_on_work_list(false); + if (!node->label()->is_bound()) { + if (node->Emit(this, &new_trace).IsError()) { + return ReportError(); + } + } + } + if (IsRegExpTooBig()) return ReportError(); + + ObjectPtr code = macro_assembler_->GetCode(pattern, flags_); + work_list_ = nullptr; + + return {&Object::Handle(code), next_register_}; +} + +bool Trace::mentions_reg(int reg) const { + for (auto trace : *this) { + if (trace->has_action() && trace->action()->Mentions(reg)) return true; + } + return false; +} + +bool Trace::GetStoredPosition(int reg, int* cp_offset) const { + DCHECK_EQ(0, *cp_offset); + for (auto trace : *this) { + if (trace->has_action() && trace->action()->Mentions(reg)) { + if (trace->action_->action_type() == ActionNode::STORE_POSITION || + trace->action_->action_type() == ActionNode::RESTORE_POSITION) { + *cp_offset = trace->next_->cp_offset(); + return true; + } else { + return false; + } + } + } + return false; +} + +// A (dynamically-sized) set of unsigned integers that behaves especially well +// on small integers (< kFirstLimit). May do zone-allocation. +class DynamicBitSet : public ZoneObject { + public: + bool Get(unsigned value) const { + if (value < kFirstLimit) { + return (first_ & (1 << value)) != 0; + } else if (remaining_ == nullptr) { + return false; + } else { + return remaining_->Contains(value); + } + } + + // Destructively set a value in this set. + void Set(unsigned value, Zone* zone) { + if (value < kFirstLimit) { + first_ |= (1 << value); + } else { + if (remaining_ == nullptr) + remaining_ = zone->New>(1, zone); + if (remaining_->is_empty() || !remaining_->Contains(value)) + remaining_->Add(value, zone); + } + } + + private: + static constexpr unsigned kFirstLimit = 32; + + uint32_t first_ = 0; + ZoneList* remaining_ = nullptr; +}; + +int Trace::FindAffectedRegisters(DynamicBitSet* affected_registers, + Zone* zone) { + int max_register = RegExpCompiler::kNoRegister; + for (auto trace : *this) { + if (ActionNode* action = trace->action_) { + int to = action->register_to(); + for (int i = action->register_from(); i <= to; i++) { + affected_registers->Set(i, zone); + } + if (to > max_register) max_register = to; + } + } + return max_register; +} + +void Trace::RestoreAffectedRegisters(RegExpMacroAssembler* assembler, + int max_register, + const DynamicBitSet& registers_to_pop, + const DynamicBitSet& registers_to_clear) { + for (int reg = max_register; reg >= 0; reg--) { + if (registers_to_pop.Get(reg)) { + assembler->PopRegister(reg); + } else if (registers_to_clear.Get(reg)) { + int clear_to = reg; + while (reg > 0 && registers_to_clear.Get(reg - 1)) { + reg--; + } + assembler->ClearRegisters(reg, clear_to); + } + } +} + +// Scans back through the deferred actions to find, for a given register, what +// needs to be done to effectuate the deferred actions. Also tells us what +// needs to be undone on backtrack. +void Trace::ScanDeferredActions(Trace* top, int reg, RegisterFlushInfo* info) { + // The chronologically first deferred action in the trace + // is used to infer the action needed to restore a register + // to its previous state (or not, if it's safe to ignore it). + + // This is a little tricky because we are scanning the actions in reverse + // historical order (newest first). + for (auto trace : *top) { + ActionNode* action = trace->action_; + if (!action) continue; + if (action->Mentions(reg)) { + switch (action->action_type()) { + case ActionNode::SET_REGISTER_FOR_LOOP: { + if (!info->absolute) { + info->value += action->value(); + info->absolute = true; + } + // SET_REGISTER_FOR_LOOP is only used for newly introduced loop + // counters. They can have a significant previous value if they + // occur in a loop. TODO(lrn): Propagate this information, so + // we can set undo_action to IGNORE_ if we know there is no value to + // restore. + info->undo_action = RESTORE; + DCHECK_EQ(info->store_position, kNoStore); + DCHECK(!info->clear); + break; + } + case ActionNode::INCREMENT_REGISTER: + if (!info->absolute) { + info->value++; + } + DCHECK_EQ(info->store_position, kNoStore); + DCHECK(!info->clear); + info->undo_action = RESTORE; + break; + case ActionNode::STORE_POSITION: + case ActionNode::RESTORE_POSITION: { + if (!info->clear && info->store_position == kNoStore) { + info->store_position = trace->next()->cp_offset(); + } + + // For captures we know that stores and clears alternate. + // Other register, are never cleared, and if the occur + // inside a loop, they might be assigned more than once. + if (reg <= 1) { + // Registers zero and one, aka "capture zero", is + // always set correctly if we succeed. There is no + // need to undo a setting on backtrack, because we + // will set it again or fail. + info->undo_action = IGNORE_; + } else { + if (action->action_type() == ActionNode::STORE_POSITION) { + info->undo_action = CLEAR; + } else { + info->undo_action = RESTORE; + } + } + DCHECK(!info->absolute); + DCHECK_EQ(info->value, 0); + break; + } + case ActionNode::CLEAR_CAPTURES: { + // Since we're scanning in reverse order, if we've already + // set the position we have to ignore historically earlier + // clearing operations. + if (info->store_position == kNoStore) { + info->clear = true; + } + info->undo_action = RESTORE; + DCHECK(!info->absolute); + DCHECK_EQ(info->value, 0); + break; + } + default: + UNREACHABLE(); + } + } + } +} + +void Trace::PerformDeferredActions(RegExpMacroAssembler* assembler, + int max_register, + const DynamicBitSet& affected_registers, + DynamicBitSet* registers_to_pop, + DynamicBitSet* registers_to_clear, + Zone* zone) { + // Count pushes performed to force a stack limit check occasionally. + int pushes = 0; + + for (int reg = 0; reg <= max_register; reg++) { + if (!affected_registers.Get(reg)) continue; + + RegisterFlushInfo info; + ScanDeferredActions(this, reg, &info); + + // Prepare for the undo-action (e.g., push if it's going to be popped). + if (info.undo_action == RESTORE) { + pushes++; + RegExpMacroAssembler::StackCheckFlag stack_check = + RegExpMacroAssembler::StackCheckFlag::kNoStackLimitCheck; + DCHECK_GT(assembler->stack_limit_slack_slot_count(), 0); + if (pushes == assembler->stack_limit_slack_slot_count()) { + stack_check = RegExpMacroAssembler::StackCheckFlag::kCheckStackLimit; + pushes = 0; + } + + assembler->PushRegister(reg, stack_check); + registers_to_pop->Set(reg, zone); + } else if (info.undo_action == CLEAR) { + registers_to_clear->Set(reg, zone); + } + // Perform the chronologically last action (or accumulated increment) + // for the register. + if (info.store_position != kNoStore) { + assembler->WriteCurrentPositionToRegister(reg, info.store_position); + } else if (info.clear) { + assembler->ClearRegisters(reg, reg); + } else if (info.absolute) { + assembler->SetRegister(reg, info.value); + } else if (info.value != 0) { + assembler->AdvanceRegister(reg, info.value); + } + } +} + +// This is called as we come into a loop choice node and some other tricky +// nodes. It normalizes the state of the code generator to ensure we can +// generate generic code. If the mode indicates that we are in a success +// situation then don't push anything, because the stack is about to be +// discarded, and also don't update the current position. +EmitResult Trace::Flush(RegExpCompiler* compiler, + RegExpNode* successor, + Trace::FlushMode mode) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { + RegExpGraphPrinter* printer = compiler->diagnostics()->graph_printer(); + std::ostream& os = compiler->diagnostics()->os(); + os << "* Flushing Trace ("; + switch (mode) { + case Trace::FlushMode::kFlushFull: + os << "Full"; + break; + case Trace::FlushMode::kFlushSuccess: + os << "Success"; + break; + } + os << "): "; + printer->PrintTrace(this); + os << std::endl; + } +#endif + + DCHECK(!is_trivial()); + + // Normally we don't need to update the current position register if we are + // about to stop because we had a successful match, but the global mode + // requires the current position register to be updated so it can start the + // next match. TODO(erikcorry): Perhaps it should use capture register 1 + // instead. + bool update_current_position = + cp_offset_ != 0 && (mode != kFlushSuccess || assembler->global()); + + if (!has_any_actions() && (backtrack() == nullptr || mode == kFlushSuccess)) { + // Here we just have some deferred cp advances to fix and we are back to + // a normal situation. We may also have to forget some information gained + // through a quick check that was already performed. + if (update_current_position) assembler->AdvanceCurrentPosition(cp_offset_); + // Create a new trivial state and generate the node with that. + Trace new_state; + return successor->Emit(compiler, &new_state); + } + + // Generate deferred actions here along with code to undo them again. + DynamicBitSet affected_registers; + + if (backtrack() != nullptr && mode != kFlushSuccess) { + // Here we have a concrete backtrack location. These are set up by choice + // nodes and so they indicate that we have a deferred save of the current + // position which we may need to emit here. + assembler->PushCurrentPosition(); + } + + int max_register = + FindAffectedRegisters(&affected_registers, compiler->zone()); + DynamicBitSet registers_to_pop; + DynamicBitSet registers_to_clear; + PerformDeferredActions(assembler, max_register, affected_registers, + ®isters_to_pop, ®isters_to_clear, + compiler->zone()); + if (update_current_position) assembler->AdvanceCurrentPosition(cp_offset_); + + if (mode == kFlushSuccess) { + Trace new_state; + return successor->Emit(compiler, &new_state); + } + + // Create a new trivial state and generate the node with that. + V8Label undo; + assembler->PushBacktrack(&undo); + if (successor->KeepRecursing(compiler)) { + Trace new_state; + EmitResult r = successor->Emit(compiler, &new_state); + if (UNLIKELY(r.IsError())) { + // TODO(jgruber): If this pattern emerges elsewhere, let's wrap affected + // labels in a scope object and add a convenience macro. + undo.UnuseNear(); + undo.Unuse(); + return r; + } + } else { + compiler->AddWork(successor); + assembler->GoTo(successor->label()); + } + + // On backtrack we need to restore state. + assembler->BindJumpTarget(&undo); + RestoreAffectedRegisters(assembler, max_register, registers_to_pop, + registers_to_clear); + if (backtrack() == nullptr) { + assembler->Backtrack(); + } else { + assembler->PopCurrentPosition(); + assembler->GoTo(backtrack()); + } + return EmitResult::Success(); +} + +EmitResult NegativeSubmatchSuccess::Emit(RegExpCompiler* compiler, + Trace* trace) { + TRACE_EMIT("NegativeSubmatchSuccess"); + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // Omit flushing the trace. We discard the entire stack frame anyway. + + if (!label()->is_bound()) { + // We are completely independent of the trace, since we ignore it, + // so this code can be used as the generic version. + assembler->Bind(label()); + } + + // Throw away everything on the backtrack stack since the start + // of the negative submatch and restore the character position. + assembler->ReadCurrentPositionFromRegister(current_position_register_); + assembler->ReadStackPointerFromRegister(stack_pointer_register_); + if (clear_capture_count_ > 0) { + // Clear any captures that might have been performed during the success + // of the body of the negative look-ahead. + int clear_capture_end = clear_capture_start_ + clear_capture_count_ - 1; + assembler->ClearRegisters(clear_capture_start_, clear_capture_end); + } + // Now that we have unwound the stack we find at the top of the stack the + // backtrack that the BeginNegativeSubmatch node got. + assembler->Backtrack(); + + return EmitResult::Success(); +} + +EmitResult EndNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("EndNode"); + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (action_ == BACKTRACK) { + // This node always backtracks, and we can do that immediately without + // flushing first. In practice many of these nodes have been eliminated + // already during the ToNode phase, so it should not happen often. + if (trace->is_trivial() && !label()->is_bound()) { + // We only need this if the node was pushed on the work list with + // AddWork, which can only happen with a stack overflow (KeepRecursing + // returns false). + assembler->Bind(label()); + } + assembler->GoTo(trace->backtrack()); + return EmitResult::Success(); + } + // The BACKTRACK case was handled above the NEGATIVE_SUBMATCH_SUCCESS is + // handled in a different virtual method. + CHECK_EQ(ACCEPT, action_); + if (!trace->is_trivial()) { + return trace->Flush(compiler, this, Trace::kFlushSuccess); + } + if (!label()->is_bound()) { + assembler->Bind(label()); + } + assembler->Succeed(); + return EmitResult::Success(); +} + +void GuardedAlternative::AddGuard(Guard* guard, Zone* zone) { + if (guards_ == nullptr) guards_ = zone->New>(1, zone); + guards_->Add(guard, zone); +} + +ActionNode* ActionNode::SetRegisterForLoop(int reg, + int val, + RegExpNode* on_success) { + return on_success->zone()->New(SET_REGISTER_FOR_LOOP, on_success, + reg, reg, val); +} + +ActionNode* ActionNode::IncrementRegister(int reg, RegExpNode* on_success) { + return on_success->zone()->New(INCREMENT_REGISTER, on_success, + reg); +} + +ActionNode* ActionNode::StorePosition(int reg, RegExpNode* on_success) { + return on_success->zone()->New(STORE_POSITION, on_success, reg); +} + +ActionNode* ActionNode::RestorePosition(int reg, RegExpNode* on_success) { + return on_success->zone()->New(RESTORE_POSITION, on_success, reg); +} + +ActionNode* ActionNode::ClearCaptures(Interval range, RegExpNode* on_success) { + return on_success->zone()->New(CLEAR_CAPTURES, on_success, + range.from(), range.to()); +} + +ActionNode* ActionNode::BeginPositiveSubmatch(int stack_reg, + int position_reg, + RegExpNode* body, + ActionNode* success_node) { + ActionNode* result = + body->zone()->New(BEGIN_POSITIVE_SUBMATCH, body); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + result->data_.u_submatch.success_node = success_node; + return result; +} + +ActionNode* ActionNode::BeginNegativeSubmatch(int stack_reg, + int position_reg, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New(BEGIN_NEGATIVE_SUBMATCH, on_success); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + return result; +} + +ActionNode* ActionNode::PositiveSubmatchSuccess(int stack_reg, + int position_reg, + int clear_register_count, + int clear_register_from, + RegExpNode* on_success) { + ActionNode* result = on_success->zone()->New( + POSITIVE_SUBMATCH_SUCCESS, on_success); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + result->data_.u_submatch.clear_register_count = clear_register_count; + result->data_.u_submatch.clear_register_from = clear_register_from; + return result; +} + +ActionNode* ActionNode::EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New(EMPTY_MATCH_CHECK, on_success); + result->data_.u_empty_match_check.start_register = start_register; + result->data_.u_empty_match_check.repetition_register = repetition_register; + result->data_.u_empty_match_check.repetition_limit = repetition_limit; + return result; +} + +ActionNode* ActionNode::ModifyFlags(RegExpFlags flags, RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New(MODIFY_FLAGS, on_success); + result->data_.u_modify_flags.flags = flags; + return result; +} + +ActionNode* ActionNode::EatsAtLeast(int characters, RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New(EATS_AT_LEAST, on_success); + result->data_.u_eats_at_least.characters = characters; + return result; +} + +#define DEFINE_ACCEPT(Type) \ + void Type##Node::Accept(NodeVisitor* visitor) { \ + visitor->Visit##Type(this); \ + } +FOR_EACH_NODE_TYPE(DEFINE_ACCEPT) +#undef DEFINE_ACCEPT + +// ------------------------------------------------------------------- +// Emit code. + +void ChoiceNode::GenerateGuard(RegExpMacroAssembler* macro_assembler, + Guard* guard, + Trace* trace) { + switch (guard->op()) { + case Guard::LT: + DCHECK(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterGE(guard->reg(), guard->value(), + trace->backtrack()); + break; + case Guard::GEQ: + DCHECK(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterLT(guard->reg(), guard->value(), + trace->backtrack()); + break; + } +} + +namespace { + +#ifdef DEBUG +bool ContainsOnlyUtf16CodeUnits(unibrow::uchar* chars, int length) { + static_assert(sizeof(unibrow::uchar) == 4); + for (int i = 0; i < length; i++) { + if (chars[i] > String::kMaxUtf16CodeUnit) return false; + } + return true; +} +#endif // DEBUG + +// Returns the number of characters in the equivalence class, omitting those +// that cannot occur in the source string because it is Latin1. This is called +// both for unicode modes /ui and /vi, and also for legacy case independent +// mode /i. In the case of Unicode modes we handled surrogate pair expansions +// earlier so at this point it's all about single-code-unit expansions. +int GetCaseIndependentLetters(Isolate* isolate, + uint16_t character, + RegExpCompiler* compiler, + unibrow::uchar* letters, + int letter_length) { + bool one_byte_subject = compiler->one_byte(); + bool unicode = IsEitherUnicode(compiler->flags()); + static const uint16_t kMaxAscii = 0x7f; + if (!unicode && character <= kMaxAscii) { + // Fast case for common characters. + uint16_t upper = character & ~0x20; + if ('A' <= upper && upper <= 'Z') { + letters[0] = upper; + letters[1] = upper | 0x20; + return 2; + } + letters[0] = character; + return 1; + } +#ifdef V8_INTL_SUPPORT + + if (!unicode && RegExpCaseFolding::IgnoreSet().contains(character)) { + if (one_byte_subject && character > String::kMaxOneByteCharCode) { + // This function promises not to return a character that is impossible + // for the subject encoding. + return 0; + } + letters[0] = character; + DCHECK(ContainsOnlyUtf16CodeUnits(letters, 1)); + return 1; + } + bool in_special_add_set = + RegExpCaseFolding::SpecialAddSet().contains(character); + + icu::UnicodeSet set; + set.add(character); + set = set.closeOver(unicode ? USET_SIMPLE_CASE_INSENSITIVE + : USET_CASE_INSENSITIVE); + + UChar32 canon = 0; + if (in_special_add_set && !unicode) { + canon = RegExpCaseFolding::Canonicalize(character); + } + + int32_t range_count = set.getRangeCount(); + int items = 0; + for (int32_t i = 0; i < range_count; i++) { + UChar32 start = set.getRangeStart(i); + UChar32 end = set.getRangeEnd(i); + CHECK(end - start + items <= letter_length); + for (UChar32 cu = start; cu <= end; cu++) { + if (one_byte_subject && cu > String::kMaxOneByteCharCode) continue; + if (!unicode && in_special_add_set && + RegExpCaseFolding::Canonicalize(cu) != canon) { + continue; + } + letters[items++] = static_cast(cu); + } + } + DCHECK(ContainsOnlyUtf16CodeUnits(letters, items)); + return items; +#else + int length = + isolate->jsregexp_uncanonicalize()->get(character, '\0', letters); + // Unibrow returns 0 or 1 for characters where case independence is + // trivial. + if (length == 0) { + letters[0] = character; + length = 1; + } + + if (one_byte_subject) { + int new_length = 0; + for (int i = 0; i < length; i++) { + if (letters[i] <= String::kMaxOneByteCharCode) { + letters[new_length++] = letters[i]; + } + } + length = new_length; + } + + DCHECK(ContainsOnlyUtf16CodeUnits(letters, length)); + return length; +#endif // V8_INTL_SUPPORT +} + +inline bool EmitSimpleCharacter(Isolate* isolate, + RegExpCompiler* compiler, + uint16_t c, + V8Label* on_failure, + int cp_offset, + bool check, + bool preloaded) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + bool bound_checked = false; + if (!preloaded) { + assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + bound_checked = true; + } + assembler->CheckNotCharacter(c, on_failure); + return bound_checked; +} + +// Only emits non-letters (things that don't have case). Only used for case +// independent matches. +inline bool EmitAtomNonLetter(Isolate* isolate, + RegExpCompiler* compiler, + uint16_t c, + V8Label* on_failure, + int cp_offset, + bool check, + bool preloaded) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool one_byte = compiler->one_byte(); + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(isolate, c, compiler, chars, 4); + if (length < 1) { + // This can't match. Must be an one-byte subject and a non-one-byte + // character. We do not need to do anything since the one-byte pass + // already handled this. + CHECK(one_byte); + return false; // Bounds not checked. + } + bool checked = false; + // We handle the length > 1 case in a later pass. + if (length == 1) { + // GetCaseIndependentLetters promises not to return characters that can't + // match because of the subject encoding. This case is already handled by + // the one-byte pass. + CHECK_IMPLIES(one_byte, chars[0] <= String::kMaxOneByteCharCodeU); + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + checked = check; + } + macro_assembler->CheckNotCharacter(chars[0], on_failure); + } + return checked; +} + +bool ShortCutEmitCharacterPair(RegExpMacroAssembler* macro_assembler, + bool one_byte, + uint16_t c1, + uint16_t c2, + V8Label* on_failure) { + const uint32_t char_mask = CharMask(one_byte); + uint16_t exor = c1 ^ c2; + // Check whether exor has only one bit set. + if (((exor - 1) & exor) == 0) { + // If c1 and c2 differ only by one bit. + // Ecma262UnCanonicalize always gives the highest number last. + DCHECK(c2 > c1); + uint16_t mask = char_mask ^ exor; + macro_assembler->CheckNotCharacterAfterAnd(c1, mask, on_failure); + return true; + } + DCHECK(c2 > c1); + uint16_t diff = c2 - c1; + if (((diff - 1) & diff) == 0 && c1 >= diff) { + // If the characters differ by 2^n but don't differ by one bit then + // subtract the difference from the found character, then do the or + // trick. We avoid the theoretical case where negative numbers are + // involved in order to simplify code generation. + uint16_t mask = char_mask ^ diff; + macro_assembler->CheckNotCharacterAfterMinusAnd(c1 - diff, diff, mask, + on_failure); + return true; + } + return false; +} + +// Only emits letters (things that have case). Only used for case independent +// matches. +inline bool EmitAtomLetter(Isolate* isolate, + RegExpCompiler* compiler, + uint16_t c, + V8Label* on_failure, + int cp_offset, + bool check, + bool preloaded) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool one_byte = compiler->one_byte(); + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(isolate, c, compiler, chars, 4); + // The 0 and 1 case are handled by earlier passes. + if (length <= 1) return false; + // We may not need to check against the end of the input string + // if this character lies before a character that matched. + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + } + V8Label ok; + switch (length) { + case 2: { + if (ShortCutEmitCharacterPair(macro_assembler, one_byte, chars[0], + chars[1], on_failure)) { + } else { + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckNotCharacter(chars[1], on_failure); + macro_assembler->Bind(&ok); + } + break; + } + case 4: + macro_assembler->CheckCharacter(chars[3], &ok); + [[fallthrough]]; + case 3: + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckCharacter(chars[1], &ok); + macro_assembler->CheckNotCharacter(chars[2], on_failure); + macro_assembler->Bind(&ok); + break; + default: + UNREACHABLE(); + } + return true; +} + +void EmitBoundaryTest(RegExpMacroAssembler* masm, + int border, + V8Label* fall_through, + V8Label* above_or_equal, + V8Label* below) { + if (below != fall_through) { + masm->CheckCharacterLT(border, below); + if (above_or_equal != fall_through) masm->GoTo(above_or_equal); + } else { + masm->CheckCharacterGT(border - 1, above_or_equal); + } +} + +void EmitDoubleBoundaryTest(RegExpMacroAssembler* masm, + int first, + int last, + V8Label* fall_through, + V8Label* in_range, + V8Label* out_of_range) { + if (in_range == fall_through) { + if (first == last) { + masm->CheckNotCharacter(first, out_of_range); + } else { + masm->CheckCharacterNotInRange(first, last, out_of_range); + } + } else { + if (first == last) { + masm->CheckCharacter(first, in_range); + } else { + masm->CheckCharacterInRange(first, last, in_range); + } + if (out_of_range != fall_through) masm->GoTo(out_of_range); + } +} + +// even_label is for ranges[i] to ranges[i + 1] where i - start_index is even. +// odd_label is for ranges[i] to ranges[i + 1] where i - start_index is odd. +void EmitUseLookupTable(RegExpMacroAssembler* masm, + ZoneList* ranges, + uint32_t start_index, + uint32_t end_index, + uint32_t min_char, + V8Label* fall_through, + V8Label* even_label, + V8Label* odd_label) { + static const uint32_t kSize = RegExpMacroAssembler::kTableSize; + static const uint32_t kMask = RegExpMacroAssembler::kTableMask; + + uint32_t base = (min_char & ~kMask); + USE(base); + + // Assert that everything is on one kTableSize page. + for (uint32_t i = start_index; i <= end_index; i++) { + DCHECK_EQ(ranges->at(i) & ~kMask, base); + } + DCHECK(start_index == 0 || (ranges->at(start_index - 1) & ~kMask) <= base); + + char templ[kSize]; + V8Label* on_bit_set; + V8Label* on_bit_clear; + int bit; + if (even_label == fall_through) { + on_bit_set = odd_label; + on_bit_clear = even_label; + bit = 1; + } else { + on_bit_set = even_label; + on_bit_clear = odd_label; + bit = 0; + } + for (uint32_t i = 0; i < (ranges->at(start_index) & kMask) && i < kSize; + i++) { + templ[i] = bit; + } + uint32_t j = 0; + bit ^= 1; + for (uint32_t i = start_index; i < end_index; i++) { + for (j = (ranges->at(i) & kMask); j < (ranges->at(i + 1) & kMask); j++) { + templ[j] = bit; + } + bit ^= 1; + } + for (uint32_t i = j; i < kSize; i++) { + templ[i] = bit; + } + // TODO(erikcorry): Cache these. + TypedData& ba = + TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, kSize)); + for (uint32_t i = 0; i < kSize; i++) { + ba.SetUint8(i, templ[i]); + } + masm->CheckBitInTable(ba, on_bit_set); + if (on_bit_clear != fall_through) masm->GoTo(on_bit_clear); +} + +void CutOutRange(RegExpMacroAssembler* masm, + ZoneList* ranges, + uint32_t start_index, + uint32_t end_index, + uint32_t cut_index, + V8Label* even_label, + V8Label* odd_label) { + bool odd = (((cut_index - start_index) & 1) == 1); + V8Label* in_range_label = odd ? odd_label : even_label; + V8Label dummy; + EmitDoubleBoundaryTest(masm, ranges->at(cut_index), + ranges->at(cut_index + 1) - 1, &dummy, in_range_label, + &dummy); + DCHECK(!dummy.is_linked()); + // Cut out the single range by rewriting the array. This creates a new + // range that is a merger of the two ranges on either side of the one we + // are cutting out. The oddity of the labels is preserved. + for (uint32_t j = cut_index; j > start_index; j--) { + ranges->at(j) = ranges->at(j - 1); + } + for (uint32_t j = cut_index + 1; j < end_index; j++) { + ranges->at(j) = ranges->at(j + 1); + } +} + +// Unicode case. Split the search space into kSize spaces that are handled +// with recursion. +void SplitSearchSpace(ZoneList* ranges, + uint32_t start_index, + uint32_t end_index, + uint32_t* new_start_index, + uint32_t* new_end_index, + uint32_t* border) { + static const uint32_t kSize = RegExpMacroAssembler::kTableSize; + static const uint32_t kMask = RegExpMacroAssembler::kTableMask; + + uint32_t first = ranges->at(start_index); + uint32_t last = ranges->at(end_index) - 1; + + *new_start_index = start_index; + *border = (ranges->at(start_index) & ~kMask) + kSize; + while (*new_start_index < end_index) { + if (ranges->at(*new_start_index) > *border) break; + (*new_start_index)++; + } + // new_start_index is the index of the first edge that is beyond the + // current kSize space. + + // For very large search spaces we do a binary chop search of the non-Latin1 + // space instead of just going to the end of the current kSize space. The + // heuristics are complicated a little by the fact that any 128-character + // encoding space can be quickly tested with a table lookup, so we don't + // wish to do binary chop search at a smaller granularity than that. A + // 128-character space can take up a lot of space in the ranges array if, + // for example, we only want to match every second character (eg. the lower + // case characters on some Unicode pages). + uint32_t binary_chop_index = (end_index + start_index) / 2; + // The first test ensures that we get to the code that handles the Latin1 + // range with a single not-taken branch, speeding up this important + // character range (even non-Latin1 charset-based text has spaces and + // punctuation). + if (*border - 1 > String::kMaxOneByteCharCode && // Latin1 case. + end_index - start_index > (*new_start_index - start_index) * 2 && + last - first > kSize * 2 && binary_chop_index > *new_start_index && + ranges->at(binary_chop_index) >= first + 2 * kSize) { + uint32_t scan_forward_for_section_border = binary_chop_index; + uint32_t new_border = (ranges->at(binary_chop_index) | kMask) + 1; + + while (scan_forward_for_section_border < end_index) { + if (ranges->at(scan_forward_for_section_border) > new_border) { + *new_start_index = scan_forward_for_section_border; + *border = new_border; + break; + } + scan_forward_for_section_border++; + } + } + + DCHECK(*new_start_index > start_index); + *new_end_index = *new_start_index - 1; + if (ranges->at(*new_end_index) == *border) { + (*new_end_index)--; + } + if (*border >= ranges->at(end_index)) { + *border = ranges->at(end_index); + *new_start_index = end_index; // Won't be used. + *new_end_index = end_index - 1; + } +} + +// Gets a series of segment boundaries representing a character class. If the +// character is in the range between an even and an odd boundary (counting from +// start_index) then go to even_label, otherwise go to odd_label. We already +// know that the character is in the range of min_char to max_char inclusive. +// Either label can be nullptr indicating backtracking. Either label can also +// be equal to the fall_through label. +void GenerateBranches(RegExpMacroAssembler* masm, + ZoneList* ranges, + uint32_t start_index, + uint32_t end_index, + uint32_t min_char, + uint32_t max_char, + V8Label* fall_through, + V8Label* even_label, + V8Label* odd_label) { + DCHECK_LE(min_char, String::kMaxUtf16CodeUnit); + DCHECK_LE(max_char, String::kMaxUtf16CodeUnit); + + uint32_t first = ranges->at(start_index); + uint32_t last = ranges->at(end_index) - 1; + + DCHECK_LT(min_char, first); + + // Just need to test if the character is before or on-or-after + // a particular character. + if (start_index == end_index) { + EmitBoundaryTest(masm, first, fall_through, even_label, odd_label); + return; + } + + // Another almost trivial case: There is one interval in the middle that is + // different from the end intervals. + if (start_index + 1 == end_index) { + EmitDoubleBoundaryTest(masm, first, last, fall_through, even_label, + odd_label); + return; + } + + // It's not worth using table lookup if there are very few intervals in the + // character class. + if (end_index - start_index <= 6) { + // It is faster to test for individual characters, so we look for those + // first, then try arbitrary ranges in the second round. + static uint32_t kNoCutIndex = -1; + uint32_t cut = kNoCutIndex; + for (uint32_t i = start_index; i < end_index; i++) { + if (ranges->at(i) == ranges->at(i + 1) - 1) { + cut = i; + break; + } + } + if (cut == kNoCutIndex) cut = start_index; + CutOutRange(masm, ranges, start_index, end_index, cut, even_label, + odd_label); + DCHECK_GE(end_index - start_index, 2); + GenerateBranches(masm, ranges, start_index + 1, end_index - 1, min_char, + max_char, fall_through, even_label, odd_label); + return; + } + + // If there are a lot of intervals in the regexp, then we will use tables to + // determine whether the character is inside or outside the character class. + static const int kBits = RegExpMacroAssembler::kTableSizeBits; + + if ((max_char >> kBits) == (min_char >> kBits)) { + EmitUseLookupTable(masm, ranges, start_index, end_index, min_char, + fall_through, even_label, odd_label); + return; + } + + if ((min_char >> kBits) != first >> kBits) { + masm->CheckCharacterLT(first, odd_label); + GenerateBranches(masm, ranges, start_index + 1, end_index, first, max_char, + fall_through, odd_label, even_label); + return; + } + + uint32_t new_start_index = 0; + uint32_t new_end_index = 0; + uint32_t border = 0; + + SplitSearchSpace(ranges, start_index, end_index, &new_start_index, + &new_end_index, &border); + + V8Label handle_rest; + V8Label* above = &handle_rest; + if (border == last + 1) { + // We didn't find any section that started after the limit, so everything + // above the border is one of the terminal labels. + above = (end_index & 1) != (start_index & 1) ? odd_label : even_label; + DCHECK(new_end_index == end_index - 1); + } + + DCHECK_LE(start_index, new_end_index); + DCHECK_LE(new_start_index, end_index); + DCHECK_LT(start_index, new_start_index); + DCHECK_LT(new_end_index, end_index); + DCHECK(new_end_index + 1 == new_start_index || + (new_end_index + 2 == new_start_index && + border == ranges->at(new_end_index + 1))); + DCHECK_LT(min_char, border - 1); + DCHECK_LT(border, max_char); + DCHECK_LT(ranges->at(new_end_index), border); + DCHECK(border < ranges->at(new_start_index) || + (border == ranges->at(new_start_index) && + new_start_index == end_index && new_end_index == end_index - 1 && + border == last + 1)); + DCHECK(new_start_index == 0 || border >= ranges->at(new_start_index - 1)); + + masm->CheckCharacterGT(border - 1, above); + V8Label dummy; + GenerateBranches(masm, ranges, start_index, new_end_index, min_char, + border - 1, &dummy, even_label, odd_label); + if (handle_rest.is_linked()) { + masm->Bind(&handle_rest); + bool flip = (new_start_index & 1) != (start_index & 1); + GenerateBranches(masm, ranges, new_start_index, end_index, border, max_char, + &dummy, flip ? odd_label : even_label, + flip ? even_label : odd_label); + } +} + +void EmitClassRanges(RegExpMacroAssembler* macro_assembler, + RegExpClassRanges* cr, + bool one_byte, + V8Label* on_failure, + int cp_offset, + bool check_offset, + bool preloaded, + Zone* zone) { + ZoneList* ranges = cr->ranges(zone); + CharacterRange::Canonicalize(ranges); + + // Now that all processing (like case-insensitivity) is done, clamp the + // ranges to the set of ranges that may actually occur in the subject string. + if (one_byte) CharacterRange::ClampToOneByte(ranges); + + const int ranges_length = ranges->length(); + if (ranges_length == 0) { + if (!cr->is_negated()) { + macro_assembler->GoTo(on_failure); + } + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + return; + } + + const uint32_t max_char = MaxCodeUnit(one_byte); + if (ranges_length == 1 && ranges->at(0).IsEverything(max_char)) { + if (cr->is_negated()) { + macro_assembler->GoTo(on_failure); + } else { + // This is a common case hit by non-anchored expressions. + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + } + return; + } + + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check_offset); + } + + if (cr->is_standard(zone) && + macro_assembler->CanOptimizeSpecialClassRanges(cr->standard_type())) { + macro_assembler->CheckSpecialClassRanges(cr->standard_type(), on_failure); + return; + } + + static constexpr int kMaxRangesForInlineBranchGeneration = 16; + if (ranges_length > kMaxRangesForInlineBranchGeneration) { + // For large range sets, emit a more compact instruction sequence to avoid + // a potentially problematic increase in code size. + // Note the flipped logic below (we check InRange if negated, NotInRange if + // not negated); this is necessary since the method falls through on + // failure whereas we want to fall through on success. + if (cr->is_negated()) { + if (macro_assembler->CheckCharacterInRangeArray(ranges, on_failure)) { + return; + } + } else { + if (macro_assembler->CheckCharacterNotInRangeArray(ranges, on_failure)) { + return; + } + } + } + + // Generate a flat list of range boundaries for consumption by + // GenerateBranches. See the comment on that function for how the list should + // be structured + ZoneList* range_boundaries = + zone->New>(ranges_length * 2, zone); + + bool zeroth_entry_is_failure = !cr->is_negated(); + + for (int i = 0; i < ranges_length; i++) { + CharacterRange& range = ranges->at(i); + if (range.from() == 0) { + DCHECK_EQ(i, 0); + zeroth_entry_is_failure = !zeroth_entry_is_failure; + } else { + range_boundaries->Add(range.from(), zone); + } + // `+ 1` to convert from inclusive to exclusive `to`. + // [from, to] == [from, to+1[. + range_boundaries->Add(range.to() + 1, zone); + } + int end_index = range_boundaries->length() - 1; + if (range_boundaries->at(end_index) > max_char) { + end_index--; + } + + V8Label fall_through; + GenerateBranches(macro_assembler, range_boundaries, + 0, // start_index. + end_index, + 0, // min_char. + max_char, &fall_through, + zeroth_entry_is_failure ? &fall_through : on_failure, + zeroth_entry_is_failure ? on_failure : &fall_through); + macro_assembler->Bind(&fall_through); +} + +} // namespace + +RegExpNode::~RegExpNode() = default; + +RegExpNode::LimitResult RegExpNode::LimitVersions(RegExpCompiler* compiler, + Trace* trace) { + // If we are generating a fixed length loop then don't stop and don't reuse + // code. + if (trace->special_loop_state() != nullptr) { + return CONTINUE; + } + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->is_trivial()) { + if (label_.is_bound() || on_work_list() || !KeepRecursing(compiler)) { + TRACE("* Limit Versions: Generic version available"); + // If a generic version is already scheduled to be generated or we have + // recursed too deeply then just generate a jump to that code. + macro_assembler->GoTo(&label_); + // This will queue it up for generation of a generic version if it hasn't + // already been queued. + compiler->AddWork(this); + return DONE; + } + // Generate generic version of the node and bind the label for later use. + macro_assembler->Bind(&label_); + return CONTINUE; + } + + // We are being asked to make a non-generic version. Keep track of how many + // non-generic versions we generate so as not to overdo it. + trace_count_++; + if (KeepRecursing(compiler) && compiler->optimize() && + trace_count_ < kMaxCopiesCodeGenerated) { + return CONTINUE; + } + + // If we get here code has been generated for this node too many times or + // recursion is too deep. Time to switch to a generic version. The code for + // generic versions above can handle deep recursion properly. + TRACE("* Limit Versions: Switch to generic version"); + bool was_limiting = compiler->limiting_recursion(); + compiler->set_limiting_recursion(true); + trace->Flush(compiler, this); + compiler->set_limiting_recursion(was_limiting); + return DONE; +} + +bool RegExpNode::KeepRecursing(RegExpCompiler* compiler) { + return !compiler->limiting_recursion() && + compiler->recursion_depth() <= RegExpCompiler::kMaxRecursion; +} + +void ActionNode::FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + std::optional old_flags; + if (action_type_ == MODIFY_FLAGS) { + // It is not guaranteed that we hit the resetting modify flags node, due to + // recursion budget limitation for filling in BMInfo. Therefore we reset the + // flags manually to the previous state after recursing. + old_flags = bm->compiler()->flags(); + bm->compiler()->set_flags(flags()); + } + if (action_type_ == BEGIN_POSITIVE_SUBMATCH) { + // We use the node after the lookaround to fill in the eats_at_least info + // so we have to use the same node to fill in the Boyer-Moore info. + success_node()->on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, + not_at_start); + } else if (action_type_ != POSITIVE_SUBMATCH_SUCCESS) { + // We don't use the node after a positive submatch success because it + // rewinds the position. Since we returned 0 as the eats_at_least value for + // this node, we don't need to fill in any data. + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + } + SaveBMInfo(bm, not_at_start, offset); + if (old_flags.has_value()) { + bm->compiler()->set_flags(*old_flags); + } +} + +void ActionNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start, + int budget) { + if (action_type_ == BEGIN_POSITIVE_SUBMATCH) { + // We use the node after the lookaround to fill in the eats_at_least info + // so we have to use the same node to fill in the QuickCheck info. + success_node()->on_success()->GetQuickCheckDetails( + details, compiler, filled_in, not_at_start, budget - 1); + } else if (action_type() != POSITIVE_SUBMATCH_SUCCESS) { + // We don't use the node after a positive submatch success because it + // rewinds the position. Since we returned 0 as the eats_at_least value + // for this node, we don't need to fill in any data. + std::optional old_flags; + if (action_type() == MODIFY_FLAGS) { + // It is not guaranteed that we hit the resetting modify flags node, as + // GetQuickCheckDetails doesn't travers the whole graph. Therefore we + // reset the flags manually to the previous state after recursing. + old_flags = compiler->flags(); + compiler->set_flags(flags()); + } + on_success()->GetQuickCheckDetails(details, compiler, filled_in, + not_at_start, budget - 1); + if (old_flags.has_value()) { + compiler->set_flags(*old_flags); + } + } +} + +void AssertionNode::FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + // Match the behaviour of EatsAtLeast on this node. + if (assertion_type() == AT_START && not_at_start) return; + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +void NegativeLookaroundChoiceNode::GetQuickCheckDetails( + QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start, + int budget) { + RegExpNode* node = continue_node(); + return node->GetQuickCheckDetails(details, compiler, filled_in, not_at_start, + budget - 1); +} + +namespace { + +// Takes the left-most 1-bit and smears it out, setting all bits to its right. +inline uint32_t SmearBitsRight(uint32_t v) { + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return v; +} + +} // namespace + +bool QuickCheckDetails::Rationalize(bool asc) { + bool found_useful_op = false; + const uint32_t char_mask = CharMask(asc); + mask_ = 0; + value_ = 0; + int char_shift = 0; + for (int i = 0; i < characters_; i++) { + Position* pos = &positions_[i]; + if ((pos->mask & String::kMaxOneByteCharCode) != 0) { + found_useful_op = true; + } + mask_ |= (pos->mask & char_mask) << char_shift; + value_ |= (pos->value & char_mask) << char_shift; + char_shift += asc ? 8 : 16; + } + return found_useful_op; +} + +uint32_t RegExpNode::EatsAtLeast(bool not_at_start) { + return not_at_start ? eats_at_least_.from_not_start + : eats_at_least_.from_possibly_start; +} + +bool RegExpNode::EmitQuickCheck(RegExpCompiler* compiler, + Trace* bounds_check_trace, + Trace* trace, + bool preload_has_checked_bounds, + V8Label* on_possible_success, + QuickCheckDetails* details, + bool fall_through_on_failure, + ChoiceNode* predecessor) { + DCHECK_NOT_NULL(predecessor); + if (details->characters() == 0) { + TRACE("* No QuickCheck characters found"); + return false; + } + GetQuickCheckDetails(details, compiler, 0, + trace->at_start() == Trace::FALSE_VALUE, + kRecursionBudget); + if (details->cannot_match()) { + TRACE("* QuickCheck cannot match"); + return false; + } + if (!details->Rationalize(compiler->one_byte())) { + TRACE("* QuickCheck didn't find a useful operation"); + return false; + } + DCHECK(details->characters() == 1 || + compiler->macro_assembler()->CanReadUnaligned()); + uint32_t mask = details->mask(); + uint32_t value = details->value(); + + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + TRACE("* Emit QuickCheck"); + if (trace->characters_preloaded() != details->characters()) { + DCHECK(trace->cp_offset() == bounds_check_trace->cp_offset()); + // The bounds check is performed using the minimum number of characters + // any choice would eat, so if the bounds check fails, then none of the + // choices can succeed, so we can just immediately backtrack, rather + // than go to the next choice. The number of characters preloaded may be + // less than the number used for the bounds check. + int eats_at_least = predecessor->EatsAtLeast( + bounds_check_trace->at_start() == Trace::FALSE_VALUE); + DCHECK_GE(eats_at_least, details->characters()); + assembler->LoadCurrentCharacter( + trace->cp_offset(), bounds_check_trace->backtrack(), + !preload_has_checked_bounds, details->characters(), eats_at_least); + } + + bool need_mask = true; + + if (details->characters() == 1) { + // If number of characters preloaded is 1 then we used a byte or 16 bit + // load so the value is already masked down. + const uint32_t char_mask = CharMask(compiler->one_byte()); + if ((mask & char_mask) == char_mask) need_mask = false; + mask &= char_mask; + } else { + // For 2-character preloads in one-byte mode or 1-character preloads in + // two-byte mode we also use a 16 bit load with zero extend. + static const uint32_t kTwoByteMask = 0xFFFF; + static const uint32_t kFourByteMask = 0xFFFFFFFF; + if (details->characters() == 2 && compiler->one_byte()) { + if ((mask & kTwoByteMask) == kTwoByteMask) need_mask = false; + } else if (details->characters() == 1 && !compiler->one_byte()) { + if ((mask & kTwoByteMask) == kTwoByteMask) need_mask = false; + } else { + if (mask == kFourByteMask) need_mask = false; + } + } + + if (fall_through_on_failure) { + if (need_mask) { + assembler->CheckCharacterAfterAnd(value, mask, on_possible_success); + } else { + assembler->CheckCharacter(value, on_possible_success); + } + } else { + if (need_mask) { + assembler->CheckNotCharacterAfterAnd(value, mask, trace->backtrack()); + } else { + assembler->CheckNotCharacter(value, trace->backtrack()); + } + } + return true; +} + +// Here is the meat of GetQuickCheckDetails (see also the comment on the +// super-class in the .h file). +// +// We iterate along the text object, building up for each character a +// mask and value that can be used to test for a quick failure to match. +// The masks and values for the positions will be combined into a single +// machine word for the current character width in order to be used in +// generating a quick check. +void TextNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) { + // Do not collect any quick check details if the text node reads backward, + // since it reads in the opposite direction than we use for quick checks. + if (read_backward()) return; + Isolate* isolate = compiler->macro_assembler()->isolate(); + DCHECK(characters_filled_in < details->characters()); + int characters = details->characters(); + const uint32_t char_mask = CharMask(compiler->one_byte()); + for (int k = 0; k < elements()->length(); k++) { + TextElement elm = elements()->at(k); + if (elm.text_type() == TextElement::ATOM) { + base::Vector quarks = elm.atom()->data(); + for (int i = 0; i < characters && i < quarks.length(); i++) { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + uint16_t c = quarks[i]; + if (IsIgnoreCase(compiler->flags())) { + unibrow::uchar chars[4]; + int length = + GetCaseIndependentLetters(isolate, c, compiler, chars, 4); + if (length == 0) { + // This can happen because all case variants are non-Latin1, but we + // know the input is Latin1. + details->set_cannot_match_from(characters_filled_in); + pos->determines_perfectly = false; + return; + } + if (length == 1) { + // This letter has no case equivalents, so it's nice and simple + // and the mask-compare will determine definitely whether we have + // a match at this character position. + pos->mask = char_mask; + pos->value = chars[0]; + pos->determines_perfectly = true; + } else { + uint32_t common_bits = char_mask; + uint32_t bits = chars[0]; + for (int j = 1; j < length; j++) { + uint32_t differing_bits = ((chars[j] & common_bits) ^ bits); + common_bits ^= differing_bits; + bits &= common_bits; + } + // If length is 2 and common bits has only one zero in it then + // our mask and compare instruction will determine definitely + // whether we have a match at this character position. Otherwise + // it can only be an approximate check. + uint32_t one_zero = (common_bits | ~char_mask); + if (length == 2 && ((~one_zero) & ((~one_zero) - 1)) == 0) { + pos->determines_perfectly = true; + } + pos->mask = common_bits; + pos->value = bits; + } + } else { + // Don't ignore case. Nice simple case where the mask-compare will + // determine definitely whether we have a match at this character + // position. + if (c > char_mask) { + details->set_cannot_match_from(characters_filled_in); + pos->determines_perfectly = false; + return; + } + pos->mask = char_mask; + pos->value = c; + pos->determines_perfectly = true; + } + characters_filled_in++; + DCHECK(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) { + return; + } + } + } else { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + RegExpClassRanges* tree = elm.class_ranges(); + ZoneList* ranges = tree->ranges(zone()); + if (tree->is_negated() || ranges->is_empty()) { + // A quick check uses multi-character mask and compare. There is no + // useful way to incorporate a negative char class into this scheme + // so we just conservatively create a mask and value that will always + // succeed. + // Likewise for empty ranges (empty ranges can occur e.g. when + // compiling for one-byte subjects and impossible (non-one-byte) ranges + // have been removed). + pos->mask = 0; + pos->value = 0; + } else { + int first_range = 0; + while (ranges->at(first_range).from() > char_mask) { + first_range++; + if (first_range == ranges->length()) { + details->set_cannot_match_from(characters_filled_in); + pos->determines_perfectly = false; + return; + } + } + CharacterRange range = ranges->at(first_range); + const uint32_t first_from = range.from(); + const uint32_t first_to = + (range.to() > char_mask) ? char_mask : range.to(); + const uint32_t differing_bits = (first_from ^ first_to); + // A mask and compare is only perfect if the differing bits form a + // number like 00011111 with one single block of trailing 1s. + if ((differing_bits & (differing_bits + 1)) == 0 && + first_from + differing_bits == first_to) { + pos->determines_perfectly = true; + } + uint32_t common_bits = ~SmearBitsRight(differing_bits); + uint32_t bits = (first_from & common_bits); + for (int i = first_range + 1; i < ranges->length(); i++) { + range = ranges->at(i); + const uint32_t from = range.from(); + if (from > char_mask) continue; + const uint32_t to = (range.to() > char_mask) ? char_mask : range.to(); + // Here we are combining more ranges into the mask and compare + // value. With each new range the mask becomes more sparse and + // so the chances of a false positive rise. A character class + // with multiple ranges is assumed never to be equivalent to a + // mask and compare operation. + pos->determines_perfectly = false; + uint32_t new_common_bits = (from ^ to); + new_common_bits = ~SmearBitsRight(new_common_bits); + common_bits &= new_common_bits; + bits &= new_common_bits; + uint32_t new_differing_bits = (from & common_bits) ^ bits; + common_bits ^= new_differing_bits; + bits &= common_bits; + } + pos->mask = common_bits; + pos->value = bits; + } + characters_filled_in++; + DCHECK(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) return; + } + } + DCHECK(characters_filled_in != details->characters()); + if (!details->cannot_match()) { + on_success()->GetQuickCheckDetails(details, compiler, characters_filled_in, + true, budget - 1); + } +} + +void QuickCheckDetails::Clear() { + for (int i = 0; i < characters_; i++) { + positions_[i].Clear(); + } + characters_ = 0; +} + +void QuickCheckDetails::Advance(int by, bool one_byte) { + if (by >= characters_ || by < 0) { + DCHECK_IMPLIES(by < 0, characters_ == 0); + Clear(); + return; + } + DCHECK_LE(characters_ - by, 4); + DCHECK_LE(characters_, 4); + for (int i = 0; i < characters_ - by; i++) { + positions_[i] = positions_[by + i]; + } + for (int i = characters_ - by; i < characters_; i++) { + positions_[i].Clear(); + } + characters_ -= by; + // We could change mask_ and value_ here but we would never advance unless + // they had already been used in a check and they won't be used again because + // it would gain us nothing. So there's no point. +} + +void QuickCheckDetails::Merge(QuickCheckDetails* other, int from_index) { + DCHECK(characters_ == other->characters_); + for (int i = from_index; i < characters_; i++) { + QuickCheckDetails::Position* pos = positions(i); + QuickCheckDetails::Position* other_pos = other->positions(i); + if (pos->cannot_match) { + *pos = *other_pos; + } else if (!other_pos->cannot_match) { + if (pos->mask != other_pos->mask || pos->value != other_pos->value || + !other_pos->determines_perfectly) { + // Our mask-compare operation will be approximate unless we have the + // exact same operation on both sides of the alternation. + pos->determines_perfectly = false; + } + pos->mask &= other_pos->mask; + pos->value &= pos->mask; + other_pos->value &= pos->mask; + uint32_t differing_bits = (pos->value ^ other_pos->value); + pos->mask &= ~differing_bits; + pos->value &= pos->mask; + } + } +} + +class VisitMarker { + public: + explicit VisitMarker(NodeInfo* info) : info_(info) { + DCHECK(!info->visited); + info->visited = true; + } + ~VisitMarker() { info_->visited = false; } + + private: + NodeInfo* info_; +}; + +// We need to check for the following characters: 0x39C 0x3BC 0x178. +bool RangeContainsLatin1Equivalents(CharacterRange range) { + // TODO(dcarney): this could be a lot more efficient. + return range.Contains(0x039C) || range.Contains(0x03BC) || + range.Contains(0x0178); +} + +namespace { + +bool RangesContainLatin1Equivalents(ZoneList* ranges) { + for (int i = 0; i < ranges->length(); i++) { + // TODO(dcarney): this could be a lot more efficient. + if (RangeContainsLatin1Equivalents(ranges->at(i))) return true; + } + return false; +} + +} // namespace + +bool TextNode::CanMatchLatin1(RegExpCompiler* compiler) { + RegExpFlags flags = compiler->flags(); + int element_count = elements()->length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()->at(i); + if (elm.text_type() == TextElement::ATOM) { + base::Vector quarks = elm.atom()->data(); + for (int j = 0; j < quarks.length(); j++) { + uint16_t c = quarks[j]; + if (!IsIgnoreCase(flags)) { + if (c > String::kMaxOneByteCharCode) return false; + } else { + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(compiler->isolate(), c, + compiler, chars, 4); + if (length == 0 || chars[0] > String::kMaxOneByteCharCode) { + return false; + } + } + } + } else { + // A character class can also be impossible to match in one-byte mode. + DCHECK(elm.text_type() == TextElement::CLASS_RANGES); + RegExpClassRanges* cr = elm.class_ranges(); + ZoneList* ranges = cr->ranges(zone()); + CharacterRange::Canonicalize(ranges); + // Now they are in order so we only need to look at the first. + // If we are in non-Unicode case independent mode then we need + // to be a bit careful here, because the character classes have + // not been case-desugared yet, but there are characters and ranges + // that can become Latin-1 when case is considered. + int range_count = ranges->length(); + if (cr->is_negated()) { + if (range_count != 0 && ranges->at(0).from() == 0 && + ranges->at(0).to() >= String::kMaxOneByteCharCode) { + bool case_complications = !IsEitherUnicode(flags) && + IsIgnoreCase(flags) && + RangesContainLatin1Equivalents(ranges); + if (!case_complications) { + return false; + } + } + } else { + if (range_count == 0 || + ranges->at(0).from() > String::kMaxOneByteCharCode) { + bool case_complications = !IsEitherUnicode(flags) && + IsIgnoreCase(flags) && + RangesContainLatin1Equivalents(ranges); + if (!case_complications) { + return false; + } + } + } + } + } + return true; // It might match Latin1 input, we can't eliminate this node. +} + +void LoopChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) { + if (body_can_be_zero_length_ || budget <= 0) return; + not_at_start = not_at_start || this->not_at_start(); + DCHECK_EQ(alternatives_->length(), 2); // There's just loop and continue. + ChoiceNode::GetQuickCheckDetails(details, compiler, characters_filled_in, + not_at_start, budget); +} + +void LoopChoiceNode::FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + if (body_can_be_zero_length_ || budget <= 0) { + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); + return; + } + ChoiceNode::FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +void ChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) { + not_at_start = (not_at_start || not_at_start_); + int choice_count = alternatives_->length(); + DCHECK_LT(0, choice_count); + budget /= choice_count; + alternatives_->at(0).node()->GetQuickCheckDetails( + details, compiler, characters_filled_in, not_at_start, budget); + for (int i = 1; i < choice_count; i++) { + QuickCheckDetails new_details(details->characters()); + RegExpNode* node = alternatives_->at(i).node(); + node->GetQuickCheckDetails(&new_details, compiler, characters_filled_in, + not_at_start, budget); + // Here we merge the quick match details of the two branches. + details->Merge(&new_details, characters_filled_in); + } +} + +namespace { + +// Check for [0-9A-Z_a-z]. +void EmitWordCheck(RegExpMacroAssembler* assembler, + V8Label* word, + V8Label* non_word, + bool fall_through_on_word) { + StandardCharacterSet character_set = fall_through_on_word + ? StandardCharacterSet::kWord + : StandardCharacterSet::kNotWord; + // \w and \W is supported on all platforms. + DCHECK(assembler->CanOptimizeSpecialClassRanges(character_set)); + assembler->CheckSpecialClassRanges(character_set, + fall_through_on_word ? non_word : word); +} + +// Emit the code to check for a ^ in multiline mode (1-character lookbehind +// that matches newline or the start of input). +EmitResult EmitHat(RegExpCompiler* compiler, + RegExpNode* on_success, + Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // We will load the previous character into the current character register. + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + // A positive (> 0) cp_offset means we've already successfully matched a + // non-empty-width part of the pattern, and thus cannot be at or before the + // start of the subject string. We can thus skip both at-start and + // bounds-checks when loading the one-character lookbehind. + const bool may_be_at_or_before_subject_string_start = + new_trace.cp_offset() <= 0; + + V8Label ok; + if (may_be_at_or_before_subject_string_start) { + // The start of input counts as a newline in this context, so skip to ok if + // we are at the start. + assembler->CheckAtStart(new_trace.cp_offset(), &ok); + } + + // If we've already checked that we are not at the start of input, it's okay + // to load the previous character without bounds checks. + const bool can_skip_bounds_check = !may_be_at_or_before_subject_string_start; + assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, + new_trace.backtrack(), can_skip_bounds_check); + // Line Terminator is supported on all platforms. + DCHECK(assembler->CanOptimizeSpecialClassRanges( + StandardCharacterSet::kLineTerminator)); + assembler->CheckSpecialClassRanges(StandardCharacterSet::kLineTerminator, + new_trace.backtrack()); + assembler->Bind(&ok); + return on_success->Emit(compiler, &new_trace); +} + +} // namespace + +// Emit the code to handle \b and \B (word-boundary or non-word-boundary). +EmitResult AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, + Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Isolate* isolate = assembler->isolate(); + Trace::TriBool next_is_word_character = Trace::UNKNOWN; + bool not_at_start = (trace->at_start() == Trace::FALSE_VALUE); + BoyerMooreLookahead* lookahead = bm_info(not_at_start); + if (lookahead == nullptr) { + int eats_at_least = + std::min(kMaxLookaheadForBoyerMoore, EatsAtLeast(not_at_start)); + if (eats_at_least >= 1) { + BoyerMooreLookahead* bm = + zone()->New(eats_at_least, compiler, zone()); + FillInBMInfo(isolate, 0, kRecursionBudget, bm, not_at_start); + if (bm->at(0)->is_non_word()) next_is_word_character = Trace::FALSE_VALUE; + if (bm->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; + } + } else { + if (lookahead->at(0)->is_non_word()) + next_is_word_character = Trace::FALSE_VALUE; + if (lookahead->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; + } + bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); + if (next_is_word_character == Trace::UNKNOWN) { + V8Label before_non_word; + V8Label before_word; + if (trace->characters_preloaded() != 1) { + assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); + } + // Fall through on non-word. + EmitWordCheck(assembler, &before_word, &before_non_word, false); + // Next character is not a word character. + assembler->Bind(&before_non_word); + V8Label ok; + RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, + at_boundary ? kIsNonWord : kIsWord)); + assembler->GoTo(&ok); + + assembler->Bind(&before_word); + RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, + at_boundary ? kIsWord : kIsNonWord)); + assembler->Bind(&ok); + } else if (next_is_word_character == Trace::TRUE_VALUE) { + RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, + at_boundary ? kIsWord : kIsNonWord)); + } else { + DCHECK(next_is_word_character == Trace::FALSE_VALUE); + RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, + at_boundary ? kIsNonWord : kIsWord)); + } + return EmitResult::Success(); +} + +EmitResult AssertionNode::BacktrackIfPrevious( + RegExpCompiler* compiler, + Trace* trace, + AssertionNode::IfPrevious backtrack_if_previous) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + V8Label fall_through; + V8Label* non_word = backtrack_if_previous == kIsNonWord + ? new_trace.backtrack() + : &fall_through; + V8Label* word = backtrack_if_previous == kIsNonWord ? &fall_through + : new_trace.backtrack(); + + // A positive (> 0) cp_offset means we've already successfully matched a + // non-empty-width part of the pattern, and thus cannot be at or before the + // start of the subject string. We can thus skip both at-start and + // bounds-checks when loading the one-character lookbehind. + const bool may_be_at_or_before_subject_string_start = + new_trace.cp_offset() <= 0; + + if (may_be_at_or_before_subject_string_start) { + // The start of input counts as a non-word character, so the question is + // decided if we are at the start. + assembler->CheckAtStart(new_trace.cp_offset(), non_word); + } + + // If we've already checked that we are not at the start of input, it's okay + // to load the previous character without bounds checks. + const bool can_skip_bounds_check = !may_be_at_or_before_subject_string_start; + static_assert(Trace::kCPOffsetSlack == 1); + assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, non_word, + can_skip_bounds_check); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); + + assembler->Bind(&fall_through); + return on_success()->Emit(compiler, &new_trace); +} + +void AssertionNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start, + int budget) { + if (assertion_type_ == AT_START && not_at_start) { + details->set_cannot_match_from(filled_in); + return; + } + if (assertion_type_ == AT_END) { + details->set_cannot_match_from(filled_in); + return; + } + return on_success()->GetQuickCheckDetails(details, compiler, filled_in, + not_at_start, budget - 1); +} + +void EndNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) { + details->set_cannot_match_from(characters_filled_in); +} + +EmitResult AssertionNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("AssertionNode"); + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + switch (assertion_type_) { + case AT_END: { + V8Label ok; + assembler->CheckPosition(trace->cp_offset(), &ok); + assembler->GoTo(trace->backtrack()); + assembler->Bind(&ok); + break; + } + case AT_START: { + if (trace->at_start() == Trace::FALSE_VALUE) { + assembler->GoTo(trace->backtrack()); + return EmitResult::Success(); + } + if (trace->at_start() == Trace::UNKNOWN) { + assembler->CheckNotAtStart(trace->cp_offset(), trace->backtrack()); + Trace at_start_trace = *trace; + at_start_trace.set_at_start(Trace::TRUE_VALUE); + return on_success()->Emit(compiler, &at_start_trace); + } + } break; + case AFTER_NEWLINE: + return EmitHat(compiler, on_success(), trace); + case AT_BOUNDARY: + case AT_NON_BOUNDARY: { + return EmitBoundaryCheck(compiler, trace); + } + } + return on_success()->Emit(compiler, trace); +} + +namespace { + +bool DeterminedAlready(const QuickCheckDetails* quick_check, int offset) { + if (quick_check == nullptr) return false; + if (offset >= quick_check->characters()) return false; + return quick_check->positions(offset)->determines_perfectly; +} + +void UpdateBoundsCheck(int index, int* checked_up_to) { + if (index > *checked_up_to) { + *checked_up_to = index; + } +} + +} // namespace + +// We call this repeatedly to generate code for each pass over the text node. +// The passes are in increasing order of difficulty because we hope one +// of the first passes will fail in which case we are saved the work of the +// later passes. for example for the case independent regexp /%[asdfghjkl]a/ +// we will check the '%' in the first pass, the case independent 'a' in the +// second pass and the character class in the last pass. +// +// The passes are done from right to left, so for example to test for /bar/ +// we will first test for an 'r' with offset 2, then an 'a' with offset 1 +// and then a 'b' with offset 0. This means we can avoid the end-of-input +// bounds check most of the time. In the example we only need to check for +// end-of-input when loading the putative 'r'. +// +// A slight complication involves the fact that the first character may already +// be fetched into a register by the previous node. In this case we want to +// do the test for that character first. We do this in separate passes. The +// 'preloaded' argument indicates that we are doing such a 'pass'. If such a +// pass has been performed then subsequent passes will have true in +// first_element_checked to indicate that that character does not need to be +// checked again. +// +// In addition to all this we are passed a Trace, which can +// contain an AlternativeGeneration object. In this AlternativeGeneration +// object we can see details of any quick check that was already passed in +// order to get to the code we are now generating. The quick check can involve +// loading characters, which means we do not need to recheck the bounds +// up to the limit the quick check already checked. In addition the quick +// check can have involved a mask and compare operation which may simplify +// or obviate the need for further checks at some character positions. +void TextNode::TextEmitPass(RegExpCompiler* compiler, + TextEmitPassType pass, + bool preloaded, + Trace* trace, + bool first_element_checked, + int* checked_up_to) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Isolate* isolate = assembler->isolate(); + bool one_byte = compiler->one_byte(); + V8Label* backtrack = trace->backtrack(); + const QuickCheckDetails* quick_check = trace->quick_check_performed(); + int element_count = elements()->length(); + int backward_offset = read_backward() ? -Length() : 0; + for (int i = preloaded ? 0 : element_count - 1; i >= 0; i--) { + TextElement elm = elements()->at(i); + int cp_offset = trace->cp_offset() + elm.cp_offset() + backward_offset; + if (elm.text_type() == TextElement::ATOM) { + base::Vector quarks = elm.atom()->data(); + for (int j = preloaded ? 0 : quarks.length() - 1; j >= 0; j--) { + if (first_element_checked && i == 0 && j == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset() + j)) continue; + uint16_t quark = quarks[j]; + bool needs_bounds_check = + *checked_up_to < cp_offset + j || read_backward(); + bool bounds_checked = false; + switch (pass) { + case NON_LATIN1_MATCH: { + DCHECK(one_byte); // This pass is only done in one-byte mode. + if (IsIgnoreCase(compiler->flags())) { + // We are compiling for a one-byte subject, case independent mode. + // We have to check whether any of the case alternatives are in + // the one-byte range. + unibrow::uchar chars[4]; + // Only returns characters that are in the one-byte range. + int length = + GetCaseIndependentLetters(isolate, quark, compiler, chars, 4); + if (length == 0) { + assembler->GoTo(backtrack); + return; + } + } else { + // Case-dependent mode. + if (quark > String::kMaxOneByteCharCode) { + assembler->GoTo(backtrack); + return; + } + } + break; + } + case NON_LETTER_CHARACTER_MATCH: + bounds_checked = + EmitAtomNonLetter(isolate, compiler, quark, backtrack, + cp_offset + j, needs_bounds_check, preloaded); + break; + case SIMPLE_CHARACTER_MATCH: + bounds_checked = EmitSimpleCharacter(isolate, compiler, quark, + backtrack, cp_offset + j, + needs_bounds_check, preloaded); + break; + case CASE_CHARACTER_MATCH: + bounds_checked = + EmitAtomLetter(isolate, compiler, quark, backtrack, + cp_offset + j, needs_bounds_check, preloaded); + break; + default: + break; + } + if (bounds_checked) UpdateBoundsCheck(cp_offset + j, checked_up_to); + } + } else { + DCHECK_EQ(TextElement::CLASS_RANGES, elm.text_type()); + if (pass == CHARACTER_CLASS_MATCH) { + if (first_element_checked && i == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset())) continue; + RegExpClassRanges* cr = elm.class_ranges(); + bool bounds_check = *checked_up_to < cp_offset || read_backward(); + EmitClassRanges(assembler, cr, one_byte, backtrack, cp_offset, + bounds_check, preloaded, zone()); + UpdateBoundsCheck(cp_offset, checked_up_to); + } + } + } +} + +int TextNode::Length() { + TextElement elm = elements()->last(); + DCHECK_LE(0, elm.cp_offset()); + return elm.cp_offset() + elm.length(); +} + +TextNode* TextNode::CreateForCharacterRanges(Zone* zone, + ZoneList* ranges, + bool read_backward, + RegExpNode* on_success) { + DCHECK_NOT_NULL(ranges); + // TODO(jgruber): There's no fundamental need to create this + // RegExpClassRanges; we could refactor to avoid the allocation. + return zone->New(zone->New(zone, ranges), + read_backward, on_success); +} + +TextNode* TextNode::CreateForSurrogatePair( + Zone* zone, + CharacterRange lead, + ZoneList* trail_ranges, + bool read_backward, + RegExpNode* on_success) { + ZoneList* elms = zone->New>(2, zone); + if (lead.from() == lead.to()) { + ZoneList lead_surrogate(1, zone); + lead_surrogate.Add(lead.from(), zone); + RegExpAtom* atom = zone->New(lead_surrogate.ToConstVector()); + elms->Add(TextElement::Atom(atom), zone); + } else { + ZoneList* lead_ranges = CharacterRange::List(zone, lead); + elms->Add(TextElement::ClassRanges( + zone->New(zone, lead_ranges)), + zone); + } + elms->Add(TextElement::ClassRanges( + zone->New(zone, trail_ranges)), + zone); + return zone->New(elms, read_backward, on_success); +} + +TextNode* TextNode::CreateForSurrogatePair( + Zone* zone, + ZoneList* lead_ranges, + CharacterRange trail, + bool read_backward, + RegExpNode* on_success) { + ZoneList* trail_ranges = CharacterRange::List(zone, trail); + ZoneList* elms = zone->New>(2, zone); + elms->Add( + TextElement::ClassRanges(zone->New(zone, lead_ranges)), + zone); + elms->Add(TextElement::ClassRanges( + zone->New(zone, trail_ranges)), + zone); + return zone->New(elms, read_backward, on_success); +} + +// This generates the code to match a text node. A text node can contain +// straight character sequences (possibly to be matched in a case-independent +// way) and character classes. For efficiency we do not do this in a single +// pass from left to right. Instead we pass over the text node several times, +// emitting code for some character positions every time. See the comment on +// TextEmitPass for details. +EmitResult TextNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("TextNode"); + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return EmitResult::Success(); + DCHECK(limit_result == CONTINUE); + + if (trace->cp_offset() + Length() > RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + return EmitResult::Error(); + } + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { + const QuickCheckDetails* quick_check = trace->quick_check_performed(); + if (quick_check != nullptr) { + for (int i = 0; i < quick_check->characters(); ++i) { + if (quick_check->positions(i)->determines_perfectly) { + TRACE(" Character at position " + << i << " already determined by QuickCheck"); + } + } + } + } +#endif + + if (compiler->one_byte()) { + int dummy = 0; + TextEmitPass(compiler, NON_LATIN1_MATCH, false, trace, false, &dummy); + } + + bool first_elt_done = false; + static_assert(Trace::kCPOffsetSlack == 1); + int bound_checked_to = trace->cp_offset() - 1; + bound_checked_to += trace->bound_checked_up_to(); + + // If a character is preloaded into the current character register then + // check that first to save reloading it. + for (int twice = 0; twice < 2; twice++) { + bool is_preloaded_pass = twice == 0; + if (is_preloaded_pass && trace->characters_preloaded() != 1) continue; + if (IsIgnoreCase(compiler->flags())) { + TextEmitPass(compiler, NON_LETTER_CHARACTER_MATCH, is_preloaded_pass, + trace, first_elt_done, &bound_checked_to); + TextEmitPass(compiler, CASE_CHARACTER_MATCH, is_preloaded_pass, trace, + first_elt_done, &bound_checked_to); + } else { + TextEmitPass(compiler, SIMPLE_CHARACTER_MATCH, is_preloaded_pass, trace, + first_elt_done, &bound_checked_to); + } + TextEmitPass(compiler, CHARACTER_CLASS_MATCH, is_preloaded_pass, trace, + first_elt_done, &bound_checked_to); + first_elt_done = true; + } + + Trace successor_trace(*trace); + // If we advance backward, we may end up at the start. + RETURN_IF_ERROR(successor_trace.AdvanceCurrentPositionInTrace( + read_backward() ? -Length() : Length(), compiler)); + successor_trace.set_at_start(read_backward() ? Trace::UNKNOWN + : Trace::FALSE_VALUE); + RecursionCheck rc(compiler); + return on_success()->Emit(compiler, &successor_trace); +} + +void Trace::InvalidateCurrentCharacter() { + characters_preloaded_ = 0; +} + +EmitResult Trace::AdvanceCurrentPositionInTrace(int by, + RegExpCompiler* compiler) { + // We don't have an instruction for shifting the current character register + // down or for using a shifted value for anything so lets just forget that + // we preloaded any characters into it. + characters_preloaded_ = 0; + // Adjust the offsets of the quick check performed information. This + // information is used to find out what we already determined about the + // characters by means of mask and compare. + quick_check_performed_.Advance(by, compiler->one_byte()); + cp_offset_ += by; + bound_checked_up_to_ = std::max(0, bound_checked_up_to_ - by); + static_assert(RegExpMacroAssembler::kMaxCPOffset == + -RegExpMacroAssembler::kMinCPOffset); + if (std::abs(cp_offset_) + kCPOffsetSlack > + RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + cp_offset_ = 0; + return EmitResult::Error(); + } + return EmitResult::Success(); +} + +void TextNode::MakeCaseIndependent(Isolate* isolate, + bool is_one_byte, + RegExpFlags flags) { + if (!IsIgnoreCase(flags)) return; +#ifdef V8_INTL_SUPPORT + // This is done in an earlier step when generating the nodes from the AST + // because we may have to split up into separate nodes. + if (NeedsUnicodeCaseEquivalents(flags)) return; +#endif + + int element_count = elements()->length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()->at(i); + if (elm.text_type() == TextElement::CLASS_RANGES) { + RegExpClassRanges* cr = elm.class_ranges(); + // None of the standard character classes is different in the case + // independent case and it slows us down if we don't know that. + if (cr->is_standard(zone())) continue; + ZoneList* ranges = cr->ranges(zone()); + CharacterRange::AddCaseEquivalents(isolate, zone(), ranges, is_one_byte); + } + } +} + +int TextNode::FixedLengthLoopLength() { + return Length(); +} + +RegExpNode* TextNode::GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) { + if (read_backward()) return nullptr; + if (elements()->length() != 1) return nullptr; + TextElement elm = elements()->at(0); + if (elm.text_type() != TextElement::CLASS_RANGES) return nullptr; + RegExpClassRanges* node = elm.class_ranges(); + ZoneList* ranges = node->ranges(zone()); + CharacterRange::Canonicalize(ranges); + if (node->is_negated()) { + return ranges->length() == 0 ? on_success() : nullptr; + } + if (ranges->length() != 1) return nullptr; + const uint32_t max_char = MaxCodeUnit(compiler->one_byte()); + return ranges->at(0).IsEverything(max_char) ? on_success() : nullptr; +} + +// Finds the fixed match length of a sequence of nodes that goes from +// this alternative and back to this choice node. If there are variable +// length nodes or other complications in the way then return a sentinel +// value indicating that a fixed length loop cannot be constructed. +int ChoiceNode::FixedLengthLoopLengthForAlternative( + GuardedAlternative* alternative) { + int length = 0; + RegExpNode* node = alternative->node(); + // Later we will generate code for all these text nodes using recursion + // so we have to limit the max number. + int recursion_depth = 0; + while (node != this) { + if (recursion_depth++ > RegExpCompiler::kMaxRecursion) { + return kNodeIsTooComplexForFixedLengthLoops; + } + int node_length = node->FixedLengthLoopLength(); + if (node_length == kNodeIsTooComplexForFixedLengthLoops) { + return kNodeIsTooComplexForFixedLengthLoops; + } + length += node_length; + node = node->AsSeqRegExpNode()->on_success(); + } + if (read_backward()) { + length = -length; + } + // Check that we can jump by the whole text length. If not, return sentinel + // to indicate the we can't construct a fixed length loop. + if (length < RegExpMacroAssembler::kMinCPOffset || + length > RegExpMacroAssembler::kMaxCPOffset) { + return kNodeIsTooComplexForFixedLengthLoops; + } + return length; +} + +void LoopChoiceNode::AddLoopAlternative(GuardedAlternative alt) { + DCHECK_NULL(loop_node_); + AddAlternative(alt); + loop_node_ = alt.node(); +} + +void LoopChoiceNode::AddContinueAlternative(GuardedAlternative alt) { + DCHECK_NULL(continue_node_); + AddAlternative(alt); + continue_node_ = alt.node(); +} + +EmitResult LoopChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("LoopChoice"); + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->special_loop_state() != nullptr && + trace->special_loop_state()->loop_choice_node() == this) { + // Back edge of fixed length optimized loop node graph. + int text_length = + FixedLengthLoopLengthForAlternative(&(alternatives_->at(0))); + DCHECK_NE(kNodeIsTooComplexForFixedLengthLoops, text_length); + // Update the counter-based backtracking info on the stack. This is an + // optimization for fixed length loops (see below). + DCHECK(trace->cp_offset() == text_length); + macro_assembler->AdvanceCurrentPosition(text_length); + trace->special_loop_state()->GoToLoopTopLabel(macro_assembler); + return EmitResult::Success(); + } + DCHECK_NULL(trace->special_loop_state()); + if (!trace->is_trivial()) { + return trace->Flush(compiler, this); + } + return ChoiceNode::Emit(compiler, trace); +} + +int ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler, + int eats_at_least) { + int preload_characters = std::min(4, eats_at_least); + DCHECK_LE(preload_characters, 4); + if (compiler->macro_assembler()->CanReadUnaligned()) { + bool one_byte = compiler->one_byte(); + if (one_byte) { + // We can't preload 3 characters because there is no machine instruction + // to do that. We can't just load 4 because we could be reading + // beyond the end of the string, which could cause a memory fault. + if (preload_characters == 3) preload_characters = 2; + } else { + if (preload_characters > 2) preload_characters = 2; + } + } else { + if (preload_characters > 1) preload_characters = 1; + } + return preload_characters; +} + +// This class is used when generating the alternatives in a choice node. It +// records the way the alternative is being code generated. +class AlternativeGeneration /*: public Malloced*/ { + public: + AlternativeGeneration() + : possible_success(), + expects_preload(false), + after(), + quick_check_details() {} + V8Label possible_success; + bool expects_preload; + V8Label after; + QuickCheckDetails quick_check_details; +}; + +// Creates a list of AlternativeGenerations. If the list has a reasonable +// size then it is on the stack, otherwise the excess is on the heap. +class AlternativeGenerationList { + public: + AlternativeGenerationList(int count, Zone* zone) : alt_gens_(count, zone) { + for (int i = 0; i < count && i < kAFew; i++) { + alt_gens_.Add(a_few_alt_gens_ + i, zone); + } + for (int i = kAFew; i < count; i++) { + alt_gens_.Add(new AlternativeGeneration(), zone); + } + } + ~AlternativeGenerationList() { + for (int i = kAFew; i < alt_gens_.length(); i++) { + delete alt_gens_[i]; + alt_gens_[i] = nullptr; + } + } + + AlternativeGeneration* at(int i) { return alt_gens_[i]; } + + private: + static const int kAFew = 10; + ZoneList alt_gens_; + AlternativeGeneration a_few_alt_gens_[kAFew]; +}; + +void BoyerMoorePositionInfo::Set(int character) { + SetInterval(Interval(character, character)); +} + +namespace { + +ContainedInLattice AddRange(ContainedInLattice containment, + const int* ranges, + int ranges_length, + Interval new_range) { + DCHECK_EQ(1, ranges_length & 1); + DCHECK_EQ(String::kMaxCodePoint + 1, ranges[ranges_length - 1]); + if (containment == kLatticeUnknown) return containment; + bool inside = false; + int last = 0; + for (int i = 0; i < ranges_length; inside = !inside, last = ranges[i], i++) { + // Consider the range from last to ranges[i]. + // We haven't got to the new range yet. + if (ranges[i] <= new_range.from()) continue; + // New range is wholly inside last-ranges[i]. Note that new_range.to() is + // inclusive, but the values in ranges are not. + if (last <= new_range.from() && new_range.to() < ranges[i]) { + return Combine(containment, inside ? kLatticeIn : kLatticeOut); + } + return kLatticeUnknown; + } + return containment; +} + +int BitsetFirstSetBit(BoyerMoorePositionInfo::Bitset bitset) { + static_assert(BoyerMoorePositionInfo::kMapSize == + 2 * kInt64Size * kBitsPerByte); + + // Slight fiddling is needed here, since the bitset is of length 128 while + // CountTrailingZeros requires an integral type and std::bitset can only + // convert to unsigned long long. So we handle the most- and least-significant + // bits separately. + + { + static constexpr BoyerMoorePositionInfo::Bitset mask(~uint64_t{0}); + BoyerMoorePositionInfo::Bitset masked_bitset = bitset & mask; + static_assert(kInt64Size >= sizeof(decltype(masked_bitset.to_ullong()))); + uint64_t lsb = masked_bitset.to_ullong(); + if (lsb != 0) return Utils::CountTrailingZeros64(lsb); + } + + { + BoyerMoorePositionInfo::Bitset masked_bitset = bitset >> 64; + uint64_t msb = masked_bitset.to_ullong(); + if (msb != 0) return 64 + Utils::CountTrailingZeros64(msb); + } + + return -1; +} + +} // namespace + +void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + + if (interval.size() >= kMapSize) { + map_count_ = kMapSize; + map_.set(); + return; + } + + for (int i = interval.from(); i <= interval.to(); i++) { + int mod_character = (i & kMask); + if (!map_[mod_character]) { + map_count_++; + map_.set(mod_character); + } + if (map_count_ == kMapSize) return; + } +} + +void BoyerMoorePositionInfo::SetAll() { + w_ = kLatticeUnknown; + if (map_count_ != kMapSize) { + map_count_ = kMapSize; + map_.set(); + } +} + +BoyerMooreLookahead::BoyerMooreLookahead(int length, + RegExpCompiler* compiler, + Zone* zone) + : length_(length), + compiler_(compiler), + max_char_(MaxCodeUnit(compiler->one_byte())) { + bitmaps_ = zone->New>(length, zone); + for (int i = 0; i < length; i++) { + bitmaps_->Add(zone->New(), zone); + } +} + +// Find the longest range of lookahead that has the fewest number of different +// characters that can occur at a given position. Since we are optimizing two +// different parameters at once this is a tradeoff. +bool BoyerMooreLookahead::FindWorthwhileInterval(int* from, int* to) { + int biggest_points = 0; + // If more than 32 characters out of 128 can occur it is unlikely that we can + // be lucky enough to step forwards much of the time. + const int kMaxMax = 32; + for (int max_number_of_chars = 4; max_number_of_chars < kMaxMax; + max_number_of_chars *= 2) { + biggest_points = + FindBestInterval(max_number_of_chars, biggest_points, from, to); + } + if (biggest_points == 0) return false; + return true; +} + +// Find the highest-points range between 0 and length_ where the character +// information is not too vague. 'Too vague' means that there are more than +// max_number_of_chars that can occur at this position. Calculates the number +// of points as the product of width-of-the-range and +// probability-of-finding-one-of-the-characters, where the probability is +// calculated using the frequency distribution of the sample subject string. +int BoyerMooreLookahead::FindBestInterval(int max_number_of_chars, + int old_biggest_points, + int* from, + int* to) { + int biggest_points = old_biggest_points; + static const int kSize = RegExpMacroAssembler::kTableSize; + for (int i = 0; i < length_;) { + while (i < length_ && Count(i) > max_number_of_chars) + i++; + if (i == length_) break; + int remembered_from = i; + + BoyerMoorePositionInfo::Bitset union_bitset; + for (; i < length_ && Count(i) <= max_number_of_chars; i++) { + union_bitset |= bitmaps_->at(i)->raw_bitset(); + } + + int frequency = 0; + + // Iterate only over set bits. + int j; + while ((j = BitsetFirstSetBit(union_bitset)) != -1) { + DCHECK(union_bitset[j]); // Sanity check. + // Add 1 to the frequency to give a small per-character boost for + // the cases where our sampling is not good enough and many + // characters have a frequency of zero. This means the frequency + // can theoretically be up to 2*kSize though we treat it mostly as + // a fraction of kSize. + frequency += compiler_->frequency_collator()->Frequency(j) + 1; + union_bitset.reset(j); + } + + // We use the probability of skipping times the distance we are skipping to + // judge the effectiveness of this. Actually we have a cut-off: By + // dividing by 2 we switch off the skipping if the probability of skipping + // is less than 50%. This is because the multibyte mask-and-compare + // skipping in quickcheck is more likely to do well on this case. + bool in_quickcheck_range = + ((i - remembered_from < 4) || + (compiler_->one_byte() ? remembered_from <= 4 : remembered_from <= 2)); + // Called 'probability' but it is only a rough estimate and can actually + // be outside the 0-kSize range. + int probability = (in_quickcheck_range ? kSize / 2 : kSize) - frequency; + int points = (i - remembered_from) * probability; + TRACE_COMPILER(compiler_, " Points for " + << max_number_of_chars << " chars: " << points + << " (start at " << remembered_from + << "; probability: " << probability << ")"); + if (points > biggest_points) { + *from = remembered_from; + *to = i - 1; + biggest_points = points; + } + } + return biggest_points; +} + +// Take all the characters that will not prevent a successful match if they +// occur in the subject string in the range between min_lookahead and +// max_lookahead (inclusive) measured from the current position. If the +// character at max_lookahead offset is not one of these characters, then we +// can safely skip forwards by the number of characters in the range. +// For example for the pattern /..abcd.../ our range will be 2-5 inclusive +// (four characters) and the characters [abcd] will be in the allow- +// match set. If we find a 'z' at offset 5 we know we can't match at the +// current position or at the next three, so we can skip forwards 4 positions. +// +// nibble_table is only used for SIMD variants and encodes the same information +// as boolean_skip_table but in only 128 bits. It contains 16 bytes where the +// index into the table represent low nibbles of a character, and the stored +// byte is a bitset representing matching high nibbles. E.g. to store the +// character 'b' (0x62) in the nibble table, we set the 6th bit in row 2. We +// use it for the case where the range is only one character wide, eg the +// regexp /..[a-d]../. In this case finding a letter in the e-z range means +// we can move forward one but we compensate for this by processing 16 +// characters at a time. +int BoyerMooreLookahead::GetSkipTable(int min_lookahead, + int max_lookahead, + const TypedData& boolean_skip_table, + const TypedData& nibble_table) { + const int kSkipArrayEntry = 0; + const int kDontSkipArrayEntry = 1; + + //std::memset(boolean_skip_table->begin(), kSkipArrayEntry, + // boolean_skip_table->length()); + for (intptr_t i = 0, n = boolean_skip_table.Length(); i < n; i++) { + boolean_skip_table.SetUint8(i, kSkipArrayEntry); + } + const bool fill_nibble_table = !nibble_table.IsNull(); + if (fill_nibble_table) { + //std::memset(nibble_table->begin(), 0, nibble_table->length()); + for (intptr_t i = 0, n = nibble_table.Length(); i < n; i++) { + nibble_table.SetUint8(i, 0); + } + } + + for (int i = max_lookahead; i >= min_lookahead; i--) { + BoyerMoorePositionInfo::Bitset bitset = bitmaps_->at(i)->raw_bitset(); + + // Iterate only over set bits. + int j; + while ((j = BitsetFirstSetBit(bitset)) != -1) { + DCHECK(bitset[j]); // Sanity check. + boolean_skip_table.SetUint8(j, kDontSkipArrayEntry); + if (fill_nibble_table) { + int lo_nibble = j & 0x0f; + int hi_nibble = (j >> 4) & 0x07; + int row = nibble_table.GetUint8(lo_nibble); + row |= 1 << hi_nibble; + nibble_table.SetUint8(lo_nibble, row); + } + bitset.reset(j); + } + } + + const int skip = max_lookahead + 1 - min_lookahead; + return skip; +} + +// See comment above on the implementation of GetSkipTable. +void BoyerMooreLookahead::EmitSkipInstructions(RegExpMacroAssembler* masm) { + const int kSize = RegExpMacroAssembler::kTableSize; + + int min_lookahead = 0; + int max_lookahead = 0; + + if (!FindWorthwhileInterval(&min_lookahead, &max_lookahead)) { + TRACE_COMPILER(compiler_, " No worthwhile interval found"); + return; + } + + // Check if we only have a single non-empty position info, and that info + // contains only one or two characters. + bool found_single_position = false; + constexpr uint32_t kNoChar = 0xffffffff; + uint32_t char_one = kNoChar; + uint32_t char_two = kNoChar; + bool use_simd = masm->SkipUntilBitInTableUseSimd(1); + for (int i = min_lookahead; i <= max_lookahead; i++) { + BoyerMoorePositionInfo* map = bitmaps_->at(i); + if (map->map_count() == 0) { + // If we have a position where no characters can match then we just can't + // match. + masm->Fail(); + return; + } + + if (found_single_position || map->map_count() > 2) { + // We found a second position or there were more than two characters that + // matched at this position. + found_single_position = false; + break; + } + + BoyerMoorePositionInfo::Bitset bitset = map->raw_bitset(); + char_one = BitsetFirstSetBit(bitset); + if (map->map_count() == 2) { + bitset.reset(char_one); + char_two = BitsetFirstSetBit(bitset); + } else { + char_two = char_one; // Everything below here works for identical chars. + } + DCHECK(!found_single_position); + if (Utils::CountOneBits(char_one ^ char_two) > 1) { + // For case independent matches we often find two characters that differ + // only at one bit positions, but in this case they differed more. We + // don't have a great bytecode for two characters that are too different. + break; + } + + DCHECK_LE(map->map_count(), 2); + + found_single_position = true; + + DCHECK_NE(char_one, kNoChar); + DCHECK_NE(char_two, kNoChar); + } + + DCHECK_IMPLIES(found_single_position, max_lookahead == min_lookahead); + + if (found_single_position && max_lookahead < 3) { + // The mask-compare can probably handle this better. + return; + } + + if (found_single_position && !use_simd) { + // TODO(pthier): Add vectorized version. At that point we will want + // to remove the ' && !use_simd' above. + DCHECK(max_char_ > kSize); // This means we will have to do the 'and'. + + V8Label cont; + uint16_t mask = RegExpMacroAssembler::kTableMask; + mask &= ~(char_one ^ char_two); // Mask out the bit where they differ. + masm->SkipUntilCharAnd(max_lookahead, 1, char_one & mask, mask, length(), + &cont, &cont); + + masm->Bind(&cont); + return; + } + + TypedData& boolean_skip_table = TypedData::Handle( + TypedData::New(kTypedDataUint8ArrayCid, kSize, Heap::kOld)); + TypedData& nibble_table = TypedData::Handle(); + const int skip_distance = max_lookahead + 1 - min_lookahead; + if (masm->SkipUntilBitInTableUseSimd(skip_distance)) { + // The current implementation is tailored specifically for 128-bit tables. + static_assert(kSize == 128); + nibble_table = TypedData::New(kTypedDataUint8ArrayCid, kSize / kBitsPerByte, + Heap::kOld); + } + GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table, nibble_table); + DCHECK_NE(0, skip_distance); + + V8Label cont; + masm->SkipUntilBitInTable(max_lookahead, boolean_skip_table, nibble_table, + skip_distance, &cont, &cont); + masm->Bind(&cont); +} + +/* Code generation for choice nodes. + * + * We generate quick checks that do a mask and compare to eliminate a + * choice. If the quick check succeeds then it jumps to the continuation to + * do slow checks and check subsequent nodes. If it fails (the common case) + * it falls through to the next choice. + * + * Here is the desired flow graph. Nodes directly below each other imply + * fallthrough. Alternatives 1 and 2 have quick checks. Alternative + * 3 doesn't have a quick check so we have to call the slow check. + * Nodes are marked Qn for quick checks and Sn for slow checks. The entire + * regexp continuation is generated directly after the Sn node, up to the + * next GoTo if we decide to reuse some already generated code. Some + * nodes expect preload_characters to be preloaded into the current + * character register. R nodes do this preloading. Vertices are marked + * F for failures and S for success (possible success in the case of quick + * nodes). L, V, < and > are used as arrow heads. + * + * ----------> R + * | + * V + * Q1 -----> S1 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * Q2 -----> S2 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * S3 + * | + * F| + * | + * R + * | + * backtrack V + * <----------Q4 + * \ F | + * \ |S + * \ F V + * \-----S4 + * + * For fixed length loops we push the current position, then generate the code + * that eats the input specially in EmitFixedLengthLoop. The other choice (the + * continuation) is generated by the normal code in EmitChoices, and steps back + * in the input to the starting position when it fails to match. The loop code + * looks like this (U is the unwind code that steps back in the fixed length + * loop). + * + * _____ + * / \ + * V | + * ----------> S1 | + * /| | + * / |S | + * F/ \_____/ + * / + * |<----- + * | \ + * V |S + * Q2 ---> U----->backtrack + * | F / + * S| / + * V F / + * S2--/ + */ + +SpecialLoopState::SpecialLoopState(bool not_at_start, + ChoiceNode* loop_choice_node) + : loop_choice_node_(loop_choice_node) { + backtrack_trace_.set_backtrack(&step_label_); + if (not_at_start) backtrack_trace_.set_at_start(Trace::FALSE_VALUE); +} + +void SpecialLoopState::BindStepLabel(RegExpMacroAssembler* macro_assembler) { + macro_assembler->Bind(&step_label_); +} + +void SpecialLoopState::BindLoopTopLabel(RegExpMacroAssembler* macro_assembler) { + macro_assembler->Bind(&loop_top_label_); +} + +void SpecialLoopState::GoToLoopTopLabel(RegExpMacroAssembler* macro_assembler) { + macro_assembler->GoTo(&loop_top_label_); +} + +void ChoiceNode::AssertGuardsMentionRegisters(Trace* trace) { +#ifdef DEBUG + int choice_count = alternatives_->length(); + for (int i = 0; i < choice_count - 1; i++) { + GuardedAlternative alternative = alternatives_->at(i); + const ZoneList* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + for (int j = 0; j < guard_count; j++) { + DCHECK(!trace->mentions_reg(guards->at(j)->reg())); + } + } +#endif +} + +void ChoiceNode::SetUpPreLoad(RegExpCompiler* compiler, + Trace* current_trace, + PreloadState* state) { + if (state->eats_at_least_ == PreloadState::kEatsAtLeastNotYetInitialized) { + // Save some time by looking at most one machine word ahead. + state->eats_at_least_ = + EatsAtLeast(current_trace->at_start() == Trace::FALSE_VALUE); + } + state->preload_characters_ = + CalculatePreloadCharacters(compiler, state->eats_at_least_); + + state->preload_is_current_ = + (current_trace->characters_preloaded() == state->preload_characters_); + state->preload_has_checked_bounds_ = state->preload_is_current_; +} + +EmitResult ChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("Choice"); + int choice_count = alternatives_->length(); + + if (choice_count == 1 && alternatives_->at(0).guards() == nullptr) { + return alternatives_->at(0).node()->Emit(compiler, trace); + } + + AssertGuardsMentionRegisters(trace); + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return EmitResult::Success(); + DCHECK(limit_result == CONTINUE); + + // For loop nodes we already flushed (see LoopChoiceNode::Emit), but for + // other choice nodes we only flush if we are out of code size budget. + if (trace->flush_budget() == 0 && trace->has_any_actions()) { + return trace->Flush(compiler, this); + } + + RecursionCheck rc(compiler); + + PreloadState preload; + preload.init(); + // This must be outside the 'if' because the trace we use for what + // comes after the special_loop is inside it and needs the lifetime. + SpecialLoopState special_loop_state(not_at_start(), this); + + int text_length = FixedLengthLoopLengthForAlternative(&alternatives_->at(0)); + AlternativeGenerationList alt_gens(choice_count, zone()); + + // Flags need to be reset to the state of the ChoiceNode at the beginning + // of each alternative (in-line and out-of-line), as flags might be modified + // when emitting an alternative. + RegExpFlags flags = compiler->flags(); + if (choice_count > 1 && text_length != kNodeIsTooComplexForFixedLengthLoops) { + trace = EmitFixedLengthLoop(compiler, trace, &alt_gens, &preload, + &special_loop_state, text_length, flags); + if (trace == nullptr) return EmitResult::Error(); + } else { + preload.eats_at_least_ = + EmitOptimizedUnanchoredSearch(compiler, trace, &special_loop_state); + + RETURN_IF_ERROR( + EmitChoices(compiler, &alt_gens, 0, trace, &preload, flags)); + } + + // At this point we need to generate slow checks for the alternatives where + // the quick check was inlined. We can recognize these because the associated + // label was bound. + int new_flush_budget = trace->flush_budget() / choice_count; + for (int i = 0; i < choice_count; i++) { + compiler->set_flags(flags); + AlternativeGeneration* alt_gen = alt_gens.at(i); + Trace new_trace(*trace); + // If there are actions to be flushed we have to limit how many times + // they are flushed. Take the budget of the parent trace and distribute + // it fairly amongst the children. + if (new_trace.has_any_actions()) { + new_trace.set_flush_budget(new_flush_budget); + } + bool next_expects_preload = + i == choice_count - 1 ? false : alt_gens.at(i + 1)->expects_preload; + RETURN_IF_ERROR(EmitOutOfLineContinuation( + compiler, &new_trace, alternatives_->at(i), alt_gen, + preload.preload_characters_, next_expects_preload)); + } + + return EmitResult::Success(); +} + +Trace* ChoiceNode::EmitFixedLengthLoop( + RegExpCompiler* compiler, + Trace* trace, + AlternativeGenerationList* alt_gens, + PreloadState* preload, + SpecialLoopState* fixed_length_loop_state, + int text_length, + RegExpFlags flags) { + TRACE("* Emit fixed length loop"); + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + // Here we have special handling for greedy loops containing only text nodes + // and other simple nodes. We call these fixed length loops. These are + // handled by pushing the current position on the stack and then incrementing + // the current position each time around the switch. On backtrack we + // decrement the current position and check it against the pushed value. + // This avoids pushing backtrack information for each iteration of the loop, + // which could take up a lot of space. + DCHECK(trace->special_loop_state() == nullptr); + macro_assembler->PushCurrentPosition(); + // This is the label for trying to match what comes after the greedy + // quantifier, either because the body of the quantifier failed, or because + // we have stepped back to try again with one iteration fewer. + V8Label after_body_match_attempt; + Trace fixed_length_match_trace; + if (not_at_start()) fixed_length_match_trace.set_at_start(Trace::FALSE_VALUE); + fixed_length_match_trace.set_backtrack(&after_body_match_attempt); + fixed_length_loop_state->BindLoopTopLabel(macro_assembler); + fixed_length_match_trace.set_special_loop_state(fixed_length_loop_state); + EmitResult result = + alternatives_->at(0).node()->Emit(compiler, &fixed_length_match_trace); + macro_assembler->Bind(&after_body_match_attempt); + if (result.IsError()) return nullptr; + + Trace* new_trace = fixed_length_loop_state->backtrack_trace(); + + // In a fixed length loop there is only one other choice, which is what + // comes after the greedy quantifer. Try to match that now. + result = EmitChoices(compiler, alt_gens, 1, new_trace, preload, flags); + if (result.IsError()) return nullptr; + + fixed_length_loop_state->BindStepLabel(macro_assembler); + // If we have unwound to the bottom then backtrack. + macro_assembler->CheckFixedLengthLoop(trace->backtrack()); + // Otherwise try the second priority at an earlier position. + macro_assembler->AdvanceCurrentPosition(-text_length); + macro_assembler->GoTo(&after_body_match_attempt); + return new_trace; +} + +int ChoiceNode::EmitOptimizedUnanchoredSearch( + RegExpCompiler* compiler, + Trace* trace, + SpecialLoopState* search_loop_state) { + int eats_at_least = PreloadState::kEatsAtLeastNotYetInitialized; + if (alternatives_->length() != 2) return eats_at_least; + + GuardedAlternative alt1 = alternatives_->at(1); + if (alt1.guards() != nullptr && alt1.guards()->length() != 0) { + TRACE( + " Alternatives with guards -> Can't emit optimized unanchored search"); + return eats_at_least; + } + RegExpNode* eats_anything_node = alt1.node(); + if (eats_anything_node->GetSuccessorOfOmnivorousTextNode(compiler) != this) { + return eats_at_least; + } + + // Really we should be creating a new trace when we execute this function, + // but there is no need, because the code it generates cannot backtrack, and + // we always arrive here with a trivial trace (since it's the entry to a + // loop. That also implies that there are no preloaded characters, which is + // good, because it means we won't be violating any assumptions by + // overwriting those characters with new load instructions. + DCHECK(trace->is_trivial()); + + TRACE("* Emit optimized unanchored search"); + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + Isolate* isolate = macro_assembler->isolate(); + // At this point we know that we are at a non-greedy loop that will eat + // any character one at a time. Any non-anchored regexp has such a + // loop prepended to it in order to find where it starts. We look for + // a pattern of the form ...abc... where we can look 6 characters ahead + // and step forwards 3 if the character is not one of abc. Abc need + // not be atoms, they can be any reasonably limited character class or + // small alternation. + BoyerMooreLookahead* bm = bm_info(false); + // The --no-regexp-quick-check is for testing. It disables the compiler's + // clever optimizations that attempt to eliminate match positions. This + // way the regular regexp machinery gets more exercise and test coverage. + if (bm == nullptr && FLAG_regexp_quick_check) { + eats_at_least = std::min(kMaxLookaheadForBoyerMoore, EatsAtLeast(false)); + if (eats_at_least >= 1) { + bm = zone()->New(eats_at_least, compiler, zone()); + GuardedAlternative alt0 = alternatives_->at(0); + alt0.node()->FillInBMInfo(isolate, 0, kRecursionBudget, bm, false); + } + } + if (bm != nullptr) { +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.trace_regexp_compiler)) { + RegExpGraphPrinter* printer = compiler->diagnostics()->graph_printer(); + std::ostream& os = compiler->diagnostics()->os(); + os << " "; + printer->PrintBoyerMooreLookahead(bm); + } +#endif + bm->EmitSkipInstructions(macro_assembler); + } + return eats_at_least; +} + +EmitResult ChoiceNode::EmitChoices(RegExpCompiler* compiler, + AlternativeGenerationList* alt_gens, + int first_choice, + Trace* trace, + PreloadState* preload, + RegExpFlags flags) { + TRACE("* Emit Choices"); + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + SetUpPreLoad(compiler, trace, preload); + + // For now we just call all choices one after the other. The idea ultimately + // is to use the Dispatch table to try only the relevant ones. + int choice_count = alternatives_->length(); + + int new_flush_budget = trace->flush_budget() / choice_count; + + bool quick_check_flags = FLAG_regexp_optimization && FLAG_regexp_quick_check; + + for (int i = first_choice; i < choice_count; i++) { + compiler->set_flags(flags); + bool is_last = i == choice_count - 1; + bool fall_through_on_failure = !is_last; + GuardedAlternative alternative = alternatives_->at(i); + AlternativeGeneration* alt_gen = alt_gens->at(i); + alt_gen->quick_check_details.set_characters(preload->preload_characters_); + const ZoneList* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + Trace new_trace(*trace); + new_trace.set_characters_preloaded( + preload->preload_is_current_ ? preload->preload_characters_ : 0); + if (preload->preload_has_checked_bounds_) { + new_trace.set_bound_checked_up_to(preload->preload_characters_); + } + new_trace.quick_check_performed()->Clear(); + if (not_at_start_) new_trace.set_at_start(Trace::FALSE_VALUE); + if (!is_last) { + new_trace.set_backtrack(&alt_gen->after); + } + alt_gen->expects_preload = preload->preload_is_current_; + bool generate_full_check_inline = false; + TRACE_WITH_NODE_AND_TRACE(compiler, " Choice " << i << " ", + alternative.node(), &new_trace); + if (quick_check_flags && try_to_emit_quick_check_for_alternative(i == 0) && + alternative.node()->EmitQuickCheck( + compiler, trace, &new_trace, preload->preload_has_checked_bounds_, + &alt_gen->possible_success, &alt_gen->quick_check_details, + fall_through_on_failure, this)) { + // Quick check was generated for this choice. + preload->preload_is_current_ = true; + preload->preload_has_checked_bounds_ = true; + // If we generated the quick check to fall through on possible success, + // we now need to generate the full check inline. + if (!fall_through_on_failure) { + macro_assembler->Bind(&alt_gen->possible_success); + new_trace.set_quick_check_performed(&alt_gen->quick_check_details); + new_trace.set_characters_preloaded(preload->preload_characters_); + new_trace.set_bound_checked_up_to(preload->preload_characters_); + generate_full_check_inline = true; + } + } else if (alt_gen->quick_check_details.cannot_match()) { + if (!fall_through_on_failure) { + macro_assembler->GoTo(trace->backtrack()); + } + continue; + } else { + // No quick check was generated. Put the full code here. + // If this is not the first choice then there could be slow checks from + // previous cases that go here when they fail. There's no reason to + // insist that they preload characters since the slow check we are about + // to generate probably can't use it. + if (i != first_choice) { + alt_gen->expects_preload = false; + new_trace.InvalidateCurrentCharacter(); + } + generate_full_check_inline = true; + } + if (generate_full_check_inline) { + if (new_trace.has_any_actions()) { + new_trace.set_flush_budget(new_flush_budget); + } + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &new_trace); + } + RETURN_IF_ERROR(alternative.node()->Emit(compiler, &new_trace)); + preload->preload_is_current_ = false; + } + macro_assembler->Bind(&alt_gen->after); + } + return EmitResult::Success(); +} + +EmitResult ChoiceNode::EmitOutOfLineContinuation(RegExpCompiler* compiler, + Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload) { + if (!alt_gen->possible_success.is_linked()) return EmitResult::Success(); + TRACE_WITH_NODE_AND_TRACE(compiler, "* Emit Out-of-Line Continuation for ", + alternative.node(), trace); + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + macro_assembler->Bind(&alt_gen->possible_success); + Trace out_of_line_trace(*trace); + out_of_line_trace.set_characters_preloaded(preload_characters); + out_of_line_trace.set_quick_check_performed(&alt_gen->quick_check_details); + if (not_at_start_) out_of_line_trace.set_at_start(Trace::FALSE_VALUE); + const ZoneList* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + if (next_expects_preload) { + V8Label reload_current_char; + out_of_line_trace.set_backtrack(&reload_current_char); + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &out_of_line_trace); + } + RETURN_IF_ERROR(alternative.node()->Emit(compiler, &out_of_line_trace)); + macro_assembler->Bind(&reload_current_char); + // Reload the current character, since the next quick check expects that. + // We don't need to check bounds here because we only get into this + // code through a quick check which already did the checked load. + macro_assembler->LoadCurrentCharacter(trace->cp_offset(), nullptr, false, + preload_characters); + macro_assembler->GoTo(&(alt_gen->after)); + } else { + out_of_line_trace.set_backtrack(&(alt_gen->after)); + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &out_of_line_trace); + } + RETURN_IF_ERROR(alternative.node()->Emit(compiler, &out_of_line_trace)); + } + return EmitResult::Success(); +} + +EmitResult ActionNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("Action"); + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return EmitResult::Success(); + DCHECK(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + switch (action_type_) { + // Start with the actions we know how to defer. These are just recorded in + // the new trace, no code is emitted right now. (If we backtrack then we + // don't have to perform and undo these actions.) + case STORE_POSITION: + case RESTORE_POSITION: + case INCREMENT_REGISTER: + case SET_REGISTER_FOR_LOOP: + case CLEAR_CAPTURES: { + Trace new_trace = *trace; + new_trace.add_action(this); + RETURN_IF_ERROR(on_success()->Emit(compiler, &new_trace)); + break; + } + case EATS_AT_LEAST: + RETURN_IF_ERROR(on_success()->Emit(compiler, trace)); + break; // Doesn't actually do anything. + // We don't yet have the ability to defer these. + case BEGIN_POSITIVE_SUBMATCH: + case BEGIN_NEGATIVE_SUBMATCH: + if (!trace->is_trivial()) { + // Complex situation: Flush the trace state to the assembler and + // generate a generic version of this action. This call will + // recurse back to the else clause here. + trace->Flush(compiler, this); + } else { + assembler->WriteCurrentPositionToRegister( + data_.u_submatch.current_position_register, 0); + assembler->WriteStackPointerToRegister( + data_.u_submatch.stack_pointer_register); + RETURN_IF_ERROR(on_success()->Emit(compiler, trace)); + } + break; + case EMPTY_MATCH_CHECK: { + int start_pos_reg = data_.u_empty_match_check.start_register; + int stored_pos = 0; + int rep_reg = data_.u_empty_match_check.repetition_register; + bool has_minimum = (rep_reg != RegExpCompiler::kNoRegister); + bool know_dist = trace->GetStoredPosition(start_pos_reg, &stored_pos); + if (know_dist && !has_minimum && stored_pos == trace->cp_offset()) { + // If we know we haven't advanced and there is no minimum we + // can just backtrack immediately. + assembler->GoTo(trace->backtrack()); + } else if (know_dist && stored_pos < trace->cp_offset()) { + // If we know we've advanced we can generate the continuation + // immediately. + RETURN_IF_ERROR(on_success()->Emit(compiler, trace)); + } else if (!trace->is_trivial()) { + trace->Flush(compiler, this); + } else { + V8Label skip_empty_check; + // If we have a minimum number of repetitions we check the current + // number first and skip the empty check if it's not enough. + if (has_minimum) { + int limit = data_.u_empty_match_check.repetition_limit; + assembler->IfRegisterLT(rep_reg, limit, &skip_empty_check); + } + // If the match is empty we bail out, otherwise we fall through + // to the on-success continuation. + assembler->IfRegisterEqPos(start_pos_reg, trace->backtrack()); + assembler->Bind(&skip_empty_check); + RETURN_IF_ERROR(on_success()->Emit(compiler, trace)); + } + break; + } + case POSITIVE_SUBMATCH_SUCCESS: { + if (!trace->is_trivial()) { + return trace->Flush(compiler, this, Trace::kFlushSuccess); + } + assembler->ReadCurrentPositionFromRegister( + data_.u_submatch.current_position_register); + assembler->ReadStackPointerFromRegister( + data_.u_submatch.stack_pointer_register); + int clear_register_count = data_.u_submatch.clear_register_count; + if (clear_register_count == 0) { + return on_success()->Emit(compiler, trace); + } + int clear_registers_from = data_.u_submatch.clear_register_from; + V8Label clear_registers_backtrack; + Trace new_trace = *trace; + new_trace.set_backtrack(&clear_registers_backtrack); + RETURN_IF_ERROR(on_success()->Emit(compiler, &new_trace)); + + assembler->Bind(&clear_registers_backtrack); + int clear_registers_to = clear_registers_from + clear_register_count - 1; + assembler->ClearRegisters(clear_registers_from, clear_registers_to); + + DCHECK(trace->backtrack() == nullptr); + assembler->Backtrack(); + return EmitResult::Success(); + } + case MODIFY_FLAGS: { + compiler->set_flags(flags()); + RETURN_IF_ERROR(on_success()->Emit(compiler, trace)); + break; + } + default: + UNREACHABLE(); + } + return EmitResult::Success(); +} + +EmitResult BackReferenceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + TRACE_EMIT("BackReference"); + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (!trace->is_trivial()) { + return trace->Flush(compiler, this); + } + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return EmitResult::Success(); + DCHECK(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + DCHECK_EQ(start_reg_ + 1, end_reg_); + if (IsIgnoreCase(compiler->flags())) { + bool unicode = IsEitherUnicode(compiler->flags()); + assembler->CheckNotBackReferenceIgnoreCase(start_reg_, read_backward(), + unicode, trace->backtrack()); + } else { + assembler->CheckNotBackReference(start_reg_, read_backward(), + trace->backtrack()); + } + // We are going to advance backward, so we may end up at the start. + if (read_backward()) trace->set_at_start(Trace::UNKNOWN); + + // Check that the back reference does not end inside a surrogate pair. + if (IsEitherUnicode(compiler->flags()) && !compiler->one_byte()) { + assembler->CheckNotInSurrogatePair(trace->cp_offset(), trace->backtrack()); + } + return on_success()->Emit(compiler, trace); +} + +void TextNode::CalculateOffsets() { + int element_count = elements()->length(); + // Set up the offsets of the elements relative to the start. This is a fixed + // quantity since a TextNode can only contain fixed-width things. + int cp_offset = 0; + for (int i = 0; i < element_count; i++) { + TextElement& elm = elements()->at(i); + elm.set_cp_offset(cp_offset); + cp_offset += elm.length(); + } +} + +namespace { + +// Assertion propagation moves information about assertions such as +// \b to the affected nodes. For instance, in /.\b./ information must +// be propagated to the first '.' that whatever follows needs to know +// if it matched a word or a non-word, and to the second '.' that it +// has to check if it succeeds a word or non-word. In this case the +// result will be something like: +// +// +-------+ +------------+ +// | . | | . | +// +-------+ ---> +------------+ +// | word? | | check word | +// +-------+ +------------+ +class AssertionPropagator : public AllStatic { + public: + static void VisitText(TextNode* that) {} + + static void VisitAction(ActionNode* that) { + // If the next node is interested in what it follows then this node + // has to be interested too so it can pass the information on. + that->info()->AddFromFollowing(that->on_success()->info()); + } + + static void VisitChoice(ChoiceNode* that, int i) { + // Anything the following nodes need to know has to be known by + // this node also, so it can pass it on. + that->info()->AddFromFollowing(that->alternatives()->at(i).node()->info()); + } + + static void VisitLoopChoiceContinueNode(LoopChoiceNode* that) { + that->info()->AddFromFollowing(that->continue_node()->info()); + } + + static void VisitLoopChoiceLoopNode(LoopChoiceNode* that) { + that->info()->AddFromFollowing(that->loop_node()->info()); + } + + static void VisitNegativeLookaroundChoiceLookaroundNode( + NegativeLookaroundChoiceNode* that) { + VisitChoice(that, NegativeLookaroundChoiceNode::kLookaroundIndex); + } + + static void VisitNegativeLookaroundChoiceContinueNode( + NegativeLookaroundChoiceNode* that) { + VisitChoice(that, NegativeLookaroundChoiceNode::kContinueIndex); + } + + static void VisitBackReference(BackReferenceNode* that) {} + + static void VisitAssertion(AssertionNode* that) {} +}; + +// Propagates information about the minimum size of successful matches from +// successor nodes to their predecessors. Note that all eats_at_least values +// are initialized to zero before analysis. +class EatsAtLeastPropagator : public AllStatic { + public: + static void VisitText(TextNode* that) { + // The eats_at_least value is not used if reading backward. + if (!that->read_backward()) { + // We are not at the start after this node, and thus we can use the + // successor's from_not_start value. + uint8_t eats_at_least = base::saturated_cast( + that->Length() + + that->on_success()->eats_at_least_info()->from_not_start); + that->set_eats_at_least_info(EatsAtLeastInfo(eats_at_least)); + } + } + + static void VisitAction(ActionNode* that) { + switch (that->action_type()) { + case ActionNode::BEGIN_POSITIVE_SUBMATCH: { + // For a begin positive submatch we propagate the eats_at_least + // data from the successor of the success node, ignoring the body of + // the lookahead, which eats nothing, since it is a zero-width + // assertion. + // TODO(chromium:42201836) This is better than discarding all + // information when there is a positive lookahead, but it loses some + // information that could be useful, since the body of the lookahead + // could tell us something about how close to the end of the string we + // are. + that->set_eats_at_least_info( + *that->success_node()->on_success()->eats_at_least_info()); + break; + } + case ActionNode::POSITIVE_SUBMATCH_SUCCESS: + // We do not propagate eats_at_least data through positive submatch + // success because it rewinds input. + DCHECK(that->eats_at_least_info()->IsZero()); + break; + case ActionNode::EATS_AT_LEAST: { + EatsAtLeastInfo eats = *that->on_success()->eats_at_least_info(); + eats.SetMax(that->stored_eats_at_least()); + that->set_eats_at_least_info(eats); + break; + } + default: + // Otherwise, the current node eats at least as much as its successor. + // Note: we can propagate eats_at_least data for BEGIN_NEGATIVE_SUBMATCH + // because NegativeLookaroundChoiceNode ignores its lookaround successor + // when computing eats-at-least and quick check information. + that->set_eats_at_least_info(*that->on_success()->eats_at_least_info()); + break; + } + } + + static void VisitChoice(ChoiceNode* that, int i) { + // The minimum possible match from a choice node is the minimum of its + // successors. + EatsAtLeastInfo eats_at_least = + i == 0 ? EatsAtLeastInfo(UINT8_MAX) : *that->eats_at_least_info(); + eats_at_least.SetMin( + *that->alternatives()->at(i).node()->eats_at_least_info()); + that->set_eats_at_least_info(eats_at_least); + } + + static void VisitLoopChoiceContinueNode(LoopChoiceNode* that) { + if (!that->read_backward()) { + that->set_eats_at_least_info( + *that->continue_node()->eats_at_least_info()); + } + } + + static void VisitLoopChoiceLoopNode(LoopChoiceNode* that) {} + + static void VisitNegativeLookaroundChoiceLookaroundNode( + NegativeLookaroundChoiceNode* that) {} + + static void VisitNegativeLookaroundChoiceContinueNode( + NegativeLookaroundChoiceNode* that) { + that->set_eats_at_least_info(*that->continue_node()->eats_at_least_info()); + } + + static void VisitBackReference(BackReferenceNode* that) { + if (!that->read_backward()) { + that->set_eats_at_least_info(*that->on_success()->eats_at_least_info()); + } + } + + static void VisitAssertion(AssertionNode* that) { + EatsAtLeastInfo eats_at_least = *that->on_success()->eats_at_least_info(); + if (that->assertion_type() == AssertionNode::AT_START) { + // If we know we are not at the start and we are asked "how many + // characters will you match if you succeed?" then we can answer anything + // since false implies false. So let's just set the max answer + // (UINT8_MAX) since that won't prevent us from preloading a lot of + // characters for the other branches in the node graph. + eats_at_least.from_not_start = UINT8_MAX; + } + that->set_eats_at_least_info(eats_at_least); + } +}; + +} // namespace + +// ------------------------------------------------------------------- +// Analysis + +// Iterates the node graph and provides the opportunity for propagators to set +// values that depend on successor nodes. +template +class Analysis : public NodeVisitor { + public: + Analysis(Isolate* isolate, bool is_one_byte, RegExpFlags flags) + : isolate_(isolate), + is_one_byte_(is_one_byte), + flags_(flags), + error_(RegExpError::kNone) {} + + void EnsureAnalyzed(RegExpNode* that) { + if (!OSThread::Current()->HasStackHeadroom()) { + if (FLAG_correctness_fuzzer_suppressions) { + FATAL("Analysis: Aborting on stack overflow"); + } + fail(RegExpError::kAnalysisStackOverflow); + return; + } + if (that->info()->been_analyzed || that->info()->being_analyzed) return; + that->info()->being_analyzed = true; + that->Accept(this); + that->info()->being_analyzed = false; + that->info()->been_analyzed = true; + } + + bool has_failed() { return error_ != RegExpError::kNone; } + RegExpError error() { + DCHECK(error_ != RegExpError::kNone); + return error_; + } + void fail(RegExpError error) { error_ = error; } + + Isolate* isolate() const { return isolate_; } + + void VisitEnd(EndNode* that) override { + // nothing to do + } + +// Used to call the given static function on each propagator / variadic template +// argument. +#define STATIC_FOR_EACH(expr) \ + do { \ + int dummy[] = {((expr), 0)...}; \ + USE(dummy); \ + } while (false) + + void VisitText(TextNode* that) override { + that->MakeCaseIndependent(isolate(), is_one_byte_, flags()); + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + that->CalculateOffsets(); + STATIC_FOR_EACH(Propagators::VisitText(that)); + } + + void VisitAction(ActionNode* that) override { + if (that->action_type() == ActionNode::MODIFY_FLAGS) { + set_flags(that->flags()); + } + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitAction(that)); + } + + void VisitChoice(ChoiceNode* that) override { + for (int i = 0; i < that->alternatives()->length(); i++) { + EnsureAnalyzed(that->alternatives()->at(i).node()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitChoice(that, i)); + } + } + + void VisitLoopChoice(LoopChoiceNode* that) override { + DCHECK_EQ(that->alternatives()->length(), 2); // Just loop and continue. + + // First propagate all information from the continuation node. + // Due to the unusual visitation order, we need to manage the flags manually + // as if we were visiting the loop node before visiting the continuation. + RegExpFlags orig_flags = flags(); + + EnsureAnalyzed(that->continue_node()); + if (has_failed()) return; + // Propagators don't access global state (including flags), so we don't need + // to reset them here. + STATIC_FOR_EACH(Propagators::VisitLoopChoiceContinueNode(that)); + + RegExpFlags continuation_flags = flags(); + + // Check the loop last since it may need the value of this node + // to get a correct result. + set_flags(orig_flags); + EnsureAnalyzed(that->loop_node()); + if (has_failed()) return; + // Propagators don't access global state (including flags), so we don't need + // to reset them here. + STATIC_FOR_EACH(Propagators::VisitLoopChoiceLoopNode(that)); + + set_flags(continuation_flags); + } + + void VisitNegativeLookaroundChoice( + NegativeLookaroundChoiceNode* that) override { + DCHECK_EQ(that->alternatives()->length(), 2); // Lookaround and continue. + + EnsureAnalyzed(that->lookaround_node()); + if (has_failed()) return; + STATIC_FOR_EACH( + Propagators::VisitNegativeLookaroundChoiceLookaroundNode(that)); + + EnsureAnalyzed(that->continue_node()); + if (has_failed()) return; + STATIC_FOR_EACH( + Propagators::VisitNegativeLookaroundChoiceContinueNode(that)); + } + + void VisitBackReference(BackReferenceNode* that) override { + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitBackReference(that)); + } + + void VisitAssertion(AssertionNode* that) override { + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitAssertion(that)); + } + +#undef STATIC_FOR_EACH + + private: + RegExpFlags flags() const { return flags_; } + void set_flags(RegExpFlags flags) { flags_ = flags; } + + Isolate* isolate_; + const bool is_one_byte_; + RegExpFlags flags_; + RegExpError error_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Analysis); +}; + +RegExpError AnalyzeRegExp(Isolate* isolate, + bool is_one_byte, + RegExpFlags flags, + RegExpNode* node) { + Analysis analysis( + isolate, is_one_byte, flags); + DCHECK_EQ(node->info()->been_analyzed, false); + analysis.EnsureAnalyzed(node); + DCHECK_IMPLIES(analysis.has_failed(), analysis.error() != RegExpError::kNone); + return analysis.has_failed() ? analysis.error() : RegExpError::kNone; +} + +void BackReferenceNode::FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + // Working out the set of characters that a backreference can match is too + // hard, so we just say that any character can match. + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); +} + +static_assert(BoyerMoorePositionInfo::kMapSize == + RegExpMacroAssembler::kTableSize); + +void ChoiceNode::FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + ZoneList* alts = alternatives(); + budget = (budget - 1) / alts->length(); + for (int i = 0; i < alts->length(); i++) { + GuardedAlternative& alt = alts->at(i); + if (alt.guards() != nullptr && alt.guards()->length() != 0) { + bm->SetRest(offset); // Give up trying to fill in info. + SaveBMInfo(bm, not_at_start, offset); + return; + } + alt.node()->FillInBMInfo(isolate, offset, budget, bm, not_at_start); + } + SaveBMInfo(bm, not_at_start, offset); +} + +void TextNode::FillInBMInfo(Isolate* isolate, + int initial_offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + TRACE_WITH_NODE(bm->compiler(), "* Fill BM Info for Text: ", this); + if (initial_offset >= bm->length()) return; + if (read_backward()) return; + int offset = initial_offset; + int max_char = bm->max_char(); + for (int i = 0; i < elements()->length(); i++) { + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + TextElement text = elements()->at(i); + if (text.text_type() == TextElement::ATOM) { + RegExpAtom* atom = text.atom(); + for (int j = 0; j < atom->length(); j++, offset++) { + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + uint16_t character = atom->data()[j]; + if (IsIgnoreCase(bm->compiler()->flags())) { + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(isolate, character, + bm->compiler(), chars, 4); + for (int k = 0; k < length; k++) { + bm->Set(offset, chars[k]); + } + } else { + if (character <= max_char) bm->Set(offset, character); + } + } + } else { + DCHECK_EQ(TextElement::CLASS_RANGES, text.text_type()); + RegExpClassRanges* class_ranges = text.class_ranges(); + ZoneList* ranges = class_ranges->ranges(zone()); + if (class_ranges->is_negated()) { + bm->SetAll(offset); + } else { + for (int k = 0; k < ranges->length(); k++) { + CharacterRange& range = ranges->at(k); + if (static_cast(range.from()) > max_char) continue; + int to = std::min(max_char, static_cast(range.to())); + bm->SetInterval(offset, Interval(range.from(), to)); + } + } + offset++; + } + } + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, + true); // Not at start after a text node. + if (initial_offset == 0) set_bm_info(not_at_start, bm); +} + +RegExpNode* RegExpCompiler::OptionallyStepBackToLeadSurrogate( + RegExpNode* on_success) { + TRACE_COMPILER(this, "* Optionally step back to lead surrogate"); + DCHECK(!read_backward()); + ZoneList* lead_surrogates = CharacterRange::List( + zone(), CharacterRange::Range(kLeadSurrogateStart, kLeadSurrogateEnd)); + ZoneList* trail_surrogates = CharacterRange::List( + zone(), CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd)); + + ChoiceNode* optional_step_back = zone()->New(2, zone()); + + int stack_register = UnicodeLookaroundStackRegister(); + int position_register = UnicodeLookaroundPositionRegister(); + RegExpNode* step_back = TextNode::CreateForCharacterRanges( + zone(), lead_surrogates, true, on_success); + RegExpLookaround::Builder builder(true, step_back, this, stack_register, + position_register); + REGISTER_NODE(step_back); + RegExpNode* match_trail = TextNode::CreateForCharacterRanges( + zone(), trail_surrogates, false, builder.on_match_success()); + REGISTER_NODE(match_trail); + + optional_step_back->AddAlternative( + GuardedAlternative(builder.ForMatch(this, match_trail))); + optional_step_back->AddAlternative(GuardedAlternative(on_success)); + + REGISTER_NODE(optional_step_back); + return optional_step_back; +} + +RegExpNode* RegExpCompiler::PreprocessRegExp(RegExpCompileData* data, + bool is_one_byte) { +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + TraceRegExpTreeScope trace_tree_scope(diagnostics()); +#endif + TRACE_GRAPH_WITH_NODE("* Preprocess RegExp ", data->tree); + REGISTER_NODE(accept()); + // Wrap the body of the regexp in capture #0. + RegExpNode* captured_body = + RegExpCapture::ToNode(data->tree, 0, this, accept()); + RegExpNode* node = captured_body; + if (!data->tree->IsAnchoredAtStart() && !IsSticky(flags())) { + // Add a .*? at the beginning, outside the body capture, unless + // this expression is anchored at the beginning or sticky. + TRACE_GRAPH("* Add .*? at beginning of unanchored, non-sticky RegExp"); + RegExpNode* loop_node = RegExpQuantifier::ToNode( + 0, RegExpTree::kInfinity, false, + zone()->New(StandardCharacterSet::kEverything), this, + captured_body, data->contains_anchor); + + if (data->contains_anchor) { + // Unroll loop once, to take care of the case that might start + // at the start of input. + TRACE_GRAPH("* Unroll loop once"); + ChoiceNode* first_step_node = zone()->New(2, zone()); + first_step_node->AddAlternative(GuardedAlternative(captured_body)); + first_step_node->AddAlternative(GuardedAlternative(zone()->New( + zone()->New(StandardCharacterSet::kEverything), + false, loop_node))); + REGISTER_NODE(first_step_node); + node = first_step_node; + } else { + node = loop_node; + } + } + if (!is_one_byte && IsEitherUnicode(flags()) && + (IsGlobal(flags()) || IsSticky(flags()))) { + node = OptionallyStepBackToLeadSurrogate(node); + } + + // We can run out of registers during preprocessing, or we can recurse too + // deep during ToNode. Indicate an error in either case. + if (reg_exp_too_big_) { + data->error = RegExpError::kTooLarge; + } + CHECK_NE(nullptr, node); + return node; +} + +void RegExpCompiler::ToNodeCheckForStackOverflow() { + //if (StackLimitCheck{isolate()}.HasOverflowed()) { + if (!OSThread::Current()->HasStackHeadroom()) { + SetRegExpTooBig(); + } +} + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS +void RegExpCompiler::set_diagnostics( + std::unique_ptr diagnostics) { + diagnostics_ = std::move(diagnostics); +} +#endif + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-compiler.h b/runtime/vm/regexp/regexp-compiler.h new file mode 100644 index 00000000000..b21b16a8f40 --- /dev/null +++ b/runtime/vm/regexp/regexp-compiler.h @@ -0,0 +1,713 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_COMPILER_H_ +#define V8_REGEXP_REGEXP_COMPILER_H_ + +#include + +#include "vm/regexp/regexp-flags.h" +#include "vm/regexp/regexp-macro-assembler.h" +#include "vm/regexp/regexp-nodes.h" +#include "vm/regexp/small-vector.h" + +namespace dart { + +class DynamicBitSet; +class Isolate; +class SpecialLoopState; +class RegExpDiagnostics; + +namespace regexp_compiler_constants { + +// The '2' variant is has inclusive from and exclusive to. +// This covers \s as defined in ECMA-262 5.1, 15.10.2.12, +// which include WhiteSpace (7.2) or LineTerminator (7.3) values. +constexpr uint32_t kRangeEndMarker = 0x110000; +constexpr int kSpaceRanges[] = { + '\t', '\r' + 1, ' ', ' ' + 1, 0x00A0, 0x00A1, 0x1680, + 0x1681, 0x2000, 0x200B, 0x2028, 0x202A, 0x202F, 0x2030, + 0x205F, 0x2060, 0x3000, 0x3001, 0xFEFF, 0xFF00, kRangeEndMarker}; +constexpr int kSpaceRangeCount = ARRAY_SIZE(kSpaceRanges); + +constexpr int kWordRanges[] = {'0', '9' + 1, 'A', 'Z' + 1, '_', + '_' + 1, 'a', 'z' + 1, kRangeEndMarker}; +constexpr int kWordRangeCount = ARRAY_SIZE(kWordRanges); +constexpr int kDigitRanges[] = {'0', '9' + 1, kRangeEndMarker}; +constexpr int kDigitRangeCount = ARRAY_SIZE(kDigitRanges); +constexpr int kSurrogateRanges[] = {Utf16::kLeadSurrogateStart, + Utf16::kLeadSurrogateStart + 1, + kRangeEndMarker}; +constexpr int kSurrogateRangeCount = ARRAY_SIZE(kSurrogateRanges); +constexpr int kLineTerminatorRanges[] = {0x000A, 0x000B, 0x000D, 0x000E, + 0x2028, 0x202A, kRangeEndMarker}; +constexpr int kLineTerminatorRangeCount = ARRAY_SIZE(kLineTerminatorRanges); + +// More makes code generation slower, less makes V8 benchmark score lower. +constexpr uint32_t kMaxLookaheadForBoyerMoore = 8; +// In a 3-character pattern you can maximally step forwards 3 characters +// at a time, which is not always enough to pay for the extra logic. +constexpr uint32_t kPatternTooShortForBoyerMoore = 2; + +} // namespace regexp_compiler_constants + +inline bool NeedsUnicodeCaseEquivalents(RegExpFlags flags) { + // Both unicode (or unicode sets) and ignore_case flags are set. We need to + // use ICU to find the closure over case equivalents. + return IsEitherUnicode(flags) && IsIgnoreCase(flags); +} + +// Details of a quick mask-compare check that can look ahead in the +// input stream. +class QuickCheckDetails { + public: + QuickCheckDetails() : characters_(0), mask_(0), value_(0) {} + explicit QuickCheckDetails(int characters) + : characters_(characters), mask_(0), value_(0) { + ASSERT(characters <= kMaxPositions); + } + bool Rationalize(bool one_byte); + // Merge in the information from another branch of an alternation. + void Merge(QuickCheckDetails* other, int from_index); + // Advance the current position by some amount. + void Advance(int by, bool one_byte); + void Clear(); + bool cannot_match() const { + for (int i = 0; i < characters(); i++) { + if (positions_[i].cannot_match) return true; + } + return false; + } + void set_cannot_match_from(int index) { + ASSERT(index >= 0); + for (int i = index; i < characters(); i++) { + positions_[i].cannot_match = true; + } + } + struct Position { + Position() + : mask(0), value(0), determines_perfectly(false), cannot_match(false) {} + void Clear() { + mask = 0; + value = 0; + determines_perfectly = false; + cannot_match = false; + } + uint32_t mask; + uint32_t value; + bool determines_perfectly; + bool cannot_match; + }; + int characters() const { return characters_; } + void set_characters(int characters) { + ASSERT(0 <= characters && characters <= kMaxPositions); + characters_ = characters; + } + Position* positions(int index) { + ASSERT(0 <= index); + ASSERT(characters_ > index); + return positions_ + index; + } + const Position* positions(int index) const { + ASSERT(0 <= index); + ASSERT(characters_ > index); + return positions_ + index; + } + uint32_t mask() { return mask_; } + uint32_t value() { return value_; } + + private: + static constexpr int kMaxPositions = 4; + // How many characters do we have quick check information from. This is + // the same for all branches of a choice node. + int characters_; + Position positions_[kMaxPositions]; + // These values are the condensate of the above array after Rationalize(). + uint32_t mask_; + uint32_t value_; +}; + +// Improve the speed that we scan for an initial point where a non-anchored +// regexp can match by using a Boyer-Moore-like table. This is done by +// identifying non-greedy non-capturing loops in the nodes that eat any +// character one at a time. For example in the middle of the regexp +// /foo[\s\S]*?bar/ we find such a loop. There is also such a loop implicitly +// inserted at the start of any non-anchored regexp. +// +// When we have found such a loop we look ahead in the nodes to find the set of +// characters that can come at given distances. For example for the regexp +// /.?foo/ we know that there are at least 3 characters ahead of us, and the +// sets of characters that can occur are [any, [f, o], [o]]. We find a range in +// the lookahead info where the set of characters is reasonably constrained. In +// our example this is from index 1 to 2 (0 is not constrained). We can now +// look 3 characters ahead and if we don't find one of [f, o] (the union of +// [f, o] and [o]) then we can skip forwards by the range size (in this case 2). +// +// For Unicode input strings we do the same, but modulo 128. +// +// We also look at the first string fed to the regexp and use that to get a hint +// of the character frequencies in the inputs. This affects the assessment of +// whether the set of characters is 'reasonably constrained'. +// +// We also have another lookahead mechanism (called quick check in the code), +// which uses a wide load of multiple characters followed by a mask and compare +// to determine whether a match is possible at this point. +enum ContainedInLattice { + kNotYet = 0, + kLatticeIn = 1, + kLatticeOut = 2, + kLatticeUnknown = 3 // Can also mean both in and out. +}; + +inline ContainedInLattice Combine(ContainedInLattice a, ContainedInLattice b) { + return static_cast(a | b); +} + +class BoyerMoorePositionInfo : public ZoneObject { + public: + bool at(int i) const { return map_[i]; } + + static constexpr int kMapSize = 128; + static constexpr int kMask = kMapSize - 1; + + int map_count() const { return map_count_; } + + void Set(int character); + void SetInterval(const Interval& interval); + void SetAll(); + + bool is_non_word() { return w_ == kLatticeOut; } + bool is_word() { return w_ == kLatticeIn; } + + using Bitset = std::bitset; + Bitset raw_bitset() const { return map_; } + + private: + Bitset map_; + int map_count_ = 0; // Number of set bits in the map. + ContainedInLattice w_ = kNotYet; // The \w character class. +}; + +class BoyerMooreLookahead : public ZoneObject { + public: + BoyerMooreLookahead(int length, RegExpCompiler* compiler, Zone* zone); + + int length() const { return length_; } + int max_char() { return max_char_; } + RegExpCompiler* compiler() { return compiler_; } + + int Count(int map_number) { return bitmaps_->at(map_number)->map_count(); } + + BoyerMoorePositionInfo* at(int i) { return bitmaps_->at(i); } + const BoyerMoorePositionInfo* at(int i) const { return bitmaps_->at(i); } + + void Set(int map_number, int character) { + if (character > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_->at(map_number); + info->Set(character); + } + + void SetInterval(int map_number, const Interval& interval) { + if (interval.from() > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_->at(map_number); + if (interval.to() > max_char_) { + info->SetInterval(Interval(interval.from(), max_char_)); + } else { + info->SetInterval(interval); + } + } + + void SetAll(int map_number) { bitmaps_->at(map_number)->SetAll(); } + + void SetRest(int from_map) { + for (int i = from_map; i < length_; i++) + SetAll(i); + } + void EmitSkipInstructions(RegExpMacroAssembler* masm); + + private: + // This is the value obtained by EatsAtLeast. If we do not have at least this + // many characters left in the sample string then the match is bound to fail. + // Therefore it is OK to read a character this far ahead of the current match + // point. + int length_; + RegExpCompiler* compiler_; + // 0xff for Latin1, 0xffff for UTF-16. + int max_char_; + ZoneList* bitmaps_; + + int GetSkipTable(int min_lookahead, + int max_lookahead, + const TypedData& boolean_skip_table, + const TypedData& nibble_table); + bool FindWorthwhileInterval(int* from, int* to); + int FindBestInterval(int max_number_of_chars, + int old_biggest_points, + int* from, + int* to); +}; + +// There are many ways to generate code for a node. This class encapsulates +// the current way we should be generating. In other words it encapsulates +// the current state of the code generator. The effect of this is that we +// generate code for paths that the matcher can take through the regular +// expression. A given node in the regexp can be code-generated several times +// as it can be part of several traces. For example for the regexp: +// /foo(bar|ip)baz/ the code to match baz will be generated twice, once as part +// of the foo-bar-baz trace and once as part of the foo-ip-baz trace. The code +// to match foo is generated only once (the traces have a common prefix). The +// code to store the capture is deferred and generated (twice) after the places +// where baz has been matched. +class Trace { + public: + // A value for a property that is either known to be true, known to be false, + // or not known. + enum TriBool { UNKNOWN = -1, FALSE_VALUE = 0, TRUE_VALUE = 1 }; + + Trace() + : cp_offset_(0), + flush_budget_(100), // Note: this is a 16 bit field. + at_start_(UNKNOWN), + has_any_actions_(false), + action_(nullptr), + backtrack_(nullptr), + special_loop_state_(nullptr), + characters_preloaded_(0), + bound_checked_up_to_(0), + next_(nullptr) {} + + Trace(const Trace& other) + : cp_offset_(other.cp_offset_), + flush_budget_(other.flush_budget_), + at_start_(other.at_start_), + has_any_actions_(other.has_any_actions_), + action_(nullptr), + backtrack_(other.backtrack_), + special_loop_state_(other.special_loop_state_), + characters_preloaded_(other.characters_preloaded_), + bound_checked_up_to_(other.bound_checked_up_to_), + quick_check_performed_(other.quick_check_performed_), + next_(&other) {} + + // End the trace. This involves flushing the deferred actions in the trace + // and pushing a backtrack location onto the backtrack stack. Once this is + // done we can start a new trace or go to one that has already been + // generated. + enum FlushMode { + // Normal flush of the deferred actions, generates code for backtracking. + kFlushFull, + // Matching has succeeded, so current position and backtrack stack will be + // ignored and need not be written. + kFlushSuccess + }; + EmitResult Flush(RegExpCompiler* compiler, + RegExpNode* successor, + FlushMode mode = kFlushFull); + + // Some callers add/subtract 1 from cp_offset, assuming that the result is + // still valid. That's obviously not the case when our `cp_offset` is only + // checked against kMinCPOffset/kMaxCPOffset, so we need to apply the some + // slack. + // TODO(jgruber): It would be better if all callers checked against limits + // themselves when doing so; but unfortunately not all callers have + // abort-compilation mechanisms. + static constexpr int kCPOffsetSlack = 1; + int cp_offset() const { return cp_offset_; } + + // Does any trace in the chain have an action? + bool has_any_actions() const { return has_any_actions_; } + // Does this particular trace object have an action? + bool has_action() const { return action_ != nullptr; } + ActionNode* action() const { return action_; } + // A trivial trace is one that has no deferred actions or other state that + // affects the assumptions used when generating code. There is no recorded + // backtrack location in a trivial trace, so with a trivial trace we will + // generate code that, on a failure to match, gets the backtrack location + // from the backtrack stack rather than using a direct jump instruction. We + // always start code generation with a trivial trace and non-trivial traces + // are created as we emit code for nodes or add to the list of deferred + // actions in the trace. The location of the code generated for a node using + // a trivial trace is recorded in a label in the node so that gotos can be + // generated to that code. + bool is_trivial() const { + return backtrack_ == nullptr && !has_any_actions_ && cp_offset_ == 0 && + characters_preloaded_ == 0 && bound_checked_up_to_ == 0 && + quick_check_performed_.characters() == 0 && at_start_ == UNKNOWN; + } + TriBool at_start() const { return at_start_; } + void set_at_start(TriBool at_start) { at_start_ = at_start; } + V8Label* backtrack() const { return backtrack_; } + SpecialLoopState* special_loop_state() const { return special_loop_state_; } + int characters_preloaded() const { return characters_preloaded_; } + int bound_checked_up_to() const { return bound_checked_up_to_; } + int flush_budget() const { return flush_budget_; } + QuickCheckDetails* quick_check_performed() { return &quick_check_performed_; } + bool mentions_reg(int reg) const; + // Returns true if a deferred position store exists to the specified + // register and stores the offset in the out-parameter. Otherwise + // returns false. + bool GetStoredPosition(int reg, int* cp_offset) const; + // These set methods and AdvanceCurrentPositionInTrace should be used only on + // new traces - the intention is that traces are immutable after creation. + void add_action(ActionNode* new_action) { + ASSERT(action_ == nullptr); // Otherwise we lose an action. + action_ = new_action; + has_any_actions_ = true; + } + void set_backtrack(V8Label* backtrack) { backtrack_ = backtrack; } + void set_special_loop_state(SpecialLoopState* state) { + special_loop_state_ = state; + } + void set_characters_preloaded(int count) { characters_preloaded_ = count; } + void set_bound_checked_up_to(int to) { bound_checked_up_to_ = to; } + void set_flush_budget(int to) { + ASSERT(to <= UINT16_MAX); // Flush-budget is 16 bit. + flush_budget_ = to; + } + void set_quick_check_performed(QuickCheckDetails* d) { + quick_check_performed_ = *d; + } + void InvalidateCurrentCharacter(); + EmitResult AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler); + const Trace* next() const { return next_; } + + class ConstIterator final { + public: + ConstIterator& operator++() { + trace_ = trace_->next(); + return *this; + } + bool operator==(const ConstIterator& other) const { + return trace_ == other.trace_; + } + const Trace* operator*() const { return trace_; } + + private: + explicit ConstIterator(const Trace* trace) : trace_(trace) {} + + const Trace* trace_; + + friend class Trace; + }; + + ConstIterator begin() const { return ConstIterator(this); } + ConstIterator end() const { return ConstIterator(nullptr); } + + private: + // Dart: `IGNORE` conflicts with something in the Windows headers. + enum DeferredActionUndoType { IGNORE_, RESTORE, CLEAR }; + static constexpr int kNoStore = kMinInt; + // For a given register, records the actions recorded in the trace. + // See ScanDeferredActions. + struct RegisterFlushInfo { + DeferredActionUndoType undo_action = IGNORE_; + int value = 0; + bool absolute = false; // Set register to value. + bool clear = false; // Clear register (set to zero): + int store_position = + kNoStore; // Store current position plus value to register. + }; + + int FindAffectedRegisters(DynamicBitSet* affected_registers, Zone* zone); + void PerformDeferredActions(RegExpMacroAssembler* macro, + int max_register, + const DynamicBitSet& affected_registers, + DynamicBitSet* registers_to_pop, + DynamicBitSet* registers_to_clear, + Zone* zone); + void RestoreAffectedRegisters(RegExpMacroAssembler* macro, + int max_register, + const DynamicBitSet& registers_to_pop, + const DynamicBitSet& registers_to_clear); + void ScanDeferredActions(Trace* top, int reg, RegisterFlushInfo* info); + + int cp_offset_; + uint16_t flush_budget_; + TriBool at_start_ : 8; // Whether we are at the start of the string. + bool has_any_actions_ : 8; // Whether any trace in the chain has an action. + ActionNode* action_; + V8Label* backtrack_; + SpecialLoopState* special_loop_state_; + int characters_preloaded_; + int bound_checked_up_to_; + QuickCheckDetails quick_check_performed_; + const Trace* next_; +}; + +// Used for fixed length greedy loops (counted loops like .*) and for +// omnivorous non-greedy loops (the initial loop ahead of a non-anchored +// regexp). +class SpecialLoopState { + public: + explicit SpecialLoopState(bool not_at_start, ChoiceNode* loop_choice_node); + + void BindStepLabel(RegExpMacroAssembler* macro_assembler); + void BindLoopTopLabel(RegExpMacroAssembler* macro_assembler); + void GoToLoopTopLabel(RegExpMacroAssembler* macro_assembler); + ChoiceNode* loop_choice_node() const { return loop_choice_node_; } + Trace* backtrack_trace() { return &backtrack_trace_; } + + private: + // Step backwards (fixed length greed loop) or forwards (non-greedy + // omnivourous loop. + V8Label step_label_; + V8Label loop_top_label_; + ChoiceNode* loop_choice_node_; + Trace backtrack_trace_; +}; + +struct PreloadState { + static constexpr int kEatsAtLeastNotYetInitialized = -1; + bool preload_is_current_; + bool preload_has_checked_bounds_; + int preload_characters_; + int eats_at_least_; + void init() { eats_at_least_ = kEatsAtLeastNotYetInitialized; } +}; + +// Analysis performs assertion propagation and computes eats_at_least_ values. +// See the comments on AssertionPropagator and EatsAtLeastPropagator for more +// details. +RegExpError AnalyzeRegExp(Isolate* isolate, + bool is_one_byte, + RegExpFlags flags, + RegExpNode* node); + +class FrequencyCollator { + public: + FrequencyCollator() : total_samples_(0) { + for (int i = 0; i < RegExpMacroAssembler::kTableSize; i++) { + frequencies_[i] = CharacterFrequency(i); + } + } + + void CountCharacter(int character) { + int index = (character & RegExpMacroAssembler::kTableMask); + frequencies_[index].Increment(); + total_samples_++; + } + + // Does not measure in percent, but rather per-128 (the table size from the + // regexp macro assembler). + int Frequency(int in_character) { + ASSERT((in_character & RegExpMacroAssembler::kTableMask) == in_character); + if (total_samples_ < 1) return 1; // Division by zero. + int freq_in_per128 = + (frequencies_[in_character].counter() * 128) / total_samples_; + return freq_in_per128; + } + + private: + class CharacterFrequency { + public: + CharacterFrequency() : counter_(0), character_(-1) {} + explicit CharacterFrequency(int character) + : counter_(0), character_(character) {} + + void Increment() { counter_++; } + int counter() { return counter_; } + int character() { return character_; } + + private: + int counter_; + int character_; + }; + + private: + CharacterFrequency frequencies_[RegExpMacroAssembler::kTableSize]; + int total_samples_; +}; + +class RegExpCompiler { + public: + RegExpCompiler(Isolate* isolate, + Zone* zone, + int capture_count, + RegExpFlags flags, + bool is_one_byte); + + int AllocateRegister() { + if (next_register_ >= RegExpMacroAssembler::kMaxRegister) { + reg_exp_too_big_ = true; + return next_register_; + } + return next_register_++; + } + + // Lookarounds to match lone surrogates for unicode character class matches + // are never nested. We can therefore reuse registers. + int UnicodeLookaroundStackRegister() { + if (unicode_lookaround_stack_register_ == kNoRegister) { + unicode_lookaround_stack_register_ = AllocateRegister(); + } + return unicode_lookaround_stack_register_; + } + + int UnicodeLookaroundPositionRegister() { + if (unicode_lookaround_position_register_ == kNoRegister) { + unicode_lookaround_position_register_ = AllocateRegister(); + } + return unicode_lookaround_position_register_; + } + + struct CompilationResult final { + explicit CompilationResult(RegExpError err) : error(err) {} + CompilationResult(Object* code, int registers) + : code(code), num_registers(registers) {} + + static CompilationResult RegExpTooBig() { + return CompilationResult(RegExpError::kTooLarge); + } + + bool Succeeded() const { return error == RegExpError::kNone; } + + const RegExpError error = RegExpError::kNone; + Object* code; + int num_registers = 0; + }; + + CompilationResult Assemble(Isolate* isolate, + RegExpMacroAssembler* assembler, + RegExpNode* start, + int capture_count, + const String& pattern); + + // Preprocessing is the final step of node creation before analysis + // and assembly. It includes: + // - Wrapping the body of the regexp in capture 0. + // - Inserting the implicit .* before/after the regexp if necessary. + // - If the input is a one-byte string, filtering out nodes that can't match. + // - Fixing up regexp matches that start within a surrogate pair. + RegExpNode* PreprocessRegExp(RegExpCompileData* data, bool is_one_byte); + + // If the regexp matching starts within a surrogate pair, step back to the + // lead surrogate and start matching from there. + RegExpNode* OptionallyStepBackToLeadSurrogate(RegExpNode* on_success); + + inline void AddWork(RegExpNode* node) { + if (!node->on_work_list() && !node->label()->is_bound()) { + node->set_on_work_list(true); + work_list_->push_back(node); + } + } + + static const int kImplementationOffset = 0; + static const int kNumberOfRegistersOffset = 0; + static const int kCodeOffset = 1; + + RegExpMacroAssembler* macro_assembler() { return macro_assembler_; } + EndNode* accept() { return accept_; } + +#if defined(V8_TARGET_OS_MACOS) + // Looks like MacOS needs a lower recursion limit since "secondary threads" + // get a smaller stack by default (512kB vs. 8MB). + // See https://crbug.com/408820921. + static constexpr int kMaxRecursion = 50; +#else + static constexpr int kMaxRecursion = 100; +#endif + inline int recursion_depth() { return recursion_depth_; } + inline void IncrementRecursionDepth() { recursion_depth_++; } + inline void DecrementRecursionDepth() { recursion_depth_--; } + + inline RegExpFlags flags() const { return flags_; } + inline void set_flags(RegExpFlags flags) { flags_ = flags; } + + void SetRegExpTooBig() { reg_exp_too_big_ = true; } + bool IsRegExpTooBig() const { return reg_exp_too_big_; } + + inline bool one_byte() { return one_byte_; } + inline bool optimize() { return optimize_; } + inline void set_optimize(bool value) { optimize_ = value; } + inline bool limiting_recursion() { return limiting_recursion_; } + inline void set_limiting_recursion(bool value) { + limiting_recursion_ = value; + } + bool read_backward() { return read_backward_; } + void set_read_backward(bool value) { read_backward_ = value; } + FrequencyCollator* frequency_collator() { return &frequency_collator_; } + + int current_expansion_factor() { return current_expansion_factor_; } + void set_current_expansion_factor(int value) { + current_expansion_factor_ = value; + } + + // The recursive nature of ToNode node generation means we may run into stack + // overflow issues. We introduce periodic checks to detect these, and the + // tick counter helps limit overhead of these checks. + // TODO(jgruber): This is super hacky and should be replaced by an abort + // mechanism or iterative node generation. + void ToNodeMaybeCheckForStackOverflow() { + if ((to_node_overflow_check_ticks_++ % 64 == 0)) { + ToNodeCheckForStackOverflow(); + } + } + void ToNodeCheckForStackOverflow(); + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + RegExpDiagnostics* diagnostics() { return diagnostics_.get(); } + void set_diagnostics(std::unique_ptr diagnostics); +#endif + Isolate* isolate() const { return isolate_; } + Zone* zone() const { return zone_; } + + static const int kNoRegister = -1; + + private: + EndNode* accept_; + int next_register_; + int unicode_lookaround_stack_register_; + int unicode_lookaround_position_register_; + ZoneVector* work_list_; + int recursion_depth_; + RegExpFlags flags_; + RegExpMacroAssembler* macro_assembler_; + bool one_byte_; + bool reg_exp_too_big_; + bool limiting_recursion_; + int to_node_overflow_check_ticks_ = 0; + bool optimize_; + bool read_backward_; + int current_expansion_factor_; + FrequencyCollator frequency_collator_; +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + std::unique_ptr diagnostics_; +#endif + Isolate* isolate_; + Zone* zone_; +}; + +// Categorizes character ranges into BMP, non-BMP, lead, and trail surrogates. +class UnicodeRangeSplitter { + public: + UnicodeRangeSplitter(ZoneList* base); + + static constexpr int kInitialSize = 8; + using CharacterRangeVector = base::SmallVector; + + const CharacterRangeVector* bmp() const { return &bmp_; } + const CharacterRangeVector* lead_surrogates() const { + return &lead_surrogates_; + } + const CharacterRangeVector* trail_surrogates() const { + return &trail_surrogates_; + } + const CharacterRangeVector* non_bmp() const { return &non_bmp_; } + + private: + void AddRange(CharacterRange range); + + CharacterRangeVector bmp_; + CharacterRangeVector lead_surrogates_; + CharacterRangeVector trail_surrogates_; + CharacterRangeVector non_bmp_; +}; + +// We need to check for the following characters: 0x39C 0x3BC 0x178. +// TODO(jgruber): Move to CharacterRange. +bool RangeContainsLatin1Equivalents(CharacterRange range); + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_COMPILER_H_ diff --git a/runtime/vm/regexp/regexp-error.cc b/runtime/vm/regexp/regexp-error.cc new file mode 100644 index 00000000000..969eef72e3c --- /dev/null +++ b/runtime/vm/regexp/regexp-error.cc @@ -0,0 +1,22 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-error.h" + +#include "vm/regexp/base.h" + +namespace dart { + +const char* const kRegExpErrorStrings[] = { +#define TEMPLATE(NAME, STRING) STRING, + REGEXP_ERROR_MESSAGES(TEMPLATE) +#undef TEMPLATE +}; + +const char* RegExpErrorString(RegExpError error) { + DCHECK_LT(error, RegExpError::NumErrors); + return kRegExpErrorStrings[static_cast(error)]; +} + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-error.h b/runtime/vm/regexp/regexp-error.h new file mode 100644 index 00000000000..039c5741852 --- /dev/null +++ b/runtime/vm/regexp/regexp-error.h @@ -0,0 +1,65 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_ERROR_H_ +#define V8_REGEXP_REGEXP_ERROR_H_ + +#include "vm/globals.h" + +namespace dart { + +#define REGEXP_ERROR_MESSAGES(T) \ + T(None, "") \ + T(StackOverflow, "Maximum call stack size exceeded") \ + T(AnalysisStackOverflow, "Stack overflow") \ + T(TooLarge, "Regular expression too large") \ + T(UnterminatedGroup, "Unterminated group") \ + T(UnmatchedParen, "Unmatched ')'") \ + T(EscapeAtEndOfPattern, "\\ at end of pattern") \ + T(InvalidPropertyName, "Invalid property name") \ + T(InvalidEscape, "Invalid escape") \ + T(InvalidDecimalEscape, "Invalid decimal escape") \ + T(InvalidUnicodeEscape, "Invalid Unicode escape") \ + T(NothingToRepeat, "Nothing to repeat") \ + T(LoneQuantifierBrackets, "Lone quantifier brackets") \ + T(RangeOutOfOrder, "numbers out of order in {} quantifier") \ + T(IncompleteQuantifier, "Incomplete quantifier") \ + T(InvalidQuantifier, "Invalid quantifier") \ + T(InvalidGroup, "Invalid group") \ + T(MultipleFlagDashes, "Multiple dashes in flag group") \ + T(NotLinear, "Cannot be executed in linear time") \ + T(RepeatedFlag, "Repeated flag in flag group") \ + T(InvalidFlagGroup, "Invalid flag group") \ + T(TooManyCaptures, "Too many captures") \ + T(InvalidCaptureGroupName, "Invalid capture group name") \ + T(DuplicateCaptureGroupName, "Duplicate capture group name") \ + T(InvalidNamedReference, "Invalid named reference") \ + T(InvalidNamedCaptureReference, "Invalid named capture referenced") \ + T(InvalidClassPropertyName, "Invalid property name in character class") \ + T(InvalidCharacterClass, "Invalid character class") \ + T(UnterminatedCharacterClass, "Unterminated character class") \ + T(OutOfOrderCharacterClass, "Range out of order in character class") \ + T(InvalidClassSetOperation, "Invalid set operation in character class") \ + T(InvalidCharacterInClass, "Invalid character in character class") \ + T(NegatedCharacterClassWithStrings, \ + "Negated character class may contain strings") \ + T(UnsupportedBytecode, "Unsupported Bytecode") + +enum class RegExpError : uint32_t { +#define TEMPLATE(NAME, STRING) k##NAME, + REGEXP_ERROR_MESSAGES(TEMPLATE) +#undef TEMPLATE + NumErrors +}; + +const char* RegExpErrorString(RegExpError error); + +inline constexpr bool RegExpErrorIsStackOverflow(RegExpError error) { + return error == RegExpError::kStackOverflow || + error == RegExpError::kAnalysisStackOverflow; +} + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_ERROR_H_ diff --git a/runtime/vm/regexp/regexp-flags.h b/runtime/vm/regexp/regexp-flags.h new file mode 100644 index 00000000000..5fd205d3b2a --- /dev/null +++ b/runtime/vm/regexp/regexp-flags.h @@ -0,0 +1,84 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_FLAGS_H_ +#define V8_REGEXP_REGEXP_FLAGS_H_ + +#include +#include + +#include "vm/regexp/flags.h" + +namespace dart { + +// TODO(jgruber,pthier): Decouple more parts of the codebase from +// JSRegExp::Flags. Consider removing JSRegExp::Flags. + +// Order is important! Sorted in alphabetic order by the flag char. Note this +// means that flag bits are shuffled. Take care to keep them contiguous when +// adding/removing flags. +#define REGEXP_FLAG_LIST(V) \ + V(has_indices, HasIndices, hasIndices, 'd', 7) \ + V(global, Global, global, 'g', 0) \ + V(ignore_case, IgnoreCase, ignoreCase, 'i', 1) \ + V(linear, Linear, linear, 'l', 6) \ + V(multiline, Multiline, multiline, 'm', 2) \ + V(dot_all, DotAll, dotAll, 's', 5) \ + V(unicode, Unicode, unicode, 'u', 4) \ + V(unicode_sets, UnicodeSets, unicodeSets, 'v', 8) \ + V(sticky, Sticky, sticky, 'y', 3) + +#define V(Lower, Camel, LowerCamel, Char, Bit) k##Camel = 1 << Bit, +enum class RegExpFlag { REGEXP_FLAG_LIST(V) }; +#undef V + +#define V(...) +1 +constexpr int kRegExpFlagCount = REGEXP_FLAG_LIST(V); +#undef V + +// Assert alpha-sorted chars. +#define V(Lower, Camel, LowerCamel, Char, Bit) < Char) && (Char +static_assert((('a' - 1) REGEXP_FLAG_LIST(V) <= 'z'), "alpha-sort chars"); +#undef V + +// Assert contiguous indices. +#define V(Lower, Camel, LowerCamel, Char, Bit) | (1 << Bit) +static_assert(((1 << kRegExpFlagCount) - 1) == (0 REGEXP_FLAG_LIST(V)), + "contiguous bits"); +#undef V + +using RegExpFlags = base::Flags; +DEFINE_OPERATORS_FOR_FLAGS(RegExpFlags) + +#define V(Lower, Camel, ...) \ + constexpr bool Is##Camel(RegExpFlags f) { \ + return (f & RegExpFlag::k##Camel) != 0; \ + } +REGEXP_FLAG_LIST(V) +#undef V + +constexpr bool IsEitherUnicode(RegExpFlags f) { + return IsUnicode(f) || IsUnicodeSets(f); +} + +// Whether to rewind the index when it initially points into the middle of a +// surrogate pair. See also OptionallyStepBackToLeadSurrogate(). +constexpr bool ShouldOptionallyStepBackToLeadSurrogate(RegExpFlags f) { + return IsEitherUnicode(f) && (IsGlobal(f) || IsSticky(f)); +} + +// clang-format off +#define V(Lower, Camel, LowerCamel, Char, Bit) \ + c == Char ? RegExpFlag::k##Camel : +constexpr std::optional TryRegExpFlagFromChar(char c) { + return REGEXP_FLAG_LIST(V) std::optional{}; +} +#undef V +// clang-format on + +std::ostream& operator<<(std::ostream& os, RegExpFlags flags); + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_FLAGS_H_ diff --git a/runtime/vm/regexp/regexp-interpreter.cc b/runtime/vm/regexp/regexp-interpreter.cc new file mode 100644 index 00000000000..404980fbc52 --- /dev/null +++ b/runtime/vm/regexp/regexp-interpreter.cc @@ -0,0 +1,1269 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A simple interpreter for the Irregexp byte code. + +#include "vm/regexp/regexp-interpreter.h" + +#include + +#include "vm/exceptions.h" +#include "vm/regexp/regexp-bytecodes-inl.h" +#include "vm/regexp/regexp-bytecodes.h" +#include "vm/regexp/regexp-macro-assembler.h" +#include "vm/regexp/regexp.h" +#include "vm/regexp/small-vector.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/uchar.h" +#endif // V8_INTL_SUPPORT + +// Use token threaded dispatch iff the compiler supports computed gotos and the +// build argument v8_enable_regexp_interpreter_threaded_dispatch was set. +#if V8_HAS_COMPUTED_GOTO && \ + defined(V8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH) +#define V8_USE_COMPUTED_GOTO 1 +#endif // V8_HAS_COMPUTED_GOTO + +namespace dart { + +namespace { + +bool BackRefMatchesNoCase(Thread* thread, + int from, + int current, + int len, + base::Vector subject, + bool unicode) { + Address offset_a = + reinterpret_cast
(const_cast(&subject.at(from))); + Address offset_b = + reinterpret_cast
(const_cast(&subject.at(current))); + size_t length = len * base::kUC16Size; + + bool result = unicode + ? RegExpMacroAssembler::CaseInsensitiveCompareUnicode( + offset_a, offset_b, length, thread->isolate()) + : RegExpMacroAssembler::CaseInsensitiveCompareNonUnicode( + offset_a, offset_b, length, thread->isolate()); + return result == 1; +} + +bool BackRefMatchesNoCase(Thread* thread, + int from, + int current, + int len, + base::Vector subject, + bool unicode) { + // For Latin1 characters the unicode flag makes no difference. + for (int i = 0; i < len; i++) { + unsigned int old_char = subject[from++]; + unsigned int new_char = subject[current++]; + if (old_char == new_char) continue; + // Convert both characters to lower case. + old_char |= 0x20; + new_char |= 0x20; + if (old_char != new_char) return false; + // Not letters in the ASCII range and Latin-1 range. + if (!(old_char - 'a' <= 'z' - 'a') && + !(old_char - 224 <= 254 - 224 && old_char != 247)) { + return false; + } + } + return true; +} + +#ifdef ENABLE_DISASSEMBLER +void MaybeTraceInterpreter(const uint8_t* code_base, + const uint8_t* pc, + int stack_depth, + int current_position, + uint32_t current_char, + int bytecode_length, + const char* bytecode_name) { + if (v8_flags.trace_regexp_bytecodes) { + // The behaviour of std::isprint is undefined if the value isn't + // representable as unsigned char. + const bool is_single_char = + current_char <= std::numeric_limits::max(); + const bool printable = is_single_char ? std::isprint(current_char) : false; + const char* format = + printable ? "pc = %02x, sp = %d, curpos = %d, curchar = %08x (%c), " + : "pc = %02x, sp = %d, curpos = %d, curchar = %08x .%c., "; + PrintF(format, pc - code_base, stack_depth, current_position, current_char, + printable ? current_char : '.'); + + RegExpBytecodeDisassembleSingle(code_base, pc); + } +} +#endif // ENABLE_DISASSEMBLER + +template +constexpr int BitsPerChar() { + return kBitsPerByte * sizeof(Char); +} + +template +uint32_t Load2Characters(const base::Vector& string, int index) { + return string[index] | (string[index + 1] << BitsPerChar()); +} + +uint32_t Load4Characters(const base::Vector& string, int index) { + return string[index] | (string[index + 1] << 8) | (string[index + 2] << 16) | + (string[index + 3] << 24); +} + +uint32_t Load4Characters(const base::Vector&, int) { + UNREACHABLE(); +} + +// A simple abstraction over the backtracking stack used by the interpreter. +// +// Despite the name 'backtracking' stack, it's actually used as a generic stack +// that stores both program counters (= offsets into the bytecode) and generic +// integer values. +class BacktrackStack { + public: + BacktrackStack() = default; + BacktrackStack(const BacktrackStack&) = delete; + BacktrackStack& operator=(const BacktrackStack&) = delete; + + V8_WARN_UNUSED_RESULT bool push(int v) { + data_.emplace_back(v); + return (static_cast(data_.size()) <= kMaxSize); + } + int peek() const { + SBXCHECK(!data_.empty()); + return data_.back(); + } + int pop() { + int v = peek(); + data_.pop_back(); + return v; + } + + // The 'sp' is the index of the first empty element in the stack. + int sp() const { return static_cast(data_.size()); } + void set_sp(uint32_t new_sp) { + // Dart: V8 has mixed sign comparison. + // DCHECK_LE(new_sp, sp()); + data_.resize(new_sp); + } + + private: + // Semi-arbitrary. Should be large enough for common cases to remain in the + // static stack-allocated backing store, but small enough not to waste space. + static constexpr int kStaticCapacity = 64; + + using ValueT = int; + base::SmallVector data_; + + static constexpr int kMaxSize = 64 * MB / sizeof(ValueT); +}; + +// Registers used during interpreter execution. These consist of output +// registers in indices [0, output_register_count[ which will contain matcher +// results as a {start,end} index tuple for each capture (where the whole match +// counts as implicit capture 0); and internal registers in indices +// [output_register_count, total_register_count[. +class InterpreterRegisters { + public: + using RegisterT = int; + static constexpr int kNoMatchValue = -1; + + InterpreterRegisters(int total_register_count, + RegisterT* output_registers, + int output_register_count) + : registers_(total_register_count), + output_registers_(output_registers), + total_register_count_(total_register_count), + output_register_count_(output_register_count) { + // TODO(jgruber): Use int32_t consistently for registers. Currently, CSA + // uses int32_t while runtime uses int. + static_assert(sizeof(int) == sizeof(int32_t)); + SBXCHECK_GE(output_register_count, 2); // At least 2 for the match itself. + SBXCHECK_GE(total_register_count, output_register_count); + SBXCHECK_LE(total_register_count, RegExpMacroAssembler::kMaxRegisterCount); + DCHECK_NOT_NULL(output_registers); + + // Initialize the output register region to -1 signifying 'no match'. + std::memset(registers_.data(), kNoMatchValue, + output_register_count * sizeof(RegisterT)); + USE(total_register_count_); + } + + const RegisterT& operator[](size_t index) const { + // Dart: V8 has mixed sign comparison + // SBXCHECK_LT(index, total_register_count_); + return registers_[index]; + } + RegisterT& operator[](size_t index) { + // Dart: V8 has mixed sign comparison + // SBXCHECK_LT(index, total_register_count_); + return registers_[index]; + } + + void CopyToOutputRegisters() { + base::MemCopy(output_registers_, registers_.data(), + output_register_count_ * sizeof(RegisterT)); + } + + private: + static constexpr int kStaticCapacity = 64; // Arbitrary. + base::SmallVector registers_; + RegisterT* const output_registers_; + const int total_register_count_; + const int output_register_count_; +}; + +IrregexpInterpreter::Result ThrowStackOverflow( + Thread* thread, + RegExpStatics::CallOrigin call_origin) { + CHECK(call_origin == RegExpStatics::CallOrigin::kFromRuntime); + + Exceptions::ThrowStackOverflow(); +} + +// Only throws if called from the runtime, otherwise just returns the EXCEPTION +// status code. +IrregexpInterpreter::Result MaybeThrowStackOverflow( + Thread* thread, + RegExpStatics::CallOrigin call_origin) { + if (call_origin == RegExpStatics::CallOrigin::kFromRuntime) { + return ThrowStackOverflow(thread, call_origin); + } else { + return IrregexpInterpreter::EXCEPTION; + } +} + +bool CheckBitInTable(const uint32_t current_char, const uint8_t* const table) { + int mask = RegExpMacroAssembler::kTableMask; + int b = table[(current_char & mask) >> kBitsPerByteLog2]; + int bit = (current_char & (kBitsPerByte - 1)); + return (b & (1 << bit)) != 0; +} + +// Returns true iff 0 <= index < length. +bool IndexIsInBounds(int index, int length) { + DCHECK_GE(length, 0); + return static_cast(index) < static_cast(length); +} + +// If computed gotos are supported by the compiler, we can get addresses to +// labels directly in C/C++. Every bytecode handler has its own label and we +// store the addresses in a dispatch table indexed by bytecode. To execute the +// next handler we simply jump (goto) directly to its address. +#if V8_USE_COMPUTED_GOTO +#define BC_LABEL(name) BC_k##name: +#define DECODE() \ + do { \ + RegExpBytecode next_bc = RegExpBytecodes::FromPtr(next_pc); \ + next_handler_addr = \ + dispatch_table[RegExpBytecodes::ToByte(next_bc) & kBytecodeMask]; \ + } while (false) +#define DISPATCH() \ + pc = next_pc; \ + goto* next_handler_addr +// Without computed goto support, we fall back to a simple switch-based +// dispatch (A large switch statement inside a loop with a case for every +// bytecode). +#else // V8_USE_COMPUTED_GOTO +#define BC_LABEL(name) case RegExpBytecode::k##name: +#define DECODE() ((void)0) +#define DISPATCH() \ + pc = next_pc; \ + goto switch_dispatch_continuation +#endif // V8_USE_COMPUTED_GOTO + +// ADVANCE/SET_PC_FROM_OFFSET are separated from DISPATCH, because ideally some +// instructions can be executed between ADVANCE/SET_PC_FROM_OFFSET and DISPATCH. +// We want those two macros as far apart as possible, because the goto in +// DISPATCH is dependent on a memory load in ADVANCE/SET_PC_FROM_OFFSET. If we +// don't hit the cache and have to fetch the next handler address from physical +// memory, instructions between ADVANCE/SET_PC_FROM_OFFSET and DISPATCH can +// potentially be executed unconditionally, reducing memory stall. +#define ADVANCE() \ + next_pc = pc + RegExpBytecodes::Size(current_bc); \ + DECODE() + +#define SET_PC_FROM_OFFSET(offset) \ + next_pc = code_base + offset; \ + DECODE() + +// Current position mutations. +#define SET_CURRENT_POSITION(value) \ + do { \ + current = (value); \ + ASSERT(base::IsInRange(current, 0, subject.length())); \ + } while (false) +#define ADVANCE_CURRENT_POSITION(by) SET_CURRENT_POSITION(current + (by)) + +// These weird looking macros are required for clang-format and cpplint to not +// interfere/complain about our logic of opening/closing blocks in our macros. +#define OPEN_BLOCK { +#define CLOSE_BLOCK } +#define BYTECODES_START() OPEN_BLOCK +#define BYTECODES_END() CLOSE_BLOCK + +#ifdef ENABLE_DISASSEMBLER +#define BYTECODE(Name, ...) \ + CLOSE_BLOCK \ + BC_LABEL(Name) OPEN_BLOCK INIT(Name __VA_OPT__(, ) __VA_ARGS__); \ + MaybeTraceInterpreter(code_base, pc, backtrack_stack.sp(), current, \ + current_char, RegExpBytecodes::Size(current_bc), \ + #Name); +#else +#define BYTECODE(Name, ...) \ + CLOSE_BLOCK \ + BC_LABEL(Name) OPEN_BLOCK INIT(Name __VA_OPT__(, ) __VA_ARGS__); +#endif // ENABLE_DISASSEMBLER + +#define DEAD_BYTECODE(Name, ...) \ + CLOSE_BLOCK \ + BC_LABEL(Name) OPEN_BLOCK { \ + UNREACHABLE(); \ + } + +#define INIT(Name, ...) \ + constexpr RegExpBytecode current_bc = RegExpBytecode::k##Name; \ + using Operands = RegExpBytecodeOperands; \ + __VA_OPT__(auto argument_tuple = std::apply( \ + [&](auto... ops) { \ + return std::make_tuple( \ + Operands::template Get(pc, no_gc)...); \ + }, \ + Operands::GetOperandsTuple()); \ + auto [__VA_ARGS__] = argument_tuple;) \ + static_assert((IS_VA_EMPTY(__VA_ARGS__)) == (Operands::kCount == 0), \ + "Number of arguments to VISIT doesn't match the bytecodes " \ + "operands count") + +namespace { + +template +bool CheckSpecialClassRanges(uint32_t current_char, + StandardCharacterSet character_set) { + constexpr bool is_one_byte = sizeof(Char) == 1; + switch (character_set) { + case StandardCharacterSet::kWhitespace: + ASSERT(is_one_byte); + if (current_char == ' ' || base::IsInRange(current_char, '\t', '\r') || + current_char == 0xA0) { + return true; + } + return false; + case StandardCharacterSet::kNotWhitespace: + UNREACHABLE(); + case StandardCharacterSet::kWord: { + if constexpr (!is_one_byte) { + if (current_char > 'z') { + return false; + } + } + base::Vector word_character_map = + RegExpMacroAssembler::word_character_map(); + DCHECK_EQ(0, + word_character_map[0]); // Character '\0' is not a word char. + return word_character_map[current_char] != 0; + return true; + } + case StandardCharacterSet::kNotWord: { + if constexpr (!is_one_byte) { + if (current_char > 'z') { + return true; + } + } + base::Vector word_character_map = + RegExpMacroAssembler::word_character_map(); + DCHECK_EQ(0, + word_character_map[0]); // Character '\0' is not a word char. + return word_character_map[current_char] == 0; + } + case StandardCharacterSet::kDigit: + if (base::IsInRange(current_char, '0', '9')) { + return true; + } + return false; + case StandardCharacterSet::kNotDigit: + if (base::IsInRange(current_char, '0', '9')) { + return false; + } + return true; + case StandardCharacterSet::kLineTerminator: { + if (current_char == '\n' || current_char == '\r') { + return true; + } + if constexpr (!is_one_byte) { + if (current_char == 0x2028 || current_char == 0x2029) { + return true; + } + } + return false; + } + case StandardCharacterSet::kNotLineTerminator: { + const bool is_one_byte_match = + current_char != '\n' && current_char != '\r'; + if constexpr (is_one_byte) { + if (is_one_byte_match) { + return true; + } + } else { + if (is_one_byte_match && current_char != 0x2028 && + current_char != 0x2029) { + return true; + } + } + return false; + } + case StandardCharacterSet::kEverything: + return true; + } + UNREACHABLE(); + return false; +} + +} // namespace + +template +IrregexpInterpreter::Result RawMatch(Thread* thread, + const TypedData& code_array, + const String& subject_string, + base::Vector subject, + int* output_registers, + int output_register_count, + int total_register_count, + int current, + uint32_t current_char, + RegExpStatics::CallOrigin call_origin, + const uint32_t backtrack_limit) { + DisallowGarbageCollection no_gc; + +#if V8_USE_COMPUTED_GOTO + + // Maximum number of bytecodes that will be used (next power of 2 of actually + // defined bytecodes). + // All slots between the last actually defined bytecode and maximum id will be + // filled with kBreaks, indicating an invalid operation. This way using + // kBytecodeMask guarantees no OOB access to the dispatch table. + constexpr int kPaddedBytecodeCount = + Utils::RoundUpToPowerOfTwo(RegExpBytecodes::kCount); + constexpr int kBytecodeMask = kPaddedBytecodeCount - 1; + static_assert(std::numeric_limits::max() >= kBytecodeMask); + + // We have to make sure that no OOB access to the dispatch table is possible + // and all values are valid label addresses. Otherwise jumps to arbitrary + // addresses could potentially happen. This is ensured as follows: Every index + // to the dispatch table gets masked using kBytecodeMask in DECODE(). This way + // we can only get values between 0 (only the least significant byte of an + // integer is used) and kPaddedBytecodeCount - 1 (kBytecodeMask is defined to + // be exactly this value). All entries from RegExpBytecodes::kCount to + // kRegExpPaddedBytecodeCount are automatically filled with kBreak (invalid + // operation). + +#define DECLARE_DISPATCH_TABLE_ENTRY(name, ...) &&BC_k##name, + static const void* const unsafe_dispatch_table[RegExpBytecodes::kCount] = { + REGEXP_BYTECODE_LIST(DECLARE_DISPATCH_TABLE_ENTRY)}; +#undef DECLARE_DISPATCH_TABLE_ENTRY +#undef BYTECODE_FILLER_ITERATOR + + static const void* const filler_entry = &&BC_kBreak; + static const std::array dispatch_table = + [=]() { + std::array table; + + size_t i = 0; + // Copy all valid Bytecodes to the dispatch table. + for (; i < RegExpBytecodes::kCount; ++i) { + table[i] = unsafe_dispatch_table[i]; + } + // Fill dispatch table from last defined bytecode up to the next power + // of two with kBreak (invalid operation). + for (; i < kPaddedBytecodeCount; ++i) { + table[i] = filler_entry; + } + return table; + }(); + +#endif // V8_USE_COMPUTED_GOTO + + const uint8_t* pc; + const uint8_t* code_base; + { + NoSafepointScope no_safepoint(thread); + pc = code_base = reinterpret_cast(code_array.DataAddr(0)); + } + + InterpreterRegisters registers(total_register_count, output_registers, + output_register_count); + BacktrackStack backtrack_stack; + + uint32_t backtrack_count = 0; + + while (true) { + const uint8_t* next_pc = pc; +#if V8_USE_COMPUTED_GOTO + const void* next_handler_addr; + DECODE(); + DISPATCH(); +#else + switch (RegExpBytecodes::FromPtr(pc)) { +#endif // V8_USE_COMPUTED_GOTO + BYTECODES_START() + BYTECODE(Break) { + UNREACHABLE(); + } + BYTECODE(PushCurrentPosition) { + ADVANCE(); + if (!backtrack_stack.push(current)) { + return MaybeThrowStackOverflow(thread, call_origin); + } + DISPATCH(); + } + BYTECODE(PushBacktrack, label) { + ADVANCE(); + if (!backtrack_stack.push(label)) { + return MaybeThrowStackOverflow(thread, call_origin); + } + DISPATCH(); + } + BYTECODE(PushRegister, register_index, stack_check) { + ADVANCE(); + USE(stack_check); // Unused in interpreter. + if (!backtrack_stack.push(registers[register_index])) { + return MaybeThrowStackOverflow(thread, call_origin); + } + DISPATCH(); + } + BYTECODE(SetRegister, register_index, value) { + ADVANCE(); + registers[register_index] = value; + DISPATCH(); + } + BYTECODE(ClearRegisters, from_register, to_register) { + ADVANCE(); + SBXCHECK_LE(from_register, to_register); + for (uint16_t i = from_register; i <= to_register; ++i) { + registers[i] = InterpreterRegisters::kNoMatchValue; + } + DISPATCH(); + } + BYTECODE(AdvanceRegister, register_index, by) { + ADVANCE(); + registers[register_index] += by; + DISPATCH(); + } + BYTECODE(WriteCurrentPositionToRegister, register_index, cp_offset) { + ADVANCE(); + registers[register_index] = current + cp_offset; + DISPATCH(); + } + BYTECODE(ReadCurrentPositionFromRegister, register_index) { + ADVANCE(); + SET_CURRENT_POSITION(registers[register_index]); + DISPATCH(); + } + BYTECODE(WriteStackPointerToRegister, register_index) { + ADVANCE(); + registers[register_index] = backtrack_stack.sp(); + DISPATCH(); + } + BYTECODE(ReadStackPointerFromRegister, register_index) { + ADVANCE(); + backtrack_stack.set_sp(registers[register_index]); + DISPATCH(); + } + BYTECODE(PopCurrentPosition) { + ADVANCE(); + SET_CURRENT_POSITION(backtrack_stack.pop()); + DISPATCH(); + } + BYTECODE(Backtrack, return_code) { + static_assert(JSRegExp::kNoBacktrackLimit == 0); + if (++backtrack_count == backtrack_limit) { + return static_cast(return_code); + } + + if (UNLIKELY(thread->HasScheduledInterrupts())) { + intptr_t pc_offset = pc - code_base; + ErrorPtr error = thread->HandleInterrupts(); + if (error != Object::null()) { + // Not throwing directly because we first need to run destructors for + // types that aren't ThreadResources. + thread->set_sticky_error(Error::Handle(error)); + return IrregexpInterpreter::EXCEPTION; + } + + NoSafepointScope no_safepoint(thread); + code_base = reinterpret_cast(code_array.DataAddr(0)); + pc = code_base + pc_offset; + subject = {NByteString::DataStart(subject_string), + static_cast(subject_string.Length())}; + } + + SET_PC_FROM_OFFSET(backtrack_stack.pop()); + DISPATCH(); + } + BYTECODE(PopRegister, register_index) { + ADVANCE(); + registers[register_index] = backtrack_stack.pop(); + DISPATCH(); + } + BYTECODE(Fail) { + //isolate->counters()->regexp_backtracks()->AddSample( + // static_cast(backtrack_count)); + return IrregexpInterpreter::FAILURE; + } + BYTECODE(Succeed) { + //isolate->counters()->regexp_backtracks()->AddSample( + // static_cast(backtrack_count)); + registers.CopyToOutputRegisters(); + return IrregexpInterpreter::SUCCESS; + } + BYTECODE(AdvanceCurrentPosition, by) { + ADVANCE(); + ADVANCE_CURRENT_POSITION(by); + DISPATCH(); + } + BYTECODE(GoTo, label) { + SET_PC_FROM_OFFSET(label); + DISPATCH(); + } + BYTECODE(AdvanceCpAndGoto, by, on_goto) { + SET_PC_FROM_OFFSET(on_goto); + ADVANCE_CURRENT_POSITION(by); + DISPATCH(); + } + BYTECODE(CheckFixedLengthLoop, on_tos_equals_current_position) { + if (current == backtrack_stack.peek()) { + SET_PC_FROM_OFFSET(on_tos_equals_current_position); + backtrack_stack.pop(); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(LoadCurrentCharacter, cp_offset, on_failure) { + int pos = current + cp_offset; + if (pos >= subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(on_failure); + } else { + ADVANCE(); + current_char = subject[pos]; + } + DISPATCH(); + } + BYTECODE(LoadCurrentCharacterUnchecked, cp_offset) { + ADVANCE(); + int pos = current + cp_offset; + current_char = subject[pos]; + DISPATCH(); + } + BYTECODE(Load2CurrentChars, cp_offset, on_failure) { + int pos = current + cp_offset; + if (pos + 2 > subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(on_failure); + } else { + ADVANCE(); + current_char = Load2Characters(subject, pos); + } + DISPATCH(); + } + BYTECODE(Load2CurrentCharsUnchecked, cp_offset) { + ADVANCE(); + int pos = current + cp_offset; + current_char = Load2Characters(subject, pos); + DISPATCH(); + } + BYTECODE(Load4CurrentChars, cp_offset, on_failure) { + DCHECK_EQ(1, sizeof(Char)); + int pos = current + cp_offset; + if (pos + 4 > subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(on_failure); + } else { + ADVANCE(); + current_char = Load4Characters(subject, pos); + } + DISPATCH(); + } + BYTECODE(Load4CurrentCharsUnchecked, cp_offset) { + ADVANCE(); + DCHECK_EQ(1, sizeof(Char)); + int pos = current + cp_offset; + current_char = Load4Characters(subject, pos); + DISPATCH(); + } + BYTECODE(Check4Chars, characters, on_equal) { + if (characters == current_char) { + SET_PC_FROM_OFFSET(on_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacter, character, on_equal) { + if (character == current_char) { + SET_PC_FROM_OFFSET(on_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNot4Chars, characters, on_not_equal) { + if (characters != current_char) { + SET_PC_FROM_OFFSET(on_not_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNotCharacter, character, on_not_equal) { + if (character != current_char) { + SET_PC_FROM_OFFSET(on_not_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(AndCheck4Chars, characters, mask, on_equal) { + if (characters == (current_char & mask)) { + SET_PC_FROM_OFFSET(on_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacterAfterAnd, character, mask, on_equal) { + if (character == (current_char & mask)) { + SET_PC_FROM_OFFSET(on_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(AndCheckNot4Chars, characters, mask, on_not_equal) { + if (characters != (current_char & mask)) { + SET_PC_FROM_OFFSET(on_not_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNotCharacterAfterAnd, character, mask, on_not_equal) { + if (character != (current_char & mask)) { + SET_PC_FROM_OFFSET(on_not_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNotCharacterAfterMinusAnd, character, minus, mask, + on_not_equal) { + if (character != ((current_char - minus) & mask)) { + SET_PC_FROM_OFFSET(on_not_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacterInRange, from, to, on_in_range) { + if (from <= current_char && current_char <= to) { + SET_PC_FROM_OFFSET(on_in_range); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacterNotInRange, from, to, on_not_in_range) { + if (from > current_char || current_char > to) { + SET_PC_FROM_OFFSET(on_not_in_range); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckBitInTable, on_bit_set, table) { + if (CheckBitInTable(current_char, table)) { + SET_PC_FROM_OFFSET(on_bit_set); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacterLT, limit, on_less) { + if (current_char < limit) { + SET_PC_FROM_OFFSET(on_less); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckCharacterGT, limit, on_greater) { + if (current_char > limit) { + SET_PC_FROM_OFFSET(on_greater); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(IfRegisterLT, register_index, comparand, on_less_than) { + if (registers[register_index] < comparand) { + SET_PC_FROM_OFFSET(on_less_than); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(IfRegisterGE, register_index, comparand, on_greater_or_equal) { + if (registers[register_index] >= comparand) { + SET_PC_FROM_OFFSET(on_greater_or_equal); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(IfRegisterEqPos, register_index, on_eq) { + if (registers[register_index] == current) { + SET_PC_FROM_OFFSET(on_eq); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNotBackRef, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !CompareCharsEqual(&subject[from], &subject[current], len)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckNotBackRefBackward, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !CompareCharsEqual(&subject[from], &subject[current - len], len)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckNotBackRefNoCaseUnicode, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !BackRefMatchesNoCase(thread, from, current, len, subject, true)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckNotBackRefNoCase, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !BackRefMatchesNoCase(thread, from, current, len, subject, false)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckNotBackRefNoCaseUnicodeBackward, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !BackRefMatchesNoCase(thread, from, current - len, len, subject, + true)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckNotBackRefNoCaseBackward, start_reg, on_not_equal) { + int from = registers[start_reg]; + int len = registers[start_reg + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !BackRefMatchesNoCase(thread, from, current - len, len, subject, + false)) { + SET_PC_FROM_OFFSET(on_not_equal); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(); + DISPATCH(); + } + BYTECODE(CheckAtStart, cp_offset, on_at_start) { + if (current + cp_offset == 0) { + SET_PC_FROM_OFFSET(on_at_start); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckNotAtStart, cp_offset, on_not_at_start) { + if (current + cp_offset == 0) { + ADVANCE(); + } else { + SET_PC_FROM_OFFSET(on_not_at_start); + } + DISPATCH(); + } + BYTECODE(SetCurrentPositionFromEnd, by) { + ADVANCE(); + if (subject.length() - current > by) { + SET_CURRENT_POSITION(subject.length() - by); + current_char = subject[current - 1]; + } + DISPATCH(); + } + BYTECODE(CheckPosition, cp_offset, on_failure) { + int pos = current + cp_offset; + if (pos >= subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(on_failure); + } else { + ADVANCE(); + } + DISPATCH(); + } + BYTECODE(CheckSpecialClassRanges, character_set, on_no_match) { + const bool match = + CheckSpecialClassRanges(current_char, character_set); + if (match) { + ADVANCE(); + } else { + SET_PC_FROM_OFFSET(on_no_match); + } + DISPATCH(); + } + BYTECODE(SkipUntilChar, cp_offset, advance_by, character, on_match, + on_no_match) { + while (IndexIsInBounds(current + cp_offset, subject.length())) { + current_char = subject[current + cp_offset]; + if (character == current_char) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilCharAnd, cp_offset, advance_by, character, mask, + eats_at_least, on_match, on_no_match) { + while (IndexIsInBounds(current + eats_at_least, subject.length())) { + current_char = subject[current + cp_offset]; + if (character == (current_char & mask)) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilCharPosChecked, cp_offset, advance_by, character, + eats_at_least, on_match, on_no_match) { + while (IndexIsInBounds(current + eats_at_least, subject.length())) { + current_char = subject[current + cp_offset]; + if (character == current_char) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilBitInTable, cp_offset, advance_by, table, on_match, + on_no_match) { + while (IndexIsInBounds(current + cp_offset, subject.length())) { + current_char = subject[current + cp_offset]; + if (CheckBitInTable(current_char, table)) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilGtOrNotBitInTable, cp_offset, advance_by, character, + table, on_match, on_no_match) { + while (IndexIsInBounds(current + cp_offset, subject.length())) { + current_char = subject[current + cp_offset]; + if (current_char > character) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + if (!CheckBitInTable(current_char, table)) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilCharOrChar, cp_offset, advance_by, char1, char2, on_match, + on_no_match) { + while (IndexIsInBounds(current + cp_offset, subject.length())) { + current_char = subject[current + cp_offset]; + // The two if-statements below are split up intentionally, as combining + // them seems to result in register allocation behaving quite + // differently and slowing down the resulting code. + if (char1 == current_char) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + if (char2 == current_char) { + SET_PC_FROM_OFFSET(on_match); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_no_match); + DISPATCH(); + } + BYTECODE(SkipUntilOneOfMasked, cp_offset, advance_by, both_chars, both_mask, + max_offset, chars1, mask1, chars2, mask2, on_match1, on_match2, + on_failure) { + DCHECK_GE(cp_offset, 0); + DCHECK_GE(max_offset, cp_offset); + // We should only get here in 1-byte mode. + DCHECK_EQ(1, sizeof(Char)); + while (IndexIsInBounds(current + max_offset, subject.length())) { + int pos = current + cp_offset; + current_char = Load4Characters(subject, pos); + if (both_chars == (current_char & both_mask)) { + if (chars1 == (current_char & mask1)) { + SET_PC_FROM_OFFSET(on_match1); + DISPATCH(); + } + if (chars2 == (current_char & mask2)) { + SET_PC_FROM_OFFSET(on_match2); + DISPATCH(); + } + } + ADVANCE_CURRENT_POSITION(advance_by); + } + SET_PC_FROM_OFFSET(on_failure); + DISPATCH(); + } + // MSVC: compiler limit: initializers nested too deeply + // But only generated by optimizer that Dart disables. + DEAD_BYTECODE(SkipUntilOneOfMasked3) { + UNREACHABLE(); + } + BYTECODES_END() +#if V8_USE_COMPUTED_GOTO +// Lint gets confused a lot if we just use !V8_USE_COMPUTED_GOTO or ifndef +// V8_USE_COMPUTED_GOTO here. +#else + default: + UNREACHABLE(); + } + // Label we jump to in DISPATCH(). There must be no instructions between the + // end of the switch, this label and the end of the loop. + switch_dispatch_continuation: {} +#endif // V8_USE_COMPUTED_GOTO + } +} + +#undef OPEN_BLOCK +#undef CLOSE_BLOCK +#undef BYTECODES_START +#undef BYTECODES_END +#undef BYTECODE +#undef ADVANCE_CURRENT_POSITION +#undef SET_CURRENT_POSITION +#undef DISPATCH +#undef DECODE +#undef SET_PC_FROM_OFFSET +#undef ADVANCE +#undef BC_LABEL +#undef V8_USE_COMPUTED_GOTO + +} // namespace + +// static +int IrregexpInterpreter::Match(Thread* thread, + const RegExp& regexp_data, + const String& subject_string, + int* output_registers, + int output_register_count, + int start_position, + RegExpStatics::CallOrigin call_origin, + bool is_sticky) { + // bool is_any_unicode = IsEitherUnicode((regexp_data.flags())); + bool is_one_byte = subject_string.IsOneByteString(); + SBXCHECK(regexp_data.has_bytecode(is_one_byte, is_sticky)); + const TypedData& code_array = + TypedData::Handle(regexp_data.bytecode(is_one_byte, is_sticky)); + int total_register_count = regexp_data.num_registers(is_one_byte); + // MatchInternal only supports returning a single match per call. In global + // mode, i.e. when output_registers has space for more than one match, we + // need to keep running until all matches are filled in. + int registers_per_match = + JSRegExp::RegistersForCaptureCount(regexp_data.num_bracket_expressions()); + // DCHECK_LE(registers_per_match, output_register_count); + // int number_of_matches_in_output_registers = + // output_register_count / registers_per_match; + + int backtrack_limit = JSRegExp::kNoBacktrackLimit; + +#ifdef ENABLE_DISASSEMBLER + if (v8_flags.trace_regexp_bytecodes) { + static constexpr uint32_t kTruncateSubjectAtLength = 64; + const char* opt_truncated = ""; + uint32_t subject_length = subject_string->length(); + if (subject_length > kTruncateSubjectAtLength) { + subject_length = kTruncateSubjectAtLength; + opt_truncated = " (truncated)"; + } + Tagged pattern = Cast(regexp_data->source()); + PrintF("\n\nStart bytecode interpreter. Pattern /%s/ Subject '%s'%s\n", + pattern->ToCString().get(), + subject_string->ToCString(0, subject_length).get(), opt_truncated); + } +#endif + + int* current_output_registers = output_registers; + return MatchInternal(thread, code_array, subject_string, + current_output_registers, registers_per_match, + total_register_count, start_position, call_origin, + backtrack_limit); +} + +IrregexpInterpreter::Result IrregexpInterpreter::MatchInternal( + Thread* thread, + const TypedData& code_array, + const String& subject_string, + int* output_registers, + int output_register_count, + int total_register_count, + int start_position, + RegExpStatics::CallOrigin call_origin, + uint32_t backtrack_limit) { + // Note: Heap allocation *is* allowed in two situations if calling from + // Runtime: + // 1. When creating & throwing a stack overflow exception. The interpreter + // aborts afterwards, and thus possible-moved objects are never used. + // 2. When handling interrupts. We manually relocate unhandlified references + // after interrupts have run. + + uint16_t previous_char = '\n'; + // Because interrupts can result in GC and string content relocation, the + // checksum verification in FlatContent may fail even though this code is + // safe. See (2) above. + //subject_content.UnsafeDisableChecksumVerification(); + if (subject_string.IsOneByteString()) { + base::Vector subject_vector; + { + NoSafepointScope no_safepoint(thread); + subject_vector = {OneByteString::DataStart(subject_string), + (size_t)subject_string.Length()}; + } + if (start_position != 0) previous_char = subject_vector[start_position - 1]; + return RawMatch( + thread, code_array, subject_string, subject_vector, output_registers, + output_register_count, total_register_count, start_position, + previous_char, call_origin, backtrack_limit); + } else { + ASSERT(subject_string.IsTwoByteString()); + base::Vector subject_vector; + { + NoSafepointScope no_safepoint(thread); + subject_vector = {TwoByteString::DataStart(subject_string), + (size_t)subject_string.Length()}; + } + if (start_position != 0) previous_char = subject_vector[start_position - 1]; + return RawMatch( + thread, code_array, subject_string, subject_vector, output_registers, + output_register_count, total_register_count, start_position, + previous_char, call_origin, backtrack_limit); + } +} + +#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// This method is called through an external reference from RegExpExecInternal +// builtin. +#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT +// Hardware sandboxing is incompatible with ASAN, see crbug.com/432168626. +DISABLE_ASAN +#endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT +int IrregexpInterpreter::MatchForCallFromJs( + Address subject, + int32_t start_position, + Address, + Address, + int* output_registers, + int32_t output_register_count, + RegExpStatics::CallOrigin call_origin, + Isolate* isolate, + Address regexp_data) { + // TODO(422992937): investigate running the interpreter in sandboxed mode. + ExitSandboxScope unsandboxed; + + DCHECK_NOT_NULL(isolate); + DCHECK_NOT_NULL(output_registers); + ASSERT(call_origin == RegExpStatics::CallOrigin::kFromJs); + + DisallowGarbageCollection no_gc; + DisallowJavascriptExecution no_js(isolate); + DisallowHandleAllocation no_handles; + DisallowHandleDereference no_deref; + + Tagged subject_string = Cast(Tagged(subject)); + Tagged regexp_data_obj = + SbxCast(Tagged(regexp_data)); + + if (regexp_data_obj->MarkedForTierUp()) { + // Returning RETRY will re-enter through runtime, where actual recompilation + // for tier-up takes place. + return IrregexpInterpreter::RETRY; + } + + return Match(isolate, regexp_data_obj, subject_string, output_registers, + output_register_count, start_position, call_origin); +} + +#endif // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +int IrregexpInterpreter::MatchForCallFromRuntime(Thread* thread, + const RegExp& regexp_data, + const String& subject_string, + int* output_registers, + int output_register_count, + int start_position, + bool is_sticky) { + return Match(thread, regexp_data, subject_string, output_registers, + output_register_count, start_position, + RegExpStatics::CallOrigin::kFromRuntime, is_sticky); +} + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-interpreter.h b/runtime/vm/regexp/regexp-interpreter.h new file mode 100644 index 00000000000..2c351db5701 --- /dev/null +++ b/runtime/vm/regexp/regexp-interpreter.h @@ -0,0 +1,83 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_INTERPRETER_H_ +#define V8_REGEXP_REGEXP_INTERPRETER_H_ + +// A simple interpreter for the Irregexp byte code. + +#include "vm/regexp/regexp.h" + +namespace dart { + +class TrustedByteArray; + +class IrregexpInterpreter : public AllStatic { + public: + enum Result { + FAILURE = RegExpStatics::kInternalRegExpFailure, + SUCCESS = RegExpStatics::kInternalRegExpSuccess, + EXCEPTION = RegExpStatics::kInternalRegExpException, + RETRY = RegExpStatics::kInternalRegExpRetry, + FALLBACK_TO_EXPERIMENTAL = + RegExpStatics::kInternalRegExpFallbackToExperimental, + }; + + // In case a StackOverflow occurs, a StackOverflowException is created and + // EXCEPTION is returned. + static int MatchForCallFromRuntime(Thread* thread, + const RegExp& regexp_data, + const String& subject_string, + int* output_registers, + int output_register_count, + int start_position, + bool is_sticky); + + // In case a StackOverflow occurs, EXCEPTION is returned. The caller is + // responsible for creating the exception. + // + // RETRY is returned if a retry through the runtime is needed (e.g. when + // interrupts have been scheduled or the regexp is marked for tier-up). + // + // Arguments input_start and input_end are unused. They are only passed to + // match the signature of the native irregex code. + // + // Arguments output_registers and output_register_count describe the results + // array, which will contain register values of all captures if one or more + // matches were found. In this case, the return value is the number of + // matches. For all other return codes, the results array remains unmodified. + static int MatchForCallFromJs(void* subject, + int32_t start_position, + void* input_start, + void* input_end, + int* output_registers, + int32_t output_register_count, + RegExpStatics::CallOrigin call_origin, + Thread* thread, + void* regexp_data); + + static Result MatchInternal(Thread* thread, + const TypedData& code_array, + const String& subject_string, + int* output_registers, + int output_register_count, + int total_register_count, + int start_position, + RegExpStatics::CallOrigin call_origin, + uint32_t backtrack_limit); + + private: + static int Match(Thread* thread, + const RegExp& regexp_data, + const String& subject_string, + int* output_registers, + int output_register_count, + int start_position, + RegExpStatics::CallOrigin call_origin, + bool is_sticky); +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_INTERPRETER_H_ diff --git a/runtime/vm/regexp/regexp-macro-assembler.cc b/runtime/vm/regexp/regexp-macro-assembler.cc new file mode 100644 index 00000000000..7fd1d5968bd --- /dev/null +++ b/runtime/vm/regexp/regexp-macro-assembler.cc @@ -0,0 +1,585 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-macro-assembler.h" + +#include + +#include "vm/regexp/base.h" +#include "vm/regexp/label.h" +#include "vm/regexp/special-case.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/uchar.h" +#include "unicode/unistr.h" +#endif // V8_INTL_SUPPORT + +namespace dart { + +RegExpMacroAssembler::RegExpMacroAssembler(Isolate* isolate, + Zone* zone, + Mode mode) + : slow_safe_compiler_(false), + backtrack_limit_(JSRegExp::kNoBacktrackLimit), + global_mode_(NOT_GLOBAL), + isolate_(isolate), + zone_(zone), + mode_(mode) {} + +bool RegExpMacroAssembler::has_backtrack_limit() const { + return backtrack_limit_ != JSRegExp::kNoBacktrackLimit; +} + +int RegExpMacroAssembler::stack_limit_slack_slot_count() const { + return 32; +} + +bool RegExpMacroAssembler::CanReadUnaligned() const { + return true; +} + +// static +int RegExpMacroAssembler::CaseInsensitiveCompareNonUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate) { +#ifdef V8_INTL_SUPPORT + DCHECK_EQ(0, byte_length % 2); + size_t length = byte_length / 2; + uint16_t* substring1 = reinterpret_cast(byte_offset1); + uint16_t* substring2 = reinterpret_cast(byte_offset2); + + for (size_t i = 0; i < length; i++) { + UChar32 c1 = RegExpCaseFolding::Canonicalize(substring1[i]); + UChar32 c2 = RegExpCaseFolding::Canonicalize(substring2[i]); + if (c1 != c2) { + return 0; + } + } + return 1; +#else + return CaseInsensitiveCompareUnicode(byte_offset1, byte_offset2, byte_length, + isolate); +#endif +} + +// static +int RegExpMacroAssembler::CaseInsensitiveCompareUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate) { + // This function is not allowed to cause a garbage collection. + // A GC might move the calling generated code and invalidate the + // return address on the stack. + DCHECK_EQ(0, byte_length % 2); + +#ifdef V8_INTL_SUPPORT + int32_t length = static_cast(byte_length >> 1); + icu::UnicodeString uni_str_1(reinterpret_cast(byte_offset1), + length); + return uni_str_1.caseCompare(reinterpret_cast(byte_offset2), + length, U_FOLD_CASE_DEFAULT) == 0; +#else + uint16_t* substring1 = reinterpret_cast(byte_offset1); + uint16_t* substring2 = reinterpret_cast(byte_offset2); + size_t length = byte_length >> 1; + DCHECK_NOT_NULL(isolate); + unibrow::Mapping* canonicalize = + isolate->regexp_macro_assembler_canonicalize(); + for (size_t i = 0; i < length; i++) { + unibrow::uchar c1 = substring1[i]; + unibrow::uchar c2 = substring2[i]; + if (c1 != c2) { + unibrow::uchar s1[1] = {c1}; + canonicalize->get(c1, '\0', s1); + if (s1[0] != c2) { + unibrow::uchar s2[1] = {c2}; + canonicalize->get(c2, '\0', s2); + if (s1[0] != s2[0]) { + return 0; + } + } + } + } + return 1; +#endif // V8_INTL_SUPPORT +} + +namespace { + +uint32_t Hash(const ZoneList* ranges) { + size_t seed = 0; + for (int i = 0; i < ranges->length(); i++) { + const CharacterRange& r = ranges->at(i); + seed ^= r.from(); + seed ^= r.to(); + } + return static_cast(seed); +} + +constexpr uint32_t MaskEndOfRangeMarker(uint32_t c) { + // CharacterRanges may use 0x10ffff as the end-of-range marker irrespective + // of whether the regexp IsUnicode or not; translate the marker value here. + DCHECK_IMPLIES(c > kMaxUint16, c == String::kMaxCodePoint); + return c & 0xffff; +} + +int RangeArrayLengthFor(const ZoneList* ranges) { + const int ranges_length = ranges->length(); + return MaskEndOfRangeMarker(ranges->at(ranges_length - 1).to()) == kMaxUint16 + ? ranges_length * 2 - 1 + : ranges_length * 2; +} + +bool Equals(const ZoneList* lhs, const TypedData& rhs) { + ASSERT(rhs.ElementSizeInBytes() == 2); // uint16 + const int rhs_length = rhs.Length(); + if (rhs_length != RangeArrayLengthFor(lhs)) return false; + for (int i = 0; i < lhs->length(); i++) { + const CharacterRange& r = lhs->at(i); + if (rhs.GetUint16(i * 2 + 0) != r.from()) return false; + if (i * 2 + 1 == rhs_length) break; + if (rhs.GetUint16(i * 2 + 1) != r.to() + 1) return false; + } + return true; +} + +TypedDataPtr MakeRangeArray(Isolate* isolate, + const ZoneList* ranges) { + const int ranges_length = ranges->length(); + const int range_array_length = RangeArrayLengthFor(ranges); + TypedData& range_array = TypedData::Handle( + TypedData::New(kTypedDataUint16ArrayCid, range_array_length)); + for (int i = 0; i < ranges_length; i++) { + const CharacterRange& r = ranges->at(i); + DCHECK_LE(r.from(), kMaxUint16); + range_array.SetUint16(i * 2 + 0, r.from()); + const uint32_t to = MaskEndOfRangeMarker(r.to()); + if (i == ranges_length - 1 && to == kMaxUint16) { + DCHECK_EQ(range_array_length, ranges_length * 2 - 1); + break; // Avoid overflow by leaving the last range open-ended. + } + DCHECK_LT(to, kMaxUint16); + range_array.SetUint16(i * 2 + 1, to + 1); // Exclusive. + } + return range_array.ptr(); +} + +} // namespace + +TypedDataPtr NativeRegExpMacroAssembler::GetOrAddRangeArray( + const ZoneList* ranges) { + const uint32_t hash = Hash(ranges); + + if (range_array_cache_.count(hash) != 0) { + TypedData* range_array = range_array_cache_[hash]; + if (Equals(ranges, *range_array)) return range_array->ptr(); + } + + TypedDataPtr range_array = MakeRangeArray(isolate(), ranges); + range_array_cache_[hash] = &TypedData::Handle(range_array); + return range_array; +} + +// static +uint32_t RegExpMacroAssembler::IsCharacterInRangeArray(uint32_t current_char, + Address raw_byte_array) { + // Use uint32_t to avoid complexity around bool return types (which may be + // optimized to use only the least significant byte). + static constexpr uint32_t kTrue = 1; + static constexpr uint32_t kFalse = 0; + + // Uint16 + const TypedData& ranges = TypedData::CheckedHandle( + Thread::Current()->zone(), UntaggedObject::FromAddr(raw_byte_array)); + DCHECK_GE(ranges.Length(), 1); + + // Shortcut for fully out of range chars. + if (current_char < ranges.GetUint16(0)) return kFalse; + if (current_char >= ranges.GetUint16(ranges.Length() - 1)) { + // The last range may be open-ended. + return (ranges.Length() % 2) == 0 ? kFalse : kTrue; + } + + // Binary search for the matching range. `ranges` is encoded as + // [from0, to0, from1, to1, ..., fromN, toN], or + // [from0, to0, from1, to1, ..., fromN] (open-ended last interval). + + int mid, lower = 0, upper = ranges.Length(); + do { + mid = lower + (upper - lower) / 2; + const uint16_t elem = ranges.GetUint16(mid); + if (current_char < elem) { + upper = mid; + } else if (current_char > elem) { + lower = mid + 1; + } else { + DCHECK_EQ(current_char, elem); + break; + } + } while (lower < upper); + + const bool current_char_ge_last_elem = current_char >= ranges.GetUint16(mid); + const int current_range_start_index = + current_char_ge_last_elem ? mid : mid - 1; + + // Ranges start at even indices and end at odd indices. + return (current_range_start_index % 2) == 0 ? kTrue : kFalse; +} + +void RegExpMacroAssembler::CheckNotInSurrogatePair(int cp_offset, + V8Label* on_failure) { + V8Label ok; + // Check that current character is not a trail surrogate. + LoadCurrentCharacter(cp_offset, &ok); + CheckCharacterNotInRange(kTrailSurrogateStart, kTrailSurrogateEnd, &ok); + // Check that previous character is not a lead surrogate. + LoadCurrentCharacter(cp_offset - 1, &ok); + CheckCharacterInRange(kLeadSurrogateStart, kLeadSurrogateEnd, on_failure); + Bind(&ok); +} + +void RegExpMacroAssembler::LoadCurrentCharacter(int cp_offset, + V8Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) { + // By default, eats_at_least = characters. + if (eats_at_least == kUseCharactersValue) { + eats_at_least = characters; + } + + LoadCurrentCharacterImpl(cp_offset, on_end_of_input, check_bounds, characters, + eats_at_least); +} + +void NativeRegExpMacroAssembler::LoadCurrentCharacterImpl( + int cp_offset, + V8Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) { + // It's possible to preload a small number of characters when each success + // path requires a large number of characters, but not the reverse. + DCHECK_GE(eats_at_least, characters); + + CHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); + if (check_bounds) { + if (cp_offset >= 0) { + CheckPosition(cp_offset + eats_at_least - 1, on_end_of_input); + } else { + CheckPosition(cp_offset, on_end_of_input); + } + } + LoadCurrentCharacterUnchecked(cp_offset, characters); +} + +void RegExpMacroAssembler::SkipUntilCharAnd(int cp_offset, + int advance_by, + unsigned character, + unsigned mask, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) { + V8Label loop; + Bind(&loop); + LoadCurrentCharacter(cp_offset, on_no_match, true, 1, eats_at_least); + CheckCharacterAfterAnd(character, mask, on_match); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +void RegExpMacroAssembler::SkipUntilChar(int cp_offset, + int advance_by, + unsigned character, + V8Label* on_match, + V8Label* on_no_match) { + V8Label loop; + Bind(&loop); + LoadCurrentCharacter(cp_offset, on_no_match, true); + CheckCharacter(character, on_match); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +void RegExpMacroAssembler::SkipUntilCharPosChecked(int cp_offset, + int advance_by, + unsigned character, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match) { + V8Label loop; + Bind(&loop); + LoadCurrentCharacter(cp_offset, on_no_match, true, 1, eats_at_least); + CheckCharacter(character, on_match); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +void RegExpMacroAssembler::SkipUntilCharOrChar(int cp_offset, + int advance_by, + unsigned char1, + unsigned char2, + V8Label* on_match, + V8Label* on_no_match) { + V8Label loop; + Bind(&loop); + LoadCurrentCharacter(cp_offset, on_no_match, true); + CheckCharacter(char1, on_match); + CheckCharacter(char2, on_match); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +void RegExpMacroAssembler::SkipUntilGtOrNotBitInTable(int cp_offset, + int advance_by, + unsigned character, + const TypedData& table, + V8Label* on_match, + V8Label* on_no_match) { + ASSERT(base::IsInRange(character, std::numeric_limits::min(), + std::numeric_limits::max())); + V8Label loop, advance_and_continue; + Bind(&loop); + LoadCurrentCharacter(cp_offset, on_no_match, true); + CheckCharacterGT(character, on_match); + CheckBitInTable(table, &advance_and_continue); + GoTo(on_match); + Bind(&advance_and_continue); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +void RegExpMacroAssembler::SkipUntilOneOfMasked(int cp_offset, + int advance_by, + unsigned both_chars, + unsigned both_mask, + int max_offset, + unsigned chars1, + unsigned mask1, + unsigned chars2, + unsigned mask2, + V8Label* on_match1, + V8Label* on_match2, + V8Label* on_failure) { + V8Label loop, found; + Bind(&loop); + CheckPosition(max_offset, on_failure); + LoadCurrentCharacter(cp_offset, on_failure, false, 4); + CheckCharacterAfterAnd(both_chars, both_mask, &found); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); + Bind(&found); + CheckCharacterAfterAnd(chars1, mask1, on_match1); + CheckCharacterAfterAnd(chars2, mask2, on_match2); + AdvanceCurrentPosition(advance_by); + GoTo(&loop); +} + +bool RegExpMacroAssembler::CanOptimizeSpecialClassRanges( + StandardCharacterSet character_set) const { + if (character_set == StandardCharacterSet::kNotWhitespace) { + // The emitted code for generic character classes is good enough. + return false; + } + if (character_set == StandardCharacterSet::kWhitespace && + mode() != Mode::LATIN1) { + // TODO(pthier): Support \s for 2-byte inputs. + return false; + } + return true; +} + +void RegExpMacroAssembler::SkipUntilOneOfMasked3( + const SkipUntilOneOfMasked3Args& args) { + // The base implementation for architectures that don't implement simd + // optimizations. + // + // See the definition of the kSkipUntilOneOfMasked3 peephole bytecode + // for more context. The initial bytecode sequence is: + // + // sequence offset name + // bc0 0 SkipUntilBitInTable + // bc1 20 CheckPosition + // bc2 28 Load4CurrentCharsUnchecked + // bc3 2c AndCheck4Chars + // bc4 3c AdvanceCpAndGoto + // bc5 48 Load4CurrentChars + // bc6 4c AndCheck4Chars + // bc7 5c AndCheck4Chars + // bc8 6c AndCheckNot4Chars + + V8Label bc0_skip_until_bit_in_table, bc1_check_current_position, + bc4_advance_cp_and_goto, bc5_load_4_current_chars; + Bind(&bc0_skip_until_bit_in_table); + SkipUntilBitInTable(args.bc0_cp_offset, *args.bc0_table, + *args.bc0_nibble_table, args.bc0_advance_by, + &bc1_check_current_position, &bc1_check_current_position); + Bind(&bc1_check_current_position); + CheckPosition(args.bc1_cp_offset, args.bc1_on_failure); + LoadCurrentCharacter(args.bc2_cp_offset, nullptr, false, 4); + CheckCharacterAfterAnd(args.bc3_characters, args.bc3_mask, + &bc5_load_4_current_chars); + Bind(&bc4_advance_cp_and_goto); + AdvanceCurrentPosition(args.bc4_by); + GoTo(&bc0_skip_until_bit_in_table); + Bind(&bc5_load_4_current_chars); + LoadCurrentCharacter(args.bc5_cp_offset, &bc4_advance_cp_and_goto, true, 4); + CheckCharacterAfterAnd(args.bc6_characters, args.bc6_mask, args.bc6_on_equal); + CheckCharacterAfterAnd(args.bc7_characters, args.bc7_mask, args.bc7_on_equal); + CheckNotCharacterAfterAnd(args.bc8_characters, args.bc8_mask, + &bc4_advance_cp_and_goto); + GoTo(args.fallthrough_jump_target); +} + +#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// Returns a {Result} sentinel, or the number of successful matches. +int NativeRegExpMacroAssembler::Match(DirectHandle regexp_data, + const String\DirectHandle subject, + int* offsets_vector, + int offsets_vector_length, + int previous_index, + Isolate* isolate) { + ASSERT(subject->IsFlat()); + DCHECK_LE(0, previous_index); + DCHECK_LE(previous_index, subject->length()); + + // No allocations before calling the regexp, but we can't use + // DisallowGarbageCollection, since regexps might be preempted, and another + // thread might do allocation anyway. + + Tagged subject_ptr = *subject; + // Character offsets into string. + int start_offset = previous_index; + int char_length = subject_ptr->length() - start_offset; + int slice_offset = 0; + + // The string has been flattened, so if it is a cons string it contains the + // full string in the first part. + if (StringShape(subject_ptr).IsCons()) { + DCHECK_EQ(0, Cast(subject_ptr)->second()->length()); + subject_ptr = Cast(subject_ptr)->first(); + } else if (StringShape(subject_ptr).IsSliced()) { + Tagged slice = Cast(subject_ptr); + subject_ptr = slice->parent(); + slice_offset = slice->offset(); + } + if (StringShape(subject_ptr).IsThin()) { + subject_ptr = Cast(subject_ptr)->actual(); + } + // Ensure that an underlying string has the same representation. + bool is_one_byte = subject_ptr->IsOneByteRepresentation(); + ASSERT(IsExternalString(subject_ptr) || IsSeqString(subject_ptr)); + // String is now either Sequential or External + int char_size_shift = is_one_byte ? 0 : 1; + + DisallowGarbageCollection no_gc; + const uint8_t* input_start = + subject_ptr->AddressOfCharacterAt(start_offset + slice_offset, no_gc); + int byte_length = char_length << char_size_shift; + const uint8_t* input_end = input_start + byte_length; + return Execute(*subject, start_offset, input_start, input_end, offsets_vector, + offsets_vector_length, isolate, *regexp_data); +} + +// static +int NativeRegExpMacroAssembler::ExecuteForTesting(Tagged input, + int start_offset, + const uint8_t* input_start, + const uint8_t* input_end, + int* output, + int output_size, + Isolate* isolate, + Tagged regexp) { + Tagged data = regexp->data(isolate); + return Execute(input, start_offset, input_start, input_end, output, + output_size, isolate, SbxCast(data)); +} + +// Returns a {Result} sentinel, or the number of successful matches. +int NativeRegExpMacroAssembler::Execute( + Tagged + input, // This needs to be the unpacked (sliced, cons) string. + int start_offset, + const uint8_t* input_start, + const uint8_t* input_end, + int* output, + int output_size, + Isolate* isolate, + Tagged regexp_data) { + bool is_one_byte = String::IsOneByteRepresentationUnderneath(input); + Tagged code = regexp_data->code(isolate, is_one_byte); + RegExp::CallOrigin call_origin = RegExp::CallOrigin::kFromRuntime; + + using RegexpMatcherSig = + // NOLINTNEXTLINE(readability/casting) + int(Address input_string, int start_offset, const uint8_t* input_start, + const uint8_t* input_end, int* output, int output_size, + int call_origin, Isolate* isolate, Address regexp_data); + + auto fn = GeneratedCode::FromCode(isolate, code); + int result = fn.CallSandboxed(input.ptr(), start_offset, input_start, + input_end, output, output_size, call_origin, + isolate, regexp_data.ptr()); + DCHECK_GE(result, SMALLEST_REGEXP_RESULT); + + if (result == EXCEPTION && !isolate->has_exception()) { + // We detected a stack overflow (on the backtrack stack) in RegExp code, + // but haven't created the exception yet. Additionally, we allow heap + // allocation because even though it invalidates {input_start} and + // {input_end}, we are about to return anyway. + AllowGarbageCollection allow_allocation; + isolate->StackOverflow(); + } + return result; +} + +#endif // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// clang-format off +const uint8_t RegExpMacroAssembler::word_character_map_[] = { + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // '0' - '7' + 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // '8' - '9' + + 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'A' - 'G' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'H' - 'O' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'P' - 'W' + 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, // 'X' - 'Z', '_' + + 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'a' - 'g' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'h' - 'o' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'p' - 'w' + 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // 'x' - 'z' + // Latin-1 range + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, +}; +// clang-format on + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-macro-assembler.h b/runtime/vm/regexp/regexp-macro-assembler.h new file mode 100644 index 00000000000..478bd5a3806 --- /dev/null +++ b/runtime/vm/regexp/regexp-macro-assembler.h @@ -0,0 +1,463 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ +#define V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ + +#include + +#include "vm/regexp/label.h" +#include "vm/regexp/regexp-ast.h" +#include "vm/regexp/regexp.h" + +namespace dart { + +class ByteArray; +class JSRegExp; +class V8Label; +class String; + +static const base::uc32 kLeadSurrogateStart = 0xd800; +static const base::uc32 kLeadSurrogateEnd = 0xdbff; +static const base::uc32 kTrailSurrogateStart = 0xdc00; +static const base::uc32 kTrailSurrogateEnd = 0xdfff; +static const base::uc32 kNonBmpStart = 0x10000; +static const base::uc32 kNonBmpEnd = 0x10ffff; + +class RegExpMacroAssembler { + public: + // The implementation must be able to handle at least: + static constexpr int kMaxRegisterCount = (1 << 16); + static constexpr int kMaxRegister = kMaxRegisterCount - 1; + static constexpr int kMaxCaptures = (kMaxRegister - 1) / 2; + // Note the minimum value is chosen s.t. a negated valid offset is also a + // valid offset. + static constexpr int kMaxCPOffset = (1 << 15) - 1; + static constexpr int kMinCPOffset = -kMaxCPOffset; + + static constexpr int kTableSizeBits = 7; + static constexpr int kTableSize = 1 << kTableSizeBits; + static constexpr int kTableMask = kTableSize - 1; + + static constexpr int kUseCharactersValue = -1; + + // Type of input string to generate code for. + enum Mode { LATIN1 = 1, UC16 = 2 }; + + RegExpMacroAssembler(Isolate* isolate, Zone* zone, Mode mode); + RegExpMacroAssembler(const RegExpMacroAssembler& other) = default; + virtual ~RegExpMacroAssembler() = default; + + virtual ObjectPtr GetCode(const String& source, RegExpFlags flags) = 0; + + // This function is called when code generation is aborted, so that + // the assembler could clean up internal data structures. + virtual void AbortedCodeGeneration() {} + // The maximal number of pushes between stack checks. Users must supply + // kCheckStackLimit flag to push operations (instead of kNoStackLimitCheck) + // at least once for every stack_limit() pushes that are executed. + int stack_limit_slack_slot_count() const; + bool CanReadUnaligned() const; + + virtual void AdvanceCurrentPosition(int by) = 0; // Signed cp change. + virtual void AdvanceRegister(int reg, int by) = 0; // r[reg] += by. + // Continues execution from the position pushed on the top of the backtrack + // stack by an earlier PushBacktrack(V8Label*). + virtual void Backtrack() = 0; + virtual void Bind(V8Label* label) = 0; + // Dispatch after looking the current character up in a 2-bits-per-entry + // map. The destinations vector has up to 4 labels. + virtual void CheckCharacter(unsigned c, V8Label* on_equal) = 0; + // Bitwise and the current character with the given constant and then + // check for a match with c. + virtual void CheckCharacterAfterAnd(unsigned c, + unsigned and_with, + V8Label* on_equal) = 0; + virtual void CheckCharacterGT(uint16_t limit, V8Label* on_greater) = 0; + virtual void CheckCharacterLT(uint16_t limit, V8Label* on_less) = 0; + virtual void CheckFixedLengthLoop( + V8Label* on_tos_equals_current_position) = 0; + virtual void CheckAtStart(int cp_offset, V8Label* on_at_start) = 0; + virtual void CheckNotAtStart(int cp_offset, V8Label* on_not_at_start) = 0; + virtual void CheckNotBackReference(int start_reg, + bool read_backward, + V8Label* on_no_match) = 0; + virtual void CheckNotBackReferenceIgnoreCase(int start_reg, + bool read_backward, + bool unicode, + V8Label* on_no_match) = 0; + // Check the current character for a match with a literal character. If we + // fail to match then goto the on_failure label. End of input always + // matches. If the label is nullptr then we should pop a backtrack address + // off the stack and go to that. + virtual void CheckNotCharacter(unsigned c, V8Label* on_not_equal) = 0; + virtual void CheckNotCharacterAfterAnd(unsigned c, + unsigned and_with, + V8Label* on_not_equal) = 0; + // Subtract a constant from the current character, then and with the given + // constant and then check for a match with c. + virtual void CheckNotCharacterAfterMinusAnd(uint16_t c, + uint16_t minus, + uint16_t and_with, + V8Label* on_not_equal) = 0; + virtual void CheckCharacterInRange(uint16_t from, + uint16_t to, // Both inclusive. + V8Label* on_in_range) = 0; + virtual void CheckCharacterNotInRange(uint16_t from, + uint16_t to, // Both inclusive. + V8Label* on_not_in_range) = 0; + // Returns true if the check was emitted, false otherwise. + virtual bool CheckCharacterInRangeArray( + const ZoneList* ranges, + V8Label* on_in_range) = 0; + virtual bool CheckCharacterNotInRangeArray( + const ZoneList* ranges, + V8Label* on_not_in_range) = 0; + + // The current character (modulus the kTableSize) is looked up in the byte + // array, and if the found byte is non-zero, we jump to the on_bit_set label. + virtual void CheckBitInTable(const TypedData& table, V8Label* on_bit_set) = 0; + + virtual void SkipUntilBitInTable(int cp_offset, + const TypedData& table, + const TypedData& nibble_table, + int advance_by, + V8Label* on_match, + V8Label* on_no_match) = 0; + virtual bool SkipUntilBitInTableUseSimd(int advance_by) { return false; } + + virtual void SkipUntilCharAnd(int cp_offset, + int advance_by, + unsigned character, + unsigned mask, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match); + virtual void SkipUntilChar(int cp_offset, + int advance_by, + unsigned character, + V8Label* on_match, + V8Label* on_no_match); + virtual void SkipUntilCharPosChecked(int cp_offset, + int advance_by, + unsigned character, + int eats_at_least, + V8Label* on_match, + V8Label* on_no_match); + virtual void SkipUntilCharOrChar(int cp_offset, + int advance_by, + unsigned char1, + unsigned char2, + V8Label* on_match, + V8Label* on_no_match); + virtual void SkipUntilGtOrNotBitInTable(int cp_offset, + int advance_by, + unsigned character, + const TypedData& table, + V8Label* on_match, + V8Label* on_no_match); + virtual void SkipUntilOneOfMasked(int cp_offset, + int advance_by, + unsigned both_chars, + unsigned both_mask, + int max_offset, + unsigned chars1, + unsigned mask1, + unsigned chars2, + unsigned mask2, + V8Label* on_match1, + V8Label* on_match2, + V8Label* on_failure); + struct SkipUntilOneOfMasked3Args { + int bc0_cp_offset; + int bc0_advance_by; + TypedData* bc0_table; + TypedData* bc0_nibble_table; + int bc1_cp_offset; + V8Label* bc1_on_failure; + int bc2_cp_offset; + unsigned bc3_characters; + unsigned bc3_mask; + int bc4_by; + int bc5_cp_offset; + unsigned bc6_characters; + unsigned bc6_mask; + V8Label* bc6_on_equal; + unsigned bc7_characters; + unsigned bc7_mask; + V8Label* bc7_on_equal; + unsigned bc8_characters; + unsigned bc8_mask; + V8Label* fallthrough_jump_target; + }; + virtual bool SkipUntilOneOfMasked3UseSimd( + const SkipUntilOneOfMasked3Args& args) { + return false; + } + virtual void SkipUntilOneOfMasked3(const SkipUntilOneOfMasked3Args& args); + + // Checks whether the given offset from the current position is is in-bounds. + // May overwrite the current character. + virtual void CheckPosition(int cp_offset, V8Label* on_outside_input) = 0; + // Check whether a special character class has custom support for more + // optimized code. + bool CanOptimizeSpecialClassRanges(StandardCharacterSet) const; + // Check whether a standard/default character class matches the current + // character. + // May clobber the current loaded character. + virtual void CheckSpecialClassRanges(StandardCharacterSet type, + V8Label* on_no_match) = 0; + + // Control-flow integrity: + // Define a jump target and bind a label. + virtual void BindJumpTarget(V8Label* label) { Bind(label); } + + virtual void Fail() = 0; + virtual void GoTo(V8Label* label) = 0; + // Check whether a register is >= a given constant and go to a label if it + // is. Backtracks instead if the label is nullptr. + virtual void IfRegisterGE(int reg, int comparand, V8Label* if_ge) = 0; + // Check whether a register is < a given constant and go to a label if it is. + // Backtracks instead if the label is nullptr. + virtual void IfRegisterLT(int reg, int comparand, V8Label* if_lt) = 0; + // Check whether a register is == to the current position and go to a + // label if it is. + virtual void IfRegisterEqPos(int reg, V8Label* if_eq) = 0; + void LoadCurrentCharacter(int cp_offset, + V8Label* on_end_of_input, + bool check_bounds = true, + int characters = 1, + int eats_at_least = kUseCharactersValue); + virtual void LoadCurrentCharacterImpl(int cp_offset, + V8Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) = 0; + virtual void PopCurrentPosition() = 0; + virtual void PopRegister(int register_index) = 0; + // Pushes the label on the backtrack stack, so that a following Backtrack + // will go to this label. Always checks the backtrack stack limit. + virtual void PushBacktrack(V8Label* label) = 0; + virtual void PushCurrentPosition() = 0; + enum class StackCheckFlag : uint8_t { + kNoStackLimitCheck = false, + kCheckStackLimit = true + }; + virtual void PushRegister(int register_index, + StackCheckFlag check_stack_limit) = 0; + virtual void ReadCurrentPositionFromRegister(int reg) = 0; + virtual void ReadStackPointerFromRegister(int reg) = 0; + virtual void SetCurrentPositionFromEnd(int by) = 0; + virtual void SetRegister(int register_index, int to) = 0; + // Return whether the matching (with a global regexp) will be restarted. + virtual bool Succeed() = 0; + virtual void WriteCurrentPositionToRegister(int reg, int cp_offset) = 0; + virtual void ClearRegisters(int reg_from, int reg_to) = 0; + virtual void WriteStackPointerToRegister(int reg) = 0; + virtual void RecordComment(std::string_view comment) = 0; + //\virtual MacroAssembler* masm() = 0; + + // Check that we are not in the middle of a surrogate pair. + void CheckNotInSurrogatePair(int cp_offset, V8Label* on_failure); + +#define IMPLEMENTATIONS_LIST(V) \ + V(IA32) \ + V(ARM) \ + V(ARM64) \ + V(MIPS) \ + V(LOONG64) \ + V(RISCV) \ + V(RISCV32) \ + V(S390) \ + V(PPC) \ + V(X64) \ + V(Bytecode) + + enum IrregexpImplementation { +#define V(Name) k##Name##Implementation, + IMPLEMENTATIONS_LIST(V) +#undef V + }; + + inline const char* ImplementationToString(IrregexpImplementation impl) { + static const char* const kNames[] = { +#define V(Name) #Name, + IMPLEMENTATIONS_LIST(V) +#undef V + }; + return kNames[impl]; + } +#undef IMPLEMENTATIONS_LIST + virtual IrregexpImplementation Implementation() = 0; + + // Compare two-byte strings case insensitively. + // + // Called from generated code. + static int CaseInsensitiveCompareNonUnicode(uword byte_offset1, + uword byte_offset2, + size_t byte_length, + Isolate* isolate); + static int CaseInsensitiveCompareUnicode(uword byte_offset1, + uword byte_offset2, + size_t byte_length, + Isolate* isolate); + + // `raw_byte_array` is a ByteArray containing a set of character ranges, + // where ranges are encoded as uint16_t elements: + // + // [from0, to0, from1, to1, ..., fromN, toN], or + // [from0, to0, from1, to1, ..., fromN] (open-ended last interval). + // + // fromN is inclusive, toN is exclusive. Returns zero if not in a range, + // non-zero otherwise. + // + // Called from generated code. + static uint32_t IsCharacterInRangeArray(uint32_t current_char, + uword raw_byte_array); + + // Controls the generation of large inlined constants in the code. + virtual void set_slow_safe(bool ssc) { slow_safe_compiler_ = ssc; } + bool slow_safe() const { return slow_safe_compiler_; } + + // Controls after how many backtracks irregexp should abort execution. If it + // can fall back to the experimental engine (see `set_can_fallback`), it will + // return the appropriate error code, otherwise it will return the number of + // matches found so far (perhaps none). + virtual void set_backtrack_limit(uint32_t backtrack_limit) { + backtrack_limit_ = backtrack_limit; + } + + // Set whether or not irregexp can fall back to the experimental engine on + // excessive backtracking. The number of backtracks considered excessive can + // be controlled with set_backtrack_limit. + virtual void set_can_fallback(bool val) { can_fallback_ = val; } + + enum GlobalMode { + NOT_GLOBAL, + GLOBAL_NO_ZERO_LENGTH_CHECK, + GLOBAL, + GLOBAL_UNICODE + }; + // Set whether the regular expression has the global flag. Exiting due to + // a failure in a global regexp may still mean success overall. + inline virtual void set_global_mode(GlobalMode mode) { global_mode_ = mode; } + inline bool global() const { return global_mode_ != NOT_GLOBAL; } + inline bool global_with_zero_length_check() const { + return global_mode_ == GLOBAL || global_mode_ == GLOBAL_UNICODE; + } + inline bool global_unicode() const { return global_mode_ == GLOBAL_UNICODE; } + + static const base::Vector word_character_map() { + return base::ArrayVector(word_character_map_); + } + + Isolate* isolate() const { return isolate_; } + Zone* zone() const { return zone_; } + + protected: + // Byte size of chars in the string to match (decided by the Mode argument). + inline int char_size() const { + static_assert(static_cast(Mode::LATIN1) == sizeof(uint8_t)); + static_assert(static_cast(Mode::UC16) == sizeof(uint16_t)); + return static_cast(mode()); + } + + bool has_backtrack_limit() const; + uint32_t backtrack_limit() const { return backtrack_limit_; } + + bool can_fallback() const { return can_fallback_; } + + // Which mode to generate code for (LATIN1 or UC16). + Mode mode() const { return mode_; } + + static constexpr size_t kWordCharacterMapSize = 256; + // Byte map of one byte characters with a 0xff if the character is a word + // character (digit, letter or underscore) and 0x00 otherwise. + // Used by generated RegExp code. + static const uint8_t word_character_map_[kWordCharacterMapSize]; + + private: + bool slow_safe_compiler_; + uint32_t backtrack_limit_; + bool can_fallback_ = false; + GlobalMode global_mode_; + Isolate* const isolate_; + Zone* const zone_; + const Mode mode_; +}; + +class NativeRegExpMacroAssembler : public RegExpMacroAssembler { + public: + // Result of calling generated native RegExp code. + // RETRY: Something significant changed during execution, and the matching + // should be retried from scratch. + // EXCEPTION: Something failed during execution. If no exception has been + // thrown, it's an internal out-of-memory, and the caller should + // throw the exception. + // FAILURE: Matching failed. + // SUCCESS: Matching succeeded, and the output array has been filled with + // capture positions. + // FALLBACK_TO_EXPERIMENTAL: Execute the regexp on this subject using the + // experimental engine instead. + enum Result { + FAILURE = RegExpStatics::kInternalRegExpFailure, + SUCCESS = RegExpStatics::kInternalRegExpSuccess, + EXCEPTION = RegExpStatics::kInternalRegExpException, + RETRY = RegExpStatics::kInternalRegExpRetry, + FALLBACK_TO_EXPERIMENTAL = + RegExpStatics::kInternalRegExpFallbackToExperimental, + SMALLEST_REGEXP_RESULT = RegExpStatics::kInternalRegExpSmallestResult, + }; + + NativeRegExpMacroAssembler(Isolate* isolate, Zone* zone, Mode mode) + : RegExpMacroAssembler(isolate, zone, mode), range_array_cache_(zone) {} + ~NativeRegExpMacroAssembler() override = default; + + // Returns a {Result} sentinel, or the number of successful matches. + static int Match(const Object& regexp_data, + const String& subject, + int* offsets_vector, + int offsets_vector_length, + int previous_index, + Isolate* isolate); + + static int ExecuteForTesting(const String& input, + int start_offset, + const uint8_t* input_start, + const uint8_t* input_end, + int* output, + int output_size, + Isolate* isolate, + const RegExp& regexp); + + void LoadCurrentCharacterImpl(int cp_offset, + V8Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) override; + // Load a number of characters at the given offset from the + // current position, into the current-character register. + virtual void LoadCurrentCharacterUnchecked(int cp_offset, + int character_count) = 0; + + protected: + TypedDataPtr GetOrAddRangeArray(const ZoneList* ranges); + + private: + // Returns a {Result} sentinel, or the number of successful matches. + static int Execute(const String& input, + int start_offset, + const uint8_t* input_start, + const uint8_t* input_end, + int* output, + int output_size, + Isolate* isolate, + const Object& regexp_data); + + ZoneUnorderedMap range_array_cache_; +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ diff --git a/runtime/vm/regexp/regexp-nodes.h b/runtime/vm/regexp/regexp-nodes.h new file mode 100644 index 00000000000..e9ab9bdab5a --- /dev/null +++ b/runtime/vm/regexp/regexp-nodes.h @@ -0,0 +1,906 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_NODES_H_ +#define V8_REGEXP_REGEXP_NODES_H_ + +#include + +#include "vm/regexp/label.h" +#include "vm/regexp/regexp-ast.h" +#include "vm/regexp/regexp.h" + +namespace dart { + +class AlternativeGenerationList; +class BoyerMooreLookahead; +class SpecialLoopState; +class NegativeSubmatchSuccess; +class NodeVisitor; +class QuickCheckDetails; +class RegExpCompiler; +class SeqRegExpNode; +class Trace; +template +class RegExpNodePrinter; +struct PreloadState; +class RegExpMacroAssembler; + +#define FOR_EACH_NODE_TYPE(VISIT) \ + VISIT(End) \ + VISIT(Action) \ + VISIT(Choice) \ + VISIT(LoopChoice) \ + VISIT(NegativeLookaroundChoice) \ + VISIT(BackReference) \ + VISIT(Assertion) \ + VISIT(Text) + +#define FORWARD_DECLARE(type) class type##Node; +FOR_EACH_NODE_TYPE(FORWARD_DECLARE) +#undef FORWARD_DECLARE + +struct NodeInfo final { + NodeInfo() + : being_analyzed(false), + been_analyzed(false), + follows_word_interest(false), + follows_newline_interest(false), + follows_start_interest(false), + at_end(false), + visited(false), + replacement_calculated(false) {} + + // Returns true if the interests and assumptions of this node + // matches the given one. + bool Matches(NodeInfo* that) { + return (at_end == that->at_end) && + (follows_word_interest == that->follows_word_interest) && + (follows_newline_interest == that->follows_newline_interest) && + (follows_start_interest == that->follows_start_interest); + } + + // Updates the interests of this node given the interests of the + // node preceding it. + void AddFromPreceding(NodeInfo* that) { + at_end |= that->at_end; + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + bool HasLookbehind() { + return follows_word_interest || follows_newline_interest || + follows_start_interest; + } + + // Sets the interests of this node to include the interests of the + // following node. + void AddFromFollowing(NodeInfo* that) { + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + void ResetCompilationState() { + being_analyzed = false; + been_analyzed = false; + } + + bool being_analyzed : 1; + bool been_analyzed : 1; + + // These bits are set of this node has to know what the preceding + // character was. + bool follows_word_interest : 1; + bool follows_newline_interest : 1; + bool follows_start_interest : 1; + + bool at_end : 1; + bool visited : 1; + bool replacement_calculated : 1; +}; + +struct EatsAtLeastInfo final { + EatsAtLeastInfo() : EatsAtLeastInfo(0) {} + explicit EatsAtLeastInfo(uint8_t eats) + : from_possibly_start(eats), from_not_start(eats) {} + void SetMin(const EatsAtLeastInfo& other) { + from_possibly_start = + std::min(from_possibly_start, other.from_possibly_start); + from_not_start = std::min(from_not_start, other.from_not_start); + } + void SetMax(int other) { + uint8_t max = base::saturated_cast(other); + from_possibly_start = std::max(from_possibly_start, max); + from_not_start = std::max(from_not_start, max); + } + + bool IsZero() const { + return from_possibly_start == 0 && from_not_start == 0; + } + + // Any successful match starting from the current node will consume at least + // this many characters. This does not necessarily mean that there is a + // possible match with exactly this many characters, but we generally try to + // get this number as high as possible to allow for early exit on failure. + uint8_t from_possibly_start; + + // Like from_possibly_start, but with the additional assumption + // that start-of-string assertions (^) can't match. This value is greater than + // or equal to from_possibly_start. + uint8_t from_not_start; +}; + +class EmitResult final { + public: + static EmitResult Success() { return EmitResult(kSuccess); } + static EmitResult Error() { return EmitResult(kError); } + + bool IsSuccess() const { return result_ == kSuccess; } + bool IsError() const { return result_ == kError; } + + private: + enum Result { kSuccess, kError }; + constexpr explicit EmitResult(Result result) : result_(result) {} + Result result_; +}; + +#define RETURN_IF_ERROR(stmt) \ + if (EmitResult r = (stmt); UNLIKELY(r.IsError())) return r + +class RegExpNode : public ZoneObject { + public: + explicit RegExpNode(Zone* zone) + : replacement_(nullptr), + on_work_list_(false), + trace_count_(0), + zone_(zone) { + bm_info_[0] = bm_info_[1] = nullptr; + } + virtual ~RegExpNode(); + virtual void Accept(NodeVisitor* visitor) = 0; + // Generates a goto to this node or actually generates the code at this point. + V8_WARN_UNUSED_RESULT virtual EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) = 0; + // How many characters must this node consume at a minimum in order to + // succeed. The not_at_start argument is used to indicate that we know we are + // not at the start of the input. In this case anchored branches will always + // fail and can be ignored when determining how many characters are consumed + // on success. If this node has not been analyzed yet, EatsAtLeast returns 0. + uint32_t EatsAtLeast(bool not_at_start); + static constexpr uint32_t kLargeEatsAtLeastValue = 255; + // Emits some quick code that checks whether the preloaded characters match. + // Falls through on certain failure, jumps to the label on possible success. + // If the node cannot make a quick check it does nothing and returns false. + bool EmitQuickCheck(RegExpCompiler* compiler, + Trace* bounds_check_trace, + Trace* trace, + bool preload_has_checked_bounds, + V8Label* on_possible_success, + QuickCheckDetails* details_return, + bool fall_through_on_failure, + ChoiceNode* predecessor); + // For a given number of characters this returns a mask and a value. The + // next n characters are anded with the mask and compared with the value. + // A comparison failure indicates the node cannot match the next n characters. + // A comparison success indicates the node may match. + // TODO(pthier): Cache QuickCheckDetails to avoid recomputation. + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) = 0; + static const int kNodeIsTooComplexForFixedLengthLoops = kMinInt; + virtual int FixedLengthLoopLength() { + return kNodeIsTooComplexForFixedLengthLoops; + } + // Only returns the successor for a text node of length 1 that matches any + // character and that has no guards on it. + virtual RegExpNode* GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) { + return nullptr; + } + + // Collects information on the possible code units (mod 128) that can match if + // we look forward. This is used for a Boyer-Moore-like string searching + // implementation. TODO(erikcorry): This should share more code with + // EatsAtLeast, GetQuickCheckDetails. The budget argument is used to limit + // the number of nodes we are willing to look at in order to create this data. + static const int kRecursionBudget = 200; + bool KeepRecursing(RegExpCompiler* compiler); + virtual void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + return; + } + + // We want to avoid recalculating the lookahead info, so we store it on the + // node. Only info that is for this node is stored. We can tell that the + // info is for this node when offset == 0, so the information is calculated + // relative to this node. + void SaveBMInfo(BoyerMooreLookahead* bm, bool not_at_start, int offset) { + if (offset == 0) set_bm_info(not_at_start, bm); + } + + V8Label* label() { return &label_; } + // If non-generic code is generated for a node (i.e. the node is not at the + // start of the trace) then it cannot be reused. This variable sets a limit + // on how often we allow that to happen before we insist on starting a new + // trace and generating generic code for a node that can be reused by flushing + // the deferred actions in the current trace and generating a goto. + static const int kMaxCopiesCodeGenerated = 10; + + bool on_work_list() { return on_work_list_; } + void set_on_work_list(bool value) { on_work_list_ = value; } + + NodeInfo* info() { return &info_; } + const EatsAtLeastInfo* eats_at_least_info() const { return &eats_at_least_; } + void set_eats_at_least_info(const EatsAtLeastInfo& eats_at_least) { + eats_at_least_ = eats_at_least; + } + + // TODO(v8:10441): This is a hacky way to avoid exponential code size growth + // for very large choice nodes that can be generated by unicode property + // escapes. In order to avoid inlining (i.e. trace recursion), we pretend to + // have generated the maximum count of code copies already. + // We should instead fix this properly, e.g. by using the code size budget + // (flush_budget) or by generating property escape matches as calls to a C + // function. + void SetDoNotInline() { trace_count_ = kMaxCopiesCodeGenerated; } + + BoyerMooreLookahead* bm_info(bool not_at_start) { + return bm_info_[not_at_start ? 1 : 0]; + } + +#define DECLARE_CAST(type) \ + virtual type##Node* As##type##Node() { return nullptr; } + FOR_EACH_NODE_TYPE(DECLARE_CAST) +#undef DECLARE_CAST + + virtual NegativeSubmatchSuccess* AsNegativeSubmatchSuccess() { + return nullptr; + } + virtual SeqRegExpNode* AsSeqRegExpNode() { return nullptr; } + + Zone* zone() const { return zone_; } + + virtual bool IsBacktrack() const { return false; } + + protected: + enum LimitResult { DONE, CONTINUE }; + RegExpNode* replacement_; + + LimitResult LimitVersions(RegExpCompiler* compiler, Trace* trace); + + void set_bm_info(bool not_at_start, BoyerMooreLookahead* bm) { + bm_info_[not_at_start ? 1 : 0] = bm; + } + + private: + static const int kFirstCharBudget = 10; + V8Label label_; + bool on_work_list_; + NodeInfo info_; + + // Saved values for EatsAtLeast results, to avoid recomputation. Filled in + // during analysis (valid if info_.been_analyzed is true). + EatsAtLeastInfo eats_at_least_; + + // This variable keeps track of how many times code has been generated for + // this node (in different traces). We don't keep track of where the + // generated code is located unless the code is generated at the start of + // a trace, in which case it is generic and can be reused by flushing the + // deferred operations in the current trace and generating a goto. + int trace_count_; + BoyerMooreLookahead* bm_info_[2]; + + Zone* zone_; +}; + +class SeqRegExpNode : public RegExpNode { + public: + explicit SeqRegExpNode(RegExpNode* on_success) + : RegExpNode(on_success->zone()), on_success_(on_success) {} + RegExpNode* on_success() const { return on_success_; } + void set_on_success(RegExpNode* node) { on_success_ = node; } + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override { + on_success_->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + if (offset == 0) set_bm_info(not_at_start, bm); + } + SeqRegExpNode* AsSeqRegExpNode() override { return this; } + + private: + RegExpNode* on_success_; +}; + +class ActionNode : public SeqRegExpNode { + public: + enum ActionType { + SET_REGISTER_FOR_LOOP, + INCREMENT_REGISTER, + STORE_POSITION, + RESTORE_POSITION, + BEGIN_POSITIVE_SUBMATCH, + BEGIN_NEGATIVE_SUBMATCH, + POSITIVE_SUBMATCH_SUCCESS, + EMPTY_MATCH_CHECK, + CLEAR_CAPTURES, + MODIFY_FLAGS, + EATS_AT_LEAST, + }; + static ActionNode* SetRegisterForLoop(int reg, + int val, + RegExpNode* on_success); + static ActionNode* IncrementRegister(int reg, RegExpNode* on_success); + static ActionNode* StorePosition(int reg, RegExpNode* on_success); + static ActionNode* RestorePosition(int reg, RegExpNode* on_success); + static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success); + static ActionNode* BeginPositiveSubmatch(int stack_pointer_reg, + int position_reg, + RegExpNode* body, + ActionNode* success_node); + static ActionNode* BeginNegativeSubmatch(int stack_pointer_reg, + int position_reg, + RegExpNode* on_success); + static ActionNode* PositiveSubmatchSuccess(int stack_pointer_reg, + int restore_reg, + int clear_capture_count, + int clear_capture_from, + RegExpNode* on_success); + static ActionNode* EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode* on_success); + static ActionNode* ModifyFlags(RegExpFlags flags, RegExpNode* on_success); + static ActionNode* EatsAtLeast(int characters, RegExpNode* on_success); + ActionNode* AsActionNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + ActionType action_type() const { return action_type_; } + // TODO(erikcorry): We should allow some action nodes in fixed length loops. + int FixedLengthLoopLength() override { + return kNodeIsTooComplexForFixedLengthLoops; + } + RegExpFlags flags() const { + DCHECK_EQ(action_type(), MODIFY_FLAGS); + return RegExpFlags{data_.u_modify_flags.flags}; + } + ActionNode* success_node() const { + DCHECK_EQ(action_type(), BEGIN_POSITIVE_SUBMATCH); + return data_.u_submatch.success_node; + } + int stored_eats_at_least() { + DCHECK_EQ(action_type(), EATS_AT_LEAST); + return data_.u_eats_at_least.characters; + } + + bool Mentions(int reg) const { + return base::IsInRange(reg, register_from(), register_to()); + } + + int value() const { + ASSERT(action_type() == SET_REGISTER_FOR_LOOP); + return data_.u_simple.value; + } + + bool IsSimpleAction() const { + return action_type() == STORE_POSITION || + action_type() == RESTORE_POSITION || + action_type() == INCREMENT_REGISTER || + action_type() == SET_REGISTER_FOR_LOOP || + action_type() == CLEAR_CAPTURES; + } + + int register_from() const { + ASSERT(IsSimpleAction()); + return data_.u_simple.register_from; + } + + int register_to() const { return data_.u_simple.register_to; } + + protected: + ActionNode(ActionType action_type, RegExpNode* on_success) + : SeqRegExpNode(on_success), action_type_(action_type) {} + + ActionNode(ActionType action_type, + RegExpNode* on_success, + int from, + int to = -1, + int value = 0) + : SeqRegExpNode(on_success), action_type_(action_type) { + data_.u_simple.register_from = from; + data_.u_simple.register_to = to == -1 ? from : to; + data_.u_simple.value = value; + ASSERT(IsSimpleAction()); + } + + private: + union { + struct { + int register_from; + int register_to; + int value; + } u_simple; + struct { + int stack_pointer_register; + int current_position_register; + int clear_register_count; + int clear_register_from; + ActionNode* success_node; // Only used for positive submatch. + } u_submatch; + struct { + int start_register; + int repetition_register; + int repetition_limit; + } u_empty_match_check; + struct { + int flags; + } u_modify_flags; + struct { + int characters; + } u_eats_at_least; + } data_; + + ActionType action_type_; + friend class DotPrinterImpl; + friend class RegExpNodePrinter; + friend Zone; +}; + +class TextNode : public SeqRegExpNode { + public: + TextNode(ZoneList* elms, + bool read_backward, + RegExpNode* on_success) + : SeqRegExpNode(on_success), elms_(elms), read_backward_(read_backward) {} + TextNode(RegExpClassRanges* that, bool read_backward, RegExpNode* on_success) + : SeqRegExpNode(on_success), + elms_(zone()->New>(1, zone())), + read_backward_(read_backward) { + elms_->Add(TextElement::ClassRanges(that), zone()); + } + // Create TextNode for a single character class for the given ranges. + static TextNode* CreateForCharacterRanges(Zone* zone, + ZoneList* ranges, + bool read_backward, + RegExpNode* on_success); + // Create TextNode for a surrogate pair (i.e. match a sequence of two uc16 + // code unit ranges). + static TextNode* CreateForSurrogatePair( + Zone* zone, + CharacterRange lead, + ZoneList* trail_ranges, + bool read_backward, + RegExpNode* on_success); + static TextNode* CreateForSurrogatePair(Zone* zone, + ZoneList* lead_ranges, + CharacterRange trail, + bool read_backward, + RegExpNode* on_success); + TextNode* AsTextNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override; + ZoneList* elements() { return elms_; } + bool read_backward() const { return read_backward_; } + void MakeCaseIndependent(Isolate* isolate, + bool is_one_byte, + RegExpFlags flags); + int FixedLengthLoopLength() override; + RegExpNode* GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + void CalculateOffsets(); + int Length(); + + // Returns false if the text node can't match in one-byte mode. + bool CanMatchLatin1(RegExpCompiler* compiler); + + private: + enum TextEmitPassType { + NON_LATIN1_MATCH, // Check for characters that can never match. + SIMPLE_CHARACTER_MATCH, // Case-dependent single character check. + NON_LETTER_CHARACTER_MATCH, // Check characters that have no case equivs. + CASE_CHARACTER_MATCH, // Case-independent single character check. + CHARACTER_CLASS_MATCH // Character class. + }; + void TextEmitPass(RegExpCompiler* compiler, + TextEmitPassType pass, + bool preloaded, + Trace* trace, + bool first_element_checked, + int* checked_up_to); + ZoneList* elms_; + bool read_backward_; +}; + +class AssertionNode : public SeqRegExpNode { + public: + enum AssertionType { + AT_END, + AT_START, + AT_BOUNDARY, + AT_NON_BOUNDARY, + AFTER_NEWLINE + }; + static AssertionNode* AtEnd(RegExpNode* on_success) { + return on_success->zone()->New(AT_END, on_success); + } + static AssertionNode* AtStart(RegExpNode* on_success) { + return on_success->zone()->New(AT_START, on_success); + } + static AssertionNode* AtBoundary(RegExpNode* on_success) { + return on_success->zone()->New(AT_BOUNDARY, on_success); + } + static AssertionNode* AtNonBoundary(RegExpNode* on_success) { + return on_success->zone()->New(AT_NON_BOUNDARY, on_success); + } + static AssertionNode* AfterNewline(RegExpNode* on_success) { + return on_success->zone()->New(AFTER_NEWLINE, on_success); + } + AssertionNode* AsAssertionNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + AssertionType assertion_type() const { return assertion_type_; } + + private: + friend Zone; + + V8_WARN_UNUSED_RESULT EmitResult EmitBoundaryCheck(RegExpCompiler* compiler, + Trace* trace); + enum IfPrevious { kIsNonWord, kIsWord }; + V8_WARN_UNUSED_RESULT EmitResult + BacktrackIfPrevious(RegExpCompiler* compiler, + Trace* trace, + IfPrevious backtrack_if_previous); + AssertionNode(AssertionType t, RegExpNode* on_success) + : SeqRegExpNode(on_success), assertion_type_(t) {} + AssertionType assertion_type_; +}; + +class BackReferenceNode : public SeqRegExpNode { + public: + BackReferenceNode(int start_reg, + int end_reg, + bool read_backward, + RegExpNode* on_success) + : SeqRegExpNode(on_success), + start_reg_(start_reg), + end_reg_(end_reg), + read_backward_(read_backward) {} + BackReferenceNode* AsBackReferenceNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + int start_register() const { return start_reg_; } + int end_register() const { return end_reg_; } + bool read_backward() const { return read_backward_; } + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override { + return; + } + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + + private: + int start_reg_; + int end_reg_; + bool read_backward_; +}; + +class EndNode : public RegExpNode { + public: + enum Action { ACCEPT, BACKTRACK, NEGATIVE_SUBMATCH_SUCCESS }; + EndNode(Action action, Zone* zone) : RegExpNode(zone), action_(action) { + EatsAtLeastInfo large(kLargeEatsAtLeastValue); + if (action == BACKTRACK) set_eats_at_least_info(large); + } + EndNode* AsEndNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override {} + Action action() const { return action_; } + + virtual bool IsBacktrack() const override { return action_ == BACKTRACK; } + + private: + Action action_; +}; + +class NegativeSubmatchSuccess : public EndNode { + public: + NegativeSubmatchSuccess(int stack_pointer_reg, + int position_reg, + int clear_capture_count, + int clear_capture_start, + Zone* zone) + : EndNode(NEGATIVE_SUBMATCH_SUCCESS, zone), + stack_pointer_register_(stack_pointer_reg), + current_position_register_(position_reg), + clear_capture_count_(clear_capture_count), + clear_capture_start_(clear_capture_start) {} + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + NegativeSubmatchSuccess* AsNegativeSubmatchSuccess() override { return this; } + + private: + int stack_pointer_register_; + int current_position_register_; + int clear_capture_count_; + int clear_capture_start_; + + friend class RegExpNodePrinter; +}; + +class Guard : public ZoneObject { + public: + enum Relation { LT, GEQ }; + Guard(int reg, Relation op, int value) : reg_(reg), op_(op), value_(value) {} + int reg() const { return reg_; } + Relation op() const { return op_; } + int value() const { return value_; } + + private: + int reg_; + Relation op_; + int value_; +}; + +class GuardedAlternative { + public: + explicit GuardedAlternative(RegExpNode* node) + : node_(node), guards_(nullptr) {} + void AddGuard(Guard* guard, Zone* zone); + RegExpNode* node() const { return node_; } + void set_node(RegExpNode* node) { node_ = node; } + const ZoneList* guards() const { return guards_; } + + private: + RegExpNode* node_; + // TODO(pthier): There are currently no uses of multiple guards. Consider + // removing the ZoneList. + ZoneList* guards_; +}; + +class AlternativeGeneration; + +class ChoiceNode : public RegExpNode { + public: + explicit ChoiceNode(int expected_size, Zone* zone) + : RegExpNode(zone), + alternatives_( + zone->New>(expected_size, zone)), + not_at_start_(false), + being_calculated_(false) {} + ChoiceNode* AsChoiceNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + void AddAlternative(GuardedAlternative node) { + alternatives()->Add(node, zone()); + } + ZoneList* alternatives() { return alternatives_; } + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + + bool being_calculated() const { return being_calculated_; } + bool not_at_start() const { return not_at_start_; } + void set_not_at_start() { not_at_start_ = true; } + void set_being_calculated(bool b) { being_calculated_ = b; } + virtual bool try_to_emit_quick_check_for_alternative(bool is_first) { + return true; + } + virtual bool read_backward() const { return false; } + + protected: + int FixedLengthLoopLengthForAlternative(GuardedAlternative* alternative); + ZoneList* alternatives_; + + private: + template + friend class Analysis; + + void GenerateGuard(RegExpMacroAssembler* macro_assembler, + Guard* guard, + Trace* trace); + int CalculatePreloadCharacters(RegExpCompiler* compiler, int eats_at_least); + V8_WARN_UNUSED_RESULT EmitResult + EmitOutOfLineContinuation(RegExpCompiler* compiler, + Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload); + void SetUpPreLoad(RegExpCompiler* compiler, + Trace* current_trace, + PreloadState* preloads); + void AssertGuardsMentionRegisters(Trace* trace); + int EmitOptimizedUnanchoredSearch(RegExpCompiler* compiler, + Trace* trace, + SpecialLoopState* search_loop_state); + // Returns nullptr on failure. + // TODO(jgruber): Consider wrapping the return value in EmitResult. + V8_WARN_UNUSED_RESULT Trace* EmitFixedLengthLoop( + RegExpCompiler* compiler, + Trace* trace, + AlternativeGenerationList* alt_gens, + PreloadState* preloads, + SpecialLoopState* fixed_length_loop_state, + int text_length, + RegExpFlags flags); + V8_WARN_UNUSED_RESULT EmitResult + EmitChoices(RegExpCompiler* compiler, + AlternativeGenerationList* alt_gens, + int first_choice, + Trace* trace, + PreloadState* preloads, + RegExpFlags flags); + + // If true, this node is never checked at the start of the input. + // Allows a new trace to start with at_start() set to false. + bool not_at_start_; + bool being_calculated_; +}; + +class NegativeLookaroundChoiceNode : public ChoiceNode { + public: + explicit NegativeLookaroundChoiceNode(GuardedAlternative this_must_fail, + GuardedAlternative then_do_this, + Zone* zone) + : ChoiceNode(2, zone) { + AddAlternative(this_must_fail); + AddAlternative(then_do_this); + } + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override { + continue_node()->FillInBMInfo(isolate, offset, budget - 1, bm, + not_at_start); + if (offset == 0) set_bm_info(not_at_start, bm); + } + static constexpr int kLookaroundIndex = 0; + static constexpr int kContinueIndex = 1; + RegExpNode* lookaround_node() { + return alternatives()->at(kLookaroundIndex).node(); + } + RegExpNode* continue_node() { + return alternatives()->at(kContinueIndex).node(); + } + // For a negative lookahead we don't emit the quick check for the + // alternative that is expected to fail. This is because quick check code + // starts by loading enough characters for the alternative that takes fewest + // characters, but on a negative lookahead the negative branch did not take + // part in that calculation (EatsAtLeast) so the assumptions don't hold. + bool try_to_emit_quick_check_for_alternative(bool is_first) override { + return !is_first; + } + NegativeLookaroundChoiceNode* AsNegativeLookaroundChoiceNode() override { + return this; + } + void Accept(NodeVisitor* visitor) override; +}; + +class LoopChoiceNode : public ChoiceNode { + public: + LoopChoiceNode(bool body_can_be_zero_length, bool read_backward, Zone* zone) + : ChoiceNode(2, zone), + loop_node_(nullptr), + continue_node_(nullptr), + body_can_be_zero_length_(body_can_be_zero_length), + read_backward_(read_backward) {} + void AddLoopAlternative(GuardedAlternative alt); + void AddContinueAlternative(GuardedAlternative alt); + V8_WARN_UNUSED_RESULT EmitResult Emit(RegExpCompiler* compiler, + Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start, + int budget) override; + void FillInBMInfo(Isolate* isolate, + int offset, + int budget, + BoyerMooreLookahead* bm, + bool not_at_start) override; + RegExpNode* loop_node() const { return loop_node_; } + RegExpNode* continue_node() const { return continue_node_; } + bool body_can_be_zero_length() const { return body_can_be_zero_length_; } + bool read_backward() const override { return read_backward_; } + LoopChoiceNode* AsLoopChoiceNode() override { return this; } + void Accept(NodeVisitor* visitor) override; + + private: + // AddAlternative is made private for loop nodes because alternatives + // should not be added freely, we need to keep track of which node + // goes back to the node itself. + void AddAlternative(GuardedAlternative node) { + ChoiceNode::AddAlternative(node); + } + + RegExpNode* loop_node_; + RegExpNode* continue_node_; + bool body_can_be_zero_length_; + bool read_backward_; +}; + +class NodeVisitor { + public: + virtual ~NodeVisitor() = default; +#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that) = 0; + FOR_EACH_NODE_TYPE(DECLARE_VISIT) +#undef DECLARE_VISIT +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_NODES_H_ diff --git a/runtime/vm/regexp/regexp-parser.cc b/runtime/vm/regexp/regexp-parser.cc new file mode 100644 index 00000000000..de38625db6d --- /dev/null +++ b/runtime/vm/regexp/regexp-parser.cc @@ -0,0 +1,3379 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vm/regexp/regexp-parser.h" + +#include "unicode/uchar.h" +#include "unicode/uniset.h" +#include "unicode/usetiter.h" + +#include "platform/unicode.h" + +#include "vm/longjump.h" +#include "vm/object_store.h" +#include "vm/regexp/char-predicates-inl.h" +#include "vm/regexp/regexp-compiler.h" +#include "vm/regexp/unibrow.h" +#include "vm/regexp/zone-containers.h" +#include "vm/symbols.h" + +namespace dart { + +namespace { + +// Whether we're currently inside the ClassEscape production +// (tc39.es/ecma262/#prod-annexB-CharacterEscape). +enum class InClassEscapeState { + kInClass, + kNotInClass, +}; + +// The production used to derive ClassSetOperand. +enum class ClassSetOperandType { + kClassSetCharacter, + kClassStringDisjunction, + kNestedClass, + kCharacterClassEscape, // \ CharacterClassEscape is a special nested class, + // as we can fold it directly into another range. + kClassSetRange +}; + +class RegExpTextBuilder { + public: + using SmallRegExpTreeVector = SmallZoneVector; + + RegExpTextBuilder(Zone* zone, + SmallRegExpTreeVector* terms_storage, + RegExpFlags flags) + : zone_(zone), flags_(flags), terms_(terms_storage), text_(zone) {} + void AddCharacter(uint16_t character); + void AddUnicodeCharacter(uint32_t character); + void AddEscapedUnicodeCharacter(uint32_t character); + void AddAtom(RegExpTree* atom); + void AddTerm(RegExpTree* term); + void AddClassRanges(RegExpClassRanges* cc); + void FlushPendingSurrogate(); + void FlushText(); + RegExpTree* PopLastAtom(); + RegExpTree* ToRegExp(); + + private: + static const uint16_t kNoPendingSurrogate = 0; + + void AddLeadSurrogate(uint16_t lead_surrogate); + void AddTrailSurrogate(uint16_t trail_surrogate); + void FlushCharacters(); + bool NeedsDesugaringForUnicode(RegExpClassRanges* cc); + bool NeedsDesugaringForIgnoreCase(uint32_t c); + void AddClassRangesForDesugaring(uint32_t c); + bool ignore_case() const { return IsIgnoreCase(flags_); } + bool IsUnicodeMode() const { + // Either /v or /u enable UnicodeMode + // https://tc39.es/ecma262/#sec-parsepattern + return IsUnicode(flags_) || IsUnicodeSets(flags_); + } + Zone* zone() const { return zone_; } + + Zone* const zone_; + const RegExpFlags flags_; + ZoneList* characters_ = nullptr; + uint16_t pending_surrogate_ = kNoPendingSurrogate; + SmallRegExpTreeVector* terms_; + SmallRegExpTreeVector text_; +}; + +void RegExpTextBuilder::AddLeadSurrogate(uint16_t lead_surrogate) { + ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); + FlushPendingSurrogate(); + // Hold onto the lead surrogate, waiting for a trail surrogate to follow. + pending_surrogate_ = lead_surrogate; +} + +void RegExpTextBuilder::AddTrailSurrogate(uint16_t trail_surrogate) { + ASSERT(Utf16::IsTrailSurrogate(trail_surrogate)); + if (pending_surrogate_ != kNoPendingSurrogate) { + uint16_t lead_surrogate = pending_surrogate_; + pending_surrogate_ = kNoPendingSurrogate; + ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); + uint32_t combined = + Utf16::CombineSurrogatePair(lead_surrogate, trail_surrogate); + if (NeedsDesugaringForIgnoreCase(combined)) { + AddClassRangesForDesugaring(combined); + } else { + ZoneList surrogate_pair(2, zone()); + surrogate_pair.Add(lead_surrogate, zone()); + surrogate_pair.Add(trail_surrogate, zone()); + RegExpAtom* atom = + zone()->New(surrogate_pair.ToConstVector()); + AddAtom(atom); + } + } else { + pending_surrogate_ = trail_surrogate; + FlushPendingSurrogate(); + } +} + +void RegExpTextBuilder::FlushPendingSurrogate() { + if (pending_surrogate_ != kNoPendingSurrogate) { + ASSERT(IsUnicodeMode()); + uint32_t c = pending_surrogate_; + pending_surrogate_ = kNoPendingSurrogate; + AddClassRangesForDesugaring(c); + } +} + +void RegExpTextBuilder::FlushCharacters() { + FlushPendingSurrogate(); + if (characters_ != nullptr) { + RegExpTree* atom = zone()->New(characters_->ToConstVector()); + characters_ = nullptr; + text_.emplace_back(atom); + } +} + +void RegExpTextBuilder::FlushText() { + FlushCharacters(); + size_t num_text = text_.size(); + if (num_text == 0) { + return; + } else if (num_text == 1) { + terms_->emplace_back(text_.back()); + } else { + RegExpText* text = zone()->New(zone()); + for (size_t i = 0; i < num_text; i++) { + text_[i]->AppendToText(text, zone()); + } + terms_->emplace_back(text); + } + text_.clear(); +} + +void RegExpTextBuilder::AddCharacter(uint16_t c) { + FlushPendingSurrogate(); + if (characters_ == nullptr) { + characters_ = zone()->New>(4, zone()); + } + characters_->Add(c, zone()); +} + +void RegExpTextBuilder::AddUnicodeCharacter(uint32_t c) { + if (c > static_cast(Utf16::kMaxNonSurrogateCharCode)) { + ASSERT(IsUnicodeMode()); + AddLeadSurrogate(Utf16::LeadSurrogate(c)); + AddTrailSurrogate(Utf16::TrailSurrogate(c)); + } else if (IsUnicodeMode() && Utf16::IsLeadSurrogate(c)) { + AddLeadSurrogate(c); + } else if (IsUnicodeMode() && Utf16::IsTrailSurrogate(c)) { + AddTrailSurrogate(c); + } else { + AddCharacter(static_cast(c)); + } +} + +void RegExpTextBuilder::AddEscapedUnicodeCharacter(uint32_t character) { + // A lead or trail surrogate parsed via escape sequence will not + // pair up with any preceding lead or following trail surrogate. + FlushPendingSurrogate(); + AddUnicodeCharacter(character); + FlushPendingSurrogate(); +} + +void RegExpTextBuilder::AddClassRanges(RegExpClassRanges* cr) { + if (NeedsDesugaringForUnicode(cr)) { + // With /u or /v, character class needs to be desugared, so it + // must be a standalone term instead of being part of a RegExpText. + AddTerm(cr); + } else { + AddAtom(cr); + } +} + +void RegExpTextBuilder::AddClassRangesForDesugaring(uint32_t c) { + AddTerm(zone()->New( + zone(), CharacterRange::List(zone(), CharacterRange::Singleton(c)))); +} + +void RegExpTextBuilder::AddAtom(RegExpTree* atom) { + ASSERT(atom->IsTextElement()); + FlushCharacters(); + text_.emplace_back(atom); +} + +void RegExpTextBuilder::AddTerm(RegExpTree* term) { + ASSERT(term->IsTextElement()); + FlushText(); + terms_->emplace_back(term); +} + +bool RegExpTextBuilder::NeedsDesugaringForUnicode(RegExpClassRanges* cc) { + if (!IsUnicodeMode()) return false; + // TODO(yangguo): we could be smarter than this. Case-insensitivity does not + // necessarily mean that we need to desugar. It's probably nicer to have a + // separate pass to figure out unicode desugarings. + if (ignore_case()) return true; + ZoneList* ranges = cc->ranges(zone()); + CharacterRange::Canonicalize(ranges); + + if (cc->is_negated()) { + ZoneList* negated_ranges = + zone()->New>(ranges->length(), zone()); + CharacterRange::Negate(ranges, negated_ranges, zone()); + ranges = negated_ranges; + } + + for (int i = ranges->length() - 1; i >= 0; i--) { + uint32_t from = ranges->at(i).from(); + uint32_t to = ranges->at(i).to(); + // Check for non-BMP characters. + if (to >= kNonBmpStart) return true; + // Check for lone surrogates. + if (from <= kTrailSurrogateEnd && to >= kLeadSurrogateStart) return true; + } + return false; +} + +// We only use this for characters made of surrogate pairs. All other +// characters outside of character classes are made case independent in the +// code generation. +bool RegExpTextBuilder::NeedsDesugaringForIgnoreCase(uint32_t c) { +#ifdef V8_INTL_SUPPORT + if (IsUnicodeMode() && ignore_case()) { + icu::UnicodeSet set(c, c); + set.closeOver(USET_CASE_INSENSITIVE); + set.removeAllStrings(); + return set.size() > 1; + } + // In the case where ICU is not included, we act as if the unicode flag is + // not set, and do not desugar. +#endif // V8_INTL_SUPPORT + return false; +} + +RegExpTree* RegExpTextBuilder::PopLastAtom() { + FlushPendingSurrogate(); + RegExpTree* atom; + if (characters_ != nullptr) { + base::Vector char_vector = characters_->ToConstVector(); + int num_chars = char_vector.length(); + if (num_chars > 1) { + base::Vector prefix = + char_vector.SubVector(0, num_chars - 1); + text_.emplace_back(zone()->New(prefix)); + char_vector = char_vector.SubVector(num_chars - 1, num_chars); + } + characters_ = nullptr; + atom = zone()->New(char_vector); + return atom; + } else if (!text_.empty()) { + atom = text_.back(); + text_.pop_back(); + return atom; + } + return nullptr; +} + +RegExpTree* RegExpTextBuilder::ToRegExp() { + FlushText(); + size_t number_of_terms = terms_->size(); + if (number_of_terms == 0) return zone()->New(); + if (number_of_terms == 1) return terms_->back(); + return zone()->New(zone()->New>( + base::VectorOf(terms_->begin(), terms_->size()), zone())); +} + +// Accumulates RegExp atoms and assertions into lists of terms and alternatives. +class RegExpBuilder { + public: + RegExpBuilder(Zone* zone, RegExpFlags flags) + : zone_(zone), + flags_(flags), + terms_(zone), + alternatives_(zone), + text_builder_(RegExpTextBuilder{zone, &terms_, flags}) {} + void AddCharacter(uint16_t character); + void AddUnicodeCharacter(uint32_t character); + void AddEscapedUnicodeCharacter(uint32_t character); + // "Adds" an empty expression. Does nothing except consume a + // following quantifier + void AddEmpty(); + void AddClassRanges(RegExpClassRanges* cc); + void AddAtom(RegExpTree* tree); + void AddTerm(RegExpTree* tree); + void AddAssertion(RegExpTree* tree); + void NewAlternative(); // '|' + bool AddQuantifierToAtom(int min, + int max, + int index, + RegExpQuantifier::QuantifierType type); + void FlushText(); + RegExpTree* ToRegExp(); + RegExpFlags flags() const { return flags_; } + + bool ignore_case() const { return IsIgnoreCase(flags_); } + bool multiline() const { return IsMultiline(flags_); } + bool dotall() const { return IsDotAll(flags_); } + + private: + void FlushTerms(); + bool IsUnicodeMode() const { + // Either /v or /u enable UnicodeMode + // https://tc39.es/ecma262/#sec-parsepattern + return IsUnicode(flags_) || IsUnicodeSets(flags_); + } + Zone* zone() const { return zone_; } + RegExpTextBuilder& text_builder() { return text_builder_; } + + Zone* const zone_; + bool pending_empty_ = false; + const RegExpFlags flags_; + + using SmallRegExpTreeVector = SmallZoneVector; + SmallRegExpTreeVector terms_; + SmallRegExpTreeVector alternatives_; + RegExpTextBuilder text_builder_; +}; + +enum SubexpressionType { + INITIAL, + CAPTURE, // All positive values represent captures. + POSITIVE_LOOKAROUND, + NEGATIVE_LOOKAROUND, + GROUPING +}; + +class RegExpParserState : public ZoneObject { + public: + // Push a state on the stack. + RegExpParserState(RegExpParserState* previous_state, + SubexpressionType group_type, + RegExpLookaround::Type lookaround_type, + int disjunction_capture_index, + const ZoneVector* capture_name, + RegExpFlags flags, + Zone* zone) + : previous_state_(previous_state), + builder_(zone, flags), + group_type_(group_type), + lookaround_type_(lookaround_type), + disjunction_capture_index_(disjunction_capture_index), + capture_name_(capture_name), + non_participating_capture_group_intervals_(zone) { + if (previous_state != nullptr) { + non_participating_capture_group_intervals_.insert( + non_participating_capture_group_intervals_.begin(), + previous_state->non_participating_capture_group_intervals_); + } + } + using IntervalVector = SmallZoneVector; + + // Parser state of containing expression, if any. + RegExpParserState* previous_state() const { return previous_state_; } + bool IsSubexpression() { return previous_state_ != nullptr; } + // RegExpBuilder building this regexp's AST. + RegExpBuilder* builder() { return &builder_; } + // Type of regexp being parsed (parenthesized group or entire regexp). + SubexpressionType group_type() const { return group_type_; } + // Lookahead or Lookbehind. + RegExpLookaround::Type lookaround_type() const { return lookaround_type_; } + // Index in captures array of first capture in this sub-expression, if any. + // Also the capture index of this sub-expression itself, if group_type + // is CAPTURE. + int capture_index() const { return disjunction_capture_index_; } + // The name of the current sub-expression, if group_type is CAPTURE. Only + // used for named captures. + const ZoneVector* capture_name() const { return capture_name_; } + const IntervalVector& non_participating_capture_group_intervals() const { + return non_participating_capture_group_intervals_; + } + + bool IsNamedCapture() const { return capture_name_ != nullptr; } + + // Check whether the parser is inside a capture group with the given index. + bool IsInsideCaptureGroup(int index) const { + for (const RegExpParserState* s = this; s != nullptr; + s = s->previous_state()) { + if (s->group_type() != CAPTURE) continue; + // Return true if we found the matching capture index. + if (index == s->capture_index()) return true; + // Abort if index is larger than what has been parsed up till this state. + if (index > s->capture_index()) return false; + } + return false; + } + + // Check whether the parser is inside a capture group with the given name. + bool IsInsideCaptureGroup(const ZoneVector* name) const { + DCHECK_NOT_NULL(name); + for (const RegExpParserState* s = this; s != nullptr; + s = s->previous_state()) { + if (s->capture_name() == nullptr) continue; + if (*s->capture_name() == *name) return true; + } + return false; + } + + void NewAlternative(int captures_started) { + // Nothing to do if there were no new captures started before the + // alternative. + if (capture_index() == captures_started) return; + + // +1 to create a closed interval (capture_index() is exclusive). + int from = capture_index() + 1; + int to = captures_started; + DCHECK_LE(from, to); + // Extend the last interval if we increase its range by exactly 1. + if (!non_participating_capture_group_intervals().empty() && + non_participating_capture_group_intervals().back().to() + 1 == to) { + Interval& interval = non_participating_capture_group_intervals_.back(); + ASSERT(!interval.is_empty()); + DCHECK_GE(from, interval.from()); + interval = interval.Union({from, to}); + } else { + non_participating_capture_group_intervals_.push_back({from, to}); + } + } + + private: + // Linked list implementation of stack of states. + RegExpParserState* const previous_state_; + // Builder for the stored disjunction. + RegExpBuilder builder_; + // Stored disjunction type (capture, look-ahead or grouping), if any. + const SubexpressionType group_type_; + // Stored read direction. + const RegExpLookaround::Type lookaround_type_; + // Stored disjunction's capture index (if any). + const int disjunction_capture_index_; + // Stored capture name (if any). + const ZoneVector* const capture_name_; + // List of Intervals of (named) capture indices [from, to] that are not + // participating in the current state (i.e. they cannot match). + // Capture indices are not participating if they were created in a different + // alternative. + IntervalVector non_participating_capture_group_intervals_; +}; + +template +class RegExpParserImpl final { + private: + RegExpParserImpl(const CharT* input, + int input_length, + RegExpFlags flags, + Zone* zone); + + bool Parse(RegExpCompileData* result); + + RegExpTree* ParsePattern(); + RegExpTree* ParseDisjunction(); + // RegExpTree* ParseGroup(); + + // Parses a {...,...} quantifier and stores the range in the given + // out parameters. + bool ParseIntervalQuantifier(int* min_out, int* max_out); + + // Checks whether the following is a length-digit hexadecimal number, + // and sets the value if it is. + bool ParseHexEscape(int length, uint32_t* value); + bool ParseUnicodeEscape(uint32_t* value); + bool ParseUnlimitedLengthHexNumber(int max_value, uint32_t* value); + + bool ParsePropertyClassName(ZoneVector* name_1, + ZoneVector* name_2); + bool AddPropertyClassRange(ZoneList* add_to_range, + CharacterClassStrings* add_to_strings, + bool negate, + const ZoneVector& name_1, + const ZoneVector& name_2); + + RegExpTree* ParseClassRanges(ZoneList* ranges, + bool add_unicode_case_equivalents); + // Parse inside a class. Either add escaped class to the range, or return + // false and pass parsed single character through |char_out|. + void ParseClassEscape(ZoneList* ranges, + Zone* zone, + bool add_unicode_case_equivalents, + uint32_t* char_out, + bool* is_class_escape); + // Returns true iff parsing was successful. + bool TryParseCharacterClassEscape(uint32_t next, + InClassEscapeState in_class_escape_state, + ZoneList* ranges, + CharacterClassStrings* strings, + Zone* zone, + bool add_unicode_case_equivalents); + RegExpTree* ParseClassStringDisjunction(ZoneList* ranges, + CharacterClassStrings* strings); + RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, + ClassSetOperandType* type_out); + RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, + ClassSetOperandType* type_out, + ZoneList* ranges, + CharacterClassStrings* strings, + uint32_t* character); + uint32_t ParseClassSetCharacter(); + // Parses and returns a single escaped character. + uint32_t ParseCharacterEscape(InClassEscapeState in_class_escape_state, + bool* is_escaped_unicode_character); + + void AddMaybeSimpleCaseFoldedRange(ZoneList* ranges, + CharacterRange new_range); + + RegExpTree* ParseClassUnion(const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type, + ZoneList* ranges, + CharacterClassStrings* strings, + uint32_t first_character); + RegExpTree* ParseClassIntersection(const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type); + RegExpTree* ParseClassSubtraction(const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type); + RegExpTree* ParseCharacterClass(const RegExpBuilder* state); + + uint32_t ParseOctalLiteral(); + + // Tries to parse the input as a back reference. If successful it + // stores the result in the output parameter and returns true. If + // it fails it will push back the characters read so the same characters + // can be reparsed. + bool ParseBackReferenceIndex(int* index_out); + + RegExpTree* ReportError(RegExpError error); + void Advance(); + void Advance(int dist); + void RewindByOneCodepoint(); // Rewinds to before the previous Advance(). + void Reset(int pos); + + // Reports whether the pattern might be used as a literal search string. + // Only use if the result of the parse is a single atom node. + bool simple() const { return simple_; } + bool contains_anchor() const { return contains_anchor_; } + void set_contains_anchor() { contains_anchor_ = true; } + int captures_started() const { return captures_started_; } + int position() const { + const bool current_is_surrogate = + current() != kEndMarker && current() > Utf16::kMaxNonSurrogateCharCode; + const int rewind_bytes = current_is_surrogate ? 2 : 1; + return next_pos_ - rewind_bytes; + } + bool failed() const { return failed_; } + RegExpFlags flags() const { return flags_; } + bool IsUnicodeMode() const { + // Either /v or /u enable UnicodeMode + // https://tc39.es/ecma262/#sec-parsepattern + return IsUnicode(flags()) || IsUnicodeSets(flags()) || force_unicode_; + } + bool unicode_sets() const { return IsUnicodeSets(flags()); } + bool ignore_case() const { return IsIgnoreCase(flags()); } + + static bool IsSyntaxCharacterOrSlash(uint32_t c); + static bool IsClassSetSyntaxCharacter(uint32_t c); + static bool IsClassSetReservedPunctuator(uint32_t c); + bool IsClassSetReservedDoublePunctuator(uint32_t c); + + static const uint32_t kEndMarker = (1 << 21); + + private: + // Return the 1-indexed RegExpCapture object, allocate if necessary. + RegExpCapture* GetCapture(int index); + + // Creates a new named capture at the specified index. Must be called exactly + // once for each named capture. Fails if a capture with the same name is + // encountered. + bool CreateNamedCaptureAtIndex(const RegExpParserState* state, int index); + + // Parses the name of a capture group (?pattern). The name must adhere + // to IdentifierName in the ECMAScript standard. + const ZoneVector* ParseCaptureGroupName(); + + bool ParseNamedBackReference(RegExpBuilder* builder, + RegExpParserState* state); + RegExpParserState* ParseOpenParenthesis(RegExpParserState* state); + + // After the initial parsing pass, patch corresponding RegExpCapture objects + // into all RegExpBackReferences. This is done after initial parsing in order + // to avoid complicating cases in which references comes before the capture. + void PatchNamedBackReferences(); + + ZoneVector* GetNamedCaptures(); + + // Returns true iff the pattern contains named captures. May call + // ScanForCaptures to look ahead at the remaining pattern. + bool HasNamedCaptures(InClassEscapeState in_class_escape_state); + + Zone* zone() const { return zone_; } + + uint32_t current() const { return current_; } + bool has_more() const { return has_more_; } + bool has_next() const { return next_pos_ < input_length(); } + uint32_t Next(); + template + uint32_t ReadNext(); + CharT InputAt(int index) const { + ASSERT(0 <= index && index < input_length()); + return input_[index]; + } + int input_length() const { return input_length_; } + void ScanForCaptures(InClassEscapeState in_class_escape_state); + + struct RegExpCaptureNameLess { + bool operator()(const RegExpCapture* lhs, const RegExpCapture* rhs) const { + DCHECK_NOT_NULL(lhs); + DCHECK_NOT_NULL(rhs); + return *lhs->name() < *rhs->name(); + } + }; + + class ForceUnicodeScope final { + public: + explicit ForceUnicodeScope(RegExpParserImpl* parser) + : parser_(parser) { + ASSERT(!parser_->force_unicode_); + parser_->force_unicode_ = true; + } + ~ForceUnicodeScope() { + ASSERT(parser_->force_unicode_); + parser_->force_unicode_ = false; + } + + private: + RegExpParserImpl* const parser_; + }; + + Zone* const zone_; + RegExpError error_ = RegExpError::kNone; + int error_pos_ = 0; + ZoneList* captures_; + // Maps capture names to a list of capture indices with this name. + ZoneMap*, RegExpCaptureNameLess>* + named_captures_; + ZoneList* named_back_references_; + const CharT* const input_; + const int input_length_; + uint32_t current_; + RegExpFlags flags_; + bool force_unicode_ = false; // Force parser to act as if unicode were set. + int next_pos_; + int captures_started_; + int capture_count_; // Only valid after we have scanned for captures. + int quantifier_count_; + int lookaround_count_; // Only valid after we have scanned for lookbehinds. + bool has_more_; + bool simple_; + bool contains_anchor_; + bool is_scanned_for_captures_; + bool has_named_captures_; // Only valid after we have scanned for captures. + bool failed_; + + friend class ::dart::RegExpParser; +}; + +template +RegExpParserImpl::RegExpParserImpl(const CharT* input, + int input_length, + RegExpFlags flags, + Zone* zone) + : zone_(zone), + captures_(nullptr), + named_captures_(nullptr), + named_back_references_(nullptr), + input_(input), + input_length_(input_length), + current_(kEndMarker), + flags_(flags), + next_pos_(0), + captures_started_(0), + capture_count_(0), + quantifier_count_(0), + lookaround_count_(0), + has_more_(true), + simple_(false), + contains_anchor_(false), + is_scanned_for_captures_(false), + has_named_captures_(false), + failed_(false) { + Advance(); +} + +template <> +template +uint32_t RegExpParserImpl::ReadNext() { + int position = next_pos_; + uint16_t c0 = InputAt(position); + position++; + ASSERT(!Utf16::IsLeadSurrogate(c0)); + if (update_position) next_pos_ = position; + return c0; +} + +template <> +template +uint32_t RegExpParserImpl::ReadNext() { + int position = next_pos_; + uint16_t c0 = InputAt(position); + uint32_t result = c0; + position++; + // Read the whole surrogate pair in case of unicode mode, if possible. + if (IsUnicodeMode() && position < input_length() && + Utf16::IsLeadSurrogate(c0)) { + uint16_t c1 = InputAt(position); + if (Utf16::IsTrailSurrogate(c1)) { + result = Utf16::CombineSurrogatePair(c0, c1); + position++; + } + } + if (update_position) next_pos_ = position; + return result; +} + +template +uint32_t RegExpParserImpl::Next() { + if (has_next()) { + return ReadNext(); + } else { + return kEndMarker; + } +} + +template +void RegExpParserImpl::Advance() { + if (has_next()) { + if (!OSThread::Current()->HasStackHeadroom()) { + if (FLAG_correctness_fuzzer_suppressions) { + FATAL("Aborting on stack overflow"); + } + ReportError(RegExpError::kStackOverflow); + } else { + current_ = ReadNext(); + } + } else { + current_ = kEndMarker; + // Advance so that position() points to 1-after-the-last-character. This is + // important so that Reset() to this position works correctly. + next_pos_ = input_length() + 1; + has_more_ = false; + } +} + +template +void RegExpParserImpl::RewindByOneCodepoint() { + if (!has_more()) return; + // Rewinds by one code point, i.e.: two code units if `current` is outside + // the basic multilingual plane (= composed of a lead and trail surrogate), + // or one code unit otherwise. + const int rewind_by = current() > Utf16::kMaxNonSurrogateCharCode ? -2 : -1; + Advance(rewind_by); // Undo the last Advance. +} + +template +void RegExpParserImpl::Reset(int pos) { + next_pos_ = pos; + has_more_ = (pos < input_length()); + Advance(); +} + +template +void RegExpParserImpl::Advance(int dist) { + next_pos_ += dist - 1; + Advance(); +} + +// static +template +bool RegExpParserImpl::IsSyntaxCharacterOrSlash(uint32_t c) { + switch (c) { + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + return true; + default: + break; + } + return false; +} + +// static +template +bool RegExpParserImpl::IsClassSetSyntaxCharacter(uint32_t c) { + switch (c) { + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '/': + case '-': + case '\\': + case '|': + return true; + default: + break; + } + return false; +} + +// static +template +bool RegExpParserImpl::IsClassSetReservedPunctuator(uint32_t c) { + switch (c) { + case '&': + case '-': + case '!': + case '#': + case '%': + case ',': + case ':': + case ';': + case '<': + case '=': + case '>': + case '@': + case '`': + case '~': + return true; + default: + break; + } + return false; +} + +template +bool RegExpParserImpl::IsClassSetReservedDoublePunctuator(uint32_t c) { +#define DOUBLE_PUNCTUATOR_CASE(Char) \ + case Char: \ + return Next() == Char + + switch (c) { + DOUBLE_PUNCTUATOR_CASE('&'); + DOUBLE_PUNCTUATOR_CASE('!'); + DOUBLE_PUNCTUATOR_CASE('#'); + DOUBLE_PUNCTUATOR_CASE('$'); + DOUBLE_PUNCTUATOR_CASE('%'); + DOUBLE_PUNCTUATOR_CASE('*'); + DOUBLE_PUNCTUATOR_CASE('+'); + DOUBLE_PUNCTUATOR_CASE(','); + DOUBLE_PUNCTUATOR_CASE('.'); + DOUBLE_PUNCTUATOR_CASE(':'); + DOUBLE_PUNCTUATOR_CASE(';'); + DOUBLE_PUNCTUATOR_CASE('<'); + DOUBLE_PUNCTUATOR_CASE('='); + DOUBLE_PUNCTUATOR_CASE('>'); + DOUBLE_PUNCTUATOR_CASE('?'); + DOUBLE_PUNCTUATOR_CASE('@'); + DOUBLE_PUNCTUATOR_CASE('^'); + DOUBLE_PUNCTUATOR_CASE('`'); + DOUBLE_PUNCTUATOR_CASE('~'); + default: + break; + } +#undef DOUBLE_PUNCTUATOR_CASE + + return false; +} + +template +RegExpTree* RegExpParserImpl::ReportError(RegExpError error) { + if (failed_) return nullptr; // Do not overwrite any existing error. + failed_ = true; + error_ = error; + error_pos_ = position(); + // Zip to the end to make sure no more input is read. + current_ = kEndMarker; + next_pos_ = input_length(); + has_more_ = false; + return nullptr; +} + +#define CHECK_FAILED /**/); \ + if (failed_) return nullptr; \ + ((void)0 + +// Pattern :: +// Disjunction +template +RegExpTree* RegExpParserImpl::ParsePattern() { + RegExpTree* result = ParseDisjunction(CHECK_FAILED); + PatchNamedBackReferences(CHECK_FAILED); + ASSERT(!has_more()); + // If the result of parsing is a literal string atom, and it has the + // same length as the input, then the atom is identical to the input. + if (result->IsAtom() && result->AsAtom()->length() == input_length()) { + simple_ = true; + } + return result; +} + +// Disjunction :: +// Alternative +// Alternative | Disjunction +// Alternative :: +// [empty] +// Term Alternative +// Term :: +// Assertion +// Atom +// Atom Quantifier +template +RegExpTree* RegExpParserImpl::ParseDisjunction() { + // Used to store current state while parsing subexpressions. + RegExpParserState initial_state(nullptr, INITIAL, RegExpLookaround::LOOKAHEAD, + 0, nullptr, flags(), zone()); + RegExpParserState* state = &initial_state; + // Cache the builder in a local variable for quick access. + RegExpBuilder* builder = initial_state.builder(); + while (true) { + switch (current()) { + case kEndMarker: + if (failed()) return nullptr; // E.g. the initial Advance failed. + if (state->IsSubexpression()) { + // Inside a parenthesized group when hitting end of input. + return ReportError(RegExpError::kUnterminatedGroup); + } + DCHECK_EQ(INITIAL, state->group_type()); + // Parsing completed successfully. + return builder->ToRegExp(); + case ')': { + if (!state->IsSubexpression()) { + return ReportError(RegExpError::kUnmatchedParen); + } + DCHECK_NE(INITIAL, state->group_type()); + + Advance(); + // End disjunction parsing and convert builder content to new single + // regexp atom. + RegExpTree* body = builder->ToRegExp(); + + int end_capture_index = captures_started(); + + int capture_index = state->capture_index(); + SubexpressionType group_type = state->group_type(); + + // Build result of subexpression. + if (group_type == CAPTURE) { + if (state->IsNamedCapture()) { + CreateNamedCaptureAtIndex(state, capture_index CHECK_FAILED); + } + RegExpCapture* capture = GetCapture(capture_index); + capture->set_body(body); + body = capture; + } else if (group_type == GROUPING) { + body = zone()->template New(body, builder->flags()); + } else { + ASSERT(group_type == POSITIVE_LOOKAROUND || + group_type == NEGATIVE_LOOKAROUND); + bool is_positive = (group_type == POSITIVE_LOOKAROUND); + body = zone()->template New( + body, is_positive, end_capture_index - capture_index, + capture_index, state->lookaround_type(), lookaround_count_); + lookaround_count_++; + } + + // Restore previous state. + state = state->previous_state(); + builder = state->builder(); + + builder->AddAtom(body); + // For compatibility with JSC and ES3, we allow quantifiers after + // lookaheads, and break in all cases. + break; + } + case '|': { + Advance(); + state->NewAlternative(captures_started()); + builder->NewAlternative(); + continue; + } + case '*': + case '+': + case '?': + return ReportError(RegExpError::kNothingToRepeat); + case '^': { + Advance(); + builder->AddAssertion(zone()->template New( + builder->multiline() ? RegExpAssertion::Type::START_OF_LINE + : RegExpAssertion::Type::START_OF_INPUT)); + set_contains_anchor(); + continue; + } + case '$': { + Advance(); + RegExpAssertion::Type assertion_type = + builder->multiline() ? RegExpAssertion::Type::END_OF_LINE + : RegExpAssertion::Type::END_OF_INPUT; + builder->AddAssertion( + zone()->template New(assertion_type)); + continue; + } + case '.': { + Advance(); + ZoneList* ranges = + zone()->template New>(2, zone()); + + if (builder->dotall()) { + // Everything. + CharacterRange::AddClassEscape(StandardCharacterSet::kEverything, + ranges, false, zone()); + } else { + // Everything except \x0A, \x0D, \u2028 and \u2029. + CharacterRange::AddClassEscape( + StandardCharacterSet::kNotLineTerminator, ranges, false, zone()); + } + + RegExpClassRanges* cc = + zone()->template New(zone(), ranges); + builder->AddClassRanges(cc); + break; + } + case '(': { + state = ParseOpenParenthesis(state CHECK_FAILED); + builder = state->builder(); + flags_ = builder->flags(); + continue; + } + case '[': { + RegExpTree* cc = ParseCharacterClass(builder CHECK_FAILED); + if (cc->IsClassRanges()) { + builder->AddClassRanges(cc->AsClassRanges()); + } else { + ASSERT(cc->IsClassSetExpression()); + builder->AddTerm(cc); + } + break; + } + // Atom :: + // \ AtomEscape + case '\\': + switch (Next()) { + case kEndMarker: + return ReportError(RegExpError::kEscapeAtEndOfPattern); + // AtomEscape :: + // [+UnicodeMode] DecimalEscape + // [~UnicodeMode] DecimalEscape but only if the CapturingGroupNumber + // of DecimalEscape is ≤ NcapturingParens + // CharacterEscape (some cases of this mixed in too) + // + // TODO(jgruber): It may make sense to disentangle all the different + // cases and make the structure mirror the spec, e.g. for AtomEscape: + // + // if (TryParseDecimalEscape(...)) return; + // if (TryParseCharacterClassEscape(...)) return; + // if (TryParseCharacterEscape(...)) return; + // if (TryParseGroupName(...)) return; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + int index = 0; + const bool is_backref = + ParseBackReferenceIndex(&index CHECK_FAILED); + if (is_backref) { + if (state->IsInsideCaptureGroup(index)) { + // The back reference is inside the capture group it refers to. + // Nothing can possibly have been captured yet, so we use empty + // instead. This ensures that, when checking a back reference, + // the capture registers of the referenced capture are either + // both set or both cleared. + builder->AddEmpty(); + } else { + RegExpCapture* capture = GetCapture(index); + RegExpTree* atom = + zone()->template New(capture, zone()); + builder->AddAtom(atom); + } + break; + } + // With /u and /v, no identity escapes except for syntax characters + // are allowed. Otherwise, all identity escapes are allowed. + if (IsUnicodeMode()) { + return ReportError(RegExpError::kInvalidEscape); + } + uint32_t first_digit = Next(); + if (first_digit == '8' || first_digit == '9') { + builder->AddCharacter(first_digit); + Advance(2); + break; + } + [[fallthrough]]; + } + case '0': { + Advance(); + if (IsUnicodeMode() && Next() >= '0' && Next() <= '9') { + // Decimal escape with leading 0 are not parsed as octal. + return ReportError(RegExpError::kInvalidDecimalEscape); + } + uint32_t octal = ParseOctalLiteral(); + builder->AddCharacter(octal); + break; + } + case 'b': + Advance(2); + builder->AddAssertion(zone()->template New( + RegExpAssertion::Type::BOUNDARY)); + continue; + case 'B': + Advance(2); + builder->AddAssertion(zone()->template New( + RegExpAssertion::Type::NON_BOUNDARY)); + continue; + // AtomEscape :: + // CharacterClassEscape + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': { + uint32_t next = Next(); + ZoneList* ranges = + zone()->template New>(2, zone()); + bool add_unicode_case_equivalents = + IsUnicodeMode() && ignore_case(); + bool parsed_character_class_escape = TryParseCharacterClassEscape( + next, InClassEscapeState::kNotInClass, ranges, nullptr, zone(), + add_unicode_case_equivalents CHECK_FAILED); + + if (parsed_character_class_escape) { + RegExpClassRanges* cc = + zone()->template New(zone(), ranges); + builder->AddClassRanges(cc); + } else { + CHECK(!IsUnicodeMode()); + Advance(2); + builder->AddCharacter(next); // IdentityEscape. + } + break; + } + case 'p': + case 'P': { + uint32_t next = Next(); + ZoneList* ranges = + zone()->template New>(2, zone()); + CharacterClassStrings* strings = nullptr; + if (unicode_sets()) { + strings = zone()->template New(zone()); + } + bool add_unicode_case_equivalents = ignore_case(); + bool parsed_character_class_escape = TryParseCharacterClassEscape( + next, InClassEscapeState::kNotInClass, ranges, strings, zone(), + add_unicode_case_equivalents CHECK_FAILED); + + if (parsed_character_class_escape) { + if (unicode_sets()) { + RegExpClassSetOperand* op = + zone()->template New(ranges, + strings); + builder->AddTerm(op); + } else { + RegExpClassRanges* cc = + zone()->template New(zone(), ranges); + builder->AddClassRanges(cc); + } + } else { + CHECK(!IsUnicodeMode()); + Advance(2); + builder->AddCharacter(next); // IdentityEscape. + } + break; + } + // AtomEscape :: + // k GroupName + case 'k': { + // Either an identity escape or a named back-reference. The two + // interpretations are mutually exclusive: '\k' is interpreted as + // an identity escape for non-Unicode patterns without named + // capture groups, and as the beginning of a named back-reference + // in all other cases. + const bool has_named_captures = + HasNamedCaptures(InClassEscapeState::kNotInClass CHECK_FAILED); + if (IsUnicodeMode() || has_named_captures) { + Advance(2); + ParseNamedBackReference(builder, state CHECK_FAILED); + break; + } + } + [[fallthrough]]; + // AtomEscape :: + // CharacterEscape + default: { + bool is_escaped_unicode_character = false; + uint32_t c = ParseCharacterEscape( + InClassEscapeState::kNotInClass, + &is_escaped_unicode_character CHECK_FAILED); + if (is_escaped_unicode_character) { + builder->AddEscapedUnicodeCharacter(c); + } else { + builder->AddCharacter(c); + } + break; + } + } + break; + case '{': { + int dummy; + bool parsed = ParseIntervalQuantifier(&dummy, &dummy CHECK_FAILED); + if (parsed) return ReportError(RegExpError::kNothingToRepeat); + [[fallthrough]]; + } + case '}': + case ']': + if (IsUnicodeMode()) { + return ReportError(RegExpError::kLoneQuantifierBrackets); + } + [[fallthrough]]; + default: + builder->AddUnicodeCharacter(current()); + Advance(); + break; + } // end switch(current()) + + int min; + int max; + switch (current()) { + // QuantifierPrefix :: + // * + // + + // ? + // { + case '*': + min = 0; + max = RegExpTree::kInfinity; + Advance(); + break; + case '+': + min = 1; + max = RegExpTree::kInfinity; + Advance(); + break; + case '?': + min = 0; + max = 1; + Advance(); + break; + case '{': + if (ParseIntervalQuantifier(&min, &max)) { + if (max < min) { + return ReportError(RegExpError::kRangeOutOfOrder); + } + break; + } else if (IsUnicodeMode()) { + // Incomplete quantifiers are not allowed. + return ReportError(RegExpError::kIncompleteQuantifier); + } + continue; + default: + continue; + } + RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; + if (current() == '?') { + quantifier_type = RegExpQuantifier::NON_GREEDY; + Advance(); + } else if (FLAG_regexp_possessive_quantifier && current() == '+') { + // v8_flags.regexp_possessive_quantifier is a debug-only flag. + quantifier_type = RegExpQuantifier::POSSESSIVE; + Advance(); + } + if (!builder->AddQuantifierToAtom(min, max, quantifier_count_, + quantifier_type)) { + return ReportError(RegExpError::kInvalidQuantifier); + } + ++quantifier_count_; + } +} + +template +RegExpParserState* RegExpParserImpl::ParseOpenParenthesis( + RegExpParserState* state) { + RegExpLookaround::Type lookaround_type = state->lookaround_type(); + bool is_named_capture = false; + const ZoneVector* capture_name = nullptr; + SubexpressionType subexpr_type = CAPTURE; + RegExpFlags flags = state->builder()->flags(); + bool parsing_modifiers = false; + bool modifiers_polarity = true; + RegExpFlags modifiers; + Advance(); + if (current() == '?') { + do { + uint32_t next = Next(); + switch (next) { + case '-': + if (!FLAG_js_regexp_modifiers) { + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + Advance(); + parsing_modifiers = true; + if (modifiers_polarity == false) { + ReportError(RegExpError::kMultipleFlagDashes); + return nullptr; + } + modifiers_polarity = false; + break; + case 'm': + case 'i': + case 's': { + if (!FLAG_js_regexp_modifiers) { + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + Advance(); + parsing_modifiers = true; + RegExpFlag flag = TryRegExpFlagFromChar(next).value(); + if ((modifiers & flag) != 0) { + ReportError(RegExpError::kRepeatedFlag); + return nullptr; + } + modifiers |= flag; + flags.set(flag, modifiers_polarity); + break; + } + case ':': + Advance(2); + parsing_modifiers = false; + subexpr_type = GROUPING; + break; + case '=': + Advance(2); + if (parsing_modifiers) { + ASSERT(FLAG_js_regexp_modifiers); + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + lookaround_type = RegExpLookaround::LOOKAHEAD; + subexpr_type = POSITIVE_LOOKAROUND; + break; + case '!': + Advance(2); + if (parsing_modifiers) { + ASSERT(FLAG_js_regexp_modifiers); + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + lookaround_type = RegExpLookaround::LOOKAHEAD; + subexpr_type = NEGATIVE_LOOKAROUND; + break; + case '<': + Advance(); + if (parsing_modifiers) { + ASSERT(FLAG_js_regexp_modifiers); + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + if (Next() == '=') { + Advance(2); + lookaround_type = RegExpLookaround::LOOKBEHIND; + subexpr_type = POSITIVE_LOOKAROUND; + break; + } else if (Next() == '!') { + Advance(2); + lookaround_type = RegExpLookaround::LOOKBEHIND; + subexpr_type = NEGATIVE_LOOKAROUND; + break; + } + is_named_capture = true; + has_named_captures_ = true; + Advance(); + break; + default: + ReportError(RegExpError::kInvalidGroup); + return nullptr; + } + } while (parsing_modifiers); + } + if (modifiers_polarity == false) { + // We encountered a dash. + if (modifiers == 0) { + ReportError(RegExpError::kInvalidFlagGroup); + return nullptr; + } + } + if (subexpr_type == CAPTURE) { + if (captures_started_ >= RegExpMacroAssembler::kMaxCaptures) { + ReportError(RegExpError::kTooManyCaptures); + return nullptr; + } + captures_started_++; + + if (is_named_capture) { + capture_name = ParseCaptureGroupName(CHECK_FAILED); + } + } + // Store current state and begin new disjunction parsing. + return zone()->template New( + state, subexpr_type, lookaround_type, captures_started_, capture_name, + flags, zone()); +} + +// In order to know whether an escape is a backreference or not we have to scan +// the entire regexp and find the number of capturing parentheses. However we +// don't want to scan the regexp twice unless it is necessary. This mini-parser +// is called when needed. It can see the difference between capturing and +// noncapturing parentheses and can skip character classes and backslash-escaped +// characters. +// +// Important: The scanner has to be in a consistent state when calling +// ScanForCaptures, e.g. not in the middle of an escape sequence '\[' or while +// parsing a nested class. +template +void RegExpParserImpl::ScanForCaptures( + InClassEscapeState in_class_escape_state) { + ASSERT(!is_scanned_for_captures_); + const int saved_position = position(); + // Start with captures started previous to current position + int capture_count = captures_started(); + // When we start inside a character class, skip everything inside the class. + if (in_class_escape_state == InClassEscapeState::kInClass) { + // \k is always invalid within a class in unicode mode, thus we should never + // call ScanForCaptures within a class. + ASSERT(!IsUnicodeMode()); + int c; + while ((c = current()) != kEndMarker) { + Advance(); + if (c == '\\') { + Advance(); + } else { + if (c == ']') break; + } + } + } + // Add count of captures after this position. + int n; + while ((n = current()) != kEndMarker) { + Advance(); + switch (n) { + case '\\': + Advance(); + break; + case '[': { + int class_nest_level = 0; + int c; + while ((c = current()) != kEndMarker) { + Advance(); + if (c == '\\') { + Advance(); + } else if (c == '[') { + // With /v, '[' inside a class is treated as a nested class. + // Without /v, '[' is a normal character. + if (unicode_sets()) class_nest_level++; + } else if (c == ']') { + if (class_nest_level == 0) break; + class_nest_level--; + } + } + break; + } + case '(': + if (current() == '?') { + // At this point we could be in + // * a non-capturing group '(:', + // * a lookbehind assertion '(?<=' '(? +bool RegExpParserImpl::ParseBackReferenceIndex(int* index_out) { + DCHECK_EQ('\\', current()); + ASSERT('1' <= Next() && Next() <= '9'); + // Try to parse a decimal literal that is no greater than the total number + // of left capturing parentheses in the input. + int start = position(); + int value = Next() - '0'; + Advance(2); + while (true) { + uint32_t c = current(); + if (IsDecimalDigit(c)) { + value = 10 * value + (c - '0'); + if (value > RegExpMacroAssembler::kMaxCaptures) { + Reset(start); + return false; + } + Advance(); + } else { + break; + } + } + if (value > captures_started()) { + if (!is_scanned_for_captures_) { + ScanForCaptures(InClassEscapeState::kNotInClass); + } + if (value > capture_count_) { + Reset(start); + return false; + } + } + *index_out = value; + return true; +} + +namespace { + +void push_code_unit(ZoneVector* v, uint32_t code_unit) { + if (code_unit <= Utf16::kMaxNonSurrogateCharCode) { + v->push_back(code_unit); + } else { + v->push_back(Utf16::LeadSurrogate(code_unit)); + v->push_back(Utf16::TrailSurrogate(code_unit)); + } +} + +} // namespace + +template +const ZoneVector* RegExpParserImpl::ParseCaptureGroupName() { + // Due to special Advance requirements (see the next comment), rewind by one + // such that names starting with a surrogate pair are parsed correctly for + // patterns where the unicode flag is unset. + // + // Note that we use this odd pattern of rewinding the last advance in order + // to adhere to the common parser behavior of expecting `current` to point at + // the first candidate character for a function (e.g. when entering ParseFoo, + // `current` should point at the first character of Foo). + RewindByOneCodepoint(); + + ZoneVector* name = + zone()->template New>(zone()); + + { + // Advance behavior inside this function is tricky since + // RegExpIdentifierName explicitly enables unicode (in spec terms, sets +U) + // and thus allows surrogate pairs and \u{}-style escapes even in + // non-unicode patterns. Therefore Advance within the capture group name + // has to force-enable unicode, and outside the name revert to default + // behavior. + ForceUnicodeScope force_unicode(this); + + bool at_start = true; + while (true) { + Advance(); + uint32_t c = current(); + + // Convert unicode escapes. + if (c == '\\' && Next() == 'u') { + Advance(2); + if (!ParseUnicodeEscape(&c)) { + ReportError(RegExpError::kInvalidUnicodeEscape); + return nullptr; + } + RewindByOneCodepoint(); + } + + // The backslash char is misclassified as both ID_Start and ID_Continue. + if (c == '\\') { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + + if (at_start) { + if (!IsIdentifierStart(c)) { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + push_code_unit(name, c); + at_start = false; + } else { + if (c == '>') { + break; + } else if (IsIdentifierPart(c)) { + push_code_unit(name, c); + } else { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + } + } + } + + // This final advance goes back into the state of pointing at the next + // relevant char, which the rest of the parser expects. See also the previous + // comments in this function. + Advance(); + return name; +} + +template +bool RegExpParserImpl::CreateNamedCaptureAtIndex( + const RegExpParserState* state, + int index) { + const ZoneVector* name = state->capture_name(); + const auto& non_participating_capture_group_intervals = + state->non_participating_capture_group_intervals(); + ASSERT(0 < index && index <= captures_started_); + DCHECK_NOT_NULL(name); + + RegExpCapture* capture = GetCapture(index); + DCHECK_NULL(capture->name()); + + capture->set_name(name); + + if (named_captures_ == nullptr) { + named_captures_ = zone_->template New< + ZoneMap*, RegExpCaptureNameLess>>(zone()); + } else { + // Check for duplicates and bail if we find any. + const auto& named_capture_it = named_captures_->find(capture); + if (named_capture_it != named_captures_->end()) { + if (FLAG_js_regexp_duplicate_named_groups) { + ZoneList* named_capture_indices = named_capture_it->second; + DCHECK_NOT_NULL(named_capture_indices); + ASSERT(!named_capture_indices->is_empty()); + for (int named_index : *named_capture_indices) { + bool is_duplicate = true; + for (Interval interval : non_participating_capture_group_intervals) { + ASSERT(!interval.is_empty()); + // We can stop as soon as we are inside one non-participating + // interval. There can't be a non-participating and participating + // interval, as intervals are never decreasing. + if (interval.Contains(named_index)) { + is_duplicate = false; + break; + } + // Intervals are ordered strictly increasing, so we can stop early + // when the current interval is past the current index. + if (named_index <= interval.from()) { + break; + } + } + if (is_duplicate) { + ReportError(RegExpError::kDuplicateCaptureGroupName); + return false; + } + } + } else { + ReportError(RegExpError::kDuplicateCaptureGroupName); + return false; + } + } + } + if (FLAG_js_regexp_duplicate_named_groups) { + // Check for nested named captures. This is necessary to find duplicate + // named captures within the same disjunct. + RegExpParserState* parent_state = state->previous_state(); + if (parent_state && parent_state->IsInsideCaptureGroup(name)) { + ReportError(RegExpError::kDuplicateCaptureGroupName); + return false; + } + } + + auto entry = named_captures_->try_emplace( + capture, zone()->template New>(1, zone())); + entry.first->second->Add(index, zone()); + return true; +} + +template +bool RegExpParserImpl::ParseNamedBackReference( + RegExpBuilder* builder, + RegExpParserState* state) { + // The parser is assumed to be on the '<' in \k. + if (current() != '<') { + ReportError(RegExpError::kInvalidNamedReference); + return false; + } + + Advance(); + const ZoneVector* name = ParseCaptureGroupName(); + if (name == nullptr) { + return false; + } + + if (state->IsInsideCaptureGroup(name)) { + builder->AddEmpty(); + } else { + RegExpBackReference* atom = + zone()->template New(zone()); + atom->set_name(name); + + builder->AddAtom(atom); + + if (named_back_references_ == nullptr) { + named_back_references_ = + zone()->template New>(1, zone()); + } + named_back_references_->Add(atom, zone()); + } + + return true; +} + +template +void RegExpParserImpl::PatchNamedBackReferences() { + if (named_back_references_ == nullptr) return; + + if (named_captures_ == nullptr) { + ReportError(RegExpError::kInvalidNamedCaptureReference); + return; + } + + // Look up and patch the actual capture for each named back reference. + + for (int i = 0; i < named_back_references_->length(); i++) { + RegExpBackReference* ref = named_back_references_->at(i); + + // Capture used to search the named_captures_ by name, index of the + // capture is never used. + static const int kInvalidIndex = 0; + RegExpCapture* search_capture = + zone()->template New(kInvalidIndex); + DCHECK_NULL(search_capture->name()); + search_capture->set_name(ref->name()); + + const auto& capture_it = named_captures_->find(search_capture); + if (capture_it == named_captures_->end()) { + ReportError(RegExpError::kInvalidNamedCaptureReference); + return; + } + + DCHECK_IMPLIES(!FLAG_js_regexp_duplicate_named_groups, + capture_it->second->length() == 1); + for (int index : *capture_it->second) { + ref->add_capture(GetCapture(index), zone()); + } + } +} + +template +RegExpCapture* RegExpParserImpl::GetCapture(int index) { + // The index for the capture groups are one-based. Its index in the list is + // zero-based. + const int known_captures = + is_scanned_for_captures_ ? capture_count_ : captures_started_; + SBXCHECK(index >= 1 && index <= known_captures); + if (captures_ == nullptr) { + captures_ = + zone()->template New>(known_captures, zone()); + } + while (captures_->length() < known_captures) { + captures_->Add(zone()->template New(captures_->length() + 1), + zone()); + } + return captures_->at(index - 1); +} + +template +ZoneVector* RegExpParserImpl::GetNamedCaptures() { + if (named_captures_ == nullptr) { + return nullptr; + } + ASSERT(!named_captures_->empty()); + + ZoneVector* flattened_named_captures = + zone()->template New>(zone()); + for (auto capture : *named_captures_) { + DCHECK_IMPLIES(!FLAG_js_regexp_duplicate_named_groups, + capture.second->length() == 1); + for (int index : *capture.second) { + flattened_named_captures->push_back(GetCapture(index)); + } + } + return flattened_named_captures; +} + +template +bool RegExpParserImpl::HasNamedCaptures( + InClassEscapeState in_class_escape_state) { + if (has_named_captures_ || is_scanned_for_captures_) { + return has_named_captures_; + } + + ScanForCaptures(in_class_escape_state); + ASSERT(is_scanned_for_captures_); + return has_named_captures_; +} + +// QuantifierPrefix :: +// { DecimalDigits } +// { DecimalDigits , } +// { DecimalDigits , DecimalDigits } +// +// Returns true if parsing succeeds, and set the min_out and max_out +// values. Values are truncated to RegExpTree::kInfinity if they overflow. +template +bool RegExpParserImpl::ParseIntervalQuantifier(int* min_out, + int* max_out) { + DCHECK_EQ(current(), '{'); + int start = position(); + Advance(); + int min = 0; + if (!IsDecimalDigit(current())) { + Reset(start); + return false; + } + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (min > (RegExpTree::kInfinity - next) / 10) { + // Overflow. Skip past remaining decimal digits and return -1. + do { + Advance(); + } while (IsDecimalDigit(current())); + min = RegExpTree::kInfinity; + break; + } + min = 10 * min + next; + Advance(); + } + int max = 0; + if (current() == '}') { + max = min; + Advance(); + } else if (current() == ',') { + Advance(); + if (current() == '}') { + max = RegExpTree::kInfinity; + Advance(); + } else { + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (max > (RegExpTree::kInfinity - next) / 10) { + do { + Advance(); + } while (IsDecimalDigit(current())); + max = RegExpTree::kInfinity; + break; + } + max = 10 * max + next; + Advance(); + } + if (current() != '}') { + Reset(start); + return false; + } + Advance(); + } + } else { + Reset(start); + return false; + } + *min_out = min; + *max_out = max; + return true; +} + +template +uint32_t RegExpParserImpl::ParseOctalLiteral() { + ASSERT(('0' <= current() && current() <= '7') || !has_more()); + // For compatibility with some other browsers (not all), we parse + // up to three octal digits with a value below 256. + // ES#prod-annexB-LegacyOctalEscapeSequence + uint32_t value = current() - '0'; + Advance(); + if ('0' <= current() && current() <= '7') { + value = value * 8 + current() - '0'; + Advance(); + if (value < 32 && '0' <= current() && current() <= '7') { + value = value * 8 + current() - '0'; + Advance(); + } + } + return value; +} + +template +bool RegExpParserImpl::ParseHexEscape(int length, uint32_t* value) { + int start = position(); + uint32_t val = 0; + for (int i = 0; i < length; ++i) { + uint32_t c = current(); + int d = base::HexValue(c); + if (d < 0) { + Reset(start); + return false; + } + val = val * 16 + d; + Advance(); + } + *value = val; + return true; +} + +// This parses RegExpUnicodeEscapeSequence as described in ECMA262. +template +bool RegExpParserImpl::ParseUnicodeEscape(uint32_t* value) { + // Accept both \uxxxx and \u{xxxxxx} (if harmony unicode escapes are + // allowed). In the latter case, the number of hex digits between { } is + // arbitrary. \ and u have already been read. + if (current() == '{' && IsUnicodeMode()) { + int start = position(); + Advance(); + if (ParseUnlimitedLengthHexNumber(0x10FFFF, value)) { + if (current() == '}') { + Advance(); + return true; + } + } + Reset(start); + return false; + } + // \u but no {, or \u{...} escapes not allowed. + bool result = ParseHexEscape(4, value); + if (result && IsUnicodeMode() && Utf16::IsLeadSurrogate(*value) && + current() == '\\') { + // Attempt to read trail surrogate. + int start = position(); + if (Next() == 'u') { + Advance(2); + uint32_t trail; + if (ParseHexEscape(4, &trail) && Utf16::IsTrailSurrogate(trail)) { + *value = Utf16::CombineSurrogatePair(static_cast(*value), + static_cast(trail)); + return true; + } + } + Reset(start); + } + return result; +} + +#ifdef V8_INTL_SUPPORT + +namespace { + +bool IsExactPropertyAlias(const char* property_name, UProperty property) { + const char* short_name = u_getPropertyName(property, U_SHORT_PROPERTY_NAME); + if (short_name != nullptr && strcmp(property_name, short_name) == 0) + return true; + for (int i = 0;; i++) { + const char* long_name = u_getPropertyName( + property, static_cast(U_LONG_PROPERTY_NAME + i)); + if (long_name == nullptr) break; + if (strcmp(property_name, long_name) == 0) return true; + } + return false; +} + +bool IsExactPropertyValueAlias(const char* property_value_name, + UProperty property, + int32_t property_value) { + const char* short_name = + u_getPropertyValueName(property, property_value, U_SHORT_PROPERTY_NAME); + if (short_name != nullptr && strcmp(property_value_name, short_name) == 0) { + return true; + } + for (int i = 0;; i++) { + const char* long_name = u_getPropertyValueName( + property, property_value, + static_cast(U_LONG_PROPERTY_NAME + i)); + if (long_name == nullptr) break; + if (strcmp(property_value_name, long_name) == 0) return true; + } + return false; +} + +void ExtractStringsFromUnicodeSet(const icu::UnicodeSet& set, + CharacterClassStrings* strings, + RegExpFlags flags, + Zone* zone) { + ASSERT(set.hasStrings()); + ASSERT(IsUnicodeSets(flags)); + DCHECK_NOT_NULL(strings); + + RegExpTextBuilder::SmallRegExpTreeVector string_storage(zone); + RegExpTextBuilder string_builder(zone, &string_storage, flags); + const bool needs_case_folding = IsIgnoreCase(flags); + icu::UnicodeSetIterator iter(set); + iter.skipToStrings(); + while (iter.next()) { + const icu::UnicodeString& s = iter.getString(); + const char16_t* p = s.getBuffer(); + int32_t length = s.length(); + ZoneList* string = + zone->template New>(length, zone); + for (int32_t i = 0; i < length;) { + UChar32 c; + U16_NEXT(p, i, length, c); + string_builder.AddUnicodeCharacter(c); + if (needs_case_folding) { + c = u_foldCase(c, U_FOLD_CASE_DEFAULT); + } + string->Add(c, zone); + } + strings->emplace(string->ToVector(), string_builder.ToRegExp()); + string_storage.clear(); + } +} + +bool LookupPropertyValueName(UProperty property, + const char* property_value_name, + bool negate, + ZoneList* result_ranges, + CharacterClassStrings* result_strings, + RegExpFlags flags, + Zone* zone) { + UProperty property_for_lookup = property; + if (property_for_lookup == UCHAR_SCRIPT_EXTENSIONS) { + // For the property Script_Extensions, we have to do the property value + // name lookup as if the property is Script. + property_for_lookup = UCHAR_SCRIPT; + } + int32_t property_value = + u_getPropertyValueEnum(property_for_lookup, property_value_name); + if (property_value == UCHAR_INVALID_CODE) return false; + + // We require the property name to match exactly to one of the property value + // aliases. However, u_getPropertyValueEnum uses loose matching. + if (!IsExactPropertyValueAlias(property_value_name, property_for_lookup, + property_value)) { + return false; + } + + UErrorCode ec = U_ZERO_ERROR; + icu::UnicodeSet set; + set.applyIntPropertyValue(property, property_value, ec); + bool success = ec == U_ZERO_ERROR && !set.isEmpty(); + + if (success) { + if (set.hasStrings()) { + ExtractStringsFromUnicodeSet(set, result_strings, flags, zone); + } + const bool needs_case_folding = IsUnicodeSets(flags) && IsIgnoreCase(flags); + if (needs_case_folding) set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); + set.removeAllStrings(); + if (negate) set.complement(); + for (int i = 0; i < set.getRangeCount(); i++) { + result_ranges->Add( + CharacterRange::Range(set.getRangeStart(i), set.getRangeEnd(i)), + zone); + } + } + return success; +} + +template +inline bool NameEquals(const char* name, const char (&literal)[N]) { + return strncmp(name, literal, N + 1) == 0; +} + +bool LookupSpecialPropertyValueName(const char* name, + ZoneList* result, + bool negate, + RegExpFlags flags, + Zone* zone) { + if (NameEquals(name, "Any")) { + if (negate) { + // Leave the list of character ranges empty, since the negation of 'Any' + // is the empty set. + } else { + result->Add(CharacterRange::Everything(), zone); + } + } else if (NameEquals(name, "ASCII")) { + result->Add(negate ? CharacterRange::Range(0x80, String::kMaxCodePoint) + : CharacterRange::Range(0x0, 0x7F), + zone); + } else if (NameEquals(name, "Assigned")) { + return LookupPropertyValueName(UCHAR_GENERAL_CATEGORY, "Unassigned", + !negate, result, nullptr, flags, zone); + } else { + return false; + } + return true; +} + +// Explicitly allowlist supported binary properties. The spec forbids supporting +// properties outside of this set to ensure interoperability. +bool IsSupportedBinaryProperty(UProperty property, bool unicode_sets) { + switch (property) { + case UCHAR_ALPHABETIC: + // 'Any' is not supported by ICU. See LookupSpecialPropertyValueName. + // 'ASCII' is not supported by ICU. See LookupSpecialPropertyValueName. + case UCHAR_ASCII_HEX_DIGIT: + // 'Assigned' is not supported by ICU. See LookupSpecialPropertyValueName. + case UCHAR_BIDI_CONTROL: + case UCHAR_BIDI_MIRRORED: + case UCHAR_CASE_IGNORABLE: + case UCHAR_CASED: + case UCHAR_CHANGES_WHEN_CASEFOLDED: + case UCHAR_CHANGES_WHEN_CASEMAPPED: + case UCHAR_CHANGES_WHEN_LOWERCASED: + case UCHAR_CHANGES_WHEN_NFKC_CASEFOLDED: + case UCHAR_CHANGES_WHEN_TITLECASED: + case UCHAR_CHANGES_WHEN_UPPERCASED: + case UCHAR_DASH: + case UCHAR_DEFAULT_IGNORABLE_CODE_POINT: + case UCHAR_DEPRECATED: + case UCHAR_DIACRITIC: + case UCHAR_EMOJI: + case UCHAR_EMOJI_COMPONENT: + case UCHAR_EMOJI_MODIFIER_BASE: + case UCHAR_EMOJI_MODIFIER: + case UCHAR_EMOJI_PRESENTATION: + case UCHAR_EXTENDED_PICTOGRAPHIC: + case UCHAR_EXTENDER: + case UCHAR_GRAPHEME_BASE: + case UCHAR_GRAPHEME_EXTEND: + case UCHAR_HEX_DIGIT: + case UCHAR_ID_CONTINUE: + case UCHAR_ID_START: + case UCHAR_IDEOGRAPHIC: + case UCHAR_IDS_BINARY_OPERATOR: + case UCHAR_IDS_TRINARY_OPERATOR: + case UCHAR_JOIN_CONTROL: + case UCHAR_LOGICAL_ORDER_EXCEPTION: + case UCHAR_LOWERCASE: + case UCHAR_MATH: + case UCHAR_NONCHARACTER_CODE_POINT: + case UCHAR_PATTERN_SYNTAX: + case UCHAR_PATTERN_WHITE_SPACE: + case UCHAR_QUOTATION_MARK: + case UCHAR_RADICAL: + case UCHAR_REGIONAL_INDICATOR: + case UCHAR_S_TERM: + case UCHAR_SOFT_DOTTED: + case UCHAR_TERMINAL_PUNCTUATION: + case UCHAR_UNIFIED_IDEOGRAPH: + case UCHAR_UPPERCASE: + case UCHAR_VARIATION_SELECTOR: + case UCHAR_WHITE_SPACE: + case UCHAR_XID_CONTINUE: + case UCHAR_XID_START: + return true; + case UCHAR_BASIC_EMOJI: + case UCHAR_EMOJI_KEYCAP_SEQUENCE: + case UCHAR_RGI_EMOJI_MODIFIER_SEQUENCE: + case UCHAR_RGI_EMOJI_FLAG_SEQUENCE: + case UCHAR_RGI_EMOJI_TAG_SEQUENCE: + case UCHAR_RGI_EMOJI_ZWJ_SEQUENCE: + case UCHAR_RGI_EMOJI: + return unicode_sets; + default: + break; + } + return false; +} + +bool IsBinaryPropertyOfStrings(UProperty property) { + switch (property) { + case UCHAR_BASIC_EMOJI: + case UCHAR_EMOJI_KEYCAP_SEQUENCE: + case UCHAR_RGI_EMOJI_MODIFIER_SEQUENCE: + case UCHAR_RGI_EMOJI_FLAG_SEQUENCE: + case UCHAR_RGI_EMOJI_TAG_SEQUENCE: + case UCHAR_RGI_EMOJI_ZWJ_SEQUENCE: + case UCHAR_RGI_EMOJI: + return true; + default: + break; + } + return false; +} + +bool IsUnicodePropertyValueCharacter(char c) { + // https://tc39.github.io/proposal-regexp-unicode-property-escapes/ + // + // Note that using this to validate each parsed char is quite conservative. + // A possible alternative solution would be to only ensure the parsed + // property name/value candidate string does not contain '\0' characters and + // let ICU lookups trigger the final failure. + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + if ('0' <= c && c <= '9') return true; + return (c == '_'); +} + +} // namespace + +template +bool RegExpParserImpl::ParsePropertyClassName(ZoneVector* name_1, + ZoneVector* name_2) { + ASSERT(name_1->empty()); + ASSERT(name_2->empty()); + // Parse the property class as follows: + // - In \p{name}, 'name' is interpreted + // - either as a general category property value name. + // - or as a binary property name. + // - In \p{name=value}, 'name' is interpreted as an enumerated property name, + // and 'value' is interpreted as one of the available property value names. + // - Aliases in PropertyAlias.txt and PropertyValueAlias.txt can be used. + // - Loose matching is not applied. + if (current() == '{') { + // Parse \p{[PropertyName=]PropertyNameValue} + for (Advance(); current() != '}' && current() != '='; Advance()) { + if (!IsUnicodePropertyValueCharacter(current())) return false; + if (!has_next()) return false; + name_1->push_back(static_cast(current())); + } + if (current() == '=') { + for (Advance(); current() != '}'; Advance()) { + if (!IsUnicodePropertyValueCharacter(current())) return false; + if (!has_next()) return false; + name_2->push_back(static_cast(current())); + } + name_2->push_back(0); // null-terminate string. + } + } else { + return false; + } + Advance(); + name_1->push_back(0); // null-terminate string. + + ASSERT(name_1->size() - 1 == std::strlen(name_1->data())); + ASSERT(name_2->empty() || name_2->size() - 1 == std::strlen(name_2->data())); + return true; +} + +template +bool RegExpParserImpl::AddPropertyClassRange( + ZoneList* add_to_ranges, + CharacterClassStrings* add_to_strings, + bool negate, + const ZoneVector& name_1, + const ZoneVector& name_2) { + if (name_2.empty()) { + // First attempt to interpret as general category property value name. + const char* name = name_1.data(); + if (LookupPropertyValueName(UCHAR_GENERAL_CATEGORY_MASK, name, negate, + add_to_ranges, add_to_strings, flags(), + zone())) { + return true; + } + // Interpret "Any", "ASCII", and "Assigned". + if (LookupSpecialPropertyValueName(name, add_to_ranges, negate, flags(), + zone())) { + return true; + } + // Then attempt to interpret as binary property name with value name 'Y'. + UProperty property = u_getPropertyEnum(name); + if (!IsSupportedBinaryProperty(property, unicode_sets())) return false; + if (!IsExactPropertyAlias(name, property)) return false; + // Negation of properties with strings is not allowed. + // See + // https://tc39.es/ecma262/#sec-static-semantics-maycontainstrings + if (negate && IsBinaryPropertyOfStrings(property)) return false; + if (unicode_sets()) { + // In /v mode we can't simple lookup the "false" binary property values, + // as the spec requires us to perform case folding before calculating the + // complement. + // See https://tc39.es/ecma262/#sec-compiletocharset + // UnicodePropertyValueExpression :: LoneUnicodePropertyNameOrValue + return LookupPropertyValueName(property, "Y", negate, add_to_ranges, + add_to_strings, flags(), zone()); + } else { + return LookupPropertyValueName(property, negate ? "N" : "Y", false, + add_to_ranges, add_to_strings, flags(), + zone()); + } + } else { + // Both property name and value name are specified. Attempt to interpret + // the property name as enumerated property. + const char* property_name = name_1.data(); + const char* value_name = name_2.data(); + UProperty property = u_getPropertyEnum(property_name); + if (!IsExactPropertyAlias(property_name, property)) return false; + if (property == UCHAR_GENERAL_CATEGORY) { + // We want to allow aggregate value names such as "Letter". + property = UCHAR_GENERAL_CATEGORY_MASK; + } else if (property != UCHAR_SCRIPT && + property != UCHAR_SCRIPT_EXTENSIONS) { + return false; + } + return LookupPropertyValueName(property, value_name, negate, add_to_ranges, + add_to_strings, flags(), zone()); + } +} + +#else // V8_INTL_SUPPORT + +template +bool RegExpParserImpl::ParsePropertyClassName(ZoneVector* name_1, + ZoneVector* name_2) { + return false; +} + +template +bool RegExpParserImpl::AddPropertyClassRange( + ZoneList* add_to_ranges, + CharacterClassStrings* add_to_strings, + bool negate, + const ZoneVector& name_1, + const ZoneVector& name_2) { + return false; +} + +#endif // V8_INTL_SUPPORT + +template +bool RegExpParserImpl::ParseUnlimitedLengthHexNumber(int max_value, + uint32_t* value) { + uint32_t x = 0; + int d = base::HexValue(current()); + if (d < 0) { + return false; + } + while (d >= 0) { + x = x * 16 + d; + if (x > static_cast(max_value)) { + return false; + } + Advance(); + d = base::HexValue(current()); + } + *value = x; + return true; +} + +// https://tc39.es/ecma262/#prod-CharacterEscape +template +uint32_t RegExpParserImpl::ParseCharacterEscape( + InClassEscapeState in_class_escape_state, + bool* is_escaped_unicode_character) { + DCHECK_EQ('\\', current()); + ASSERT(has_next()); + + Advance(); + + const uint32_t c = current(); + switch (c) { + // CharacterEscape :: + // ControlEscape :: one of + // f n r t v + case 'f': + Advance(); + return '\f'; + case 'n': + Advance(); + return '\n'; + case 'r': + Advance(); + return '\r'; + case 't': + Advance(); + return '\t'; + case 'v': + Advance(); + return '\v'; + // CharacterEscape :: + // c ControlLetter + case 'c': { + uint32_t controlLetter = Next(); + uint32_t letter = controlLetter & ~('A' ^ 'a'); + if (letter >= 'A' && letter <= 'Z') { + Advance(2); + // Control letters mapped to ASCII control characters in the range + // 0x00-0x1F. + return controlLetter & 0x1F; + } + if (IsUnicodeMode()) { + // With /u and /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidUnicodeEscape); + return 0; + } + if (in_class_escape_state == InClassEscapeState::kInClass) { + // Inside a character class, we also accept digits and underscore as + // control characters, unless with /u or /v. See Annex B: + // ES#prod-annexB-ClassControlLetter + if ((controlLetter >= '0' && controlLetter <= '9') || + controlLetter == '_') { + Advance(2); + return controlLetter & 0x1F; + } + } + // We match JSC in reading the backslash as a literal + // character instead of as starting an escape. + return '\\'; + } + // CharacterEscape :: + // 0 [lookahead ∉ DecimalDigit] + // [~UnicodeMode] LegacyOctalEscapeSequence + case '0': + // \0 is interpreted as NUL if not followed by another digit. + if (Next() < '0' || Next() > '9') { + Advance(); + return 0; + } + [[fallthrough]]; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + // For compatibility, we interpret a decimal escape that isn't + // a back reference (and therefore either \0 or not valid according + // to the specification) as a 1..3 digit octal character code. + // ES#prod-annexB-LegacyOctalEscapeSequence + if (IsUnicodeMode()) { + // With /u or /v, decimal escape is not interpreted as octal character + // code. + ReportError(RegExpError::kInvalidDecimalEscape); + return 0; + } + return ParseOctalLiteral(); + // CharacterEscape :: + // HexEscapeSequence + case 'x': { + Advance(); + uint32_t value; + if (ParseHexEscape(2, &value)) return value; + if (IsUnicodeMode()) { + // With /u or /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidEscape); + return 0; + } + // If \x is not followed by a two-digit hexadecimal, treat it + // as an identity escape. + return 'x'; + } + // CharacterEscape :: + // RegExpUnicodeEscapeSequence [?UnicodeMode] + case 'u': { + Advance(); + uint32_t value; + if (ParseUnicodeEscape(&value)) { + *is_escaped_unicode_character = true; + return value; + } + if (IsUnicodeMode()) { + // With /u or /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidUnicodeEscape); + return 0; + } + // If \u is not followed by a two-digit hexadecimal, treat it + // as an identity escape. + return 'u'; + } + default: + break; + } + + // CharacterEscape :: + // IdentityEscape[?UnicodeMode, ?N] + // + // * With /u, no identity escapes except for syntax characters are + // allowed. + // * With /v, no identity escapes except for syntax characters and + // ClassSetReservedPunctuators (if within a class) are allowed. + // * Without /u or /v: + // * '\c' is not an IdentityEscape. + // * '\k' is not an IdentityEscape when named captures exist. + // * Otherwise, all identity escapes are allowed. + if (unicode_sets() && in_class_escape_state == InClassEscapeState::kInClass) { + if (IsClassSetReservedPunctuator(c)) { + Advance(); + return c; + } + } + if (IsUnicodeMode()) { + if (!IsSyntaxCharacterOrSlash(c)) { + ReportError(RegExpError::kInvalidEscape); + return 0; + } + Advance(); + return c; + } + ASSERT(!IsUnicodeMode()); + if (c == 'c') { + ReportError(RegExpError::kInvalidEscape); + return 0; + } + Advance(); + // Note: It's important to Advance before the HasNamedCaptures call s.t. we + // don't start scanning in the middle of an escape. + if (c == 'k' && HasNamedCaptures(in_class_escape_state)) { + ReportError(RegExpError::kInvalidEscape); + return 0; + } + return c; +} + +// https://tc39.es/ecma262/#prod-ClassRanges +template +RegExpTree* RegExpParserImpl::ParseClassRanges( + ZoneList* ranges, + bool add_unicode_case_equivalents) { + uint32_t char_1, char_2; + bool is_class_1, is_class_2; + while (has_more() && current() != ']') { + ParseClassEscape(ranges, zone(), add_unicode_case_equivalents, &char_1, + &is_class_1 CHECK_FAILED); + // ClassAtom + if (current() == '-') { + Advance(); + if (!has_more()) { + // If we reach the end we break out of the loop and let the + // following code report an error. + break; + } else if (current() == ']') { + if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1), zone()); + ranges->Add(CharacterRange::Singleton('-'), zone()); + break; + } + ParseClassEscape(ranges, zone(), add_unicode_case_equivalents, &char_2, + &is_class_2 CHECK_FAILED); + if (is_class_1 || is_class_2) { + // Either end is an escaped character class. Treat the '-' verbatim. + if (IsUnicodeMode()) { + // ES2015 21.2.2.15.1 step 1. + return ReportError(RegExpError::kInvalidCharacterClass); + } + if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1), zone()); + ranges->Add(CharacterRange::Singleton('-'), zone()); + if (!is_class_2) ranges->Add(CharacterRange::Singleton(char_2), zone()); + continue; + } + // ES2015 21.2.2.15.1 step 6. + if (char_1 > char_2) { + return ReportError(RegExpError::kOutOfOrderCharacterClass); + } + ranges->Add(CharacterRange::Range(char_1, char_2), zone()); + } else { + if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1), zone()); + } + } + return nullptr; +} + +// https://tc39.es/ecma262/#prod-ClassEscape +template +void RegExpParserImpl::ParseClassEscape( + ZoneList* ranges, + Zone* zone, + bool add_unicode_case_equivalents, + uint32_t* char_out, + bool* is_class_escape) { + *is_class_escape = false; + + if (current() != '\\') { + // Not a ClassEscape. + *char_out = current(); + Advance(); + return; + } + + const uint32_t next = Next(); + switch (next) { + case 'b': + *char_out = '\b'; + Advance(2); + return; + case '-': + if (IsUnicodeMode()) { + *char_out = next; + Advance(2); + return; + } + break; + case kEndMarker: + ReportError(RegExpError::kEscapeAtEndOfPattern); + return; + default: + break; + } + + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + *is_class_escape = + TryParseCharacterClassEscape(next, kInClassEscape, ranges, nullptr, zone, + add_unicode_case_equivalents); + if (*is_class_escape) return; + + bool dummy = false; // Unused. + *char_out = ParseCharacterEscape(kInClassEscape, &dummy); +} + +// https://tc39.es/ecma262/#prod-CharacterClassEscape +template +bool RegExpParserImpl::TryParseCharacterClassEscape( + uint32_t next, + InClassEscapeState in_class_escape_state, + ZoneList* ranges, + CharacterClassStrings* strings, + Zone* zone, + bool add_unicode_case_equivalents) { + DCHECK_EQ(current(), '\\'); + DCHECK_EQ(Next(), next); + + switch (next) { + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + CharacterRange::AddClassEscape(static_cast(next), + ranges, add_unicode_case_equivalents, + zone); + Advance(2); + return true; + case 'p': + case 'P': { + if (!IsUnicodeMode()) return false; + bool negate = next == 'P'; + Advance(2); + ZoneVector name_1(zone); + ZoneVector name_2(zone); + if (!ParsePropertyClassName(&name_1, &name_2) || + !AddPropertyClassRange(ranges, strings, negate, name_1, name_2)) { + ReportError(in_class_escape_state == InClassEscapeState::kInClass + ? RegExpError::kInvalidClassPropertyName + : RegExpError::kInvalidPropertyName); + } + return true; + } + default: + return false; + } +} + +namespace { + +// Add |string| to |ranges| if length of |string| == 1, otherwise add |string| +// to |strings|. +void AddClassString(ZoneList* normalized_string, + RegExpTree* regexp_string, + ZoneList* ranges, + CharacterClassStrings* strings, + Zone* zone) { + if (normalized_string->length() == 1) { + ranges->Add(CharacterRange::Singleton(normalized_string->at(0)), zone); + } else { + strings->emplace(normalized_string->ToVector(), regexp_string); + } +} + +} // namespace + +// https://tc39.es/ecma262/#prod-ClassStringDisjunction +template +RegExpTree* RegExpParserImpl::ParseClassStringDisjunction( + ZoneList* ranges, + CharacterClassStrings* strings) { + ASSERT(unicode_sets()); + DCHECK_EQ(current(), '\\'); + DCHECK_EQ(Next(), 'q'); + Advance(2); + if (current() != '{') { + // Identity escape of 'q' is not allowed in unicode mode. + return ReportError(RegExpError::kInvalidEscape); + } + Advance(); + + ZoneList* string = + zone()->template New>(4, zone()); + RegExpTextBuilder::SmallRegExpTreeVector string_storage(zone()); + RegExpTextBuilder string_builder(zone(), &string_storage, flags()); + + while (has_more() && current() != '}') { + if (current() == '|') { + AddClassString(string, string_builder.ToRegExp(), ranges, strings, + zone()); + string = zone()->template New>(4, zone()); + string_storage.clear(); + Advance(); + } else { + uint32_t c = ParseClassSetCharacter(CHECK_FAILED); + if (ignore_case()) { +#ifdef V8_INTL_SUPPORT + c = u_foldCase(c, U_FOLD_CASE_DEFAULT); +#else + c = AsciiAlphaToLower(c); +#endif + } + string->Add(c, zone()); + string_builder.AddUnicodeCharacter(c); + } + } + + AddClassString(string, string_builder.ToRegExp(), ranges, strings, zone()); + CharacterRange::Canonicalize(ranges); + + // We don't need to handle missing closing '}' here. + // If the character class is correctly closed, ParseClassSetCharacter will + // report an error. + Advance(); + return nullptr; +} + +// https://tc39.es/ecma262/#prod-ClassSetOperand +// Tree returned based on type_out: +// * kNestedClass: RegExpClassSetExpression +// * For all other types: RegExpClassSetOperand +template +RegExpTree* RegExpParserImpl::ParseClassSetOperand( + const RegExpBuilder* builder, + ClassSetOperandType* type_out) { + ZoneList* ranges = + zone()->template New>(1, zone()); + CharacterClassStrings* strings = + zone()->template New(zone()); + uint32_t character; + RegExpTree* tree = ParseClassSetOperand(builder, type_out, ranges, strings, + &character CHECK_FAILED); + DCHECK_IMPLIES(*type_out != ClassSetOperandType::kNestedClass, + tree == nullptr); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, + ranges->is_empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, + strings->empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + ranges->is_empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + strings->empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + tree->IsClassSetExpression()); + // ClassSetRange is only used within ClassSetUnion(). + DCHECK_NE(*type_out, ClassSetOperandType::kClassSetRange); + // There are no restrictions for kCharacterClassEscape. + // CharacterClassEscape includes \p{}, which can contain ranges, strings or + // both and \P{}, which could contain nothing (i.e. \P{Any}). + if (tree == nullptr) { + if (*type_out == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Singleton(character)); + } + tree = zone()->template New(ranges, strings); + } + return tree; +} + +// https://tc39.es/ecma262/#prod-ClassSetOperand +// Based on |type_out| either a tree is returned or +// |ranges|/|strings|/|character| modified. If a tree is returned, +// ranges/strings are not modified. If |type_out| is kNestedClass, a tree of +// type RegExpClassSetExpression is returned. If | type_out| is +// kClassSetCharacter, |character| is set and nullptr returned. For all other +// types, |ranges|/|strings|/|character| is modified and nullptr is returned. +template +RegExpTree* RegExpParserImpl::ParseClassSetOperand( + const RegExpBuilder* builder, + ClassSetOperandType* type_out, + ZoneList* ranges, + CharacterClassStrings* strings, + uint32_t* character) { + ASSERT(unicode_sets()); + uint32_t c = current(); + if (c == '\\') { + const uint32_t next = Next(); + if (next == 'q') { + *type_out = ClassSetOperandType::kClassStringDisjunction; + ParseClassStringDisjunction(ranges, strings CHECK_FAILED); + return nullptr; + } + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + const bool add_unicode_case_equivalents = ignore_case(); + if (TryParseCharacterClassEscape(next, kInClassEscape, ranges, strings, + zone(), add_unicode_case_equivalents)) { + *type_out = ClassSetOperandType::kCharacterClassEscape; + return nullptr; + } + } + + if (c == '[') { + *type_out = ClassSetOperandType::kNestedClass; + return ParseCharacterClass(builder); + } + + *type_out = ClassSetOperandType::kClassSetCharacter; + c = ParseClassSetCharacter(CHECK_FAILED); + *character = c; + return nullptr; +} + +template +uint32_t RegExpParserImpl::ParseClassSetCharacter() { + ASSERT(unicode_sets()); + const uint32_t c = current(); + if (c == '\\') { + const uint32_t next = Next(); + switch (next) { + case 'b': + Advance(2); + return '\b'; + case kEndMarker: + ReportError(RegExpError::kEscapeAtEndOfPattern); + return 0; + } + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + + bool dummy = false; // Unused. + return ParseCharacterEscape(kInClassEscape, &dummy); + } + if (IsClassSetSyntaxCharacter(c)) { + ReportError(RegExpError::kInvalidCharacterInClass); + return 0; + } + if (IsClassSetReservedDoublePunctuator(c)) { + ReportError(RegExpError::kInvalidClassSetOperation); + return 0; + } + Advance(); + return c; +} + +namespace { + +bool MayContainStrings(ClassSetOperandType type, RegExpTree* operand) { + switch (type) { + case ClassSetOperandType::kClassSetCharacter: + case ClassSetOperandType::kClassSetRange: + return false; + case ClassSetOperandType::kCharacterClassEscape: + case ClassSetOperandType::kClassStringDisjunction: + return operand->AsClassSetOperand()->has_strings(); + case ClassSetOperandType::kNestedClass: + if (operand->IsClassRanges()) return false; + return operand->AsClassSetExpression()->may_contain_strings(); + } + UNREACHABLE(); + return false; +} + +} // namespace + +template +void RegExpParserImpl::AddMaybeSimpleCaseFoldedRange( + ZoneList* ranges, + CharacterRange new_range) { + ASSERT(unicode_sets()); + if (ignore_case()) { + ZoneList* new_ranges = + zone()->template New>(2, zone()); + new_ranges->Add(new_range, zone()); + CharacterRange::AddUnicodeCaseEquivalents(new_ranges, zone()); + ranges->AddAll(*new_ranges, zone()); + } else { + ranges->Add(new_range, zone()); + } + CharacterRange::Canonicalize(ranges); +} + +// https://tc39.es/ecma262/#prod-ClassUnion +template +RegExpTree* RegExpParserImpl::ParseClassUnion( + const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type, + ZoneList* ranges, + CharacterClassStrings* strings, + uint32_t character) { + ASSERT(unicode_sets()); + ZoneList* operands = + zone()->template New>(2, zone()); + bool may_contain_strings = false; + // Add the lhs to operands if necessary. + // Either the lhs values were added to |ranges|/|strings| (in which case + // |first_operand| is nullptr), or the lhs was evaluated to a tree and passed + // as |first_operand| (in which case |ranges| and |strings| are empty). + if (first_operand != nullptr) { + may_contain_strings = MayContainStrings(first_operand_type, first_operand); + operands->Add(first_operand, zone()); + } + ClassSetOperandType last_type = first_operand_type; + while (has_more() && current() != ']') { + if (current() == '-') { + // Mix of ClassSetRange and ClassSubtraction is not allowed. + if (Next() == '-') { + return ReportError(RegExpError::kInvalidClassSetOperation); + } + Advance(); + if (!has_more()) { + // If we reach the end we break out of the loop and let the + // following code report an error. + break; + } + // If the lhs and rhs around '-' are both ClassSetCharacters, they + // represent a character range. + // In case one of them is not a ClassSetCharacter, it is a syntax error, + // as '-' can not be used unescaped within a class with /v. + // See + // https://tc39.es/ecma262/#prod-ClassSetRange + if (last_type != ClassSetOperandType::kClassSetCharacter) { + return ReportError(RegExpError::kInvalidCharacterClass); + } + uint32_t from = character; + ParseClassSetOperand(builder, &last_type, ranges, strings, + &character CHECK_FAILED); + if (last_type != ClassSetOperandType::kClassSetCharacter) { + return ReportError(RegExpError::kInvalidCharacterClass); + } + if (from > character) { + return ReportError(RegExpError::kOutOfOrderCharacterClass); + } + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Range(from, character)); + last_type = ClassSetOperandType::kClassSetRange; + } else { + DCHECK_NE(current(), '-'); + if (last_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Singleton(character)); + } + RegExpTree* operand = ParseClassSetOperand( + builder, &last_type, ranges, strings, &character CHECK_FAILED); + if (operand != nullptr) { + may_contain_strings |= MayContainStrings(last_type, operand); + // Add the range we started building as operand and reset the current + // range. + if (!ranges->is_empty() || !strings->empty()) { + may_contain_strings |= !strings->empty(); + operands->Add( + zone()->template New(ranges, strings), + zone()); + ranges = zone()->template New>(2, zone()); + strings = zone()->template New(zone()); + } + operands->Add(operand, zone()); + } + } + } + + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + + if (last_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, CharacterRange::Singleton(character)); + } + + // Add the range we started building as operand. + if (!ranges->is_empty() || !strings->empty()) { + may_contain_strings |= !strings->empty(); + operands->Add(zone()->template New(ranges, strings), + zone()); + } + + DCHECK_EQ(current(), ']'); + Advance(); + + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + + if (operands->is_empty()) { + // Return empty expression if no operands were added (e.g. [\P{Any}] + // produces an empty range). + ASSERT(ranges->is_empty()); + ASSERT(strings->empty()); + return RegExpClassSetExpression::Empty(zone(), is_negated); + } + + return zone()->template New( + RegExpClassSetExpression::OperationType::kUnion, is_negated, + may_contain_strings, operands); +} + +// https://tc39.es/ecma262/#prod-ClassIntersection +template +RegExpTree* RegExpParserImpl::ParseClassIntersection( + const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type) { + ASSERT(unicode_sets()); + ASSERT(current() == '&' && Next() == '&'); + bool may_contain_strings = + MayContainStrings(first_operand_type, first_operand); + ZoneList* operands = + zone()->template New>(2, zone()); + operands->Add(first_operand, zone()); + while (has_more() && current() != ']') { + if (current() != '&' || Next() != '&') { + return ReportError(RegExpError::kInvalidClassSetOperation); + } + Advance(2); + // [lookahead ≠ &] + if (current() == '&') { + return ReportError(RegExpError::kInvalidCharacterInClass); + } + + ClassSetOperandType operand_type; + RegExpTree* operand = + ParseClassSetOperand(builder, &operand_type CHECK_FAILED); + may_contain_strings &= MayContainStrings(operand_type, operand); + operands->Add(operand, zone()); + } + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + DCHECK_EQ(current(), ']'); + Advance(); + return zone()->template New( + RegExpClassSetExpression::OperationType::kIntersection, is_negated, + may_contain_strings, operands); +} + +// https://tc39.es/ecma262/#prod-ClassSubtraction +template +RegExpTree* RegExpParserImpl::ParseClassSubtraction( + const RegExpBuilder* builder, + bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type) { + ASSERT(unicode_sets()); + ASSERT(current() == '-' && Next() == '-'); + const bool may_contain_strings = + MayContainStrings(first_operand_type, first_operand); + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + ZoneList* operands = + zone()->template New>(2, zone()); + operands->Add(first_operand, zone()); + while (has_more() && current() != ']') { + if (current() != '-' || Next() != '-') { + return ReportError(RegExpError::kInvalidClassSetOperation); + } + Advance(2); + ClassSetOperandType dummy; // unused + RegExpTree* operand = ParseClassSetOperand(builder, &dummy CHECK_FAILED); + operands->Add(operand, zone()); + } + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + DCHECK_EQ(current(), ']'); + Advance(); + return zone()->template New( + RegExpClassSetExpression::OperationType::kSubtraction, is_negated, + may_contain_strings, operands); +} + +// https://tc39.es/ecma262/#prod-CharacterClass +template +RegExpTree* RegExpParserImpl::ParseCharacterClass( + const RegExpBuilder* builder) { + DCHECK_EQ(current(), '['); + Advance(); + bool is_negated = false; + if (current() == '^') { + is_negated = true; + Advance(); + } + ZoneList* ranges = + zone()->template New>(2, zone()); + if (current() == ']') { + Advance(); + if (unicode_sets()) { + return RegExpClassSetExpression::Empty(zone(), is_negated); + } else { + RegExpClassRanges::ClassRangesFlags class_ranges_flags; + if (is_negated) class_ranges_flags = RegExpClassRanges::NEGATED; + return zone()->template New(zone(), ranges, + class_ranges_flags); + } + } + + if (!unicode_sets()) { + bool add_unicode_case_equivalents = IsUnicodeMode() && ignore_case(); + ParseClassRanges(ranges, add_unicode_case_equivalents CHECK_FAILED); + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + DCHECK_EQ(current(), ']'); + Advance(); + RegExpClassRanges::ClassRangesFlags character_class_flags; + if (is_negated) character_class_flags = RegExpClassRanges::NEGATED; + if (!ignore_case()) { + character_class_flags |= RegExpClassRanges::NO_CASE_FOLDING_NEEDED; + } + if (sizeof(CharT) == 1) { + // No surrogate pairs. + character_class_flags |= RegExpClassRanges::IS_CERTAINLY_ONE_CODE_POINT; + } + return zone()->template New(zone(), ranges, + character_class_flags); + } else { + ClassSetOperandType operand_type; + CharacterClassStrings* strings = + zone()->template New(zone()); + uint32_t character; + RegExpTree* operand = ParseClassSetOperand( + builder, &operand_type, ranges, strings, &character CHECK_FAILED); + switch (current()) { + case '-': + if (Next() == '-') { + if (operand == nullptr) { + if (operand_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange( + ranges, CharacterRange::Singleton(character)); + } + operand = + zone()->template New(ranges, strings); + } + return ParseClassSubtraction(builder, is_negated, operand, + operand_type); + } + // ClassSetRange is handled in ParseClassUnion(). + break; + case '&': + if (Next() == '&') { + if (operand == nullptr) { + if (operand_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange( + ranges, CharacterRange::Singleton(character)); + } + operand = + zone()->template New(ranges, strings); + } + return ParseClassIntersection(builder, is_negated, operand, + operand_type); + } + } + return ParseClassUnion(builder, is_negated, operand, operand_type, ranges, + strings, character); + } +} + +#undef CHECK_FAILED + +template +bool RegExpParserImpl::Parse(RegExpCompileData* result) { + DCHECK_NOT_NULL(result); + RegExpTree* tree = ParsePattern(); + + if (failed()) { + DCHECK_NULL(tree); + DCHECK_NE(error_, RegExpError::kNone); + result->error = error_; + result->error_pos = error_pos_; + return false; + } + + DCHECK_NOT_NULL(tree); + DCHECK_EQ(error_, RegExpError::kNone); +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(FLAG_trace_regexp_parser)) { + StdoutStream os; + RegExpAstNodePrinter printer(os, nullptr, zone()); + printer.Print(tree); + os << "\n"; + } +#endif + + result->tree = tree; + const int capture_count = captures_started(); + result->simple = tree->IsAtom() && simple() && capture_count == 0; + result->contains_anchor = contains_anchor(); + result->capture_count = capture_count; + result->named_captures = GetNamedCaptures(); + return true; +} + +void RegExpBuilder::FlushText() { + text_builder().FlushText(); +} + +void RegExpBuilder::AddCharacter(uint16_t c) { + pending_empty_ = false; + text_builder().AddCharacter(c); +} + +void RegExpBuilder::AddUnicodeCharacter(uint32_t c) { + pending_empty_ = false; + text_builder().AddUnicodeCharacter(c); +} + +void RegExpBuilder::AddEscapedUnicodeCharacter(uint32_t character) { + pending_empty_ = false; + text_builder().AddEscapedUnicodeCharacter(character); +} + +void RegExpBuilder::AddEmpty() { + text_builder().FlushPendingSurrogate(); + pending_empty_ = true; +} + +void RegExpBuilder::AddClassRanges(RegExpClassRanges* cc) { + pending_empty_ = false; + text_builder().AddClassRanges(cc); +} + +void RegExpBuilder::AddAtom(RegExpTree* term) { + if (term->IsEmpty()) { + AddEmpty(); + return; + } + pending_empty_ = false; + if (term->IsTextElement()) { + text_builder().AddAtom(term); + } else { + FlushText(); + terms_.emplace_back(term); + } +} + +void RegExpBuilder::AddTerm(RegExpTree* term) { + ASSERT(!term->IsEmpty()); + pending_empty_ = false; + if (term->IsTextElement()) { + text_builder().AddTerm(term); + } else { + FlushText(); + terms_.emplace_back(term); + } +} + +void RegExpBuilder::AddAssertion(RegExpTree* assert) { + FlushText(); + pending_empty_ = false; + terms_.emplace_back(assert); +} + +void RegExpBuilder::NewAlternative() { + FlushTerms(); +} + +void RegExpBuilder::FlushTerms() { + FlushText(); + size_t num_terms = terms_.size(); + RegExpTree* alternative; + if (num_terms == 0) { + alternative = zone()->New(); + } else if (num_terms == 1) { + alternative = terms_.back(); + } else { + alternative = + zone()->New(zone()->New>( + base::VectorOf(terms_.begin(), terms_.size()), zone())); + } + alternatives_.emplace_back(alternative); + terms_.clear(); +} + +RegExpTree* RegExpBuilder::ToRegExp() { + FlushTerms(); + size_t num_alternatives = alternatives_.size(); + if (num_alternatives == 0) return zone()->New(); + if (num_alternatives == 1) return alternatives_.back(); + return zone()->New(zone()->New>( + base::VectorOf(alternatives_.begin(), alternatives_.size()), zone())); +} + +bool RegExpBuilder::AddQuantifierToAtom( + int min, + int max, + int index, + RegExpQuantifier::QuantifierType quantifier_type) { + if (pending_empty_) { + pending_empty_ = false; + return true; + } + RegExpTree* atom = text_builder().PopLastAtom(); + if (atom != nullptr) { + FlushText(); + } else if (!terms_.empty()) { + atom = terms_.back(); + terms_.pop_back(); + if (atom->IsLookaround()) { + // With /u or /v, lookarounds are not quantifiable. + if (IsUnicodeMode()) return false; + // Lookbehinds are not quantifiable. + if (atom->AsLookaround()->type() == RegExpLookaround::LOOKBEHIND) { + return false; + } + } + if (atom->max_match() == 0) { + // Guaranteed to only match an empty string. + if (min == 0) { + return true; + } + terms_.emplace_back(atom); + return true; + } + } else { + // Only call immediately after adding an atom or character! + UNREACHABLE(); + } + terms_.emplace_back( + zone()->New(min, max, quantifier_type, index, atom)); + return true; +} + +template class RegExpParserImpl; +template class RegExpParserImpl; + +} // namespace + +// static +bool RegExpParser::ParseRegExpFromHeapString(Isolate* isolate, + Zone* zone, + const String& input, + RegExpFlags flags, + RegExpCompileData* result) { + NoSafepointScope no_safepoint; + if (input.IsOneByteString()) { + RegExpParserImpl p(OneByteString::DataStart(input), input.Length(), + flags, zone); + return p.Parse(result); + } else { + RegExpParserImpl p(TwoByteString::DataStart(input), + input.Length(), flags, zone); + return p.Parse(result); + } +} + +// static +template +bool RegExpParser::VerifyRegExpSyntax(Zone* zone, + uintptr_t stack_limit, + const CharT* input, + int input_length, + RegExpFlags flags, + RegExpCompileData* result) { + RegExpParserImpl p(input, input_length, flags, zone); + return p.Parse(result); +} + +template bool RegExpParser::VerifyRegExpSyntax(Zone*, + uintptr_t, + const uint8_t*, + int, + RegExpFlags, + RegExpCompileData*); +template bool RegExpParser::VerifyRegExpSyntax(Zone*, + uintptr_t, + const uint16_t*, + int, + RegExpFlags, + RegExpCompileData*); + +} // namespace dart diff --git a/runtime/vm/regexp/regexp-parser.h b/runtime/vm/regexp/regexp-parser.h new file mode 100644 index 00000000000..fcbf00fc812 --- /dev/null +++ b/runtime/vm/regexp/regexp-parser.h @@ -0,0 +1,39 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_REGEXP_PARSER_H_ +#define V8_REGEXP_REGEXP_PARSER_H_ + +#include "vm/allocation.h" +#include "vm/growable_array.h" +#include "vm/regexp/regexp-ast.h" +#include "vm/regexp/regexp-flags.h" + +namespace dart { + +class String; +class Zone; + +struct RegExpCompileData; + +class RegExpParser : public AllStatic { + public: + static bool ParseRegExpFromHeapString(Isolate* isolate, + Zone* zone, + const String& input, + RegExpFlags flags, + RegExpCompileData* result); + + template + static bool VerifyRegExpSyntax(Zone* zone, + uintptr_t stack_limit, + const CharT* input, + int input_length, + RegExpFlags flags, + RegExpCompileData* result); +}; + +} // namespace dart + +#endif // V8_REGEXP_REGEXP_PARSER_H_ diff --git a/runtime/vm/regexp/regexp.cc b/runtime/vm/regexp/regexp.cc index fcc979896ba..7ee9453cd61 100644 --- a/runtime/vm/regexp/regexp.cc +++ b/runtime/vm/regexp/regexp.cc @@ -1,5603 +1,551 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. #include "vm/regexp/regexp.h" +#include +#include #include +#include -#include "platform/splay-tree-inl.h" -#include "platform/unicode.h" - -#include "unicode/uniset.h" - -#include "vm/dart_entry.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/regexp/regexp_assembler_bytecode.h" -#include "vm/regexp/regexp_ast.h" -#include "vm/regexp/unibrow-inl.h" +#include "vm/regexp/regexp-bytecode-generator.h" +#include "vm/regexp/regexp-bytecodes.h" +#include "vm/regexp/regexp-compiler.h" +#include "vm/regexp/regexp-interpreter.h" +#include "vm/regexp/regexp-macro-assembler.h" +#include "vm/regexp/regexp-parser.h" #include "vm/symbols.h" -#include "vm/thread.h" - -#if !defined(DART_PRECOMPILED_RUNTIME) -#include "vm/regexp/regexp_assembler_ir.h" -#endif // !defined(DART_PRECOMPILED_RUNTIME) - -#define Z (zone()) namespace dart { -// Default to generating optimized regexp code. -static constexpr bool kRegexpOptimization = true; +using namespace regexp_compiler_constants; // NOLINT(build/namespaces) -// More makes code generation slower, less makes V8 benchmark score lower. -static constexpr intptr_t kMaxLookaheadForBoyerMoore = 8; - -ContainedInLattice AddRange(ContainedInLattice containment, - const int32_t* ranges, - intptr_t ranges_length, - Interval new_range) { - ASSERT((ranges_length & 1) == 1); - ASSERT(ranges[ranges_length - 1] == Utf::kMaxCodePoint + 1); - if (containment == kLatticeUnknown) return containment; - bool inside = false; - int32_t last = 0; - for (intptr_t i = 0; i < ranges_length; - inside = !inside, last = ranges[i], i++) { - // Consider the range from last to ranges[i]. - // We haven't got to the new range yet. - if (ranges[i] <= new_range.from()) continue; - // New range is wholly inside last-ranges[i]. Note that new_range.to() is - // inclusive, but the values in ranges are not. - if (last <= new_range.from() && new_range.to() < ranges[i]) { - return Combine(containment, inside ? kLatticeIn : kLatticeOut); - } - return kLatticeUnknown; - } - return containment; -} - -// ------------------------------------------------------------------- -// Implementation of the Irregexp regular expression engine. -// -// The Irregexp regular expression engine is intended to be a complete -// implementation of ECMAScript regular expressions. It generates -// IR code that is subsequently compiled to native code. - -// The Irregexp regexp engine is structured in three steps. -// 1) The parser generates an abstract syntax tree. See regexp_ast.cc. -// 2) From the AST a node network is created. The nodes are all -// subclasses of RegExpNode. The nodes represent states when -// executing a regular expression. Several optimizations are -// performed on the node network. -// 3) From the nodes we generate IR instructions that can actually -// execute the regular expression (perform the search). The -// code generation step is described in more detail below. - -// Code generation. -// -// The nodes are divided into four main categories. -// * Choice nodes -// These represent places where the regular expression can -// match in more than one way. For example on entry to an -// alternation (foo|bar) or a repetition (*, +, ? or {}). -// * Action nodes -// These represent places where some action should be -// performed. Examples include recording the current position -// in the input string to a register (in order to implement -// captures) or other actions on register for example in order -// to implement the counters needed for {} repetitions. -// * Matching nodes -// These attempt to match some element part of the input string. -// Examples of elements include character classes, plain strings -// or back references. -// * End nodes -// These are used to implement the actions required on finding -// a successful match or failing to find a match. -// -// The code generated maintains some state as it runs. This consists of the -// following elements: -// -// * The capture registers. Used for string captures. -// * Other registers. Used for counters etc. -// * The current position. -// * The stack of backtracking information. Used when a matching node -// fails to find a match and needs to try an alternative. -// -// Conceptual regular expression execution model: -// -// There is a simple conceptual model of regular expression execution -// which will be presented first. The actual code generated is a more -// efficient simulation of the simple conceptual model: -// -// * Choice nodes are implemented as follows: -// For each choice except the last { -// push current position -// push backtrack code location -// -// backtrack code location: -// pop current position -// } -// -// -// * Actions nodes are generated as follows -// -// -// push backtrack code location -// -// backtrack code location: -// -// -// -// * Matching nodes are generated as follows: -// if input string matches at current position -// update current position -// -// else -// -// -// Thus it can be seen that the current position is saved and restored -// by the choice nodes, whereas the registers are saved and restored by -// by the action nodes that manipulate them. -// -// The other interesting aspect of this model is that nodes are generated -// at the point where they are needed by a recursive call to Emit(). If -// the node has already been code generated then the Emit() call will -// generate a jump to the previously generated code instead. In order to -// limit recursion it is possible for the Emit() function to put the node -// on a work list for later generation and instead generate a jump. The -// destination of the jump is resolved later when the code is generated. -// -// Actual regular expression code generation. -// -// Code generation is actually more complicated than the above. In order -// to improve the efficiency of the generated code some optimizations are -// performed -// -// * Choice nodes have 1-character lookahead. -// A choice node looks at the following character and eliminates some of -// the choices immediately based on that character. This is not yet -// implemented. -// * Simple greedy loops store reduced backtracking information. -// A quantifier like /.*foo/m will greedily match the whole input. It will -// then need to backtrack to a point where it can match "foo". The naive -// implementation of this would push each character position onto the -// backtracking stack, then pop them off one by one. This would use space -// proportional to the length of the input string. However since the "." -// can only match in one way and always has a constant length (in this case -// of 1) it suffices to store the current position on the top of the stack -// once. Matching now becomes merely incrementing the current position and -// backtracking becomes decrementing the current position and checking the -// result against the stored current position. This is faster and saves -// space. -// * The current state is virtualized. -// This is used to defer expensive operations until it is clear that they -// are needed and to generate code for a node more than once, allowing -// specialized an efficient versions of the code to be created. This is -// explained in the section below. -// -// Execution state virtualization. -// -// Instead of emitting code, nodes that manipulate the state can record their -// manipulation in an object called the Trace. The Trace object can record a -// current position offset, an optional backtrack code location on the top of -// the virtualized backtrack stack and some register changes. When a node is -// to be emitted it can flush the Trace or update it. Flushing the Trace -// will emit code to bring the actual state into line with the virtual state. -// Avoiding flushing the state can postpone some work (e.g. updates of capture -// registers). Postponing work can save time when executing the regular -// expression since it may be found that the work never has to be done as a -// failure to match can occur. In addition it is much faster to jump to a -// known backtrack code location than it is to pop an unknown backtrack -// location from the stack and jump there. -// -// The virtual state found in the Trace affects code generation. For example -// the virtual state contains the difference between the actual current -// position and the virtual current position, and matching code needs to use -// this offset to attempt a match in the correct location of the input -// string. Therefore code generated for a non-trivial trace is specialized -// to that trace. The code generator therefore has the ability to generate -// code for each node several times. In order to limit the size of the -// generated code there is an arbitrary limit on how many specialized sets of -// code may be generated for a given node. If the limit is reached, the -// trace is flushed and a generic version of the code for a node is emitted. -// This is subsequently used for that node. The code emitted for non-generic -// trace is not recorded in the node and so it cannot currently be reused in -// the event that code generation is requested for an identical trace. - -void RegExpTree::AppendToText(RegExpText* text) { - UNREACHABLE(); -} - -void RegExpAtom::AppendToText(RegExpText* text) { - text->AddElement(TextElement::Atom(this)); -} - -void RegExpCharacterClass::AppendToText(RegExpText* text) { - text->AddElement(TextElement::CharClass(this)); -} - -void RegExpText::AppendToText(RegExpText* text) { - for (intptr_t i = 0; i < elements()->length(); i++) - text->AddElement((*elements())[i]); -} - -TextElement TextElement::Atom(RegExpAtom* atom) { - return TextElement(ATOM, atom); -} - -TextElement TextElement::CharClass(RegExpCharacterClass* char_class) { - return TextElement(CHAR_CLASS, char_class); -} - -intptr_t TextElement::length() const { - switch (text_type()) { - case ATOM: - return atom()->length(); - - case CHAR_CLASS: - return 1; - } - UNREACHABLE(); - return 0; -} - -class FrequencyCollator : public ValueObject { +class RegExpImpl final : public AllStatic { public: - FrequencyCollator() : total_samples_(0) { - for (intptr_t i = 0; i < RegExpMacroAssembler::kTableSize; i++) { - frequencies_[i] = CharacterFrequency(i); - } - } + // Returns a string representation of a regular expression. + // Implements RegExp.prototype.toString, see ECMA-262 section 15.10.6.4. + // This function calls the garbage collector if necessary. + static const StringPtr ToString(const Object& value); - void CountCharacter(intptr_t character) { - intptr_t index = (character & RegExpMacroAssembler::kTableMask); - frequencies_[index].Increment(); - total_samples_++; - } + // Prepares a JSRegExp object with Irregexp-specific data. + static void IrregexpInitialize(Isolate* isolate, + const RegExp& re, + const String& pattern, + RegExpFlags flags, + int capture_count, + uint32_t backtrack_limit, + uint32_t bit_field); - // Does not measure in percent, but rather per-128 (the table size from the - // regexp macro assembler). - intptr_t Frequency(intptr_t in_character) { - ASSERT((in_character & RegExpMacroAssembler::kTableMask) == in_character); - if (total_samples_ < 1) return 1; // Division by zero. - intptr_t freq_in_per128 = - (frequencies_[in_character].counter() * 128) / total_samples_; - return freq_in_per128; - } + // Prepare a RegExp for being executed one or more times (using + // IrregexpExecOnce) on the subject. + // This ensures that the regexp is compiled for the subject, and that + // the subject is flat. + // Returns the number of integer spaces required by IrregexpExecOnce + // as its "registers" argument. If the regexp cannot be compiled, + // an exception is thrown as indicated by a negative return value. + static int IrregexpPrepare(Isolate* isolate, + const RegExp& regexp_data, + const String& subject, + bool is_sticky); - private: - class CharacterFrequency { - public: - CharacterFrequency() : counter_(0), character_(-1) {} - explicit CharacterFrequency(intptr_t character) - : counter_(0), character_(character) {} + // Execute a regular expression on the subject, starting from index. + // If matching succeeds, return the number of matches. This can be larger + // than one in the case of global regular expressions. + // The captures and subcaptures are stored into the registers vector. + // If matching fails, returns RE_FAILURE. + // If execution fails, sets an exception and returns RE_EXCEPTION. + static int IrregexpExecRaw(Isolate* isolate, + const RegExp& regexp_data, + const String& subject, + int index, + int32_t* output, + int output_size); - void Increment() { counter_++; } - intptr_t counter() { return counter_; } - intptr_t character() { return character_; } + // Execute an Irregexp bytecode pattern. Returns the number of matches, or an + // empty handle in case of an exception. + V8_WARN_UNUSED_RESULT static std::optional IrregexpExec( + Isolate* isolate, + const RegExp& regexp_data, + const String& subject, + int index, + int32_t* result_offsets_vector, + uint32_t result_offsets_vector_length); - private: - intptr_t counter_; - intptr_t character_; + static bool CompileIrregexpFromSource( + Thread* thread, + const RegExp& re_data, + const String& sample_subject, + bool is_one_byte, + bool sticky, + RegExpCompilationTarget compilation_target); + static bool CompileIrregexpFromBytecode(Isolate* isolate, + const RegExp& re_data, + const String& sample_subject, + bool is_one_byte); + static inline bool EnsureCompiledIrregexp(Thread* thread, + const RegExp& re_data, + const String& sample_subject, + bool is_one_byte, + bool sticky); - DISALLOW_ALLOCATION(); - }; - - private: - CharacterFrequency frequencies_[RegExpMacroAssembler::kTableSize]; - intptr_t total_samples_; + // Returns true on success, false on failure. + static bool Compile(Isolate* isolate, + Zone* zone, + RegExpCompileData* input, + RegExpFlags flags, + const String& pattern, + const String& sample_subject, + const RegExp& re_data, + bool is_one_byte); }; -class RegExpCompiler : public ValueObject { - public: - RegExpCompiler(intptr_t capture_count, bool is_one_byte); - - intptr_t AllocateRegister() { return next_register_++; } - - // Lookarounds to match lone surrogates for unicode character class matches - // are never nested. We can therefore reuse registers. - intptr_t UnicodeLookaroundStackRegister() { - if (unicode_lookaround_stack_register_ == kNoRegister) { - unicode_lookaround_stack_register_ = AllocateRegister(); - } - return unicode_lookaround_stack_register_; - } - - intptr_t UnicodeLookaroundPositionRegister() { - if (unicode_lookaround_position_register_ == kNoRegister) { - unicode_lookaround_position_register_ = AllocateRegister(); - } - return unicode_lookaround_position_register_; - } - -#if !defined(DART_PRECOMPILED_RUNTIME) - RegExpEngine::CompilationResult Assemble(IRRegExpMacroAssembler* assembler, - RegExpNode* start, - intptr_t capture_count, - const String& pattern); -#endif - - RegExpEngine::CompilationResult Assemble( - BytecodeRegExpMacroAssembler* assembler, - RegExpNode* start, - intptr_t capture_count, - const String& pattern); - - inline void AddWork(RegExpNode* node) { work_list_->Add(node); } - - static constexpr intptr_t kImplementationOffset = 0; - static constexpr intptr_t kNumberOfRegistersOffset = 0; - static constexpr intptr_t kCodeOffset = 1; - - RegExpMacroAssembler* macro_assembler() { return macro_assembler_; } - EndNode* accept() { return accept_; } - - static constexpr intptr_t kMaxRecursion = 100; - inline intptr_t recursion_depth() { return recursion_depth_; } - inline void IncrementRecursionDepth() { recursion_depth_++; } - inline void DecrementRecursionDepth() { recursion_depth_--; } - - void SetRegExpTooBig() { reg_exp_too_big_ = true; } - - inline bool one_byte() const { return is_one_byte_; } - bool read_backward() { return read_backward_; } - void set_read_backward(bool value) { read_backward_ = value; } - FrequencyCollator* frequency_collator() { return &frequency_collator_; } - - intptr_t current_expansion_factor() { return current_expansion_factor_; } - void set_current_expansion_factor(intptr_t value) { - current_expansion_factor_ = value; - } - - Zone* zone() const { return zone_; } - - static constexpr intptr_t kNoRegister = -1; - - private: - EndNode* accept_; - intptr_t next_register_; - intptr_t unicode_lookaround_stack_register_; - intptr_t unicode_lookaround_position_register_; - ZoneGrowableArray* work_list_; - intptr_t recursion_depth_; - RegExpMacroAssembler* macro_assembler_; - bool is_one_byte_; - bool reg_exp_too_big_; - bool read_backward_; - intptr_t current_expansion_factor_; - FrequencyCollator frequency_collator_; - Zone* zone_; -}; - -class RecursionCheck : public ValueObject { - public: - explicit RecursionCheck(RegExpCompiler* compiler) : compiler_(compiler) { - compiler->IncrementRecursionDepth(); - } - ~RecursionCheck() { compiler_->DecrementRecursionDepth(); } - - private: - RegExpCompiler* compiler_; -}; - -static RegExpEngine::CompilationResult IrregexpRegExpTooBig() { - return RegExpEngine::CompilationResult("RegExp too big"); -} - -// Attempts to compile the regexp using an Irregexp code generator. Returns -// a fixed array or a null handle depending on whether it succeeded. -RegExpCompiler::RegExpCompiler(intptr_t capture_count, bool is_one_byte) - : next_register_(2 * (capture_count + 1)), - unicode_lookaround_stack_register_(kNoRegister), - unicode_lookaround_position_register_(kNoRegister), - work_list_(nullptr), - recursion_depth_(0), - is_one_byte_(is_one_byte), - reg_exp_too_big_(false), - read_backward_(false), - current_expansion_factor_(1), - zone_(Thread::Current()->zone()) { - accept_ = new (Z) EndNode(EndNode::ACCEPT, Z); -} - -#if !defined(DART_PRECOMPILED_RUNTIME) -RegExpEngine::CompilationResult RegExpCompiler::Assemble( - IRRegExpMacroAssembler* macro_assembler, - RegExpNode* start, - intptr_t capture_count, - const String& pattern) { - macro_assembler->set_slow_safe(false /* use_slow_safe_regexp_compiler */); - macro_assembler_ = macro_assembler; - - ZoneGrowableArray work_list(0); - work_list_ = &work_list; - BlockLabel fail; - macro_assembler_->PushBacktrack(&fail); - Trace new_trace; - start->Emit(this, &new_trace); - macro_assembler_->BindBlock(&fail); - macro_assembler_->Fail(); - while (!work_list.is_empty()) { - work_list.RemoveLast()->Emit(this, &new_trace); - } - if (reg_exp_too_big_) return IrregexpRegExpTooBig(); - - macro_assembler->GenerateBacktrackBlock(); - macro_assembler->FinalizeRegistersArray(); - - return RegExpEngine::CompilationResult( - macro_assembler->backtrack_goto(), macro_assembler->graph_entry(), - macro_assembler->num_blocks(), macro_assembler->num_stack_locals(), - next_register_); -} -#endif - -RegExpEngine::CompilationResult RegExpCompiler::Assemble( - BytecodeRegExpMacroAssembler* macro_assembler, - RegExpNode* start, - intptr_t capture_count, - const String& pattern) { - macro_assembler->set_slow_safe(false /* use_slow_safe_regexp_compiler */); - macro_assembler_ = macro_assembler; - - ZoneGrowableArray work_list(0); - work_list_ = &work_list; - BlockLabel fail; - macro_assembler_->PushBacktrack(&fail); - Trace new_trace; - start->Emit(this, &new_trace); - macro_assembler_->BindBlock(&fail); - macro_assembler_->Fail(); - while (!work_list.is_empty()) { - work_list.RemoveLast()->Emit(this, &new_trace); - } - if (reg_exp_too_big_) return IrregexpRegExpTooBig(); - - TypedData& bytecode = TypedData::ZoneHandle(macro_assembler->GetBytecode()); - return RegExpEngine::CompilationResult(&bytecode, next_register_); -} - -bool Trace::DeferredAction::Mentions(intptr_t that) { - if (action_type() == ActionNode::CLEAR_CAPTURES) { - Interval range = static_cast(this)->range(); - return range.Contains(that); - } else { - return reg() == that; - } -} - -bool Trace::mentions_reg(intptr_t reg) { - for (DeferredAction* action = actions_; action != nullptr; - action = action->next()) { - if (action->Mentions(reg)) return true; - } - return false; -} - -bool Trace::GetStoredPosition(intptr_t reg, intptr_t* cp_offset) { - ASSERT(*cp_offset == 0); - for (DeferredAction* action = actions_; action != nullptr; - action = action->next()) { - if (action->Mentions(reg)) { - if (action->action_type() == ActionNode::STORE_POSITION) { - *cp_offset = static_cast(action)->cp_offset(); - return true; - } else { - return false; - } - } - } - return false; -} - -// This is called as we come into a loop choice node and some other tricky -// nodes. It normalizes the state of the code generator to ensure we can -// generate generic code. -intptr_t Trace::FindAffectedRegisters(OutSet* affected_registers, Zone* zone) { - intptr_t max_register = RegExpCompiler::kNoRegister; - for (DeferredAction* action = actions_; action != nullptr; - action = action->next()) { - if (action->action_type() == ActionNode::CLEAR_CAPTURES) { - Interval range = static_cast(action)->range(); - for (intptr_t i = range.from(); i <= range.to(); i++) - affected_registers->Set(i, zone); - if (range.to() > max_register) max_register = range.to(); - } else { - affected_registers->Set(action->reg(), zone); - if (action->reg() > max_register) max_register = action->reg(); - } - } - return max_register; -} - -void Trace::RestoreAffectedRegisters(RegExpMacroAssembler* assembler, - intptr_t max_register, - const OutSet& registers_to_pop, - const OutSet& registers_to_clear) { - for (intptr_t reg = max_register; reg >= 0; reg--) { - if (registers_to_pop.Get(reg)) { - assembler->PopRegister(reg); - } else if (registers_to_clear.Get(reg)) { - intptr_t clear_to = reg; - while (reg > 0 && registers_to_clear.Get(reg - 1)) { - reg--; - } - assembler->ClearRegisters(reg, clear_to); - } - } -} - -void Trace::PerformDeferredActions(RegExpMacroAssembler* assembler, - intptr_t max_register, - const OutSet& affected_registers, - OutSet* registers_to_pop, - OutSet* registers_to_clear, - Zone* zone) { - for (intptr_t reg = 0; reg <= max_register; reg++) { - if (!affected_registers.Get(reg)) { - continue; - } - - // The chronologically first deferred action in the trace - // is used to infer the action needed to restore a register - // to its previous state (or not, if it's safe to ignore it). - enum DeferredActionUndoType { ACTION_IGNORE, ACTION_RESTORE, ACTION_CLEAR }; - DeferredActionUndoType undo_action = ACTION_IGNORE; - - intptr_t value = 0; - bool absolute = false; - bool clear = false; - const intptr_t kNoStore = kMinInt32; - intptr_t store_position = kNoStore; - // This is a little tricky because we are scanning the actions in reverse - // historical order (newest first). - for (DeferredAction* action = actions_; action != nullptr; - action = action->next()) { - if (action->Mentions(reg)) { - switch (action->action_type()) { - case ActionNode::SET_REGISTER: { - Trace::DeferredSetRegister* psr = - static_cast(action); - if (!absolute) { - value += psr->value(); - absolute = true; - } - // SET_REGISTER is currently only used for newly introduced loop - // counters. They can have a significant previous value if they - // occur in a loop. TODO(lrn): Propagate this information, so we - // can set undo_action to ACTION_IGNORE if we know there is no - // value to restore. - undo_action = ACTION_RESTORE; - ASSERT(store_position == kNoStore); - ASSERT(!clear); - break; - } - case ActionNode::INCREMENT_REGISTER: - if (!absolute) { - value++; - } - ASSERT(store_position == kNoStore); - ASSERT(!clear); - undo_action = ACTION_RESTORE; - break; - case ActionNode::STORE_POSITION: { - Trace::DeferredCapture* pc = - static_cast(action); - if (!clear && store_position == kNoStore) { - store_position = pc->cp_offset(); - } - - // For captures we know that stores and clears alternate. - // Other register, are never cleared, and if the occur - // inside a loop, they might be assigned more than once. - if (reg <= 1) { - // Registers zero and one, aka "capture zero", is - // always set correctly if we succeed. There is no - // need to undo a setting on backtrack, because we - // will set it again or fail. - undo_action = ACTION_IGNORE; - } else { - undo_action = pc->is_capture() ? ACTION_CLEAR : ACTION_RESTORE; - } - ASSERT(!absolute); - ASSERT(value == 0); - break; - } - case ActionNode::CLEAR_CAPTURES: { - // Since we're scanning in reverse order, if we've already - // set the position we have to ignore historically earlier - // clearing operations. - if (store_position == kNoStore) { - clear = true; - } - undo_action = ACTION_RESTORE; - ASSERT(!absolute); - ASSERT(value == 0); - break; - } - default: - UNREACHABLE(); - break; - } - } - } - // Prepare for the undo-action (e.g., push if it's going to be popped). - if (undo_action == ACTION_RESTORE) { - assembler->PushRegister(reg); - registers_to_pop->Set(reg, zone); - } else if (undo_action == ACTION_CLEAR) { - registers_to_clear->Set(reg, zone); - } - // Perform the chronologically last action (or accumulated increment) - // for the register. - if (store_position != kNoStore) { - assembler->WriteCurrentPositionToRegister(reg, store_position); - } else if (clear) { - assembler->ClearRegisters(reg, reg); - } else if (absolute) { - assembler->SetRegister(reg, value); - } else if (value != 0) { - assembler->AdvanceRegister(reg, value); - } - } -} - -// This is called as we come into a loop choice node and some other tricky -// nodes. It normalizes the state of the code generator to ensure we can -// generate generic code. -void Trace::Flush(RegExpCompiler* compiler, RegExpNode* successor) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - - ASSERT(!is_trivial()); - - if (actions_ == nullptr && backtrack() == nullptr) { - // Here we just have some deferred cp advances to fix and we are back to - // a normal situation. We may also have to forget some information gained - // through a quick check that was already performed. - if (cp_offset_ != 0) assembler->AdvanceCurrentPosition(cp_offset_); - // Create a new trivial state and generate the node with that. - Trace new_state; - successor->Emit(compiler, &new_state); - return; - } - - // Generate deferred actions here along with code to undo them again. - OutSet affected_registers; - - if (backtrack() != nullptr) { - // Here we have a concrete backtrack location. These are set up by choice - // nodes and so they indicate that we have a deferred save of the current - // position which we may need to emit here. - assembler->PushCurrentPosition(); - } - Zone* zone = successor->zone(); - intptr_t max_register = FindAffectedRegisters(&affected_registers, zone); - OutSet registers_to_pop; - OutSet registers_to_clear; - PerformDeferredActions(assembler, max_register, affected_registers, - ®isters_to_pop, ®isters_to_clear, zone); - if (cp_offset_ != 0) { - assembler->AdvanceCurrentPosition(cp_offset_); - } - - // Create a new trivial state and generate the node with that. - BlockLabel undo; - assembler->PushBacktrack(&undo); - Trace new_state; - successor->Emit(compiler, &new_state); - - // On backtrack we need to restore state. - assembler->BindBlock(&undo); - RestoreAffectedRegisters(assembler, max_register, registers_to_pop, - registers_to_clear); - if (backtrack() == nullptr) { - assembler->Backtrack(); - } else { - assembler->PopCurrentPosition(); - assembler->GoTo(backtrack()); - } -} - -void NegativeSubmatchSuccess::Emit(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - - // Omit flushing the trace. We discard the entire stack frame anyway. - - if (!label()->is_bound()) { - // We are completely independent of the trace, since we ignore it, - // so this code can be used as the generic version. - assembler->BindBlock(label()); - } - - // Throw away everything on the backtrack stack since the start - // of the negative submatch and restore the character position. - assembler->ReadCurrentPositionFromRegister(current_position_register_); - assembler->ReadStackPointerFromRegister(stack_pointer_register_); - if (clear_capture_count_ > 0) { - // Clear any captures that might have been performed during the success - // of the body of the negative look-ahead. - int clear_capture_end = clear_capture_start_ + clear_capture_count_ - 1; - assembler->ClearRegisters(clear_capture_start_, clear_capture_end); - } - // Now that we have unwound the stack we find at the top of the stack the - // backtrack that the BeginSubmatch node got. - assembler->Backtrack(); -} - -void EndNode::Emit(RegExpCompiler* compiler, Trace* trace) { - if (!trace->is_trivial()) { - trace->Flush(compiler, this); - return; - } - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - if (!label()->is_bound()) { - assembler->BindBlock(label()); - } - switch (action_) { - case ACCEPT: - assembler->Succeed(); - return; - case BACKTRACK: - assembler->GoTo(trace->backtrack()); - return; - case NEGATIVE_SUBMATCH_SUCCESS: - // This case is handled in a different virtual method. - UNREACHABLE(); - } - UNIMPLEMENTED(); -} - -void GuardedAlternative::AddGuard(Guard* guard, Zone* zone) { - if (guards_ == nullptr) guards_ = new (zone) ZoneGrowableArray(1); - guards_->Add(guard); -} - -ActionNode* ActionNode::SetRegister(intptr_t reg, - intptr_t val, - RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(SET_REGISTER, on_success); - result->data_.u_store_register.reg = reg; - result->data_.u_store_register.value = val; - return result; -} - -ActionNode* ActionNode::IncrementRegister(intptr_t reg, - RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(INCREMENT_REGISTER, on_success); - result->data_.u_increment_register.reg = reg; - return result; -} - -ActionNode* ActionNode::StorePosition(intptr_t reg, - bool is_capture, - RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(STORE_POSITION, on_success); - result->data_.u_position_register.reg = reg; - result->data_.u_position_register.is_capture = is_capture; - return result; -} - -ActionNode* ActionNode::ClearCaptures(Interval range, RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(CLEAR_CAPTURES, on_success); - result->data_.u_clear_captures.range_from = range.from(); - result->data_.u_clear_captures.range_to = range.to(); - return result; -} - -ActionNode* ActionNode::BeginSubmatch(intptr_t stack_reg, - intptr_t position_reg, - RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(BEGIN_SUBMATCH, on_success); - result->data_.u_submatch.stack_pointer_register = stack_reg; - result->data_.u_submatch.current_position_register = position_reg; - return result; -} - -ActionNode* ActionNode::PositiveSubmatchSuccess(intptr_t stack_reg, - intptr_t position_reg, - intptr_t clear_register_count, - intptr_t clear_register_from, - RegExpNode* on_success) { - ActionNode* result = new (on_success->zone()) - ActionNode(POSITIVE_SUBMATCH_SUCCESS, on_success); - result->data_.u_submatch.stack_pointer_register = stack_reg; - result->data_.u_submatch.current_position_register = position_reg; - result->data_.u_submatch.clear_register_count = clear_register_count; - result->data_.u_submatch.clear_register_from = clear_register_from; - return result; -} - -ActionNode* ActionNode::EmptyMatchCheck(intptr_t start_register, - intptr_t repetition_register, - intptr_t repetition_limit, - RegExpNode* on_success) { - ActionNode* result = - new (on_success->zone()) ActionNode(EMPTY_MATCH_CHECK, on_success); - result->data_.u_empty_match_check.start_register = start_register; - result->data_.u_empty_match_check.repetition_register = repetition_register; - result->data_.u_empty_match_check.repetition_limit = repetition_limit; - return result; -} - -#define DEFINE_ACCEPT(Type) \ - void Type##Node::Accept(NodeVisitor* visitor) { \ - visitor->Visit##Type(this); \ - } -FOR_EACH_NODE_TYPE(DEFINE_ACCEPT) -#undef DEFINE_ACCEPT - -void LoopChoiceNode::Accept(NodeVisitor* visitor) { - visitor->VisitLoopChoice(this); -} - -// ------------------------------------------------------------------- -// Emit code. - -void ChoiceNode::GenerateGuard(RegExpMacroAssembler* macro_assembler, - Guard* guard, - Trace* trace) { - switch (guard->op()) { - case Guard::LT: - ASSERT(!trace->mentions_reg(guard->reg())); - macro_assembler->IfRegisterGE(guard->reg(), guard->value(), - trace->backtrack()); - break; - case Guard::GEQ: - ASSERT(!trace->mentions_reg(guard->reg())); - macro_assembler->IfRegisterLT(guard->reg(), guard->value(), - trace->backtrack()); - break; - } -} - -// Returns the number of characters in the equivalence class, omitting those -// that cannot occur in the source string because it is ASCII. -static intptr_t GetCaseIndependentLetters(uint16_t character, - bool one_byte_subject, - int32_t* letters) { - unibrow::Mapping jsregexp_uncanonicalize; - intptr_t length = jsregexp_uncanonicalize.get(character, '\0', letters); - // Unibrow returns 0 or 1 for characters where case independence is - // trivial. - if (length == 0) { - letters[0] = character; - length = 1; - } - if (!one_byte_subject || character <= Symbols::kMaxOneCharCodeSymbol) { - return length; - } - - // The standard requires that non-ASCII characters cannot have ASCII - // character codes in their equivalence class. - // TODO(dcarney): issue 3550 this is not actually true for Latin1 anymore, - // is it? For example, \u00C5 is equivalent to \u212B. - return 0; -} - -static inline bool EmitSimpleCharacter(Zone* zone, - RegExpCompiler* compiler, - uint16_t c, - BlockLabel* on_failure, - intptr_t cp_offset, - bool check, - bool preloaded) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - bool bound_checked = false; - if (!preloaded) { - assembler->LoadCurrentCharacter(cp_offset, on_failure, check); - bound_checked = true; - } - assembler->CheckNotCharacter(c, on_failure); - return bound_checked; -} - -// Only emits non-letters (things that don't have case). Only used for case -// independent matches. -static inline bool EmitAtomNonLetter(Zone* zone, - RegExpCompiler* compiler, - uint16_t c, - BlockLabel* on_failure, - intptr_t cp_offset, - bool check, - bool preloaded) { - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - bool one_byte = compiler->one_byte(); - int32_t chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - intptr_t length = GetCaseIndependentLetters(c, one_byte, chars); - if (length < 1) { - // This can't match. Must be an one-byte subject and a non-one-byte - // character. We do not need to do anything since the one-byte pass - // already handled this. - return false; // Bounds not checked. - } - bool checked = false; - // We handle the length > 1 case in a later pass. - if (length == 1) { - if (one_byte && c > Symbols::kMaxOneCharCodeSymbol) { - // Can't match - see above. - return false; // Bounds not checked. - } - if (!preloaded) { - macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); - checked = check; - } - macro_assembler->CheckNotCharacter(c, on_failure); - } - return checked; -} - -static bool ShortCutEmitCharacterPair(RegExpMacroAssembler* macro_assembler, - bool one_byte, - uint16_t c1, - uint16_t c2, - BlockLabel* on_failure) { - uint16_t char_mask; - if (one_byte) { - char_mask = Symbols::kMaxOneCharCodeSymbol; - } else { - char_mask = Utf16::kMaxCodeUnit; - } - uint16_t exor = c1 ^ c2; - // Check whether exor has only one bit set. - if (((exor - 1) & exor) == 0) { - // If c1 and c2 differ only by one bit. - // Ecma262UnCanonicalize always gives the highest number last. - ASSERT(c2 > c1); - uint16_t mask = char_mask ^ exor; - macro_assembler->CheckNotCharacterAfterAnd(c1, mask, on_failure); - return true; - } - ASSERT(c2 > c1); - uint16_t diff = c2 - c1; - if (((diff - 1) & diff) == 0 && c1 >= diff) { - // If the characters differ by 2^n but don't differ by one bit then - // subtract the difference from the found character, then do the or - // trick. We avoid the theoretical case where negative numbers are - // involved in order to simplify code generation. - uint16_t mask = char_mask ^ diff; - macro_assembler->CheckNotCharacterAfterMinusAnd(c1 - diff, diff, mask, - on_failure); - return true; - } - return false; -} - -typedef bool EmitCharacterFunction(Zone* zone, - RegExpCompiler* compiler, - uint16_t c, - BlockLabel* on_failure, - intptr_t cp_offset, - bool check, - bool preloaded); - -// Only emits letters (things that have case). Only used for case independent -// matches. -static inline bool EmitAtomLetter(Zone* zone, - RegExpCompiler* compiler, - uint16_t c, - BlockLabel* on_failure, - intptr_t cp_offset, - bool check, - bool preloaded) { - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - bool one_byte = compiler->one_byte(); - int32_t chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - intptr_t length = GetCaseIndependentLetters(c, one_byte, chars); - if (length <= 1) return false; - // We may not need to check against the end of the input string - // if this character lies before a character that matched. - if (!preloaded) { - macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); - } - BlockLabel ok; - ASSERT(unibrow::Ecma262UnCanonicalize::kMaxWidth == 4); - switch (length) { - case 2: { - if (ShortCutEmitCharacterPair(macro_assembler, one_byte, chars[0], - chars[1], on_failure)) { - } else { - macro_assembler->CheckCharacter(chars[0], &ok); - macro_assembler->CheckNotCharacter(chars[1], on_failure); - macro_assembler->BindBlock(&ok); - } - break; - } - case 4: - macro_assembler->CheckCharacter(chars[3], &ok); - FALL_THROUGH; - case 3: - macro_assembler->CheckCharacter(chars[0], &ok); - macro_assembler->CheckCharacter(chars[1], &ok); - macro_assembler->CheckNotCharacter(chars[2], on_failure); - macro_assembler->BindBlock(&ok); - break; - default: - UNREACHABLE(); - break; - } +// static +bool RegExpStatics::CanGenerateBytecode() { return true; } -static void EmitBoundaryTest(RegExpMacroAssembler* masm, - uint16_t border, - BlockLabel* fall_through, - BlockLabel* above_or_equal, - BlockLabel* below) { - if (below != fall_through) { - masm->CheckCharacterLT(border, below); - if (above_or_equal != fall_through) masm->GoTo(above_or_equal); - } else { - masm->CheckCharacterGT(border - 1, above_or_equal); - } -} - -static void EmitDoubleBoundaryTest(RegExpMacroAssembler* masm, - uint16_t first, - uint16_t last, - BlockLabel* fall_through, - BlockLabel* in_range, - BlockLabel* out_of_range) { - if (in_range == fall_through) { - if (first == last) { - masm->CheckNotCharacter(first, out_of_range); - } else { - masm->CheckCharacterNotInRange(first, last, out_of_range); - } - } else { - if (first == last) { - masm->CheckCharacter(first, in_range); - } else { - masm->CheckCharacterInRange(first, last, in_range); - } - if (out_of_range != fall_through) masm->GoTo(out_of_range); - } -} - -// even_label is for ranges[i] to ranges[i + 1] where i - start_index is even. -// odd_label is for ranges[i] to ranges[i + 1] where i - start_index is odd. -static void EmitUseLookupTable(RegExpMacroAssembler* masm, - ZoneGrowableArray* ranges, - intptr_t start_index, - intptr_t end_index, - uint16_t min_char, - BlockLabel* fall_through, - BlockLabel* even_label, - BlockLabel* odd_label) { - const intptr_t kSize = RegExpMacroAssembler::kTableSize; - const intptr_t kMask = RegExpMacroAssembler::kTableMask; - - intptr_t base = (min_char & ~kMask); - - // Assert that everything is on one kTableSize page. - for (intptr_t i = start_index; i <= end_index; i++) { - ASSERT((ranges->At(i) & ~kMask) == base); - } - ASSERT(start_index == 0 || (ranges->At(start_index - 1) & ~kMask) <= base); - - char templ[kSize]; - BlockLabel* on_bit_set; - BlockLabel* on_bit_clear; - intptr_t bit; - if (even_label == fall_through) { - on_bit_set = odd_label; - on_bit_clear = even_label; - bit = 1; - } else { - on_bit_set = even_label; - on_bit_clear = odd_label; - bit = 0; - } - for (intptr_t i = 0; i < (ranges->At(start_index) & kMask) && i < kSize; - i++) { - templ[i] = bit; - } - intptr_t j = 0; - bit ^= 1; - for (intptr_t i = start_index; i < end_index; i++) { - for (j = (ranges->At(i) & kMask); j < (ranges->At(i + 1) & kMask); j++) { - templ[j] = bit; - } - bit ^= 1; - } - for (intptr_t i = j; i < kSize; i++) { - templ[i] = bit; - } - // TODO(erikcorry): Cache these. - const TypedData& ba = TypedData::ZoneHandle( - masm->zone(), TypedData::New(kTypedDataUint8ArrayCid, kSize, Heap::kOld)); - for (intptr_t i = 0; i < kSize; i++) { - ba.SetUint8(i, templ[i]); - } - masm->CheckBitInTable(ba, on_bit_set); - if (on_bit_clear != fall_through) masm->GoTo(on_bit_clear); -} - -static void CutOutRange(RegExpMacroAssembler* masm, - ZoneGrowableArray* ranges, - intptr_t start_index, - intptr_t end_index, - intptr_t cut_index, - BlockLabel* even_label, - BlockLabel* odd_label) { - bool odd = (((cut_index - start_index) & 1) == 1); - BlockLabel* in_range_label = odd ? odd_label : even_label; - BlockLabel dummy; - EmitDoubleBoundaryTest(masm, ranges->At(cut_index), - ranges->At(cut_index + 1) - 1, &dummy, in_range_label, - &dummy); - ASSERT(!dummy.is_linked()); - // Cut out the single range by rewriting the array. This creates a new - // range that is a merger of the two ranges on either side of the one we - // are cutting out. The oddity of the labels is preserved. - for (intptr_t j = cut_index; j > start_index; j--) { - (*ranges)[j] = ranges->At(j - 1); - } - for (intptr_t j = cut_index + 1; j < end_index; j++) { - (*ranges)[j] = ranges->At(j + 1); - } -} - -// Unicode case. Split the search space into kSize spaces that are handled -// with recursion. -static void SplitSearchSpace(ZoneGrowableArray* ranges, - intptr_t start_index, - intptr_t end_index, - intptr_t* new_start_index, - intptr_t* new_end_index, - uint16_t* border) { - const intptr_t kSize = RegExpMacroAssembler::kTableSize; - const intptr_t kMask = RegExpMacroAssembler::kTableMask; - - uint16_t first = ranges->At(start_index); - uint16_t last = ranges->At(end_index) - 1; - - *new_start_index = start_index; - *border = (ranges->At(start_index) & ~kMask) + kSize; - while (*new_start_index < end_index) { - if (ranges->At(*new_start_index) > *border) break; - (*new_start_index)++; - } - // new_start_index is the index of the first edge that is beyond the - // current kSize space. - - // For very large search spaces we do a binary chop search of the non-Latin1 - // space instead of just going to the end of the current kSize space. The - // heuristics are complicated a little by the fact that any 128-character - // encoding space can be quickly tested with a table lookup, so we don't - // wish to do binary chop search at a smaller granularity than that. A - // 128-character space can take up a lot of space in the ranges array if, - // for example, we only want to match every second character (eg. the lower - // case characters on some Unicode pages). - intptr_t binary_chop_index = (end_index + start_index) / 2; - // The first test ensures that we get to the code that handles the Latin1 - // range with a single not-taken branch, speeding up this important - // character range (even non-Latin1 charset-based text has spaces and - // punctuation). - if (*border - 1 > Symbols::kMaxOneCharCodeSymbol && // Latin1 case. - end_index - start_index > (*new_start_index - start_index) * 2 && - last - first > kSize * 2 && binary_chop_index > *new_start_index && - ranges->At(binary_chop_index) >= first + 2 * kSize) { - intptr_t scan_forward_for_section_border = binary_chop_index; - intptr_t new_border = (ranges->At(binary_chop_index) | kMask) + 1; - - while (scan_forward_for_section_border < end_index) { - if (ranges->At(scan_forward_for_section_border) > new_border) { - *new_start_index = scan_forward_for_section_border; - *border = new_border; - break; - } - scan_forward_for_section_border++; - } - } - - ASSERT(*new_start_index > start_index); - *new_end_index = *new_start_index - 1; - if (ranges->At(*new_end_index) == *border) { - (*new_end_index)--; - } - if (*border >= ranges->At(end_index)) { - *border = ranges->At(end_index); - *new_start_index = end_index; // Won't be used. - *new_end_index = end_index - 1; - } -} - -// Gets a series of segment boundaries representing a character class. If the -// character is in the range between an even and an odd boundary (counting from -// start_index) then go to even_label, otherwise go to odd_label. We already -// know that the character is in the range of min_char to max_char inclusive. -// Either label can be null indicating backtracking. Either label can also be -// equal to the fall_through label. -static void GenerateBranches(RegExpMacroAssembler* masm, - ZoneGrowableArray* ranges, - intptr_t start_index, - intptr_t end_index, - uint16_t min_char, - uint16_t max_char, - BlockLabel* fall_through, - BlockLabel* even_label, - BlockLabel* odd_label) { - uint16_t first = ranges->At(start_index); - uint16_t last = ranges->At(end_index) - 1; - - ASSERT(min_char < first); - - // Just need to test if the character is before or on-or-after - // a particular character. - if (start_index == end_index) { - EmitBoundaryTest(masm, first, fall_through, even_label, odd_label); - return; - } - - // Another almost trivial case: There is one interval in the middle that is - // different from the end intervals. - if (start_index + 1 == end_index) { - EmitDoubleBoundaryTest(masm, first, last, fall_through, even_label, - odd_label); - return; - } - - // It's not worth using table lookup if there are very few intervals in the - // character class. - if (end_index - start_index <= 6) { - // It is faster to test for individual characters, so we look for those - // first, then try arbitrary ranges in the second round. - static intptr_t kNoCutIndex = -1; - intptr_t cut = kNoCutIndex; - for (intptr_t i = start_index; i < end_index; i++) { - if (ranges->At(i) == ranges->At(i + 1) - 1) { - cut = i; - break; - } - } - if (cut == kNoCutIndex) cut = start_index; - CutOutRange(masm, ranges, start_index, end_index, cut, even_label, - odd_label); - ASSERT(end_index - start_index >= 2); - GenerateBranches(masm, ranges, start_index + 1, end_index - 1, min_char, - max_char, fall_through, even_label, odd_label); - return; - } - - // If there are a lot of intervals in the regexp, then we will use tables to - // determine whether the character is inside or outside the character class. - const intptr_t kBits = RegExpMacroAssembler::kTableSizeBits; - - if ((max_char >> kBits) == (min_char >> kBits)) { - EmitUseLookupTable(masm, ranges, start_index, end_index, min_char, - fall_through, even_label, odd_label); - return; - } - - if ((min_char >> kBits) != (first >> kBits)) { - masm->CheckCharacterLT(first, odd_label); - GenerateBranches(masm, ranges, start_index + 1, end_index, first, max_char, - fall_through, odd_label, even_label); - return; - } - - intptr_t new_start_index = 0; - intptr_t new_end_index = 0; - uint16_t border = 0; - - SplitSearchSpace(ranges, start_index, end_index, &new_start_index, - &new_end_index, &border); - - BlockLabel handle_rest; - BlockLabel* above = &handle_rest; - if (border == last + 1) { - // We didn't find any section that started after the limit, so everything - // above the border is one of the terminal labels. - above = (end_index & 1) != (start_index & 1) ? odd_label : even_label; - ASSERT(new_end_index == end_index - 1); - } - - ASSERT(start_index <= new_end_index); - ASSERT(new_start_index <= end_index); - ASSERT(start_index < new_start_index); - ASSERT(new_end_index < end_index); - ASSERT(new_end_index + 1 == new_start_index || - (new_end_index + 2 == new_start_index && - border == ranges->At(new_end_index + 1))); - ASSERT(min_char < border - 1); - ASSERT(border < max_char); - ASSERT(ranges->At(new_end_index) < border); - ASSERT(border < ranges->At(new_start_index) || - (border == ranges->At(new_start_index) && - new_start_index == end_index && new_end_index == end_index - 1 && - border == last + 1)); - ASSERT(new_start_index == 0 || border >= ranges->At(new_start_index - 1)); - - masm->CheckCharacterGT(border - 1, above); - BlockLabel dummy; - GenerateBranches(masm, ranges, start_index, new_end_index, min_char, - border - 1, &dummy, even_label, odd_label); - - if (handle_rest.is_linked()) { - masm->BindBlock(&handle_rest); - bool flip = (new_start_index & 1) != (start_index & 1); - GenerateBranches(masm, ranges, new_start_index, end_index, border, max_char, - &dummy, flip ? odd_label : even_label, - flip ? even_label : odd_label); - } -} - -static void EmitCharClass(RegExpMacroAssembler* macro_assembler, - RegExpCharacterClass* cc, - bool one_byte, - BlockLabel* on_failure, - intptr_t cp_offset, - bool check_offset, - bool preloaded, - Zone* zone) { - ZoneGrowableArray* ranges = cc->ranges(); - if (!CharacterRange::IsCanonical(ranges)) { - CharacterRange::Canonicalize(ranges); - } - - uint16_t max_char; - if (one_byte) { - max_char = Symbols::kMaxOneCharCodeSymbol; - } else { - max_char = Utf16::kMaxCodeUnit; - } - - intptr_t range_count = ranges->length(); - - intptr_t last_valid_range = range_count - 1; - while (last_valid_range >= 0) { - const CharacterRange& range = ranges->At(last_valid_range); - if (range.from() <= max_char) { - break; - } - last_valid_range--; - } - - if (last_valid_range < 0) { - if (!cc->is_negated()) { - macro_assembler->GoTo(on_failure); - } - if (check_offset) { - macro_assembler->CheckPosition(cp_offset, on_failure); - } - return; - } - - if (last_valid_range == 0 && ranges->At(0).IsEverything(max_char)) { - if (cc->is_negated()) { - macro_assembler->GoTo(on_failure); - } else { - // This is a common case hit by non-anchored expressions. - if (check_offset) { - macro_assembler->CheckPosition(cp_offset, on_failure); - } - } - return; - } - - if (!preloaded) { - macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check_offset); - } - - if (cc->is_standard() && macro_assembler->CheckSpecialCharacterClass( - cc->standard_type(), on_failure)) { - return; - } - - // A new list with ascending entries. Each entry is a code unit - // where there is a boundary between code units that are part of - // the class and code units that are not. Normally we insert an - // entry at zero which goes to the failure label, but if there - // was already one there we fall through for success on that entry. - // Subsequent entries have alternating meaning (success/failure). - ZoneGrowableArray* range_boundaries = - new (zone) ZoneGrowableArray(last_valid_range); - - bool zeroth_entry_is_failure = !cc->is_negated(); - - for (intptr_t i = 0; i <= last_valid_range; i++) { - const CharacterRange& range = ranges->At(i); - if (range.from() == 0) { - ASSERT(i == 0); - zeroth_entry_is_failure = !zeroth_entry_is_failure; - } else { - range_boundaries->Add(range.from()); - } - if (range.to() + 1 <= max_char) { - range_boundaries->Add(range.to() + 1); - } - } - intptr_t end_index = range_boundaries->length() - 1; - - BlockLabel fall_through; - GenerateBranches(macro_assembler, range_boundaries, - 0, // start_index. - end_index, - 0, // min_char. - max_char, &fall_through, - zeroth_entry_is_failure ? &fall_through : on_failure, - zeroth_entry_is_failure ? on_failure : &fall_through); - macro_assembler->BindBlock(&fall_through); -} - -RegExpNode::~RegExpNode() {} - -RegExpNode::LimitResult RegExpNode::LimitVersions(RegExpCompiler* compiler, - Trace* trace) { - // If we are generating a greedy loop then don't stop and don't reuse code. - if (trace->stop_node() != nullptr) { - return CONTINUE; - } - - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - if (trace->is_trivial()) { - if (label_.is_bound()) { - // We are being asked to generate a generic version, but that's already - // been done so just go to it. - macro_assembler->GoTo(&label_); - return DONE; - } - if (compiler->recursion_depth() >= RegExpCompiler::kMaxRecursion) { - // To avoid too deep recursion we push the node to the work queue and just - // generate a goto here. - compiler->AddWork(this); - macro_assembler->GoTo(&label_); - return DONE; - } - // Generate generic version of the node and bind the label for later use. - macro_assembler->BindBlock(&label_); - return CONTINUE; - } - - // We are being asked to make a non-generic version. Keep track of how many - // non-generic versions we generate so as not to overdo it. - trace_count_++; - if (kRegexpOptimization && trace_count_ < kMaxCopiesCodeGenerated && - compiler->recursion_depth() <= RegExpCompiler::kMaxRecursion) { - return CONTINUE; - } - - // If we get here code has been generated for this node too many times or - // recursion is too deep. Time to switch to a generic version. The code for - // generic versions above can handle deep recursion properly. - trace->Flush(compiler, this); - return DONE; -} - -intptr_t ActionNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - if (budget <= 0) return 0; - if (action_type_ == POSITIVE_SUBMATCH_SUCCESS) return 0; // Rewinds input! - return on_success()->EatsAtLeast(still_to_find, budget - 1, not_at_start); -} - -void ActionNode::FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - if (action_type_ == BEGIN_SUBMATCH) { - bm->SetRest(offset); - } else if (action_type_ != POSITIVE_SUBMATCH_SUCCESS) { - on_success()->FillInBMInfo(offset, budget - 1, bm, not_at_start); - } - SaveBMInfo(bm, not_at_start, offset); -} - -intptr_t AssertionNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - if (budget <= 0) return 0; - // If we know we are not at the start and we are asked "how many characters - // will you match if you succeed?" then we can answer anything since false - // implies false. So lets just return the max answer (still_to_find) since - // that won't prevent us from preloading a lot of characters for the other - // branches in the node graph. - if (assertion_type() == AT_START && not_at_start) return still_to_find; - return on_success()->EatsAtLeast(still_to_find, budget - 1, not_at_start); -} - -void AssertionNode::FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - // Match the behaviour of EatsAtLeast on this node. - if (assertion_type() == AT_START && not_at_start) return; - on_success()->FillInBMInfo(offset, budget - 1, bm, not_at_start); - SaveBMInfo(bm, not_at_start, offset); -} - -intptr_t BackReferenceNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - if (read_backward()) return 0; - if (budget <= 0) return 0; - return on_success()->EatsAtLeast(still_to_find, budget - 1, not_at_start); -} - -intptr_t TextNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - if (read_backward()) return 0; - intptr_t answer = Length(); - if (answer >= still_to_find) return answer; - if (budget <= 0) return answer; - // We are not at start after this node so we set the last argument to 'true'. - return answer + - on_success()->EatsAtLeast(still_to_find - answer, budget - 1, true); -} - -intptr_t NegativeLookaroundChoiceNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - if (budget <= 0) return 0; - // Alternative 0 is the negative lookahead, alternative 1 is what comes - // afterwards. - RegExpNode* node = (*alternatives_)[1].node(); - return node->EatsAtLeast(still_to_find, budget - 1, not_at_start); -} - -void NegativeLookaroundChoiceNode::GetQuickCheckDetails( - QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t filled_in, - bool not_at_start) { - // Alternative 0 is the negative lookahead, alternative 1 is what comes - // afterwards. - RegExpNode* node = (*alternatives_)[1].node(); - return node->GetQuickCheckDetails(details, compiler, filled_in, not_at_start); -} - -intptr_t ChoiceNode::EatsAtLeastHelper(intptr_t still_to_find, - intptr_t budget, - RegExpNode* ignore_this_node, - bool not_at_start) { - if (budget <= 0) return 0; - intptr_t min = 100; - intptr_t choice_count = alternatives_->length(); - budget = (budget - 1) / choice_count; - for (intptr_t i = 0; i < choice_count; i++) { - RegExpNode* node = (*alternatives_)[i].node(); - if (node == ignore_this_node) continue; - intptr_t node_eats_at_least = - node->EatsAtLeast(still_to_find, budget, not_at_start); - if (node_eats_at_least < min) min = node_eats_at_least; - if (min == 0) return 0; - } - return min; -} - -intptr_t LoopChoiceNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - return EatsAtLeastHelper(still_to_find, budget - 1, loop_node_, not_at_start); -} - -intptr_t ChoiceNode::EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) { - return EatsAtLeastHelper(still_to_find, budget, nullptr, not_at_start); -} - -// Takes the left-most 1-bit and smears it out, setting all bits to its right. -static inline uint32_t SmearBitsRight(uint32_t v) { - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return v; -} - -bool QuickCheckDetails::Rationalize(bool asc) { - bool found_useful_op = false; - uint32_t char_mask; - if (asc) { - char_mask = Symbols::kMaxOneCharCodeSymbol; - } else { - char_mask = Utf16::kMaxCodeUnit; - } - mask_ = 0; - value_ = 0; - intptr_t char_shift = 0; - for (intptr_t i = 0; i < characters_; i++) { - Position* pos = &positions_[i]; - if ((pos->mask & Symbols::kMaxOneCharCodeSymbol) != 0) { - found_useful_op = true; - } - mask_ |= (pos->mask & char_mask) << char_shift; - value_ |= (pos->value & char_mask) << char_shift; - char_shift += asc ? 8 : 16; - } - return found_useful_op; -} - -bool RegExpNode::EmitQuickCheck(RegExpCompiler* compiler, - Trace* bounds_check_trace, - Trace* trace, - bool preload_has_checked_bounds, - BlockLabel* on_possible_success, - QuickCheckDetails* details, - bool fall_through_on_failure) { - if (details->characters() == 0) return false; - GetQuickCheckDetails(details, compiler, 0, - trace->at_start() == Trace::FALSE_VALUE); - if (details->cannot_match()) return false; - if (!details->Rationalize(compiler->one_byte())) return false; - ASSERT(details->characters() == 1 || - compiler->macro_assembler()->CanReadUnaligned()); - uint32_t mask = details->mask(); - uint32_t value = details->value(); - - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - - if (trace->characters_preloaded() != details->characters()) { - ASSERT(trace->cp_offset() == bounds_check_trace->cp_offset()); - // We are attempting to preload the minimum number of characters - // any choice would eat, so if the bounds check fails, then none of the - // choices can succeed, so we can just immediately backtrack, rather - // than go to the next choice. - assembler->LoadCurrentCharacter( - trace->cp_offset(), bounds_check_trace->backtrack(), - !preload_has_checked_bounds, details->characters()); - } - - bool need_mask = true; - - if (details->characters() == 1) { - // If number of characters preloaded is 1 then we used a byte or 16 bit - // load so the value is already masked down. - uint32_t char_mask; - if (compiler->one_byte()) { - char_mask = Symbols::kMaxOneCharCodeSymbol; - } else { - char_mask = Utf16::kMaxCodeUnit; - } - if ((mask & char_mask) == char_mask) need_mask = false; - mask &= char_mask; - } else { - // For 2-character preloads in one-byte mode or 1-character preloads in - // two-byte mode we also use a 16 bit load with zero extend. - if (details->characters() == 2 && compiler->one_byte()) { - if ((mask & 0xffff) == 0xffff) need_mask = false; - } else if (details->characters() == 1 && !compiler->one_byte()) { - if ((mask & 0xffff) == 0xffff) need_mask = false; - } else { - if (mask == 0xffffffff) need_mask = false; - } - } - - if (fall_through_on_failure) { - if (need_mask) { - assembler->CheckCharacterAfterAnd(value, mask, on_possible_success); - } else { - assembler->CheckCharacter(value, on_possible_success); - } - } else { - if (need_mask) { - assembler->CheckNotCharacterAfterAnd(value, mask, trace->backtrack()); - } else { - assembler->CheckNotCharacter(value, trace->backtrack()); - } - } +// static +bool RegExpStatics::VerifyFlags(RegExpFlags flags) { + if (IsUnicode(flags) && IsUnicodeSets(flags)) return false; return true; } -// Here is the meat of GetQuickCheckDetails (see also the comment on the -// super-class in the .h file). -// -// We iterate along the text object, building up for each character a -// mask and value that can be used to test for a quick failure to match. -// The masks and values for the positions will be combined into a single -// machine word for the current character width in order to be used in -// generating a quick check. -void TextNode::GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) { -#if defined(__GNUC__) && defined(__BYTE_ORDER__) - // TODO(zerny): Make the combination code byte-order independent. - ASSERT(details->characters() == 1 || - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)); -#endif - // Do not collect any quick check details if the text node reads backward, - // since it reads in the opposite direction than we use for quick checks. - if (read_backward()) return; - ASSERT(characters_filled_in < details->characters()); - intptr_t characters = details->characters(); - int32_t char_mask; - if (compiler->one_byte()) { - char_mask = Symbols::kMaxOneCharCodeSymbol; - } else { - char_mask = Utf16::kMaxCodeUnit; - } - for (intptr_t k = 0; k < elms_->length(); k++) { - TextElement elm = elms_->At(k); - if (elm.text_type() == TextElement::ATOM) { - ZoneGrowableArray* quarks = elm.atom()->data(); - for (intptr_t i = 0; i < characters && i < quarks->length(); i++) { - QuickCheckDetails::Position* pos = - details->positions(characters_filled_in); - uint16_t c = quarks->At(i); - if (c > char_mask) { - // If we expect a non-Latin1 character from an one-byte string, - // there is no way we can match. Not even case independent - // matching can turn an Latin1 character into non-Latin1 or - // vice versa. - // TODO(dcarney): issue 3550. Verify that this works as expected. - // For example, \u0178 is uppercase of \u00ff (y-umlaut). - details->set_cannot_match(); - pos->determines_perfectly = false; - return; - } - if (elm.atom()->ignore_case()) { - int32_t chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - intptr_t length = - GetCaseIndependentLetters(c, compiler->one_byte(), chars); - ASSERT(length != 0); // Can only happen if c > char_mask (see above). - if (length == 1) { - // This letter has no case equivalents, so it's nice and simple - // and the mask-compare will determine definitely whether we have - // a match at this character position. - pos->mask = char_mask; - pos->value = c; - pos->determines_perfectly = true; - } else { - uint32_t common_bits = char_mask; - uint32_t bits = chars[0]; - for (intptr_t j = 1; j < length; j++) { - uint32_t differing_bits = ((chars[j] & common_bits) ^ bits); - common_bits ^= differing_bits; - bits &= common_bits; - } - // If length is 2 and common bits has only one zero in it then - // our mask and compare instruction will determine definitely - // whether we have a match at this character position. Otherwise - // it can only be an approximate check. - uint32_t one_zero = (common_bits | ~char_mask); - if (length == 2 && ((~one_zero) & ((~one_zero) - 1)) == 0) { - pos->determines_perfectly = true; - } - pos->mask = common_bits; - pos->value = bits; - } - } else { - // Don't ignore case. Nice simple case where the mask-compare will - // determine definitely whether we have a match at this character - // position. - pos->mask = char_mask; - pos->value = c; - pos->determines_perfectly = true; - } - characters_filled_in++; - ASSERT(characters_filled_in <= details->characters()); - if (characters_filled_in == details->characters()) { - return; - } - } - } else { - QuickCheckDetails::Position* pos = - details->positions(characters_filled_in); - RegExpCharacterClass* tree = elm.char_class(); - ZoneGrowableArray* ranges = tree->ranges(); - ASSERT(!ranges->is_empty()); - if (!CharacterRange::IsCanonical(ranges)) { - CharacterRange::Canonicalize(ranges); - } - if (tree->is_negated()) { - // A quick check uses multi-character mask and compare. There is no - // useful way to incorporate a negative char class into this scheme - // so we just conservatively create a mask and value that will always - // succeed. - pos->mask = 0; - pos->value = 0; - } else { - intptr_t first_range = 0; - while (ranges->At(first_range).from() > char_mask) { - first_range++; - if (first_range == ranges->length()) { - details->set_cannot_match(); - pos->determines_perfectly = false; - return; - } - } - CharacterRange range = ranges->At(first_range); - uint16_t from = range.from(); - uint16_t to = range.to(); - if (to > char_mask) { - to = char_mask; - } - uint32_t differing_bits = (from ^ to); - // A mask and compare is only perfect if the differing bits form a - // number like 00011111 with one single block of trailing 1s. - if ((differing_bits & (differing_bits + 1)) == 0 && - from + differing_bits == to) { - pos->determines_perfectly = true; - } - uint32_t common_bits = ~SmearBitsRight(differing_bits); - uint32_t bits = (from & common_bits); - for (intptr_t i = first_range + 1; i < ranges->length(); i++) { - CharacterRange range = ranges->At(i); - uint16_t from = range.from(); - uint16_t to = range.to(); - if (from > char_mask) continue; - if (to > char_mask) to = char_mask; - // Here we are combining more ranges into the mask and compare - // value. With each new range the mask becomes more sparse and - // so the chances of a false positive rise. A character class - // with multiple ranges is assumed never to be equivalent to a - // mask and compare operation. - pos->determines_perfectly = false; - uint32_t new_common_bits = (from ^ to); - new_common_bits = ~SmearBitsRight(new_common_bits); - common_bits &= new_common_bits; - bits &= new_common_bits; - uint32_t differing_bits = (from & common_bits) ^ bits; - common_bits ^= differing_bits; - bits &= common_bits; - } - pos->mask = common_bits; - pos->value = bits; - } - characters_filled_in++; - ASSERT(characters_filled_in <= details->characters()); - if (characters_filled_in == details->characters()) { - return; - } - } - } - ASSERT(characters_filled_in != details->characters()); - if (!details->cannot_match()) { - on_success()->GetQuickCheckDetails(details, compiler, characters_filled_in, - true); - } +// static +template +bool RegExpStatics::VerifySyntax(Zone* zone, + uintptr_t stack_limit, + const CharT* input, + int input_length, + RegExpFlags flags, + RegExpError* regexp_error_out) { + RegExpCompileData data; + bool pattern_is_valid = RegExpParser::VerifyRegExpSyntax( + zone, stack_limit, input, input_length, flags, &data); + *regexp_error_out = data.error; + return pattern_is_valid; } -void QuickCheckDetails::Clear() { - for (int i = 0; i < characters_; i++) { - positions_[i].mask = 0; - positions_[i].value = 0; - positions_[i].determines_perfectly = false; - } - characters_ = 0; +template bool RegExpStatics::VerifySyntax( + Zone*, + uintptr_t, + const uint8_t*, + int, + RegExpFlags, + RegExpError* regexp_error_out); +template bool RegExpStatics::VerifySyntax( + Zone*, + uintptr_t, + const uint16_t*, + int, + RegExpFlags, + RegExpError* regexp_error_out); + +ObjectPtr RegExpStatics::ThrowRegExpException(Isolate* isolate, + RegExpFlags flags, + const String& pattern, + RegExpError error) { + Array& args = Array::Handle(); + String& str = String::Handle(); + str ^= String::New(RegExpErrorString(error)); + args ^= Array::New(2); + args.SetAt(0, str); + args.SetAt(1, pattern); + // TODO(regexp) args.SetAt(2, position) sometimes available but not used by V8 + Exceptions::ThrowByType(Exceptions::kFormat, args); } -void QuickCheckDetails::Advance(intptr_t by, bool one_byte) { - if (by >= characters_ || by < 0) { - // check that by < 0 => characters_ == 0 - ASSERT(by >= 0 || characters_ == 0); - Clear(); - return; - } - for (intptr_t i = 0; i < characters_ - by; i++) { - positions_[i] = positions_[by + i]; - } - for (intptr_t i = characters_ - by; i < characters_; i++) { - positions_[i].mask = 0; - positions_[i].value = 0; - positions_[i].determines_perfectly = false; - } - characters_ -= by; - // We could change mask_ and value_ here but we would never advance unless - // they had already been used in a check and they won't be used again because - // it would gain us nothing. So there's no point. +void RegExpStatics::ThrowRegExpException(Isolate* isolate, + const RegExp& re_data, + RegExpError error_text) { + USE(ThrowRegExpException(isolate, re_data.flags(), + String::Handle(re_data.pattern()), error_text)); } -void QuickCheckDetails::Merge(QuickCheckDetails* other, intptr_t from_index) { - ASSERT(characters_ == other->characters_); - if (other->cannot_match_) { - return; - } - if (cannot_match_) { - *this = *other; - return; - } - for (intptr_t i = from_index; i < characters_; i++) { - QuickCheckDetails::Position* pos = positions(i); - QuickCheckDetails::Position* other_pos = other->positions(i); - if (pos->mask != other_pos->mask || pos->value != other_pos->value || - !other_pos->determines_perfectly) { - // Our mask-compare operation will be approximate unless we have the - // exact same operation on both sides of the alternation. - pos->determines_perfectly = false; - } - pos->mask &= other_pos->mask; - pos->value &= pos->mask; - other_pos->value &= pos->mask; - uint16_t differing_bits = (pos->value ^ other_pos->value); - pos->mask &= ~differing_bits; - pos->value &= pos->mask; - } -} - -class VisitMarker : public ValueObject { - public: - explicit VisitMarker(NodeInfo* info) : info_(info) { - ASSERT(!info->visited); - info->visited = true; - } - ~VisitMarker() { info_->visited = false; } - - private: - NodeInfo* info_; -}; - -RegExpNode* SeqRegExpNode::FilterOneByte(intptr_t depth) { - if (info()->replacement_calculated) return replacement(); - if (depth < 0) return this; - ASSERT(!info()->visited); - VisitMarker marker(info()); - return FilterSuccessor(depth - 1); -} - -RegExpNode* SeqRegExpNode::FilterSuccessor(intptr_t depth) { - RegExpNode* next = on_success_->FilterOneByte(depth - 1); - if (next == nullptr) return set_replacement(nullptr); - on_success_ = next; - return set_replacement(this); -} - -// We need to check for the following characters: 0x39c 0x3bc 0x178. -static inline bool RangeContainsLatin1Equivalents(CharacterRange range) { - // TODO(dcarney): this could be a lot more efficient. - return range.Contains(0x39c) || range.Contains(0x3bc) || - range.Contains(0x178); -} - -static bool RangesContainLatin1Equivalents( - ZoneGrowableArray* ranges) { - for (intptr_t i = 0; i < ranges->length(); i++) { - // TODO(dcarney): this could be a lot more efficient. - if (RangeContainsLatin1Equivalents(ranges->At(i))) return true; - } - return false; -} - -static uint16_t ConvertNonLatin1ToLatin1(uint16_t c) { - ASSERT(c > Symbols::kMaxOneCharCodeSymbol); - switch (c) { - // This are equivalent characters in unicode. - case 0x39c: - case 0x3bc: - return 0xb5; - // This is an uppercase of a Latin-1 character - // outside of Latin-1. - case 0x178: - return 0xff; - } - return 0; -} - -RegExpNode* TextNode::FilterOneByte(intptr_t depth) { - if (info()->replacement_calculated) return replacement(); - if (depth < 0) return this; - ASSERT(!info()->visited); - VisitMarker marker(info()); - intptr_t element_count = elms_->length(); - for (intptr_t i = 0; i < element_count; i++) { - TextElement elm = elms_->At(i); - if (elm.text_type() == TextElement::ATOM) { - ZoneGrowableArray* quarks = elm.atom()->data(); - for (intptr_t j = 0; j < quarks->length(); j++) { - uint16_t c = quarks->At(j); - if (c <= Symbols::kMaxOneCharCodeSymbol) continue; - if (!elm.atom()->ignore_case()) return set_replacement(nullptr); - // Here, we need to check for characters whose upper and lower cases - // are outside the Latin-1 range. - uint16_t converted = ConvertNonLatin1ToLatin1(c); - // Character is outside Latin-1 completely - if (converted == 0) return set_replacement(nullptr); - // Convert quark to Latin-1 in place. - (*quarks)[0] = converted; - } - } else { - ASSERT(elm.text_type() == TextElement::CHAR_CLASS); - RegExpCharacterClass* cc = elm.char_class(); - ZoneGrowableArray* ranges = cc->ranges(); - if (!CharacterRange::IsCanonical(ranges)) { - CharacterRange::Canonicalize(ranges); - } - // Now they are in order so we only need to look at the first. - intptr_t range_count = ranges->length(); - if (cc->is_negated()) { - if (range_count != 0 && ranges->At(0).from() == 0 && - ranges->At(0).to() >= Symbols::kMaxOneCharCodeSymbol) { - // This will be handled in a later filter. - if (cc->flags().IgnoreCase() && - RangesContainLatin1Equivalents(ranges)) { - continue; - } - return set_replacement(nullptr); - } - } else { - if (range_count == 0 || - ranges->At(0).from() > Symbols::kMaxOneCharCodeSymbol) { - // This will be handled in a later filter. - if (cc->flags().IgnoreCase() && - RangesContainLatin1Equivalents(ranges)) - continue; - return set_replacement(nullptr); - } - } - } - } - return FilterSuccessor(depth - 1); -} - -RegExpNode* LoopChoiceNode::FilterOneByte(intptr_t depth) { - if (info()->replacement_calculated) return replacement(); - if (depth < 0) return this; - if (info()->visited) return this; - { - VisitMarker marker(info()); - - RegExpNode* continue_replacement = continue_node_->FilterOneByte(depth - 1); - // If we can't continue after the loop then there is no sense in doing the - // loop. - if (continue_replacement == nullptr) return set_replacement(nullptr); - } - - return ChoiceNode::FilterOneByte(depth - 1); -} - -RegExpNode* ChoiceNode::FilterOneByte(intptr_t depth) { - if (info()->replacement_calculated) return replacement(); - if (depth < 0) return this; - if (info()->visited) return this; - VisitMarker marker(info()); - intptr_t choice_count = alternatives_->length(); - - for (intptr_t i = 0; i < choice_count; i++) { - GuardedAlternative alternative = alternatives_->At(i); - if (alternative.guards() != nullptr && - alternative.guards()->length() != 0) { - set_replacement(this); - return this; - } - } - - intptr_t surviving = 0; - RegExpNode* survivor = nullptr; - for (intptr_t i = 0; i < choice_count; i++) { - GuardedAlternative alternative = alternatives_->At(i); - RegExpNode* replacement = alternative.node()->FilterOneByte(depth - 1); - ASSERT(replacement != this); // No missing EMPTY_MATCH_CHECK. - if (replacement != nullptr) { - (*alternatives_)[i].set_node(replacement); - surviving++; - survivor = replacement; - } - } - if (surviving < 2) return set_replacement(survivor); - - set_replacement(this); - if (surviving == choice_count) { - return this; - } - // Only some of the nodes survived the filtering. We need to rebuild the - // alternatives list. - ZoneGrowableArray* new_alternatives = - new (Z) ZoneGrowableArray(surviving); - for (intptr_t i = 0; i < choice_count; i++) { - RegExpNode* replacement = - (*alternatives_)[i].node()->FilterOneByte(depth - 1); - if (replacement != nullptr) { - (*alternatives_)[i].set_node(replacement); - new_alternatives->Add((*alternatives_)[i]); - } - } - alternatives_ = new_alternatives; - return this; -} - -RegExpNode* NegativeLookaroundChoiceNode::FilterOneByte(intptr_t depth) { - if (info()->replacement_calculated) return replacement(); - if (depth < 0) return this; - if (info()->visited) return this; - VisitMarker marker(info()); - // Alternative 0 is the negative lookahead, alternative 1 is what comes - // afterwards. - RegExpNode* node = (*alternatives_)[1].node(); - RegExpNode* replacement = node->FilterOneByte(depth - 1); - if (replacement == nullptr) return set_replacement(nullptr); - (*alternatives_)[1].set_node(replacement); - - RegExpNode* neg_node = (*alternatives_)[0].node(); - RegExpNode* neg_replacement = neg_node->FilterOneByte(depth - 1); - // If the negative lookahead is always going to fail then - // we don't need to check it. - if (neg_replacement == nullptr) return set_replacement(replacement); - (*alternatives_)[0].set_node(neg_replacement); - return set_replacement(this); -} - -void LoopChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) { - if (body_can_be_zero_length_ || info()->visited) return; - VisitMarker marker(info()); - return ChoiceNode::GetQuickCheckDetails(details, compiler, - characters_filled_in, not_at_start); -} - -void LoopChoiceNode::FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - if (body_can_be_zero_length_ || budget <= 0) { - bm->SetRest(offset); - SaveBMInfo(bm, not_at_start, offset); - return; - } - ChoiceNode::FillInBMInfo(offset, budget - 1, bm, not_at_start); - SaveBMInfo(bm, not_at_start, offset); -} - -void ChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) { - not_at_start = (not_at_start || not_at_start_); - intptr_t choice_count = alternatives_->length(); - ASSERT(choice_count > 0); - (*alternatives_)[0].node()->GetQuickCheckDetails( - details, compiler, characters_filled_in, not_at_start); - for (intptr_t i = 1; i < choice_count; i++) { - QuickCheckDetails new_details(details->characters()); - RegExpNode* node = (*alternatives_)[i].node(); - node->GetQuickCheckDetails(&new_details, compiler, characters_filled_in, - not_at_start); - // Here we merge the quick match details of the two branches. - details->Merge(&new_details, characters_filled_in); - } -} - -// Check for [0-9A-Z_a-z]. -static void EmitWordCheck(RegExpMacroAssembler* assembler, - BlockLabel* word, - BlockLabel* non_word, - bool fall_through_on_word) { - if (assembler->CheckSpecialCharacterClass( - fall_through_on_word ? 'w' : 'W', - fall_through_on_word ? non_word : word)) { - // Optimized implementation available. - return; - } - assembler->CheckCharacterGT('z', non_word); - assembler->CheckCharacterLT('0', non_word); - assembler->CheckCharacterGT('a' - 1, word); - assembler->CheckCharacterLT('9' + 1, word); - assembler->CheckCharacterLT('A', non_word); - assembler->CheckCharacterLT('Z' + 1, word); - if (fall_through_on_word) { - assembler->CheckNotCharacter('_', non_word); - } else { - assembler->CheckCharacter('_', word); - } -} - -// Emit the code to check for a ^ in multiline mode (1-character lookbehind -// that matches newline or the start of input). -static void EmitHat(RegExpCompiler* compiler, - RegExpNode* on_success, - Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - // We will be loading the previous character into the current character - // register. - Trace new_trace(*trace); - new_trace.InvalidateCurrentCharacter(); - - BlockLabel ok; - if (new_trace.cp_offset() == 0) { - // The start of input counts as a newline in this context, so skip to - // ok if we are at the start. - assembler->CheckAtStart(&ok); - } - // We already checked that we are not at the start of input so it must be - // OK to load the previous character. - assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, - new_trace.backtrack(), false); - if (!assembler->CheckSpecialCharacterClass('n', new_trace.backtrack())) { - // Newline means \n, \r, 0x2028 or 0x2029. - if (!compiler->one_byte()) { - assembler->CheckCharacterAfterAnd(0x2028, 0xfffe, &ok); - } - assembler->CheckCharacter('\n', &ok); - assembler->CheckNotCharacter('\r', new_trace.backtrack()); - } - assembler->BindBlock(&ok); - on_success->Emit(compiler, &new_trace); -} - -// Emit the code to handle \b and \B (word-boundary or non-word-boundary). -void AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - Trace::TriBool next_is_word_character = Trace::UNKNOWN; - bool not_at_start = (trace->at_start() == Trace::FALSE_VALUE); - BoyerMooreLookahead* lookahead = bm_info(not_at_start); - if (lookahead == nullptr) { - intptr_t eats_at_least = - Utils::Minimum(kMaxLookaheadForBoyerMoore, - EatsAtLeast(kMaxLookaheadForBoyerMoore, kRecursionBudget, - not_at_start)); - if (eats_at_least >= 1) { - BoyerMooreLookahead* bm = - new (Z) BoyerMooreLookahead(eats_at_least, compiler, Z); - FillInBMInfo(0, kRecursionBudget, bm, not_at_start); - if (bm->at(0)->is_non_word()) next_is_word_character = Trace::FALSE_VALUE; - if (bm->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; - } - } else { - if (lookahead->at(0)->is_non_word()) - next_is_word_character = Trace::FALSE_VALUE; - if (lookahead->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; - } - bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); - if (next_is_word_character == Trace::UNKNOWN) { - BlockLabel before_non_word; - BlockLabel before_word; - if (trace->characters_preloaded() != 1) { - assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); - } - // Fall through on non-word. - EmitWordCheck(assembler, &before_word, &before_non_word, false); - // Next character is not a word character. - assembler->BindBlock(&before_non_word); - BlockLabel ok; - // Backtrack on \B (non-boundary check) if previous is a word, - // since we know next *is not* a word and this would be a boundary. - BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); - - if (!assembler->IsClosed()) { - assembler->GoTo(&ok); - } - - assembler->BindBlock(&before_word); - BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); - assembler->BindBlock(&ok); - } else if (next_is_word_character == Trace::TRUE_VALUE) { - BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); - } else { - ASSERT(next_is_word_character == Trace::FALSE_VALUE); - BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); - } -} - -void AssertionNode::BacktrackIfPrevious( - RegExpCompiler* compiler, - Trace* trace, - AssertionNode::IfPrevious backtrack_if_previous) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - Trace new_trace(*trace); - new_trace.InvalidateCurrentCharacter(); - - BlockLabel fall_through, dummy; - - BlockLabel* non_word = backtrack_if_previous == kIsNonWord - ? new_trace.backtrack() - : &fall_through; - BlockLabel* word = backtrack_if_previous == kIsNonWord - ? &fall_through - : new_trace.backtrack(); - - if (new_trace.cp_offset() == 0) { - // The start of input counts as a non-word character, so the question is - // decided if we are at the start. - assembler->CheckAtStart(non_word); - } - // We already checked that we are not at the start of input so it must be - // OK to load the previous character. - assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, &dummy, false); - EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); - - assembler->BindBlock(&fall_through); - on_success()->Emit(compiler, &new_trace); -} - -void AssertionNode::GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t filled_in, - bool not_at_start) { - if (assertion_type_ == AT_START && not_at_start) { - details->set_cannot_match(); - return; - } - return on_success()->GetQuickCheckDetails(details, compiler, filled_in, - not_at_start); -} - -void AssertionNode::Emit(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - switch (assertion_type_) { - case AT_END: { - BlockLabel ok; - assembler->CheckPosition(trace->cp_offset(), &ok); - assembler->GoTo(trace->backtrack()); - assembler->BindBlock(&ok); - break; - } - case AT_START: { - if (trace->at_start() == Trace::FALSE_VALUE) { - assembler->GoTo(trace->backtrack()); - return; - } - if (trace->at_start() == Trace::UNKNOWN) { - assembler->CheckNotAtStart(trace->cp_offset(), trace->backtrack()); - Trace at_start_trace = *trace; - at_start_trace.set_at_start(Trace::TRUE_VALUE); - on_success()->Emit(compiler, &at_start_trace); - return; - } - } break; - case AFTER_NEWLINE: - EmitHat(compiler, on_success(), trace); - return; - case AT_BOUNDARY: - case AT_NON_BOUNDARY: { - EmitBoundaryCheck(compiler, trace); - return; - } - } - on_success()->Emit(compiler, trace); -} - -static bool DeterminedAlready(QuickCheckDetails* quick_check, intptr_t offset) { - if (quick_check == nullptr) return false; - if (offset >= quick_check->characters()) return false; - return quick_check->positions(offset)->determines_perfectly; -} - -static void UpdateBoundsCheck(intptr_t index, intptr_t* checked_up_to) { - if (index > *checked_up_to) { - *checked_up_to = index; - } -} - -// We call this repeatedly to generate code for each pass over the text node. -// The passes are in increasing order of difficulty because we hope one -// of the first passes will fail in which case we are saved the work of the -// later passes. for example for the case independent regexp /%[asdfghjkl]a/ -// we will check the '%' in the first pass, the case independent 'a' in the -// second pass and the character class in the last pass. -// -// The passes are done from right to left, so for example to test for /bar/ -// we will first test for an 'r' with offset 2, then an 'a' with offset 1 -// and then a 'b' with offset 0. This means we can avoid the end-of-input -// bounds check most of the time. In the example we only need to check for -// end-of-input when loading the putative 'r'. -// -// A slight complication involves the fact that the first character may already -// be fetched into a register by the previous node. In this case we want to -// do the test for that character first. We do this in separate passes. The -// 'preloaded' argument indicates that we are doing such a 'pass'. If such a -// pass has been performed then subsequent passes will have true in -// first_element_checked to indicate that character does not need to be -// checked again. -// -// In addition to all this we are passed a Trace, which can -// contain an AlternativeGeneration object. In this AlternativeGeneration -// object we can see details of any quick check that was already passed in -// order to get to the code we are now generating. The quick check can involve -// loading characters, which means we do not need to recheck the bounds -// up to the limit the quick check already checked. In addition the quick -// check can have involved a mask and compare operation which may simplify -// or obviate the need for further checks at some character positions. -void TextNode::TextEmitPass(RegExpCompiler* compiler, - TextEmitPassType pass, - bool preloaded, - Trace* trace, - bool first_element_checked, - intptr_t* checked_up_to) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - bool one_byte = compiler->one_byte(); - BlockLabel* backtrack = trace->backtrack(); - QuickCheckDetails* quick_check = trace->quick_check_performed(); - intptr_t element_count = elms_->length(); - intptr_t backward_offset = read_backward() ? -Length() : 0; - for (intptr_t i = preloaded ? 0 : element_count - 1; i >= 0; i--) { - TextElement elm = elms_->At(i); - intptr_t cp_offset = trace->cp_offset() + elm.cp_offset() + backward_offset; - if (elm.text_type() == TextElement::ATOM) { - ZoneGrowableArray* quarks = elm.atom()->data(); - for (intptr_t j = preloaded ? 0 : quarks->length() - 1; j >= 0; j--) { - if (SkipPass(pass, elm.atom()->ignore_case())) continue; - if (first_element_checked && i == 0 && j == 0) continue; - if (DeterminedAlready(quick_check, elm.cp_offset() + j)) continue; - EmitCharacterFunction* emit_function = nullptr; - uint16_t quark = quarks->At(j); - if (elm.atom()->ignore_case()) { - // Everywhere else we assume that a non-Latin-1 character cannot match - // a Latin-1 character. Avoid the cases where this is assumption is - // invalid by using the Latin1 equivalent instead. - quark = Latin1::TryConvertToLatin1(quark); - } - switch (pass) { - case NON_LATIN1_MATCH: - ASSERT(one_byte); - if (quark > Symbols::kMaxOneCharCodeSymbol) { - assembler->GoTo(backtrack); - return; - } - break; - case NON_LETTER_CHARACTER_MATCH: - emit_function = &EmitAtomNonLetter; - break; - case SIMPLE_CHARACTER_MATCH: - emit_function = &EmitSimpleCharacter; - break; - case CASE_CHARACTER_MATCH: - emit_function = &EmitAtomLetter; - break; - default: - break; - } - if (emit_function != nullptr) { - const bool bounds_check = - *checked_up_to < (cp_offset + j) || read_backward(); - bool bound_checked = - emit_function(Z, compiler, quarks->At(j), backtrack, - cp_offset + j, bounds_check, preloaded); - if (bound_checked) UpdateBoundsCheck(cp_offset + j, checked_up_to); - } - } - } else { - ASSERT(elm.text_type() == TextElement::CHAR_CLASS); - if (pass == CHARACTER_CLASS_MATCH) { - if (first_element_checked && i == 0) continue; - if (DeterminedAlready(quick_check, elm.cp_offset())) continue; - RegExpCharacterClass* cc = elm.char_class(); - bool bounds_check = *checked_up_to < cp_offset || read_backward(); - EmitCharClass(assembler, cc, one_byte, backtrack, cp_offset, - bounds_check, preloaded, Z); - UpdateBoundsCheck(cp_offset, checked_up_to); - } - } - } -} - -intptr_t TextNode::Length() { - TextElement elm = elms_->Last(); - ASSERT(elm.cp_offset() >= 0); - return elm.cp_offset() + elm.length(); -} - -bool TextNode::SkipPass(intptr_t intptr_t_pass, bool ignore_case) { - TextEmitPassType pass = static_cast(intptr_t_pass); - if (ignore_case) { - return pass == SIMPLE_CHARACTER_MATCH; - } else { - return pass == NON_LETTER_CHARACTER_MATCH || pass == CASE_CHARACTER_MATCH; - } -} - -TextNode* TextNode::CreateForCharacterRanges( - ZoneGrowableArray* ranges, - bool read_backward, - RegExpNode* on_success, - RegExpFlags flags) { - ASSERT(ranges != nullptr); - ZoneGrowableArray* elms = new ZoneGrowableArray(1); - elms->Add(TextElement::CharClass(new RegExpCharacterClass(ranges, flags))); - return new TextNode(elms, read_backward, on_success); -} - -TextNode* TextNode::CreateForSurrogatePair(CharacterRange lead, - CharacterRange trail, - bool read_backward, - RegExpNode* on_success, - RegExpFlags flags) { - auto lead_ranges = CharacterRange::List(on_success->zone(), lead); - auto trail_ranges = CharacterRange::List(on_success->zone(), trail); - auto elms = new ZoneGrowableArray(2); - - elms->Add( - TextElement::CharClass(new RegExpCharacterClass(lead_ranges, flags))); - elms->Add( - TextElement::CharClass(new RegExpCharacterClass(trail_ranges, flags))); - - return new TextNode(elms, read_backward, on_success); -} - -// This generates the code to match a text node. A text node can contain -// straight character sequences (possibly to be matched in a case-independent -// way) and character classes. For efficiency we do not do this in a single -// pass from left to right. Instead we pass over the text node several times, -// emitting code for some character positions every time. See the comment on -// TextEmitPass for details. -void TextNode::Emit(RegExpCompiler* compiler, Trace* trace) { - LimitResult limit_result = LimitVersions(compiler, trace); - if (limit_result == DONE) return; - ASSERT(limit_result == CONTINUE); - - if (trace->cp_offset() + Length() > RegExpMacroAssembler::kMaxCPOffset) { - compiler->SetRegExpTooBig(); - return; - } - - if (compiler->one_byte()) { - intptr_t dummy = 0; - TextEmitPass(compiler, NON_LATIN1_MATCH, false, trace, false, &dummy); - } - - bool first_elt_done = false; - intptr_t bound_checked_to = trace->cp_offset() - 1; - bound_checked_to += trace->bound_checked_up_to(); - - // If a character is preloaded into the current character register then - // check that now. - if (trace->characters_preloaded() == 1) { - for (intptr_t pass = kFirstRealPass; pass <= kLastPass; pass++) { - TextEmitPass(compiler, static_cast(pass), true, trace, - false, &bound_checked_to); - } - first_elt_done = true; - } - - for (intptr_t pass = kFirstRealPass; pass <= kLastPass; pass++) { - TextEmitPass(compiler, static_cast(pass), false, trace, - first_elt_done, &bound_checked_to); - } - - Trace successor_trace(*trace); - // If we advance backward, we may end up at the start. - successor_trace.AdvanceCurrentPositionInTrace( - read_backward() ? -Length() : Length(), compiler); - successor_trace.set_at_start(read_backward() ? Trace::UNKNOWN - : Trace::FALSE_VALUE); - RecursionCheck rc(compiler); - on_success()->Emit(compiler, &successor_trace); -} - -void Trace::InvalidateCurrentCharacter() { - characters_preloaded_ = 0; -} - -void Trace::AdvanceCurrentPositionInTrace(intptr_t by, - RegExpCompiler* compiler) { - // We don't have an instruction for shifting the current character register - // down or for using a shifted value for anything so lets just forget that - // we preloaded any characters into it. - characters_preloaded_ = 0; - // Adjust the offsets of the quick check performed information. This - // information is used to find out what we already determined about the - // characters by means of mask and compare. - quick_check_performed_.Advance(by, compiler->one_byte()); - cp_offset_ += by; - if (cp_offset_ > RegExpMacroAssembler::kMaxCPOffset) { - compiler->SetRegExpTooBig(); - cp_offset_ = 0; - } - bound_checked_up_to_ = - Utils::Maximum(static_cast(0), bound_checked_up_to_ - by); -} - -void TextNode::MakeCaseIndependent(bool is_one_byte) { - intptr_t element_count = elms_->length(); - for (intptr_t i = 0; i < element_count; i++) { - TextElement elm = elms_->At(i); - if (elm.text_type() == TextElement::CHAR_CLASS) { - RegExpCharacterClass* cc = elm.char_class(); - bool case_equivalents_already_added = - cc->flags().NeedsUnicodeCaseEquivalents(); - if (cc->flags().IgnoreCase() && !case_equivalents_already_added) { - // None of the standard character classes is different in the case - // independent case and it slows us down if we don't know that. - if (cc->is_standard()) continue; - CharacterRange::AddCaseEquivalents(cc->ranges(), is_one_byte, Z); - } - } - } -} - -intptr_t TextNode::GreedyLoopTextLength() { - TextElement elm = elms_->At(elms_->length() - 1); - return elm.cp_offset() + elm.length(); -} - -RegExpNode* TextNode::GetSuccessorOfOmnivorousTextNode( - RegExpCompiler* compiler) { - if (read_backward()) return nullptr; - if (elms_->length() != 1) return nullptr; - TextElement elm = elms_->At(0); - if (elm.text_type() != TextElement::CHAR_CLASS) return nullptr; - RegExpCharacterClass* node = elm.char_class(); - ZoneGrowableArray* ranges = node->ranges(); - if (!CharacterRange::IsCanonical(ranges)) { - CharacterRange::Canonicalize(ranges); - } - if (node->is_negated()) { - return ranges->length() == 0 ? on_success() : nullptr; - } - if (ranges->length() != 1) return nullptr; - uint32_t max_char; - if (compiler->one_byte()) { - max_char = Symbols::kMaxOneCharCodeSymbol; - } else { - max_char = Utf16::kMaxCodeUnit; - } - return ranges->At(0).IsEverything(max_char) ? on_success() : nullptr; -} - -// Finds the fixed match length of a sequence of nodes that goes from -// this alternative and back to this choice node. If there are variable -// length nodes or other complications in the way then return a sentinel -// value indicating that a greedy loop cannot be constructed. -intptr_t ChoiceNode::GreedyLoopTextLengthForAlternative( - const GuardedAlternative* alternative) { - intptr_t length = 0; - RegExpNode* node = alternative->node(); - // Later we will generate code for all these text nodes using recursion - // so we have to limit the max number. - intptr_t recursion_depth = 0; - while (node != this) { - if (recursion_depth++ > RegExpCompiler::kMaxRecursion) { - return kNodeIsTooComplexForGreedyLoops; - } - intptr_t node_length = node->GreedyLoopTextLength(); - if (node_length == kNodeIsTooComplexForGreedyLoops) { - return kNodeIsTooComplexForGreedyLoops; - } - length += node_length; - SeqRegExpNode* seq_node = static_cast(node); - node = seq_node->on_success(); - } - return read_backward() ? -length : length; -} - -void LoopChoiceNode::AddLoopAlternative(GuardedAlternative alt) { - ASSERT(loop_node_ == nullptr); - AddAlternative(alt); - loop_node_ = alt.node(); -} - -void LoopChoiceNode::AddContinueAlternative(GuardedAlternative alt) { - ASSERT(continue_node_ == nullptr); - AddAlternative(alt); - continue_node_ = alt.node(); -} - -void LoopChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - if (trace->stop_node() == this) { - // Back edge of greedy optimized loop node graph. - intptr_t text_length = - GreedyLoopTextLengthForAlternative(&alternatives_->At(0)); - ASSERT(text_length != kNodeIsTooComplexForGreedyLoops); - // Update the counter-based backtracking info on the stack. This is an - // optimization for greedy loops (see below). - ASSERT(trace->cp_offset() == text_length); - macro_assembler->AdvanceCurrentPosition(text_length); - macro_assembler->GoTo(trace->loop_label()); - return; - } - ASSERT(trace->stop_node() == nullptr); - if (!trace->is_trivial()) { - trace->Flush(compiler, this); - return; - } - ChoiceNode::Emit(compiler, trace); -} - -intptr_t ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler, - intptr_t eats_at_least) { - intptr_t preload_characters = - Utils::Minimum(static_cast(4), eats_at_least); - if (compiler->one_byte()) { -#if !defined(DART_COMPRESSED_POINTERS) && !defined(TARGET_ARCH_RISCV32) - if (preload_characters > 4) preload_characters = 4; - // We can't preload 3 characters because there is no machine instruction - // to do that. We can't just load 4 because we could be reading - // beyond the end of the string, which could cause a memory fault. - if (preload_characters == 3) preload_characters = 2; -#else - // Ensure LoadCodeUnitsInstr can always produce a Smi. See - // https://github.com/dart-lang/sdk/issues/29951 - if (preload_characters > 2) preload_characters = 2; -#endif - } else { -#if !defined(DART_COMPRESSED_POINTERS) && !defined(TARGET_ARCH_RISCV32) - if (preload_characters > 2) preload_characters = 2; -#else - // Ensure LoadCodeUnitsInstr can always produce a Smi. See - // https://github.com/dart-lang/sdk/issues/29951 - if (preload_characters > 1) preload_characters = 1; -#endif - } - if (!compiler->macro_assembler()->CanReadUnaligned()) { - if (preload_characters > 1) preload_characters = 1; - } - return preload_characters; -} - -// This structure is used when generating the alternatives in a choice node. It -// records the way the alternative is being code generated. -struct AlternativeGeneration { - AlternativeGeneration() - : possible_success(), - expects_preload(false), - after(), - quick_check_details() {} - BlockLabel possible_success; - bool expects_preload; - BlockLabel after; - QuickCheckDetails quick_check_details; -}; - -// Creates a list of AlternativeGenerations. If the list has a reasonable -// size then it is on the stack, otherwise the excess is on the heap. -class AlternativeGenerationList { - public: - explicit AlternativeGenerationList(intptr_t count) : count_(count) { - ASSERT(count >= 0); - if (count > kAFew) { - excess_alt_gens_.reset(new AlternativeGeneration[count - kAFew]); - } - } - - AlternativeGeneration* at(intptr_t i) { - ASSERT(0 <= i); - ASSERT(i < count_); - if (i < kAFew) { - return &a_few_alt_gens_[i]; - } - return &excess_alt_gens_[i - kAFew]; - } - - private: - static constexpr intptr_t kAFew = 10; - - intptr_t count_; - AlternativeGeneration a_few_alt_gens_[kAFew]; - std::unique_ptr excess_alt_gens_; - - DISALLOW_ALLOCATION(); - DISALLOW_COPY_AND_ASSIGN(AlternativeGenerationList); -}; - -static constexpr int32_t kRangeEndMarker = Utf::kMaxCodePoint + 1; - -// The '2' variant is inclusive from and exclusive to. -// This covers \s as defined in ECMA-262 5.1, 15.10.2.12, -// which include WhiteSpace (7.2) or LineTerminator (7.3) values. -// 0x180E has been removed from Unicode's Zs category and thus -// from ECMAScript's WhiteSpace category as of Unicode 6.3. -static constexpr int32_t kSpaceRanges[] = { - '\t', '\r' + 1, ' ', ' ' + 1, 0x00A0, 0x00A1, 0x1680, - 0x1681, 0x2000, 0x200B, 0x2028, 0x202A, 0x202F, 0x2030, - 0x205F, 0x2060, 0x3000, 0x3001, 0xFEFF, 0xFF00, kRangeEndMarker}; -static constexpr intptr_t kSpaceRangeCount = ARRAY_SIZE(kSpaceRanges); -static constexpr int32_t kWordRanges[] = { - '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, kRangeEndMarker}; -static constexpr intptr_t kWordRangeCount = ARRAY_SIZE(kWordRanges); -static constexpr int32_t kDigitRanges[] = {'0', '9' + 1, kRangeEndMarker}; -static constexpr intptr_t kDigitRangeCount = ARRAY_SIZE(kDigitRanges); -static constexpr int32_t kSurrogateRanges[] = {0xd800, 0xe000, kRangeEndMarker}; -static constexpr intptr_t kSurrogateRangeCount = ARRAY_SIZE(kSurrogateRanges); -static constexpr int32_t kLineTerminatorRanges[] = { - 0x000A, 0x000B, 0x000D, 0x000E, 0x2028, 0x202A, kRangeEndMarker}; -static constexpr intptr_t kLineTerminatorRangeCount = - ARRAY_SIZE(kLineTerminatorRanges); - -void BoyerMoorePositionInfo::Set(intptr_t character) { - SetInterval(Interval(character, character)); -} - -void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { - s_ = AddRange(s_, kSpaceRanges, kSpaceRangeCount, interval); - w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); - d_ = AddRange(d_, kDigitRanges, kDigitRangeCount, interval); - surrogate_ = - AddRange(surrogate_, kSurrogateRanges, kSurrogateRangeCount, interval); - if (interval.to() - interval.from() >= kMapSize - 1) { - if (map_count_ != kMapSize) { - map_count_ = kMapSize; - for (intptr_t i = 0; i < kMapSize; i++) - (*map_)[i] = true; - } - return; - } - for (intptr_t i = interval.from(); i <= interval.to(); i++) { - intptr_t mod_character = (i & kMask); - if (!map_->At(mod_character)) { - map_count_++; - (*map_)[mod_character] = true; - } - if (map_count_ == kMapSize) return; - } -} - -void BoyerMoorePositionInfo::SetAll() { - s_ = w_ = d_ = kLatticeUnknown; - if (map_count_ != kMapSize) { - map_count_ = kMapSize; - for (intptr_t i = 0; i < kMapSize; i++) - (*map_)[i] = true; - } -} - -BoyerMooreLookahead::BoyerMooreLookahead(intptr_t length, - RegExpCompiler* compiler, - Zone* zone) - : length_(length), compiler_(compiler) { - if (compiler->one_byte()) { - max_char_ = Symbols::kMaxOneCharCodeSymbol; - } else { - max_char_ = Utf16::kMaxCodeUnit; - } - bitmaps_ = new (zone) ZoneGrowableArray(length); - for (intptr_t i = 0; i < length; i++) { - bitmaps_->Add(new (zone) BoyerMoorePositionInfo(zone)); - } -} - -// Find the longest range of lookahead that has the fewest number of different -// characters that can occur at a given position. Since we are optimizing two -// different parameters at once this is a tradeoff. -bool BoyerMooreLookahead::FindWorthwhileInterval(intptr_t* from, intptr_t* to) { - intptr_t biggest_points = 0; - // If more than 32 characters out of 128 can occur it is unlikely that we can - // be lucky enough to step forwards much of the time. - const intptr_t kMaxMax = 32; - for (intptr_t max_number_of_chars = 4; max_number_of_chars < kMaxMax; - max_number_of_chars *= 2) { - biggest_points = - FindBestInterval(max_number_of_chars, biggest_points, from, to); - } - if (biggest_points == 0) return false; +bool RegExpStatics::IsUnmodifiedRegExp(Isolate* isolate, const RegExp& regexp) { + // Can't monkey patch in Dart. return true; } -// Find the highest-points range between 0 and length_ where the character -// information is not too vague. 'Too vague' means that there are more than -// max_number_of_chars that can occur at this position. Calculates the number -// of points as the product of width-of-the-range and -// probability-of-finding-one-of-the-characters, where the probability is -// calculated using the frequency distribution of the sample subject string. -intptr_t BoyerMooreLookahead::FindBestInterval(intptr_t max_number_of_chars, - intptr_t old_biggest_points, - intptr_t* from, - intptr_t* to) { - intptr_t biggest_points = old_biggest_points; - static constexpr intptr_t kSize = RegExpMacroAssembler::kTableSize; - for (intptr_t i = 0; i < length_;) { - while (i < length_ && Count(i) > max_number_of_chars) - i++; - if (i == length_) break; - intptr_t remembered_from = i; - bool union_map[kSize]; - for (intptr_t j = 0; j < kSize; j++) - union_map[j] = false; - while (i < length_ && Count(i) <= max_number_of_chars) { - BoyerMoorePositionInfo* map = bitmaps_->At(i); - for (intptr_t j = 0; j < kSize; j++) - union_map[j] |= map->at(j); - i++; - } - intptr_t frequency = 0; - for (intptr_t j = 0; j < kSize; j++) { - if (union_map[j]) { - // Add 1 to the frequency to give a small per-character boost for - // the cases where our sampling is not good enough and many - // characters have a frequency of zero. This means the frequency - // can theoretically be up to 2*kSize though we treat it mostly as - // a fraction of kSize. - frequency += compiler_->frequency_collator()->Frequency(j) + 1; - } - } - // We use the probability of skipping times the distance we are skipping to - // judge the effectiveness of this. Actually we have a cut-off: By - // dividing by 2 we switch off the skipping if the probability of skipping - // is less than 50%. This is because the multibyte mask-and-compare - // skipping in quickcheck is more likely to do well on this case. - bool in_quickcheck_range = - ((i - remembered_from < 4) || - (compiler_->one_byte() ? remembered_from <= 4 : remembered_from <= 2)); - // Called 'probability' but it is only a rough estimate and can actually - // be outside the 0-kSize range. - intptr_t probability = - (in_quickcheck_range ? kSize / 2 : kSize) - frequency; - intptr_t points = (i - remembered_from) * probability; - if (points > biggest_points) { - *from = remembered_from; - *to = i - 1; - biggest_points = points; - } - } - return biggest_points; -} - -// Take all the characters that will not prevent a successful match if they -// occur in the subject string in the range between min_lookahead and -// max_lookahead (inclusive) measured from the current position. If the -// character at max_lookahead offset is not one of these characters, then we -// can safely skip forwards by the number of characters in the range. -intptr_t BoyerMooreLookahead::GetSkipTable( - intptr_t min_lookahead, - intptr_t max_lookahead, - const TypedData& boolean_skip_table) { - const intptr_t kSize = RegExpMacroAssembler::kTableSize; - - const intptr_t kSkipArrayEntry = 0; - const intptr_t kDontSkipArrayEntry = 1; - - for (intptr_t i = 0; i < kSize; i++) { - boolean_skip_table.SetUint8(i, kSkipArrayEntry); - } - intptr_t skip = max_lookahead + 1 - min_lookahead; - - for (intptr_t i = max_lookahead; i >= min_lookahead; i--) { - BoyerMoorePositionInfo* map = bitmaps_->At(i); - for (intptr_t j = 0; j < kSize; j++) { - if (map->at(j)) { - boolean_skip_table.SetUint8(j, kDontSkipArrayEntry); - } - } - } - - return skip; -} - -// See comment above on the implementation of GetSkipTable. -void BoyerMooreLookahead::EmitSkipInstructions(RegExpMacroAssembler* masm) { - const intptr_t kSize = RegExpMacroAssembler::kTableSize; - - intptr_t min_lookahead = 0; - intptr_t max_lookahead = 0; - - if (!FindWorthwhileInterval(&min_lookahead, &max_lookahead)) return; - - bool found_single_character = false; - intptr_t single_character = 0; - for (intptr_t i = max_lookahead; i >= min_lookahead; i--) { - BoyerMoorePositionInfo* map = bitmaps_->At(i); - if (map->map_count() > 1 || - (found_single_character && map->map_count() != 0)) { - found_single_character = false; - break; - } - for (intptr_t j = 0; j < kSize; j++) { - if (map->at(j)) { - found_single_character = true; - single_character = j; - break; - } - } - } - - intptr_t lookahead_width = max_lookahead + 1 - min_lookahead; - - if (found_single_character && lookahead_width == 1 && max_lookahead < 3) { - // The mask-compare can probably handle this better. - return; - } - - if (found_single_character) { - BlockLabel cont, again; - masm->BindBlock(&again); - masm->LoadCurrentCharacter(max_lookahead, &cont, true); - if (max_char_ > kSize) { - masm->CheckCharacterAfterAnd(single_character, - RegExpMacroAssembler::kTableMask, &cont); - } else { - masm->CheckCharacter(single_character, &cont); - } - masm->AdvanceCurrentPosition(lookahead_width); - masm->GoTo(&again); - masm->BindBlock(&cont); - return; - } - - const TypedData& boolean_skip_table = TypedData::ZoneHandle( - compiler_->zone(), - TypedData::New(kTypedDataUint8ArrayCid, kSize, Heap::kOld)); - intptr_t skip_distance = - GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table); - ASSERT(skip_distance != 0); - - BlockLabel cont, again; - - masm->BindBlock(&again); - masm->CheckPreemption(/*is_backtrack=*/false); - masm->LoadCurrentCharacter(max_lookahead, &cont, true); - masm->CheckBitInTable(boolean_skip_table, &cont); - masm->AdvanceCurrentPosition(skip_distance); - masm->GoTo(&again); - masm->BindBlock(&cont); - - return; -} - -/* Code generation for choice nodes. - * - * We generate quick checks that do a mask and compare to eliminate a - * choice. If the quick check succeeds then it jumps to the continuation to - * do slow checks and check subsequent nodes. If it fails (the common case) - * it falls through to the next choice. - * - * Here is the desired flow graph. Nodes directly below each other imply - * fallthrough. Alternatives 1 and 2 have quick checks. Alternative - * 3 doesn't have a quick check so we have to call the slow check. - * Nodes are marked Qn for quick checks and Sn for slow checks. The entire - * regexp continuation is generated directly after the Sn node, up to the - * next GoTo if we decide to reuse some already generated code. Some - * nodes expect preload_characters to be preloaded into the current - * character register. R nodes do this preloading. Vertices are marked - * F for failures and S for success (possible success in the case of quick - * nodes). L, V, < and > are used as arrow heads. - * - * ----------> R - * | - * V - * Q1 -----> S1 - * | S / - * F| / - * | F/ - * | / - * | R - * | / - * V L - * Q2 -----> S2 - * | S / - * F| / - * | F/ - * | / - * | R - * | / - * V L - * S3 - * | - * F| - * | - * R - * | - * backtrack V - * <----------Q4 - * \ F | - * \ |S - * \ F V - * \-----S4 - * - * For greedy loops we push the current position, then generate the code that - * eats the input specially in EmitGreedyLoop. The other choice (the - * continuation) is generated by the normal code in EmitChoices, and steps back - * in the input to the starting position when it fails to match. The loop code - * looks like this (U is the unwind code that steps back in the greedy loop). - * - * _____ - * / \ - * V | - * ----------> S1 | - * /| | - * / |S | - * F/ \_____/ - * / - * |<----- - * | \ - * V |S - * Q2 ---> U----->backtrack - * | F / - * S| / - * V F / - * S2--/ - */ - -GreedyLoopState::GreedyLoopState(bool not_at_start) { - counter_backtrack_trace_.set_backtrack(&label_); - if (not_at_start) counter_backtrack_trace_.set_at_start(Trace::FALSE_VALUE); -} - -void ChoiceNode::AssertGuardsMentionRegisters(Trace* trace) { -#ifdef DEBUG - intptr_t choice_count = alternatives_->length(); - for (intptr_t i = 0; i < choice_count - 1; i++) { - GuardedAlternative alternative = alternatives_->At(i); - ZoneGrowableArray* guards = alternative.guards(); - intptr_t guard_count = (guards == nullptr) ? 0 : guards->length(); - for (intptr_t j = 0; j < guard_count; j++) { - ASSERT(!trace->mentions_reg(guards->At(j)->reg())); - } - } -#endif -} - -void ChoiceNode::SetUpPreLoad(RegExpCompiler* compiler, - Trace* current_trace, - PreloadState* state) { - if (state->eats_at_least_ == PreloadState::kEatsAtLeastNotYetInitialized) { - // Save some time by looking at most one machine word ahead. - state->eats_at_least_ = - EatsAtLeast(compiler->one_byte() ? 4 : 2, kRecursionBudget, - current_trace->at_start() == Trace::FALSE_VALUE); - } - state->preload_characters_ = - CalculatePreloadCharacters(compiler, state->eats_at_least_); - - state->preload_is_current_ = - (current_trace->characters_preloaded() == state->preload_characters_); - state->preload_has_checked_bounds_ = state->preload_is_current_; -} - -void ChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { - intptr_t choice_count = alternatives_->length(); - - if (choice_count == 1 && alternatives_->At(0).guards() == nullptr) { - alternatives_->At(0).node()->Emit(compiler, trace); - return; - } - - AssertGuardsMentionRegisters(trace); - - LimitResult limit_result = LimitVersions(compiler, trace); - if (limit_result == DONE) return; - ASSERT(limit_result == CONTINUE); - - // For loop nodes we already flushed (see LoopChoiceNode::Emit), but for - // other choice nodes we only flush if we are out of code size budget. - if (trace->flush_budget() == 0 && trace->actions() != nullptr) { - trace->Flush(compiler, this); - return; - } - - RecursionCheck rc(compiler); - - PreloadState preload; - preload.init(); - GreedyLoopState greedy_loop_state(not_at_start()); - - intptr_t text_length = - GreedyLoopTextLengthForAlternative(&alternatives_->At(0)); - AlternativeGenerationList alt_gens(choice_count); - - if (choice_count > 1 && text_length != kNodeIsTooComplexForGreedyLoops) { - trace = EmitGreedyLoop(compiler, trace, &alt_gens, &preload, - &greedy_loop_state, text_length); - } else { - // TODO(erikcorry): Delete this. We don't need this label, but it makes us - // match the traces produced pre-cleanup. - BlockLabel second_choice; - compiler->macro_assembler()->BindBlock(&second_choice); - - preload.eats_at_least_ = EmitOptimizedUnanchoredSearch(compiler, trace); - - EmitChoices(compiler, &alt_gens, 0, trace, &preload); - } - - // At this point we need to generate slow checks for the alternatives where - // the quick check was inlined. We can recognize these because the associated - // label was bound. - intptr_t new_flush_budget = trace->flush_budget() / choice_count; - for (intptr_t i = 0; i < choice_count; i++) { - AlternativeGeneration* alt_gen = alt_gens.at(i); - Trace new_trace(*trace); - // If there are actions to be flushed we have to limit how many times - // they are flushed. Take the budget of the parent trace and distribute - // it fairly amongst the children. - if (new_trace.actions() != nullptr) { - new_trace.set_flush_budget(new_flush_budget); - } - bool next_expects_preload = - i == choice_count - 1 ? false : alt_gens.at(i + 1)->expects_preload; - EmitOutOfLineContinuation(compiler, &new_trace, alternatives_->At(i), - alt_gen, preload.preload_characters_, - next_expects_preload); - } -} - -Trace* ChoiceNode::EmitGreedyLoop(RegExpCompiler* compiler, - Trace* trace, - AlternativeGenerationList* alt_gens, - PreloadState* preload, - GreedyLoopState* greedy_loop_state, - intptr_t text_length) { - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - // Here we have special handling for greedy loops containing only text nodes - // and other simple nodes. These are handled by pushing the current - // position on the stack and then incrementing the current position each - // time around the switch. On backtrack we decrement the current position - // and check it against the pushed value. This avoids pushing backtrack - // information for each iteration of the loop, which could take up a lot of - // space. - ASSERT(trace->stop_node() == nullptr); - macro_assembler->PushCurrentPosition(); - BlockLabel greedy_match_failed; - Trace greedy_match_trace; - if (not_at_start()) greedy_match_trace.set_at_start(Trace::FALSE_VALUE); - greedy_match_trace.set_backtrack(&greedy_match_failed); - BlockLabel loop_label; - macro_assembler->BindBlock(&loop_label); - macro_assembler->CheckPreemption(/*is_backtrack=*/false); - greedy_match_trace.set_stop_node(this); - greedy_match_trace.set_loop_label(&loop_label); - (*alternatives_)[0].node()->Emit(compiler, &greedy_match_trace); - macro_assembler->BindBlock(&greedy_match_failed); - - BlockLabel second_choice; // For use in greedy matches. - macro_assembler->BindBlock(&second_choice); - - Trace* new_trace = greedy_loop_state->counter_backtrack_trace(); - - EmitChoices(compiler, alt_gens, 1, new_trace, preload); - - macro_assembler->BindBlock(greedy_loop_state->label()); - // If we have unwound to the bottom then backtrack. - macro_assembler->CheckGreedyLoop(trace->backtrack()); - // Otherwise try the second priority at an earlier position. - macro_assembler->AdvanceCurrentPosition(-text_length); - macro_assembler->GoTo(&second_choice); - return new_trace; -} - -intptr_t ChoiceNode::EmitOptimizedUnanchoredSearch(RegExpCompiler* compiler, - Trace* trace) { - intptr_t eats_at_least = PreloadState::kEatsAtLeastNotYetInitialized; - if (alternatives_->length() != 2) return eats_at_least; - - GuardedAlternative alt1 = alternatives_->At(1); - if (alt1.guards() != nullptr && alt1.guards()->length() != 0) { - return eats_at_least; - } - RegExpNode* eats_anything_node = alt1.node(); - if (eats_anything_node->GetSuccessorOfOmnivorousTextNode(compiler) != this) { - return eats_at_least; - } - - // Really we should be creating a new trace when we execute this function, - // but there is no need, because the code it generates cannot backtrack, and - // we always arrive here with a trivial trace (since it's the entry to a - // loop. That also implies that there are no preloaded characters, which is - // good, because it means we won't be violating any assumptions by - // overwriting those characters with new load instructions. - ASSERT(trace->is_trivial()); - - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - // At this point we know that we are at a non-greedy loop that will eat - // any character one at a time. Any non-anchored regexp has such a - // loop prepended to it in order to find where it starts. We look for - // a pattern of the form ...abc... where we can look 6 characters ahead - // and step forwards 3 if the character is not one of abc. Abc need - // not be atoms, they can be any reasonably limited character class or - // small alternation. - BoyerMooreLookahead* bm = bm_info(false); - if (bm == nullptr) { - eats_at_least = Utils::Minimum( - kMaxLookaheadForBoyerMoore, - EatsAtLeast(kMaxLookaheadForBoyerMoore, kRecursionBudget, false)); - if (eats_at_least >= 1) { - bm = new (Z) BoyerMooreLookahead(eats_at_least, compiler, Z); - GuardedAlternative alt0 = alternatives_->At(0); - alt0.node()->FillInBMInfo(0, kRecursionBudget, bm, false); - } - } - if (bm != nullptr) { - bm->EmitSkipInstructions(macro_assembler); - } - return eats_at_least; -} - -void ChoiceNode::EmitChoices(RegExpCompiler* compiler, - AlternativeGenerationList* alt_gens, - intptr_t first_choice, - Trace* trace, - PreloadState* preload) { - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - SetUpPreLoad(compiler, trace, preload); - - // For now we just call all choices one after the other. The idea ultimately - // is to use the Dispatch table to try only the relevant ones. - intptr_t choice_count = alternatives_->length(); - - intptr_t new_flush_budget = trace->flush_budget() / choice_count; - - for (intptr_t i = first_choice; i < choice_count; i++) { - bool is_last = i == choice_count - 1; - bool fall_through_on_failure = !is_last; - GuardedAlternative alternative = alternatives_->At(i); - AlternativeGeneration* alt_gen = alt_gens->at(i); - alt_gen->quick_check_details.set_characters(preload->preload_characters_); - ZoneGrowableArray* guards = alternative.guards(); - intptr_t guard_count = (guards == nullptr) ? 0 : guards->length(); - Trace new_trace(*trace); - new_trace.set_characters_preloaded( - preload->preload_is_current_ ? preload->preload_characters_ : 0); - if (preload->preload_has_checked_bounds_) { - new_trace.set_bound_checked_up_to(preload->preload_characters_); - } - new_trace.quick_check_performed()->Clear(); - if (not_at_start_) new_trace.set_at_start(Trace::FALSE_VALUE); - if (!is_last) { - new_trace.set_backtrack(&alt_gen->after); - } - alt_gen->expects_preload = preload->preload_is_current_; - bool generate_full_check_inline = false; - if (kRegexpOptimization && - try_to_emit_quick_check_for_alternative(i == 0) && - alternative.node()->EmitQuickCheck( - compiler, trace, &new_trace, preload->preload_has_checked_bounds_, - &alt_gen->possible_success, &alt_gen->quick_check_details, - fall_through_on_failure)) { - // Quick check was generated for this choice. - preload->preload_is_current_ = true; - preload->preload_has_checked_bounds_ = true; - // If we generated the quick check to fall through on possible success, - // we now need to generate the full check inline. - if (!fall_through_on_failure) { - macro_assembler->BindBlock(&alt_gen->possible_success); - new_trace.set_quick_check_performed(&alt_gen->quick_check_details); - new_trace.set_characters_preloaded(preload->preload_characters_); - new_trace.set_bound_checked_up_to(preload->preload_characters_); - generate_full_check_inline = true; - } - } else if (alt_gen->quick_check_details.cannot_match()) { - if (!fall_through_on_failure) { - macro_assembler->GoTo(trace->backtrack()); - } - continue; - } else { - // No quick check was generated. Put the full code here. - // If this is not the first choice then there could be slow checks from - // previous cases that go here when they fail. There's no reason to - // insist that they preload characters since the slow check we are about - // to generate probably can't use it. - if (i != first_choice) { - alt_gen->expects_preload = false; - new_trace.InvalidateCurrentCharacter(); - } - generate_full_check_inline = true; - } - if (generate_full_check_inline) { - if (new_trace.actions() != nullptr) { - new_trace.set_flush_budget(new_flush_budget); - } - for (intptr_t j = 0; j < guard_count; j++) { - GenerateGuard(macro_assembler, guards->At(j), &new_trace); - } - alternative.node()->Emit(compiler, &new_trace); - preload->preload_is_current_ = false; - } - macro_assembler->BindBlock(&alt_gen->after); - } -} - -void ChoiceNode::EmitOutOfLineContinuation(RegExpCompiler* compiler, - Trace* trace, - GuardedAlternative alternative, - AlternativeGeneration* alt_gen, - intptr_t preload_characters, - bool next_expects_preload) { - if (!alt_gen->possible_success.is_linked()) return; - - RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); - macro_assembler->BindBlock(&alt_gen->possible_success); - Trace out_of_line_trace(*trace); - out_of_line_trace.set_characters_preloaded(preload_characters); - out_of_line_trace.set_quick_check_performed(&alt_gen->quick_check_details); - if (not_at_start_) out_of_line_trace.set_at_start(Trace::FALSE_VALUE); - ZoneGrowableArray* guards = alternative.guards(); - intptr_t guard_count = (guards == nullptr) ? 0 : guards->length(); - if (next_expects_preload) { - BlockLabel reload_current_char; - out_of_line_trace.set_backtrack(&reload_current_char); - for (intptr_t j = 0; j < guard_count; j++) { - GenerateGuard(macro_assembler, guards->At(j), &out_of_line_trace); - } - alternative.node()->Emit(compiler, &out_of_line_trace); - macro_assembler->BindBlock(&reload_current_char); - // Reload the current character, since the next quick check expects that. - // We don't need to check bounds here because we only get into this - // code through a quick check which already did the checked load. - macro_assembler->LoadCurrentCharacter(trace->cp_offset(), nullptr, false, - preload_characters); - macro_assembler->GoTo(&(alt_gen->after)); - } else { - out_of_line_trace.set_backtrack(&(alt_gen->after)); - for (intptr_t j = 0; j < guard_count; j++) { - GenerateGuard(macro_assembler, guards->At(j), &out_of_line_trace); - } - alternative.node()->Emit(compiler, &out_of_line_trace); - } -} - -void ActionNode::Emit(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - LimitResult limit_result = LimitVersions(compiler, trace); - if (limit_result == DONE) return; - ASSERT(limit_result == CONTINUE); - - RecursionCheck rc(compiler); - - switch (action_type_) { - case STORE_POSITION: { - Trace::DeferredCapture new_capture(data_.u_position_register.reg, - data_.u_position_register.is_capture, - trace); - Trace new_trace = *trace; - new_trace.add_action(&new_capture); - on_success()->Emit(compiler, &new_trace); - break; - } - case INCREMENT_REGISTER: { - Trace::DeferredIncrementRegister new_increment( - data_.u_increment_register.reg); - Trace new_trace = *trace; - new_trace.add_action(&new_increment); - on_success()->Emit(compiler, &new_trace); - break; - } - case SET_REGISTER: { - Trace::DeferredSetRegister new_set(data_.u_store_register.reg, - data_.u_store_register.value); - Trace new_trace = *trace; - new_trace.add_action(&new_set); - on_success()->Emit(compiler, &new_trace); - break; - } - case CLEAR_CAPTURES: { - Trace::DeferredClearCaptures new_capture(Interval( - data_.u_clear_captures.range_from, data_.u_clear_captures.range_to)); - Trace new_trace = *trace; - new_trace.add_action(&new_capture); - on_success()->Emit(compiler, &new_trace); - break; - } - case BEGIN_SUBMATCH: - if (!trace->is_trivial()) { - trace->Flush(compiler, this); - } else { - assembler->WriteCurrentPositionToRegister( - data_.u_submatch.current_position_register, 0); - assembler->WriteStackPointerToRegister( - data_.u_submatch.stack_pointer_register); - on_success()->Emit(compiler, trace); - } - break; - case EMPTY_MATCH_CHECK: { - intptr_t start_pos_reg = data_.u_empty_match_check.start_register; - intptr_t stored_pos = 0; - intptr_t rep_reg = data_.u_empty_match_check.repetition_register; - bool has_minimum = (rep_reg != RegExpCompiler::kNoRegister); - bool know_dist = trace->GetStoredPosition(start_pos_reg, &stored_pos); - if (know_dist && !has_minimum && stored_pos == trace->cp_offset()) { - // If we know we haven't advanced and there is no minimum we - // can just backtrack immediately. - assembler->GoTo(trace->backtrack()); - } else if (know_dist && stored_pos < trace->cp_offset()) { - // If we know we've advanced we can generate the continuation - // immediately. - on_success()->Emit(compiler, trace); - } else if (!trace->is_trivial()) { - trace->Flush(compiler, this); - } else { - BlockLabel skip_empty_check; - // If we have a minimum number of repetitions we check the current - // number first and skip the empty check if it's not enough. - if (has_minimum) { - intptr_t limit = data_.u_empty_match_check.repetition_limit; - assembler->IfRegisterLT(rep_reg, limit, &skip_empty_check); - } - // If the match is empty we bail out, otherwise we fall through - // to the on-success continuation. - assembler->IfRegisterEqPos(data_.u_empty_match_check.start_register, - trace->backtrack()); - assembler->BindBlock(&skip_empty_check); - on_success()->Emit(compiler, trace); - } - break; - } - case POSITIVE_SUBMATCH_SUCCESS: { - if (!trace->is_trivial()) { - trace->Flush(compiler, this); - return; - } - assembler->ReadCurrentPositionFromRegister( - data_.u_submatch.current_position_register); - assembler->ReadStackPointerFromRegister( - data_.u_submatch.stack_pointer_register); - intptr_t clear_register_count = data_.u_submatch.clear_register_count; - if (clear_register_count == 0) { - on_success()->Emit(compiler, trace); - return; - } - intptr_t clear_registers_from = data_.u_submatch.clear_register_from; - BlockLabel clear_registers_backtrack; - Trace new_trace = *trace; - new_trace.set_backtrack(&clear_registers_backtrack); - on_success()->Emit(compiler, &new_trace); - - assembler->BindBlock(&clear_registers_backtrack); - intptr_t clear_registers_to = - clear_registers_from + clear_register_count - 1; - assembler->ClearRegisters(clear_registers_from, clear_registers_to); - - ASSERT(trace->backtrack() == nullptr); - assembler->Backtrack(); - return; - } - default: - UNREACHABLE(); - } -} - -void BackReferenceNode::Emit(RegExpCompiler* compiler, Trace* trace) { - RegExpMacroAssembler* assembler = compiler->macro_assembler(); - if (!trace->is_trivial()) { - trace->Flush(compiler, this); - return; - } - - LimitResult limit_result = LimitVersions(compiler, trace); - if (limit_result == DONE) return; - ASSERT(limit_result == CONTINUE); - - RecursionCheck rc(compiler); - - ASSERT(start_reg_ + 1 == end_reg_); - if (flags_.IgnoreCase()) { - assembler->CheckNotBackReferenceIgnoreCase( - start_reg_, read_backward(), flags_.IsUnicode(), trace->backtrack()); - } else { - assembler->CheckNotBackReference(start_reg_, read_backward(), - trace->backtrack()); - } - // We are going to advance backward, so we may end up at the start. - if (read_backward()) trace->set_at_start(Trace::UNKNOWN); - - // Check that the back reference does not end inside a surrogate pair. - if (flags_.IsUnicode() && !compiler->one_byte()) { - assembler->CheckNotInSurrogatePair(trace->cp_offset(), trace->backtrack()); - } - - on_success()->Emit(compiler, trace); -} - -// ------------------------------------------------------------------- -// Dot/dotty output - -#ifdef DEBUG - -class DotPrinter : public NodeVisitor { - public: - explicit DotPrinter(bool ignore_case) {} - void PrintNode(const char* label, RegExpNode* node); - void Visit(RegExpNode* node); - void PrintAttributes(RegExpNode* from); - void PrintOnFailure(RegExpNode* from, RegExpNode* to); -#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that); - FOR_EACH_NODE_TYPE(DECLARE_VISIT) -#undef DECLARE_VISIT -}; - -void DotPrinter::PrintNode(const char* label, RegExpNode* node) { - OS::PrintErr("digraph G {\n graph [label=\""); - for (intptr_t i = 0; label[i] != '\0'; i++) { - switch (label[i]) { - case '\\': - OS::PrintErr("\\\\"); - break; - case '"': - OS::PrintErr("\""); - break; - default: - OS::PrintErr("%c", label[i]); - break; - } - } - OS::PrintErr("\"];\n"); - Visit(node); - OS::PrintErr("}\n"); -} - -void DotPrinter::Visit(RegExpNode* node) { - if (node->info()->visited) return; - node->info()->visited = true; - node->Accept(this); -} - -void DotPrinter::PrintOnFailure(RegExpNode* from, RegExpNode* on_failure) { - OS::PrintErr(" n%p -> n%p [style=dotted];\n", from, on_failure); - Visit(on_failure); -} - -class AttributePrinter : public ValueObject { - public: - AttributePrinter() : first_(true) {} - void PrintSeparator() { - if (first_) { - first_ = false; - } else { - OS::PrintErr("|"); - } - } - void PrintBit(const char* name, bool value) { - if (!value) return; - PrintSeparator(); - OS::PrintErr("{%s}", name); - } - void PrintPositive(const char* name, intptr_t value) { - if (value < 0) return; - PrintSeparator(); - OS::PrintErr("{%s|%" Pd "}", name, value); - } - - private: - bool first_; -}; - -void DotPrinter::PrintAttributes(RegExpNode* that) { - OS::PrintErr( - " a%p [shape=Mrecord, color=grey, fontcolor=grey, " - "margin=0.1, fontsize=10, label=\"{", - that); - AttributePrinter printer; - NodeInfo* info = that->info(); - printer.PrintBit("NI", info->follows_newline_interest); - printer.PrintBit("WI", info->follows_word_interest); - printer.PrintBit("SI", info->follows_start_interest); - BlockLabel* label = that->label(); - if (label->is_bound()) printer.PrintPositive("@", label->pos()); - OS::PrintErr( - "}\"];\n" - " a%p -> n%p [style=dashed, color=grey, arrowhead=none];\n", - that, that); -} - -void DotPrinter::VisitChoice(ChoiceNode* that) { - OS::PrintErr(" n%p [shape=Mrecord, label=\"?\"];\n", that); - for (intptr_t i = 0; i < that->alternatives()->length(); i++) { - GuardedAlternative alt = that->alternatives()->At(i); - OS::PrintErr(" n%p -> n%p", that, alt.node()); - } - for (intptr_t i = 0; i < that->alternatives()->length(); i++) { - GuardedAlternative alt = that->alternatives()->At(i); - alt.node()->Accept(this); - } -} - -void DotPrinter::VisitText(TextNode* that) { - OS::PrintErr(" n%p [label=\"", that); - for (intptr_t i = 0; i < that->elements()->length(); i++) { - if (i > 0) OS::PrintErr(" "); - TextElement elm = that->elements()->At(i); - switch (elm.text_type()) { - case TextElement::ATOM: { - ZoneGrowableArray* data = elm.atom()->data(); - for (intptr_t i = 0; i < data->length(); i++) { - OS::PrintErr("%c", static_cast(data->At(i))); - } - break; - } - case TextElement::CHAR_CLASS: { - RegExpCharacterClass* node = elm.char_class(); - OS::PrintErr("["); - if (node->is_negated()) OS::PrintErr("^"); - for (intptr_t j = 0; j < node->ranges()->length(); j++) { - CharacterRange range = node->ranges()->At(j); - PrintUtf16(range.from()); - OS::PrintErr("-"); - PrintUtf16(range.to()); - } - OS::PrintErr("]"); - break; - } - default: - UNREACHABLE(); - } - } - OS::PrintErr("\", shape=box, peripheries=2];\n"); - PrintAttributes(that); - OS::PrintErr(" n%p -> n%p;\n", that, that->on_success()); - Visit(that->on_success()); -} - -void DotPrinter::VisitBackReference(BackReferenceNode* that) { - OS::PrintErr(" n%p [label=\"$%" Pd "..$%" Pd "\", shape=doubleoctagon];\n", - that, that->start_register(), that->end_register()); - PrintAttributes(that); - OS::PrintErr(" n%p -> n%p;\n", that, that->on_success()); - Visit(that->on_success()); -} - -void DotPrinter::VisitEnd(EndNode* that) { - OS::PrintErr(" n%p [style=bold, shape=point];\n", that); - PrintAttributes(that); -} - -void DotPrinter::VisitAssertion(AssertionNode* that) { - OS::PrintErr(" n%p [", that); - switch (that->assertion_type()) { - case AssertionNode::AT_END: - OS::PrintErr("label=\"$\", shape=septagon"); - break; - case AssertionNode::AT_START: - OS::PrintErr("label=\"^\", shape=septagon"); - break; - case AssertionNode::AT_BOUNDARY: - OS::PrintErr("label=\"\\b\", shape=septagon"); - break; - case AssertionNode::AT_NON_BOUNDARY: - OS::PrintErr("label=\"\\B\", shape=septagon"); - break; - case AssertionNode::AFTER_NEWLINE: - OS::PrintErr("label=\"(?<=\\n)\", shape=septagon"); - break; - } - OS::PrintErr("];\n"); - PrintAttributes(that); - RegExpNode* successor = that->on_success(); - OS::PrintErr(" n%p -> n%p;\n", that, successor); - Visit(successor); -} - -void DotPrinter::VisitAction(ActionNode* that) { - OS::PrintErr(" n%p [", that); - switch (that->action_type_) { - case ActionNode::SET_REGISTER: - OS::PrintErr("label=\"$%" Pd ":=%" Pd "\", shape=octagon", - that->data_.u_store_register.reg, - that->data_.u_store_register.value); - break; - case ActionNode::INCREMENT_REGISTER: - OS::PrintErr("label=\"$%" Pd "++\", shape=octagon", - that->data_.u_increment_register.reg); - break; - case ActionNode::STORE_POSITION: - OS::PrintErr("label=\"$%" Pd ":=$pos\", shape=octagon", - that->data_.u_position_register.reg); - break; - case ActionNode::BEGIN_SUBMATCH: - OS::PrintErr("label=\"$%" Pd ":=$pos,begin\", shape=septagon", - that->data_.u_submatch.current_position_register); - break; - case ActionNode::POSITIVE_SUBMATCH_SUCCESS: - OS::PrintErr("label=\"escape\", shape=septagon"); - break; - case ActionNode::EMPTY_MATCH_CHECK: - OS::PrintErr("label=\"$%" Pd "=$pos?,$%" Pd "<%" Pd "?\", shape=septagon", - that->data_.u_empty_match_check.start_register, - that->data_.u_empty_match_check.repetition_register, - that->data_.u_empty_match_check.repetition_limit); - break; - case ActionNode::CLEAR_CAPTURES: { - OS::PrintErr("label=\"clear $%" Pd " to $%" Pd "\", shape=septagon", - that->data_.u_clear_captures.range_from, - that->data_.u_clear_captures.range_to); - break; - } - } - OS::PrintErr("];\n"); - PrintAttributes(that); - RegExpNode* successor = that->on_success(); - OS::PrintErr(" n%p -> n%p;\n", that, successor); - Visit(successor); -} - -void RegExpEngine::DotPrint(const char* label, - RegExpNode* node, - bool ignore_case) { - DotPrinter printer(ignore_case); - printer.PrintNode(label, node); -} - -#endif // DEBUG - -// ------------------------------------------------------------------- -// Tree to graph conversion - -// The zone in which we allocate graph nodes. -#define OZ (on_success->zone()) - -RegExpNode* RegExpAtom::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - ZoneGrowableArray* elms = - new (OZ) ZoneGrowableArray(1); - elms->Add(TextElement::Atom(this)); - return new (OZ) TextNode(elms, compiler->read_backward(), on_success); -} - -RegExpNode* RegExpText::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - ZoneGrowableArray* elms = - new (OZ) ZoneGrowableArray(1); - for (intptr_t i = 0; i < elements()->length(); i++) { - elms->Add(elements()->At(i)); - } - return new (OZ) TextNode(elms, compiler->read_backward(), on_success); -} - -static bool CompareInverseRanges(ZoneGrowableArray* ranges, - const int32_t* special_class, - intptr_t length) { - length--; // Remove final kRangeEndMarker. - ASSERT(special_class[length] == kRangeEndMarker); - ASSERT(ranges->length() != 0); - ASSERT(length != 0); - ASSERT(special_class[0] != 0); - if (ranges->length() != (length >> 1) + 1) { - return false; - } - CharacterRange range = ranges->At(0); - if (range.from() != 0) { - return false; - } - for (intptr_t i = 0; i < length; i += 2) { - if (special_class[i] != (range.to() + 1)) { - return false; - } - range = ranges->At((i >> 1) + 1); - if (special_class[i + 1] != range.from()) { - return false; - } - } - if (range.to() != Utf::kMaxCodePoint) { - return false; - } - return true; -} - -static bool CompareRanges(ZoneGrowableArray* ranges, - const int32_t* special_class, - intptr_t length) { - length--; // Remove final kRangeEndMarker. - ASSERT(special_class[length] == kRangeEndMarker); - if (ranges->length() * 2 != length) { - return false; - } - for (intptr_t i = 0; i < length; i += 2) { - CharacterRange range = ranges->At(i >> 1); - if (range.from() != special_class[i] || - range.to() != special_class[i + 1] - 1) { - return false; - } - } - return true; -} - -bool RegExpCharacterClass::is_standard() { - // TODO(lrn): Remove need for this function, by not throwing away information - // along the way. - if (is_negated()) { - return false; - } - if (set_.is_standard()) { - return true; - } - if (CompareRanges(set_.ranges(), kSpaceRanges, kSpaceRangeCount)) { - set_.set_standard_set_type('s'); - return true; - } - if (CompareInverseRanges(set_.ranges(), kSpaceRanges, kSpaceRangeCount)) { - set_.set_standard_set_type('S'); - return true; - } - if (CompareInverseRanges(set_.ranges(), kLineTerminatorRanges, - kLineTerminatorRangeCount)) { - set_.set_standard_set_type('.'); - return true; - } - if (CompareRanges(set_.ranges(), kLineTerminatorRanges, - kLineTerminatorRangeCount)) { - set_.set_standard_set_type('n'); - return true; - } - if (CompareRanges(set_.ranges(), kWordRanges, kWordRangeCount)) { - set_.set_standard_set_type('w'); - return true; - } - if (CompareInverseRanges(set_.ranges(), kWordRanges, kWordRangeCount)) { - set_.set_standard_set_type('W'); - return true; - } - return false; -} - -UnicodeRangeSplitter::UnicodeRangeSplitter( - Zone* zone, - ZoneGrowableArray* base) - : zone_(zone), - table_(zone), - bmp_(nullptr), - lead_surrogates_(nullptr), - trail_surrogates_(nullptr), - non_bmp_(nullptr) { - // The unicode range splitter categorizes given character ranges into: - // - Code points from the BMP representable by one code unit. - // - Code points outside the BMP that need to be split into surrogate pairs. - // - Lone lead surrogates. - // - Lone trail surrogates. - // Lone surrogates are valid code points, even though no actual characters. - // They require special matching to make sure we do not split surrogate pairs. - // We use the dispatch table to accomplish this. The base range is split up - // by the table by the overlay ranges, and the Call callback is used to - // filter and collect ranges for each category. - for (intptr_t i = 0; i < base->length(); i++) { - table_.AddRange(base->At(i), kBase, zone_); - } - // Add overlay ranges. - table_.AddRange(CharacterRange::Range(0, Utf16::kLeadSurrogateStart - 1), - kBmpCodePoints, zone_); - table_.AddRange(CharacterRange::Range(Utf16::kLeadSurrogateStart, - Utf16::kLeadSurrogateEnd), - kLeadSurrogates, zone_); - table_.AddRange(CharacterRange::Range(Utf16::kTrailSurrogateStart, - Utf16::kTrailSurrogateEnd), - kTrailSurrogates, zone_); - table_.AddRange( - CharacterRange::Range(Utf16::kTrailSurrogateEnd + 1, Utf16::kMaxCodeUnit), - kBmpCodePoints, zone_); - table_.AddRange( - CharacterRange::Range(Utf16::kMaxCodeUnit + 1, Utf::kMaxCodePoint), - kNonBmpCodePoints, zone_); - table_.ForEach(this); -} - -void UnicodeRangeSplitter::Call(uint32_t from, ChoiceTable::Entry entry) { - OutSet* outset = entry.out_set(); - if (!outset->Get(kBase)) return; - ZoneGrowableArray** target = nullptr; - if (outset->Get(kBmpCodePoints)) { - target = &bmp_; - } else if (outset->Get(kLeadSurrogates)) { - target = &lead_surrogates_; - } else if (outset->Get(kTrailSurrogates)) { - target = &trail_surrogates_; - } else { - ASSERT(outset->Get(kNonBmpCodePoints)); - target = &non_bmp_; - } - if (*target == nullptr) { - *target = new (zone_) ZoneGrowableArray(2); - } - (*target)->Add(CharacterRange::Range(entry.from(), entry.to())); -} - -void AddBmpCharacters(RegExpCompiler* compiler, - ChoiceNode* result, - RegExpNode* on_success, - UnicodeRangeSplitter* splitter) { - ZoneGrowableArray* bmp = splitter->bmp(); - if (bmp == nullptr) return; - result->AddAlternative(GuardedAlternative(TextNode::CreateForCharacterRanges( - bmp, compiler->read_backward(), on_success, RegExpFlags()))); -} - -void AddNonBmpSurrogatePairs(RegExpCompiler* compiler, - ChoiceNode* result, - RegExpNode* on_success, - UnicodeRangeSplitter* splitter) { - ZoneGrowableArray* non_bmp = splitter->non_bmp(); - if (non_bmp == nullptr) return; - ASSERT(!compiler->one_byte()); - CharacterRange::Canonicalize(non_bmp); - for (int i = 0; i < non_bmp->length(); i++) { - // Match surrogate pair. - // E.g. [\u10005-\u11005] becomes - // \ud800[\udc05-\udfff]| - // [\ud801-\ud803][\udc00-\udfff]| - // \ud804[\udc00-\udc05] - uint32_t from = non_bmp->At(i).from(); - uint32_t to = non_bmp->At(i).to(); - uint16_t from_points[2]; - Utf16::Encode(from, from_points); - uint16_t to_points[2]; - Utf16::Encode(to, to_points); - if (from_points[0] == to_points[0]) { - // The lead surrogate is the same. - result->AddAlternative( - GuardedAlternative(TextNode::CreateForSurrogatePair( - CharacterRange::Singleton(from_points[0]), - CharacterRange::Range(from_points[1], to_points[1]), - compiler->read_backward(), on_success, RegExpFlags()))); - } else { - if (from_points[1] != Utf16::kTrailSurrogateStart) { - // Add [from_l][from_t-\udfff] - result->AddAlternative( - GuardedAlternative(TextNode::CreateForSurrogatePair( - CharacterRange::Singleton(from_points[0]), - CharacterRange::Range(from_points[1], - Utf16::kTrailSurrogateEnd), - compiler->read_backward(), on_success, RegExpFlags()))); - from_points[0]++; - } - if (to_points[1] != Utf16::kTrailSurrogateEnd) { - // Add [to_l][\udc00-to_t] - result->AddAlternative( - GuardedAlternative(TextNode::CreateForSurrogatePair( - CharacterRange::Singleton(to_points[0]), - CharacterRange::Range(Utf16::kTrailSurrogateStart, - to_points[1]), - compiler->read_backward(), on_success, RegExpFlags()))); - to_points[0]--; - } - if (from_points[0] <= to_points[0]) { - // Add [from_l-to_l][\udc00-\udfff] - result->AddAlternative( - GuardedAlternative(TextNode::CreateForSurrogatePair( - CharacterRange::Range(from_points[0], to_points[0]), - CharacterRange::Range(Utf16::kTrailSurrogateStart, - Utf16::kTrailSurrogateEnd), - compiler->read_backward(), on_success, RegExpFlags()))); - } - } - } -} - -RegExpNode* NegativeLookaroundAgainstReadDirectionAndMatch( - RegExpCompiler* compiler, - ZoneGrowableArray* lookbehind, - ZoneGrowableArray* match, - RegExpNode* on_success, - bool read_backward, - RegExpFlags flags) { - RegExpNode* match_node = TextNode::CreateForCharacterRanges( - match, read_backward, on_success, flags); - int stack_register = compiler->UnicodeLookaroundStackRegister(); - int position_register = compiler->UnicodeLookaroundPositionRegister(); - RegExpLookaround::Builder lookaround(false, match_node, stack_register, - position_register); - RegExpNode* negative_match = TextNode::CreateForCharacterRanges( - lookbehind, !read_backward, lookaround.on_match_success(), flags); - return lookaround.ForMatch(negative_match); -} - -RegExpNode* MatchAndNegativeLookaroundInReadDirection( - RegExpCompiler* compiler, - ZoneGrowableArray* match, - ZoneGrowableArray* lookahead, - RegExpNode* on_success, - bool read_backward, - RegExpFlags flags) { - int stack_register = compiler->UnicodeLookaroundStackRegister(); - int position_register = compiler->UnicodeLookaroundPositionRegister(); - RegExpLookaround::Builder lookaround(false, on_success, stack_register, - position_register); - RegExpNode* negative_match = TextNode::CreateForCharacterRanges( - lookahead, read_backward, lookaround.on_match_success(), flags); - return TextNode::CreateForCharacterRanges( - match, read_backward, lookaround.ForMatch(negative_match), flags); -} - -void AddLoneLeadSurrogates(RegExpCompiler* compiler, - ChoiceNode* result, - RegExpNode* on_success, - UnicodeRangeSplitter* splitter) { - auto lead_surrogates = splitter->lead_surrogates(); - if (lead_surrogates == nullptr) return; - // E.g. \ud801 becomes \ud801(?![\udc00-\udfff]). - auto trail_surrogates = CharacterRange::List( - on_success->zone(), CharacterRange::Range(Utf16::kTrailSurrogateStart, - Utf16::kTrailSurrogateEnd)); - - RegExpNode* match; - if (compiler->read_backward()) { - // Reading backward. Assert that reading forward, there is no trail - // surrogate, and then backward match the lead surrogate. - match = NegativeLookaroundAgainstReadDirectionAndMatch( - compiler, trail_surrogates, lead_surrogates, on_success, true, - RegExpFlags()); - } else { - // Reading forward. Forward match the lead surrogate and assert that - // no trail surrogate follows. - match = MatchAndNegativeLookaroundInReadDirection( - compiler, lead_surrogates, trail_surrogates, on_success, false, - RegExpFlags()); - } - result->AddAlternative(GuardedAlternative(match)); -} - -void AddLoneTrailSurrogates(RegExpCompiler* compiler, - ChoiceNode* result, - RegExpNode* on_success, - UnicodeRangeSplitter* splitter) { - auto trail_surrogates = splitter->trail_surrogates(); - if (trail_surrogates == nullptr) return; - // E.g. \udc01 becomes (?zone(), CharacterRange::Range(Utf16::kLeadSurrogateStart, - Utf16::kLeadSurrogateEnd)); - - RegExpNode* match; - if (compiler->read_backward()) { - // Reading backward. Backward match the trail surrogate and assert that no - // lead surrogate precedes it. - match = MatchAndNegativeLookaroundInReadDirection( - compiler, trail_surrogates, lead_surrogates, on_success, true, - RegExpFlags()); - } else { - // Reading forward. Assert that reading backward, there is no lead - // surrogate, and then forward match the trail surrogate. - match = NegativeLookaroundAgainstReadDirectionAndMatch( - compiler, lead_surrogates, trail_surrogates, on_success, false, - RegExpFlags()); - } - result->AddAlternative(GuardedAlternative(match)); -} - -RegExpNode* UnanchoredAdvance(RegExpCompiler* compiler, - RegExpNode* on_success) { - // This implements ES2015 21.2.5.2.3, AdvanceStringIndex. - ASSERT(!compiler->read_backward()); - // Advance any character. If the character happens to be a lead surrogate and - // we advanced into the middle of a surrogate pair, it will work out, as - // nothing will match from there. We will have to advance again, consuming - // the associated trail surrogate. - auto range = CharacterRange::List( - on_success->zone(), CharacterRange::Range(0, Utf16::kMaxCodeUnit)); - return TextNode::CreateForCharacterRanges(range, false, on_success, - RegExpFlags()); -} - -void AddUnicodeCaseEquivalents(ZoneGrowableArray* ranges) { - ASSERT(CharacterRange::IsCanonical(ranges)); - - // Micro-optimization to avoid passing large ranges to UnicodeSet::closeOver. - // See also https://crbug.com/v8/6727. - // TODO(sstrickl): This only covers the special case of the {0,0x10FFFF} - // range, which we use frequently internally. But large ranges can also easily - // be created by the user. We might want to have a more general caching - // mechanism for such ranges. - if (ranges->length() == 1 && ranges->At(0).IsEverything(Utf::kMaxCodePoint)) { - return; - } - - icu::UnicodeSet set; - for (int i = 0; i < ranges->length(); i++) { - set.add(ranges->At(i).from(), ranges->At(i).to()); - } - ranges->Clear(); - set.closeOver(USET_CASE_INSENSITIVE); - // Full case mapping map single characters to multiple characters. - // Those are represented as strings in the set. Remove them so that - // we end up with only simple and common case mappings. - set.removeAllStrings(); - for (int i = 0; i < set.getRangeCount(); i++) { - ranges->Add( - CharacterRange::Range(set.getRangeStart(i), set.getRangeEnd(i))); - } - // No errors and everything we collected have been ranges. - CharacterRange::Canonicalize(ranges); -} - -RegExpNode* RegExpCharacterClass::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - set_.Canonicalize(); - ZoneGrowableArray* ranges = this->ranges(); - if (flags_.NeedsUnicodeCaseEquivalents()) { - AddUnicodeCaseEquivalents(ranges); - } - if (flags_.IsUnicode() && !compiler->one_byte() && - !contains_split_surrogate()) { - if (is_negated()) { - ZoneGrowableArray* negated = - new ZoneGrowableArray(2); - CharacterRange::Negate(ranges, negated); - ranges = negated; - } - if (ranges->length() == 0) { - RegExpCharacterClass* fail = - new RegExpCharacterClass(ranges, RegExpFlags()); - return new TextNode(fail, compiler->read_backward(), on_success); - } - if (standard_type() == '*') { - return UnanchoredAdvance(compiler, on_success); - } else { - ChoiceNode* result = new (OZ) ChoiceNode(2, OZ); - UnicodeRangeSplitter splitter(OZ, ranges); - AddBmpCharacters(compiler, result, on_success, &splitter); - AddNonBmpSurrogatePairs(compiler, result, on_success, &splitter); - AddLoneLeadSurrogates(compiler, result, on_success, &splitter); - AddLoneTrailSurrogates(compiler, result, on_success, &splitter); - return result; - } - } else { - return new TextNode(this, compiler->read_backward(), on_success); - } - return new (OZ) TextNode(this, compiler->read_backward(), on_success); -} - -RegExpNode* RegExpDisjunction::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - ZoneGrowableArray* alternatives = this->alternatives(); - intptr_t length = alternatives->length(); - ChoiceNode* result = new (OZ) ChoiceNode(length, OZ); - for (intptr_t i = 0; i < length; i++) { - GuardedAlternative alternative( - alternatives->At(i)->ToNode(compiler, on_success)); - result->AddAlternative(alternative); - } - return result; -} - -RegExpNode* RegExpQuantifier::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - return ToNode(min(), max(), is_greedy(), body(), compiler, on_success); -} - -// Scoped object to keep track of how much we unroll quantifier loops in the -// regexp graph generator. -class RegExpExpansionLimiter : public ValueObject { - public: - static constexpr intptr_t kMaxExpansionFactor = 6; - RegExpExpansionLimiter(RegExpCompiler* compiler, intptr_t factor) - : compiler_(compiler), - saved_expansion_factor_(compiler->current_expansion_factor()), - ok_to_expand_(saved_expansion_factor_ <= kMaxExpansionFactor) { - ASSERT(factor > 0); - if (ok_to_expand_) { - if (factor > kMaxExpansionFactor) { - // Avoid integer overflow of the current expansion factor. - ok_to_expand_ = false; - compiler->set_current_expansion_factor(kMaxExpansionFactor + 1); - } else { - intptr_t new_factor = saved_expansion_factor_ * factor; - ok_to_expand_ = (new_factor <= kMaxExpansionFactor); - compiler->set_current_expansion_factor(new_factor); - } - } - } - - ~RegExpExpansionLimiter() { - compiler_->set_current_expansion_factor(saved_expansion_factor_); - } - - bool ok_to_expand() { return ok_to_expand_; } - - private: - RegExpCompiler* compiler_; - intptr_t saved_expansion_factor_; - bool ok_to_expand_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpExpansionLimiter); -}; - -RegExpNode* RegExpQuantifier::ToNode(intptr_t min, - intptr_t max, - bool is_greedy, - RegExpTree* body, - RegExpCompiler* compiler, - RegExpNode* on_success, - bool not_at_start) { - // x{f, t} becomes this: - // - // (r++)<-. - // | ` - // | (x) - // v ^ - // (r=0)-->(?)---/ [if r < t] - // | - // [if r >= f] \----> ... - // - - // 15.10.2.5 RepeatMatcher algorithm. - // The parser has already eliminated the case where max is 0. In the case - // where max_match is zero the parser has removed the quantifier if min was - // > 0 and removed the atom if min was 0. See AddQuantifierToAtom. - - // If we know that we cannot match zero length then things are a little - // simpler since we don't need to make the special zero length match check - // from step 2.1. If the min and max are small we can unroll a little in - // this case. - // Unroll (foo)+ and (foo){3,} - const intptr_t kMaxUnrolledMinMatches = 3; - // Unroll (foo)? and (foo){x,3} - const intptr_t kMaxUnrolledMaxMatches = 3; - if (max == 0) return on_success; // This can happen due to recursion. - bool body_can_be_empty = (body->min_match() == 0); - intptr_t body_start_reg = RegExpCompiler::kNoRegister; - Interval capture_registers = body->CaptureRegisters(); - bool needs_capture_clearing = !capture_registers.is_empty(); - Zone* zone = compiler->zone(); - - if (body_can_be_empty) { - body_start_reg = compiler->AllocateRegister(); - } else if (kRegexpOptimization && !needs_capture_clearing) { - // Only unroll if there are no captures and the body can't be - // empty. - { - RegExpExpansionLimiter limiter(compiler, min + ((max != min) ? 1 : 0)); - if (min > 0 && min <= kMaxUnrolledMinMatches && limiter.ok_to_expand()) { - intptr_t new_max = (max == kInfinity) ? max : max - min; - // Recurse once to get the loop or optional matches after the fixed - // ones. - RegExpNode* answer = - ToNode(0, new_max, is_greedy, body, compiler, on_success, true); - // Unroll the forced matches from 0 to min. This can cause chains of - // TextNodes (which the parser does not generate). These should be - // combined if it turns out they hinder good code generation. - for (intptr_t i = 0; i < min; i++) { - answer = body->ToNode(compiler, answer); - } - return answer; - } - } - if (max <= kMaxUnrolledMaxMatches && min == 0) { - ASSERT(max > 0); // Due to the 'if' above. - RegExpExpansionLimiter limiter(compiler, max); - if (limiter.ok_to_expand()) { - // Unroll the optional matches up to max. - RegExpNode* answer = on_success; - for (intptr_t i = 0; i < max; i++) { - ChoiceNode* alternation = new (zone) ChoiceNode(2, zone); - if (is_greedy) { - alternation->AddAlternative( - GuardedAlternative(body->ToNode(compiler, answer))); - alternation->AddAlternative(GuardedAlternative(on_success)); - } else { - alternation->AddAlternative(GuardedAlternative(on_success)); - alternation->AddAlternative( - GuardedAlternative(body->ToNode(compiler, answer))); - } - answer = alternation; - if (not_at_start && !compiler->read_backward()) { - alternation->set_not_at_start(); - } - } - return answer; - } - } - } - bool has_min = min > 0; - bool has_max = max < RegExpTree::kInfinity; - bool needs_counter = has_min || has_max; - intptr_t reg_ctr = needs_counter ? compiler->AllocateRegister() - : RegExpCompiler::kNoRegister; - LoopChoiceNode* center = new (zone) - LoopChoiceNode(body->min_match() == 0, compiler->read_backward(), zone); - if (not_at_start && !compiler->read_backward()) center->set_not_at_start(); - RegExpNode* loop_return = - needs_counter ? static_cast( - ActionNode::IncrementRegister(reg_ctr, center)) - : static_cast(center); - if (body_can_be_empty) { - // If the body can be empty we need to check if it was and then - // backtrack. - loop_return = - ActionNode::EmptyMatchCheck(body_start_reg, reg_ctr, min, loop_return); - } - RegExpNode* body_node = body->ToNode(compiler, loop_return); - if (body_can_be_empty) { - // If the body can be empty we need to store the start position - // so we can bail out if it was empty. - body_node = ActionNode::StorePosition(body_start_reg, false, body_node); - } - if (needs_capture_clearing) { - // Before entering the body of this loop we need to clear captures. - body_node = ActionNode::ClearCaptures(capture_registers, body_node); - } - GuardedAlternative body_alt(body_node); - if (has_max) { - Guard* body_guard = new (zone) Guard(reg_ctr, Guard::LT, max); - body_alt.AddGuard(body_guard, zone); - } - GuardedAlternative rest_alt(on_success); - if (has_min) { - Guard* rest_guard = new (zone) Guard(reg_ctr, Guard::GEQ, min); - rest_alt.AddGuard(rest_guard, zone); - } - if (is_greedy) { - center->AddLoopAlternative(body_alt); - center->AddContinueAlternative(rest_alt); - } else { - center->AddContinueAlternative(rest_alt); - center->AddLoopAlternative(body_alt); - } - if (needs_counter) { - return ActionNode::SetRegister(reg_ctr, 0, center); - } else { - return center; - } +// Irregexp implementation. + +// Ensures that the regexp object contains a compiled version of the +// source for either one-byte or two-byte subject strings. +// If the compiled version doesn't already exist, it is compiled +// from the source pattern. +// If compilation fails, an exception is thrown and this function +// returns false. +bool RegExpImpl::EnsureCompiledIrregexp(Thread* thread, + const RegExp& re_data, + const String& sample_subject, + bool is_one_byte, + bool sticky) { + if (re_data.has_bytecode(is_one_byte, sticky)) return true; + + return CompileIrregexpFromSource(thread, re_data, sample_subject, is_one_byte, + sticky, RegExpCompilationTarget::kBytecode); } namespace { -// Desugar \b to (?<=\w)(?=\W)|(?<=\W)(?=\w) and -// \B to (?<=\w)(?=\w)|(?<=\W)(?=\W) -RegExpNode* BoundaryAssertionAsLookaround(RegExpCompiler* compiler, - RegExpNode* on_success, - RegExpAssertion::AssertionType type, - RegExpFlags flags) { - ASSERT(flags.NeedsUnicodeCaseEquivalents()); - ZoneGrowableArray* word_range = - new ZoneGrowableArray(2); - CharacterRange::AddClassEscape('w', word_range, true); - int stack_register = compiler->UnicodeLookaroundStackRegister(); - int position_register = compiler->UnicodeLookaroundPositionRegister(); - ChoiceNode* result = new (OZ) ChoiceNode(2, OZ); - // Add two choices. The (non-)boundary could start with a word or - // a non-word-character. - for (int i = 0; i < 2; i++) { - bool lookbehind_for_word = i == 0; - bool lookahead_for_word = - (type == RegExpAssertion::BOUNDARY) ^ lookbehind_for_word; - // Look to the left. - RegExpLookaround::Builder lookbehind(lookbehind_for_word, on_success, - stack_register, position_register); - RegExpNode* backward = TextNode::CreateForCharacterRanges( - word_range, true, lookbehind.on_match_success(), flags); - // Look to the right. - RegExpLookaround::Builder lookahead(lookahead_for_word, - lookbehind.ForMatch(backward), - stack_register, position_register); - RegExpNode* forward = TextNode::CreateForCharacterRanges( - word_range, false, lookahead.on_match_success(), flags); - result->AddAlternative(GuardedAlternative(lookahead.ForMatch(forward))); + +struct RegExpCaptureIndexLess { + bool operator()(const RegExpCapture* lhs, const RegExpCapture* rhs) const { + DCHECK_NOT_NULL(lhs); + DCHECK_NOT_NULL(rhs); + return lhs->index() < rhs->index(); } - return result; -} -} // anonymous namespace +}; -RegExpNode* RegExpAssertion::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - switch (assertion_type()) { - case START_OF_LINE: - return AssertionNode::AfterNewline(on_success); - case START_OF_INPUT: - return AssertionNode::AtStart(on_success); - case BOUNDARY: - return flags_.NeedsUnicodeCaseEquivalents() - ? BoundaryAssertionAsLookaround(compiler, on_success, BOUNDARY, - flags_) - : AssertionNode::AtBoundary(on_success); - case NON_BOUNDARY: - return flags_.NeedsUnicodeCaseEquivalents() - ? BoundaryAssertionAsLookaround(compiler, on_success, - NON_BOUNDARY, flags_) - : AssertionNode::AtNonBoundary(on_success); - case END_OF_INPUT: - return AssertionNode::AtEnd(on_success); - case END_OF_LINE: { - // Compile $ in multiline regexps as an alternation with a positive - // lookahead in one side and an end-of-input on the other side. - // We need two registers for the lookahead. - intptr_t stack_pointer_register = compiler->AllocateRegister(); - intptr_t position_register = compiler->AllocateRegister(); - // The ChoiceNode to distinguish between a newline and end-of-input. - ChoiceNode* result = new ChoiceNode(2, on_success->zone()); - // Create a newline atom. - ZoneGrowableArray* newline_ranges = - new ZoneGrowableArray(3); - CharacterRange::AddClassEscape('n', newline_ranges); - RegExpCharacterClass* newline_atom = - new RegExpCharacterClass('n', RegExpFlags()); - TextNode* newline_matcher = - new TextNode(newline_atom, /*read_backward=*/false, - ActionNode::PositiveSubmatchSuccess( - stack_pointer_register, position_register, - 0, // No captures inside. - -1, // Ignored if no captures. - on_success)); - // Create an end-of-input matcher. - RegExpNode* end_of_line = ActionNode::BeginSubmatch( - stack_pointer_register, position_register, newline_matcher); - // Add the two alternatives to the ChoiceNode. - GuardedAlternative eol_alternative(end_of_line); - result->AddAlternative(eol_alternative); - GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success)); - result->AddAlternative(end_alternative); - return result; - } - default: - UNREACHABLE(); +} // namespace + +// static +ArrayPtr RegExpStatics::CreateCaptureNameMap( + Isolate* isolate, + ZoneVector* named_captures) { + if (named_captures == nullptr) return Array::null(); + + ASSERT(!named_captures->empty()); + + // Named captures are sorted by name (because the set is used to ensure + // name uniqueness). But the capture name map must to be sorted by index. + + std::sort(named_captures->begin(), named_captures->end(), + RegExpCaptureIndexLess{}); + + int len = static_cast(named_captures->size()) * 2; + const Array& array = Array::Handle(Array::New(len)); + + int i = 0; + for (const RegExpCapture* capture : *named_captures) { + const String& name = String::Handle( + String::FromUTF16(capture->name()->data(), capture->name()->size())); + array.SetAt(i * 2, name); + array.SetAt(i * 2 + 1, Smi::Handle(Smi::New(capture->index()))); + i++; } - return on_success; + DCHECK_EQ(i * 2, len); + + return array.ptr(); } -RegExpNode* RegExpBackReference::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - return new (OZ) BackReferenceNode(RegExpCapture::StartRegister(index()), - RegExpCapture::EndRegister(index()), flags_, - compiler->read_backward(), on_success); -} - -RegExpNode* RegExpEmpty::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - return on_success; -} - -RegExpLookaround::Builder::Builder(bool is_positive, - RegExpNode* on_success, - intptr_t stack_pointer_register, - intptr_t position_register, - intptr_t capture_register_count, - intptr_t capture_register_start) - : is_positive_(is_positive), - on_success_(on_success), - stack_pointer_register_(stack_pointer_register), - position_register_(position_register) { - if (is_positive_) { - on_match_success_ = ActionNode::PositiveSubmatchSuccess( - stack_pointer_register, position_register, capture_register_count, - capture_register_start, on_success); - } else { - on_match_success_ = new (OZ) NegativeSubmatchSuccess( - stack_pointer_register, position_register, capture_register_count, - capture_register_start, OZ); - } -} - -RegExpNode* RegExpLookaround::Builder::ForMatch(RegExpNode* match) { - if (is_positive_) { - return ActionNode::BeginSubmatch(stack_pointer_register_, - position_register_, match); - } else { - Zone* zone = on_success_->zone(); - // We use a ChoiceNode to represent the negative lookaround. The first - // alternative is the negative match. On success, the end node backtracks. - // On failure, the second alternative is tried and leads to success. - // NegativeLookaroundChoiceNode is a special ChoiceNode that ignores the - // first exit when calculating quick checks. - ChoiceNode* choice_node = new (zone) NegativeLookaroundChoiceNode( - GuardedAlternative(match), GuardedAlternative(on_success_), zone); - return ActionNode::BeginSubmatch(stack_pointer_register_, - position_register_, choice_node); - } -} - -RegExpNode* RegExpLookaround::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - intptr_t stack_pointer_register = compiler->AllocateRegister(); - intptr_t position_register = compiler->AllocateRegister(); - - const intptr_t registers_per_capture = 2; - const intptr_t register_of_first_capture = 2; - intptr_t register_count = capture_count_ * registers_per_capture; - intptr_t register_start = - register_of_first_capture + capture_from_ * registers_per_capture; - - RegExpNode* result; - bool was_reading_backward = compiler->read_backward(); - compiler->set_read_backward(type() == LOOKBEHIND); - Builder builder(is_positive(), on_success, stack_pointer_register, - position_register, register_count, register_start); - RegExpNode* match = body_->ToNode(compiler, builder.on_match_success()); - result = builder.ForMatch(match); - compiler->set_read_backward(was_reading_backward); - return result; -} - -RegExpNode* RegExpCapture::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - return ToNode(body(), index(), compiler, on_success); -} - -RegExpNode* RegExpCapture::ToNode(RegExpTree* body, - intptr_t index, - RegExpCompiler* compiler, - RegExpNode* on_success) { - ASSERT(body != nullptr); - intptr_t start_reg = RegExpCapture::StartRegister(index); - intptr_t end_reg = RegExpCapture::EndRegister(index); - if (compiler->read_backward()) { - intptr_t tmp = end_reg; - end_reg = start_reg; - start_reg = tmp; - } - RegExpNode* store_end = ActionNode::StorePosition(end_reg, true, on_success); - RegExpNode* body_node = body->ToNode(compiler, store_end); - return ActionNode::StorePosition(start_reg, true, body_node); -} - -RegExpNode* RegExpAlternative::ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) { - ZoneGrowableArray* children = nodes(); - RegExpNode* current = on_success; - if (compiler->read_backward()) { - for (intptr_t i = 0; i < children->length(); i++) { - current = children->At(i)->ToNode(compiler, current); - } - } else { - for (intptr_t i = children->length() - 1; i >= 0; i--) { - current = children->At(i)->ToNode(compiler, current); - } - } - return current; -} - -static void AddClass(const int32_t* elmv, - intptr_t elmc, - ZoneGrowableArray* ranges) { - elmc--; - ASSERT(elmv[elmc] == kRangeEndMarker); - for (intptr_t i = 0; i < elmc; i += 2) { - ASSERT(elmv[i] < elmv[i + 1]); - ranges->Add(CharacterRange(elmv[i], elmv[i + 1] - 1)); - } -} - -static void AddClassNegated(const int32_t* elmv, - intptr_t elmc, - ZoneGrowableArray* ranges) { - elmc--; - ASSERT(elmv[elmc] == kRangeEndMarker); - ASSERT(elmv[0] != 0x0000); - ASSERT(elmv[elmc - 1] != Utf::kMaxCodePoint); - uint16_t last = 0x0000; - for (intptr_t i = 0; i < elmc; i += 2) { - ASSERT(last <= elmv[i] - 1); - ASSERT(elmv[i] < elmv[i + 1]); - ranges->Add(CharacterRange(last, elmv[i] - 1)); - last = elmv[i + 1]; - } - ranges->Add(CharacterRange(last, Utf::kMaxCodePoint)); -} - -void CharacterRange::AddClassEscape(uint16_t type, - ZoneGrowableArray* ranges, - bool add_unicode_case_equivalents) { - if (add_unicode_case_equivalents && (type == 'w' || type == 'W')) { - // See #sec-runtime-semantics-wordcharacters-abstract-operation - // In case of unicode and ignore_case, we need to create the closure over - // case equivalent characters before negating. - ZoneGrowableArray* new_ranges = - new ZoneGrowableArray(2); - AddClass(kWordRanges, kWordRangeCount, new_ranges); - AddUnicodeCaseEquivalents(new_ranges); - if (type == 'W') { - ZoneGrowableArray* negated = - new ZoneGrowableArray(2); - CharacterRange::Negate(new_ranges, negated); - new_ranges = negated; - } - ranges->AddArray(*new_ranges); - return; - } - AddClassEscape(type, ranges); -} - -void CharacterRange::AddClassEscape(uint16_t type, - ZoneGrowableArray* ranges) { - switch (type) { - case 's': - AddClass(kSpaceRanges, kSpaceRangeCount, ranges); - break; - case 'S': - AddClassNegated(kSpaceRanges, kSpaceRangeCount, ranges); - break; - case 'w': - AddClass(kWordRanges, kWordRangeCount, ranges); - break; - case 'W': - AddClassNegated(kWordRanges, kWordRangeCount, ranges); - break; - case 'd': - AddClass(kDigitRanges, kDigitRangeCount, ranges); - break; - case 'D': - AddClassNegated(kDigitRanges, kDigitRangeCount, ranges); - break; - case '.': - AddClassNegated(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges); - break; - // This is not a character range as defined by the spec but a - // convenient shorthand for a character class that matches any - // character. - case '*': - ranges->Add(CharacterRange::Everything()); - break; - // This is the set of characters matched by the $ and ^ symbols - // in multiline mode. - case 'n': - AddClass(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges); - break; - default: - UNREACHABLE(); - } -} - -void CharacterRange::AddCaseEquivalents( - ZoneGrowableArray* ranges, +bool RegExpImpl::CompileIrregexpFromSource( + Thread* thread, + const RegExp& re_data, + const String& sample_subject, bool is_one_byte, - Zone* zone) { - CharacterRange::Canonicalize(ranges); - int range_count = ranges->length(); - for (intptr_t i = 0; i < range_count; i++) { - CharacterRange range = ranges->At(i); - int32_t bottom = range.from(); - if (bottom > Utf16::kMaxCodeUnit) continue; - int32_t top = Utils::Minimum(range.to(), Utf16::kMaxCodeUnit); - // Nothing to be done for surrogates - if (bottom >= Utf16::kLeadSurrogateStart && - top <= Utf16::kTrailSurrogateEnd) { - continue; - } - if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { - if (bottom > Symbols::kMaxOneCharCodeSymbol) continue; - if (top > Symbols::kMaxOneCharCodeSymbol) { - top = Symbols::kMaxOneCharCodeSymbol; - } - } - - unibrow::Mapping jsregexp_uncanonicalize; - unibrow::Mapping jsregexp_canonrange; - int32_t chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - if (top == bottom) { - // If this is a singleton we just expand the one character. - intptr_t length = jsregexp_uncanonicalize.get(bottom, '\0', chars); - for (intptr_t i = 0; i < length; i++) { - int32_t chr = chars[i]; - if (chr != bottom) { - ranges->Add(CharacterRange::Singleton(chars[i])); - } - } - } else { - // If this is a range we expand the characters block by block, - // expanding contiguous subranges (blocks) one at a time. - // The approach is as follows. For a given start character we - // look up the remainder of the block that contains it (represented - // by the end point), for instance we find 'z' if the character - // is 'c'. A block is characterized by the property - // that all characters uncanonicalize in the same way, except that - // each entry in the result is incremented by the distance from the first - // element. So a-z is a block because 'a' uncanonicalizes to ['a', 'A'] - // and the k'th letter uncanonicalizes to ['a' + k, 'A' + k]. - // Once we've found the end point we look up its uncanonicalization - // and produce a range for each element. For instance for [c-f] - // we look up ['z', 'Z'] and produce [c-f] and [C-F]. We then only - // add a range if it is not already contained in the input, so [c-f] - // will be skipped but [C-F] will be added. If this range is not - // completely contained in a block we do this for all the blocks - // covered by the range (handling characters that is not in a block - // as a "singleton block"). - int32_t range[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - intptr_t pos = bottom; - while (pos <= top) { - intptr_t length = jsregexp_canonrange.get(pos, '\0', range); - int32_t block_end; - if (length == 0) { - block_end = pos; - } else { - ASSERT(length == 1); - block_end = range[0]; - } - intptr_t end = (block_end > top) ? top : block_end; - length = jsregexp_uncanonicalize.get(block_end, '\0', range); - for (intptr_t i = 0; i < length; i++) { - int32_t c = range[i]; - int32_t range_from = c - (block_end - pos); - int32_t range_to = c - (block_end - end); - if (!(bottom <= range_from && range_to <= top)) { - ranges->Add(CharacterRange(range_from, range_to)); - } - } - pos = end + 1; - } - } + bool sticky, + RegExpCompilationTarget compilation_target) { + // Since we can't abort gracefully during compilation, check for sufficient + // stack space (including the additional gap as used for Turbofan + // compilation) here in advance. + if (!OSThread::Current()->HasStackHeadroom()) { + RegExpStatics::ThrowRegExpException(thread->isolate(), re_data, + RegExpError::kAnalysisStackOverflow); + return false; } -} -bool CharacterRange::IsCanonical(ZoneGrowableArray* ranges) { - ASSERT(ranges != nullptr); - intptr_t n = ranges->length(); - if (n <= 1) return true; - intptr_t max = ranges->At(0).to(); - for (intptr_t i = 1; i < n; i++) { - CharacterRange next_range = ranges->At(i); - if (next_range.from() <= max + 1) return false; - max = next_range.to(); + // Compile the RegExp. + // Zone zone(isolate->allocator(), ZONE_NAME); + // PostponeInterruptsScope postpone(isolate); + + // ASSERT(RegExpCodeIsValidForPreCompilation(isolate, re_data, is_one_byte)); + + RegExpFlags flags = re_data.flags(); + if (sticky) { + flags |= RegExpFlag::kSticky; } + Zone* zone = thread->zone(); + const String& pattern = String::Handle(zone, re_data.pattern()); + + RegExpCompileData compile_data; + if (!RegExpParser::ParseRegExpFromHeapString(thread->isolate(), zone, pattern, + flags, &compile_data)) { + // Throw an exception if we fail to parse the pattern. + // THIS SHOULD NOT HAPPEN. We already pre-parsed it successfully once. + USE(RegExpStatics::ThrowRegExpException(thread->isolate(), flags, pattern, + compile_data.error)); + return false; + } + compile_data.compilation_target = compilation_target; + const bool compilation_succeeded = + Compile(thread->isolate(), zone, &compile_data, flags, pattern, + sample_subject, re_data, is_one_byte); + if (!compilation_succeeded) { + ASSERT(compile_data.error != RegExpError::kNone); + RegExpStatics::ThrowRegExpException(thread->isolate(), re_data, + compile_data.error); + return false; + } + + if (compile_data.compilation_target == RegExpCompilationTarget::kNative) { + UNREACHABLE(); + } else { + DCHECK_EQ(compile_data.compilation_target, + RegExpCompilationTarget::kBytecode); + // Store code generated by compiler in bytecode and trampoline to + // interpreter in code. + re_data.set_bytecode(is_one_byte, sticky, + TypedData::Cast(*compile_data.code)); + } + const Array& capture_name_map = + Array::Handle(zone, RegExpStatics::CreateCaptureNameMap( + thread->isolate(), compile_data.named_captures)); + re_data.set_capture_name_map(capture_name_map); + re_data.set_num_registers(is_one_byte, compile_data.register_count); + re_data.set_num_bracket_expressions(compile_data.capture_count); + return true; } -ZoneGrowableArray* CharacterSet::ranges() { - if (ranges_ == nullptr) { - ranges_ = new ZoneGrowableArray(2); - CharacterRange::AddClassEscape(standard_set_type_, ranges_); - } - return ranges_; +namespace { + +void SetBacktrackAndExperimentalFallback(RegExpMacroAssembler* macro_assembler, + const RegExp& re_data) { + uint32_t backtrack_limit = JSRegExp::kNoBacktrackLimit; + macro_assembler->set_backtrack_limit(backtrack_limit); + macro_assembler->set_can_fallback(false); } -// Move a number of elements in a zone array to another position -// in the same array. Handles overlapping source and target areas. -static void MoveRanges(ZoneGrowableArray* list, - intptr_t from, - intptr_t to, - intptr_t count) { - // Ranges are potentially overlapping. - if (from < to) { - for (intptr_t i = count - 1; i >= 0; i--) { - (*list)[to + i] = list->At(from + i); - } - } else { - for (intptr_t i = 0; i < count; i++) { - (*list)[to + i] = list->At(from + i); - } - } -} +} // namespace -static intptr_t InsertRangeInCanonicalList( - ZoneGrowableArray* list, - intptr_t count, - CharacterRange insert) { - // Inserts a range into list[0..count[, which must be sorted - // by from value and non-overlapping and non-adjacent, using at most - // list[0..count] for the result. Returns the number of resulting - // canonicalized ranges. Inserting a range may collapse existing ranges into - // fewer ranges, so the return value can be anything in the range 1..count+1. - int32_t from = insert.from(); - int32_t to = insert.to(); - intptr_t start_pos = 0; - intptr_t end_pos = count; - for (intptr_t i = count - 1; i >= 0; i--) { - CharacterRange current = list->At(i); - if (current.from() > to + 1) { - end_pos = i; - } else if (current.to() + 1 < from) { - start_pos = i + 1; - break; - } - } +namespace { - // Inserted range overlaps, or is adjacent to, ranges at positions - // [start_pos..end_pos[. Ranges before start_pos or at or after end_pos are - // not affected by the insertion. - // If start_pos == end_pos, the range must be inserted before start_pos. - // if start_pos < end_pos, the entire range from start_pos to end_pos - // must be merged with the insert range. +// Returns true if we've either generated too much irregex code within this +// isolate, or the pattern string is too long. +bool TooMuchRegExpCode(Isolate* isolate, const String& pattern) { + // Limit the space regexps take up on the heap. In order to limit this we + // would like to keep track of the amount of regexp code on the heap. This + // is not tracked, however. As a conservative approximation we track the + // total regexp code compiled including code that has subsequently been freed + // and the total executable memory at any point. + // static constexpr size_t kRegExpExecutableMemoryLimit = 16 * MB; + // static constexpr size_t kRegExpCompiledLimit = 1 * MB; - if (start_pos == end_pos) { - // Insert between existing ranges at position start_pos. - if (start_pos < count) { - MoveRanges(list, start_pos, start_pos + 1, count - start_pos); - } - (*list)[start_pos] = insert; - return count + 1; - } - if (start_pos + 1 == end_pos) { - // Replace single existing range at position start_pos. - CharacterRange to_replace = list->At(start_pos); - intptr_t new_from = Utils::Minimum(to_replace.from(), from); - intptr_t new_to = Utils::Maximum(to_replace.to(), to); - (*list)[start_pos] = CharacterRange(new_from, new_to); - return count; - } - // Replace a number of existing ranges from start_pos to end_pos - 1. - // Move the remaining ranges down. - - intptr_t new_from = Utils::Minimum(list->At(start_pos).from(), from); - intptr_t new_to = Utils::Maximum(list->At(end_pos - 1).to(), to); - if (end_pos < count) { - MoveRanges(list, end_pos, start_pos + 1, count - end_pos); - } - (*list)[start_pos] = CharacterRange(new_from, new_to); - return count - (end_pos - start_pos) + 1; -} - -void CharacterSet::Canonicalize() { - // Special/default classes are always considered canonical. The result - // of calling ranges() will be sorted. - if (ranges_ == nullptr) return; - CharacterRange::Canonicalize(ranges_); -} - -void CharacterRange::Canonicalize( - ZoneGrowableArray* character_ranges) { - if (character_ranges->length() <= 1) return; - // Check whether ranges are already canonical (increasing, non-overlapping, - // non-adjacent). - intptr_t n = character_ranges->length(); - intptr_t max = character_ranges->At(0).to(); - intptr_t i = 1; - while (i < n) { - CharacterRange current = character_ranges->At(i); - if (current.from() <= max + 1) { - break; - } - max = current.to(); - i++; - } - // Canonical until the i'th range. If that's all of them, we are done. - if (i == n) return; - - // The ranges at index i and forward are not canonicalized. Make them so by - // doing the equivalent of insertion sort (inserting each into the previous - // list, in order). - // Notice that inserting a range can reduce the number of ranges in the - // result due to combining of adjacent and overlapping ranges. - intptr_t read = i; // Range to insert. - intptr_t num_canonical = i; // Length of canonicalized part of list. - do { - num_canonical = InsertRangeInCanonicalList(character_ranges, num_canonical, - character_ranges->At(read)); - read++; - } while (read < n); - character_ranges->TruncateTo(num_canonical); - - ASSERT(CharacterRange::IsCanonical(character_ranges)); -} - -void CharacterRange::Negate(ZoneGrowableArray* ranges, - ZoneGrowableArray* negated_ranges) { - ASSERT(CharacterRange::IsCanonical(ranges)); - ASSERT(negated_ranges->length() == 0); - intptr_t range_count = ranges->length(); - uint32_t from = 0; - intptr_t i = 0; - if (range_count > 0 && ranges->At(0).from() == 0) { - from = ranges->At(0).to(); - i = 1; - } - while (i < range_count) { - CharacterRange range = ranges->At(i); - negated_ranges->Add(CharacterRange(from + 1, range.from() - 1)); - from = range.to(); - i++; - } - if (from < Utf::kMaxCodePoint) { - negated_ranges->Add(CharacterRange(from + 1, Utf::kMaxCodePoint)); - } -} - -// ------------------------------------------------------------------- -// Splay tree - -// Workaround for the fact that ZoneGrowableArray does not have contains(). -static bool ArrayContains(ZoneGrowableArray* array, unsigned value) { - for (intptr_t i = 0; i < array->length(); i++) { - if (array->At(i) == value) { - return true; - } - } + // Heap* heap = isolate->heap(); + if (pattern.Length() > RegExpStatics::kRegExpTooLargeToOptimize) return true; + // TODO(regexp): Not relevant for Dart if we're only ever doing bytecode? + // return (isolate->total_regexp_code_generated() > kRegExpCompiledLimit && + // heap->CommittedMemoryExecutable() > kRegExpExecutableMemoryLimit); return false; } -OutSet* OutSet::Extend(unsigned value, Zone* zone) { - if (Get(value)) return this; - if (successors() != nullptr) { - for (int i = 0; i < successors()->length(); i++) { - OutSet* successor = successors()->At(i); - if (successor->Get(value)) return successor; - } - } else { - successors_ = new (zone) ZoneGrowableArray(2); - } - OutSet* result = new (zone) OutSet(first_, remaining_); - result->Set(value, zone); - successors()->Add(result); - return result; -} +} // namespace -void OutSet::Set(unsigned value, Zone* zone) { - if (value < kFirstLimit) { - first_ |= (1 << value); - } else { - if (remaining_ == nullptr) - remaining_ = new (zone) ZoneGrowableArray(1); - - bool remaining_contains_value = ArrayContains(remaining_, value); - if (remaining_->is_empty() || !remaining_contains_value) { - remaining_->Add(value); - } - } -} - -bool OutSet::Get(unsigned value) const { - if (value < kFirstLimit) { - return (first_ & (1 << value)) != 0; - } else if (remaining_ == nullptr) { +bool RegExpImpl::Compile(Isolate* isolate, + Zone* zone, + RegExpCompileData* data, + RegExpFlags flags, + const String& pattern, + const String& sample_subject, + const RegExp& re_data, + bool is_one_byte) { + if (JSRegExp::RegistersForCaptureCount(data->capture_count) > + RegExpMacroAssembler::kMaxRegisterCount) { + data->error = RegExpError::kTooLarge; return false; + } + + RegExpCompiler compiler(isolate, zone, data->capture_count, flags, + is_one_byte); +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + const bool needs_graph_printer = v8_flags.print_regexp_graph || + v8_flags.trace_regexp_graph_building || + v8_flags.trace_regexp_compiler; + const bool needs_ast_printer = v8_flags.trace_regexp_graph_building; + std::unique_ptr diagnostics; + if (UNLIKELY(needs_ast_printer || needs_graph_printer)) { + diagnostics = std::make_unique(std::cout, zone); + } + if (UNLIKELY(needs_ast_printer)) { + diagnostics->set_tree_labeller( + std::make_unique>()); + diagnostics->set_ast_printer(std::make_unique( + diagnostics->os(), diagnostics->tree_labeller(), diagnostics->zone())); + } + if (UNLIKELY(needs_graph_printer)) { + diagnostics->set_graph_labeller( + std::make_unique>()); + diagnostics->set_graph_printer(std::make_unique( + std::make_unique(diagnostics->os(), + diagnostics->graph_labeller(), + diagnostics->zone()))); + } + if (UNLIKELY(needs_ast_printer || needs_graph_printer)) { + compiler.set_diagnostics(std::move(diagnostics)); + } +#endif + + if (compiler.optimize()) { + compiler.set_optimize(!TooMuchRegExpCode(isolate, pattern)); + } + + data->node = compiler.PreprocessRegExp(data, is_one_byte); + if (data->error != RegExpError::kNone) { + return false; + } + data->error = AnalyzeRegExp(isolate, is_one_byte, flags, data->node); + if (data->error != RegExpError::kNone) { + return false; + } + +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.print_regexp_graph)) + compiler.diagnostics()->graph_printer()->PrintGraph(data->node); + if (v8_flags.trace_regexp_graph) DotPrinter::DotPrint("Start", data->node); +#endif + + std::unique_ptr macro_assembler; + if (data->compilation_target == RegExpCompilationTarget::kNative) { + UNREACHABLE(); } else { - return ArrayContains(remaining_, value); - } -} - -const int32_t ChoiceTable::Config::kNoKey = Utf::kInvalidChar; - -void ChoiceTable::AddRange(CharacterRange full_range, - int32_t value, - Zone* zone) { - CharacterRange current = full_range; - if (tree()->is_empty()) { - // If this is the first range we just insert into the table. - ZoneSplayTree::Locator loc; - bool inserted = tree()->Insert(current.from(), &loc); - ASSERT(inserted); - USE(inserted); - loc.set_value( - Entry(current.from(), current.to(), empty()->Extend(value, zone))); - return; - } - // First see if there is a range to the left of this one that - // overlaps. - ZoneSplayTree::Locator loc; - if (tree()->FindGreatestLessThan(current.from(), &loc)) { - Entry* entry = &loc.value(); - // If we've found a range that overlaps with this one, and it - // starts strictly to the left of this one, we have to fix it - // because the following code only handles ranges that start on - // or after the start point of the range we're adding. - if (entry->from() < current.from() && entry->to() >= current.from()) { - // Snap the overlapping range in half around the start point of - // the range we're adding. - CharacterRange left = - CharacterRange::Range(entry->from(), current.from() - 1); - CharacterRange right = CharacterRange::Range(current.from(), entry->to()); - // The left part of the overlapping range doesn't overlap. - // Truncate the whole entry to be just the left part. - entry->set_to(left.to()); - // The right part is the one that overlaps. We add this part - // to the map and let the next step deal with merging it with - // the range we're adding. - ZoneSplayTree::Locator loc; - bool inserted = tree()->Insert(right.from(), &loc); - ASSERT(inserted); - USE(inserted); - loc.set_value(Entry(right.from(), right.to(), entry->out_set())); + DCHECK_EQ(data->compilation_target, RegExpCompilationTarget::kBytecode); + // Interpreted regexp implementation. + macro_assembler.reset( + new RegExpBytecodeGenerator(isolate, zone, + is_one_byte ? RegExpMacroAssembler::LATIN1 + : RegExpMacroAssembler::UC16)); +#ifdef V8_ENABLE_REGEXP_DIAGNOSTICS + if (UNLIKELY(v8_flags.trace_regexp_assembler)) { + std::unique_ptr tracer_macro_assembler = + std::make_unique( + std::move(macro_assembler)); + macro_assembler = std::move(tracer_macro_assembler); } - } - while (current.is_valid()) { - if (tree()->FindLeastGreaterThan(current.from(), &loc) && - (loc.value().from() <= current.to()) && - (loc.value().to() >= current.from())) { - Entry* entry = &loc.value(); - // We have overlap. If there is space between the start point of - // the range we're adding and where the overlapping range starts - // then we have to add a range covering just that space. - if (current.from() < entry->from()) { - ZoneSplayTree::Locator ins; - bool inserted = tree()->Insert(current.from(), &ins); - ASSERT(inserted); - USE(inserted); - ins.set_value(Entry(current.from(), entry->from() - 1, - empty()->Extend(value, zone))); - current.set_from(entry->from()); - } - ASSERT(current.from() == entry->from()); - // If the overlapping range extends beyond the one we want to add - // we have to snap the right part off and add it separately. - if (entry->to() > current.to()) { - ZoneSplayTree::Locator ins; - bool inserted = tree()->Insert(current.to() + 1, &ins); - ASSERT(inserted); - USE(inserted); - ins.set_value(Entry(current.to() + 1, entry->to(), entry->out_set())); - entry->set_to(current.to()); - } - ASSERT(entry->to() <= current.to()); - // The overlapping range is now completely contained by the range - // we're adding so we can just update it and move the start point - // of the range we're adding just past it. - entry->AddValue(value, zone); - ASSERT(entry->to() + 1 > current.from()); - current.set_from(entry->to() + 1); - } else { - // There is no overlap so we can just add the range - ZoneSplayTree::Locator ins; - bool inserted = tree()->Insert(current.from(), &ins); - ASSERT(inserted); - USE(inserted); - ins.set_value( - Entry(current.from(), current.to(), empty()->Extend(value, zone))); - break; - } - } -} - -OutSet* ChoiceTable::Get(int32_t value) { - ZoneSplayTree::Locator loc; - if (!tree()->FindGreatestLessThan(value, &loc)) return empty(); - Entry* entry = &loc.value(); - if (value <= entry->to()) - return entry->out_set(); - else - return empty(); -} - -// ------------------------------------------------------------------- -// Analysis - -void Analysis::EnsureAnalyzed(RegExpNode* that) { - if (that->info()->been_analyzed || that->info()->being_analyzed) return; - that->info()->being_analyzed = true; - that->Accept(this); - that->info()->being_analyzed = false; - that->info()->been_analyzed = true; -} - -void Analysis::VisitEnd(EndNode* that) { - // nothing to do -} - -void TextNode::CalculateOffsets() { - intptr_t element_count = elements()->length(); - // Set up the offsets of the elements relative to the start. This is a fixed - // quantity since a TextNode can only contain fixed-width things. - intptr_t cp_offset = 0; - for (intptr_t i = 0; i < element_count; i++) { - TextElement& elm = (*elements())[i]; - elm.set_cp_offset(cp_offset); - cp_offset += elm.length(); - } -} - -void Analysis::VisitText(TextNode* that) { - that->MakeCaseIndependent(is_one_byte_); - EnsureAnalyzed(that->on_success()); - if (!has_failed()) { - that->CalculateOffsets(); - } -} - -void Analysis::VisitAction(ActionNode* that) { - RegExpNode* target = that->on_success(); - EnsureAnalyzed(target); - if (!has_failed()) { - // If the next node is interested in what it follows then this node - // has to be interested too so it can pass the information on. - that->info()->AddFromFollowing(target->info()); - } -} - -void Analysis::VisitChoice(ChoiceNode* that) { - NodeInfo* info = that->info(); - for (intptr_t i = 0; i < that->alternatives()->length(); i++) { - RegExpNode* node = (*that->alternatives())[i].node(); - EnsureAnalyzed(node); - if (has_failed()) return; - // Anything the following nodes need to know has to be known by - // this node also, so it can pass it on. - info->AddFromFollowing(node->info()); - } -} - -void Analysis::VisitLoopChoice(LoopChoiceNode* that) { - NodeInfo* info = that->info(); - for (intptr_t i = 0; i < that->alternatives()->length(); i++) { - RegExpNode* node = (*that->alternatives())[i].node(); - if (node != that->loop_node()) { - EnsureAnalyzed(node); - if (has_failed()) return; - info->AddFromFollowing(node->info()); - } - } - // Check the loop last since it may need the value of this node - // to get a correct result. - EnsureAnalyzed(that->loop_node()); - if (!has_failed()) { - info->AddFromFollowing(that->loop_node()->info()); - } -} - -void Analysis::VisitBackReference(BackReferenceNode* that) { - EnsureAnalyzed(that->on_success()); -} - -void Analysis::VisitAssertion(AssertionNode* that) { - EnsureAnalyzed(that->on_success()); -} - -void BackReferenceNode::FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - // Working out the set of characters that a backreference can match is too - // hard, so we just say that any character can match. - bm->SetRest(offset); - SaveBMInfo(bm, not_at_start, offset); -} - -COMPILE_ASSERT(BoyerMoorePositionInfo::kMapSize == - RegExpMacroAssembler::kTableSize); - -void ChoiceNode::FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - ZoneGrowableArray* alts = alternatives(); - budget = (budget - 1) / alts->length(); - for (intptr_t i = 0; i < alts->length(); i++) { - GuardedAlternative& alt = (*alts)[i]; - if (alt.guards() != nullptr && alt.guards()->length() != 0) { - bm->SetRest(offset); // Give up trying to fill in info. - SaveBMInfo(bm, not_at_start, offset); - return; - } - alt.node()->FillInBMInfo(offset, budget, bm, not_at_start); - } - SaveBMInfo(bm, not_at_start, offset); -} - -void TextNode::FillInBMInfo(intptr_t initial_offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - if (initial_offset >= bm->length()) return; - intptr_t offset = initial_offset; - intptr_t max_char = bm->max_char(); - for (intptr_t i = 0; i < elements()->length(); i++) { - if (offset >= bm->length()) { - if (initial_offset == 0) set_bm_info(not_at_start, bm); - return; - } - TextElement text = elements()->At(i); - if (text.text_type() == TextElement::ATOM) { - RegExpAtom* atom = text.atom(); - for (intptr_t j = 0; j < atom->length(); j++, offset++) { - if (offset >= bm->length()) { - if (initial_offset == 0) set_bm_info(not_at_start, bm); - return; - } - uint16_t character = atom->data()->At(j); - if (atom->flags().IgnoreCase()) { - int32_t chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; - intptr_t length = GetCaseIndependentLetters( - character, bm->max_char() == Symbols::kMaxOneCharCodeSymbol, - chars); - for (intptr_t j = 0; j < length; j++) { - bm->Set(offset, chars[j]); - } - } else { - if (character <= max_char) bm->Set(offset, character); - } - } - } else { - ASSERT(text.text_type() == TextElement::CHAR_CLASS); - RegExpCharacterClass* char_class = text.char_class(); - ZoneGrowableArray* ranges = char_class->ranges(); - if (char_class->is_negated()) { - bm->SetAll(offset); - } else { - for (intptr_t k = 0; k < ranges->length(); k++) { - const CharacterRange& range = ranges->At(k); - if (range.from() > max_char) continue; - intptr_t to = - Utils::Minimum(max_char, static_cast(range.to())); - bm->SetInterval(offset, Interval(range.from(), to)); - } - } - offset++; - } - } - if (offset >= bm->length()) { - if (initial_offset == 0) set_bm_info(not_at_start, bm); - return; - } - on_success()->FillInBMInfo(offset, budget - 1, bm, - true); // Not at start after a text node. - if (initial_offset == 0) set_bm_info(not_at_start, bm); -} - -RegExpNode* OptionallyStepBackToLeadSurrogate(RegExpCompiler* compiler, - RegExpNode* on_success, - RegExpFlags flags) { - // If the regexp matching starts within a surrogate pair, step back - // to the lead surrogate and start matching from there. - ASSERT(!compiler->read_backward()); - Zone* zone = compiler->zone(); - - auto lead_surrogates = CharacterRange::List( - on_success->zone(), CharacterRange::Range(Utf16::kLeadSurrogateStart, - Utf16::kLeadSurrogateEnd)); - auto trail_surrogates = CharacterRange::List( - on_success->zone(), CharacterRange::Range(Utf16::kTrailSurrogateStart, - Utf16::kTrailSurrogateEnd)); - - ChoiceNode* optional_step_back = new (zone) ChoiceNode(2, zone); - - int stack_register = compiler->UnicodeLookaroundStackRegister(); - int position_register = compiler->UnicodeLookaroundPositionRegister(); - RegExpNode* step_back = TextNode::CreateForCharacterRanges( - lead_surrogates, /*read_backward=*/true, on_success, flags); - RegExpLookaround::Builder builder(/*is_positive=*/true, step_back, - stack_register, position_register); - RegExpNode* match_trail = TextNode::CreateForCharacterRanges( - trail_surrogates, /*read_backward=*/false, builder.on_match_success(), - flags); - - optional_step_back->AddAlternative( - GuardedAlternative(builder.ForMatch(match_trail))); - optional_step_back->AddAlternative(GuardedAlternative(on_success)); - - return optional_step_back; -} - -#if !defined(DART_PRECOMPILED_RUNTIME) -RegExpEngine::CompilationResult RegExpEngine::CompileIR( - RegExpCompileData* data, - const ParsedFunction* parsed_function, - const ZoneGrowableArray& ic_data_array, - intptr_t osr_id) { - ASSERT(!FLAG_interpret_irregexp); - Zone* zone = Thread::Current()->zone(); - - const Function& function = parsed_function->function(); - const intptr_t specialization_cid = function.string_specialization_cid(); - const bool is_sticky = function.is_sticky_specialization(); - const bool is_one_byte = (specialization_cid == kOneByteStringCid); - RegExp& regexp = RegExp::Handle(zone, function.regexp()); - const String& pattern = String::Handle(zone, regexp.pattern()); - - ASSERT(!regexp.IsNull()); - ASSERT(!pattern.IsNull()); - - const bool is_global = regexp.flags().IsGlobal(); - const bool is_unicode = regexp.flags().IsUnicode(); - - RegExpCompiler compiler(data->capture_count, is_one_byte); - - // TODO(zerny): Frequency sampling is currently disabled because of several - // issues. We do not want to store subject strings in the regexp object since - // they might be long and we should not prevent their garbage collection. - // Passing them to this function explicitly does not help, since we must - // generate exactly the same IR for both the unoptimizing and optimizing - // pipelines (otherwise it gets confused when i.e. deopt id's differ). - // An option would be to store sampling results in the regexp object, but - // I'm not sure the performance gains are relevant enough. - - // Wrap the body of the regexp in capture #0. - RegExpNode* captured_body = - RegExpCapture::ToNode(data->tree, 0, &compiler, compiler.accept()); - - RegExpNode* node = captured_body; - const bool is_end_anchored = data->tree->IsAnchoredAtEnd(); - const bool is_start_anchored = data->tree->IsAnchoredAtStart(); - intptr_t max_length = data->tree->max_match(); - if (!is_start_anchored && !is_sticky) { - // Add a .*? at the beginning, outside the body capture, unless - // this expression is anchored at the beginning or is sticky. - RegExpNode* loop_node = RegExpQuantifier::ToNode( - 0, RegExpTree::kInfinity, false, - new (zone) RegExpCharacterClass('*', RegExpFlags()), &compiler, - captured_body, data->contains_anchor); - - if (data->contains_anchor) { - // Unroll loop once, to take care of the case that might start - // at the start of input. - ChoiceNode* first_step_node = new (zone) ChoiceNode(2, zone); - first_step_node->AddAlternative(GuardedAlternative(captured_body)); - first_step_node->AddAlternative(GuardedAlternative(new (zone) TextNode( - new (zone) RegExpCharacterClass('*', RegExpFlags()), - /*read_backward=*/false, loop_node))); - node = first_step_node; - } else { - node = loop_node; - } - } - if (is_one_byte) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion); - // Do it again to propagate the new nodes to places where they were not - // put because they had not been calculated yet. - if (node != nullptr) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion); - } - } else if (is_unicode && (is_global || is_sticky)) { - node = OptionallyStepBackToLeadSurrogate(&compiler, node, regexp.flags()); +#endif } - if (node == nullptr) node = new (zone) EndNode(EndNode::BACKTRACK, zone); - data->node = node; - Analysis analysis(is_one_byte); - analysis.EnsureAnalyzed(node); - if (analysis.has_failed()) { - const char* error_message = analysis.error_message(); - return CompilationResult(error_message); - } - - // Native regexp implementation. - - IRRegExpMacroAssembler* macro_assembler = new (zone) - IRRegExpMacroAssembler(specialization_cid, data->capture_count, - parsed_function, ic_data_array, osr_id, zone); + macro_assembler->set_slow_safe(TooMuchRegExpCode(isolate, pattern)); + SetBacktrackAndExperimentalFallback(macro_assembler.get(), re_data); // Inserted here, instead of in Assembler, because it depends on information // in the AST that isn't replicated in the Node structure. - const intptr_t kMaxBacksearchLimit = 1024; - if (is_end_anchored && !is_start_anchored && !is_sticky && - max_length < kMaxBacksearchLimit) { - macro_assembler->SetCurrentPositionFromEnd(max_length); - } - - if (is_global) { - RegExpMacroAssembler::GlobalMode mode = RegExpMacroAssembler::GLOBAL; - if (data->tree->min_match() > 0) { - mode = RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK; - } else if (is_unicode) { - mode = RegExpMacroAssembler::GLOBAL_UNICODE; - } - macro_assembler->set_global_mode(mode); - } - - RegExpEngine::CompilationResult result = - compiler.Assemble(macro_assembler, node, data->capture_count, pattern); - - if (FLAG_trace_irregexp) { - macro_assembler->PrintBlocks(); - } - - return result; -} -#endif // !defined(DART_PRECOMPILED_RUNTIME) - -RegExpEngine::CompilationResult RegExpEngine::CompileBytecode( - RegExpCompileData* data, - const RegExp& regexp, - bool is_one_byte, - bool is_sticky, - Zone* zone) { - ASSERT(FLAG_interpret_irregexp); - const String& pattern = String::Handle(zone, regexp.pattern()); - - ASSERT(!regexp.IsNull()); - ASSERT(!pattern.IsNull()); - - const bool is_global = regexp.flags().IsGlobal(); - const bool is_unicode = regexp.flags().IsUnicode(); - - RegExpCompiler compiler(data->capture_count, is_one_byte); - - // TODO(zerny): Frequency sampling is currently disabled because of several - // issues. We do not want to store subject strings in the regexp object since - // they might be long and we should not prevent their garbage collection. - // Passing them to this function explicitly does not help, since we must - // generate exactly the same IR for both the unoptimizing and optimizing - // pipelines (otherwise it gets confused when i.e. deopt id's differ). - // An option would be to store sampling results in the regexp object, but - // I'm not sure the performance gains are relevant enough. - - // Wrap the body of the regexp in capture #0. - RegExpNode* captured_body = - RegExpCapture::ToNode(data->tree, 0, &compiler, compiler.accept()); - - RegExpNode* node = captured_body; bool is_end_anchored = data->tree->IsAnchoredAtEnd(); bool is_start_anchored = data->tree->IsAnchoredAtStart(); - intptr_t max_length = data->tree->max_match(); - if (!is_start_anchored && !is_sticky) { - // Add a .*? at the beginning, outside the body capture, unless - // this expression is anchored at the beginning. - RegExpNode* loop_node = RegExpQuantifier::ToNode( - 0, RegExpTree::kInfinity, false, - new (zone) RegExpCharacterClass('*', RegExpFlags()), &compiler, - captured_body, data->contains_anchor); - - if (data->contains_anchor) { - // Unroll loop once, to take care of the case that might start - // at the start of input. - ChoiceNode* first_step_node = new (zone) ChoiceNode(2, zone); - first_step_node->AddAlternative(GuardedAlternative(captured_body)); - first_step_node->AddAlternative(GuardedAlternative(new (zone) TextNode( - new (zone) RegExpCharacterClass('*', RegExpFlags()), - /*read_backward=*/false, loop_node))); - node = first_step_node; - } else { - node = loop_node; - } - } - if (is_one_byte) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion); - // Do it again to propagate the new nodes to places where they were not - // put because they had not been calculated yet. - if (node != nullptr) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion); - } - } else if (is_unicode && (is_global || is_sticky)) { - node = OptionallyStepBackToLeadSurrogate(&compiler, node, regexp.flags()); - } - - if (node == nullptr) node = new (zone) EndNode(EndNode::BACKTRACK, zone); - data->node = node; - Analysis analysis(is_one_byte); - analysis.EnsureAnalyzed(node); - if (analysis.has_failed()) { - const char* error_message = analysis.error_message(); - return CompilationResult(error_message); - } - - // Bytecode regexp implementation. - - ZoneGrowableArray buffer(zone, 1024); - BytecodeRegExpMacroAssembler* macro_assembler = - new (zone) BytecodeRegExpMacroAssembler(&buffer, zone); - - // Inserted here, instead of in Assembler, because it depends on information - // in the AST that isn't replicated in the Node structure. - const intptr_t kMaxBacksearchLimit = 1024; - if (is_end_anchored && !is_start_anchored && !is_sticky && + int max_length = data->tree->max_match(); + static const int kMaxBacksearchLimit = 1024; + if (is_end_anchored && !is_start_anchored && !IsSticky(flags) && max_length < kMaxBacksearchLimit) { macro_assembler->SetCurrentPositionFromEnd(max_length); } - if (is_global) { + if (IsGlobal(flags)) { RegExpMacroAssembler::GlobalMode mode = RegExpMacroAssembler::GLOBAL; if (data->tree->min_match() > 0) { mode = RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK; - } else if (is_unicode) { + } else if (IsEitherUnicode(flags)) { mode = RegExpMacroAssembler::GLOBAL_UNICODE; } macro_assembler->set_global_mode(mode); } - RegExpEngine::CompilationResult result = - compiler.Assemble(macro_assembler, node, data->capture_count, pattern); + RegExpCompiler::CompilationResult result = compiler.Assemble( + isolate, macro_assembler.get(), data->node, data->capture_count, pattern); - if (FLAG_trace_irregexp) { - macro_assembler->PrintBlocks(); + // Code / bytecode printing. + { +#ifdef ENABLE_DISASSEMBLER + if (UNLIKELY(v8_flags.print_regexp_code && + data->compilation_target == RegExpCompilationTarget::kNative && + result.Succeeded())) { + CodeTracer::Scope trace_scope(isolate->GetCodeTracer()); + OFStream os(trace_scope.file()); + auto code = CheckedCast(result.code); + std::unique_ptr pattern_cstring = pattern->ToCString(); + code->Disassemble(pattern_cstring.get(), os, isolate); + } + if (UNLIKELY(v8_flags.print_regexp_bytecode && + data->compilation_target == + RegExpCompilationTarget::kBytecode && + result.Succeeded())) { + auto bytecode = CheckedCast(result.code); + std::unique_ptr pattern_cstring = pattern->ToCString(); + RegExpBytecodeDisassemble(bytecode->begin(), bytecode->length(), + pattern_cstring.get()); + } +#endif } - return result; + if (result.error != RegExpError::kNone) { + if (FLAG_correctness_fuzzer_suppressions && + result.error == RegExpError::kStackOverflow) { + FATAL("Aborting on stack overflow"); + } + data->error = result.error; + } + + data->code = result.code; + data->register_count = result.num_registers; + + return result.Succeeded(); } -void CreateSpecializedFunction(Thread* thread, - Zone* zone, - const RegExp& regexp, - intptr_t specialization_cid, - bool sticky, - const Object& owner) { - const intptr_t kParamCount = RegExpMacroAssembler::kParamCount; - - const FunctionType& signature = - FunctionType::Handle(zone, FunctionType::New()); - const String& pattern = String::Handle(zone, regexp.pattern()); - Function& fn = - Function::Handle(zone, Function::New(signature, pattern, - UntaggedFunction::kIrregexpFunction, - true, // Static. - false, // Not const. - false, // Not abstract. - false, // Not external. - false, // Not native. - owner, TokenPosition::kMinSource)); - - // TODO(zerny): Share these arrays between all irregexp functions. - // TODO(regis): Better, share a common signature. - signature.set_num_fixed_parameters(kParamCount); - signature.set_parameter_types( - Array::Handle(zone, Array::New(kParamCount, Heap::kOld))); - fn.CreateNameArray(); - signature.SetParameterTypeAt(RegExpMacroAssembler::kParamRegExpIndex, - Object::dynamic_type()); - fn.SetParameterNameAt(RegExpMacroAssembler::kParamRegExpIndex, - Symbols::This()); - signature.SetParameterTypeAt(RegExpMacroAssembler::kParamStringIndex, - Object::dynamic_type()); - fn.SetParameterNameAt(RegExpMacroAssembler::kParamStringIndex, - Symbols::string_param()); - signature.SetParameterTypeAt(RegExpMacroAssembler::kParamStartOffsetIndex, - Object::dynamic_type()); - fn.SetParameterNameAt(RegExpMacroAssembler::kParamStartOffsetIndex, - Symbols::start_index_param()); - signature.set_result_type(Type::Handle(zone, Type::ArrayType())); - - // Cache the result. - regexp.set_function(specialization_cid, sticky, fn); - - fn.SetRegExpData(regexp, specialization_cid, sticky); - fn.set_is_debuggable(false); - - // The function is compiled lazily during the first call. +std::ostream& operator<<(std::ostream& os, RegExpFlags flags) { +#define V(Lower, Camel, LowerCamel, Char, Bit) \ + if (flags & RegExpFlag::k##Camel) os << Char; + REGEXP_FLAG_LIST(V) +#undef V + return os; } -RegExpPtr RegExpEngine::CreateRegExp(Thread* thread, - const String& pattern, - RegExpFlags flags) { - Zone* zone = thread->zone(); - const RegExp& regexp = RegExp::Handle(RegExp::New(zone)); - - regexp.set_pattern(pattern); - regexp.set_flags(flags); - - // TODO(zerny): We might want to use normal string searching algorithms - // for simple patterns. - regexp.set_is_complex(); - regexp.set_is_global(); // All dart regexps are global. - - if (!FLAG_interpret_irregexp) { - const Library& lib = Library::Handle(zone, Library::CoreLibrary()); - const Class& owner = - Class::Handle(zone, lib.LookupClass(Symbols::RegExp())); - - for (intptr_t cid = kOneByteStringCid; cid <= kTwoByteStringCid; cid++) { - CreateSpecializedFunction(thread, zone, regexp, cid, /*sticky=*/false, - owner); - CreateSpecializedFunction(thread, zone, regexp, cid, /*sticky=*/true, - owner); +ObjectPtr RegExpStatics::Interpret(Thread* thread, + const RegExp& regexp, + const String& subject, + int start_index, + bool sticky) { + bool is_one_byte = subject.IsOneByteString(); + if (!regexp.has_bytecode(is_one_byte, sticky)) { + if (!RegExpImpl::CompileIrregexpFromSource( + thread, regexp, subject, is_one_byte, sticky, + RegExpCompilationTarget::kBytecode)) { + // RegExp was verified at construction. + UNREACHABLE(); } } - return regexp.ptr(); + int register_count = regexp.num_registers(is_one_byte); + ASSERT(register_count >= 2); + int32_t* registers = thread->zone()->Alloc(register_count); + + for (intptr_t i = 0; i < register_count; i++) { + registers[i] = -1; + } + + int r = IrregexpInterpreter::MatchForCallFromRuntime( + thread, regexp, subject, registers, register_count, start_index, sticky); + if (r == IrregexpInterpreter::SUCCESS) { + const TypedData& result = TypedData::Handle( + thread->zone(), + TypedData::New(kTypedDataInt32ArrayCid, register_count)); + { +#ifdef DEBUG + // These indices will be used with substring operations that don't check + // bounds, so sanity check them here. + for (intptr_t i = 0; i < register_count; i++) { + int32_t val = registers[i]; + ASSERT(val == -1 || (val >= 0 && val <= subject.Length())); + } +#endif + + NoSafepointScope no_safepoint(thread); + memcpy(result.DataAddr(0), registers, + register_count * sizeof(int32_t)); // NOLINT + } + + return result.ptr(); + } else if (r == IrregexpInterpreter::FAILURE) { + return Instance::null(); + } else if (r == IrregexpInterpreter::EXCEPTION) { + const Error& error = Error::Handle(thread->StealStickyError()); + Exceptions::PropagateError(error); + UNREACHABLE(); + } else if (r == IrregexpInterpreter::RETRY) { + UNREACHABLE(); // No tier up in Dart. + } else if (r == IrregexpInterpreter::FALLBACK_TO_EXPERIMENTAL) { + UNREACHABLE(); // No alt implementation for Dart. + } else { + UNREACHABLE(); + } + return Instance::null(); } } // namespace dart diff --git a/runtime/vm/regexp/regexp.h b/runtime/vm/regexp/regexp.h index c2743e2ff99..6dbe7fbdfcd 100644 --- a/runtime/vm/regexp/regexp.h +++ b/runtime/vm/regexp/regexp.h @@ -1,1530 +1,204 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. -#ifndef RUNTIME_VM_REGEXP_REGEXP_H_ -#define RUNTIME_VM_REGEXP_REGEXP_H_ - -#include "platform/unicode.h" +#ifndef V8_REGEXP_REGEXP_H_ +#define V8_REGEXP_REGEXP_H_ #include "vm/object.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/splay-tree.h" +#include "vm/regexp/base.h" +#include "vm/regexp/regexp-error.h" +#include "vm/regexp/regexp-flags.h" +#include "vm/regexp/zone-containers.h" namespace dart { -class NodeVisitor; -class RegExpCompiler; -class RegExpMacroAssembler; +class JSRegExp; +class RegExpCapture; +class RegExpData; +class IrRegExpData; +class AtomRegExpData; +class RegExpMatchInfo; class RegExpNode; class RegExpTree; -class BoyerMooreLookahead; -// Represents code units in the range from from_ to to_, both ends are -// inclusive. -class CharacterRange { - public: - CharacterRange() : from_(0), to_(0) {} - CharacterRange(int32_t from, int32_t to) : from_(from), to_(to) {} +enum class RegExpCompilationTarget : int { kBytecode, kNative }; - static void AddClassEscape(uint16_t type, - ZoneGrowableArray* ranges); - // Add class escapes with case equivalent closure for \w and \W if necessary. - static void AddClassEscape(uint16_t type, - ZoneGrowableArray* ranges, - bool add_unicode_case_equivalents); - static GrowableArray GetWordBounds(); - static inline CharacterRange Singleton(int32_t value) { - return CharacterRange(value, value); - } - static inline CharacterRange Range(int32_t from, int32_t to) { - ASSERT(from <= to); - return CharacterRange(from, to); - } - static inline CharacterRange Everything() { - return CharacterRange(0, Utf::kMaxCodePoint); - } - static inline ZoneGrowableArray* List(Zone* zone, - CharacterRange range) { - auto list = new (zone) ZoneGrowableArray(1); - list->Add(range); - return list; - } - bool Contains(int32_t i) const { return from_ <= i && i <= to_; } - int32_t from() const { return from_; } - void set_from(int32_t value) { from_ = value; } - int32_t to() const { return to_; } - void set_to(int32_t value) { to_ = value; } - bool is_valid() const { return from_ <= to_; } - bool IsEverything(int32_t max) const { return from_ == 0 && to_ >= max; } - bool IsSingleton() const { return (from_ == to_); } - static void AddCaseEquivalents(ZoneGrowableArray* ranges, - bool is_one_byte, - Zone* zone); - static void Split(ZoneGrowableArray* base, - GrowableArray overlay, - ZoneGrowableArray** included, - ZoneGrowableArray** excluded, - Zone* zone); - // Whether a range list is in canonical form: Ranges ordered by from value, - // and ranges non-overlapping and non-adjacent. - static bool IsCanonical(ZoneGrowableArray* ranges); - // Convert range list to canonical form. The characters covered by the ranges - // will still be the same, but no character is in more than one range, and - // adjacent ranges are merged. The resulting list may be shorter than the - // original, but cannot be longer. - static void Canonicalize(ZoneGrowableArray* ranges); - // Negate the contents of a character range in canonical form. - static void Negate(ZoneGrowableArray* src, - ZoneGrowableArray* dst); - static constexpr intptr_t kStartMarker = (1 << 24); - static constexpr intptr_t kPayloadMask = (1 << 24) - 1; +// TODO(jgruber): Do not expose in regexp.h. +// TODO(jgruber): Consider splitting between ParseData and CompileData. +struct RegExpCompileData { + // The parsed AST as produced by the RegExpParser. + RegExpTree* tree = nullptr; - private: - int32_t from_; - int32_t to_; + // The compiled Node graph as produced by RegExpTree::ToNode methods. + RegExpNode* node = nullptr; - DISALLOW_ALLOCATION(); + // Either the generated code as produced by the compiler or a trampoline + // to the interpreter. + Object* code; + + // True, iff the pattern is a 'simple' atom with zero captures. In other + // words, the pattern consists of a string with no metacharacters and special + // regexp features, and can be implemented as a standard string search. + bool simple = true; + + // True, iff the pattern is anchored at the start of the string with '^'. + bool contains_anchor = false; + + // Only set if the pattern contains named captures. + // Note: the lifetime equals that of the parse/compile zone. + ZoneVector* named_captures = nullptr; + + // The error message. Only used if an error occurred during parsing or + // compilation. + RegExpError error = RegExpError::kNone; + + // The position at which the error was detected. Only used if an + // error occurred. + int error_pos = 0; + + // The number of capture groups, without the global capture \0. + int capture_count = 0; + + // The number of registers used by the generated code. + int register_count = 0; + + // The compilation target (bytecode or native code). + RegExpCompilationTarget compilation_target; }; -// A set of unsigned integers that behaves especially well on small -// integers (< 32). May do zone-allocation. -class OutSet : public ZoneObject { +class RegExpStatics final : public AllStatic { public: - OutSet() : first_(0), remaining_(nullptr), successors_(nullptr) {} - OutSet* Extend(unsigned value, Zone* zone); - bool Get(unsigned value) const; - static constexpr unsigned kFirstLimit = 32; + // Whether the irregexp engine generates interpreter bytecode. + static bool CanGenerateBytecode(); - private: - // Destructively set a value in this set. In most cases you want - // to use Extend instead to ensure that only one instance exists - // that contains the same values. - void Set(unsigned value, Zone* zone); + // Verify that the given flags combination is valid. + static bool VerifyFlags(RegExpFlags flags); - // The successors are a list of sets that contain the same values - // as this set and the one more value that is not present in this - // set. - ZoneGrowableArray* successors() { return successors_; } + // Verify the given pattern, i.e. check that parsing succeeds. If + // verification fails, `regexp_error_out` is set. + template + static bool VerifySyntax(Zone* zone, + uintptr_t stack_limit, + const CharT* input, + int input_length, + RegExpFlags flags, + RegExpError* regexp_error_out); - OutSet(uint32_t first, ZoneGrowableArray* remaining) - : first_(first), remaining_(remaining), successors_(nullptr) {} - uint32_t first_; - ZoneGrowableArray* remaining_; - ZoneGrowableArray* successors_; - friend class Trace; -}; + // Parses the RegExp pattern and prepares the JSRegExp object with + // generic data and choice of implementation - as well as what + // the implementation wants to store in the data field. + // Returns false if compilation fails. + V8_WARN_UNUSED_RESULT static ObjectPtr Compile(Isolate* isolate, + const RegExp& re, + const String& pattern, + RegExpFlags flags, + uint32_t backtrack_limit); -// A mapping from integers, specified as ranges, to a set of integers. -// Used for mapping character ranges to choices. -class ChoiceTable : public ValueObject { - public: - explicit ChoiceTable(Zone* zone) : tree_(zone) {} + // Ensures that a regexp is fully compiled and ready to be executed on a + // subject string. Returns true on success. Throw and return false on + // failure. + V8_WARN_UNUSED_RESULT static bool EnsureFullyCompiled(Isolate* isolate, + const RegExp& re_data, + const String& subject); - class Entry { - public: - Entry() : from_(0), to_(0), out_set_(nullptr) {} - Entry(int32_t from, int32_t to, OutSet* out_set) - : from_(from), to_(to), out_set_(out_set) { - ASSERT(from <= to); - } - int32_t from() { return from_; } - int32_t to() { return to_; } - void set_to(int32_t value) { to_ = value; } - void AddValue(int value, Zone* zone) { - out_set_ = out_set_->Extend(value, zone); - } - OutSet* out_set() { return out_set_; } - - private: - int32_t from_; - int32_t to_; - OutSet* out_set_; + enum CallOrigin : int { + kFromRuntime = 0, + kFromJs = 1, }; - class Config { - public: - typedef int32_t Key; - typedef Entry Value; - static const int32_t kNoKey; - static const Entry NoValue() { return Value(); } - static inline int Compare(int32_t a, int32_t b) { - if (a == b) - return 0; - else if (a < b) - return -1; - else - return 1; - } + // See ECMA-262 section 15.10.6.2. + // This function calls the garbage collector if necessary. + V8_WARN_UNUSED_RESULT static std::optional Exec( + Isolate* isolate, + RegExp& regexp, + const String& subject, + int index, + int32_t* result_offsets_vector, + uint32_t result_offsets_vector_length); + // As above, but passes the result through the old-style RegExpMatchInfo|Null + // interface. At most one match is returned. + V8_WARN_UNUSED_RESULT static ObjectPtr Exec_Single(Isolate* isolate, + RegExp& regexp, + const String& subject, + int index, + Object& last_match_info); + + V8_WARN_UNUSED_RESULT static std::optional ExperimentalOneshotExec( + Isolate* isolate, + RegExp& regexp, + const String& subject, + int index, + int32_t* result_offsets_vector, + uint32_t result_offsets_vector_length); + + // Called directly from generated code through ExternalReference. + static intptr_t AtomExecRaw(Isolate* isolate, + uword /* AtomRegExpData */ data_address, + uword /* String */ subject_address, + int32_t index, + int32_t* result_offsets_vector, + int32_t result_offsets_vector_length); + + // Integral return values used throughout regexp code layers. + static constexpr int kInternalRegExpFailure = 0; + static constexpr int kInternalRegExpSuccess = 1; + static constexpr int kInternalRegExpException = -1; + static constexpr int kInternalRegExpRetry = -2; + static constexpr int kInternalRegExpFallbackToExperimental = -3; + static constexpr int kInternalRegExpSmallestResult = -3; + + enum IrregexpResult : int32_t { + RE_FAILURE = kInternalRegExpFailure, + RE_SUCCESS = kInternalRegExpSuccess, + RE_EXCEPTION = kInternalRegExpException, + RE_RETRY = kInternalRegExpRetry, + RE_FALLBACK_TO_EXPERIMENTAL = kInternalRegExpFallbackToExperimental, }; - void AddRange(CharacterRange range, int32_t value, Zone* zone); - OutSet* Get(int32_t value); - void Dump(); - - template - void ForEach(Callback* callback) { - return tree()->ForEach(callback); - } - - private: - // There can't be a static empty set since it allocates its - // successors in a zone and caches them. - OutSet* empty() { return &empty_; } - OutSet empty_; - ZoneSplayTree* tree() { return &tree_; } - ZoneSplayTree tree_; -}; - -// Categorizes character ranges into BMP, non-BMP, lead, and trail surrogates. -class UnicodeRangeSplitter : public ValueObject { - public: - UnicodeRangeSplitter(Zone* zone, ZoneGrowableArray* base); - void Call(uint32_t from, ChoiceTable::Entry entry); - - ZoneGrowableArray* bmp() { return bmp_; } - ZoneGrowableArray* lead_surrogates() { - return lead_surrogates_; - } - ZoneGrowableArray* trail_surrogates() { - return trail_surrogates_; - } - ZoneGrowableArray* non_bmp() const { return non_bmp_; } - - private: - static constexpr int kBase = 0; - // Separate ranges into - static constexpr int kBmpCodePoints = 1; - static constexpr int kLeadSurrogates = 2; - static constexpr int kTrailSurrogates = 3; - static constexpr int kNonBmpCodePoints = 4; - - Zone* zone_; - ChoiceTable table_; - ZoneGrowableArray* bmp_; - ZoneGrowableArray* lead_surrogates_; - ZoneGrowableArray* trail_surrogates_; - ZoneGrowableArray* non_bmp_; -}; - -#define FOR_EACH_NODE_TYPE(VISIT) \ - VISIT(End) \ - VISIT(Action) \ - VISIT(Choice) \ - VISIT(BackReference) \ - VISIT(Assertion) \ - VISIT(Text) - -#define FOR_EACH_REG_EXP_TREE_TYPE(VISIT) \ - VISIT(Disjunction) \ - VISIT(Alternative) \ - VISIT(Assertion) \ - VISIT(CharacterClass) \ - VISIT(Atom) \ - VISIT(Quantifier) \ - VISIT(Capture) \ - VISIT(Lookaround) \ - VISIT(BackReference) \ - VISIT(Empty) \ - VISIT(Text) - -#define FORWARD_DECLARE(Name) class RegExp##Name; -FOR_EACH_REG_EXP_TREE_TYPE(FORWARD_DECLARE) -#undef FORWARD_DECLARE - -class TextElement { - public: - enum TextType { ATOM, CHAR_CLASS }; - - static TextElement Atom(RegExpAtom* atom); - static TextElement CharClass(RegExpCharacterClass* char_class); - - intptr_t cp_offset() const { return cp_offset_; } - void set_cp_offset(intptr_t cp_offset) { cp_offset_ = cp_offset; } - intptr_t length() const; - - TextType text_type() const { return text_type_; } - - RegExpTree* tree() const { return tree_; } - - RegExpAtom* atom() const { - ASSERT(text_type() == ATOM); - return reinterpret_cast(tree()); - } - - RegExpCharacterClass* char_class() const { - ASSERT(text_type() == CHAR_CLASS); - return reinterpret_cast(tree()); - } - - private: - TextElement(TextType text_type, RegExpTree* tree) - : cp_offset_(-1), text_type_(text_type), tree_(tree) {} - - intptr_t cp_offset_; - TextType text_type_; - RegExpTree* tree_; - - DISALLOW_ALLOCATION(); -}; - -class Trace; -struct PreloadState; -class GreedyLoopState; -class AlternativeGenerationList; - -struct NodeInfo { - NodeInfo() - : being_analyzed(false), - been_analyzed(false), - follows_word_interest(false), - follows_newline_interest(false), - follows_start_interest(false), - at_end(false), - visited(false), - replacement_calculated(false) {} - - // Returns true if the interests and assumptions of this node - // matches the given one. - bool Matches(NodeInfo* that) { - return (at_end == that->at_end) && - (follows_word_interest == that->follows_word_interest) && - (follows_newline_interest == that->follows_newline_interest) && - (follows_start_interest == that->follows_start_interest); - } - - // Updates the interests of this node given the interests of the - // node preceding it. - void AddFromPreceding(NodeInfo* that) { - at_end |= that->at_end; - follows_word_interest |= that->follows_word_interest; - follows_newline_interest |= that->follows_newline_interest; - follows_start_interest |= that->follows_start_interest; - } - - bool HasLookbehind() { - return follows_word_interest || follows_newline_interest || - follows_start_interest; - } - - // Sets the interests of this node to include the interests of the - // following node. - void AddFromFollowing(NodeInfo* that) { - follows_word_interest |= that->follows_word_interest; - follows_newline_interest |= that->follows_newline_interest; - follows_start_interest |= that->follows_start_interest; - } - - void ResetCompilationState() { - being_analyzed = false; - been_analyzed = false; - } - - bool being_analyzed : 1; - bool been_analyzed : 1; - - // These bits are set of this node has to know what the preceding - // character was. - bool follows_word_interest : 1; - bool follows_newline_interest : 1; - bool follows_start_interest : 1; - - bool at_end : 1; - bool visited : 1; - bool replacement_calculated : 1; -}; - -// Details of a quick mask-compare check that can look ahead in the -// input stream. -class QuickCheckDetails { - public: - QuickCheckDetails() - : characters_(0), mask_(0), value_(0), cannot_match_(false) {} - explicit QuickCheckDetails(intptr_t characters) - : characters_(characters), mask_(0), value_(0), cannot_match_(false) {} - bool Rationalize(bool one_byte); - // Merge in the information from another branch of an alternation. - void Merge(QuickCheckDetails* other, intptr_t from_index); - // Advance the current position by some amount. - void Advance(intptr_t by, bool one_byte); - void Clear(); - bool cannot_match() { return cannot_match_; } - void set_cannot_match() { cannot_match_ = true; } - struct Position { - Position() : mask(0), value(0), determines_perfectly(false) {} - uint16_t mask; - uint16_t value; - bool determines_perfectly; - }; - intptr_t characters() { return characters_; } - void set_characters(intptr_t characters) { characters_ = characters; } - Position* positions(intptr_t index) { - ASSERT(index >= 0); - ASSERT(index < characters_); - return positions_ + index; - } - uint32_t mask() { return mask_; } - uint32_t value() { return value_; } - - private: - // How many characters do we have quick check information from. This is - // the same for all branches of a choice node. - intptr_t characters_; - Position positions_[4]; - // These values are the condensate of the above array after Rationalize(). - uint32_t mask_; - uint32_t value_; - // If set to true, there is no way this quick check can match at all. - // E.g., if it requires to be at the start of the input, and isn't. - bool cannot_match_; - - DISALLOW_ALLOCATION(); -}; - -class RegExpNode : public ZoneObject { - public: - explicit RegExpNode(Zone* zone) - : replacement_(nullptr), trace_count_(0), zone_(zone) { - bm_info_[0] = bm_info_[1] = nullptr; - } - virtual ~RegExpNode(); - virtual void Accept(NodeVisitor* visitor) = 0; - // Generates a goto to this node or actually generates the code at this point. - virtual void Emit(RegExpCompiler* compiler, Trace* trace) = 0; - // How many characters must this node consume at a minimum in order to - // succeed. If we have found at least 'still_to_find' characters that - // must be consumed there is no need to ask any following nodes whether - // they are sure to eat any more characters. The not_at_start argument is - // used to indicate that we know we are not at the start of the input. In - // this case anchored branches will always fail and can be ignored when - // determining how many characters are consumed on success. - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start) = 0; - // Emits some quick code that checks whether the preloaded characters match. - // Falls through on certain failure, jumps to the label on possible success. - // If the node cannot make a quick check it does nothing and returns false. - bool EmitQuickCheck(RegExpCompiler* compiler, - Trace* bounds_check_trace, - Trace* trace, - bool preload_has_checked_bounds, - BlockLabel* on_possible_success, - QuickCheckDetails* details_return, - bool fall_through_on_failure); - // For a given number of characters this returns a mask and a value. The - // next n characters are anded with the mask and compared with the value. - // A comparison failure indicates the node cannot match the next n characters. - // A comparison success indicates the node may match. - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) = 0; - static constexpr intptr_t kNodeIsTooComplexForGreedyLoops = -1; - virtual intptr_t GreedyLoopTextLength() { - return kNodeIsTooComplexForGreedyLoops; - } - // Only returns the successor for a text node of length 1 that matches any - // character and that has no guards on it. - virtual RegExpNode* GetSuccessorOfOmnivorousTextNode( - RegExpCompiler* compiler) { - return nullptr; - } - - // Collects information on the possible code units (mod 128) that can match if - // we look forward. This is used for a Boyer-Moore-like string searching - // implementation. TODO(erikcorry): This should share more code with - // EatsAtLeast, GetQuickCheckDetails. The budget argument is used to limit - // the number of nodes we are willing to look at in order to create this data. - static constexpr intptr_t kRecursionBudget = 200; - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - UNREACHABLE(); - } - - // If we know that the input is one-byte then there are some nodes that can - // never match. This method returns a node that can be substituted for - // itself, or nullptr if the node can never match. - virtual RegExpNode* FilterOneByte(intptr_t depth) { return this; } - // Helper for FilterOneByte. - RegExpNode* replacement() { - ASSERT(info()->replacement_calculated); - return replacement_; - } - RegExpNode* set_replacement(RegExpNode* replacement) { - info()->replacement_calculated = true; - replacement_ = replacement; - return replacement; // For convenience. - } - - // We want to avoid recalculating the lookahead info, so we store it on the - // node. Only info that is for this node is stored. We can tell that the - // info is for this node when offset == 0, so the information is calculated - // relative to this node. - void SaveBMInfo(BoyerMooreLookahead* bm, bool not_at_start, intptr_t offset) { - if (offset == 0) set_bm_info(not_at_start, bm); - } - - BlockLabel* label() { return &label_; } - // If non-generic code is generated for a node (i.e. the node is not at the - // start of the trace) then it cannot be reused. This variable sets a limit - // on how often we allow that to happen before we insist on starting a new - // trace and generating generic code for a node that can be reused by flushing - // the deferred actions in the current trace and generating a goto. - static constexpr intptr_t kMaxCopiesCodeGenerated = 10; - - NodeInfo* info() { return &info_; } - - BoyerMooreLookahead* bm_info(bool not_at_start) { - return bm_info_[not_at_start ? 1 : 0]; - } - - Zone* zone() const { return zone_; } - - protected: - enum LimitResult { DONE, CONTINUE }; - RegExpNode* replacement_; - - LimitResult LimitVersions(RegExpCompiler* compiler, Trace* trace); - - void set_bm_info(bool not_at_start, BoyerMooreLookahead* bm) { - bm_info_[not_at_start ? 1 : 0] = bm; - } - - private: - static constexpr intptr_t kFirstCharBudget = 10; - BlockLabel label_; - NodeInfo info_; - // This variable keeps track of how many times code has been generated for - // this node (in different traces). We don't keep track of where the - // generated code is located unless the code is generated at the start of - // a trace, in which case it is generic and can be reused by flushing the - // deferred operations in the current trace and generating a goto. - intptr_t trace_count_; - BoyerMooreLookahead* bm_info_[2]; - Zone* zone_; -}; - -// A simple closed interval. -class Interval { - public: - Interval() : from_(kNone), to_(kNone) {} - Interval(intptr_t from, intptr_t to) : from_(from), to_(to) {} - - Interval Union(Interval that) { - if (that.from_ == kNone) - return *this; - else if (from_ == kNone) - return that; - else - return Interval(Utils::Minimum(from_, that.from_), - Utils::Maximum(to_, that.to_)); - } - bool Contains(intptr_t value) const { - return (from_ <= value) && (value <= to_); - } - bool is_empty() const { return from_ == kNone; } - intptr_t from() const { return from_; } - intptr_t to() const { return to_; } - static Interval Empty() { return Interval(); } - static constexpr intptr_t kNone = -1; - - private: - intptr_t from_; - intptr_t to_; - - DISALLOW_ALLOCATION(); -}; - -class SeqRegExpNode : public RegExpNode { - public: - explicit SeqRegExpNode(RegExpNode* on_success) - : RegExpNode(on_success->zone()), on_success_(on_success) {} - RegExpNode* on_success() { return on_success_; } - void set_on_success(RegExpNode* node) { on_success_ = node; } - virtual RegExpNode* FilterOneByte(intptr_t depth); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - on_success_->FillInBMInfo(offset, budget - 1, bm, not_at_start); - if (offset == 0) set_bm_info(not_at_start, bm); - } - - protected: - RegExpNode* FilterSuccessor(intptr_t depth); - - private: - RegExpNode* on_success_; -}; - -class ActionNode : public SeqRegExpNode { - public: - enum ActionType { - SET_REGISTER, - INCREMENT_REGISTER, - STORE_POSITION, - BEGIN_SUBMATCH, - POSITIVE_SUBMATCH_SUCCESS, - EMPTY_MATCH_CHECK, - CLEAR_CAPTURES - }; - static ActionNode* SetRegister(intptr_t reg, - intptr_t val, - RegExpNode* on_success); - static ActionNode* IncrementRegister(intptr_t reg, RegExpNode* on_success); - static ActionNode* StorePosition(intptr_t reg, - bool is_capture, - RegExpNode* on_success); - static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success); - static ActionNode* BeginSubmatch(intptr_t stack_pointer_reg, - intptr_t position_reg, - RegExpNode* on_success); - static ActionNode* PositiveSubmatchSuccess(intptr_t stack_pointer_reg, - intptr_t restore_reg, - intptr_t clear_capture_count, - intptr_t clear_capture_from, - RegExpNode* on_success); - static ActionNode* EmptyMatchCheck(intptr_t start_register, - intptr_t repetition_register, - intptr_t repetition_limit, - RegExpNode* on_success); - virtual void Accept(NodeVisitor* visitor); - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t filled_in, - bool not_at_start) { - return on_success()->GetQuickCheckDetails(details, compiler, filled_in, - not_at_start); - } - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - ActionType action_type() { return action_type_; } - // TODO(erikcorry): We should allow some action nodes in greedy loops. - virtual intptr_t GreedyLoopTextLength() { - return kNodeIsTooComplexForGreedyLoops; - } - - private: - union { - struct { - intptr_t reg; - intptr_t value; - } u_store_register; - struct { - intptr_t reg; - } u_increment_register; - struct { - intptr_t reg; - bool is_capture; - } u_position_register; - struct { - intptr_t stack_pointer_register; - intptr_t current_position_register; - intptr_t clear_register_count; - intptr_t clear_register_from; - } u_submatch; - struct { - intptr_t start_register; - intptr_t repetition_register; - intptr_t repetition_limit; - } u_empty_match_check; - struct { - intptr_t range_from; - intptr_t range_to; - } u_clear_captures; - } data_; - ActionNode(ActionType action_type, RegExpNode* on_success) - : SeqRegExpNode(on_success), action_type_(action_type) {} - ActionType action_type_; - friend class DotPrinter; -}; - -class TextNode : public SeqRegExpNode { - public: - TextNode(ZoneGrowableArray* elms, - bool read_backward, - RegExpNode* on_success) - : SeqRegExpNode(on_success), elms_(elms), read_backward_(read_backward) {} - TextNode(RegExpCharacterClass* that, - bool read_backward, - RegExpNode* on_success) - : SeqRegExpNode(on_success), - elms_(new (zone()) ZoneGrowableArray(1)), - read_backward_(read_backward) { - elms_->Add(TextElement::CharClass(that)); - } - // Create TextNode for a single character class for the given ranges. - static TextNode* CreateForCharacterRanges( - ZoneGrowableArray* ranges, - bool read_backward, - RegExpNode* on_success, - RegExpFlags flags); - // Create TextNode for a surrogate pair with a range given for the - // lead and the trail surrogate each. - static TextNode* CreateForSurrogatePair(CharacterRange lead, - CharacterRange trail, - bool read_backward, - RegExpNode* on_success, - RegExpFlags flags); - virtual void Accept(NodeVisitor* visitor); - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start); - ZoneGrowableArray* elements() { return elms_; } - bool read_backward() { return read_backward_; } - void MakeCaseIndependent(bool is_one_byte); - virtual intptr_t GreedyLoopTextLength(); - virtual RegExpNode* GetSuccessorOfOmnivorousTextNode( - RegExpCompiler* compiler); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - void CalculateOffsets(); - virtual RegExpNode* FilterOneByte(intptr_t depth); - - private: - enum TextEmitPassType { - NON_LATIN1_MATCH, // Check for characters that can't match. - SIMPLE_CHARACTER_MATCH, // Case-dependent single character check. - NON_LETTER_CHARACTER_MATCH, // Check characters that have no case equivs. - CASE_CHARACTER_MATCH, // Case-independent single character check. - CHARACTER_CLASS_MATCH // Character class. - }; - static bool SkipPass(intptr_t pass, bool ignore_case); - static constexpr intptr_t kFirstRealPass = SIMPLE_CHARACTER_MATCH; - static constexpr intptr_t kLastPass = CHARACTER_CLASS_MATCH; - void TextEmitPass(RegExpCompiler* compiler, - TextEmitPassType pass, - bool preloaded, - Trace* trace, - bool first_element_checked, - intptr_t* checked_up_to); - intptr_t Length(); - ZoneGrowableArray* elms_; - bool read_backward_; -}; - -class AssertionNode : public SeqRegExpNode { - public: - enum AssertionType { - AT_END, - AT_START, - AT_BOUNDARY, - AT_NON_BOUNDARY, - AFTER_NEWLINE - }; - static AssertionNode* AtEnd(RegExpNode* on_success) { - return new (on_success->zone()) AssertionNode(AT_END, on_success); - } - static AssertionNode* AtStart(RegExpNode* on_success) { - return new (on_success->zone()) AssertionNode(AT_START, on_success); - } - static AssertionNode* AtBoundary(RegExpNode* on_success) { - return new (on_success->zone()) AssertionNode(AT_BOUNDARY, on_success); - } - static AssertionNode* AtNonBoundary(RegExpNode* on_success) { - return new (on_success->zone()) AssertionNode(AT_NON_BOUNDARY, on_success); - } - static AssertionNode* AfterNewline(RegExpNode* on_success) { - return new (on_success->zone()) AssertionNode(AFTER_NEWLINE, on_success); - } - virtual void Accept(NodeVisitor* visitor); - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t filled_in, - bool not_at_start); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - AssertionType assertion_type() { return assertion_type_; } - - private: - void EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace); - enum IfPrevious { kIsNonWord, kIsWord }; - void BacktrackIfPrevious(RegExpCompiler* compiler, - Trace* trace, - IfPrevious backtrack_if_previous); - AssertionNode(AssertionType t, RegExpNode* on_success) - : SeqRegExpNode(on_success), assertion_type_(t) {} - AssertionType assertion_type_; -}; - -class BackReferenceNode : public SeqRegExpNode { - public: - BackReferenceNode(intptr_t start_reg, - intptr_t end_reg, - RegExpFlags flags, - bool read_backward, - RegExpNode* on_success) - : SeqRegExpNode(on_success), - start_reg_(start_reg), - end_reg_(end_reg), - flags_(flags), - read_backward_(read_backward) {} - virtual void Accept(NodeVisitor* visitor); - intptr_t start_register() { return start_reg_; } - intptr_t end_register() { return end_reg_; } - bool read_backward() { return read_backward_; } - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t recursion_depth, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) { - return; - } - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - - private: - intptr_t start_reg_; - intptr_t end_reg_; - RegExpFlags flags_; - bool read_backward_; -}; - -class EndNode : public RegExpNode { - public: - enum Action { ACCEPT, BACKTRACK, NEGATIVE_SUBMATCH_SUCCESS }; - explicit EndNode(Action action, Zone* zone) - : RegExpNode(zone), action_(action) {} - virtual void Accept(NodeVisitor* visitor); - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t recursion_depth, - bool not_at_start) { - return 0; - } - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start) { - // Returning 0 from EatsAtLeast should ensure we never get here. - UNREACHABLE(); - } - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - // Returning 0 from EatsAtLeast should ensure we never get here. - UNREACHABLE(); - } - - private: - Action action_; -}; - -class NegativeSubmatchSuccess : public EndNode { - public: - NegativeSubmatchSuccess(intptr_t stack_pointer_reg, - intptr_t position_reg, - intptr_t clear_capture_count, - intptr_t clear_capture_start, - Zone* zone) - : EndNode(NEGATIVE_SUBMATCH_SUCCESS, zone), - stack_pointer_register_(stack_pointer_reg), - current_position_register_(position_reg), - clear_capture_count_(clear_capture_count), - clear_capture_start_(clear_capture_start) {} - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - - private: - intptr_t stack_pointer_register_; - intptr_t current_position_register_; - intptr_t clear_capture_count_; - intptr_t clear_capture_start_; -}; - -class Guard : public ZoneObject { - public: - enum Relation { LT, GEQ }; - Guard(intptr_t reg, Relation op, intptr_t value) - : reg_(reg), op_(op), value_(value) {} - intptr_t reg() { return reg_; } - Relation op() { return op_; } - intptr_t value() { return value_; } - - private: - intptr_t reg_; - Relation op_; - intptr_t value_; -}; - -class GuardedAlternative { - public: - explicit GuardedAlternative(RegExpNode* node) - : node_(node), guards_(nullptr) {} - void AddGuard(Guard* guard, Zone* zone); - RegExpNode* node() const { return node_; } - void set_node(RegExpNode* node) { node_ = node; } - ZoneGrowableArray* guards() const { return guards_; } - - private: - RegExpNode* node_; - ZoneGrowableArray* guards_; - - DISALLOW_ALLOCATION(); -}; - -struct AlternativeGeneration; - -class ChoiceNode : public RegExpNode { - public: - explicit ChoiceNode(intptr_t expected_size, Zone* zone) - : RegExpNode(zone), - alternatives_(new (zone) - ZoneGrowableArray(expected_size)), - not_at_start_(false), - being_calculated_(false) {} - virtual void Accept(NodeVisitor* visitor); - void AddAlternative(GuardedAlternative node) { alternatives()->Add(node); } - ZoneGrowableArray* alternatives() { - return alternatives_; - } - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - intptr_t EatsAtLeastHelper(intptr_t still_to_find, - intptr_t budget, - RegExpNode* ignore_this_node, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - - bool being_calculated() { return being_calculated_; } - bool not_at_start() { return not_at_start_; } - void set_not_at_start() { not_at_start_ = true; } - void set_being_calculated(bool b) { being_calculated_ = b; } - virtual bool try_to_emit_quick_check_for_alternative(bool is_first) { - return true; - } - virtual RegExpNode* FilterOneByte(intptr_t depth); - virtual bool read_backward() { return false; } - - protected: - intptr_t GreedyLoopTextLengthForAlternative( - const GuardedAlternative* alternative); - ZoneGrowableArray* alternatives_; - - private: - friend class Analysis; - void GenerateGuard(RegExpMacroAssembler* macro_assembler, - Guard* guard, - Trace* trace); - intptr_t CalculatePreloadCharacters(RegExpCompiler* compiler, - intptr_t eats_at_least); - void EmitOutOfLineContinuation(RegExpCompiler* compiler, - Trace* trace, - GuardedAlternative alternative, - AlternativeGeneration* alt_gen, - intptr_t preload_characters, - bool next_expects_preload); - void SetUpPreLoad(RegExpCompiler* compiler, - Trace* current_trace, - PreloadState* preloads); - void AssertGuardsMentionRegisters(Trace* trace); - intptr_t EmitOptimizedUnanchoredSearch(RegExpCompiler* compiler, - Trace* trace); - Trace* EmitGreedyLoop(RegExpCompiler* compiler, - Trace* trace, - AlternativeGenerationList* alt_gens, - PreloadState* preloads, - GreedyLoopState* greedy_loop_state, - intptr_t text_length); - void EmitChoices(RegExpCompiler* compiler, - AlternativeGenerationList* alt_gens, - intptr_t first_choice, - Trace* trace, - PreloadState* preloads); - // If true, this node is never checked at the start of the input. - // Allows a new trace to start with at_start() set to false. - bool not_at_start_; - bool being_calculated_; -}; - -class NegativeLookaroundChoiceNode : public ChoiceNode { - public: - explicit NegativeLookaroundChoiceNode(GuardedAlternative this_must_fail, - GuardedAlternative then_do_this, - Zone* zone) - : ChoiceNode(2, zone) { - AddAlternative(this_must_fail); - AddAlternative(then_do_this); - } - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start) { - (*alternatives_)[1].node()->FillInBMInfo(offset, budget - 1, bm, - not_at_start); - if (offset == 0) set_bm_info(not_at_start, bm); - } - // For a negative lookahead we don't emit the quick check for the - // alternative that is expected to fail. This is because quick check code - // starts by loading enough characters for the alternative that takes fewest - // characters, but on a negative lookahead the negative branch did not take - // part in that calculation (EatsAtLeast) so the assumptions don't hold. - virtual bool try_to_emit_quick_check_for_alternative(bool is_first) { - return !is_first; - } - virtual RegExpNode* FilterOneByte(intptr_t depth); -}; - -class LoopChoiceNode : public ChoiceNode { - public: - explicit LoopChoiceNode(bool body_can_be_zero_length, - bool read_backward, - Zone* zone) - : ChoiceNode(2, zone), - loop_node_(nullptr), - continue_node_(nullptr), - body_can_be_zero_length_(body_can_be_zero_length), - read_backward_(read_backward) {} - void AddLoopAlternative(GuardedAlternative alt); - void AddContinueAlternative(GuardedAlternative alt); - virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual intptr_t EatsAtLeast(intptr_t still_to_find, - intptr_t budget, - bool not_at_start); - virtual void GetQuickCheckDetails(QuickCheckDetails* details, - RegExpCompiler* compiler, - intptr_t characters_filled_in, - bool not_at_start); - virtual void FillInBMInfo(intptr_t offset, - intptr_t budget, - BoyerMooreLookahead* bm, - bool not_at_start); - RegExpNode* loop_node() { return loop_node_; } - RegExpNode* continue_node() { return continue_node_; } - bool body_can_be_zero_length() { return body_can_be_zero_length_; } - virtual bool read_backward() { return read_backward_; } - virtual void Accept(NodeVisitor* visitor); - virtual RegExpNode* FilterOneByte(intptr_t depth); - - private: - // AddAlternative is made private for loop nodes because alternatives - // should not be added freely, we need to keep track of which node - // goes back to the node itself. - void AddAlternative(GuardedAlternative node) { - ChoiceNode::AddAlternative(node); - } - - RegExpNode* loop_node_; - RegExpNode* continue_node_; - bool body_can_be_zero_length_; - bool read_backward_; -}; - -// Improve the speed that we scan for an initial point where a non-anchored -// regexp can match by using a Boyer-Moore-like table. This is done by -// identifying non-greedy non-capturing loops in the nodes that eat any -// character one at a time. For example in the middle of the regexp -// /foo[\s\S]*?bar/ we find such a loop. There is also such a loop implicitly -// inserted at the start of any non-anchored regexp. -// -// When we have found such a loop we look ahead in the nodes to find the set of -// characters that can come at given distances. For example for the regexp -// /.?foo/ we know that there are at least 3 characters ahead of us, and the -// sets of characters that can occur are [any, [f, o], [o]]. We find a range in -// the lookahead info where the set of characters is reasonably constrained. In -// our example this is from index 1 to 2 (0 is not constrained). We can now -// look 3 characters ahead and if we don't find one of [f, o] (the union of -// [f, o] and [o]) then we can skip forwards by the range size (in this case 2). -// -// For Unicode input strings we do the same, but modulo 128. -// -// We also look at the first string fed to the regexp and use that to get a hint -// of the character frequencies in the inputs. This affects the assessment of -// whether the set of characters is 'reasonably constrained'. -// -// We also have another lookahead mechanism (called quick check in the code), -// which uses a wide load of multiple characters followed by a mask and compare -// to determine whether a match is possible at this point. -enum ContainedInLattice { - kNotYet = 0, - kLatticeIn = 1, - kLatticeOut = 2, - kLatticeUnknown = 3 // Can also mean both in and out. -}; - -inline ContainedInLattice Combine(ContainedInLattice a, ContainedInLattice b) { - return static_cast(a | b); -} - -ContainedInLattice AddRange(ContainedInLattice a, - const intptr_t* ranges, - intptr_t ranges_size, - Interval new_range); - -class BoyerMoorePositionInfo : public ZoneObject { - public: - explicit BoyerMoorePositionInfo(Zone* zone) - : map_(new (zone) ZoneGrowableArray(kMapSize)), - map_count_(0), - w_(kNotYet), - s_(kNotYet), - d_(kNotYet), - surrogate_(kNotYet) { - for (intptr_t i = 0; i < kMapSize; i++) { - map_->Add(false); - } - } - - bool& at(intptr_t i) { return (*map_)[i]; } - - static constexpr intptr_t kMapSize = 128; - static constexpr intptr_t kMask = kMapSize - 1; - - intptr_t map_count() const { return map_count_; } - - void Set(intptr_t character); - void SetInterval(const Interval& interval); - void SetAll(); - bool is_non_word() { return w_ == kLatticeOut; } - bool is_word() { return w_ == kLatticeIn; } - - private: - ZoneGrowableArray* map_; - intptr_t map_count_; // Number of set bits in the map. - ContainedInLattice w_; // The \w character class. - ContainedInLattice s_; // The \s character class. - ContainedInLattice d_; // The \d character class. - ContainedInLattice surrogate_; // Surrogate UTF-16 code units. -}; - -class BoyerMooreLookahead : public ZoneObject { - public: - BoyerMooreLookahead(intptr_t length, RegExpCompiler* compiler, Zone* Zone); - - intptr_t length() { return length_; } - intptr_t max_char() { return max_char_; } - RegExpCompiler* compiler() { return compiler_; } - - intptr_t Count(intptr_t map_number) { - return bitmaps_->At(map_number)->map_count(); - } - - BoyerMoorePositionInfo* at(intptr_t i) { return bitmaps_->At(i); } - - void Set(intptr_t map_number, intptr_t character) { - if (character > max_char_) return; - BoyerMoorePositionInfo* info = bitmaps_->At(map_number); - info->Set(character); - } - - void SetInterval(intptr_t map_number, const Interval& interval) { - if (interval.from() > max_char_) return; - BoyerMoorePositionInfo* info = bitmaps_->At(map_number); - if (interval.to() > max_char_) { - info->SetInterval(Interval(interval.from(), max_char_)); - } else { - info->SetInterval(interval); - } - } - - void SetAll(intptr_t map_number) { bitmaps_->At(map_number)->SetAll(); } - - void SetRest(intptr_t from_map) { - for (intptr_t i = from_map; i < length_; i++) - SetAll(i); - } - void EmitSkipInstructions(RegExpMacroAssembler* masm); - - private: - // This is the value obtained by EatsAtLeast. If we do not have at least this - // many characters left in the sample string then the match is bound to fail. - // Therefore it is OK to read a character this far ahead of the current match - // point. - intptr_t length_; - RegExpCompiler* compiler_; - // 0xff for Latin1, 0xffff for UTF-16. - intptr_t max_char_; - ZoneGrowableArray* bitmaps_; - - intptr_t GetSkipTable(intptr_t min_lookahead, - intptr_t max_lookahead, - const TypedData& boolean_skip_table); - bool FindWorthwhileInterval(intptr_t* from, intptr_t* to); - intptr_t FindBestInterval(intptr_t max_number_of_chars, - intptr_t old_biggest_points, - intptr_t* from, - intptr_t* to); -}; - -// There are many ways to generate code for a node. This class encapsulates -// the current way we should be generating. In other words it encapsulates -// the current state of the code generator. The effect of this is that we -// generate code for paths that the matcher can take through the regular -// expression. A given node in the regexp can be code-generated several times -// as it can be part of several traces. For example for the regexp: -// /foo(bar|ip)baz/ the code to match baz will be generated twice, once as part -// of the foo-bar-baz trace and once as part of the foo-ip-baz trace. The code -// to match foo is generated only once (the traces have a common prefix). The -// code to store the capture is deferred and generated (twice) after the places -// where baz has been matched. -class Trace { - public: - // A value for a property that is either known to be true, know to be false, - // or not known. - enum TriBool { UNKNOWN = -1, FALSE_VALUE = 0, TRUE_VALUE = 1 }; - - class DeferredAction { - public: - DeferredAction(ActionNode::ActionType action_type, intptr_t reg) - : action_type_(action_type), reg_(reg), next_(nullptr) {} - DeferredAction* next() { return next_; } - bool Mentions(intptr_t reg); - intptr_t reg() { return reg_; } - ActionNode::ActionType action_type() { return action_type_; } - - private: - ActionNode::ActionType action_type_; - intptr_t reg_; - DeferredAction* next_; - friend class Trace; - - DISALLOW_ALLOCATION(); - }; - - class DeferredCapture : public DeferredAction { - public: - DeferredCapture(intptr_t reg, bool is_capture, Trace* trace) - : DeferredAction(ActionNode::STORE_POSITION, reg), - cp_offset_(trace->cp_offset()), - is_capture_(is_capture) {} - intptr_t cp_offset() { return cp_offset_; } - bool is_capture() { return is_capture_; } - - private: - intptr_t cp_offset_; - bool is_capture_; - void set_cp_offset(intptr_t cp_offset) { cp_offset_ = cp_offset; } - }; - - class DeferredSetRegister : public DeferredAction { - public: - DeferredSetRegister(intptr_t reg, intptr_t value) - : DeferredAction(ActionNode::SET_REGISTER, reg), value_(value) {} - intptr_t value() { return value_; } - - private: - intptr_t value_; - }; - - class DeferredClearCaptures : public DeferredAction { - public: - explicit DeferredClearCaptures(Interval range) - : DeferredAction(ActionNode::CLEAR_CAPTURES, -1), range_(range) {} - Interval range() { return range_; } - - private: - Interval range_; - }; - - class DeferredIncrementRegister : public DeferredAction { - public: - explicit DeferredIncrementRegister(intptr_t reg) - : DeferredAction(ActionNode::INCREMENT_REGISTER, reg) {} - }; - - Trace() - : cp_offset_(0), - actions_(nullptr), - backtrack_(nullptr), - stop_node_(nullptr), - loop_label_(nullptr), - characters_preloaded_(0), - bound_checked_up_to_(0), - flush_budget_(100), - at_start_(UNKNOWN) {} - - // End the trace. This involves flushing the deferred actions in the trace - // and pushing a backtrack location onto the backtrack stack. Once this is - // done we can start a new trace or go to one that has already been - // generated. - void Flush(RegExpCompiler* compiler, RegExpNode* successor); - intptr_t cp_offset() { return cp_offset_; } - DeferredAction* actions() { return actions_; } - // A trivial trace is one that has no deferred actions or other state that - // affects the assumptions used when generating code. There is no recorded - // backtrack location in a trivial trace, so with a trivial trace we will - // generate code that, on a failure to match, gets the backtrack location - // from the backtrack stack rather than using a direct jump instruction. We - // always start code generation with a trivial trace and non-trivial traces - // are created as we emit code for nodes or add to the list of deferred - // actions in the trace. The location of the code generated for a node using - // a trivial trace is recorded in a label in the node so that gotos can be - // generated to that code. - bool is_trivial() { - return backtrack_ == nullptr && actions_ == nullptr && cp_offset_ == 0 && - characters_preloaded_ == 0 && bound_checked_up_to_ == 0 && - quick_check_performed_.characters() == 0 && at_start_ == UNKNOWN; - } - TriBool at_start() { return at_start_; } - void set_at_start(TriBool at_start) { at_start_ = at_start; } - BlockLabel* backtrack() { return backtrack_; } - BlockLabel* loop_label() { return loop_label_; } - RegExpNode* stop_node() { return stop_node_; } - intptr_t characters_preloaded() { return characters_preloaded_; } - intptr_t bound_checked_up_to() { return bound_checked_up_to_; } - intptr_t flush_budget() { return flush_budget_; } - QuickCheckDetails* quick_check_performed() { return &quick_check_performed_; } - bool mentions_reg(intptr_t reg); - // Returns true if a deferred position store exists to the specified - // register and stores the offset in the out-parameter. Otherwise - // returns false. - bool GetStoredPosition(intptr_t reg, intptr_t* cp_offset); - // These set methods and AdvanceCurrentPositionInTrace should be used only on - // new traces - the intention is that traces are immutable after creation. - void add_action(DeferredAction* new_action) { - ASSERT(new_action->next_ == nullptr); - new_action->next_ = actions_; - actions_ = new_action; - } - void set_backtrack(BlockLabel* backtrack) { backtrack_ = backtrack; } - void set_stop_node(RegExpNode* node) { stop_node_ = node; } - void set_loop_label(BlockLabel* label) { loop_label_ = label; } - void set_characters_preloaded(intptr_t count) { - characters_preloaded_ = count; - } - void set_bound_checked_up_to(intptr_t to) { bound_checked_up_to_ = to; } - void set_flush_budget(intptr_t to) { flush_budget_ = to; } - void set_quick_check_performed(QuickCheckDetails* d) { - quick_check_performed_ = *d; - } - void InvalidateCurrentCharacter(); - void AdvanceCurrentPositionInTrace(intptr_t by, RegExpCompiler* compiler); - - private: - intptr_t FindAffectedRegisters(OutSet* affected_registers, Zone* zone); - void PerformDeferredActions(RegExpMacroAssembler* macro, - intptr_t max_register, - const OutSet& affected_registers, - OutSet* registers_to_pop, - OutSet* registers_to_clear, - Zone* zone); - void RestoreAffectedRegisters(RegExpMacroAssembler* macro, - intptr_t max_register, - const OutSet& registers_to_pop, - const OutSet& registers_to_clear); - intptr_t cp_offset_; - DeferredAction* actions_; - BlockLabel* backtrack_; - RegExpNode* stop_node_; - BlockLabel* loop_label_; - intptr_t characters_preloaded_; - intptr_t bound_checked_up_to_; - QuickCheckDetails quick_check_performed_; - intptr_t flush_budget_; - TriBool at_start_; - - DISALLOW_ALLOCATION(); -}; - -class GreedyLoopState { - public: - explicit GreedyLoopState(bool not_at_start); - - BlockLabel* label() { return &label_; } - Trace* counter_backtrack_trace() { return &counter_backtrack_trace_; } - - private: - BlockLabel label_; - Trace counter_backtrack_trace_; -}; - -struct PreloadState { - static constexpr intptr_t kEatsAtLeastNotYetInitialized = -1; - bool preload_is_current_; - bool preload_has_checked_bounds_; - intptr_t preload_characters_; - intptr_t eats_at_least_; - void init() { eats_at_least_ = kEatsAtLeastNotYetInitialized; } - - DISALLOW_ALLOCATION(); -}; - -class NodeVisitor : public ValueObject { - public: - virtual ~NodeVisitor() {} -#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that) = 0; - FOR_EACH_NODE_TYPE(DECLARE_VISIT) -#undef DECLARE_VISIT - virtual void VisitLoopChoice(LoopChoiceNode* that) { VisitChoice(that); } -}; - -// Assertion propagation moves information about assertions such as -// \b to the affected nodes. For instance, in /.\b./ information must -// be propagated to the first '.' that whatever follows needs to know -// if it matched a word or a non-word, and to the second '.' that it -// has to check if it succeeds a word or non-word. In this case the -// result will be something like: -// -// +-------+ +------------+ -// | . | | . | -// +-------+ ---> +------------+ -// | word? | | check word | -// +-------+ +------------+ -class Analysis : public NodeVisitor { - public: - explicit Analysis(bool is_one_byte) - : is_one_byte_(is_one_byte), error_message_(nullptr) {} - void EnsureAnalyzed(RegExpNode* node); - -#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that); - FOR_EACH_NODE_TYPE(DECLARE_VISIT) -#undef DECLARE_VISIT - virtual void VisitLoopChoice(LoopChoiceNode* that); - - bool has_failed() { return error_message_ != nullptr; } - const char* error_message() { - ASSERT(error_message_ != nullptr); - return error_message_; - } - void fail(const char* error_message) { error_message_ = error_message; } - - private: - bool is_one_byte_; - const char* error_message_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(Analysis); -}; - -struct RegExpCompileData : public ZoneObject { - RegExpCompileData() - : tree(nullptr), - node(nullptr), - simple(true), - contains_anchor(false), - capture_name_map(Array::Handle(Array::null())), - error(String::Handle(String::null())), - capture_count(0) {} - RegExpTree* tree; - RegExpNode* node; - bool simple; - bool contains_anchor; - Array& capture_name_map; - String& error; - intptr_t capture_count; -}; - -class RegExpEngine : public AllStatic { - public: - struct CompilationResult { - explicit CompilationResult(const char* error_message) - : error_message(error_message), -#if !defined(DART_PRECOMPILED_RUNTIME) - backtrack_goto(nullptr), - graph_entry(nullptr), - num_blocks(-1), - num_stack_locals(-1), -#endif - bytecode(nullptr), - num_registers(-1) { - } - - CompilationResult(TypedData* bytecode, intptr_t num_registers) - : error_message(nullptr), -#if !defined(DART_PRECOMPILED_RUNTIME) - backtrack_goto(nullptr), - graph_entry(nullptr), - num_blocks(-1), - num_stack_locals(-1), -#endif - bytecode(bytecode), - num_registers(num_registers) { - } - -#if !defined(DART_PRECOMPILED_RUNTIME) - CompilationResult(IndirectGotoInstr* backtrack_goto, - GraphEntryInstr* graph_entry, - intptr_t num_blocks, - intptr_t num_stack_locals, - intptr_t num_registers) - : error_message(nullptr), - backtrack_goto(backtrack_goto), - graph_entry(graph_entry), - num_blocks(num_blocks), - num_stack_locals(num_stack_locals), - bytecode(nullptr) {} -#endif - - const char* error_message; - - NOT_IN_PRECOMPILED(IndirectGotoInstr* backtrack_goto); - NOT_IN_PRECOMPILED(GraphEntryInstr* graph_entry); - NOT_IN_PRECOMPILED(const intptr_t num_blocks); - NOT_IN_PRECOMPILED(const intptr_t num_stack_locals); - - TypedData* bytecode; - intptr_t num_registers; - }; - -#if !defined(DART_PRECOMPILED_RUNTIME) - static CompilationResult CompileIR( - RegExpCompileData* input, - const ParsedFunction* parsed_function, - const ZoneGrowableArray& ic_data_array, - intptr_t osr_id); -#endif - - static CompilationResult CompileBytecode(RegExpCompileData* data, - const RegExp& regexp, - bool is_one_byte, - bool sticky, - Zone* zone); - - static RegExpPtr CreateRegExp(Thread* thread, + // Set last match info. If match is nullptr, then setting captures is + // omitted. + static ObjectPtr SetLastMatchInfo(Isolate* isolate, + ObjectPtr last_match_info, + const String& subject, + int capture_count, + int32_t* match); + + static bool CompileForTesting(Isolate* isolate, + Zone* zone, + RegExpCompileData* input, + RegExpFlags flags, const String& pattern, - RegExpFlags flags); + const String& sample_subject, + Object& re_data, + bool is_one_byte); - static void DotPrint(const char* label, RegExpNode* node, bool ignore_case); + static void DotPrintForTesting(const char* label, RegExpNode* node); + + static const int kRegExpTooLargeToOptimize = 20 * KB; + + V8_WARN_UNUSED_RESULT + static ObjectPtr ThrowRegExpException(Isolate* isolate, + RegExpFlags flags, + const String& pattern, + RegExpError error); + static void ThrowRegExpException(Isolate* isolate, + const RegExp& re_data, + RegExpError error_text); + + static bool IsUnmodifiedRegExp(Isolate* isolate, const RegExp& regexp); + + static ArrayPtr CreateCaptureNameMap( + Isolate* isolate, + ZoneVector* named_captures); + + static ObjectPtr Interpret(Thread* thread, + const RegExp& regexp, + const String& subject, + int start_index, + bool sticky); }; -void CreateSpecializedFunction(Thread* thread, - Zone* zone, - const RegExp& regexp, - intptr_t specialization_cid, - bool sticky, - const Object& owner); - } // namespace dart -#endif // RUNTIME_VM_REGEXP_REGEXP_H_ +#endif // V8_REGEXP_REGEXP_H_ diff --git a/runtime/vm/regexp/regexp_assembler.cc b/runtime/vm/regexp/regexp_assembler.cc deleted file mode 100644 index 10bfd087aa7..00000000000 --- a/runtime/vm/regexp/regexp_assembler.cc +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#include "vm/regexp/regexp_assembler.h" - -#include "unicode/uchar.h" - -#include "platform/unicode.h" - -#include "vm/flags.h" -#include "vm/regexp/regexp.h" -#include "vm/regexp/unibrow-inl.h" -#include "vm/runtime_entry.h" - -namespace dart { - -void PrintUtf16(uint16_t c) { - const char* format = (0x20 <= c && c <= 0x7F) ? "%c" - : (c <= 0xff) ? "\\x%02x" - : "\\u%04x"; - OS::PrintErr(format, c); -} - -extern "C" uword /*BoolPtr*/ DLRT_CaseInsensitiveCompareUCS2( - uword /*StringPtr*/ str_raw, - uword /*SmiPtr*/ lhs_index_raw, - uword /*SmiPtr*/ rhs_index_raw, - uword /*SmiPtr*/ length_raw) { - const String& str = String::Handle(static_cast(str_raw)); - const Smi& lhs_index = Smi::Handle(static_cast(lhs_index_raw)); - const Smi& rhs_index = Smi::Handle(static_cast(rhs_index_raw)); - const Smi& length = Smi::Handle(static_cast(length_raw)); - - // TODO(zerny): Optimize as single instance. V8 has this as an - // isolate member. - unibrow::Mapping canonicalize; - - for (intptr_t i = 0; i < length.Value(); i++) { - int32_t c1 = str.CharAt(lhs_index.Value() + i); - int32_t c2 = str.CharAt(rhs_index.Value() + i); - if (c1 != c2) { - int32_t s1[1] = {c1}; - canonicalize.get(c1, '\0', s1); - if (s1[0] != c2) { - int32_t s2[1] = {c2}; - canonicalize.get(c2, '\0', s2); - if (s1[0] != s2[0]) { - return static_cast(Bool::False().ptr()); - } - } - } - } - return static_cast(Bool::True().ptr()); -} - -extern "C" uword /*BoolPtr*/ DLRT_CaseInsensitiveCompareUTF16( - uword /*StringPtr*/ str_raw, - uword /*SmiPtr*/ lhs_index_raw, - uword /*SmiPtr*/ rhs_index_raw, - uword /*SmiPtr*/ length_raw) { - const String& str = String::Handle(static_cast(str_raw)); - const Smi& lhs_index = Smi::Handle(static_cast(lhs_index_raw)); - const Smi& rhs_index = Smi::Handle(static_cast(rhs_index_raw)); - const Smi& length = Smi::Handle(static_cast(length_raw)); - - for (intptr_t i = 0; i < length.Value(); i++) { - int32_t c1 = str.CharAt(lhs_index.Value() + i); - int32_t c2 = str.CharAt(rhs_index.Value() + i); - if (Utf16::IsLeadSurrogate(c1)) { - // Non-BMP characters do not have case-equivalents in the BMP. - // Both have to be non-BMP for them to be able to match. - if (!Utf16::IsLeadSurrogate(c2)) - return static_cast(Bool::False().ptr()); - if (i + 1 < length.Value()) { - uint16_t c1t = str.CharAt(lhs_index.Value() + i + 1); - uint16_t c2t = str.CharAt(rhs_index.Value() + i + 1); - if (Utf16::IsTrailSurrogate(c1t) && Utf16::IsTrailSurrogate(c2t)) { - c1 = Utf16::Decode(c1, c1t); - c2 = Utf16::Decode(c2, c2t); - i++; - } - } - } - c1 = u_foldCase(c1, U_FOLD_CASE_DEFAULT); - c2 = u_foldCase(c2, U_FOLD_CASE_DEFAULT); - if (c1 != c2) return static_cast(Bool::False().ptr()); - } - return static_cast(Bool::True().ptr()); -} - -DEFINE_LEAF_RUNTIME_ENTRY(CaseInsensitiveCompareUCS2, - /*argument_count=*/4, - DLRT_CaseInsensitiveCompareUCS2); - -DEFINE_LEAF_RUNTIME_ENTRY(CaseInsensitiveCompareUTF16, - /*argument_count=*/4, - DLRT_CaseInsensitiveCompareUTF16); - -BlockLabel::BlockLabel() { -#if !defined(DART_PRECOMPILED_RUNTIME) - if (!FLAG_interpret_irregexp) { - // Only needed by the compiled IR backend. - block_ = - new JoinEntryInstr(-1, -1, CompilerState::Current().GetNextDeoptId()); - } -#endif -} - -RegExpMacroAssembler::RegExpMacroAssembler(Zone* zone) - : slow_safe_compiler_(false), global_mode_(NOT_GLOBAL), zone_(zone) {} - -RegExpMacroAssembler::~RegExpMacroAssembler() {} - -void RegExpMacroAssembler::CheckNotInSurrogatePair(intptr_t cp_offset, - BlockLabel* on_failure) { - BlockLabel ok; - // Check that current character is not a trail surrogate. - LoadCurrentCharacter(cp_offset, &ok); - CheckCharacterNotInRange(Utf16::kTrailSurrogateStart, - Utf16::kTrailSurrogateEnd, &ok); - // Check that previous character is not a lead surrogate. - LoadCurrentCharacter(cp_offset - 1, &ok); - CheckCharacterInRange(Utf16::kLeadSurrogateStart, Utf16::kLeadSurrogateEnd, - on_failure); - BindBlock(&ok); -} - -} // namespace dart diff --git a/runtime/vm/regexp/regexp_assembler.h b/runtime/vm/regexp/regexp_assembler.h deleted file mode 100644 index 16e7297d5b6..00000000000 --- a/runtime/vm/regexp/regexp_assembler.h +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_H_ -#define RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_H_ - -#include "vm/object.h" - -#if !defined(DART_PRECOMPILED_RUNTIME) -#include "vm/compiler/assembler/assembler.h" -#include "vm/compiler/backend/il.h" -#endif // !defined(DART_PRECOMPILED_RUNTIME) - -namespace dart { - -// Utility function for the DotPrinter -void PrintUtf16(uint16_t c); - -// Compares two-byte strings case insensitively as UCS2. -// Called from generated RegExp code. -extern "C" uword /*BoolPtr*/ DLRT_CaseInsensitiveCompareUCS2( - uword /*StringPtr*/ str_raw, - uword /*SmiPtr*/ lhs_index_raw, - uword /*SmiPtr*/ rhs_index_raw, - uword /*SmiPtr*/ length_raw); - -// Compares two-byte strings case insensitively as UTF16. -// Called from generated RegExp code. -extern "C" uword /*BoolPtr*/ DLRT_CaseInsensitiveCompareUTF16( - uword /*StringPtr*/ str_raw, - uword /*SmiPtr*/ lhs_index_raw, - uword /*SmiPtr*/ rhs_index_raw, - uword /*SmiPtr*/ length_raw); - -/// Convenience wrapper around a BlockEntryInstr pointer. -class BlockLabel : public ValueObject { - // Used by the IR assembler. - public: - BlockLabel(); - ~BlockLabel() { ASSERT(!is_linked()); } - - intptr_t pos() const { return pos_; } - bool is_bound() const { return is_bound_; } - bool is_linked() const { return !is_bound_ && is_linked_; } -#if !defined(DART_PRECOMPILED_RUNTIME) - JoinEntryInstr* block() const { return block_; } -#endif // !defined(DART_PRECOMPILED_RUNTIME) - - void Unuse() { - pos_ = -1; - is_bound_ = false; - is_linked_ = false; - } - - void BindTo(intptr_t pos) { - pos_ = pos; -#if !defined(DART_PRECOMPILED_RUNTIME) - if (block_ != nullptr) block_->set_block_id(pos); -#endif // !defined(DART_PRECOMPILED_RUNTIME) - is_bound_ = true; - is_linked_ = false; - ASSERT(is_bound()); - } - - // Used by bytecode assembler to form a linked list out of - // forward jumps to an unbound label. - void LinkTo(intptr_t pos) { -#if !defined(DART_PRECOMPILED_RUNTIME) - ASSERT(block_ == nullptr); -#endif - ASSERT(!is_bound_); - pos_ = pos; - is_linked_ = true; - } - - // Used by IR builder to mark block label as used. - void SetLinked() { -#if !defined(DART_PRECOMPILED_RUNTIME) - ASSERT(block_ != nullptr); -#endif - if (!is_bound_) { - is_linked_ = true; - } - } - - private: - bool is_bound_ = false; - bool is_linked_ = false; - intptr_t pos_ = -1; -#if !defined(DART_PRECOMPILED_RUNTIME) - JoinEntryInstr* block_ = nullptr; -#endif // !defined(DART_PRECOMPILED_RUNTIME) -}; - -class RegExpMacroAssembler : public ZoneObject { - public: - // The implementation must be able to handle at least: - static constexpr intptr_t kMaxRegister = (1 << 16) - 1; - static constexpr intptr_t kMaxCPOffset = (1 << 15) - 1; - static constexpr intptr_t kMinCPOffset = -(1 << 15); - - static constexpr intptr_t kTableSizeBits = 7; - static constexpr intptr_t kTableSize = 1 << kTableSizeBits; - static constexpr intptr_t kTableMask = kTableSize - 1; - - enum { - kParamRegExpIndex = 0, - kParamStringIndex, - kParamStartOffsetIndex, - kParamCount - }; - - enum IrregexpImplementation { kBytecodeImplementation, kIRImplementation }; - - explicit RegExpMacroAssembler(Zone* zone); - virtual ~RegExpMacroAssembler(); - // The maximal number of pushes between stack checks. Users must supply - // kCheckStackLimit flag to push operations (instead of kNoStackLimitCheck) - // at least once for every stack_limit() pushes that are executed. - virtual intptr_t stack_limit_slack() = 0; - virtual bool CanReadUnaligned() = 0; - virtual void AdvanceCurrentPosition(intptr_t by) = 0; // Signed cp change. - virtual void AdvanceRegister(intptr_t reg, intptr_t by) = 0; // r[reg] += by. - // Continues execution from the position pushed on the top of the backtrack - // stack by an earlier PushBacktrack(BlockLabel*). - virtual void Backtrack() = 0; - virtual void BindBlock(BlockLabel* label) = 0; - virtual void CheckAtStart(BlockLabel* on_at_start) = 0; - // Dispatch after looking the current character up in a 2-bits-per-entry - // map. The destinations vector has up to 4 labels. - virtual void CheckCharacter(unsigned c, BlockLabel* on_equal) = 0; - // Bitwise and the current character with the given constant and then - // check for a match with c. - virtual void CheckCharacterAfterAnd(unsigned c, - unsigned and_with, - BlockLabel* on_equal) = 0; - virtual void CheckCharacterGT(uint16_t limit, BlockLabel* on_greater) = 0; - virtual void CheckCharacterLT(uint16_t limit, BlockLabel* on_less) = 0; - virtual void CheckGreedyLoop(BlockLabel* on_tos_equals_current_position) = 0; - virtual void CheckNotAtStart(intptr_t cp_offset, - BlockLabel* on_not_at_start) = 0; - virtual void CheckNotBackReference(intptr_t start_reg, - bool read_backward, - BlockLabel* on_no_match) = 0; - virtual void CheckNotBackReferenceIgnoreCase(intptr_t start_reg, - bool read_backward, - bool unicode, - BlockLabel* on_no_match) = 0; - // Check the current character for a match with a literal character. If we - // fail to match then goto the on_failure label. End of input always - // matches. If the label is null then we should pop a backtrack address off - // the stack and go to that. - virtual void CheckNotCharacter(unsigned c, BlockLabel* on_not_equal) = 0; - virtual void CheckNotCharacterAfterAnd(unsigned c, - unsigned and_with, - BlockLabel* on_not_equal) = 0; - // Subtract a constant from the current character, then and with the given - // constant and then check for a match with c. - virtual void CheckNotCharacterAfterMinusAnd(uint16_t c, - uint16_t minus, - uint16_t and_with, - BlockLabel* on_not_equal) = 0; - virtual void CheckCharacterInRange(uint16_t from, - uint16_t to, // Both inclusive. - BlockLabel* on_in_range) = 0; - virtual void CheckCharacterNotInRange(uint16_t from, - uint16_t to, // Both inclusive. - BlockLabel* on_not_in_range) = 0; - - // The current character (modulus the kTableSize) is looked up in the byte - // array, and if the found byte is non-zero, we jump to the on_bit_set label. - virtual void CheckBitInTable(const TypedData& table, - BlockLabel* on_bit_set) = 0; - - // Checks for preemption and serves as an OSR entry. - virtual void CheckPreemption(bool is_backtrack) {} - - // Checks whether the given offset from the current position is before - // the end of the string. May overwrite the current character. - virtual void CheckPosition(intptr_t cp_offset, BlockLabel* on_outside_input) { - LoadCurrentCharacter(cp_offset, on_outside_input, true); - } - // Check whether a standard/default character class matches the current - // character. Returns false if the type of special character class does - // not have custom support. - // May clobber the current loaded character. - virtual bool CheckSpecialCharacterClass(uint16_t type, - BlockLabel* on_no_match) { - return false; - } - virtual void Fail() = 0; - // Check whether a register is >= a given constant and go to a label if it - // is. Backtracks instead if the label is nullptr. - virtual void IfRegisterGE(intptr_t reg, - intptr_t comparand, - BlockLabel* if_ge) = 0; - // Check whether a register is < a given constant and go to a label if it is. - // Backtracks instead if the label is nullptr. - virtual void IfRegisterLT(intptr_t reg, - intptr_t comparand, - BlockLabel* if_lt) = 0; - // Check whether a register is == to the current position and go to a - // label if it is. - virtual void IfRegisterEqPos(intptr_t reg, BlockLabel* if_eq) = 0; - virtual IrregexpImplementation Implementation() = 0; - // The assembler is closed, iff there is no current instruction assigned. - virtual bool IsClosed() const = 0; - // Jump to the target label without setting it as the current instruction. - virtual void GoTo(BlockLabel* to) = 0; - virtual void LoadCurrentCharacter(intptr_t cp_offset, - BlockLabel* on_end_of_input, - bool check_bounds = true, - intptr_t characters = 1) = 0; - virtual void PopCurrentPosition() = 0; - virtual void PopRegister(intptr_t register_index) = 0; - // Prints string within the generated code. Used for debugging. - virtual void Print(const char* str) = 0; - // Prints all emitted blocks. - virtual void PrintBlocks() = 0; - // Pushes the label on the backtrack stack, so that a following Backtrack - // will go to this label. Always checks the backtrack stack limit. - virtual void PushBacktrack(BlockLabel* label) = 0; - virtual void PushCurrentPosition() = 0; - virtual void PushRegister(intptr_t register_index) = 0; - virtual void ReadCurrentPositionFromRegister(intptr_t reg) = 0; - virtual void ReadStackPointerFromRegister(intptr_t reg) = 0; - virtual void SetCurrentPositionFromEnd(intptr_t by) = 0; - virtual void SetRegister(intptr_t register_index, intptr_t to) = 0; - // Return whether the matching (with a global regexp) will be restarted. - virtual bool Succeed() = 0; - virtual void WriteCurrentPositionToRegister(intptr_t reg, - intptr_t cp_offset) = 0; - virtual void ClearRegisters(intptr_t reg_from, intptr_t reg_to) = 0; - virtual void WriteStackPointerToRegister(intptr_t reg) = 0; - - // Check that we are not in the middle of a surrogate pair. - void CheckNotInSurrogatePair(intptr_t cp_offset, BlockLabel* on_failure); - - // Controls the generation of large inlined constants in the code. - void set_slow_safe(bool ssc) { slow_safe_compiler_ = ssc; } - bool slow_safe() { return slow_safe_compiler_; } - - enum GlobalMode { - NOT_GLOBAL, - GLOBAL, - GLOBAL_NO_ZERO_LENGTH_CHECK, - GLOBAL_UNICODE - }; - // Set whether the regular expression has the global flag. Exiting due to - // a failure in a global regexp may still mean success overall. - inline void set_global_mode(GlobalMode mode) { global_mode_ = mode; } - inline bool global() { return global_mode_ != NOT_GLOBAL; } - inline bool global_with_zero_length_check() { - return global_mode_ == GLOBAL || global_mode_ == GLOBAL_UNICODE; - } - inline bool global_unicode() { return global_mode_ == GLOBAL_UNICODE; } - - Zone* zone() const { return zone_; } - - private: - bool slow_safe_compiler_; - GlobalMode global_mode_; - Zone* zone_; -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_H_ diff --git a/runtime/vm/regexp/regexp_assembler_bytecode.cc b/runtime/vm/regexp/regexp_assembler_bytecode.cc deleted file mode 100644 index 927bba97b32..00000000000 --- a/runtime/vm/regexp/regexp_assembler_bytecode.cc +++ /dev/null @@ -1,562 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#include "vm/regexp/regexp_assembler_bytecode.h" - -#include "vm/exceptions.h" -#include "vm/object_store.h" -#include "vm/regexp/regexp.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/regexp/regexp_assembler_bytecode_inl.h" -#include "vm/regexp/regexp_bytecodes.h" -#include "vm/regexp/regexp_interpreter.h" -#include "vm/regexp/regexp_parser.h" -#include "vm/timeline.h" - -namespace dart { - -BytecodeRegExpMacroAssembler::BytecodeRegExpMacroAssembler( - ZoneGrowableArray* buffer, - Zone* zone) - : RegExpMacroAssembler(zone), - buffer_(buffer), - pc_(0), - advance_current_end_(kInvalidPC) {} - -BytecodeRegExpMacroAssembler::~BytecodeRegExpMacroAssembler() { - if (backtrack_.is_linked()) backtrack_.Unuse(); -} - -BytecodeRegExpMacroAssembler::IrregexpImplementation -BytecodeRegExpMacroAssembler::Implementation() { - return kBytecodeImplementation; -} - -void BytecodeRegExpMacroAssembler::BindBlock(BlockLabel* l) { - advance_current_end_ = kInvalidPC; - ASSERT(!l->is_bound()); - if (l->is_linked()) { - intptr_t pos = l->pos(); - while (pos != 0) { - intptr_t fixup = pos; - pos = *reinterpret_cast(buffer_->data() + fixup); - *reinterpret_cast(buffer_->data() + fixup) = pc_; - } - } - l->BindTo(pc_); -} - -void BytecodeRegExpMacroAssembler::EmitOrLink(BlockLabel* l) { - if (l == nullptr) l = &backtrack_; - if (l->is_bound()) { - Emit32(l->pos()); - } else { - int pos = 0; - if (l->is_linked()) { - pos = l->pos(); - } - l->LinkTo(pc_); - Emit32(pos); - } -} - -void BytecodeRegExpMacroAssembler::PopRegister(intptr_t register_index) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_POP_REGISTER, register_index); -} - -void BytecodeRegExpMacroAssembler::PushRegister(intptr_t register_index) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_PUSH_REGISTER, register_index); -} - -void BytecodeRegExpMacroAssembler::WriteCurrentPositionToRegister( - intptr_t register_index, - intptr_t cp_offset) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_SET_REGISTER_TO_CP, register_index); - Emit32(cp_offset); // Current position offset. -} - -void BytecodeRegExpMacroAssembler::ClearRegisters(intptr_t reg_from, - intptr_t reg_to) { - ASSERT(reg_from <= reg_to); - for (int reg = reg_from; reg <= reg_to; reg++) { - SetRegister(reg, -1); - } -} - -void BytecodeRegExpMacroAssembler::ReadCurrentPositionFromRegister( - intptr_t register_index) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_SET_CP_TO_REGISTER, register_index); -} - -void BytecodeRegExpMacroAssembler::WriteStackPointerToRegister( - intptr_t register_index) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_SET_REGISTER_TO_SP, register_index); -} - -void BytecodeRegExpMacroAssembler::ReadStackPointerFromRegister( - intptr_t register_index) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_SET_SP_TO_REGISTER, register_index); -} - -void BytecodeRegExpMacroAssembler::SetCurrentPositionFromEnd(intptr_t by) { - ASSERT(Utils::IsUint(24, by)); - Emit(BC_SET_CURRENT_POSITION_FROM_END, by); -} - -void BytecodeRegExpMacroAssembler::SetRegister(intptr_t register_index, - intptr_t to) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_SET_REGISTER, register_index); - Emit32(to); -} - -void BytecodeRegExpMacroAssembler::AdvanceRegister(intptr_t register_index, - intptr_t by) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_ADVANCE_REGISTER, register_index); - Emit32(by); -} - -void BytecodeRegExpMacroAssembler::PopCurrentPosition() { - Emit(BC_POP_CP, 0); -} - -void BytecodeRegExpMacroAssembler::PushCurrentPosition() { - Emit(BC_PUSH_CP, 0); -} - -void BytecodeRegExpMacroAssembler::Backtrack() { - Emit(BC_POP_BT, 0); -} - -void BytecodeRegExpMacroAssembler::GoTo(BlockLabel* l) { - if (advance_current_end_ == pc_) { - // Combine advance current and goto. - pc_ = advance_current_start_; - Emit(BC_ADVANCE_CP_AND_GOTO, advance_current_offset_); - EmitOrLink(l); - advance_current_end_ = kInvalidPC; - } else { - // Regular goto. - Emit(BC_GOTO, 0); - EmitOrLink(l); - } -} - -void BytecodeRegExpMacroAssembler::PushBacktrack(BlockLabel* l) { - Emit(BC_PUSH_BT, 0); - EmitOrLink(l); -} - -bool BytecodeRegExpMacroAssembler::Succeed() { - Emit(BC_SUCCEED, 0); - return false; // Restart matching for global regexp not supported. -} - -void BytecodeRegExpMacroAssembler::Fail() { - Emit(BC_FAIL, 0); -} - -void BytecodeRegExpMacroAssembler::AdvanceCurrentPosition(intptr_t by) { - ASSERT(by >= kMinCPOffset); - ASSERT(by <= kMaxCPOffset); - advance_current_start_ = pc_; - advance_current_offset_ = by; - Emit(BC_ADVANCE_CP, by); - advance_current_end_ = pc_; -} - -void BytecodeRegExpMacroAssembler::CheckGreedyLoop( - BlockLabel* on_tos_equals_current_position) { - Emit(BC_CHECK_GREEDY, 0); - EmitOrLink(on_tos_equals_current_position); -} - -void BytecodeRegExpMacroAssembler::LoadCurrentCharacter(intptr_t cp_offset, - BlockLabel* on_failure, - bool check_bounds, - intptr_t characters) { - ASSERT(cp_offset >= kMinCPOffset); - ASSERT(cp_offset <= kMaxCPOffset); - int bytecode; - if (check_bounds) { - if (characters == 4) { - bytecode = BC_LOAD_4_CURRENT_CHARS; - } else if (characters == 2) { - bytecode = BC_LOAD_2_CURRENT_CHARS; - } else { - ASSERT(characters == 1); - bytecode = BC_LOAD_CURRENT_CHAR; - } - } else { - if (characters == 4) { - bytecode = BC_LOAD_4_CURRENT_CHARS_UNCHECKED; - } else if (characters == 2) { - bytecode = BC_LOAD_2_CURRENT_CHARS_UNCHECKED; - } else { - ASSERT(characters == 1); - bytecode = BC_LOAD_CURRENT_CHAR_UNCHECKED; - } - } - Emit(bytecode, cp_offset); - if (check_bounds) EmitOrLink(on_failure); -} - -void BytecodeRegExpMacroAssembler::CheckCharacterLT(uint16_t limit, - BlockLabel* on_less) { - Emit(BC_CHECK_LT, limit); - EmitOrLink(on_less); -} - -void BytecodeRegExpMacroAssembler::CheckCharacterGT(uint16_t limit, - BlockLabel* on_greater) { - Emit(BC_CHECK_GT, limit); - EmitOrLink(on_greater); -} - -void BytecodeRegExpMacroAssembler::CheckCharacter(uint32_t c, - BlockLabel* on_equal) { - if (c > MAX_FIRST_ARG) { - Emit(BC_CHECK_4_CHARS, 0); - Emit32(c); - } else { - Emit(BC_CHECK_CHAR, c); - } - EmitOrLink(on_equal); -} - -void BytecodeRegExpMacroAssembler::CheckAtStart(BlockLabel* on_at_start) { - Emit(BC_CHECK_AT_START, 0); - EmitOrLink(on_at_start); -} - -void BytecodeRegExpMacroAssembler::CheckNotAtStart( - intptr_t cp_offset, - BlockLabel* on_not_at_start) { - Emit(BC_CHECK_NOT_AT_START, cp_offset); - EmitOrLink(on_not_at_start); -} - -void BytecodeRegExpMacroAssembler::CheckNotCharacter(uint32_t c, - BlockLabel* on_not_equal) { - if (c > MAX_FIRST_ARG) { - Emit(BC_CHECK_NOT_4_CHARS, 0); - Emit32(c); - } else { - Emit(BC_CHECK_NOT_CHAR, c); - } - EmitOrLink(on_not_equal); -} - -void BytecodeRegExpMacroAssembler::CheckCharacterAfterAnd( - uint32_t c, - uint32_t mask, - BlockLabel* on_equal) { - if (c > MAX_FIRST_ARG) { - Emit(BC_AND_CHECK_4_CHARS, 0); - Emit32(c); - } else { - Emit(BC_AND_CHECK_CHAR, c); - } - Emit32(mask); - EmitOrLink(on_equal); -} - -void BytecodeRegExpMacroAssembler::CheckNotCharacterAfterAnd( - uint32_t c, - uint32_t mask, - BlockLabel* on_not_equal) { - if (c > MAX_FIRST_ARG) { - Emit(BC_AND_CHECK_NOT_4_CHARS, 0); - Emit32(c); - } else { - Emit(BC_AND_CHECK_NOT_CHAR, c); - } - Emit32(mask); - EmitOrLink(on_not_equal); -} - -void BytecodeRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd( - uint16_t c, - uint16_t minus, - uint16_t mask, - BlockLabel* on_not_equal) { - Emit(BC_MINUS_AND_CHECK_NOT_CHAR, c); - Emit16(minus); - Emit16(mask); - EmitOrLink(on_not_equal); -} - -void BytecodeRegExpMacroAssembler::CheckCharacterInRange( - uint16_t from, - uint16_t to, - BlockLabel* on_in_range) { - Emit(BC_CHECK_CHAR_IN_RANGE, 0); - Emit16(from); - Emit16(to); - EmitOrLink(on_in_range); -} - -void BytecodeRegExpMacroAssembler::CheckCharacterNotInRange( - uint16_t from, - uint16_t to, - BlockLabel* on_not_in_range) { - Emit(BC_CHECK_CHAR_NOT_IN_RANGE, 0); - Emit16(from); - Emit16(to); - EmitOrLink(on_not_in_range); -} - -void BytecodeRegExpMacroAssembler::CheckBitInTable(const TypedData& table, - BlockLabel* on_bit_set) { - Emit(BC_CHECK_BIT_IN_TABLE, 0); - EmitOrLink(on_bit_set); - for (int i = 0; i < kTableSize; i += kBitsPerByte) { - int byte = 0; - for (int j = 0; j < kBitsPerByte; j++) { - if (table.GetUint8(i + j) != 0) byte |= 1 << j; - } - Emit8(byte); - } -} - -void BytecodeRegExpMacroAssembler::CheckNotBackReference( - intptr_t start_reg, - bool read_backward, - BlockLabel* on_not_equal) { - ASSERT(start_reg >= 0); - ASSERT(start_reg <= kMaxRegister); - Emit(read_backward ? BC_CHECK_NOT_BACK_REF_BACKWARD : BC_CHECK_NOT_BACK_REF, - start_reg); - EmitOrLink(on_not_equal); -} - -void BytecodeRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase( - intptr_t start_reg, - bool read_backward, - bool unicode, - BlockLabel* on_not_equal) { - ASSERT(start_reg >= 0); - ASSERT(start_reg <= kMaxRegister); - Emit(read_backward ? (unicode ? BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD - : BC_CHECK_NOT_BACK_REF_NO_CASE_BACKWARD) - : (unicode ? BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE - : BC_CHECK_NOT_BACK_REF_NO_CASE), - start_reg); - EmitOrLink(on_not_equal); -} - -void BytecodeRegExpMacroAssembler::IfRegisterLT(intptr_t register_index, - intptr_t comparand, - BlockLabel* on_less_than) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_CHECK_REGISTER_LT, register_index); - Emit32(comparand); - EmitOrLink(on_less_than); -} - -void BytecodeRegExpMacroAssembler::IfRegisterGE( - intptr_t register_index, - intptr_t comparand, - BlockLabel* on_greater_or_equal) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_CHECK_REGISTER_GE, register_index); - Emit32(comparand); - EmitOrLink(on_greater_or_equal); -} - -void BytecodeRegExpMacroAssembler::IfRegisterEqPos(intptr_t register_index, - BlockLabel* on_eq) { - ASSERT(register_index >= 0); - ASSERT(register_index <= kMaxRegister); - Emit(BC_CHECK_REGISTER_EQ_POS, register_index); - EmitOrLink(on_eq); -} - -TypedDataPtr BytecodeRegExpMacroAssembler::GetBytecode() { - BindBlock(&backtrack_); - Emit(BC_POP_BT, 0); - - intptr_t len = length(); - const TypedData& bytecode = - TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, len)); - - NoSafepointScope no_safepoint; - memmove(bytecode.DataAddr(0), buffer_->data(), len); - - return bytecode.ptr(); -} - -intptr_t BytecodeRegExpMacroAssembler::length() { - return pc_; -} - -void BytecodeRegExpMacroAssembler::Expand() { - // BOGUS - buffer_->Add(0); - buffer_->Add(0); - buffer_->Add(0); - buffer_->Add(0); - intptr_t x = buffer_->length(); - for (intptr_t i = 0; i < x; i++) - buffer_->Add(0); -} - -static intptr_t Prepare(const RegExp& regexp, - const String& subject, - bool sticky, - Zone* zone) { - bool is_one_byte = subject.IsOneByteString(); - - if (regexp.bytecode(is_one_byte, sticky) == TypedData::null()) { - const String& pattern = String::Handle(zone, regexp.pattern()); -#if defined(SUPPORT_TIMELINE) - TimelineBeginEndScope tbes(Thread::Current(), Timeline::GetCompilerStream(), - "CompileIrregexpBytecode"); - if (tbes.enabled()) { - tbes.SetNumArguments(1); - tbes.CopyArgument(0, "pattern", pattern.ToCString()); - } -#endif // !defined(PRODUCT) - - RegExpCompileData* compile_data = new (zone) RegExpCompileData(); - - // Parsing failures are handled in the RegExp factory constructor. - RegExpParser::ParseRegExp(pattern, regexp.flags(), compile_data); - - regexp.set_num_bracket_expressions(compile_data->capture_count); - regexp.set_capture_name_map(compile_data->capture_name_map); - if (compile_data->simple) { - regexp.set_is_simple(); - } else { - regexp.set_is_complex(); - } - - RegExpEngine::CompilationResult result = RegExpEngine::CompileBytecode( - compile_data, regexp, is_one_byte, sticky, zone); - if (result.error_message != nullptr) { - Exceptions::ThrowUnsupportedError(result.error_message); - } - ASSERT(result.bytecode != nullptr); - ASSERT(regexp.num_registers(is_one_byte) == -1 || - regexp.num_registers(is_one_byte) == result.num_registers); - regexp.set_num_registers(is_one_byte, result.num_registers); - regexp.set_bytecode(is_one_byte, sticky, *(result.bytecode)); - } - - ASSERT(regexp.num_registers(is_one_byte) != -1); - - return regexp.num_registers(is_one_byte) + - (regexp.num_bracket_expressions() + 1) * 2; -} - -static ObjectPtr ExecRaw(const RegExp& regexp, - const String& subject, - int32_t index, - bool sticky, - int32_t* output, - intptr_t output_size, - Zone* zone) { - bool is_one_byte = subject.IsOneByteString(); - - // We must have done EnsureCompiledIrregexp, so we can get the number of - // registers. - int number_of_capture_registers = (regexp.num_bracket_expressions() + 1) * 2; - int32_t* raw_output = &output[number_of_capture_registers]; - - // We do not touch the actual capture result registers until we know there - // has been a match so that we can use those capture results to set the - // last match info. - for (int i = number_of_capture_registers - 1; i >= 0; i--) { - raw_output[i] = -1; - } - - const TypedData& bytecode = - TypedData::Handle(zone, regexp.bytecode(is_one_byte, sticky)); - ASSERT(!bytecode.IsNull()); - const Object& result = Object::Handle( - zone, IrregexpInterpreter::Match(bytecode, subject, raw_output, index)); - - if (result.ptr() == Bool::True().ptr()) { - // Copy capture results to the start of the registers array. - memmove(output, raw_output, number_of_capture_registers * sizeof(int32_t)); - } - if (result.ptr() == Object::null()) { - // Exception during regexp processing - Exceptions::ThrowStackOverflow(); - UNREACHABLE(); - } - return result.ptr(); -} - -ObjectPtr BytecodeRegExpMacroAssembler::Interpret(const RegExp& regexp, - const String& subject, - const Smi& start_index, - bool sticky, - Zone* zone) { - intptr_t required_registers = Prepare(regexp, subject, sticky, zone); - if (required_registers < 0) { - // Compiling failed with an exception. - UNREACHABLE(); - } - - // V8 uses a shared copy on the isolate when smaller than some threshold. - int32_t* output_registers = zone->Alloc(required_registers); - - const Object& result = - Object::Handle(zone, ExecRaw(regexp, subject, start_index.Value(), sticky, - output_registers, required_registers, zone)); - if (result.ptr() == Bool::True().ptr()) { - intptr_t capture_count = regexp.num_bracket_expressions(); - intptr_t capture_register_count = (capture_count + 1) * 2; - ASSERT(required_registers >= capture_register_count); - - const TypedData& result = TypedData::Handle( - TypedData::New(kTypedDataInt32ArrayCid, capture_register_count)); - { -#ifdef DEBUG - // These indices will be used with substring operations that don't check - // bounds, so sanity check them here. - for (intptr_t i = 0; i < capture_register_count; i++) { - int32_t val = output_registers[i]; - ASSERT(val == -1 || (val >= 0 && val <= subject.Length())); - } -#endif - - NoSafepointScope no_safepoint; - memmove(result.DataAddr(0), output_registers, - capture_register_count * sizeof(int32_t)); - } - - return result.ptr(); - } - if (result.ptr() == Object::null()) { - // internal exception - UNREACHABLE(); - } - if (result.IsError()) { - Exceptions::PropagateError(Error::Cast(result)); - UNREACHABLE(); - } - ASSERT(result.ptr() == Bool::False().ptr()); - return Instance::null(); -} - -} // namespace dart diff --git a/runtime/vm/regexp/regexp_assembler_bytecode.h b/runtime/vm/regexp/regexp_assembler_bytecode.h deleted file mode 100644 index bbe18ebfd6b..00000000000 --- a/runtime/vm/regexp/regexp_assembler_bytecode.h +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_H_ -#define RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_H_ - -#include "vm/object.h" -#include "vm/regexp/regexp_assembler.h" - -namespace dart { - -class BytecodeRegExpMacroAssembler : public RegExpMacroAssembler { - public: - // Create an assembler. Instructions and relocation information are emitted - // into a buffer, with the instructions starting from the beginning and the - // relocation information starting from the end of the buffer. See CodeDesc - // for a detailed comment on the layout (globals.h). - // - // If the provided buffer is null, the assembler allocates and grows its own - // buffer, and buffer_size determines the initial buffer size. The buffer is - // owned by the assembler and deallocated upon destruction of the assembler. - // - // If the provided buffer is not null, the assembler uses the provided buffer - // for code generation and assumes its size to be buffer_size. If the buffer - // is too small, a fatal error occurs. No deallocation of the buffer is done - // upon destruction of the assembler. - BytecodeRegExpMacroAssembler(ZoneGrowableArray* buffer, Zone* zone); - virtual ~BytecodeRegExpMacroAssembler(); - - // The byte-code interpreter checks on each push anyway. - virtual intptr_t stack_limit_slack() { return 1; } - virtual bool CanReadUnaligned() { return false; } - virtual void BindBlock(BlockLabel* label); - virtual void AdvanceCurrentPosition(intptr_t by); // Signed cp change. - virtual void PopCurrentPosition(); - virtual void PushCurrentPosition(); - virtual void Backtrack(); - virtual void GoTo(BlockLabel* label); - virtual void PushBacktrack(BlockLabel* label); - virtual bool Succeed(); - virtual void Fail(); - virtual void PopRegister(intptr_t register_index); - virtual void PushRegister(intptr_t register_index); - virtual void AdvanceRegister(intptr_t reg, intptr_t by); // r[reg] += by. - virtual void SetCurrentPositionFromEnd(intptr_t by); - virtual void SetRegister(intptr_t register_index, intptr_t to); - virtual void WriteCurrentPositionToRegister(intptr_t reg, intptr_t cp_offset); - virtual void ClearRegisters(intptr_t reg_from, intptr_t reg_to); - virtual void ReadCurrentPositionFromRegister(intptr_t reg); - virtual void WriteStackPointerToRegister(intptr_t reg); - virtual void ReadStackPointerFromRegister(intptr_t reg); - virtual void LoadCurrentCharacter(intptr_t cp_offset, - BlockLabel* on_end_of_input, - bool check_bounds = true, - intptr_t characters = 1); - virtual void CheckCharacter(unsigned c, BlockLabel* on_equal); - virtual void CheckCharacterAfterAnd(unsigned c, - unsigned mask, - BlockLabel* on_equal); - virtual void CheckCharacterGT(uint16_t limit, BlockLabel* on_greater); - virtual void CheckCharacterLT(uint16_t limit, BlockLabel* on_less); - virtual void CheckGreedyLoop(BlockLabel* on_tos_equals_current_position); - virtual void CheckAtStart(BlockLabel* on_at_start); - virtual void CheckNotAtStart(intptr_t cp_offset, BlockLabel* on_not_at_start); - virtual void CheckNotCharacter(unsigned c, BlockLabel* on_not_equal); - virtual void CheckNotCharacterAfterAnd(unsigned c, - unsigned mask, - BlockLabel* on_not_equal); - virtual void CheckNotCharacterAfterMinusAnd(uint16_t c, - uint16_t minus, - uint16_t mask, - BlockLabel* on_not_equal); - virtual void CheckCharacterInRange(uint16_t from, - uint16_t to, - BlockLabel* on_in_range); - virtual void CheckCharacterNotInRange(uint16_t from, - uint16_t to, - BlockLabel* on_not_in_range); - virtual void CheckBitInTable(const TypedData& table, BlockLabel* on_bit_set); - virtual void CheckNotBackReference(intptr_t start_reg, - bool read_backward, - BlockLabel* on_no_match); - virtual void CheckNotBackReferenceIgnoreCase(intptr_t start_reg, - bool read_backward, - bool unicode, - BlockLabel* on_no_match); - virtual void IfRegisterLT(intptr_t register_index, - intptr_t comparand, - BlockLabel* if_lt); - virtual void IfRegisterGE(intptr_t register_index, - intptr_t comparand, - BlockLabel* if_ge); - virtual void IfRegisterEqPos(intptr_t register_index, BlockLabel* if_eq); - - virtual IrregexpImplementation Implementation(); - // virtual Handle GetCode(Handle source); - TypedDataPtr GetBytecode(); - - // New - virtual bool IsClosed() const { - // Added by Dart for the IR version. Bytecode version should never need an - // extra goto. - return true; - } - virtual void Print(const char* str) { UNIMPLEMENTED(); } - virtual void PrintBlocks() { UNIMPLEMENTED(); } - ///// - - static ObjectPtr Interpret(const RegExp& regexp, - const String& str, - const Smi& start_index, - bool is_sticky, - Zone* zone); - - private: - void Expand(); - // Code and bitmap emission. - inline void EmitOrLink(BlockLabel* label); - inline void Emit32(uint32_t x); - inline void Emit16(uint32_t x); - inline void Emit8(uint32_t x); - inline void Emit(uint32_t bc, uint32_t arg); - // Bytecode buffer. - intptr_t length(); - - // The buffer into which code and relocation info are generated. - ZoneGrowableArray* buffer_; - - // The program counter. - intptr_t pc_; - - BlockLabel backtrack_; - - intptr_t advance_current_start_; - intptr_t advance_current_offset_; - intptr_t advance_current_end_; - - static constexpr int kInvalidPC = -1; - - DISALLOW_IMPLICIT_CONSTRUCTORS(BytecodeRegExpMacroAssembler); -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_H_ diff --git a/runtime/vm/regexp/regexp_assembler_bytecode_inl.h b/runtime/vm/regexp/regexp_assembler_bytecode_inl.h deleted file mode 100644 index a05c788fba8..00000000000 --- a/runtime/vm/regexp/regexp_assembler_bytecode_inl.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -// A light-weight assembler for the Irregexp byte code. - -#include "vm/regexp/regexp_bytecodes.h" - -#ifndef RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_INL_H_ -#define RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_INL_H_ - -namespace dart { - -void BytecodeRegExpMacroAssembler::Emit(uint32_t byte, - uint32_t twenty_four_bits) { - uint32_t word = ((twenty_four_bits << BYTECODE_SHIFT) | byte); - ASSERT(pc_ <= buffer_->length()); - if (pc_ + 3 >= buffer_->length()) { - Expand(); - } - *reinterpret_cast(buffer_->data() + pc_) = word; - pc_ += 4; -} - -void BytecodeRegExpMacroAssembler::Emit16(uint32_t word) { - ASSERT(pc_ <= buffer_->length()); - if (pc_ + 1 >= buffer_->length()) { - Expand(); - } - *reinterpret_cast(buffer_->data() + pc_) = word; - pc_ += 2; -} - -void BytecodeRegExpMacroAssembler::Emit8(uint32_t word) { - ASSERT(pc_ <= buffer_->length()); - if (pc_ == buffer_->length()) { - Expand(); - } - *reinterpret_cast(buffer_->data() + pc_) = word; - pc_ += 1; -} - -void BytecodeRegExpMacroAssembler::Emit32(uint32_t word) { - ASSERT(pc_ <= buffer_->length()); - if (pc_ + 3 >= buffer_->length()) { - Expand(); - } - *reinterpret_cast(buffer_->data() + pc_) = word; - pc_ += 4; -} - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_INL_H_ diff --git a/runtime/vm/regexp/regexp_assembler_ir.cc b/runtime/vm/regexp/regexp_assembler_ir.cc deleted file mode 100644 index 72755f217ad..00000000000 --- a/runtime/vm/regexp/regexp_assembler_ir.cc +++ /dev/null @@ -1,1740 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#if !defined(DART_PRECOMPILED_RUNTIME) - -#include "vm/regexp/regexp_assembler_ir.h" - -#include - -#include "platform/unicode.h" -#include "vm/bit_vector.h" -#include "vm/compiler/backend/il_printer.h" -#include "vm/compiler/frontend/flow_graph_builder.h" -#include "vm/compiler/jit/compiler.h" -#include "vm/compiler/runtime_api.h" -#include "vm/dart_entry.h" -#include "vm/longjump.h" -#include "vm/object_store.h" -#include "vm/regexp/regexp.h" -#include "vm/resolver.h" -#include "vm/runtime_entry.h" -#include "vm/stack_frame.h" - -#define Z zone() - -// Debugging output macros. TAG() is called at the head of each interesting -// function and prints its name during execution if irregexp tracing is enabled. -#define TAG() \ - if (FLAG_trace_irregexp) { \ - TAG_(); \ - } -#define TAG_() \ - Print(Bind(new (Z) ConstantInstr(String::ZoneHandle( \ - Z, Symbols::FromConcat(thread_, String::Handle(String::New("TAG: ")), \ - String::Handle(String::New(__FUNCTION__))))))); - -#define PRINT(arg) \ - if (FLAG_trace_irregexp) { \ - Print(arg); \ - } - -namespace dart { - -/* - * This assembler uses the following main local variables: - * - stack_: A pointer to a growable list which we use as an all-purpose stack - * storing backtracking offsets, positions & stored register values. - * - current_character_: Stores the currently loaded characters (possibly more - * than one). - * - current_position_: The current position within the string, stored as a - * negative offset from the end of the string (i.e. the - * position corresponding to str[0] is -str.length). - * Note that current_position_ is *not* byte-based, unlike - * original V8 code. - * - * Results are returned though an array of capture indices, stored at - * matches_param_. A null array specifies a failure to match. The match indices - * [start_inclusive, end_exclusive] for capture group i are stored at positions - * matches_param_[i * 2] and matches_param_[i * 2 + 1], respectively. Match - * indices of -1 denote non-matched groups. Note that we store these indices - * as a negative offset from the end of the string in registers_array_ - * during processing, and convert them to standard indexes when copying them - * to matches_param_ on successful match. - */ -IRRegExpMacroAssembler::IRRegExpMacroAssembler( - intptr_t specialization_cid, - intptr_t capture_count, - const ParsedFunction* parsed_function, - const ZoneGrowableArray& ic_data_array, - intptr_t osr_id, - Zone* zone) - : RegExpMacroAssembler(zone), - thread_(Thread::Current()), - specialization_cid_(specialization_cid), - parsed_function_(parsed_function), - ic_data_array_(ic_data_array), - current_instruction_(nullptr), - stack_(nullptr), - stack_pointer_(nullptr), - current_character_(nullptr), - current_position_(nullptr), - string_param_(nullptr), - string_param_length_(nullptr), - start_index_param_(nullptr), - registers_count_(0), - saved_registers_count_((capture_count + 1) * 2), - // B0 is taken by GraphEntry thus block ids must start at 1. - block_id_(1) { - switch (specialization_cid) { - case kOneByteStringCid: - mode_ = ASCII; - break; - case kTwoByteStringCid: - mode_ = UC16; - break; - default: - UNREACHABLE(); - } - - InitializeLocals(); - - // Create and generate all preset blocks. - entry_block_ = new (zone) GraphEntryInstr(*parsed_function_, osr_id); - - auto function_entry = new (zone) FunctionEntryInstr( - entry_block_, block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - entry_block_->set_normal_entry(function_entry); - - start_block_ = new (zone) - JoinEntryInstr(block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - success_block_ = new (zone) - JoinEntryInstr(block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - backtrack_block_ = new (zone) - JoinEntryInstr(block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - exit_block_ = new (zone) - JoinEntryInstr(block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - - GenerateEntryBlock(); - GenerateSuccessBlock(); - GenerateExitBlock(); - - blocks_.Add(entry_block_); - blocks_.Add(entry_block_->normal_entry()); - blocks_.Add(start_block_); - blocks_.Add(success_block_); - blocks_.Add(backtrack_block_); - blocks_.Add(exit_block_); - - // Begin emission at the start_block_. - set_current_instruction(start_block_); -} - -IRRegExpMacroAssembler::~IRRegExpMacroAssembler() {} - -void IRRegExpMacroAssembler::InitializeLocals() { - // All generated functions are expected to have a current-context variable. - // This variable is unused in irregexp functions. - parsed_function_->current_context_var()->set_index( - VariableIndex(GetNextLocalIndex())); - - // Create local variables and parameters. - stack_ = Local(Symbols::stack()); - stack_pointer_ = Local(Symbols::stack_pointer()); - registers_ = Local(Symbols::position_registers()); - current_character_ = Local(Symbols::current_character()); - current_position_ = Local(Symbols::current_position()); - string_param_length_ = Local(Symbols::string_param_length()); - capture_length_ = Local(Symbols::capture_length()); - match_start_index_ = Local(Symbols::match_start_index()); - capture_start_index_ = Local(Symbols::capture_start_index()); - match_end_index_ = Local(Symbols::match_end_index()); - char_in_capture_ = Local(Symbols::char_in_capture()); - char_in_match_ = Local(Symbols::char_in_match()); - index_temp_ = Local(Symbols::index_temp()); - result_ = Local(Symbols::c_result()); - - string_param_ = Parameter(Symbols::string_param(), - RegExpMacroAssembler::kParamStringIndex); - start_index_param_ = Parameter(Symbols::start_index_param(), - RegExpMacroAssembler::kParamStartOffsetIndex); -} - -void IRRegExpMacroAssembler::GenerateEntryBlock() { - set_current_instruction(entry_block_->normal_entry()); - TAG(); - - // Store string.length. - Value* string_push = PushLocal(string_param_); - - StoreLocal(string_param_length_, - Bind(InstanceCall(InstanceCallDescriptor(String::ZoneHandle( - Field::GetterSymbol(Symbols::Length()))), - string_push))); - - // Store (start_index - string.length) as the current position (since it's a - // negative offset from the end of the string). - Value* start_index_push = PushLocal(start_index_param_); - Value* length_push = PushLocal(string_param_length_); - - StoreLocal(current_position_, Bind(Sub(start_index_push, length_push))); - - { - const Library& lib = Library::Handle(Library::CoreLibrary()); - const Class& regexp_class = - Class::Handle(lib.LookupClassAllowPrivate(Symbols::_RegExp())); - const Function& get_registers_function = Function::ZoneHandle( - Z, regexp_class.LookupFunctionAllowPrivate(Symbols::_getRegisters())); - - // The "0" placeholder constant will be replaced with correct value - // determined at the end of regexp graph construction in Finalization. - num_registers_constant_instr = - new (Z) ConstantInstr(Integer::ZoneHandle(Z, Integer::NewCanonical(0))); - StoreLocal(registers_, Bind(StaticCall(get_registers_function, - Bind(num_registers_constant_instr), - ICData::kStatic))); - - const Function& get_backtracking_stack_function = - Function::ZoneHandle(Z, regexp_class.LookupFunctionAllowPrivate( - Symbols::_getBacktrackingStack())); - StoreLocal(stack_, Bind(StaticCall(get_backtracking_stack_function, - ICData::kStatic))); - } - ClearRegisters(0, saved_registers_count_ - 1); - - StoreLocal(stack_pointer_, Bind(Int64Constant(-1))); - - // Jump to the start block. - current_instruction_->Goto(start_block_); -} - -void IRRegExpMacroAssembler::GenerateBacktrackBlock() { - set_current_instruction(backtrack_block_); - TAG(); - CheckPreemption(/*is_backtrack=*/true); - - const intptr_t entries_count = entry_block_->indirect_entries().length(); - - Value* block_id_push = Bind(PopStack()); - backtrack_goto_ = new (Z) IndirectGotoInstr(entries_count, block_id_push); - CloseBlockWith(backtrack_goto_); - - // Add an edge from the "indirect" goto to each of the targets. - for (intptr_t j = 0; j < entries_count; j++) { - backtrack_goto_->AddSuccessor( - TargetWithJoinGoto(entry_block_->indirect_entries().At(j))); - } -} - -void IRRegExpMacroAssembler::GenerateSuccessBlock() { - set_current_instruction(success_block_); - TAG(); - - Value* type = Bind(new (Z) ConstantInstr(TypeArguments::ZoneHandle( - Z, IsolateGroup::Current()->object_store()->type_argument_int()))); - Value* length = Bind(Uint64Constant(saved_registers_count_)); - Value* array = Bind(new (Z) CreateArrayInstr(InstructionSource(), type, - length, GetNextDeoptId())); - StoreLocal(result_, array); - - // Store captured offsets in the `matches` parameter. - for (intptr_t i = 0; i < saved_registers_count_; i++) { - Value* matches_push = PushLocal(result_); - Value* index_push = Bind(Uint64Constant(i)); - - // Convert negative offsets from the end of the string to string indices. - // TODO(zerny): use positive offsets from the get-go. - Value* offset_push = LoadRegister(i); - Value* len_push = PushLocal(string_param_length_); - Value* value_push = Bind(Add(offset_push, len_push)); - - Do(InstanceCall(InstanceCallDescriptor::FromToken(Token::kASSIGN_INDEX), - matches_push, index_push, value_push)); - } - - // Print the result if tracing. - PRINT(PushLocal(result_)); - - // Return true on success. - AppendInstruction(new (Z) DartReturnInstr( - InstructionSource(), Bind(LoadLocal(result_)), GetNextDeoptId())); -} - -void IRRegExpMacroAssembler::GenerateExitBlock() { - set_current_instruction(exit_block_); - TAG(); - - // Return false on failure. - AppendInstruction(new (Z) DartReturnInstr( - InstructionSource(), Bind(LoadLocal(result_)), GetNextDeoptId())); -} - -void IRRegExpMacroAssembler::FinalizeRegistersArray() { - ASSERT(registers_count_ >= saved_registers_count_); - - ConstantInstr* new_constant = Int64Constant(registers_count_); - new_constant->set_temp_index(num_registers_constant_instr->temp_index()); - num_registers_constant_instr->ReplaceWith(new_constant, /*iterator=*/nullptr); -} - -bool IRRegExpMacroAssembler::CanReadUnaligned() { - return !slow_safe(); -} - -ArrayPtr IRRegExpMacroAssembler::Execute(const RegExp& regexp, - const String& input, - const Smi& start_offset, - bool sticky, - Zone* zone) { - const intptr_t cid = input.GetClassId(); - const Function& fun = Function::Handle(regexp.function(cid, sticky)); - ASSERT(!fun.IsNull()); - // Create the argument list. - const Array& args = - Array::Handle(Array::New(RegExpMacroAssembler::kParamCount)); - args.SetAt(RegExpMacroAssembler::kParamRegExpIndex, regexp); - args.SetAt(RegExpMacroAssembler::kParamStringIndex, input); - args.SetAt(RegExpMacroAssembler::kParamStartOffsetIndex, start_offset); - - // And finally call the generated code. - - const Object& retval = - Object::Handle(zone, DartEntry::InvokeFunction(fun, args)); - if (retval.IsLanguageError()) { - Exceptions::ThrowCompileTimeError(LanguageError::Cast(retval)); - UNREACHABLE(); - } - if (retval.IsError()) { - Exceptions::PropagateError(Error::Cast(retval)); - } - - if (retval.IsNull()) { - return Array::null(); - } - - ASSERT(retval.IsArray()); - return Array::Cast(retval).ptr(); -} - -LocalVariable* IRRegExpMacroAssembler::Parameter(const String& name, - intptr_t index) const { - LocalVariable* local = - new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, - name, Object::dynamic_type()); - - intptr_t param_frame_index = kParamCount - index; - local->set_index(VariableIndex(param_frame_index)); - - return local; -} - -LocalVariable* IRRegExpMacroAssembler::Local(const String& name) { - LocalVariable* local = - new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, - name, Object::dynamic_type()); - local->set_index(VariableIndex(GetNextLocalIndex())); - - return local; -} - -ConstantInstr* IRRegExpMacroAssembler::Int64Constant(int64_t value) const { - return new (Z) - ConstantInstr(Integer::ZoneHandle(Z, Integer::NewCanonical(value))); -} - -ConstantInstr* IRRegExpMacroAssembler::Uint64Constant(uint64_t value) const { - ASSERT(value < static_cast(kMaxInt64)); - return Int64Constant(static_cast(value)); -} - -ConstantInstr* IRRegExpMacroAssembler::BoolConstant(bool value) const { - return new (Z) ConstantInstr(value ? Bool::True() : Bool::False()); -} - -ConstantInstr* IRRegExpMacroAssembler::StringConstant(const char* value) const { - return new (Z) - ConstantInstr(String::ZoneHandle(Z, String::New(value, Heap::kOld))); -} - -ConstantInstr* IRRegExpMacroAssembler::WordCharacterMapConstant() const { - const Library& lib = Library::Handle(Z, Library::CoreLibrary()); - const Class& regexp_class = - Class::Handle(Z, lib.LookupClassAllowPrivate(Symbols::_RegExp())); - const Field& word_character_field = Field::ZoneHandle( - Z, - regexp_class.LookupStaticFieldAllowPrivate(Symbols::_wordCharacterMap())); - ASSERT(!word_character_field.IsNull()); - - DEBUG_ASSERT(Thread::Current()->TopErrorHandlerIsSetJump()); - - const auto& value = - Object::Handle(Z, word_character_field.StaticConstFieldValue()); - if (value.IsError()) { - Report::LongJump(Error::Cast(value)); - } - return new (Z) - ConstantInstr(Instance::ZoneHandle(Z, Instance::RawCast(value.ptr()))); -} - -ComparisonInstr* IRRegExpMacroAssembler::Comparison(ComparisonKind kind, - Value* lhs, - Value* rhs) { - Token::Kind strict_comparison = Token::kEQ_STRICT; - Token::Kind intermediate_operator = Token::kILLEGAL; - switch (kind) { - case kEQ: - intermediate_operator = Token::kEQ; - break; - case kNE: - intermediate_operator = Token::kEQ; - strict_comparison = Token::kNE_STRICT; - break; - case kLT: - intermediate_operator = Token::kLT; - break; - case kGT: - intermediate_operator = Token::kGT; - break; - case kLTE: - intermediate_operator = Token::kLTE; - break; - case kGTE: - intermediate_operator = Token::kGTE; - break; - default: - UNREACHABLE(); - } - - ASSERT(intermediate_operator != Token::kILLEGAL); - - Value* lhs_value = Bind(InstanceCall( - InstanceCallDescriptor::FromToken(intermediate_operator), lhs, rhs)); - Value* rhs_value = Bind(BoolConstant(true)); - - return new (Z) - StrictCompareInstr(InstructionSource(), strict_comparison, lhs_value, - rhs_value, true, GetNextDeoptId()); -} - -ComparisonInstr* IRRegExpMacroAssembler::Comparison(ComparisonKind kind, - Definition* lhs, - Definition* rhs) { - Value* lhs_push = Bind(lhs); - Value* rhs_push = Bind(rhs); - return Comparison(kind, lhs_push, rhs_push); -} - -StaticCallInstr* IRRegExpMacroAssembler::StaticCall( - const Function& function, - ICData::RebindRule rebind_rule) const { - InputsArray arguments(Z, 0); - return StaticCall(function, std::move(arguments), rebind_rule); -} - -StaticCallInstr* IRRegExpMacroAssembler::StaticCall( - const Function& function, - Value* arg1, - ICData::RebindRule rebind_rule) const { - InputsArray arguments(Z, 1); - arguments.Add(arg1); - - return StaticCall(function, std::move(arguments), rebind_rule); -} - -StaticCallInstr* IRRegExpMacroAssembler::StaticCall( - const Function& function, - Value* arg1, - Value* arg2, - ICData::RebindRule rebind_rule) const { - InputsArray arguments(Z, 2); - arguments.Add(arg1); - arguments.Add(arg2); - - return StaticCall(function, std::move(arguments), rebind_rule); -} - -StaticCallInstr* IRRegExpMacroAssembler::StaticCall( - const Function& function, - InputsArray&& arguments, - ICData::RebindRule rebind_rule) const { - const intptr_t kTypeArgsLen = 0; - return new (Z) StaticCallInstr(InstructionSource(), function, kTypeArgsLen, - Object::null_array(), std::move(arguments), - ic_data_array_, GetNextDeoptId(), rebind_rule); -} - -InstanceCallInstr* IRRegExpMacroAssembler::InstanceCall( - const InstanceCallDescriptor& desc, - Value* arg1) const { - InputsArray arguments(Z, 1); - arguments.Add(arg1); - - return InstanceCall(desc, std::move(arguments)); -} - -InstanceCallInstr* IRRegExpMacroAssembler::InstanceCall( - const InstanceCallDescriptor& desc, - Value* arg1, - Value* arg2) const { - InputsArray arguments(Z, 2); - arguments.Add(arg1); - arguments.Add(arg2); - - return InstanceCall(desc, std::move(arguments)); -} - -InstanceCallInstr* IRRegExpMacroAssembler::InstanceCall( - const InstanceCallDescriptor& desc, - Value* arg1, - Value* arg2, - Value* arg3) const { - InputsArray arguments(Z, 3); - arguments.Add(arg1); - arguments.Add(arg2); - arguments.Add(arg3); - - return InstanceCall(desc, std::move(arguments)); -} - -InstanceCallInstr* IRRegExpMacroAssembler::InstanceCall( - const InstanceCallDescriptor& desc, - InputsArray&& arguments) const { - const intptr_t kTypeArgsLen = 0; - return new (Z) InstanceCallInstr( - InstructionSource(), desc.name, desc.token_kind, std::move(arguments), - kTypeArgsLen, Object::null_array(), desc.checked_argument_count, - ic_data_array_, GetNextDeoptId()); -} - -LoadLocalInstr* IRRegExpMacroAssembler::LoadLocal(LocalVariable* local) const { - return new (Z) LoadLocalInstr(*local, InstructionSource()); -} - -void IRRegExpMacroAssembler::StoreLocal(LocalVariable* local, Value* value) { - Do(new (Z) StoreLocalInstr(*local, value, InstructionSource())); -} - -LoadStaticFieldInstr* IRRegExpMacroAssembler::LoadStaticField( - const Field& field, - bool calls_initializer) const { - return new (Z) LoadStaticFieldInstr( - field, InstructionSource(), - calls_initializer ? SlowPathOnSentinelValue::kCallInitializer - : SlowPathOnSentinelValue::kDoNothing, - GetNextDeoptId()); -} - -void IRRegExpMacroAssembler::set_current_instruction(Instruction* instruction) { - current_instruction_ = instruction; -} - -Value* IRRegExpMacroAssembler::Bind(Definition* definition) { - AppendInstruction(definition); - definition->set_temp_index(temp_id_.Alloc()); - - return new (Z) Value(definition); -} - -void IRRegExpMacroAssembler::Do(Definition* definition) { - AppendInstruction(definition); -} - -Value* IRRegExpMacroAssembler::BindLoadLocal(const LocalVariable& local) { - ASSERT(!local.is_captured()); - return Bind(new (Z) LoadLocalInstr(local, InstructionSource())); -} - -// In some cases, the V8 irregexp engine generates unreachable code by emitting -// a jmp not followed by a bind. We cannot do the same, since it is impossible -// to append to a block following a jmp. In such cases, assume that we are doing -// the correct thing, but output a warning when tracing. -#define HANDLE_DEAD_CODE_EMISSION() \ - if (current_instruction_ == nullptr) { \ - if (FLAG_trace_irregexp) { \ - OS::PrintErr( \ - "WARNING: Attempting to append to a closed assembler. " \ - "This could be either a bug or generation of dead code " \ - "inherited from V8.\n"); \ - } \ - BlockLabel dummy; \ - BindBlock(&dummy); \ - } - -void IRRegExpMacroAssembler::AppendInstruction(Instruction* instruction) { - HANDLE_DEAD_CODE_EMISSION(); - - ASSERT(current_instruction_ != nullptr); - ASSERT(current_instruction_->next() == nullptr); - - temp_id_.Dealloc(instruction->InputCount()); - - current_instruction_->LinkTo(instruction); - set_current_instruction(instruction); -} - -void IRRegExpMacroAssembler::CloseBlockWith(Instruction* instruction) { - HANDLE_DEAD_CODE_EMISSION(); - - ASSERT(current_instruction_ != nullptr); - ASSERT(current_instruction_->next() == nullptr); - - temp_id_.Dealloc(instruction->InputCount()); - - current_instruction_->LinkTo(instruction); - set_current_instruction(nullptr); -} - -void IRRegExpMacroAssembler::GoTo(BlockLabel* to) { - if (to == nullptr) { - Backtrack(); - } else { - to->SetLinked(); - GoTo(to->block()); - } -} - -// Closes the current block with a goto, and unsets current_instruction_. -// BindBlock() must be called before emission can continue. -void IRRegExpMacroAssembler::GoTo(JoinEntryInstr* to) { - HANDLE_DEAD_CODE_EMISSION(); - - ASSERT(current_instruction_ != nullptr); - ASSERT(current_instruction_->next() == nullptr); - current_instruction_->Goto(to); - set_current_instruction(nullptr); -} - -Value* IRRegExpMacroAssembler::PushLocal(LocalVariable* local) { - return Bind(LoadLocal(local)); -} - -void IRRegExpMacroAssembler::Print(const char* str) { - Print(Bind(new (Z) ConstantInstr( - String::ZoneHandle(Z, String::New(str, Heap::kOld))))); -} - -void IRRegExpMacroAssembler::Print(Value* argument) { - const Library& lib = Library::Handle(Library::CoreLibrary()); - const Function& print_fn = - Function::ZoneHandle(Z, lib.LookupFunctionAllowPrivate(Symbols::print())); - Do(StaticCall(print_fn, argument, ICData::kStatic)); -} - -void IRRegExpMacroAssembler::PrintBlocks() { - for (intptr_t i = 0; i < blocks_.length(); i++) { - FlowGraphPrinter::PrintBlock(blocks_[i], false); - } -} - -intptr_t IRRegExpMacroAssembler::stack_limit_slack() { - return 32; -} - -void IRRegExpMacroAssembler::AdvanceCurrentPosition(intptr_t by) { - TAG(); - if (by != 0) { - Value* cur_pos_push = PushLocal(current_position_); - Value* by_push = Bind(Int64Constant(by)); - - Value* new_pos_value = Bind(Add(cur_pos_push, by_push)); - StoreLocal(current_position_, new_pos_value); - } -} - -void IRRegExpMacroAssembler::AdvanceRegister(intptr_t reg, intptr_t by) { - TAG(); - ASSERT(reg >= 0); - ASSERT(reg < registers_count_); - - if (by != 0) { - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(reg); - Value* reg_push = LoadRegister(reg); - Value* by_push = Bind(Int64Constant(by)); - Value* value_push = Bind(Add(reg_push, by_push)); - StoreRegister(registers_push, index_push, value_push); - } -} - -void IRRegExpMacroAssembler::Backtrack() { - TAG(); - GoTo(backtrack_block_); -} - -// A BindBlock is analogous to assigning a label to a basic block. -// If the BlockLabel does not yet contain a block, it is created. -// If there is a current instruction, append a goto to the bound block. -void IRRegExpMacroAssembler::BindBlock(BlockLabel* label) { - ASSERT(!label->is_bound()); - ASSERT(label->block()->next() == nullptr); - - label->BindTo(block_id_.Alloc()); - blocks_.Add(label->block()); - - if (current_instruction_ != nullptr) { - GoTo(label); - } - set_current_instruction(label->block()); - - // Print the id of the current block if tracing. - PRINT(Bind(Uint64Constant(label->block()->block_id()))); -} - -intptr_t IRRegExpMacroAssembler::GetNextLocalIndex() { - intptr_t id = local_id_.Alloc(); - return -id; -} - -Value* IRRegExpMacroAssembler::LoadRegister(intptr_t index) { - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(index); - return Bind(InstanceCall(InstanceCallDescriptor::FromToken(Token::kINDEX), - registers_push, index_push)); -} - -void IRRegExpMacroAssembler::StoreRegister(intptr_t index, intptr_t value) { - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(index); - Value* value_push = Bind(Uint64Constant(value)); - StoreRegister(registers_push, index_push, value_push); -} - -void IRRegExpMacroAssembler::StoreRegister(Value* registers, - Value* index, - Value* value) { - TAG(); - Do(InstanceCall(InstanceCallDescriptor::FromToken(Token::kASSIGN_INDEX), - registers, index, value)); -} - -Value* IRRegExpMacroAssembler::PushRegisterIndex(intptr_t index) { - if (registers_count_ <= index) { - registers_count_ = index + 1; - } - return Bind(Uint64Constant(index)); -} - -void IRRegExpMacroAssembler::CheckCharacter(uint32_t c, BlockLabel* on_equal) { - TAG(); - Definition* cur_char_def = LoadLocal(current_character_); - Definition* char_def = Uint64Constant(c); - - BranchOrBacktrack(Comparison(kEQ, cur_char_def, char_def), on_equal); -} - -void IRRegExpMacroAssembler::CheckCharacterGT(uint16_t limit, - BlockLabel* on_greater) { - TAG(); - BranchOrBacktrack( - Comparison(kGT, LoadLocal(current_character_), Uint64Constant(limit)), - on_greater); -} - -void IRRegExpMacroAssembler::CheckAtStart(BlockLabel* on_at_start) { - TAG(); - - // Are we at the start of the input, i.e. is (offset == string_length * -1)? - Definition* neg_len_def = - InstanceCall(InstanceCallDescriptor::FromToken(Token::kNEGATE), - PushLocal(string_param_length_)); - Definition* offset_def = LoadLocal(current_position_); - BranchOrBacktrack(Comparison(kEQ, neg_len_def, offset_def), on_at_start); -} - -// cp_offset => offset from the current (character) pointer -// This offset may be negative due to traversing backwards during lookbehind. -void IRRegExpMacroAssembler::CheckNotAtStart(intptr_t cp_offset, - BlockLabel* on_not_at_start) { - TAG(); - - // Are we at the start of the input, i.e. is (offset == string_length * -1)? - auto neg_len_def = - Bind(InstanceCall(InstanceCallDescriptor::FromToken(Token::kNEGATE), - PushLocal(string_param_length_))); - auto current_pos_def = PushLocal(current_position_); - auto cp_offset_def = Bind(Int64Constant(cp_offset)); - auto offset_def = Bind(Add(current_pos_def, cp_offset_def)); - BranchOrBacktrack(Comparison(kNE, neg_len_def, offset_def), on_not_at_start); -} - -void IRRegExpMacroAssembler::CheckCharacterLT(uint16_t limit, - BlockLabel* on_less) { - TAG(); - BranchOrBacktrack( - Comparison(kLT, LoadLocal(current_character_), Uint64Constant(limit)), - on_less); -} - -void IRRegExpMacroAssembler::CheckGreedyLoop(BlockLabel* on_equal) { - TAG(); - - BlockLabel fallthrough; - - Definition* head = PeekStack(); - Definition* cur_pos_def = LoadLocal(current_position_); - BranchOrBacktrack(Comparison(kNE, head, cur_pos_def), &fallthrough); - - // Pop, throwing away the value. - Do(PopStack()); - - BranchOrBacktrack(nullptr, on_equal); - - BindBlock(&fallthrough); -} - -void IRRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase( - intptr_t start_reg, - bool read_backward, - bool unicode, - BlockLabel* on_no_match) { - TAG(); - ASSERT(start_reg + 1 <= registers_count_); - - BlockLabel fallthrough; - - Value* end_push = LoadRegister(start_reg + 1); - Value* start_push = LoadRegister(start_reg); - StoreLocal(capture_length_, Bind(Sub(end_push, start_push))); - - // The length of a capture should not be negative. This can only happen - // if the end of the capture is unrecorded, or at a point earlier than - // the start of the capture. - // BranchOrBacktrack(less, on_no_match); - - BranchOrBacktrack( - Comparison(kLT, LoadLocal(capture_length_), Uint64Constant(0)), - on_no_match); - - // If length is zero, either the capture is empty or it is completely - // uncaptured. In either case succeed immediately. - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(capture_length_), Uint64Constant(0)), - &fallthrough); - - Value* pos_push = nullptr; - Value* len_push = nullptr; - - if (!read_backward) { - // Check that there are sufficient characters left in the input. - pos_push = PushLocal(current_position_); - len_push = PushLocal(capture_length_); - BranchOrBacktrack( - Comparison(kGT, - InstanceCall(InstanceCallDescriptor::FromToken(Token::kADD), - pos_push, len_push), - Uint64Constant(0)), - on_no_match); - } - - pos_push = PushLocal(current_position_); - len_push = PushLocal(string_param_length_); - StoreLocal(match_start_index_, Bind(Add(pos_push, len_push))); - - if (read_backward) { - // First check that there are enough characters before this point in - // the string that we can match the backreference. - BranchOrBacktrack(Comparison(kLT, LoadLocal(match_start_index_), - LoadLocal(capture_length_)), - on_no_match); - - // The string to check is before the current position, not at it. - pos_push = PushLocal(match_start_index_); - len_push = PushLocal(capture_length_); - StoreLocal(match_start_index_, Bind(Sub(pos_push, len_push))); - } - - pos_push = LoadRegister(start_reg); - len_push = PushLocal(string_param_length_); - StoreLocal(capture_start_index_, Bind(Add(pos_push, len_push))); - - pos_push = PushLocal(match_start_index_); - len_push = PushLocal(capture_length_); - StoreLocal(match_end_index_, Bind(Add(pos_push, len_push))); - - BlockLabel success; - if (mode_ == ASCII) { - BlockLabel loop_increment; - BlockLabel loop; - BindBlock(&loop); - - StoreLocal(char_in_capture_, CharacterAt(capture_start_index_)); - StoreLocal(char_in_match_, CharacterAt(match_start_index_)); - - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(char_in_capture_), LoadLocal(char_in_match_)), - &loop_increment); - - // Mismatch, try case-insensitive match (converting letters to lower-case). - Value* match_char_push = PushLocal(char_in_match_); - Value* mask_push = Bind(Uint64Constant(0x20)); - StoreLocal( - char_in_match_, - Bind(InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_OR), - match_char_push, mask_push))); - - BlockLabel convert_capture; - BlockLabel on_not_in_range; - BranchOrBacktrack( - Comparison(kLT, LoadLocal(char_in_match_), Uint64Constant('a')), - &on_not_in_range); - BranchOrBacktrack( - Comparison(kGT, LoadLocal(char_in_match_), Uint64Constant('z')), - &on_not_in_range); - GoTo(&convert_capture); - BindBlock(&on_not_in_range); - - // Latin-1: Check for values in range [224,254] but not 247. - BranchOrBacktrack( - Comparison(kLT, LoadLocal(char_in_match_), Uint64Constant(224)), - on_no_match); - BranchOrBacktrack( - Comparison(kGT, LoadLocal(char_in_match_), Uint64Constant(254)), - on_no_match); - - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(char_in_match_), Uint64Constant(247)), - on_no_match); - - // Also convert capture character. - BindBlock(&convert_capture); - - Value* capture_char_push = PushLocal(char_in_capture_); - mask_push = Bind(Uint64Constant(0x20)); - StoreLocal( - char_in_capture_, - Bind(InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_OR), - capture_char_push, mask_push))); - - BranchOrBacktrack( - Comparison(kNE, LoadLocal(char_in_match_), LoadLocal(char_in_capture_)), - on_no_match); - - BindBlock(&loop_increment); - - // Increment indexes into capture and match strings. - Value* index_push = PushLocal(capture_start_index_); - Value* inc_push = Bind(Uint64Constant(1)); - StoreLocal(capture_start_index_, Bind(Add(index_push, inc_push))); - - index_push = PushLocal(match_start_index_); - inc_push = Bind(Uint64Constant(1)); - StoreLocal(match_start_index_, Bind(Add(index_push, inc_push))); - - // Compare to end of match, and loop if not done. - BranchOrBacktrack(Comparison(kLT, LoadLocal(match_start_index_), - LoadLocal(match_end_index_)), - &loop); - } else { - ASSERT(mode_ == UC16); - - Value* string_value = Bind(LoadLocal(string_param_)); - Value* lhs_index_value = Bind(LoadLocal(match_start_index_)); - Value* rhs_index_value = Bind(LoadLocal(capture_start_index_)); - Value* length_value = Bind(LoadLocal(capture_length_)); - - Definition* is_match_def; - - is_match_def = new (Z) CaseInsensitiveCompareInstr( - string_value, lhs_index_value, rhs_index_value, length_value, - /*handle_surrogates=*/unicode, specialization_cid_); - - BranchOrBacktrack(Comparison(kNE, is_match_def, BoolConstant(true)), - on_no_match); - } - - BindBlock(&success); - - if (read_backward) { - // Move current character position to start of match. - pos_push = PushLocal(current_position_); - len_push = PushLocal(capture_length_); - StoreLocal(current_position_, Bind(Sub(pos_push, len_push))); - } else { - // Move current character position to position after match. - Value* match_end_push = PushLocal(match_end_index_); - len_push = PushLocal(string_param_length_); - StoreLocal(current_position_, Bind(Sub(match_end_push, len_push))); - } - - BindBlock(&fallthrough); -} - -void IRRegExpMacroAssembler::CheckNotBackReference(intptr_t start_reg, - bool read_backward, - BlockLabel* on_no_match) { - TAG(); - ASSERT(start_reg + 1 <= registers_count_); - - BlockLabel fallthrough; - BlockLabel success; - - // Find length of back-referenced capture. - Value* end_push = LoadRegister(start_reg + 1); - Value* start_push = LoadRegister(start_reg); - StoreLocal(capture_length_, Bind(Sub(end_push, start_push))); - - // Fail on partial or illegal capture (start of capture after end of capture). - BranchOrBacktrack( - Comparison(kLT, LoadLocal(capture_length_), Uint64Constant(0)), - on_no_match); - - // Succeed on empty capture (including no capture) - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(capture_length_), Uint64Constant(0)), - &fallthrough); - - Value* pos_push = nullptr; - Value* len_push = nullptr; - - if (!read_backward) { - // Check that there are sufficient characters left in the input. - pos_push = PushLocal(current_position_); - len_push = PushLocal(capture_length_); - BranchOrBacktrack( - Comparison(kGT, - InstanceCall(InstanceCallDescriptor::FromToken(Token::kADD), - pos_push, len_push), - Uint64Constant(0)), - on_no_match); - } - - // Compute pointers to match string and capture string. - pos_push = PushLocal(current_position_); - len_push = PushLocal(string_param_length_); - StoreLocal(match_start_index_, Bind(Add(pos_push, len_push))); - - if (read_backward) { - // First check that there are enough characters before this point in - // the string that we can match the backreference. - BranchOrBacktrack(Comparison(kLT, LoadLocal(match_start_index_), - LoadLocal(capture_length_)), - on_no_match); - - // The string to check is before the current position, not at it. - pos_push = PushLocal(match_start_index_); - len_push = PushLocal(capture_length_); - StoreLocal(match_start_index_, Bind(Sub(pos_push, len_push))); - } - - pos_push = LoadRegister(start_reg); - len_push = PushLocal(string_param_length_); - StoreLocal(capture_start_index_, Bind(Add(pos_push, len_push))); - - pos_push = PushLocal(match_start_index_); - len_push = PushLocal(capture_length_); - StoreLocal(match_end_index_, Bind(Add(pos_push, len_push))); - - BlockLabel loop; - BindBlock(&loop); - - StoreLocal(char_in_capture_, CharacterAt(capture_start_index_)); - StoreLocal(char_in_match_, CharacterAt(match_start_index_)); - - BranchOrBacktrack( - Comparison(kNE, LoadLocal(char_in_capture_), LoadLocal(char_in_match_)), - on_no_match); - - // Increment indexes into capture and match strings. - Value* index_push = PushLocal(capture_start_index_); - Value* inc_push = Bind(Uint64Constant(1)); - StoreLocal(capture_start_index_, Bind(Add(index_push, inc_push))); - - index_push = PushLocal(match_start_index_); - inc_push = Bind(Uint64Constant(1)); - StoreLocal(match_start_index_, Bind(Add(index_push, inc_push))); - - // Check if we have reached end of match area. - BranchOrBacktrack(Comparison(kLT, LoadLocal(match_start_index_), - LoadLocal(match_end_index_)), - &loop); - - BindBlock(&success); - - if (read_backward) { - // Move current character position to start of match. - pos_push = PushLocal(current_position_); - len_push = PushLocal(capture_length_); - StoreLocal(current_position_, Bind(Sub(pos_push, len_push))); - } else { - // Move current character position to position after match. - Value* match_end_push = PushLocal(match_end_index_); - len_push = PushLocal(string_param_length_); - StoreLocal(current_position_, Bind(Sub(match_end_push, len_push))); - } - - BindBlock(&fallthrough); -} - -void IRRegExpMacroAssembler::CheckNotCharacter(uint32_t c, - BlockLabel* on_not_equal) { - TAG(); - BranchOrBacktrack( - Comparison(kNE, LoadLocal(current_character_), Uint64Constant(c)), - on_not_equal); -} - -void IRRegExpMacroAssembler::CheckCharacterAfterAnd(uint32_t c, - uint32_t mask, - BlockLabel* on_equal) { - TAG(); - - Definition* actual_def = LoadLocal(current_character_); - - Value* actual_push = Bind(actual_def); - Value* mask_push = Bind(Uint64Constant(mask)); - actual_def = InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_AND), - actual_push, mask_push); - Definition* expected_def = Uint64Constant(c); - - BranchOrBacktrack(Comparison(kEQ, actual_def, expected_def), on_equal); -} - -void IRRegExpMacroAssembler::CheckNotCharacterAfterAnd( - uint32_t c, - uint32_t mask, - BlockLabel* on_not_equal) { - TAG(); - - Definition* actual_def = LoadLocal(current_character_); - - Value* actual_push = Bind(actual_def); - Value* mask_push = Bind(Uint64Constant(mask)); - actual_def = InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_AND), - actual_push, mask_push); - Definition* expected_def = Uint64Constant(c); - - BranchOrBacktrack(Comparison(kNE, actual_def, expected_def), on_not_equal); -} - -void IRRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd( - uint16_t c, - uint16_t minus, - uint16_t mask, - BlockLabel* on_not_equal) { - TAG(); - ASSERT(minus < Utf16::kMaxCodeUnit); // NOLINT - - Definition* actual_def = LoadLocal(current_character_); - - Value* actual_push = Bind(actual_def); - Value* minus_push = Bind(Uint64Constant(minus)); - - actual_push = Bind(Sub(actual_push, minus_push)); - Value* mask_push = Bind(Uint64Constant(mask)); - actual_def = InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_AND), - actual_push, mask_push); - Definition* expected_def = Uint64Constant(c); - - BranchOrBacktrack(Comparison(kNE, actual_def, expected_def), on_not_equal); -} - -void IRRegExpMacroAssembler::CheckCharacterInRange(uint16_t from, - uint16_t to, - BlockLabel* on_in_range) { - TAG(); - ASSERT(from <= to); - - // TODO(zerny): All range comparisons could be done cheaper with unsigned - // compares. This pattern repeats in various places. - - BlockLabel on_not_in_range; - BranchOrBacktrack( - Comparison(kLT, LoadLocal(current_character_), Uint64Constant(from)), - &on_not_in_range); - BranchOrBacktrack( - Comparison(kGT, LoadLocal(current_character_), Uint64Constant(to)), - &on_not_in_range); - BranchOrBacktrack(nullptr, on_in_range); - - BindBlock(&on_not_in_range); -} - -void IRRegExpMacroAssembler::CheckCharacterNotInRange( - uint16_t from, - uint16_t to, - BlockLabel* on_not_in_range) { - TAG(); - ASSERT(from <= to); - - BranchOrBacktrack( - Comparison(kLT, LoadLocal(current_character_), Uint64Constant(from)), - on_not_in_range); - - BranchOrBacktrack( - Comparison(kGT, LoadLocal(current_character_), Uint64Constant(to)), - on_not_in_range); -} - -void IRRegExpMacroAssembler::CheckBitInTable(const TypedData& table, - BlockLabel* on_bit_set) { - TAG(); - - Value* table_push = Bind(new (Z) ConstantInstr(table)); - Value* index_push = PushLocal(current_character_); - - if (mode_ != ASCII || kTableMask != Symbols::kMaxOneCharCodeSymbol) { - Value* mask_push = Bind(Uint64Constant(kTableSize - 1)); - index_push = - Bind(InstanceCall(InstanceCallDescriptor::FromToken(Token::kBIT_AND), - index_push, mask_push)); - } - - Definition* byte_def = InstanceCall( - InstanceCallDescriptor::FromToken(Token::kINDEX), table_push, index_push); - Definition* zero_def = Int64Constant(0); - - BranchOrBacktrack(Comparison(kNE, byte_def, zero_def), on_bit_set); -} - -bool IRRegExpMacroAssembler::CheckSpecialCharacterClass( - uint16_t type, - BlockLabel* on_no_match) { - TAG(); - - // Range checks (c in min..max) are generally implemented by an unsigned - // (c - min) <= (max - min) check - switch (type) { - case 's': - // Match space-characters - if (mode_ == ASCII) { - // One byte space characters are '\t'..'\r', ' ' and \u00a0. - BlockLabel success; - // Space (' '). - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(current_character_), Uint64Constant(' ')), - &success); - // Check range 0x09..0x0d. - CheckCharacterInRange('\t', '\r', &success); - // \u00a0 (NBSP). - BranchOrBacktrack(Comparison(kNE, LoadLocal(current_character_), - Uint64Constant(0x00a0)), - on_no_match); - BindBlock(&success); - return true; - } - return false; - case 'S': - // The emitted code for generic character classes is good enough. - return false; - case 'd': - // Match ASCII digits ('0'..'9') - CheckCharacterNotInRange('0', '9', on_no_match); - return true; - case 'D': - // Match non ASCII-digits - CheckCharacterInRange('0', '9', on_no_match); - return true; - case '.': { - // Match non-newlines (not 0x0a('\n'), 0x0d('\r'), 0x2028 and 0x2029) - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(current_character_), Uint64Constant('\n')), - on_no_match); - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(current_character_), Uint64Constant('\r')), - on_no_match); - if (mode_ == UC16) { - BranchOrBacktrack(Comparison(kEQ, LoadLocal(current_character_), - Uint64Constant(0x2028)), - on_no_match); - BranchOrBacktrack(Comparison(kEQ, LoadLocal(current_character_), - Uint64Constant(0x2029)), - on_no_match); - } - return true; - } - case 'w': { - if (mode_ != ASCII) { - // Table is 128 entries, so all ASCII characters can be tested. - BranchOrBacktrack( - Comparison(kGT, LoadLocal(current_character_), Uint64Constant('z')), - on_no_match); - } - - Value* table_push = Bind(WordCharacterMapConstant()); - Value* index_push = PushLocal(current_character_); - - Definition* byte_def = - InstanceCall(InstanceCallDescriptor::FromToken(Token::kINDEX), - table_push, index_push); - Definition* zero_def = Int64Constant(0); - - BranchOrBacktrack(Comparison(kEQ, byte_def, zero_def), on_no_match); - - return true; - } - case 'W': { - BlockLabel done; - if (mode_ != ASCII) { - // Table is 128 entries, so all ASCII characters can be tested. - BranchOrBacktrack( - Comparison(kGT, LoadLocal(current_character_), Uint64Constant('z')), - &done); - } - - // TODO(zerny): Refactor to use CheckBitInTable if possible. - - Value* table_push = Bind(WordCharacterMapConstant()); - Value* index_push = PushLocal(current_character_); - - Definition* byte_def = - InstanceCall(InstanceCallDescriptor::FromToken(Token::kINDEX), - table_push, index_push); - Definition* zero_def = Int64Constant(0); - - BranchOrBacktrack(Comparison(kNE, byte_def, zero_def), on_no_match); - - if (mode_ != ASCII) { - BindBlock(&done); - } - return true; - } - // Non-standard classes (with no syntactic shorthand) used internally. - case '*': - // Match any character. - return true; - case 'n': { - // Match newlines (0x0a('\n'), 0x0d('\r'), 0x2028 or 0x2029). - // The opposite of '.'. - BlockLabel success; - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(current_character_), Uint64Constant('\n')), - &success); - BranchOrBacktrack( - Comparison(kEQ, LoadLocal(current_character_), Uint64Constant('\r')), - &success); - if (mode_ == UC16) { - BranchOrBacktrack(Comparison(kEQ, LoadLocal(current_character_), - Uint64Constant(0x2028)), - &success); - BranchOrBacktrack(Comparison(kEQ, LoadLocal(current_character_), - Uint64Constant(0x2029)), - &success); - } - BranchOrBacktrack(nullptr, on_no_match); - BindBlock(&success); - return true; - } - // No custom implementation (yet): s(uint16_t), S(uint16_t). - default: - return false; - } -} - -void IRRegExpMacroAssembler::Fail() { - TAG(); - ASSERT(FAILURE == 0); // Return value for failure is zero. - if (!global()) { - UNREACHABLE(); // Dart regexps are always global. - } - GoTo(exit_block_); -} - -void IRRegExpMacroAssembler::IfRegisterGE(intptr_t reg, - intptr_t comparand, - BlockLabel* if_ge) { - TAG(); - Value* reg_push = LoadRegister(reg); - Value* pos = Bind(Int64Constant(comparand)); - BranchOrBacktrack(Comparison(kGTE, reg_push, pos), if_ge); -} - -void IRRegExpMacroAssembler::IfRegisterLT(intptr_t reg, - intptr_t comparand, - BlockLabel* if_lt) { - TAG(); - Value* reg_push = LoadRegister(reg); - Value* pos = Bind(Int64Constant(comparand)); - BranchOrBacktrack(Comparison(kLT, reg_push, pos), if_lt); -} - -void IRRegExpMacroAssembler::IfRegisterEqPos(intptr_t reg, BlockLabel* if_eq) { - TAG(); - Value* reg_push = LoadRegister(reg); - Value* pos = Bind(LoadLocal(current_position_)); - BranchOrBacktrack(Comparison(kEQ, reg_push, pos), if_eq); -} - -RegExpMacroAssembler::IrregexpImplementation -IRRegExpMacroAssembler::Implementation() { - return kIRImplementation; -} - -void IRRegExpMacroAssembler::LoadCurrentCharacter(intptr_t cp_offset, - BlockLabel* on_end_of_input, - bool check_bounds, - intptr_t characters) { - TAG(); - ASSERT(cp_offset < (1 << 30)); // Be sane! (And ensure negation works) - if (check_bounds) { - if (cp_offset >= 0) { - CheckPosition(cp_offset + characters - 1, on_end_of_input); - } else { - CheckPosition(cp_offset, on_end_of_input); - } - } - LoadCurrentCharacterUnchecked(cp_offset, characters); -} - -void IRRegExpMacroAssembler::PopCurrentPosition() { - TAG(); - StoreLocal(current_position_, Bind(PopStack())); -} - -void IRRegExpMacroAssembler::PopRegister(intptr_t reg) { - TAG(); - ASSERT(reg < registers_count_); - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(reg); - Value* pop_push = Bind(PopStack()); - StoreRegister(registers_push, index_push, pop_push); -} - -void IRRegExpMacroAssembler::PushStack(Definition* definition) { - Value* stack_push = PushLocal(stack_); - Value* stack_pointer_push = PushLocal(stack_pointer_); - StoreLocal(stack_pointer_, - Bind(Add(stack_pointer_push, Bind(Uint64Constant(1))))); - stack_pointer_push = PushLocal(stack_pointer_); - // TODO(zerny): bind value and push could break stack discipline. - Value* value_push = Bind(definition); - Do(InstanceCall(InstanceCallDescriptor::FromToken(Token::kASSIGN_INDEX), - stack_push, stack_pointer_push, value_push)); -} - -Definition* IRRegExpMacroAssembler::PopStack() { - Value* stack_push = PushLocal(stack_); - Value* stack_pointer_push1 = PushLocal(stack_pointer_); - Value* stack_pointer_push2 = PushLocal(stack_pointer_); - StoreLocal(stack_pointer_, - Bind(Sub(stack_pointer_push2, Bind(Uint64Constant(1))))); - return InstanceCall(InstanceCallDescriptor::FromToken(Token::kINDEX), - stack_push, stack_pointer_push1); -} - -Definition* IRRegExpMacroAssembler::PeekStack() { - Value* stack_push = PushLocal(stack_); - Value* stack_pointer_push = PushLocal(stack_pointer_); - return InstanceCall(InstanceCallDescriptor::FromToken(Token::kINDEX), - stack_push, stack_pointer_push); -} - -// Pushes the location corresponding to label to the backtracking stack. -void IRRegExpMacroAssembler::PushBacktrack(BlockLabel* label) { - TAG(); - - // Ensure that targets of indirect jumps are never accessed through a - // normal control flow instructions by creating a new block for each backtrack - // target. - IndirectEntryInstr* indirect_target = IndirectWithJoinGoto(label->block()); - - // Add a fake edge from the graph entry for data flow analysis. - entry_block_->AddIndirectEntry(indirect_target); - - ConstantInstr* offset = Uint64Constant(indirect_target->indirect_id()); - PushStack(offset); - CheckStackLimit(); -} - -void IRRegExpMacroAssembler::PushCurrentPosition() { - TAG(); - PushStack(LoadLocal(current_position_)); -} - -void IRRegExpMacroAssembler::PushRegister(intptr_t reg) { - TAG(); - // TODO(zerny): Refactor PushStack so it can be reused here. - Value* stack_push = PushLocal(stack_); - Value* stack_pointer_push = PushLocal(stack_pointer_); - StoreLocal(stack_pointer_, - Bind(Add(stack_pointer_push, Bind(Uint64Constant(1))))); - stack_pointer_push = PushLocal(stack_pointer_); - // TODO(zerny): bind value and push could break stack discipline. - Value* value_push = LoadRegister(reg); - Do(InstanceCall(InstanceCallDescriptor::FromToken(Token::kASSIGN_INDEX), - stack_push, stack_pointer_push, value_push)); - CheckStackLimit(); -} - -// Checks that (stack.capacity - stack_limit_slack) > stack_pointer. -// This ensures that up to stack_limit_slack stack pushes can be -// done without exhausting the stack space. If the check fails the -// stack will be grown. -void IRRegExpMacroAssembler::CheckStackLimit() { - TAG(); - Value* stack_push = PushLocal(stack_); - Value* length_push = - Bind(InstanceCall(InstanceCallDescriptor(String::ZoneHandle( - Field::GetterSymbol(Symbols::Length()))), - stack_push)); - Value* capacity_push = - Bind(Sub(length_push, Bind(Uint64Constant(stack_limit_slack())))); - Value* stack_pointer_push = PushLocal(stack_pointer_); - BranchInstr* branch = new (Z) BranchInstr( - Comparison(kGT, capacity_push, stack_pointer_push), GetNextDeoptId()); - CloseBlockWith(branch); - - BlockLabel grow_stack; - BlockLabel fallthrough; - *branch->true_successor_address() = TargetWithJoinGoto(fallthrough.block()); - *branch->false_successor_address() = TargetWithJoinGoto(grow_stack.block()); - - BindBlock(&grow_stack); - GrowStack(); - - BindBlock(&fallthrough); -} - -void IRRegExpMacroAssembler::GrowStack() { - TAG(); - const Library& lib = Library::Handle(Library::CoreLibrary()); - const Class& regexp_class = - Class::Handle(lib.LookupClassAllowPrivate(Symbols::_RegExp())); - - const Function& grow_backtracking_stack_function = - Function::ZoneHandle(Z, regexp_class.LookupFunctionAllowPrivate( - Symbols::_growBacktrackingStack())); - StoreLocal(stack_, Bind(StaticCall(grow_backtracking_stack_function, - ICData::kStatic))); -} - -void IRRegExpMacroAssembler::ReadCurrentPositionFromRegister(intptr_t reg) { - TAG(); - StoreLocal(current_position_, LoadRegister(reg)); -} - -// Resets the tip of the stack to the value stored in reg. -void IRRegExpMacroAssembler::ReadStackPointerFromRegister(intptr_t reg) { - TAG(); - ASSERT(reg < registers_count_); - StoreLocal(stack_pointer_, LoadRegister(reg)); -} - -void IRRegExpMacroAssembler::SetCurrentPositionFromEnd(intptr_t by) { - TAG(); - - BlockLabel after_position; - - Definition* cur_pos_def = LoadLocal(current_position_); - Definition* by_value_def = Int64Constant(-by); - - BranchOrBacktrack(Comparison(kGTE, cur_pos_def, by_value_def), - &after_position); - - StoreLocal(current_position_, Bind(Int64Constant(-by))); - - // On RegExp code entry (where this operation is used), the character before - // the current position is expected to be already loaded. - // We have advanced the position, so it's safe to read backwards. - LoadCurrentCharacterUnchecked(-1, 1); - - BindBlock(&after_position); -} - -void IRRegExpMacroAssembler::SetRegister(intptr_t reg, intptr_t to) { - TAG(); - // Reserved for positions! - ASSERT(reg >= saved_registers_count_); - StoreRegister(reg, to); -} - -bool IRRegExpMacroAssembler::Succeed() { - TAG(); - GoTo(success_block_); - return global(); -} - -void IRRegExpMacroAssembler::WriteCurrentPositionToRegister( - intptr_t reg, - intptr_t cp_offset) { - TAG(); - - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(reg); - Value* pos_push = PushLocal(current_position_); - Value* off_push = Bind(Int64Constant(cp_offset)); - Value* neg_off_push = Bind(Add(pos_push, off_push)); - // Push the negative offset; these are converted to positive string positions - // within the success block. - StoreRegister(registers_push, index_push, neg_off_push); -} - -void IRRegExpMacroAssembler::ClearRegisters(intptr_t reg_from, - intptr_t reg_to) { - TAG(); - - ASSERT(reg_from <= reg_to); - - // In order to clear registers to a final result value of -1, set them to - // (-1 - string length), the offset of -1 from the end of the string. - - for (intptr_t reg = reg_from; reg <= reg_to; reg++) { - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(reg); - Value* minus_one_push = Bind(Int64Constant(-1)); - Value* length_push = PushLocal(string_param_length_); - Value* value_push = Bind(Sub(minus_one_push, length_push)); - StoreRegister(registers_push, index_push, value_push); - } -} - -void IRRegExpMacroAssembler::WriteStackPointerToRegister(intptr_t reg) { - TAG(); - - Value* registers_push = PushLocal(registers_); - Value* index_push = PushRegisterIndex(reg); - Value* tip_push = PushLocal(stack_pointer_); - StoreRegister(registers_push, index_push, tip_push); -} - -// Private methods: - -void IRRegExpMacroAssembler::CheckPosition(intptr_t cp_offset, - BlockLabel* on_outside_input) { - TAG(); - if (cp_offset >= 0) { - Definition* curpos_def = LoadLocal(current_position_); - Definition* cp_off_def = Int64Constant(-cp_offset); - // If (current_position_ < -cp_offset), we are in bounds. - // Remember, current_position_ is a negative offset from the string end. - - BranchOrBacktrack(Comparison(kGTE, curpos_def, cp_off_def), - on_outside_input); - } else { - // We need to see if there's enough characters left in the string to go - // back cp_offset characters, so get the normalized position and then - // make sure that (normalized_position >= -cp_offset). - Value* pos_push = PushLocal(current_position_); - Value* len_push = PushLocal(string_param_length_); - BranchOrBacktrack( - Comparison(kLT, Add(pos_push, len_push), Uint64Constant(-cp_offset)), - on_outside_input); - } -} - -void IRRegExpMacroAssembler::BranchOrBacktrack(ConditionInstr* condition, - BlockLabel* true_successor) { - if (condition == nullptr) { // No condition - if (true_successor == nullptr) { - Backtrack(); - return; - } - GoTo(true_successor); - return; - } - - // If no successor block has been passed in, backtrack. - JoinEntryInstr* true_successor_block = backtrack_block_; - if (true_successor != nullptr) { - true_successor->SetLinked(); - true_successor_block = true_successor->block(); - } - ASSERT(true_successor_block != nullptr); - - // If the condition is not true, fall through to a new block. - BlockLabel fallthrough; - - BranchInstr* branch = new (Z) BranchInstr(condition, GetNextDeoptId()); - *branch->true_successor_address() = TargetWithJoinGoto(true_successor_block); - *branch->false_successor_address() = TargetWithJoinGoto(fallthrough.block()); - - CloseBlockWith(branch); - BindBlock(&fallthrough); -} - -TargetEntryInstr* IRRegExpMacroAssembler::TargetWithJoinGoto( - JoinEntryInstr* dst) { - TargetEntryInstr* target = new (Z) - TargetEntryInstr(block_id_.Alloc(), kInvalidTryIndex, GetNextDeoptId()); - blocks_.Add(target); - - target->AppendInstruction(new (Z) GotoInstr(dst, GetNextDeoptId())); - - return target; -} - -IndirectEntryInstr* IRRegExpMacroAssembler::IndirectWithJoinGoto( - JoinEntryInstr* dst) { - IndirectEntryInstr* target = - new (Z) IndirectEntryInstr(block_id_.Alloc(), indirect_id_.Alloc(), - kInvalidTryIndex, GetNextDeoptId()); - blocks_.Add(target); - - target->AppendInstruction(new (Z) GotoInstr(dst, GetNextDeoptId())); - - return target; -} - -void IRRegExpMacroAssembler::CheckPreemption(bool is_backtrack) { - TAG(); - - // We don't have the loop_depth available when compiling regexps, but - // we set loop_depth to a non-zero value because this instruction does - // not act as an OSR entry outside loops. - AppendInstruction(new (Z) CheckStackOverflowInstr( - InstructionSource(), - /*stack_depth=*/0, - /*loop_depth=*/1, GetNextDeoptId(), - is_backtrack ? CheckStackOverflowInstr::kOsrAndPreemption - : CheckStackOverflowInstr::kOsrOnly)); -} - -Definition* IRRegExpMacroAssembler::Add(Value* lhs, Value* rhs) { - return InstanceCall(InstanceCallDescriptor::FromToken(Token::kADD), lhs, rhs); -} - -Definition* IRRegExpMacroAssembler::Sub(Value* lhs, Value* rhs) { - return InstanceCall(InstanceCallDescriptor::FromToken(Token::kSUB), lhs, rhs); -} - -void IRRegExpMacroAssembler::LoadCurrentCharacterUnchecked( - intptr_t cp_offset, - intptr_t characters) { - TAG(); - - ASSERT(characters == 1 || CanReadUnaligned()); - if (mode_ == ASCII) { - ASSERT(characters == 1 || characters == 2 || characters == 4); - } else { - ASSERT(mode_ == UC16); - ASSERT(characters == 1 || characters == 2); - } - - // Calculate the addressed string index as: - // cp_offset + current_position_ + string_param_length_ - // TODO(zerny): Avoid generating 'add' instance-calls here. - Value* off_arg = Bind(Int64Constant(cp_offset)); - Value* pos_arg = BindLoadLocal(*current_position_); - Value* off_pos_arg = Bind(Add(off_arg, pos_arg)); - Value* len_arg = BindLoadLocal(*string_param_length_); - // Index is stored in a temporary local so that we can later load it safely. - StoreLocal(index_temp_, Bind(Add(off_pos_arg, len_arg))); - - // Load and store the code units. - Value* code_unit_value = LoadCodeUnitsAt(index_temp_, characters); - StoreLocal(current_character_, code_unit_value); - PRINT(PushLocal(current_character_)); -} - -Value* IRRegExpMacroAssembler::CharacterAt(LocalVariable* index) { - return LoadCodeUnitsAt(index, 1); -} - -Value* IRRegExpMacroAssembler::LoadCodeUnitsAt(LocalVariable* index, - intptr_t characters) { - // Bind the pattern as the load receiver. - Value* pattern_val = BindLoadLocal(*string_param_); - - // Here pattern_val might be untagged so this must not trigger a GC. - Value* index_val = BindLoadLocal(*index); - - return Bind(new (Z) - LoadCodeUnitsInstr(pattern_val, index_val, characters, - specialization_cid_, InstructionSource())); -} - -#undef __ - -} // namespace dart - -#endif // !defined(DART_PRECOMPILED_RUNTIME) diff --git a/runtime/vm/regexp/regexp_assembler_ir.h b/runtime/vm/regexp/regexp_assembler_ir.h deleted file mode 100644 index 3105076d571..00000000000 --- a/runtime/vm/regexp/regexp_assembler_ir.h +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_IR_H_ -#define RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_IR_H_ - -#include "vm/compiler/assembler/assembler.h" -#include "vm/compiler/backend/il.h" -#include "vm/object.h" -#include "vm/regexp/regexp_assembler.h" - -namespace dart { - -class IRRegExpMacroAssembler : public RegExpMacroAssembler { - public: - // Type of input string to generate code for. - enum Mode { ASCII = 1, UC16 = 2 }; - - // Result of calling generated native RegExp code. - // RETRY: Something significant changed during execution, and the matching - // should be retried from scratch. - // EXCEPTION: Something failed during execution. If no exception has been - // thrown, it's an internal out-of-memory, and the caller should - // throw the exception. - // FAILURE: Matching failed. - // SUCCESS: Matching succeeded, and the output array has been filled with - // capture positions. - enum Result { RETRY = -2, EXCEPTION = -1, FAILURE = 0, SUCCESS = 1 }; - - IRRegExpMacroAssembler(intptr_t specialization_cid, - intptr_t capture_count, - const ParsedFunction* parsed_function, - const ZoneGrowableArray& ic_data_array, - intptr_t osr_id, - Zone* zone); - virtual ~IRRegExpMacroAssembler(); - - virtual bool CanReadUnaligned(); - - static ArrayPtr Execute(const RegExp& regexp, - const String& input, - const Smi& start_offset, - bool sticky, - Zone* zone); - - virtual bool IsClosed() const { return (current_instruction_ == nullptr); } - - virtual intptr_t stack_limit_slack(); - virtual void AdvanceCurrentPosition(intptr_t by); - virtual void AdvanceRegister(intptr_t reg, intptr_t by); - virtual void Backtrack(); - virtual void BindBlock(BlockLabel* label); - virtual void CheckAtStart(BlockLabel* on_at_start); - virtual void CheckCharacter(uint32_t c, BlockLabel* on_equal); - virtual void CheckCharacterAfterAnd(uint32_t c, - uint32_t mask, - BlockLabel* on_equal); - virtual void CheckCharacterGT(uint16_t limit, BlockLabel* on_greater); - virtual void CheckCharacterLT(uint16_t limit, BlockLabel* on_less); - // A "greedy loop" is a loop that is both greedy and with a simple - // body. It has a particularly simple implementation. - virtual void CheckGreedyLoop(BlockLabel* on_tos_equals_current_position); - virtual void CheckNotAtStart(intptr_t cp_offset, BlockLabel* on_not_at_start); - virtual void CheckNotBackReference(intptr_t start_reg, - bool read_backward, - BlockLabel* on_no_match); - virtual void CheckNotBackReferenceIgnoreCase(intptr_t start_reg, - bool read_backward, - bool unicode, - BlockLabel* on_no_match); - virtual void CheckNotCharacter(uint32_t c, BlockLabel* on_not_equal); - virtual void CheckNotCharacterAfterAnd(uint32_t c, - uint32_t mask, - BlockLabel* on_not_equal); - virtual void CheckNotCharacterAfterMinusAnd(uint16_t c, - uint16_t minus, - uint16_t mask, - BlockLabel* on_not_equal); - virtual void CheckCharacterInRange(uint16_t from, - uint16_t to, - BlockLabel* on_in_range); - virtual void CheckCharacterNotInRange(uint16_t from, - uint16_t to, - BlockLabel* on_not_in_range); - virtual void CheckBitInTable(const TypedData& table, BlockLabel* on_bit_set); - - // Checks whether the given offset from the current position is before - // the end of the string. - virtual void CheckPosition(intptr_t cp_offset, BlockLabel* on_outside_input); - virtual bool CheckSpecialCharacterClass(uint16_t type, - BlockLabel* on_no_match); - virtual void Fail(); - virtual void IfRegisterGE(intptr_t reg, - intptr_t comparand, - BlockLabel* if_ge); - virtual void IfRegisterLT(intptr_t reg, - intptr_t comparand, - BlockLabel* if_lt); - virtual void IfRegisterEqPos(intptr_t reg, BlockLabel* if_eq); - virtual IrregexpImplementation Implementation(); - virtual void GoTo(BlockLabel* to); - virtual void LoadCurrentCharacter(intptr_t cp_offset, - BlockLabel* on_end_of_input, - bool check_bounds = true, - intptr_t characters = 1); - virtual void PopCurrentPosition(); - virtual void PopRegister(intptr_t register_index); - virtual void Print(const char* str); - virtual void PushBacktrack(BlockLabel* label); - virtual void PushCurrentPosition(); - virtual void PushRegister(intptr_t register_index); - virtual void ReadCurrentPositionFromRegister(intptr_t reg); - virtual void ReadStackPointerFromRegister(intptr_t reg); - virtual void SetCurrentPositionFromEnd(intptr_t by); - virtual void SetRegister(intptr_t register_index, intptr_t to); - virtual bool Succeed(); - virtual void WriteCurrentPositionToRegister(intptr_t reg, intptr_t cp_offset); - virtual void ClearRegisters(intptr_t reg_from, intptr_t reg_to); - virtual void WriteStackPointerToRegister(intptr_t reg); - - virtual void PrintBlocks(); - - IndirectGotoInstr* backtrack_goto() const { return backtrack_goto_; } - GraphEntryInstr* graph_entry() const { return entry_block_; } - - intptr_t num_stack_locals() const { return local_id_.Count(); } - intptr_t num_blocks() const { return block_id_.Count(); } - - // Generate a dispatch block implementing backtracking. Must be done after - // graph construction. - void GenerateBacktrackBlock(); - - // Allocate the actual registers array once its size is known. Must be done - // after graph construction. - void FinalizeRegistersArray(); - - private: - intptr_t GetNextDeoptId() const { - return thread_->compiler_state().GetNextDeoptId(); - } - - // Generate the contents of preset blocks. The entry block is the entry point - // of the generated code. - void GenerateEntryBlock(); - // Copies capture indices into the result area and returns true. - void GenerateSuccessBlock(); - // Returns false. - void GenerateExitBlock(); - - enum ComparisonKind { - kEQ, - kNE, - kLT, - kGT, - kLTE, - kGTE, - }; - - struct InstanceCallDescriptor { - // Standard (i.e. most non-Smi) functions. - explicit InstanceCallDescriptor(const String& name) - : name(name), token_kind(Token::kILLEGAL), checked_argument_count(1) {} - - InstanceCallDescriptor(const String& name, - Token::Kind token_kind, - intptr_t checked_argument_count) - : name(name), - token_kind(token_kind), - checked_argument_count(checked_argument_count) {} - - // Special cases for Smi and indexing functions. - static InstanceCallDescriptor FromToken(Token::Kind token_kind) { - switch (token_kind) { - case Token::kEQ: - return InstanceCallDescriptor(Symbols::EqualOperator(), token_kind, - 2); - case Token::kADD: - return InstanceCallDescriptor(Symbols::Plus(), token_kind, 2); - case Token::kSUB: - return InstanceCallDescriptor(Symbols::Minus(), token_kind, 2); - case Token::kBIT_OR: - return InstanceCallDescriptor(Symbols::BitOr(), token_kind, 2); - case Token::kBIT_AND: - return InstanceCallDescriptor(Symbols::BitAnd(), token_kind, 2); - case Token::kLT: - return InstanceCallDescriptor(Symbols::LAngleBracket(), token_kind, - 2); - case Token::kLTE: - return InstanceCallDescriptor(Symbols::LessEqualOperator(), - token_kind, 2); - case Token::kGT: - return InstanceCallDescriptor(Symbols::RAngleBracket(), token_kind, - 2); - case Token::kGTE: - return InstanceCallDescriptor(Symbols::GreaterEqualOperator(), - token_kind, 2); - case Token::kNEGATE: - return InstanceCallDescriptor(Symbols::UnaryMinus(), token_kind, 1); - case Token::kINDEX: - return InstanceCallDescriptor(Symbols::IndexToken(), token_kind, 2); - case Token::kASSIGN_INDEX: - return InstanceCallDescriptor(Symbols::AssignIndexToken(), token_kind, - 2); - default: - UNREACHABLE(); - } - UNREACHABLE(); - return InstanceCallDescriptor(Symbols::Empty()); - } - - const String& name; - Token::Kind token_kind; - intptr_t checked_argument_count; - }; - - LocalVariable* Local(const String& name); - LocalVariable* Parameter(const String& name, intptr_t index) const; - - ConstantInstr* Int64Constant(int64_t value) const; - ConstantInstr* Uint64Constant(uint64_t value) const; - ConstantInstr* BoolConstant(bool value) const; - ConstantInstr* StringConstant(const char* value) const; - - // The word character map static member of the RegExp class. - // Byte map of one byte characters with a 0xff if the character is a word - // character (digit, letter or underscore) and 0x00 otherwise. - // Used by generated RegExp code. - ConstantInstr* WordCharacterMapConstant() const; - - ComparisonInstr* Comparison(ComparisonKind kind, Value* lhs, Value* rhs); - ComparisonInstr* Comparison(ComparisonKind kind, - Definition* lhs, - Definition* rhs); - - InstanceCallInstr* InstanceCall(const InstanceCallDescriptor& desc, - Value* arg1) const; - InstanceCallInstr* InstanceCall(const InstanceCallDescriptor& desc, - Value* arg1, - Value* arg2) const; - InstanceCallInstr* InstanceCall(const InstanceCallDescriptor& desc, - Value* arg1, - Value* arg2, - Value* arg3) const; - InstanceCallInstr* InstanceCall(const InstanceCallDescriptor& desc, - InputsArray&& arguments) const; - - StaticCallInstr* StaticCall(const Function& function, - ICData::RebindRule rebind_rule) const; - StaticCallInstr* StaticCall(const Function& function, - Value* arg1, - ICData::RebindRule rebind_rule) const; - StaticCallInstr* StaticCall(const Function& function, - Value* arg1, - Value* arg2, - ICData::RebindRule rebind_rule) const; - StaticCallInstr* StaticCall(const Function& function, - InputsArray&& arguments, - ICData::RebindRule rebind_rule) const; - - // Creates a new block consisting simply of a goto to dst. - TargetEntryInstr* TargetWithJoinGoto(JoinEntryInstr* dst); - IndirectEntryInstr* IndirectWithJoinGoto(JoinEntryInstr* dst); - - // Adds, respectively subtracts lhs and rhs and returns the result. - Definition* Add(Value* lhs, Value* rhs); - Definition* Sub(Value* lhs, Value* rhs); - - LoadLocalInstr* LoadLocal(LocalVariable* local) const; - void StoreLocal(LocalVariable* local, Value* value); - - LoadStaticFieldInstr* LoadStaticField(const Field& field, - bool calls_initializer = false) const; - - Value* PushLocal(LocalVariable* local); - - Value* PushRegisterIndex(intptr_t reg); - Value* LoadRegister(intptr_t reg); - void StoreRegister(intptr_t reg, intptr_t value); - void StoreRegister(Value* registers, Value* index, Value* value); - - // Load a number of characters at the given offset from the - // current position, into the current-character register. - void LoadCurrentCharacterUnchecked(intptr_t cp_offset, - intptr_t character_count); - - // Returns the character within the passed string at the specified index. - Value* CharacterAt(LocalVariable* index); - - // Load a number of characters starting from index in the pattern string. - Value* LoadCodeUnitsAt(LocalVariable* index, intptr_t character_count); - - // Check whether preemption has been requested. Also serves as an OSR entry. - void CheckPreemption(bool is_backtrack); - - // Byte size of chars in the string to match (decided by the Mode argument) - inline intptr_t char_size() { return static_cast(mode_); } - - // Equivalent to a conditional branch to the label, unless the label - // is nullptr, in which case it is a conditional Backtrack. - void BranchOrBacktrack(ConditionInstr* condition, BlockLabel* true_successor); - - // Set up all local variables and parameters. - void InitializeLocals(); - - // Allocates a new local, and returns the appropriate id for placing it - // on the stack. - intptr_t GetNextLocalIndex(); - - // We never have any copied parameters. - intptr_t num_copied_params() const { return 0; } - - // Return the position register at the specified index, creating it if - // necessary. Note that the number of such registers can exceed the amount - // required by the number of output captures. - LocalVariable* position_register(intptr_t index); - - void set_current_instruction(Instruction* instruction); - - // The following functions are responsible for appending instructions - // to the current instruction in various ways. The most simple one - // is AppendInstruction, which simply appends an instruction and performs - // bookkeeping. - void AppendInstruction(Instruction* instruction); - // Similar to AppendInstruction, but closes the current block by - // setting current_instruction_ to nullptr. - void CloseBlockWith(Instruction* instruction); - // Appends definition and allocates a temp index for the result. - Value* Bind(Definition* definition); - // Loads and binds a local variable. - Value* BindLoadLocal(const LocalVariable& local); - - // Appends the definition. - void Do(Definition* definition); - // Closes the current block with a jump to the specified block. - void GoTo(JoinEntryInstr* to); - - // Accessors for our local stack_. - void PushStack(Definition* definition); - Definition* PopStack(); - Definition* PeekStack(); - void CheckStackLimit(); - void GrowStack(); - - // Prints the specified argument. Used for debugging. - void Print(Value* argument); - - // A utility class tracking ids of various objects such as blocks, temps, etc. - class IdAllocator : public ValueObject { - public: - explicit IdAllocator(intptr_t first_id = 0) : next_id(first_id) {} - - intptr_t Count() const { return next_id; } - intptr_t Alloc(intptr_t count = 1) { - ASSERT(count >= 0); - intptr_t current_id = next_id; - next_id += count; - return current_id; - } - void Dealloc(intptr_t count = 1) { - ASSERT(count <= next_id); - next_id -= count; - } - - private: - intptr_t next_id; - }; - - Thread* thread_; - - // Which mode to generate code for (ASCII or UC16). - Mode mode_; - - // Which specific string class to generate code for. - intptr_t specialization_cid_; - - // Block entries used internally. - GraphEntryInstr* entry_block_; - JoinEntryInstr* start_block_; - JoinEntryInstr* success_block_; - JoinEntryInstr* exit_block_; - - // Shared backtracking block. - JoinEntryInstr* backtrack_block_; - // Single indirect goto instruction which performs all backtracking. - IndirectGotoInstr* backtrack_goto_; - - const ParsedFunction* parsed_function_; - const ZoneGrowableArray& ic_data_array_; - - // All created blocks are contained within this set. Used for printing - // the generated code. - GrowableArray blocks_; - - // The current instruction to link to when new code is emitted. - Instruction* current_instruction_; - - // A list, acting as the runtime stack for both backtrack locations and - // stored positions within the string. - LocalVariable* stack_; - LocalVariable* stack_pointer_; - - // Stores the current character within the string. - LocalVariable* current_character_; - - // Stores the current location within the string as a negative offset - // from the end of the string. - LocalVariable* current_position_; - - // The string being processed, passed as a function parameter. - LocalVariable* string_param_; - - // Stores the length of string_param_. - LocalVariable* string_param_length_; - - // The start index within the string, passed as a function parameter. - LocalVariable* start_index_param_; - - // An assortment of utility variables. - LocalVariable* capture_length_; - LocalVariable* match_start_index_; - LocalVariable* capture_start_index_; - LocalVariable* match_end_index_; - LocalVariable* char_in_capture_; - LocalVariable* char_in_match_; - LocalVariable* index_temp_; - - LocalVariable* result_; - - // Stored positions containing group bounds. Generated as needed. - LocalVariable* registers_; - intptr_t registers_count_; - const intptr_t saved_registers_count_; - - IdAllocator block_id_; - IdAllocator temp_id_; - IdAllocator local_id_; - IdAllocator indirect_id_; - - // Placeholder instruction holding number of registers in Irregexp entry block - // that is replaced with correct value during code finalization. - ConstantInstr* num_registers_constant_instr = nullptr; -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_IR_H_ diff --git a/runtime/vm/regexp/regexp_ast.cc b/runtime/vm/regexp/regexp_ast.cc deleted file mode 100644 index 3bfd878a7ba..00000000000 --- a/runtime/vm/regexp/regexp_ast.cc +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#include "vm/regexp/regexp_ast.h" - -#include "platform/utils.h" -#include "vm/os.h" - -namespace dart { - -#define MAKE_ACCEPT(Name) \ - void* RegExp##Name::Accept(RegExpVisitor* visitor, void* data) { \ - return visitor->Visit##Name(this, data); \ - } -FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ACCEPT) -#undef MAKE_ACCEPT - -#define MAKE_TYPE_CASE(Name) \ - RegExp##Name* RegExpTree::As##Name() { \ - return nullptr; \ - } \ - bool RegExpTree::Is##Name() const { \ - return false; \ - } -FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) -#undef MAKE_TYPE_CASE - -#define MAKE_TYPE_CASE(Name) \ - RegExp##Name* RegExp##Name::As##Name() { \ - return this; \ - } \ - bool RegExp##Name::Is##Name() const { \ - return true; \ - } -FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) -#undef MAKE_TYPE_CASE - -static Interval ListCaptureRegisters(ZoneGrowableArray* children) { - Interval result = Interval::Empty(); - for (intptr_t i = 0; i < children->length(); i++) - result = result.Union(children->At(i)->CaptureRegisters()); - return result; -} - -Interval RegExpAlternative::CaptureRegisters() const { - return ListCaptureRegisters(nodes()); -} - -Interval RegExpDisjunction::CaptureRegisters() const { - return ListCaptureRegisters(alternatives()); -} - -Interval RegExpLookaround::CaptureRegisters() const { - return body()->CaptureRegisters(); -} - -Interval RegExpCapture::CaptureRegisters() const { - Interval self(StartRegister(index()), EndRegister(index())); - return self.Union(body()->CaptureRegisters()); -} - -Interval RegExpQuantifier::CaptureRegisters() const { - return body()->CaptureRegisters(); -} - -bool RegExpAssertion::IsAnchoredAtStart() const { - return assertion_type() == RegExpAssertion::START_OF_INPUT; -} - -bool RegExpAssertion::IsAnchoredAtEnd() const { - return assertion_type() == RegExpAssertion::END_OF_INPUT; -} - -bool RegExpAlternative::IsAnchoredAtStart() const { - ZoneGrowableArray* nodes = this->nodes(); - for (intptr_t i = 0; i < nodes->length(); i++) { - RegExpTree* node = nodes->At(i); - if (node->IsAnchoredAtStart()) { - return true; - } - if (node->max_match() > 0) { - return false; - } - } - return false; -} - -bool RegExpAlternative::IsAnchoredAtEnd() const { - ZoneGrowableArray* nodes = this->nodes(); - for (intptr_t i = nodes->length() - 1; i >= 0; i--) { - RegExpTree* node = nodes->At(i); - if (node->IsAnchoredAtEnd()) { - return true; - } - if (node->max_match() > 0) { - return false; - } - } - return false; -} - -bool RegExpDisjunction::IsAnchoredAtStart() const { - ZoneGrowableArray* alternatives = this->alternatives(); - for (intptr_t i = 0; i < alternatives->length(); i++) { - if (!alternatives->At(i)->IsAnchoredAtStart()) return false; - } - return true; -} - -bool RegExpDisjunction::IsAnchoredAtEnd() const { - ZoneGrowableArray* alternatives = this->alternatives(); - for (intptr_t i = 0; i < alternatives->length(); i++) { - if (!alternatives->At(i)->IsAnchoredAtEnd()) return false; - } - return true; -} - -bool RegExpLookaround::IsAnchoredAtStart() const { - return is_positive() && type() == LOOKAHEAD && body()->IsAnchoredAtStart(); -} - -bool RegExpCapture::IsAnchoredAtStart() const { - return body()->IsAnchoredAtStart(); -} - -bool RegExpCapture::IsAnchoredAtEnd() const { - return body()->IsAnchoredAtEnd(); -} - -// Convert regular expression trees to a simple sexp representation. -// This representation should be different from the input grammar -// in as many cases as possible, to make it more difficult for incorrect -// parses to look as correct ones which is likely if the input and -// output formats are alike. -class RegExpUnparser : public RegExpVisitor { - public: - void VisitCharacterRange(CharacterRange that); -#define MAKE_CASE(Name) virtual void* Visit##Name(RegExp##Name*, void* data); - FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) -#undef MAKE_CASE -}; - -void* RegExpUnparser::VisitDisjunction(RegExpDisjunction* that, void* data) { - OS::PrintErr("(|"); - for (intptr_t i = 0; i < that->alternatives()->length(); i++) { - OS::PrintErr(" "); - (*that->alternatives())[i]->Accept(this, data); - } - OS::PrintErr(")"); - return nullptr; -} - -void* RegExpUnparser::VisitAlternative(RegExpAlternative* that, void* data) { - OS::PrintErr("(:"); - for (intptr_t i = 0; i < that->nodes()->length(); i++) { - OS::PrintErr(" "); - (*that->nodes())[i]->Accept(this, data); - } - OS::PrintErr(")"); - return nullptr; -} - -void RegExpUnparser::VisitCharacterRange(CharacterRange that) { - PrintUtf16(that.from()); - if (!that.IsSingleton()) { - OS::PrintErr("-"); - PrintUtf16(that.to()); - } -} - -void* RegExpUnparser::VisitCharacterClass(RegExpCharacterClass* that, - void* data) { - if (that->is_negated()) OS::PrintErr("^"); - OS::PrintErr("["); - for (intptr_t i = 0; i < that->ranges()->length(); i++) { - if (i > 0) OS::PrintErr(" "); - VisitCharacterRange((*that->ranges())[i]); - } - OS::PrintErr("]"); - return nullptr; -} - -void* RegExpUnparser::VisitAssertion(RegExpAssertion* that, void* data) { - switch (that->assertion_type()) { - case RegExpAssertion::START_OF_INPUT: - OS::PrintErr("@^i"); - break; - case RegExpAssertion::END_OF_INPUT: - OS::PrintErr("@$i"); - break; - case RegExpAssertion::START_OF_LINE: - OS::PrintErr("@^l"); - break; - case RegExpAssertion::END_OF_LINE: - OS::PrintErr("@$l"); - break; - case RegExpAssertion::BOUNDARY: - OS::PrintErr("@b"); - break; - case RegExpAssertion::NON_BOUNDARY: - OS::PrintErr("@B"); - break; - } - return nullptr; -} - -void* RegExpUnparser::VisitAtom(RegExpAtom* that, void* data) { - OS::PrintErr("'"); - ZoneGrowableArray* chardata = that->data(); - for (intptr_t i = 0; i < chardata->length(); i++) { - PrintUtf16(chardata->At(i)); - } - OS::PrintErr("'"); - return nullptr; -} - -void* RegExpUnparser::VisitText(RegExpText* that, void* data) { - if (that->elements()->length() == 1) { - (*that->elements())[0].tree()->Accept(this, data); - } else { - OS::PrintErr("(!"); - for (intptr_t i = 0; i < that->elements()->length(); i++) { - OS::PrintErr(" "); - (*that->elements())[i].tree()->Accept(this, data); - } - OS::PrintErr(")"); - } - return nullptr; -} - -void* RegExpUnparser::VisitQuantifier(RegExpQuantifier* that, void* data) { - OS::PrintErr("(# %" Pd " ", that->min()); - if (that->max() == RegExpTree::kInfinity) { - OS::PrintErr("- "); - } else { - OS::PrintErr("%" Pd " ", that->max()); - } - OS::PrintErr(that->is_greedy() ? "g " : that->is_possessive() ? "p " : "n "); - that->body()->Accept(this, data); - OS::PrintErr(")"); - return nullptr; -} - -void* RegExpUnparser::VisitCapture(RegExpCapture* that, void* data) { - OS::PrintErr("(^ "); - that->body()->Accept(this, data); - OS::PrintErr(")"); - return nullptr; -} - -void* RegExpUnparser::VisitLookaround(RegExpLookaround* that, void* data) { - OS::PrintErr("("); - OS::PrintErr("(%s %s", - (that->type() == RegExpLookaround::LOOKAHEAD ? "->" : "<-"), - (that->is_positive() ? "+ " : "- ")); - that->body()->Accept(this, data); - OS::PrintErr(")"); - return nullptr; -} - -void* RegExpUnparser::VisitBackReference(RegExpBackReference* that, void*) { - OS::PrintErr("(<- %" Pd ")", that->index()); - return nullptr; -} - -void* RegExpUnparser::VisitEmpty(RegExpEmpty*, void*) { - OS::PrintErr("%%"); - return nullptr; -} - -void RegExpTree::Print() { - RegExpUnparser unparser; - Accept(&unparser, nullptr); -} - -RegExpDisjunction::RegExpDisjunction( - ZoneGrowableArray* alternatives) - : alternatives_(alternatives) { - ASSERT(alternatives->length() > 1); - RegExpTree* first_alternative = alternatives->At(0); - min_match_ = first_alternative->min_match(); - max_match_ = first_alternative->max_match(); - for (intptr_t i = 1; i < alternatives->length(); i++) { - RegExpTree* alternative = alternatives->At(i); - min_match_ = Utils::Minimum(min_match_, alternative->min_match()); - max_match_ = Utils::Maximum(max_match_, alternative->max_match()); - } -} - -static intptr_t IncreaseBy(intptr_t previous, intptr_t increase) { - if (RegExpTree::kInfinity - previous < increase) { - return RegExpTree::kInfinity; - } else { - return previous + increase; - } -} - -RegExpAlternative::RegExpAlternative(ZoneGrowableArray* nodes) - : nodes_(nodes) { - ASSERT(nodes->length() > 1); - min_match_ = 0; - max_match_ = 0; - for (intptr_t i = 0; i < nodes->length(); i++) { - RegExpTree* node = nodes->At(i); - intptr_t node_min_match = node->min_match(); - min_match_ = IncreaseBy(min_match_, node_min_match); - intptr_t node_max_match = node->max_match(); - max_match_ = IncreaseBy(max_match_, node_max_match); - } -} - -} // namespace dart diff --git a/runtime/vm/regexp/regexp_ast.h b/runtime/vm/regexp/regexp_ast.h deleted file mode 100644 index 7b87283cb2f..00000000000 --- a/runtime/vm/regexp/regexp_ast.h +++ /dev/null @@ -1,448 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_AST_H_ -#define RUNTIME_VM_REGEXP_REGEXP_AST_H_ - -#include "platform/globals.h" -#include "platform/utils.h" -#include "vm/allocation.h" -#include "vm/regexp/regexp.h" - -namespace dart { - -class RegExpAlternative; -class RegExpAssertion; -class RegExpAtom; -class RegExpBackReference; -class RegExpCapture; -class RegExpCharacterClass; -class RegExpCompiler; -class RegExpDisjunction; -class RegExpEmpty; -class RegExpLookaround; -class RegExpQuantifier; -class RegExpText; - -class RegExpVisitor : public ValueObject { - public: - virtual ~RegExpVisitor() {} -#define MAKE_CASE(Name) \ - virtual void* Visit##Name(RegExp##Name*, void* data) = 0; - FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) -#undef MAKE_CASE -}; - -class RegExpTree : public ZoneObject { - public: - static constexpr intptr_t kInfinity = kMaxInt32; - virtual ~RegExpTree() {} - virtual void* Accept(RegExpVisitor* visitor, void* data) = 0; - virtual RegExpNode* ToNode(RegExpCompiler* compiler, - RegExpNode* on_success) = 0; - virtual bool IsTextElement() const { return false; } - virtual bool IsAnchoredAtStart() const { return false; } - virtual bool IsAnchoredAtEnd() const { return false; } - virtual intptr_t min_match() const = 0; - virtual intptr_t max_match() const = 0; - // Returns the interval of registers used for captures within this - // expression. - virtual Interval CaptureRegisters() const { return Interval::Empty(); } - virtual void AppendToText(RegExpText* text); - void Print(); -#define MAKE_ASTYPE(Name) \ - virtual RegExp##Name* As##Name(); \ - virtual bool Is##Name() const; - FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE) -#undef MAKE_ASTYPE -}; - -class RegExpDisjunction : public RegExpTree { - public: - explicit RegExpDisjunction(ZoneGrowableArray* alternatives); - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpDisjunction* AsDisjunction(); - virtual Interval CaptureRegisters() const; - virtual bool IsDisjunction() const; - virtual bool IsAnchoredAtStart() const; - virtual bool IsAnchoredAtEnd() const; - virtual intptr_t min_match() const { return min_match_; } - virtual intptr_t max_match() const { return max_match_; } - ZoneGrowableArray* alternatives() const { return alternatives_; } - - private: - ZoneGrowableArray* alternatives_; - intptr_t min_match_; - intptr_t max_match_; -}; - -class RegExpAlternative : public RegExpTree { - public: - explicit RegExpAlternative(ZoneGrowableArray* nodes); - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpAlternative* AsAlternative(); - virtual Interval CaptureRegisters() const; - virtual bool IsAlternative() const; - virtual bool IsAnchoredAtStart() const; - virtual bool IsAnchoredAtEnd() const; - virtual intptr_t min_match() const { return min_match_; } - virtual intptr_t max_match() const { return max_match_; } - ZoneGrowableArray* nodes() const { return nodes_; } - - private: - ZoneGrowableArray* nodes_; - intptr_t min_match_; - intptr_t max_match_; -}; - -class RegExpAssertion : public RegExpTree { - public: - enum AssertionType { - START_OF_LINE, - START_OF_INPUT, - END_OF_LINE, - END_OF_INPUT, - BOUNDARY, - NON_BOUNDARY - }; - RegExpAssertion(AssertionType type, RegExpFlags flags) - : assertion_type_(type), flags_(flags) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpAssertion* AsAssertion(); - virtual bool IsAssertion() const; - virtual bool IsAnchoredAtStart() const; - virtual bool IsAnchoredAtEnd() const; - virtual intptr_t min_match() const { return 0; } - virtual intptr_t max_match() const { return 0; } - AssertionType assertion_type() const { return assertion_type_; } - - private: - AssertionType assertion_type_; - RegExpFlags flags_; -}; - -class CharacterSet : public ValueObject { - public: - explicit CharacterSet(uint16_t standard_set_type) - : ranges_(nullptr), standard_set_type_(standard_set_type) {} - explicit CharacterSet(ZoneGrowableArray* ranges) - : ranges_(ranges), standard_set_type_(0) {} - CharacterSet(const CharacterSet& that) - : ValueObject(), - ranges_(that.ranges_), - standard_set_type_(that.standard_set_type_) {} - ZoneGrowableArray* ranges(); - uint16_t standard_set_type() const { return standard_set_type_; } - void set_standard_set_type(uint16_t special_set_type) { - standard_set_type_ = special_set_type; - } - bool is_standard() { return standard_set_type_ != 0; } - void Canonicalize(); - - private: - ZoneGrowableArray* ranges_; - // If non-zero, the value represents a standard set (e.g., all whitespace - // characters) without having to expand the ranges. - uint16_t standard_set_type_; -}; - -class RegExpCharacterClass : public RegExpTree { - public: - enum Flag { - // The character class is negated and should match everything but the - // specified ranges. - NEGATED = 1 << 0, - // The character class contains part of a split surrogate and should not - // be unicode-desugared. - CONTAINS_SPLIT_SURROGATE = 1 << 1, - }; - using CharacterClassFlags = intptr_t; - static inline CharacterClassFlags DefaultFlags() { return 0; } - - RegExpCharacterClass( - ZoneGrowableArray* ranges, - RegExpFlags flags, - CharacterClassFlags character_class_flags = DefaultFlags()) - : set_(ranges), - flags_(flags), - character_class_flags_(character_class_flags) { - // Convert the empty set of ranges to the negated Everything() range. - if (ranges->is_empty()) { - ranges->Add(CharacterRange::Everything()); - character_class_flags_ ^= NEGATED; - } - } - RegExpCharacterClass(uint16_t type, RegExpFlags flags) - : set_(type), flags_(flags), character_class_flags_(0) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpCharacterClass* AsCharacterClass(); - virtual bool IsCharacterClass() const; - virtual bool IsTextElement() const { return true; } - virtual intptr_t min_match() const { return 1; } - // The character class may match two code units for unicode regexps. - virtual intptr_t max_match() const { return 2; } - virtual void AppendToText(RegExpText* text); - CharacterSet character_set() const { return set_; } - // TODO(lrn): Remove need for complex version if is_standard that - // recognizes a mangled standard set and just do { return set_.is_special(); } - bool is_standard(); - // Returns a value representing the standard character set if is_standard() - // returns true. - // Currently used values are: - // s : unicode whitespace - // S : unicode non-whitespace - // w : ASCII word character (digit, letter, underscore) - // W : non-ASCII word character - // d : ASCII digit - // D : non-ASCII digit - // . : non-unicode non-newline - // * : All characters - uint16_t standard_type() const { return set_.standard_set_type(); } - ZoneGrowableArray* ranges() { return set_.ranges(); } - bool is_negated() const { return (character_class_flags_ & NEGATED) != 0; } - RegExpFlags flags() const { return flags_; } - bool contains_split_surrogate() const { - return (character_class_flags_ & CONTAINS_SPLIT_SURROGATE) != 0; - } - - private: - CharacterSet set_; - RegExpFlags flags_; - CharacterClassFlags character_class_flags_; -}; - -class RegExpAtom : public RegExpTree { - public: - RegExpAtom(ZoneGrowableArray* data, RegExpFlags flags) - : data_(data), flags_(flags) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpAtom* AsAtom(); - virtual bool IsAtom() const; - virtual bool IsTextElement() const { return true; } - virtual intptr_t min_match() const { return data_->length(); } - virtual intptr_t max_match() const { return data_->length(); } - virtual void AppendToText(RegExpText* text); - ZoneGrowableArray* data() const { return data_; } - intptr_t length() const { return data_->length(); } - RegExpFlags flags() const { return flags_; } - bool ignore_case() const { return flags_.IgnoreCase(); } - - private: - ZoneGrowableArray* data_; - const RegExpFlags flags_; -}; - -class RegExpText : public RegExpTree { - public: - RegExpText() : elements_(2), length_(0) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpText* AsText(); - virtual bool IsText() const; - virtual bool IsTextElement() const { return true; } - virtual intptr_t min_match() const { return length_; } - virtual intptr_t max_match() const { return length_; } - virtual void AppendToText(RegExpText* text); - void AddElement(TextElement elm) { - elements_.Add(elm); - length_ += elm.length(); - } - GrowableArray* elements() { return &elements_; } - - private: - GrowableArray elements_; - intptr_t length_; -}; - -class RegExpQuantifier : public RegExpTree { - public: - enum QuantifierType { GREEDY, NON_GREEDY, POSSESSIVE }; - RegExpQuantifier(intptr_t min, - intptr_t max, - QuantifierType type, - RegExpTree* body) - : body_(body), - min_(min), - max_(max), - min_match_(min * body->min_match()), - quantifier_type_(type) { - if (max > 0 && body->max_match() > kInfinity / max) { - max_match_ = kInfinity; - } else { - max_match_ = max * body->max_match(); - } - } - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - static RegExpNode* ToNode(intptr_t min, - intptr_t max, - bool is_greedy, - RegExpTree* body, - RegExpCompiler* compiler, - RegExpNode* on_success, - bool not_at_start = false); - virtual RegExpQuantifier* AsQuantifier(); - virtual Interval CaptureRegisters() const; - virtual bool IsQuantifier() const; - virtual intptr_t min_match() const { return min_match_; } - virtual intptr_t max_match() const { return max_match_; } - intptr_t min() const { return min_; } - intptr_t max() const { return max_; } - bool is_possessive() const { return quantifier_type_ == POSSESSIVE; } - bool is_non_greedy() const { return quantifier_type_ == NON_GREEDY; } - bool is_greedy() const { return quantifier_type_ == GREEDY; } - RegExpTree* body() const { return body_; } - - private: - RegExpTree* body_; - intptr_t min_; - intptr_t max_; - intptr_t min_match_; - intptr_t max_match_; - QuantifierType quantifier_type_; -}; - -class RegExpCapture : public RegExpTree { - public: - explicit RegExpCapture(intptr_t index) - : body_(nullptr), index_(index), name_(nullptr) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - static RegExpNode* ToNode(RegExpTree* body, - intptr_t index, - RegExpCompiler* compiler, - RegExpNode* on_success); - virtual RegExpCapture* AsCapture(); - virtual bool IsAnchoredAtStart() const; - virtual bool IsAnchoredAtEnd() const; - virtual Interval CaptureRegisters() const; - virtual bool IsCapture() const; - virtual intptr_t min_match() const { return body_->min_match(); } - virtual intptr_t max_match() const { return body_->max_match(); } - RegExpTree* body() const { return body_; } - // When a backreference is parsed before the corresponding capture group, - // which can happen because of lookbehind, we create the capture object when - // we create the backreference, and fill in the body later when the actual - // capture group is parsed. - void set_body(RegExpTree* body) { body_ = body; } - intptr_t index() const { return index_; } - const ZoneGrowableArray* name() { return name_; } - void set_name(const ZoneGrowableArray* name) { name_ = name; } - static intptr_t StartRegister(intptr_t index) { return index * 2; } - static intptr_t EndRegister(intptr_t index) { return index * 2 + 1; } - - private: - RegExpTree* body_; - intptr_t index_; - const ZoneGrowableArray* name_; -}; - -class RegExpLookaround : public RegExpTree { - public: - enum Type { LOOKAHEAD, LOOKBEHIND }; - RegExpLookaround(RegExpTree* body, - bool is_positive, - intptr_t capture_count, - intptr_t capture_from, - Type type) - : body_(body), - is_positive_(is_positive), - capture_count_(capture_count), - capture_from_(capture_from), - type_(type) {} - - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpLookaround* AsLookaround(); - virtual Interval CaptureRegisters() const; - virtual bool IsLookaround() const; - virtual bool IsAnchoredAtStart() const; - virtual intptr_t min_match() const { return 0; } - virtual intptr_t max_match() const { return 0; } - RegExpTree* body() const { return body_; } - bool is_positive() const { return is_positive_; } - intptr_t capture_count() const { return capture_count_; } - intptr_t capture_from() const { return capture_from_; } - Type type() const { return type_; } - - // The RegExpLookaround::Builder class abstracts out the process of building - // the compiling a RegExpLookaround object by splitting it into two phases, - // represented by the provided methods. - class Builder : public ValueObject { - public: - Builder(bool is_positive, - RegExpNode* on_success, - intptr_t stack_pointer_register, - intptr_t position_register, - intptr_t capture_register_count = 0, - intptr_t capture_register_start = 0); - RegExpNode* on_match_success() { return on_match_success_; } - RegExpNode* ForMatch(RegExpNode* match); - - private: - bool is_positive_; - RegExpNode* on_match_success_; - RegExpNode* on_success_; - intptr_t stack_pointer_register_; - intptr_t position_register_; - }; - - private: - RegExpTree* body_; - bool is_positive_; - intptr_t capture_count_; - intptr_t capture_from_; - Type type_; -}; - -class RegExpBackReference : public RegExpTree { - public: - explicit RegExpBackReference(RegExpFlags flags) - : capture_(nullptr), name_(nullptr), flags_(flags) {} - RegExpBackReference(RegExpCapture* capture, RegExpFlags flags) - : capture_(capture), name_(nullptr), flags_(flags) {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpBackReference* AsBackReference(); - virtual bool IsBackReference() const; - virtual intptr_t min_match() const { return 0; } - // The back reference may be recursive, e.g. /(\2)(\1)/. To avoid infinite - // recursion, we give up and just assume arbitrary length, which matches v8's - // behavior. - virtual intptr_t max_match() const { return kInfinity; } - intptr_t index() const { return capture_->index(); } - RegExpCapture* capture() const { return capture_; } - void set_capture(RegExpCapture* capture) { capture_ = capture; } - const ZoneGrowableArray* name() { return name_; } - void set_name(const ZoneGrowableArray* name) { name_ = name; } - - private: - RegExpCapture* capture_; - const ZoneGrowableArray* name_; - RegExpFlags flags_; -}; - -class RegExpEmpty : public RegExpTree { - public: - RegExpEmpty() {} - virtual void* Accept(RegExpVisitor* visitor, void* data); - virtual RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success); - virtual RegExpEmpty* AsEmpty(); - virtual bool IsEmpty() const; - virtual intptr_t min_match() const { return 0; } - virtual intptr_t max_match() const { return 0; } - static RegExpEmpty* GetInstance() { - static RegExpEmpty* instance = ::new RegExpEmpty(); - return instance; - } -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_AST_H_ diff --git a/runtime/vm/regexp/regexp_bytecodes.h b/runtime/vm/regexp/regexp_bytecodes.h deleted file mode 100644 index 271a85932db..00000000000 --- a/runtime/vm/regexp/regexp_bytecodes.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_BYTECODES_H_ -#define RUNTIME_VM_REGEXP_REGEXP_BYTECODES_H_ - -namespace dart { - -const int BYTECODE_MASK = 0xff; -// The first argument is packed in with the byte code in one word, but so it -// has 24 bits, but it can be positive and negative so only use 23 bits for -// positive values. -const unsigned int MAX_FIRST_ARG = 0x7fffffu; -const int BYTECODE_SHIFT = 8; - -// clang-format off -#define BYTECODE_ITERATOR(V) \ -V(BREAK, 0, 4) /* bc8 */ \ -V(PUSH_CP, 1, 4) /* bc8 pad24 */ \ -V(PUSH_BT, 2, 8) /* bc8 pad24 offset32 */ \ -V(PUSH_REGISTER, 3, 4) /* bc8 reg_idx24 */ \ -V(SET_REGISTER_TO_CP, 4, 8) /* bc8 reg_idx24 offset32 */ \ -V(SET_CP_TO_REGISTER, 5, 4) /* bc8 reg_idx24 */ \ -V(SET_REGISTER_TO_SP, 6, 4) /* bc8 reg_idx24 */ \ -V(SET_SP_TO_REGISTER, 7, 4) /* bc8 reg_idx24 */ \ -V(SET_REGISTER, 8, 8) /* bc8 reg_idx24 value32 */ \ -V(ADVANCE_REGISTER, 9, 8) /* bc8 reg_idx24 value32 */ \ -V(POP_CP, 10, 4) /* bc8 pad24 */ \ -V(POP_BT, 11, 4) /* bc8 pad24 */ \ -V(POP_REGISTER, 12, 4) /* bc8 reg_idx24 */ \ -V(FAIL, 13, 4) /* bc8 pad24 */ \ -V(SUCCEED, 14, 4) /* bc8 pad24 */ \ -V(ADVANCE_CP, 15, 4) /* bc8 offset24 */ \ -V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \ -V(LOAD_CURRENT_CHAR, 17, 8) /* bc8 offset24 addr32 */ \ -V(LOAD_CURRENT_CHAR_UNCHECKED, 18, 4) /* bc8 offset24 */ \ -V(LOAD_2_CURRENT_CHARS, 19, 8) /* bc8 offset24 addr32 */ \ -V(LOAD_2_CURRENT_CHARS_UNCHECKED, 20, 4) /* bc8 offset24 */ \ -V(LOAD_4_CURRENT_CHARS, 21, 8) /* bc8 offset24 addr32 */ \ -V(LOAD_4_CURRENT_CHARS_UNCHECKED, 22, 4) /* bc8 offset24 */ \ -V(CHECK_4_CHARS, 23, 12) /* bc8 pad24 uint32 addr32 */ \ -V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \ -V(CHECK_NOT_4_CHARS, 25, 12) /* bc8 pad24 uint32 addr32 */ \ -V(CHECK_NOT_CHAR, 26, 8) /* bc8 pad8 uint16 addr32 */ \ -V(AND_CHECK_4_CHARS, 27, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ -V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ -V(AND_CHECK_NOT_4_CHARS, 29, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ -V(AND_CHECK_NOT_CHAR, 30, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ -V(MINUS_AND_CHECK_NOT_CHAR, 31, 12) /* bc8 pad8 uc16 uc16 uc16 addr32 */ \ -V(CHECK_CHAR_IN_RANGE, 32, 12) /* bc8 pad24 uc16 uc16 addr32 */ \ -V(CHECK_CHAR_NOT_IN_RANGE, 33, 12) /* bc8 pad24 uc16 uc16 addr32 */ \ -V(CHECK_BIT_IN_TABLE, 34, 24) /* bc8 pad24 addr32 bits128 */ \ -V(CHECK_LT, 35, 8) /* bc8 pad8 uc16 addr32 */ \ -V(CHECK_GT, 36, 8) /* bc8 pad8 uc16 addr32 */ \ -V(CHECK_NOT_BACK_REF, 37, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_NOT_BACK_REF_NO_CASE, 38, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_NOT_BACK_REF_NO_CASE_UNICODE, 39, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_NOT_BACK_REF_BACKWARD, 40, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_NOT_BACK_REF_NO_CASE_BACKWARD, 41, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD, 42, 8) /*bc8 reg_idx24 addr32*/ \ -V(CHECK_NOT_REGS_EQUAL, 43, 12) /* bc8 regidx24 reg_idx32 addr32 */ \ -V(CHECK_REGISTER_LT, 44, 12) /* bc8 reg_idx24 value32 addr32 */ \ -V(CHECK_REGISTER_GE, 45, 12) /* bc8 reg_idx24 value32 addr32 */ \ -V(CHECK_REGISTER_EQ_POS, 46, 8) /* bc8 reg_idx24 addr32 */ \ -V(CHECK_AT_START, 47, 8) /* bc8 pad24 addr32 */ \ -V(CHECK_NOT_AT_START, 48, 8) /* bc8 offset24 addr32 */ \ -V(CHECK_GREEDY, 49, 8) /* bc8 pad24 addr32 */ \ -V(ADVANCE_CP_AND_GOTO, 50, 8) /* bc8 offset24 addr32 */ \ -V(SET_CURRENT_POSITION_FROM_END, 51, 4) /* bc8 idx24 */ - -// clang-format on - -#define DECLARE_BYTECODES(name, code, length) \ - static constexpr int BC_##name = code; -BYTECODE_ITERATOR(DECLARE_BYTECODES) -#undef DECLARE_BYTECODES - -#define DECLARE_BYTECODE_LENGTH(name, code, length) \ - static constexpr int BC_##name##_LENGTH = length; -BYTECODE_ITERATOR(DECLARE_BYTECODE_LENGTH) -#undef DECLARE_BYTECODE_LENGTH - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_BYTECODES_H_ diff --git a/runtime/vm/regexp/regexp_interpreter.cc b/runtime/vm/regexp/regexp_interpreter.cc deleted file mode 100644 index fead12cbbdc..00000000000 --- a/runtime/vm/regexp/regexp_interpreter.cc +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -// A simple interpreter for the Irregexp byte code. - -#include -#include - -#include "vm/heap/safepoint.h" -#include "vm/regexp/regexp_interpreter.h" - -#include "platform/unicode.h" -#include "vm/object.h" -#include "vm/regexp/regexp_assembler.h" -#include "vm/regexp/regexp_bytecodes.h" -#include "vm/regexp/unibrow-inl.h" -#include "vm/regexp/unibrow.h" - -namespace dart { - -DEFINE_FLAG(bool, trace_regexp_bytecodes, false, "trace_regexp_bytecodes"); -DEFINE_FLAG(int, - regexp_backtrack_stack_size_kb, - 256, - "Size of backtracking stack"); - -typedef unibrow::Mapping Canonicalize; - -template -static bool BackRefMatchesNoCase(Canonicalize* interp_canonicalize, - intptr_t from, - intptr_t current, - intptr_t len, - const String& subject, - bool unicode); - -template <> -bool BackRefMatchesNoCase(Canonicalize* interp_canonicalize, - intptr_t from, - intptr_t current, - intptr_t len, - const String& subject, - bool unicode) { - Bool& ret = Bool::Handle(); - if (unicode) { - ret = static_cast(DLRT_CaseInsensitiveCompareUTF16( - static_cast(subject.ptr()), static_cast(Smi::New(from)), - static_cast(Smi::New(current)), - static_cast(Smi::New(len)))); - } else { - ret = static_cast(DLRT_CaseInsensitiveCompareUCS2( - static_cast(subject.ptr()), static_cast(Smi::New(from)), - static_cast(Smi::New(current)), - static_cast(Smi::New(len)))); - } - return ret.value(); -} - -template <> -bool BackRefMatchesNoCase(Canonicalize* interp_canonicalize, - intptr_t from, - intptr_t current, - intptr_t len, - const String& subject, - bool unicode) { - // For Latin1 characters the unicode flag makes no difference. - for (int i = 0; i < len; i++) { - unsigned int old_char = subject.CharAt(from++); - unsigned int new_char = subject.CharAt(current++); - if (old_char == new_char) continue; - // Convert both characters to lower case. - old_char |= 0x20; - new_char |= 0x20; - if (old_char != new_char) return false; - // Not letters in the ASCII range and Latin-1 range. - if (!(old_char - 'a' <= 'z' - 'a') && - !(old_char - 224 <= 254 - 224 && old_char != 247)) { - return false; - } - } - return true; -} - -#ifdef DEBUG -static void TraceInterpreter(const uint8_t* code_base, - const uint8_t* pc, - int stack_depth, - int current_position, - uint32_t current_char, - int bytecode_length, - const char* bytecode_name) { - if (FLAG_trace_regexp_bytecodes) { - bool printable = (current_char < 127 && current_char >= 32); - const char* format = - printable - ? "pc = %02x, sp = %d, curpos = %d, curchar = %08x (%c), bc = %s" - : "pc = %02x, sp = %d, curpos = %d, curchar = %08x .%c., bc = %s"; - OS::PrintErr(format, pc - code_base, stack_depth, current_position, - current_char, printable ? current_char : '.', bytecode_name); - for (int i = 0; i < bytecode_length; i++) { - OS::PrintErr(", %02x", pc[i]); - } - OS::PrintErr(" "); - for (int i = 1; i < bytecode_length; i++) { - unsigned char b = pc[i]; - if (b < 127 && b >= 32) { - OS::PrintErr("%c", b); - } else { - OS::PrintErr("."); - } - } - OS::PrintErr("\n"); - } -} - -#define BYTECODE(name) \ - case BC_##name: \ - TraceInterpreter(code_base, pc, \ - static_cast(backtrack_sp - backtrack_stack_base), \ - current, current_char, BC_##name##_LENGTH, #name); -#else -#define BYTECODE(name) case BC_##name: -#endif - -static int32_t Load32Aligned(const uint8_t* pc) { - ASSERT((reinterpret_cast(pc) & 3) == 0); - return *reinterpret_cast(pc); -} - -static int32_t Load16Aligned(const uint8_t* pc) { - ASSERT((reinterpret_cast(pc) & 1) == 0); - return *reinterpret_cast(pc); -} - -// A simple abstraction over the backtracking stack used by the interpreter. -// This backtracking stack does not grow automatically, but it ensures that the -// the memory held by the stack is released or remembered in a cache if the -// matching terminates. -class BacktrackStack { - public: - BacktrackStack() { - memory_ = Thread::Current()->TakeRegexpBacktrackStack(); - // Note: using malloc here has a potential of triggering jemalloc/tcmalloc - // bugs which cause application to leak memory and eventually OOM. - // See https://github.com/dart-lang/sdk/issues/38820 and - // https://github.com/flutter/flutter/issues/29007 for examples. - // So instead we directly ask OS to provide us memory. - if (memory_ == nullptr) { - const bool executable = false; - const bool compressed = false; - const intptr_t size_in_bytes = Utils::RoundUp( - FLAG_regexp_backtrack_stack_size_kb * KB, VirtualMemory::PageSize()); - memory_ = std::unique_ptr(VirtualMemory::Allocate( - size_in_bytes, executable, compressed, "regexp-backtrack-stack")); - } - } - - ~BacktrackStack() { - if (memory_ != nullptr) { - Thread::Current()->CacheRegexpBacktrackStack(std::move(memory_)); - } - } - - bool out_of_memory() const { return memory_ == nullptr; } - - int32_t* data() const { - return reinterpret_cast(memory_->address()); - } - - intptr_t max_size() const { return memory_->size() / sizeof(int32_t); } - - private: - std::unique_ptr memory_; - - DISALLOW_COPY_AND_ASSIGN(BacktrackStack); -}; - -// Returns True if success, False if failure, Null if internal exception, -// Error if VM error needs to be propagated up the callchain. -template -static ObjectPtr RawMatch(const TypedData& bytecode, - const String& subject, - int32_t* registers, - int32_t current, - uint32_t current_char) { - // BacktrackStack ensures that the memory allocated for the backtracking stack - // is returned to the system or cached if there is no stack being cached at - // the moment. - BacktrackStack backtrack_stack; - if (backtrack_stack.out_of_memory()) { - Exceptions::ThrowOOM(); - UNREACHABLE(); - } - int32_t* backtrack_stack_base = backtrack_stack.data(); - int32_t* backtrack_sp = backtrack_stack_base; - intptr_t backtrack_stack_space = backtrack_stack.max_size(); - - // TODO(zerny): Optimize as single instance. V8 has this as an - // isolate member. - unibrow::Mapping canonicalize; - - intptr_t subject_length = subject.Length(); - -#ifdef DEBUG - if (FLAG_trace_regexp_bytecodes) { - OS::PrintErr("Start irregexp bytecode interpreter\n"); - } -#endif - const auto thread = Thread::Current(); - const uint8_t* code_base; - const uint8_t* pc; - { - NoSafepointScope no_safepoint; - code_base = reinterpret_cast(bytecode.DataAddr(0)); - pc = code_base; - } - while (true) { - if (UNLIKELY(thread->HasScheduledInterrupts())) { - intptr_t pc_offset = pc - code_base; - ErrorPtr error = thread->HandleInterrupts(); - if (error != Object::null()) { - // Needs to be propagated to the Dart native invoking the - // regex matcher. - return error; - } - NoSafepointScope no_safepoint; - code_base = reinterpret_cast(bytecode.DataAddr(0)); - pc = code_base + pc_offset; - } - NoSafepointScope no_safepoint; - bool check_for_safepoint_now = false; - while (!check_for_safepoint_now) { - int32_t insn = Load32Aligned(pc); - switch (insn & BYTECODE_MASK) { - BYTECODE(BREAK) - UNREACHABLE(); - return Bool::False().ptr(); - BYTECODE(PUSH_CP) - if (--backtrack_stack_space < 0) { - return Object::null(); - } - *backtrack_sp++ = current; - pc += BC_PUSH_CP_LENGTH; - break; - BYTECODE(PUSH_BT) - if (--backtrack_stack_space < 0) { - return Object::null(); - } - *backtrack_sp++ = Load32Aligned(pc + 4); - pc += BC_PUSH_BT_LENGTH; - break; - BYTECODE(PUSH_REGISTER) - if (--backtrack_stack_space < 0) { - return Object::null(); - } - *backtrack_sp++ = registers[insn >> BYTECODE_SHIFT]; - pc += BC_PUSH_REGISTER_LENGTH; - break; - BYTECODE(SET_REGISTER) - registers[insn >> BYTECODE_SHIFT] = Load32Aligned(pc + 4); - pc += BC_SET_REGISTER_LENGTH; - break; - BYTECODE(ADVANCE_REGISTER) - registers[insn >> BYTECODE_SHIFT] += Load32Aligned(pc + 4); - pc += BC_ADVANCE_REGISTER_LENGTH; - break; - BYTECODE(SET_REGISTER_TO_CP) - registers[insn >> BYTECODE_SHIFT] = current + Load32Aligned(pc + 4); - pc += BC_SET_REGISTER_TO_CP_LENGTH; - break; - BYTECODE(SET_CP_TO_REGISTER) - current = registers[insn >> BYTECODE_SHIFT]; - pc += BC_SET_CP_TO_REGISTER_LENGTH; - break; - BYTECODE(SET_REGISTER_TO_SP) - registers[insn >> BYTECODE_SHIFT] = - static_cast(backtrack_sp - backtrack_stack_base); - pc += BC_SET_REGISTER_TO_SP_LENGTH; - break; - BYTECODE(SET_SP_TO_REGISTER) - backtrack_sp = backtrack_stack_base + registers[insn >> BYTECODE_SHIFT]; - backtrack_stack_space = - backtrack_stack.max_size() - - static_cast(backtrack_sp - backtrack_stack_base); - pc += BC_SET_SP_TO_REGISTER_LENGTH; - break; - BYTECODE(POP_CP) - backtrack_stack_space++; - --backtrack_sp; - current = *backtrack_sp; - pc += BC_POP_CP_LENGTH; - break; - BYTECODE(POP_BT) - backtrack_stack_space++; - --backtrack_sp; - pc = code_base + *backtrack_sp; - // This should match check cadence in JIT irregexp implementation. - check_for_safepoint_now = true; - break; - BYTECODE(POP_REGISTER) - backtrack_stack_space++; - --backtrack_sp; - registers[insn >> BYTECODE_SHIFT] = *backtrack_sp; - pc += BC_POP_REGISTER_LENGTH; - break; - BYTECODE(FAIL) - return Bool::False().ptr(); - BYTECODE(SUCCEED) - return Bool::True().ptr(); - BYTECODE(ADVANCE_CP) - current += insn >> BYTECODE_SHIFT; - pc += BC_ADVANCE_CP_LENGTH; - break; - BYTECODE(GOTO) - pc = code_base + Load32Aligned(pc + 4); - break; - BYTECODE(ADVANCE_CP_AND_GOTO) - current += insn >> BYTECODE_SHIFT; - pc = code_base + Load32Aligned(pc + 4); - break; - BYTECODE(CHECK_GREEDY) - if (current == backtrack_sp[-1]) { - backtrack_sp--; - backtrack_stack_space++; - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_GREEDY_LENGTH; - } - break; - BYTECODE(LOAD_CURRENT_CHAR) { - int pos = current + (insn >> BYTECODE_SHIFT); - if (pos < 0 || pos >= subject_length) { - pc = code_base + Load32Aligned(pc + 4); - } else { - current_char = subject.CharAt(pos); - pc += BC_LOAD_CURRENT_CHAR_LENGTH; - } - break; - } - BYTECODE(LOAD_CURRENT_CHAR_UNCHECKED) { - int pos = current + (insn >> BYTECODE_SHIFT); - current_char = subject.CharAt(pos); - pc += BC_LOAD_CURRENT_CHAR_UNCHECKED_LENGTH; - break; - } - BYTECODE(LOAD_2_CURRENT_CHARS) { - int pos = current + (insn >> BYTECODE_SHIFT); - if (pos + 2 > subject_length) { - pc = code_base + Load32Aligned(pc + 4); - } else { - Char next = subject.CharAt(pos + 1); - current_char = - subject.CharAt(pos) | (next << (kBitsPerByte * sizeof(Char))); - pc += BC_LOAD_2_CURRENT_CHARS_LENGTH; - } - break; - } - BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) { - int pos = current + (insn >> BYTECODE_SHIFT); - Char next = subject.CharAt(pos + 1); - current_char = - subject.CharAt(pos) | (next << (kBitsPerByte * sizeof(Char))); - pc += BC_LOAD_2_CURRENT_CHARS_UNCHECKED_LENGTH; - break; - } - BYTECODE(LOAD_4_CURRENT_CHARS) { - ASSERT(sizeof(Char) == 1); - int pos = current + (insn >> BYTECODE_SHIFT); - if (pos + 4 > subject_length) { - pc = code_base + Load32Aligned(pc + 4); - } else { - Char next1 = subject.CharAt(pos + 1); - Char next2 = subject.CharAt(pos + 2); - Char next3 = subject.CharAt(pos + 3); - current_char = (subject.CharAt(pos) | (next1 << 8) | (next2 << 16) | - (next3 << 24)); - pc += BC_LOAD_4_CURRENT_CHARS_LENGTH; - } - break; - } - BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED) { - ASSERT(sizeof(Char) == 1); - int pos = current + (insn >> BYTECODE_SHIFT); - Char next1 = subject.CharAt(pos + 1); - Char next2 = subject.CharAt(pos + 2); - Char next3 = subject.CharAt(pos + 3); - current_char = (subject.CharAt(pos) | (next1 << 8) | (next2 << 16) | - (next3 << 24)); - pc += BC_LOAD_4_CURRENT_CHARS_UNCHECKED_LENGTH; - break; - } - BYTECODE(CHECK_4_CHARS) { - uint32_t c = Load32Aligned(pc + 4); - if (c == current_char) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_4_CHARS_LENGTH; - } - break; - } - BYTECODE(CHECK_CHAR) { - uint32_t c = (insn >> BYTECODE_SHIFT); - if (c == current_char) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_CHAR_LENGTH; - } - break; - } - BYTECODE(CHECK_NOT_4_CHARS) { - uint32_t c = Load32Aligned(pc + 4); - if (c != current_char) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_NOT_4_CHARS_LENGTH; - } - break; - } - BYTECODE(CHECK_NOT_CHAR) { - uint32_t c = (insn >> BYTECODE_SHIFT); - if (c != current_char) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_NOT_CHAR_LENGTH; - } - break; - } - BYTECODE(AND_CHECK_4_CHARS) { - uint32_t c = Load32Aligned(pc + 4); - if (c == (current_char & Load32Aligned(pc + 8))) { - pc = code_base + Load32Aligned(pc + 12); - } else { - pc += BC_AND_CHECK_4_CHARS_LENGTH; - } - break; - } - BYTECODE(AND_CHECK_CHAR) { - uint32_t c = (insn >> BYTECODE_SHIFT); - if (c == (current_char & Load32Aligned(pc + 4))) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_AND_CHECK_CHAR_LENGTH; - } - break; - } - BYTECODE(AND_CHECK_NOT_4_CHARS) { - uint32_t c = Load32Aligned(pc + 4); - if (c != (current_char & Load32Aligned(pc + 8))) { - pc = code_base + Load32Aligned(pc + 12); - } else { - pc += BC_AND_CHECK_NOT_4_CHARS_LENGTH; - } - break; - } - BYTECODE(AND_CHECK_NOT_CHAR) { - uint32_t c = (insn >> BYTECODE_SHIFT); - if (c != (current_char & Load32Aligned(pc + 4))) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_AND_CHECK_NOT_CHAR_LENGTH; - } - break; - } - BYTECODE(MINUS_AND_CHECK_NOT_CHAR) { - uint32_t c = (insn >> BYTECODE_SHIFT); - uint32_t minus = Load16Aligned(pc + 4); - uint32_t mask = Load16Aligned(pc + 6); - if (c != ((current_char - minus) & mask)) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_MINUS_AND_CHECK_NOT_CHAR_LENGTH; - } - break; - } - BYTECODE(CHECK_CHAR_IN_RANGE) { - uint32_t from = Load16Aligned(pc + 4); - uint32_t to = Load16Aligned(pc + 6); - if (from <= current_char && current_char <= to) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_CHAR_IN_RANGE_LENGTH; - } - break; - } - BYTECODE(CHECK_CHAR_NOT_IN_RANGE) { - uint32_t from = Load16Aligned(pc + 4); - uint32_t to = Load16Aligned(pc + 6); - if (from > current_char || current_char > to) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_CHAR_NOT_IN_RANGE_LENGTH; - } - break; - } - BYTECODE(CHECK_BIT_IN_TABLE) { - int mask = RegExpMacroAssembler::kTableMask; - uint8_t b = pc[8 + ((current_char & mask) >> kBitsPerByteLog2)]; - int bit = (current_char & (kBitsPerByte - 1)); - if ((b & (1 << bit)) != 0) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_BIT_IN_TABLE_LENGTH; - } - break; - } - BYTECODE(CHECK_LT) { - uint32_t limit = (insn >> BYTECODE_SHIFT); - if (current_char < limit) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_LT_LENGTH; - } - break; - } - BYTECODE(CHECK_GT) { - uint32_t limit = (insn >> BYTECODE_SHIFT); - if (current_char > limit) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_GT_LENGTH; - } - break; - } - BYTECODE(CHECK_REGISTER_LT) - if (registers[insn >> BYTECODE_SHIFT] < Load32Aligned(pc + 4)) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_REGISTER_LT_LENGTH; - } - break; - BYTECODE(CHECK_REGISTER_GE) - if (registers[insn >> BYTECODE_SHIFT] >= Load32Aligned(pc + 4)) { - pc = code_base + Load32Aligned(pc + 8); - } else { - pc += BC_CHECK_REGISTER_GE_LENGTH; - } - break; - BYTECODE(CHECK_REGISTER_EQ_POS) - if (registers[insn >> BYTECODE_SHIFT] == current) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_REGISTER_EQ_POS_LENGTH; - } - break; - BYTECODE(CHECK_NOT_REGS_EQUAL) - if (registers[insn >> BYTECODE_SHIFT] == - registers[Load32Aligned(pc + 4)]) { - pc += BC_CHECK_NOT_REGS_EQUAL_LENGTH; - } else { - pc = code_base + Load32Aligned(pc + 8); - } - break; - BYTECODE(CHECK_NOT_BACK_REF) { - int from = registers[insn >> BYTECODE_SHIFT]; - int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; - if (from < 0 || len <= 0) { - pc += BC_CHECK_NOT_BACK_REF_LENGTH; - break; - } - if (current + len > subject_length) { - pc = code_base + Load32Aligned(pc + 4); - break; - } else { - int i; - for (i = 0; i < len; i++) { - if (subject.CharAt(from + i) != subject.CharAt(current + i)) { - pc = code_base + Load32Aligned(pc + 4); - break; - } - } - if (i < len) break; - current += len; - } - pc += BC_CHECK_NOT_BACK_REF_LENGTH; - break; - } - BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE) - FALL_THROUGH; - BYTECODE(CHECK_NOT_BACK_REF_NO_CASE) { - const bool unicode = - (insn & BYTECODE_MASK) == BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE; - int from = registers[insn >> BYTECODE_SHIFT]; - int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; - if (from < 0 || len <= 0) { - pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH; - break; - } - if (current + len > subject_length) { - pc = code_base + Load32Aligned(pc + 4); - break; - } else { - if (BackRefMatchesNoCase(&canonicalize, from, current, len, - subject, unicode)) { - current += len; - pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH; - } else { - pc = code_base + Load32Aligned(pc + 4); - } - } - break; - } - BYTECODE(CHECK_NOT_BACK_REF_BACKWARD) { - const int from = registers[insn >> BYTECODE_SHIFT]; - const int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; - if (from < 0 || len <= 0) { - pc += BC_CHECK_NOT_BACK_REF_BACKWARD_LENGTH; - break; - } - if ((current - len) < 0) { - pc = code_base + Load32Aligned(pc + 4); - break; - } else { - // When looking behind, the string to match (if it is there) lies - // before the current position, so we will check the [len] - // characters before the current position, excluding the current - // position itself. - const int start = current - len; - int i; - for (i = 0; i < len; i++) { - if (subject.CharAt(from + i) != subject.CharAt(start + i)) { - pc = code_base + Load32Aligned(pc + 4); - break; - } - } - if (i < len) break; - current -= len; - } - pc += BC_CHECK_NOT_BACK_REF_BACKWARD_LENGTH; - break; - } - BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD) - FALL_THROUGH; - BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_BACKWARD) { - bool unicode = (insn & BYTECODE_MASK) == - BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD; - int from = registers[insn >> BYTECODE_SHIFT]; - int len = registers[(insn >> BYTECODE_SHIFT) + 1] - from; - if (from < 0 || len <= 0) { - pc += BC_CHECK_NOT_BACK_REF_NO_CASE_BACKWARD_LENGTH; - break; - } - if (current < len) { - pc = code_base + Load32Aligned(pc + 4); - break; - } else { - if (BackRefMatchesNoCase(&canonicalize, from, current - len, - len, subject, unicode)) { - current -= len; - pc += BC_CHECK_NOT_BACK_REF_NO_CASE_BACKWARD_LENGTH; - } else { - pc = code_base + Load32Aligned(pc + 4); - } - } - break; - } - BYTECODE(CHECK_AT_START) - if (current == 0) { - pc = code_base + Load32Aligned(pc + 4); - } else { - pc += BC_CHECK_AT_START_LENGTH; - } - break; - BYTECODE(CHECK_NOT_AT_START) { - const int32_t cp_offset = insn >> BYTECODE_SHIFT; - if (current + cp_offset == 0) { - pc += BC_CHECK_NOT_AT_START_LENGTH; - } else { - pc = code_base + Load32Aligned(pc + 4); - } - break; - } - BYTECODE(SET_CURRENT_POSITION_FROM_END) { - int by = static_cast(insn) >> BYTECODE_SHIFT; - if (subject_length - current > by) { - current = subject_length - by; - current_char = subject.CharAt(current - 1); - } - pc += BC_SET_CURRENT_POSITION_FROM_END_LENGTH; - break; - } - default: - UNREACHABLE(); - break; - } - } - } -} - -// Returns True if success, False if failure, Null if internal exception, -// Error if VM error needs to be propagated up the callchain. -ObjectPtr IrregexpInterpreter::Match(const TypedData& bytecode, - const String& subject, - int32_t* registers, - int32_t start_position) { - uint16_t previous_char = '\n'; - if (start_position != 0) { - previous_char = subject.CharAt(start_position - 1); - } - - if (subject.IsOneByteString()) { - return RawMatch(bytecode, subject, registers, start_position, - previous_char); - } else if (subject.IsTwoByteString()) { - return RawMatch(bytecode, subject, registers, start_position, - previous_char); - } else { - UNREACHABLE(); - return Bool::False().ptr(); - } -} - -} // namespace dart diff --git a/runtime/vm/regexp/regexp_interpreter.h b/runtime/vm/regexp/regexp_interpreter.h deleted file mode 100644 index 78c7b564383..00000000000 --- a/runtime/vm/regexp/regexp_interpreter.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -// A simple interpreter for the Irregexp byte code. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_INTERPRETER_H_ -#define RUNTIME_VM_REGEXP_REGEXP_INTERPRETER_H_ - -#include "vm/allocation.h" -#include "vm/object.h" -#include "vm/zone.h" - -namespace dart { - -class IrregexpInterpreter : public AllStatic { - public: - // Returns True in case of a success, False in case of a failure, - // Null in case of internal exception, - // Error in case VM error has to propagated up to the caller. - static ObjectPtr Match(const TypedData& bytecode, - const String& subject, - int32_t* captures, - int32_t start_position); -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_INTERPRETER_H_ diff --git a/runtime/vm/regexp/regexp_parser.cc b/runtime/vm/regexp/regexp_parser.cc deleted file mode 100644 index 792a1b3397e..00000000000 --- a/runtime/vm/regexp/regexp_parser.cc +++ /dev/null @@ -1,1989 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#include "vm/regexp/regexp_parser.h" - -#include "unicode/uchar.h" -#include "unicode/uniset.h" - -#include "platform/unicode.h" - -#include "vm/longjump.h" -#include "vm/object_store.h" -#include "vm/symbols.h" - -namespace dart { - -#define Z zone() - -// Enables possessive quantifier syntax for testing. -static constexpr bool FLAG_regexp_possessive_quantifier = false; - -RegExpBuilder::RegExpBuilder(RegExpFlags flags) - : zone_(Thread::Current()->zone()), - pending_empty_(false), - flags_(flags), - characters_(nullptr), - pending_surrogate_(kNoPendingSurrogate), - terms_(), - text_(), - alternatives_() -#ifdef DEBUG - , - last_added_(ADD_NONE) -#endif -{ -} - -void RegExpBuilder::AddLeadSurrogate(uint16_t lead_surrogate) { - ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); - FlushPendingSurrogate(); - // Hold onto the lead surrogate, waiting for a trail surrogate to follow. - pending_surrogate_ = lead_surrogate; -} - -void RegExpBuilder::AddTrailSurrogate(uint16_t trail_surrogate) { - ASSERT(Utf16::IsTrailSurrogate(trail_surrogate)); - if (pending_surrogate_ != kNoPendingSurrogate) { - uint16_t lead_surrogate = pending_surrogate_; - pending_surrogate_ = kNoPendingSurrogate; - ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); - uint32_t combined = Utf16::Decode(lead_surrogate, trail_surrogate); - if (NeedsDesugaringForIgnoreCase(combined)) { - AddCharacterClassForDesugaring(combined); - } else { - auto surrogate_pair = new (Z) ZoneGrowableArray(2); - surrogate_pair->Add(lead_surrogate); - surrogate_pair->Add(trail_surrogate); - RegExpAtom* atom = new (Z) RegExpAtom(surrogate_pair, flags_); - AddAtom(atom); - } - } else { - pending_surrogate_ = trail_surrogate; - FlushPendingSurrogate(); - } -} - -void RegExpBuilder::FlushPendingSurrogate() { - if (pending_surrogate_ != kNoPendingSurrogate) { - ASSERT(is_unicode()); - uint32_t c = pending_surrogate_; - pending_surrogate_ = kNoPendingSurrogate; - AddCharacterClassForDesugaring(c); - } -} - -void RegExpBuilder::FlushCharacters() { - FlushPendingSurrogate(); - pending_empty_ = false; - if (characters_ != nullptr) { - RegExpTree* atom = new (Z) RegExpAtom(characters_, flags_); - characters_ = nullptr; - text_.Add(atom); - LAST(ADD_ATOM); - } -} - -void RegExpBuilder::FlushText() { - FlushCharacters(); - intptr_t num_text = text_.length(); - if (num_text == 0) { - return; - } else if (num_text == 1) { - terms_.Add(text_.Last()); - } else { - RegExpText* text = new (Z) RegExpText(); - for (intptr_t i = 0; i < num_text; i++) - text_[i]->AppendToText(text); - terms_.Add(text); - } - text_.Clear(); -} - -void RegExpBuilder::AddCharacter(uint16_t c) { - FlushPendingSurrogate(); - pending_empty_ = false; - if (NeedsDesugaringForIgnoreCase(c)) { - AddCharacterClassForDesugaring(c); - } else { - if (characters_ == nullptr) { - characters_ = new (Z) ZoneGrowableArray(4); - } - characters_->Add(c); - LAST(ADD_CHAR); - } -} - -void RegExpBuilder::AddUnicodeCharacter(uint32_t c) { - if (c > static_cast(Utf16::kMaxCodeUnit)) { - ASSERT(is_unicode()); - uint16_t surrogates[2]; - Utf16::Encode(c, surrogates); - AddLeadSurrogate(surrogates[0]); - AddTrailSurrogate(surrogates[1]); - } else if (is_unicode() && Utf16::IsLeadSurrogate(c)) { - AddLeadSurrogate(c); - } else if (is_unicode() && Utf16::IsTrailSurrogate(c)) { - AddTrailSurrogate(c); - } else { - AddCharacter(static_cast(c)); - } -} - -void RegExpBuilder::AddEscapedUnicodeCharacter(uint32_t character) { - // A lead or trail surrogate parsed via escape sequence will not - // pair up with any preceding lead or following trail surrogate. - FlushPendingSurrogate(); - AddUnicodeCharacter(character); - FlushPendingSurrogate(); -} - -void RegExpBuilder::AddEmpty() { - pending_empty_ = true; -} - -void RegExpBuilder::AddCharacterClass(RegExpCharacterClass* cc) { - if (NeedsDesugaringForUnicode(cc)) { - // With /u, character class needs to be desugared, so it - // must be a standalone term instead of being part of a RegExpText. - AddTerm(cc); - } else { - AddAtom(cc); - } -} - -void RegExpBuilder::AddCharacterClassForDesugaring(uint32_t c) { - auto ranges = CharacterRange::List(Z, CharacterRange::Singleton(c)); - AddTerm(new (Z) RegExpCharacterClass(ranges, flags_)); -} - -void RegExpBuilder::AddAtom(RegExpTree* term) { - if (term->IsEmpty()) { - AddEmpty(); - return; - } - if (term->IsTextElement()) { - FlushCharacters(); - text_.Add(term); - } else { - FlushText(); - terms_.Add(term); - } - LAST(ADD_ATOM); -} - -void RegExpBuilder::AddTerm(RegExpTree* term) { - FlushText(); - terms_.Add(term); - LAST(ADD_ATOM); -} - -void RegExpBuilder::AddAssertion(RegExpTree* assert) { - FlushText(); - terms_.Add(assert); - LAST(ADD_ASSERT); -} - -void RegExpBuilder::NewAlternative() { - FlushTerms(); -} - -void RegExpBuilder::FlushTerms() { - FlushText(); - intptr_t num_terms = terms_.length(); - RegExpTree* alternative; - if (num_terms == 0) { - alternative = RegExpEmpty::GetInstance(); - } else if (num_terms == 1) { - alternative = terms_.Last(); - } else { - ZoneGrowableArray* terms = - new (Z) ZoneGrowableArray(); - for (intptr_t i = 0; i < terms_.length(); i++) { - terms->Add(terms_[i]); - } - alternative = new (Z) RegExpAlternative(terms); - } - alternatives_.Add(alternative); - terms_.Clear(); - LAST(ADD_NONE); -} - -bool RegExpBuilder::NeedsDesugaringForUnicode(RegExpCharacterClass* cc) { - if (!is_unicode()) return false; - // TODO(yangguo): we could be smarter than this. Case-insensitivity does not - // necessarily mean that we need to desugar. It's probably nicer to have a - // separate pass to figure out unicode desugarings. - if (ignore_case()) return true; - ZoneGrowableArray* ranges = cc->ranges(); - CharacterRange::Canonicalize(ranges); - - if (cc->is_negated()) { - auto negated_ranges = - new (Z) ZoneGrowableArray(ranges->length()); - CharacterRange::Negate(ranges, negated_ranges); - ranges = negated_ranges; - } - - for (int i = ranges->length() - 1; i >= 0; i--) { - uint32_t from = ranges->At(i).from(); - uint32_t to = ranges->At(i).to(); - // Check for non-BMP characters. - if (to >= Utf16::kMaxCodeUnit) return true; - // Check for lone surrogates. - if (from <= Utf16::kTrailSurrogateEnd && to >= Utf16::kLeadSurrogateStart) { - return true; - } - } - return false; -} - -bool RegExpBuilder::NeedsDesugaringForIgnoreCase(uint32_t c) { - if (is_unicode() && ignore_case()) { - icu::UnicodeSet set(c, c); - set.closeOver(USET_CASE_INSENSITIVE); - set.removeAllStrings(); - return set.size() > 1; - } - return false; -} - -RegExpTree* RegExpBuilder::ToRegExp() { - FlushTerms(); - intptr_t num_alternatives = alternatives_.length(); - if (num_alternatives == 0) { - return RegExpEmpty::GetInstance(); - } - if (num_alternatives == 1) { - return alternatives_.Last(); - } - ZoneGrowableArray* alternatives = - new (Z) ZoneGrowableArray(); - for (intptr_t i = 0; i < alternatives_.length(); i++) { - alternatives->Add(alternatives_[i]); - } - return new (Z) RegExpDisjunction(alternatives); -} - -bool RegExpBuilder::AddQuantifierToAtom( - intptr_t min, - intptr_t max, - RegExpQuantifier::QuantifierType quantifier_type) { - if (pending_empty_) { - pending_empty_ = false; - return true; - } - RegExpTree* atom; - if (characters_ != nullptr) { - DEBUG_ASSERT(last_added_ == ADD_CHAR); - // Last atom was character. - - ZoneGrowableArray* char_vector = - new (Z) ZoneGrowableArray(); - char_vector->AddArray(*characters_); - intptr_t num_chars = char_vector->length(); - if (num_chars > 1) { - ZoneGrowableArray* prefix = - new (Z) ZoneGrowableArray(); - for (intptr_t i = 0; i < num_chars - 1; i++) { - prefix->Add(char_vector->At(i)); - } - text_.Add(new (Z) RegExpAtom(prefix, flags_)); - ZoneGrowableArray* tail = new (Z) ZoneGrowableArray(); - tail->Add(char_vector->At(num_chars - 1)); - char_vector = tail; - } - characters_ = nullptr; - atom = new (Z) RegExpAtom(char_vector, flags_); - FlushText(); - } else if (text_.length() > 0) { - DEBUG_ASSERT(last_added_ == ADD_ATOM); - atom = text_.RemoveLast(); - FlushText(); - } else if (terms_.length() > 0) { - DEBUG_ASSERT(last_added_ == ADD_ATOM); - atom = terms_.RemoveLast(); - if (auto lookaround = atom->AsLookaround()) { - // With /u, lookarounds are not quantifiable. - if (is_unicode()) return false; - // Lookbehinds are not quantifiable. - if (lookaround->type() == RegExpLookaround::LOOKBEHIND) { - return false; - } - } - if (atom->max_match() == 0) { - // Guaranteed to only match an empty string. - LAST(ADD_TERM); - if (min == 0) { - return true; - } - terms_.Add(atom); - return true; - } - } else { - // Only call immediately after adding an atom or character! - UNREACHABLE(); - } - terms_.Add(new (Z) RegExpQuantifier(min, max, quantifier_type, atom)); - LAST(ADD_TERM); - return true; -} - -// ---------------------------------------------------------------------------- -// Implementation of Parser - -RegExpParser::RegExpParser(const String& in, String* error, RegExpFlags flags) - : zone_(Thread::Current()->zone()), - captures_(nullptr), - named_captures_(nullptr), - named_back_references_(nullptr), - in_(in), - current_(kEndMarker), - next_pos_(0), - captures_started_(0), - capture_count_(0), - has_more_(true), - top_level_flags_(flags), - simple_(false), - contains_anchor_(false), - is_scanned_for_captures_(false), - has_named_captures_(false) { - Advance(); -} - -inline uint32_t RegExpParser::ReadNext(bool update_position) { - intptr_t position = next_pos_; - const uint16_t c0 = in().CharAt(position); - uint32_t c = c0; - position++; - if (is_unicode() && position < in().Length() && Utf16::IsLeadSurrogate(c0)) { - const uint16_t c1 = in().CharAt(position); - if (Utf16::IsTrailSurrogate(c1)) { - c = Utf16::Decode(c0, c1); - position++; - } - } - if (update_position) next_pos_ = position; - return c; -} - -uint32_t RegExpParser::Next() { - if (has_next()) { - return ReadNext(false); - } else { - return kEndMarker; - } -} - -void RegExpParser::Advance() { - if (has_next()) { - current_ = ReadNext(true); - } else { - current_ = kEndMarker; - // Advance so that position() points to 1 after the last character. This is - // important so that Reset() to this position works correctly. - next_pos_ = in().Length() + 1; - has_more_ = false; - } -} - -void RegExpParser::Reset(intptr_t pos) { - next_pos_ = pos; - has_more_ = (pos < in().Length()); - Advance(); -} - -void RegExpParser::Advance(intptr_t dist) { - next_pos_ += dist - 1; - Advance(); -} - -bool RegExpParser::simple() { - return simple_; -} - -bool RegExpParser::IsSyntaxCharacterOrSlash(uint32_t c) { - switch (c) { - case '^': - case '$': - case '\\': - case '.': - case '*': - case '+': - case '?': - case '(': - case ')': - case '[': - case ']': - case '{': - case '}': - case '|': - case '/': - return true; - default: - break; - } - return false; -} - -void RegExpParser::ReportError(const char* message) { - // Zip to the end to make sure the no more input is read. - current_ = kEndMarker; - next_pos_ = in().Length(); - - // Throw a FormatException on parsing failures. - Array& args = Array::Handle(); - String& str = String::Handle(); - args ^= Array::New(3); - str ^= String::New(message); - args.SetAt(0, str); - args.SetAt(1, Symbols::Blank()); - args.SetAt(2, in()); - str ^= String::ConcatAll(args); - args ^= Array::New(1); - args.SetAt(0, str); - Exceptions::ThrowByType(Exceptions::kFormat, args); - UNREACHABLE(); -} - -// Pattern :: -// Disjunction -RegExpTree* RegExpParser::ParsePattern() { - RegExpTree* result = ParseDisjunction(); - PatchNamedBackReferences(); - ASSERT(!has_more()); - // If the result of parsing is a literal string atom, and it has the - // same length as the input, then the atom is identical to the input. - if (result->IsAtom() && result->AsAtom()->length() == in().Length()) { - simple_ = true; - } - return result; -} - -// Used for error messages where we would have fallen back on treating an -// escape as the identity escape, but we are in Unicode mode. -static const char* kUnicodeIdentity = - "Invalid identity escape in Unicode pattern"; - -// Disjunction :: -// Alternative -// Alternative | Disjunction -// Alternative :: -// [empty] -// Term Alternative -// Term :: -// Assertion -// Atom -// Atom Quantifier -RegExpTree* RegExpParser::ParseDisjunction() { - // Used to store current state while parsing subexpressions. - RegExpParserState initial_state(nullptr, INITIAL, RegExpLookaround::LOOKAHEAD, - 0, nullptr, top_level_flags_, Z); - RegExpParserState* stored_state = &initial_state; - // Cache the builder in a local variable for quick access. - RegExpBuilder* builder = initial_state.builder(); - while (true) { - switch (current()) { - case kEndMarker: - if (stored_state->IsSubexpression()) { - // Inside a parenthesized group when hitting end of input. - ReportError("Unterminated group"); - UNREACHABLE(); - } - ASSERT(INITIAL == stored_state->group_type()); - // Parsing completed successfully. - return builder->ToRegExp(); - case ')': { - if (!stored_state->IsSubexpression()) { - ReportError("Unmatched ')'"); - UNREACHABLE(); - } - ASSERT(INITIAL != stored_state->group_type()); - - Advance(); - // End disjunction parsing and convert builder content to new single - // regexp atom. - RegExpTree* body = builder->ToRegExp(); - - intptr_t end_capture_index = captures_started(); - - intptr_t capture_index = stored_state->capture_index(); - SubexpressionType group_type = stored_state->group_type(); - - // Build result of subexpression. - if (group_type == CAPTURE) { - if (stored_state->IsNamedCapture()) { - CreateNamedCaptureAtIndex(stored_state->capture_name(), - capture_index); - } - RegExpCapture* capture = GetCapture(capture_index); - capture->set_body(body); - body = capture; - } else if (group_type != GROUPING) { - ASSERT(group_type == POSITIVE_LOOKAROUND || - group_type == NEGATIVE_LOOKAROUND); - bool is_positive = (group_type == POSITIVE_LOOKAROUND); - body = new (Z) RegExpLookaround( - body, is_positive, end_capture_index - capture_index, - capture_index, stored_state->lookaround_type()); - } - - // Restore previous state. - stored_state = stored_state->previous_state(); - builder = stored_state->builder(); - - builder->AddAtom(body); - // For compatibility with JSC and ES3, we allow quantifiers after - // lookaheads, and break in all cases. - break; - } - case '|': { - Advance(); - builder->NewAlternative(); - continue; - } - case '*': - case '+': - case '?': - ReportError("Nothing to repeat"); - UNREACHABLE(); - case '^': { - Advance(); - if (builder->is_multi_line()) { - builder->AddAssertion(new (Z) RegExpAssertion( - RegExpAssertion::START_OF_LINE, builder->flags())); - } else { - builder->AddAssertion(new (Z) RegExpAssertion( - RegExpAssertion::START_OF_INPUT, builder->flags())); - set_contains_anchor(); - } - continue; - } - case '$': { - Advance(); - RegExpAssertion::AssertionType assertion_type = - builder->is_multi_line() ? RegExpAssertion::END_OF_LINE - : RegExpAssertion::END_OF_INPUT; - builder->AddAssertion( - new (Z) RegExpAssertion(assertion_type, builder->flags())); - continue; - } - case '.': { - Advance(); - auto ranges = new (Z) ZoneGrowableArray(2); - if (builder->is_dot_all()) { - // Everything. - CharacterRange::AddClassEscape( - '*', ranges, - /*add_unicode_case_equivalents=*/false); - } else { - // everything except \x0a, \x0d, \u2028 and \u2029 - CharacterRange::AddClassEscape( - '.', ranges, - /*add_unicode_case_equivalents=*/false); - } - RegExpCharacterClass* cc = - new (Z) RegExpCharacterClass(ranges, builder->flags()); - builder->AddCharacterClass(cc); - break; - } - case '(': { - stored_state = ParseOpenParenthesis(stored_state); - builder = stored_state->builder(); - continue; - } - case '[': { - RegExpTree* atom = ParseCharacterClass(builder); - builder->AddCharacterClass(atom->AsCharacterClass()); - break; - } - // Atom :: - // \ AtomEscape - case '\\': - switch (Next()) { - case kEndMarker: - ReportError("\\ at end of pattern"); - UNREACHABLE(); - case 'b': - Advance(2); - builder->AddAssertion(new (Z) RegExpAssertion( - RegExpAssertion::BOUNDARY, builder->flags())); - continue; - case 'B': - Advance(2); - builder->AddAssertion(new (Z) RegExpAssertion( - RegExpAssertion::NON_BOUNDARY, builder->flags())); - continue; - // AtomEscape :: - // CharacterClassEscape - // - // CharacterClassEscape :: one of - // d D s S w W - case 'd': - case 'D': - case 's': - case 'S': - case 'w': - case 'W': { - uint32_t c = Next(); - Advance(2); - auto ranges = new (Z) ZoneGrowableArray(2); - CharacterRange::AddClassEscape( - c, ranges, is_unicode() && builder->ignore_case()); - RegExpCharacterClass* cc = - new (Z) RegExpCharacterClass(ranges, builder->flags()); - builder->AddCharacterClass(cc); - break; - } - case 'p': - case 'P': { - uint32_t p = Next(); - Advance(2); - - if (is_unicode()) { - auto name_1 = new (Z) ZoneGrowableArray(); - auto name_2 = new (Z) ZoneGrowableArray(); - auto ranges = new (Z) ZoneGrowableArray(2); - if (ParsePropertyClassName(name_1, name_2)) { - if (AddPropertyClassRange(ranges, p == 'P', name_1, name_2)) { - RegExpCharacterClass* cc = - new (Z) RegExpCharacterClass(ranges, builder->flags()); - builder->AddCharacterClass(cc); - break; - } - } - ReportError("Invalid property name"); - UNREACHABLE(); - } else { - builder->AddCharacter(p); - } - break; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - intptr_t index = 0; - if (ParseBackReferenceIndex(&index)) { - if (stored_state->IsInsideCaptureGroup(index)) { - // The back reference is inside the capture group it refers to. - // Nothing can possibly have been captured yet, so we use empty - // instead. This ensures that, when checking a back reference, - // the capture registers of the referenced capture are either - // both set or both cleared. - builder->AddEmpty(); - } else { - RegExpCapture* capture = GetCapture(index); - RegExpTree* atom = - new (Z) RegExpBackReference(capture, builder->flags()); - builder->AddAtom(atom); - } - break; - } - // With /u, no identity escapes except for syntax characters are - // allowed. Otherwise, all identity escapes are allowed. - if (is_unicode()) { - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - uint32_t first_digit = Next(); - if (first_digit == '8' || first_digit == '9') { - builder->AddCharacter(first_digit); - Advance(2); - break; - } - } - FALL_THROUGH; - case '0': { - Advance(); - if (is_unicode() && Next() >= '0' && Next() <= '9') { - // With /u, decimal escape with leading 0 are not parsed as octal. - ReportError("Invalid decimal escape"); - UNREACHABLE(); - } - uint32_t octal = ParseOctalLiteral(); - builder->AddCharacter(octal); - break; - } - // ControlEscape :: one of - // f n r t v - case 'f': - Advance(2); - builder->AddCharacter('\f'); - break; - case 'n': - Advance(2); - builder->AddCharacter('\n'); - break; - case 'r': - Advance(2); - builder->AddCharacter('\r'); - break; - case 't': - Advance(2); - builder->AddCharacter('\t'); - break; - case 'v': - Advance(2); - builder->AddCharacter('\v'); - break; - case 'c': { - Advance(); - uint32_t controlLetter = Next(); - // Special case if it is an ASCII letter. - // Convert lower case letters to uppercase. - uint32_t letter = controlLetter & ~('a' ^ 'A'); - if (letter < 'A' || 'Z' < letter) { - // controlLetter is not in range 'A'-'Z' or 'a'-'z'. - // This is outside the specification. We match JSC in - // reading the backslash as a literal character instead - // of as starting an escape. - if (is_unicode()) { - // With /u, invalid escapes are not treated as identity escapes. - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - builder->AddCharacter('\\'); - } else { - Advance(2); - builder->AddCharacter(controlLetter & 0x1f); - } - break; - } - case 'x': { - Advance(2); - uint32_t value; - if (ParseHexEscape(2, &value)) { - builder->AddCharacter(value); - } else if (!is_unicode()) { - builder->AddCharacter('x'); - } else { - // With /u, invalid escapes are not treated as identity escapes. - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - break; - } - case 'u': { - Advance(2); - uint32_t value; - if (ParseUnicodeEscape(&value)) { - builder->AddEscapedUnicodeCharacter(value); - } else if (!is_unicode()) { - builder->AddCharacter('u'); - } else { - // With /u, invalid escapes are not treated as identity escapes. - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - break; - } - case 'k': - // Either an identity escape or a named back-reference. The two - // interpretations are mutually exclusive: '\k' is interpreted as - // an identity escape for non-Unicode patterns without named - // capture groups, and as the beginning of a named back-reference - // in all other cases. - if (is_unicode() || HasNamedCaptures()) { - Advance(2); - ParseNamedBackReference(builder, stored_state); - break; - } - FALL_THROUGH; - default: - Advance(); - // With the unicode flag, no identity escapes except for syntax - // characters are allowed. Otherwise, all identity escapes are - // allowed. - if (!is_unicode() || IsSyntaxCharacterOrSlash(current())) { - builder->AddCharacter(current()); - Advance(); - } else { - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - break; - } - break; - case '{': { - intptr_t dummy; - if (ParseIntervalQuantifier(&dummy, &dummy)) { - ReportError("Nothing to repeat"); - UNREACHABLE(); - } - } - FALL_THROUGH; - case '}': - case ']': - if (is_unicode()) { - ReportError("Lone quantifier brackets"); - UNREACHABLE(); - } - FALL_THROUGH; - default: - builder->AddUnicodeCharacter(current()); - Advance(); - break; - } // end switch(current()) - - intptr_t min; - intptr_t max; - switch (current()) { - // QuantifierPrefix :: - // * - // + - // ? - // { - case '*': - min = 0; - max = RegExpTree::kInfinity; - Advance(); - break; - case '+': - min = 1; - max = RegExpTree::kInfinity; - Advance(); - break; - case '?': - min = 0; - max = 1; - Advance(); - break; - case '{': - if (ParseIntervalQuantifier(&min, &max)) { - if (max < min) { - ReportError("numbers out of order in {} quantifier."); - UNREACHABLE(); - } - break; - } else { - continue; - } - default: - continue; - } - RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; - if (current() == '?') { - quantifier_type = RegExpQuantifier::NON_GREEDY; - Advance(); - } else if (FLAG_regexp_possessive_quantifier && current() == '+') { - // FLAG_regexp_possessive_quantifier is a debug-only flag. - quantifier_type = RegExpQuantifier::POSSESSIVE; - Advance(); - } - if (!builder->AddQuantifierToAtom(min, max, quantifier_type)) { - ReportError("invalid quantifier."); - UNREACHABLE(); - } - } -} - -#ifdef DEBUG -// Currently only used in an ASSERT. -static bool IsSpecialClassEscape(uint32_t c) { - switch (c) { - case 'd': - case 'D': - case 's': - case 'S': - case 'w': - case 'W': - return true; - default: - return false; - } -} -#endif - -RegExpParser::RegExpParserState* RegExpParser::ParseOpenParenthesis( - RegExpParserState* state) { - RegExpLookaround::Type lookaround_type = state->lookaround_type(); - bool is_named_capture = false; - const RegExpCaptureName* capture_name = nullptr; - SubexpressionType subexpr_type = CAPTURE; - Advance(); - if (current() == '?') { - switch (Next()) { - case ':': - Advance(2); - subexpr_type = GROUPING; - break; - case '=': - Advance(2); - lookaround_type = RegExpLookaround::LOOKAHEAD; - subexpr_type = POSITIVE_LOOKAROUND; - break; - case '!': - Advance(2); - lookaround_type = RegExpLookaround::LOOKAHEAD; - subexpr_type = NEGATIVE_LOOKAROUND; - break; - case '<': - Advance(); - if (Next() == '=') { - Advance(2); - lookaround_type = RegExpLookaround::LOOKBEHIND; - subexpr_type = POSITIVE_LOOKAROUND; - break; - } else if (Next() == '!') { - Advance(2); - lookaround_type = RegExpLookaround::LOOKBEHIND; - subexpr_type = NEGATIVE_LOOKAROUND; - break; - } - is_named_capture = true; - has_named_captures_ = true; - Advance(); - break; - default: - ReportError("Invalid group"); - UNREACHABLE(); - } - } - - if (subexpr_type == CAPTURE) { - if (captures_started_ >= kMaxCaptures) { - ReportError("Too many captures"); - UNREACHABLE(); - } - captures_started_++; - - if (is_named_capture) { - capture_name = ParseCaptureGroupName(); - } - } - // Store current state and begin new disjunction parsing. - return new (Z) - RegExpParserState(state, subexpr_type, lookaround_type, captures_started_, - capture_name, state->builder()->flags(), Z); -} - -// In order to know whether an escape is a backreference or not we have to scan -// the entire regexp and find the number of capturing parentheses. However we -// don't want to scan the regexp twice unless it is necessary. This mini-parser -// is called when needed. It can see the difference between capturing and -// noncapturing parentheses and can skip character classes and backslash-escaped -// characters. -void RegExpParser::ScanForCaptures() { - ASSERT(!is_scanned_for_captures_); - const intptr_t saved_position = position(); - // Start with captures started previous to current position - intptr_t capture_count = captures_started(); - // Add count of captures after this position. - uintptr_t n; - while ((n = current()) != kEndMarker) { - Advance(); - switch (n) { - case '\\': - Advance(); - break; - case '[': { - uintptr_t c; - while ((c = current()) != kEndMarker) { - Advance(); - if (c == '\\') { - Advance(); - } else { - if (c == ']') break; - } - } - break; - } - case '(': - // At this point we could be in - // * a non-capturing group '(:', - // * a lookbehind assertion '(?<=' '(? kMaxCaptures) { - Reset(start); - return false; - } - Advance(); - } else { - break; - } - } - if (value > captures_started()) { - if (!is_scanned_for_captures_) ScanForCaptures(); - if (value > capture_count_) { - Reset(start); - return false; - } - } - *index_out = value; - return true; -} - -namespace { - -static inline constexpr bool IsAsciiIdentifierPart(uint32_t ch) { - return Utils::IsAlphaNumeric(ch) || ch == '_' || ch == '$'; -} - -// ES#sec-names-and-keywords Names and Keywords -// UnicodeIDStart, '$', '_' and '\' -static bool IsIdentifierStartSlow(uint32_t c) { - // cannot use u_isIDStart because it does not work for - // Other_ID_Start characters. - return u_hasBinaryProperty(c, UCHAR_ID_START) || - (c < 0x60 && (c == '$' || c == '\\' || c == '_')); -} - -// ES#sec-names-and-keywords Names and Keywords -// UnicodeIDContinue, '$', '_', '\', ZWJ, and ZWNJ -static bool IsIdentifierPartSlow(uint32_t c) { - const uint32_t kZeroWidthNonJoiner = 0x200C; - const uint32_t kZeroWidthJoiner = 0x200D; - // Can't use u_isIDPart because it does not work for - // Other_ID_Continue characters. - return u_hasBinaryProperty(c, UCHAR_ID_CONTINUE) || - (c < 0x60 && (c == '$' || c == '\\' || c == '_')) || - c == kZeroWidthNonJoiner || c == kZeroWidthJoiner; -} - -static inline bool IsIdentifierStart(uint32_t c) { - if (c > 127) return IsIdentifierStartSlow(c); - return IsAsciiIdentifierPart(c) && !Utils::IsDecimalDigit(c); -} - -static inline bool IsIdentifierPart(uint32_t c) { - if (c > 127) return IsIdentifierPartSlow(c); - return IsAsciiIdentifierPart(c); -} - -static bool IsSameName(const RegExpCaptureName* name1, - const RegExpCaptureName* name2) { - if (name1->length() != name2->length()) return false; - for (intptr_t i = 0; i < name1->length(); i++) { - if (name1->At(i) != name2->At(i)) return false; - } - return true; -} - -} // end namespace - -static void PushCodeUnit(RegExpCaptureName* v, uint32_t code_unit) { - if (code_unit <= Utf16::kMaxCodeUnit) { - v->Add(code_unit); - } else { - uint16_t units[2]; - Utf16::Encode(code_unit, units); - v->Add(units[0]); - v->Add(units[1]); - } -} - -const RegExpCaptureName* RegExpParser::ParseCaptureGroupName() { - auto name = new (Z) RegExpCaptureName(); - - bool at_start = true; - while (true) { - uint32_t c = current(); - Advance(); - - // Convert unicode escapes. - if (c == '\\' && current() == 'u') { - Advance(); - if (!ParseUnicodeEscape(&c)) { - ReportError("Invalid Unicode escape sequence"); - UNREACHABLE(); - } - } - - // The backslash char is misclassified as both ID_Start and ID_Continue. - if (c == '\\') { - ReportError("Invalid capture group name"); - UNREACHABLE(); - } - - if (at_start) { - if (!IsIdentifierStart(c)) { - ReportError("Invalid capture group name"); - UNREACHABLE(); - } - PushCodeUnit(name, c); - at_start = false; - } else { - if (c == '>') { - break; - } else if (IsIdentifierPart(c)) { - PushCodeUnit(name, c); - } else { - ReportError("Invalid capture group name"); - UNREACHABLE(); - } - } - } - - return name; -} - -intptr_t RegExpParser::GetNamedCaptureIndex(const RegExpCaptureName* name) { - for (const auto& capture : *named_captures_) { - if (IsSameName(name, capture->name())) return capture->index(); - } - return -1; -} - -void RegExpParser::CreateNamedCaptureAtIndex(const RegExpCaptureName* name, - intptr_t index) { - ASSERT(0 < index && index <= captures_started_); - ASSERT(name != nullptr); - - if (named_captures_ == nullptr) { - named_captures_ = new (Z) ZoneGrowableArray(1); - } else { - // Check for duplicates and bail if we find any. Currently O(n^2). - if (GetNamedCaptureIndex(name) >= 0) { - ReportError("Duplicate capture group name"); - UNREACHABLE(); - } - } - - RegExpCapture* capture = GetCapture(index); - ASSERT(capture->name() == nullptr); - - capture->set_name(name); - named_captures_->Add(capture); -} - -bool RegExpParser::ParseNamedBackReference(RegExpBuilder* builder, - RegExpParserState* state) { - // The parser is assumed to be on the '<' in \k. - if (current() != '<') { - ReportError("Invalid named reference"); - UNREACHABLE(); - } - - Advance(); - const RegExpCaptureName* name = ParseCaptureGroupName(); - if (name == nullptr) { - return false; - } - - if (state->IsInsideCaptureGroup(name)) { - builder->AddEmpty(); - } else { - RegExpBackReference* atom = new (Z) RegExpBackReference(builder->flags()); - atom->set_name(name); - - builder->AddAtom(atom); - - if (named_back_references_ == nullptr) { - named_back_references_ = - new (Z) ZoneGrowableArray(1); - } - named_back_references_->Add(atom); - } - - return true; -} - -void RegExpParser::PatchNamedBackReferences() { - if (named_back_references_ == nullptr) return; - - if (named_captures_ == nullptr) { - ReportError("Invalid named capture referenced"); - return; - } - - // Look up and patch the actual capture for each named back reference. - // Currently O(n^2), optimize if necessary. - for (intptr_t i = 0; i < named_back_references_->length(); i++) { - RegExpBackReference* ref = named_back_references_->At(i); - intptr_t index = GetNamedCaptureIndex(ref->name()); - - if (index < 0) { - ReportError("Invalid named capture referenced"); - UNREACHABLE(); - } - ref->set_capture(GetCapture(index)); - } -} - -RegExpCapture* RegExpParser::GetCapture(intptr_t index) { - // The index for the capture groups are one-based. Its index in the list is - // zero-based. - const intptr_t know_captures = - is_scanned_for_captures_ ? capture_count_ : captures_started_; - ASSERT(index <= know_captures); - if (captures_ == nullptr) { - captures_ = new (Z) ZoneGrowableArray(know_captures); - } - while (captures_->length() < know_captures) { - captures_->Add(new (Z) RegExpCapture(captures_->length() + 1)); - } - return captures_->At(index - 1); -} - -ArrayPtr RegExpParser::CreateCaptureNameMap() { - if (named_captures_ == nullptr || named_captures_->is_empty()) { - return Array::null(); - } - - const intptr_t len = named_captures_->length() * 2; - - const Array& array = Array::Handle(Array::New(len)); - - auto& name = String::Handle(); - auto& smi = Smi::Handle(); - for (intptr_t i = 0; i < named_captures_->length(); i++) { - RegExpCapture* capture = named_captures_->At(i); - name = - String::FromUTF16(capture->name()->data(), capture->name()->length()); - smi = Smi::New(capture->index()); - array.SetAt(i * 2, name); - array.SetAt(i * 2 + 1, smi); - } - - return array.ptr(); -} - -bool RegExpParser::HasNamedCaptures() { - if (has_named_captures_ || is_scanned_for_captures_) { - return has_named_captures_; - } - - ScanForCaptures(); - ASSERT(is_scanned_for_captures_); - return has_named_captures_; -} - -bool RegExpParser::RegExpParserState::IsInsideCaptureGroup(intptr_t index) { - for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { - if (s->group_type() != CAPTURE) continue; - // Return true if we found the matching capture index. - if (index == s->capture_index()) return true; - // Abort if index is larger than what has been parsed up till this state. - if (index > s->capture_index()) return false; - } - return false; -} - -bool RegExpParser::RegExpParserState::IsInsideCaptureGroup( - const RegExpCaptureName* name) { - ASSERT(name != nullptr); - for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { - if (s->capture_name() == nullptr) continue; - if (IsSameName(s->capture_name(), name)) return true; - } - return false; -} - -// QuantifierPrefix :: -// { DecimalDigits } -// { DecimalDigits , } -// { DecimalDigits , DecimalDigits } -// -// Returns true if parsing succeeds, and set the min_out and max_out -// values. Values are truncated to RegExpTree::kInfinity if they overflow. -bool RegExpParser::ParseIntervalQuantifier(intptr_t* min_out, - intptr_t* max_out) { - ASSERT(current() == '{'); - intptr_t start = position(); - Advance(); - intptr_t min = 0; - if (!Utils::IsDecimalDigit(current())) { - Reset(start); - return false; - } - while (Utils::IsDecimalDigit(current())) { - intptr_t next = current() - '0'; - if (min > (RegExpTree::kInfinity - next) / 10) { - // Overflow. Skip past remaining decimal digits and return -1. - do { - Advance(); - } while (Utils::IsDecimalDigit(current())); - min = RegExpTree::kInfinity; - break; - } - min = 10 * min + next; - Advance(); - } - intptr_t max = 0; - if (current() == '}') { - max = min; - Advance(); - } else if (current() == ',') { - Advance(); - if (current() == '}') { - max = RegExpTree::kInfinity; - Advance(); - } else { - while (Utils::IsDecimalDigit(current())) { - intptr_t next = current() - '0'; - if (max > (RegExpTree::kInfinity - next) / 10) { - do { - Advance(); - } while (Utils::IsDecimalDigit(current())); - max = RegExpTree::kInfinity; - break; - } - max = 10 * max + next; - Advance(); - } - if (current() != '}') { - Reset(start); - return false; - } - Advance(); - } - } else { - Reset(start); - return false; - } - *min_out = min; - *max_out = max; - return true; -} - -uint32_t RegExpParser::ParseOctalLiteral() { - ASSERT(('0' <= current() && current() <= '7') || current() == kEndMarker); - // For compatibility with some other browsers (not all), we parse - // up to three octal digits with a value below 256. - uint32_t value = current() - '0'; - Advance(); - if ('0' <= current() && current() <= '7') { - value = value * 8 + current() - '0'; - Advance(); - if (value < 32 && '0' <= current() && current() <= '7') { - value = value * 8 + current() - '0'; - Advance(); - } - } - return value; -} - -// Returns the value (0 .. 15) of a hexadecimal character c. -// If c is not a legal hexadecimal character, returns a value < 0. -static inline intptr_t HexValue(uint32_t c) { - c -= '0'; - if (static_cast(c) <= 9) return c; - c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. - if (static_cast(c) <= 5) return c + 10; - return -1; -} - -bool RegExpParser::ParseHexEscape(intptr_t length, uint32_t* value) { - intptr_t start = position(); - uint32_t val = 0; - bool done = false; - for (intptr_t i = 0; !done; i++) { - uint32_t c = current(); - intptr_t d = HexValue(c); - if (d < 0) { - Reset(start); - return false; - } - val = val * 16 + d; - Advance(); - if (i == length - 1) { - done = true; - } - } - *value = val; - return true; -} - -// This parses RegExpUnicodeEscapeSequence as described in ECMA262. -bool RegExpParser::ParseUnicodeEscape(uint32_t* value) { - // Accept both \uxxxx and \u{xxxxxx} (if harmony unicode escapes are - // allowed). In the latter case, the number of hex digits between { } is - // arbitrary. \ and u have already been read. - if (current() == '{' && is_unicode()) { - int start = position(); - Advance(); - if (ParseUnlimitedLengthHexNumber(Utf::kMaxCodePoint, value)) { - if (current() == '}') { - Advance(); - return true; - } - } - Reset(start); - return false; - } - // \u but no {, or \u{...} escapes not allowed. - bool result = ParseHexEscape(4, value); - if (result && is_unicode() && Utf16::IsLeadSurrogate(*value) && - current() == '\\') { - // Attempt to read trail surrogate. - int start = position(); - if (Next() == 'u') { - Advance(2); - uint32_t trail; - if (ParseHexEscape(4, &trail) && Utf16::IsTrailSurrogate(trail)) { - *value = Utf16::Decode(static_cast(*value), - static_cast(trail)); - return true; - } - } - Reset(start); - } - return result; -} - -namespace { - -bool IsExactPropertyAlias(const char* property_name, UProperty property) { - const char* short_name = u_getPropertyName(property, U_SHORT_PROPERTY_NAME); - if (short_name != nullptr && strcmp(property_name, short_name) == 0) { - return true; - } - for (int i = 0;; i++) { - const char* long_name = u_getPropertyName( - property, static_cast(U_LONG_PROPERTY_NAME + i)); - if (long_name == nullptr) break; - if (strcmp(property_name, long_name) == 0) return true; - } - return false; -} - -bool IsExactPropertyValueAlias(const char* property_value_name, - UProperty property, - int32_t property_value) { - const char* short_name = - u_getPropertyValueName(property, property_value, U_SHORT_PROPERTY_NAME); - if (short_name != nullptr && strcmp(property_value_name, short_name) == 0) { - return true; - } - for (int i = 0;; i++) { - const char* long_name = u_getPropertyValueName( - property, property_value, - static_cast(U_LONG_PROPERTY_NAME + i)); - if (long_name == nullptr) break; - if (strcmp(property_value_name, long_name) == 0) return true; - } - return false; -} - -bool LookupPropertyValueName(UProperty property, - const char* property_value_name, - bool negate, - ZoneGrowableArray* result) { - UProperty property_for_lookup = property; - if (property_for_lookup == UCHAR_SCRIPT_EXTENSIONS) { - // For the property Script_Extensions, we have to do the property value - // name lookup as if the property is Script. - property_for_lookup = UCHAR_SCRIPT; - } - int32_t property_value = - u_getPropertyValueEnum(property_for_lookup, property_value_name); - if (property_value == UCHAR_INVALID_CODE) return false; - - // We require the property name to match exactly to one of the property value - // aliases. However, u_getPropertyValueEnum uses loose matching. - if (!IsExactPropertyValueAlias(property_value_name, property_for_lookup, - property_value)) { - return false; - } - - UErrorCode ec = U_ZERO_ERROR; - icu::UnicodeSet set; - set.applyIntPropertyValue(property, property_value, ec); - bool success = ec == U_ZERO_ERROR && (set.isEmpty() == 0); - - if (success) { - set.removeAllStrings(); - if (negate) set.complement(); - for (int i = 0; i < set.getRangeCount(); i++) { - result->Add( - CharacterRange::Range(set.getRangeStart(i), set.getRangeEnd(i))); - } - } - return success; -} - -template -inline bool NameEquals(const char* name, const char (&literal)[N]) { - return strncmp(name, literal, N + 1) == 0; -} - -bool LookupSpecialPropertyValueName(const char* name, - ZoneGrowableArray* result, - bool negate) { - if (NameEquals(name, "Any")) { - if (negate) { - // Leave the list of character ranges empty, since the negation of 'Any' - // is the empty set. - } else { - result->Add(CharacterRange::Everything()); - } - } else if (NameEquals(name, "ASCII")) { - result->Add(negate ? CharacterRange::Range(0x80, Utf::kMaxCodePoint) - : CharacterRange::Range(0x0, 0x7F)); - } else if (NameEquals(name, "Assigned")) { - return LookupPropertyValueName(UCHAR_GENERAL_CATEGORY, "Unassigned", - !negate, result); - } else { - return false; - } - return true; -} - -// Explicitly list supported binary properties. The spec forbids supporting -// properties outside of this set to ensure interoperability. -bool IsSupportedBinaryProperty(UProperty property) { - switch (property) { - case UCHAR_ALPHABETIC: - // 'Any' is not supported by ICU. See LookupSpecialPropertyValueName. - // 'ASCII' is not supported by ICU. See LookupSpecialPropertyValueName. - case UCHAR_ASCII_HEX_DIGIT: - // 'Assigned' is not supported by ICU. See LookupSpecialPropertyValueName. - case UCHAR_BIDI_CONTROL: - case UCHAR_BIDI_MIRRORED: - case UCHAR_CASE_IGNORABLE: - case UCHAR_CASED: - case UCHAR_CHANGES_WHEN_CASEFOLDED: - case UCHAR_CHANGES_WHEN_CASEMAPPED: - case UCHAR_CHANGES_WHEN_LOWERCASED: - case UCHAR_CHANGES_WHEN_NFKC_CASEFOLDED: - case UCHAR_CHANGES_WHEN_TITLECASED: - case UCHAR_CHANGES_WHEN_UPPERCASED: - case UCHAR_DASH: - case UCHAR_DEFAULT_IGNORABLE_CODE_POINT: - case UCHAR_DEPRECATED: - case UCHAR_DIACRITIC: - case UCHAR_EMOJI: - case UCHAR_EMOJI_COMPONENT: - case UCHAR_EMOJI_MODIFIER_BASE: - case UCHAR_EMOJI_MODIFIER: - case UCHAR_EMOJI_PRESENTATION: - case UCHAR_EXTENDED_PICTOGRAPHIC: - case UCHAR_EXTENDER: - case UCHAR_GRAPHEME_BASE: - case UCHAR_GRAPHEME_EXTEND: - case UCHAR_HEX_DIGIT: - case UCHAR_ID_CONTINUE: - case UCHAR_ID_START: - case UCHAR_IDEOGRAPHIC: - case UCHAR_IDS_BINARY_OPERATOR: - case UCHAR_IDS_TRINARY_OPERATOR: - case UCHAR_JOIN_CONTROL: - case UCHAR_LOGICAL_ORDER_EXCEPTION: - case UCHAR_LOWERCASE: - case UCHAR_MATH: - case UCHAR_NONCHARACTER_CODE_POINT: - case UCHAR_PATTERN_SYNTAX: - case UCHAR_PATTERN_WHITE_SPACE: - case UCHAR_QUOTATION_MARK: - case UCHAR_RADICAL: - case UCHAR_REGIONAL_INDICATOR: - case UCHAR_S_TERM: - case UCHAR_SOFT_DOTTED: - case UCHAR_TERMINAL_PUNCTUATION: - case UCHAR_UNIFIED_IDEOGRAPH: - case UCHAR_UPPERCASE: - case UCHAR_VARIATION_SELECTOR: - case UCHAR_WHITE_SPACE: - case UCHAR_XID_CONTINUE: - case UCHAR_XID_START: - return true; - default: - break; - } - return false; -} - -bool IsUnicodePropertyValueCharacter(char c) { - // https://tc39.github.io/proposal-regexp-unicode-property-escapes/ - // - // Note that using this to validate each parsed char is quite conservative. - // A possible alternative solution would be to only ensure the parsed - // property name/value candidate string does not contain '\0' characters and - // let ICU lookups trigger the final failure. - if (Utils::IsAlphaNumeric(c)) return true; - return (c == '_'); -} - -} // anonymous namespace - -bool RegExpParser::ParsePropertyClassName(ZoneGrowableArray* name_1, - ZoneGrowableArray* name_2) { - ASSERT(name_1->is_empty()); - ASSERT(name_2->is_empty()); - // Parse the property class as follows: - // - In \p{name}, 'name' is interpreted - // - either as a general category property value name. - // - or as a binary property name. - // - In \p{name=value}, 'name' is interpreted as an enumerated property name, - // and 'value' is interpreted as one of the available property value names. - // - Aliases in PropertyAlias.txt and PropertyValueAlias.txt can be used. - // - Loose matching is not applied. - if (current() == '{') { - // Parse \p{[PropertyName=]PropertyNameValue} - for (Advance(); current() != '}' && current() != '='; Advance()) { - if (!IsUnicodePropertyValueCharacter(current())) return false; - if (!has_next()) return false; - name_1->Add(static_cast(current())); - } - if (current() == '=') { - for (Advance(); current() != '}'; Advance()) { - if (!IsUnicodePropertyValueCharacter(current())) return false; - if (!has_next()) return false; - name_2->Add(static_cast(current())); - } - name_2->Add(0); // null-terminate string. - } - } else { - return false; - } - Advance(); - name_1->Add(0); // null-terminate string. - - ASSERT(static_cast(name_1->length() - 1) == strlen(name_1->data())); - ASSERT(name_2->is_empty() || - static_cast(name_2->length() - 1) == strlen(name_2->data())); - return true; -} - -bool RegExpParser::AddPropertyClassRange( - ZoneGrowableArray* add_to, - bool negate, - ZoneGrowableArray* name_1, - ZoneGrowableArray* name_2) { - ASSERT(name_1->At(name_1->length() - 1) == '\0'); - ASSERT(name_2->is_empty() || name_2->At(name_2->length() - 1) == '\0'); - if (name_2->is_empty()) { - // First attempt to interpret as general category property value name. - const char* name = name_1->data(); - if (LookupPropertyValueName(UCHAR_GENERAL_CATEGORY_MASK, name, negate, - add_to)) { - return true; - } - // Interpret "Any", "ASCII", and "Assigned". - if (LookupSpecialPropertyValueName(name, add_to, negate)) { - return true; - } - // Then attempt to interpret as binary property name with value name 'Y'. - UProperty property = u_getPropertyEnum(name); - if (!IsSupportedBinaryProperty(property)) return false; - if (!IsExactPropertyAlias(name, property)) return false; - return LookupPropertyValueName(property, negate ? "N" : "Y", false, add_to); - } else { - // Both property name and value name are specified. Attempt to interpret - // the property name as enumerated property. - const char* property_name = name_1->data(); - const char* value_name = name_2->data(); - UProperty property = u_getPropertyEnum(property_name); - if (!IsExactPropertyAlias(property_name, property)) return false; - if (property == UCHAR_GENERAL_CATEGORY) { - // We want to allow aggregate value names such as "Letter". - property = UCHAR_GENERAL_CATEGORY_MASK; - } else if (property != UCHAR_SCRIPT && - property != UCHAR_SCRIPT_EXTENSIONS) { - return false; - } - return LookupPropertyValueName(property, value_name, negate, add_to); - } -} - -bool RegExpParser::ParseUnlimitedLengthHexNumber(uint32_t max_value, - uint32_t* value) { - uint32_t x = 0; - int d = HexValue(current()); - if (d < 0) { - return false; - } - while (d >= 0) { - x = x * 16 + d; - if (x > max_value) { - return false; - } - Advance(); - d = HexValue(current()); - } - *value = x; - return true; -} - -uint32_t RegExpParser::ParseClassCharacterEscape() { - ASSERT(current() == '\\'); - DEBUG_ASSERT(has_next() && !IsSpecialClassEscape(Next())); - Advance(); - switch (current()) { - case 'b': - Advance(); - return '\b'; - // ControlEscape :: one of - // f n r t v - case 'f': - Advance(); - return '\f'; - case 'n': - Advance(); - return '\n'; - case 'r': - Advance(); - return '\r'; - case 't': - Advance(); - return '\t'; - case 'v': - Advance(); - return '\v'; - case 'c': { - uint32_t controlLetter = Next(); - uint32_t letter = controlLetter & ~('A' ^ 'a'); - // For compatibility with JSC, inside a character class - // we also accept digits and underscore as control characters. - if (letter >= 'A' && letter <= 'Z') { - Advance(2); - // Control letters mapped to ASCII control characters in the range - // 0x00-0x1f. - return controlLetter & 0x1f; - } - if (is_unicode()) { - // With /u, \c# or \c_ are invalid. - ReportError("Invalid class escape"); - UNREACHABLE(); - } - if (Utils::IsDecimalDigit(controlLetter) || controlLetter == '_') { - Advance(2); - return controlLetter & 0x1f; - } - // We match JSC in reading the backslash as a literal - // character instead of as starting an escape. - return '\\'; - } - case '0': - // With /u, \0 is interpreted as NUL if not followed by another digit. - if (is_unicode() && !(Next() >= '0' && Next() <= '9')) { - Advance(); - return 0; - } - FALL_THROUGH; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - // For compatibility, we interpret a decimal escape that isn't - // a back reference (and therefore either \0 or not valid according - // to the specification) as a 1..3 digit octal character code. - if (is_unicode()) { - // With \u, decimal escape is not interpreted as octal character code. - ReportError("Invalid class escape"); - UNREACHABLE(); - } - return ParseOctalLiteral(); - case 'x': { - Advance(); - uint32_t value; - if (ParseHexEscape(2, &value)) { - return value; - } - if (is_unicode()) { - // With \u, invalid escapes are not treated as identity escapes. - ReportError("Invalid escape"); - UNREACHABLE(); - } - // If \x is not followed by a two-digit hexadecimal, treat it - // as an identity escape. - return 'x'; - } - case 'u': { - Advance(); - uint32_t value; - if (ParseUnicodeEscape(&value)) { - return value; - } - if (is_unicode()) { - // With \u, invalid escapes are not treated as identity escapes. - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - // If \u is not followed by a four-digit hexadecimal, treat it - // as an identity escape. - return 'u'; - } - default: { - // Extended identity escape. We accept any character that hasn't - // been matched by a more specific case, not just the subset required - // by the ECMAScript specification. - uint32_t result = current(); - if (!is_unicode() || IsSyntaxCharacterOrSlash(result) || result == '-') { - Advance(); - return result; - } - ReportError(kUnicodeIdentity); - UNREACHABLE(); - } - } - return 0; -} - -bool RegExpParser::ParseClassEscape(ZoneGrowableArray* ranges, - bool add_unicode_case_equivalents, - uint32_t* char_out) { - uint32_t first = current(); - if (first == '\\') { - switch (Next()) { - case 'w': - case 'W': - case 'd': - case 'D': - case 's': - case 'S': { - CharacterRange::AddClassEscape(static_cast(Next()), ranges, - add_unicode_case_equivalents); - Advance(2); - return true; - } - case 'p': - case 'P': { - if (!is_unicode()) break; - bool negate = Next() == 'P'; - Advance(2); - auto name_1 = new (Z) ZoneGrowableArray(); - auto name_2 = new (Z) ZoneGrowableArray(); - if (!ParsePropertyClassName(name_1, name_2) || - !AddPropertyClassRange(ranges, negate, name_1, name_2)) { - ReportError("Invalid property name in character class"); - UNREACHABLE(); - } - return true; - } - case kEndMarker: - ReportError("\\ at end of pattern"); - UNREACHABLE(); - default: - break; - } - *char_out = ParseClassCharacterEscape(); - return false; - } - Advance(); - *char_out = first; - return false; -} - -RegExpTree* RegExpParser::ParseCharacterClass(const RegExpBuilder* builder) { - static const char* kUnterminated = "Unterminated character class"; - static const char* kRangeInvalid = "Invalid character class"; - static const char* kRangeOutOfOrder = "Range out of order in character class"; - - ASSERT(current() == '['); - Advance(); - bool is_negated = false; - if (current() == '^') { - is_negated = true; - Advance(); - } - ZoneGrowableArray* ranges = - new (Z) ZoneGrowableArray(2); - bool add_unicode_case_equivalents = is_unicode() && builder->ignore_case(); - while (has_more() && current() != ']') { - uint32_t char_1 = 0; - bool is_class_1 = - ParseClassEscape(ranges, add_unicode_case_equivalents, &char_1); - if (current() == '-') { - Advance(); - if (current() == kEndMarker) { - // If we reach the end we break out of the loop and let the - // following code report an error. - break; - } else if (current() == ']') { - if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); - ranges->Add(CharacterRange::Singleton('-')); - break; - } - uint32_t char_2 = 0; - bool is_class_2 = - ParseClassEscape(ranges, add_unicode_case_equivalents, &char_2); - if (is_class_1 || is_class_2) { - // Either end is an escaped character class. Treat the '-' verbatim. - if (is_unicode()) { - // ES2015 21.2.2.15.1 step 1. - ReportError(kRangeInvalid); - UNREACHABLE(); - } - if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); - ranges->Add(CharacterRange::Singleton('-')); - if (!is_class_2) ranges->Add(CharacterRange::Singleton(char_2)); - continue; - } - if (char_1 > char_2) { - ReportError(kRangeOutOfOrder); - UNREACHABLE(); - } - ranges->Add(CharacterRange::Range(char_1, char_2)); - } else { - if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); - } - } - if (!has_more()) { - ReportError(kUnterminated); - UNREACHABLE(); - } - Advance(); - RegExpCharacterClass::CharacterClassFlags character_class_flags = - RegExpCharacterClass::DefaultFlags(); - if (is_negated) character_class_flags |= RegExpCharacterClass::NEGATED; - return new (Z) - RegExpCharacterClass(ranges, builder->flags(), character_class_flags); -} - -// ---------------------------------------------------------------------------- -// The Parser interface. - -void RegExpParser::ParseRegExp(const String& input, - RegExpFlags flags, - RegExpCompileData* result) { - ASSERT(result != nullptr); - RegExpParser parser(input, &result->error, flags); - // Throws an exception if 'input' is not valid. - RegExpTree* tree = parser.ParsePattern(); - ASSERT(tree != nullptr); - ASSERT(result->error.IsNull()); - result->tree = tree; - intptr_t capture_count = parser.captures_started(); - result->simple = tree->IsAtom() && parser.simple() && capture_count == 0; - result->contains_anchor = parser.contains_anchor(); - result->capture_name_map = parser.CreateCaptureNameMap(); - result->capture_count = capture_count; -} - -} // namespace dart diff --git a/runtime/vm/regexp/regexp_parser.h b/runtime/vm/regexp/regexp_parser.h deleted file mode 100644 index c159695ea18..00000000000 --- a/runtime/vm/regexp/regexp_parser.h +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#ifndef RUNTIME_VM_REGEXP_REGEXP_PARSER_H_ -#define RUNTIME_VM_REGEXP_REGEXP_PARSER_H_ - -#include "vm/allocation.h" -#include "vm/growable_array.h" -#include "vm/regexp/regexp_ast.h" - -namespace dart { - -// Accumulates RegExp atoms and assertions into lists of terms and alternatives. -class RegExpBuilder : public ZoneObject { - public: - explicit RegExpBuilder(RegExpFlags flags); - - void AddCharacter(uint16_t character); - void AddUnicodeCharacter(uint32_t character); - void AddEscapedUnicodeCharacter(uint32_t character); - // "Adds" an empty expression. Does nothing except consume a - // following quantifier - void AddEmpty(); - void AddCharacterClass(RegExpCharacterClass* cc); - void AddCharacterClassForDesugaring(uint32_t c); - void AddAtom(RegExpTree* tree); - void AddTerm(RegExpTree* tree); - void AddAssertion(RegExpTree* tree); - void NewAlternative(); // '|' - // Attempt to add a quantifier to the last atom added. The return value - // denotes whether the attempt succeeded, since some atoms like lookbehind - // cannot be quantified. - bool AddQuantifierToAtom(intptr_t min, - intptr_t max, - RegExpQuantifier::QuantifierType type); - RegExpTree* ToRegExp(); - RegExpFlags flags() const { return flags_; } - bool ignore_case() const { return flags_.IgnoreCase(); } - bool is_multi_line() const { return flags_.IsMultiLine(); } - bool is_dot_all() const { return flags_.IsDotAll(); } - - private: - static constexpr uint16_t kNoPendingSurrogate = 0; - void AddLeadSurrogate(uint16_t lead_surrogate); - void AddTrailSurrogate(uint16_t trail_surrogate); - void FlushPendingSurrogate(); - void FlushCharacters(); - void FlushText(); - void FlushTerms(); - bool NeedsDesugaringForUnicode(RegExpCharacterClass* cc); - bool NeedsDesugaringForIgnoreCase(uint32_t c); - - Zone* zone() const { return zone_; } - bool is_unicode() const { return flags_.IsUnicode(); } - - Zone* zone_; - bool pending_empty_; - RegExpFlags flags_; - ZoneGrowableArray* characters_; - uint16_t pending_surrogate_; - GrowableArray terms_; - GrowableArray text_; - GrowableArray alternatives_; -#ifdef DEBUG - enum { ADD_NONE, ADD_CHAR, ADD_TERM, ADD_ASSERT, ADD_ATOM } last_added_; -#define LAST(x) last_added_ = x; -#else -#define LAST(x) -#endif -}; - -using RegExpCaptureName = ZoneGrowableArray; - -class RegExpParser : public ValueObject { - public: - RegExpParser(const String& in, String* error, RegExpFlags regexp_flags); - - static void ParseRegExp(const String& input, - RegExpFlags regexp_flags, - RegExpCompileData* result); - - RegExpTree* ParsePattern(); - RegExpTree* ParseDisjunction(); - RegExpTree* ParseGroup(); - - // Parses a {...,...} quantifier and stores the range in the given - // out parameters. - bool ParseIntervalQuantifier(intptr_t* min_out, intptr_t* max_out); - - // Parses and returns a single escaped character. The character - // must not be 'b' or 'B' since they are usually handle specially. - uint32_t ParseClassCharacterEscape(); - - // Checks whether the following is a length-digit hexadecimal number, - // and sets the value if it is. - bool ParseHexEscape(intptr_t length, uint32_t* value); - bool ParseUnicodeEscape(uint32_t* value); - bool ParseUnlimitedLengthHexNumber(uint32_t max_value, uint32_t* value); - - // Parses either {UNICODE_PROPERTY_NAME=UNICODE_PROPERTY_VALUE} or - // the shorthand {UNICODE_PROPERTY_NAME_OR_VALUE} and stores the - // result in the given out parameters. If the shorthand is used, - // nothing will be added to name_2. - bool ParsePropertyClassName(ZoneGrowableArray* name_1, - ZoneGrowableArray* name_2); - // Adds the specified unicode property to the provided character range. - bool AddPropertyClassRange(ZoneGrowableArray* add_to, - bool negate, - ZoneGrowableArray* name_1, - ZoneGrowableArray* name_2); - // Returns a regexp node that corresponds to one of these unicode - // property sequences: "Any", "ASCII", "Assigned". - RegExpTree* GetPropertySequence(ZoneGrowableArray* name_1); - RegExpTree* ParseCharacterClass(const RegExpBuilder* builder); - - uint32_t ParseOctalLiteral(); - - // Tries to parse the input as a back reference. If successful it - // stores the result in the output parameter and returns true. If - // it fails it will push back the characters read so the same characters - // can be reparsed. - bool ParseBackReferenceIndex(intptr_t* index_out); - - // Attempts to parse a possible escape within a character class. - bool ParseClassEscape(ZoneGrowableArray* ranges, - bool add_unicode_case_equivalents, - uint32_t* char_out); - void ReportError(const char* message); - void Advance(); - void Advance(intptr_t dist); - void Reset(intptr_t pos); - - // Reports whether the pattern might be used as a literal search string. - // Only use if the result of the parse is a single atom node. - bool simple(); - bool contains_anchor() { return contains_anchor_; } - void set_contains_anchor() { contains_anchor_ = true; } - intptr_t captures_started() { return captures_started_; } - intptr_t position() { return next_pos_ - 1; } - bool is_unicode() const { return top_level_flags_.IsUnicode(); } - - static bool IsSyntaxCharacterOrSlash(uint32_t c); - - static constexpr intptr_t kMaxCaptures = 1 << 16; - static constexpr uint32_t kEndMarker = (1 << 21); - - private: - enum SubexpressionType { - INITIAL, - CAPTURE, // All positive values represent captures. - POSITIVE_LOOKAROUND, - NEGATIVE_LOOKAROUND, - GROUPING - }; - - class RegExpParserState : public ZoneObject { - public: - RegExpParserState(RegExpParserState* previous_state, - SubexpressionType group_type, - RegExpLookaround::Type lookaround_type, - intptr_t disjunction_capture_index, - const RegExpCaptureName* capture_name, - RegExpFlags flags, - Zone* zone) - : previous_state_(previous_state), - builder_(new (zone) RegExpBuilder(flags)), - group_type_(group_type), - lookaround_type_(lookaround_type), - disjunction_capture_index_(disjunction_capture_index), - capture_name_(capture_name) {} - // Parser state of containing expression, if any. - RegExpParserState* previous_state() { return previous_state_; } - bool IsSubexpression() { return previous_state_ != nullptr; } - // RegExpBuilder building this regexp's AST. - RegExpBuilder* builder() { return builder_; } - // Type of regexp being parsed (parenthesized group or entire regexp). - SubexpressionType group_type() { return group_type_; } - // Lookahead or lookbehind. - RegExpLookaround::Type lookaround_type() { return lookaround_type_; } - // Index in captures array of first capture in this sub-expression, if any. - // Also the capture index of this sub-expression itself, if group_type - // is CAPTURE. - intptr_t capture_index() { return disjunction_capture_index_; } - const RegExpCaptureName* capture_name() const { return capture_name_; } - - bool IsNamedCapture() const { return capture_name_ != nullptr; } - - // Check whether the parser is inside a capture group with the given index. - bool IsInsideCaptureGroup(intptr_t index); - // Check whether the parser is inside a capture group with the given name. - bool IsInsideCaptureGroup(const RegExpCaptureName* name); - - private: - // Linked list implementation of stack of states. - RegExpParserState* previous_state_; - // Builder for the stored disjunction. - RegExpBuilder* builder_; - // Stored disjunction type (capture, look-ahead or grouping), if any. - SubexpressionType group_type_; - // Stored read direction. - const RegExpLookaround::Type lookaround_type_; - // Stored disjunction's capture index (if any). - intptr_t disjunction_capture_index_; - // Stored capture name (if any). - const RegExpCaptureName* const capture_name_; - }; - - // Return the 1-indexed RegExpCapture object, allocate if necessary. - RegExpCapture* GetCapture(intptr_t index); - - // Creates a new named capture at the specified index. Must be called exactly - // once for each named capture. Fails if a capture with the same name is - // encountered. - void CreateNamedCaptureAtIndex(const RegExpCaptureName* name, intptr_t index); - - // Parses the name of a capture group (?pattern). The name must adhere - // to IdentifierName in the ECMAScript standard. - const RegExpCaptureName* ParseCaptureGroupName(); - - bool ParseNamedBackReference(RegExpBuilder* builder, - RegExpParserState* state); - RegExpParserState* ParseOpenParenthesis(RegExpParserState* state); - intptr_t GetNamedCaptureIndex(const RegExpCaptureName* name); - - // After the initial parsing pass, patch corresponding RegExpCapture objects - // into all RegExpBackReferences. This is done after initial parsing in order - // to avoid complicating cases in which references come before the capture. - void PatchNamedBackReferences(); - - ArrayPtr CreateCaptureNameMap(); - - // Returns true iff the pattern contains named captures. May call - // ScanForCaptures to look ahead at the remaining pattern. - bool HasNamedCaptures(); - - Zone* zone() { return zone_; } - - uint32_t current() { return current_; } - bool has_more() { return has_more_; } - bool has_next() { return next_pos_ < in().Length(); } - uint32_t Next(); - uint32_t ReadNext(bool update_position); - const String& in() { return in_; } - void ScanForCaptures(); - - Zone* zone_; - ZoneGrowableArray* captures_; - ZoneGrowableArray* named_captures_; - ZoneGrowableArray* named_back_references_; - const String& in_; - uint32_t current_; - intptr_t next_pos_; - intptr_t captures_started_; - // The capture count is only valid after we have scanned for captures. - intptr_t capture_count_; - bool has_more_; - RegExpFlags top_level_flags_; - bool simple_; - bool contains_anchor_; - bool is_scanned_for_captures_; - bool has_named_captures_; -}; - -} // namespace dart - -#endif // RUNTIME_VM_REGEXP_REGEXP_PARSER_H_ diff --git a/runtime/vm/regexp/regexp_sources.gni b/runtime/vm/regexp/regexp_sources.gni index 04e640ce992..2b89233d6b7 100644 --- a/runtime/vm/regexp/regexp_sources.gni +++ b/runtime/vm/regexp/regexp_sources.gni @@ -3,25 +3,45 @@ # BSD-style license that can be found in the LICENSE file. regexp_sources = [ + "base.h", + "char-predicates-inl.h", + "char-predicates.cc", + "char-predicates.h", + "flags.h", + "label.h", + "memcopy.h", + "regexp-ast.cc", + "regexp-ast.h", + "regexp-bytecode-generator-inl.h", + "regexp-bytecode-generator.cc", + "regexp-bytecode-generator.h", + "regexp-bytecodes-inl.h", + "regexp-bytecodes.h", + "regexp-compiler-tonode.cc", + "regexp-compiler.cc", + "regexp-compiler.h", + "regexp-error.cc", + "regexp-error.h", + "regexp-flags.h", + "regexp-interpreter.cc", + "regexp-interpreter.h", + "regexp-macro-assembler.cc", + "regexp-macro-assembler.h", + "regexp-nodes.h", + "regexp-parser.cc", + "regexp-parser.h", "regexp.cc", "regexp.h", - "regexp_assembler.cc", - "regexp_assembler.h", - "regexp_assembler_bytecode.cc", - "regexp_assembler_bytecode.h", - "regexp_assembler_bytecode_inl.h", - "regexp_assembler_ir.cc", - "regexp_assembler_ir.h", - "regexp_ast.cc", - "regexp_ast.h", - "regexp_bytecodes.h", - "regexp_interpreter.cc", - "regexp_interpreter.h", - "regexp_parser.cc", - "regexp_parser.h", + "small-vector.h", + "special-case.cc", + "special-case.h", "unibrow-inl.h", "unibrow.cc", "unibrow.h", + "vector.h", + "zone-containers.h", + "zone-list-inl.h", + "zone-list.h", ] regexp_sources_tests = [ "regexp_test.cc" ] diff --git a/runtime/vm/regexp/regexp_test.cc b/runtime/vm/regexp/regexp_test.cc index 3f92ccdf4b2..4fc5b1cd83e 100644 --- a/runtime/vm/regexp/regexp_test.cc +++ b/runtime/vm/regexp/regexp_test.cc @@ -7,19 +7,15 @@ #include "vm/isolate.h" #include "vm/object.h" #include "vm/regexp/regexp.h" -#include "vm/regexp/regexp_assembler_ir.h" +#include "vm/symbols.h" #include "vm/unit_test.h" namespace dart { -static ArrayPtr Match(const String& pat, const String& str) { - Thread* thread = Thread::Current(); - Zone* zone = thread->zone(); - const RegExp& regexp = - RegExp::Handle(RegExpEngine::CreateRegExp(thread, pat, RegExpFlags())); - const Smi& idx = Object::smi_zero(); - return IRRegExpMacroAssembler::Execute(regexp, str, idx, /*sticky=*/false, - zone); +static ObjectPtr Match(const String& pattern, const String& subject) { + const RegExp& regexp = RegExp::Handle(RegExp::New(pattern, RegExpFlags())); + return RegExpStatics::Interpret(Thread::Current(), regexp, subject, 0, + /*sticky=*/false); } ISOLATE_UNIT_TEST_CASE(RegExp_OneByteString) { @@ -30,18 +26,11 @@ ISOLATE_UNIT_TEST_CASE(RegExp_OneByteString) { const String& pat = String::Handle(Symbols::New(thread, String::Handle(String::New("bc")))); - const Array& res = Array::Handle(Match(pat, str)); + TypedData& res = TypedData::Handle(); + res ^= Match(pat, str); EXPECT_EQ(2, res.Length()); - - const Object& res_1 = Object::Handle(res.At(0)); - const Object& res_2 = Object::Handle(res.At(1)); - EXPECT(res_1.IsSmi()); - EXPECT(res_2.IsSmi()); - - const Smi& smi_1 = Smi::Cast(res_1); - const Smi& smi_2 = Smi::Cast(res_2); - EXPECT_EQ(1, smi_1.Value()); - EXPECT_EQ(3, smi_2.Value()); + EXPECT_EQ(1, res.GetInt32(0 * sizeof(int32_t))); + EXPECT_EQ(3, res.GetInt32(1 * sizeof(int32_t))); } ISOLATE_UNIT_TEST_CASE(RegExp_TwoByteString) { @@ -52,18 +41,11 @@ ISOLATE_UNIT_TEST_CASE(RegExp_TwoByteString) { const String& pat = String::Handle(Symbols::New(thread, String::Handle(String::New("bc")))); - const Array& res = Array::Handle(Match(pat, str)); + TypedData& res = TypedData::Handle(); + res ^= Match(pat, str); EXPECT_EQ(2, res.Length()); - - const Object& res_1 = Object::Handle(res.At(0)); - const Object& res_2 = Object::Handle(res.At(1)); - EXPECT(res_1.IsSmi()); - EXPECT(res_2.IsSmi()); - - const Smi& smi_1 = Smi::Cast(res_1); - const Smi& smi_2 = Smi::Cast(res_2); - EXPECT_EQ(1, smi_1.Value()); - EXPECT_EQ(3, smi_2.Value()); + EXPECT_EQ(1, res.GetInt32(0 * sizeof(int32_t))); + EXPECT_EQ(3, res.GetInt32(1 * sizeof(int32_t))); } } // namespace dart diff --git a/runtime/vm/regexp/small-vector.h b/runtime/vm/regexp/small-vector.h new file mode 100644 index 00000000000..fd52f1798c3 --- /dev/null +++ b/runtime/vm/regexp/small-vector.h @@ -0,0 +1,387 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_BASE_SMALL_VECTOR_H_ +#define V8_BASE_SMALL_VECTOR_H_ + +#include +#include +#include +#include + +#include "platform/utils.h" +#include "vm/regexp/memcopy.h" +#include "vm/regexp/vector.h" + +#define V8_NO_UNIQUE_ADDRESS + +namespace base { + +// Minimal SmallVector implementation. Uses inline storage first, switches to +// dynamic storage when it overflows. +template > +class SmallVector { + // TODO(mliedtke): Remove kHasTrivialElement and replace usages with the + // proper conditions. + static constexpr bool kHasTrivialElement = + std::is_trivially_copyable::value && + std::is_trivially_destructible::value; + + public: + static constexpr size_t kInlineSize = kSize; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using iterator = T*; + using const_iterator = const T*; + using difference_type = std::ptrdiff_t; + using size_type = std::size_t; + + SmallVector() = default; + explicit SmallVector(const Allocator& allocator) : allocator_(allocator) {} + // Constructs a SmallVector with `size` elements. These elements will be + // default-initialized(!), differently to e.g. `std::vector`. If + // value-initialization is desired, use the constructor overload with an + // explicit `initial_value` instead. + explicit V8_INLINE SmallVector(size_t size, + const Allocator& allocator = Allocator()) + requires std::default_initializable + : allocator_(allocator) { + resize(size); + } + explicit V8_INLINE SmallVector(size_t size, + const T& initial_value, + const Allocator& allocator = Allocator()) + : allocator_(allocator) { + resize(size, initial_value); + } + SmallVector(const SmallVector& other) V8_NOEXCEPT + : allocator_(other.allocator_) { + *this = other; + } + SmallVector(const SmallVector& other, const Allocator& allocator) V8_NOEXCEPT + : allocator_(allocator) { + *this = other; + } + SmallVector(SmallVector&& other) V8_NOEXCEPT + : allocator_(std::move(other.allocator_)) { + *this = std::move(other); + } + SmallVector(SmallVector&& other, const Allocator& allocator) V8_NOEXCEPT + : allocator_(allocator) { + *this = std::move(other); + } + V8_INLINE SmallVector(std::initializer_list init, + const Allocator& allocator = Allocator()) + : allocator_(allocator) { + if (init.size() > capacity()) Grow(init.size()); + DCHECK_GE(capacity(), init.size()); // Sanity check. + std::uninitialized_move(init.begin(), init.end(), begin_); + end_ = begin_ + init.size(); + } + explicit V8_INLINE SmallVector(base::Vector init, + const Allocator& allocator = Allocator()) + : allocator_(allocator) { + if (init.size() > capacity()) Grow(init.size()); + DCHECK_GE(capacity(), init.size()); // Sanity check. + std::uninitialized_copy(init.begin(), init.end(), begin_); + end_ = begin_ + init.size(); + } + + ~SmallVector() { FreeStorage(); } + + SmallVector& operator=(const SmallVector& other) V8_NOEXCEPT { + if (this == &other) return *this; + size_t other_size = other.size(); + if (capacity() < other_size) { + // Create large-enough heap-allocated storage. + FreeStorage(); + begin_ = AllocateDynamicStorage(other_size); + end_of_storage_ = begin_ + other_size; + std::uninitialized_copy(other.begin_, other.end_, begin_); + } else if constexpr (kHasTrivialElement) { + std::copy(other.begin_, other.end_, begin_); + } else { + ptrdiff_t to_copy = + std::min(static_cast(other_size), end_ - begin_); + std::copy(other.begin_, other.begin_ + to_copy, begin_); + if (other.begin_ + to_copy < other.end_) { + std::uninitialized_copy(other.begin_ + to_copy, other.end_, + begin_ + to_copy); + } else { + std::destroy_n(begin_ + to_copy, size() - to_copy); + } + } + end_ = begin_ + other_size; + return *this; + } + + SmallVector& operator=(SmallVector&& other) V8_NOEXCEPT { + if (this == &other) return *this; + if (other.is_big()) { + FreeStorage(); + begin_ = other.begin_; + end_ = other.end_; + end_of_storage_ = other.end_of_storage_; + } else { + DCHECK_GE(capacity(), other.size()); // Sanity check. + size_t other_size = other.size(); + if constexpr (kHasTrivialElement) { + // Ranges cannot overlap and we can just emit a trivial memcpy. + base::MemCopy(begin_, other.begin_, other_size * sizeof(T)); + } else { + ptrdiff_t to_move = + std::min(static_cast(other_size), end_ - begin_); + std::move(other.begin_, other.begin_ + to_move, begin_); + if (other.begin_ + to_move < other.end_) { + std::uninitialized_move(other.begin_ + to_move, other.end_, + begin_ + to_move); + } else { + std::destroy_n(begin_ + to_move, size() - to_move); + } + } + end_ = begin_ + other_size; + } + other.reset_to_inline_storage(); + return *this; + } + + T* data() { return begin_; } + const T* data() const { return begin_; } + + T* begin() { return begin_; } + const T* begin() const { return begin_; } + + T* end() { return end_; } + const T* end() const { return end_; } + + auto rbegin() { return std::make_reverse_iterator(end_); } + auto rbegin() const { return std::make_reverse_iterator(end_); } + + auto rend() { return std::make_reverse_iterator(begin_); } + auto rend() const { return std::make_reverse_iterator(begin_); } + + size_t size() const { return end_ - begin_; } + bool empty() const { return end_ == begin_; } + size_t capacity() const { return end_of_storage_ - begin_; } + + T& front() { + DCHECK_NE(0, size()); + return begin_[0]; + } + const T& front() const { + DCHECK_NE(0, size()); + return begin_[0]; + } + + T& back() { + DCHECK_NE(0, size()); + return end_[-1]; + } + const T& back() const { + DCHECK_NE(0, size()); + return end_[-1]; + } + + T& at(size_t index) { + DCHECK_GT(size(), index); + return begin_[index]; + } + + T& operator[](size_t index) { + DCHECK_GT(size(), index); + return begin_[index]; + } + + const T& at(size_t index) const { + DCHECK_GT(size(), index); + return begin_[index]; + } + + const T& operator[](size_t index) const { return at(index); } + + template + void emplace_back(Args&&... args) { + if (V8_UNLIKELY(end_ == end_of_storage_)) Grow(); + void* storage = end_; + end_ += 1; + new (storage) T(std::forward(args)...); + } + + void push_back(T x) { emplace_back(std::move(x)); } + + void pop_back(size_t count = 1) { + DCHECK_GE(size(), count); + end_ -= count; + std::destroy_n(end_, count); + } + + T* insert(T* pos, const T& value) { + return insert(pos, static_cast(1), value); + } + T* insert(T* pos, size_t count, const T& value) { + DCHECK_LE(pos, end_); + size_t offset = pos - begin_; + size_t old_size = size(); + resize(old_size + count); + pos = begin_ + offset; + T* old_end = begin_ + old_size; + DCHECK_LE(old_end, end_); + std::move_backward(pos, old_end, end_); + std::fill_n(pos, count, value); + return pos; + } + template + T* insert(T* pos, It begin, It end) { + DCHECK_LE(pos, end_); + size_t offset = pos - begin_; + size_t count = std::distance(begin, end); + size_t old_size = size(); + resize(old_size + count); + pos = begin_ + offset; + T* old_end = begin_ + old_size; + DCHECK_LE(old_end, end_); + std::move_backward(pos, old_end, end_); + std::copy(begin, end, pos); + return pos; + } + + T* insert(T* pos, std::initializer_list values) { + return insert(pos, values.begin(), values.end()); + } + + template + requires requires(const Container& v) { + std::is_same_v; + } + T* insert(T* pos, const Container& values) { + return insert(pos, std::begin(values), std::end(values)); + } + + T* erase(T* erase_start, T* erase_end) { + DCHECK_GE(erase_start, begin_); + DCHECK_LE(erase_start, erase_end); + DCHECK_LE(erase_end, end_); + T* new_end = std::move(erase_end, end_, erase_start); + std::destroy(new_end, end_); + end_ = new_end; + return erase_start; + } + + T* erase(T* pos) { return erase(pos, pos + 1); } + + // Resizes the SmallVector to the provided `new_size`. If `new_size` is larger + // than the current size, the new elements will not be default-initialized, + // (meaning the objects will only be allocated, not constructed.) + // This is only valid if `T` is an implicit lifetime type. + void resize_no_init(size_t new_size) + requires kHasTrivialElement + { + if (new_size > capacity()) Grow(new_size); + end_ = begin_ + new_size; + } + + // Resizes the SmallVector to the provided `new_size`. If `new_size` is larger + // than the current size, the new elements will be default-initialized. + void resize(size_t new_size) + requires std::default_initializable + { + if (new_size > capacity()) Grow(new_size); + T* new_end = begin_ + new_size; + if (new_end > end_) { + std::uninitialized_default_construct(end_, new_end); + } else { + std::destroy(new_end, end_); + } + end_ = new_end; + } + + void resize(size_t new_size, const T& initial_value) { + if (new_size > capacity()) Grow(new_size); + T* new_end = begin_ + new_size; + if (new_end > end_) { + std::uninitialized_fill(end_, new_end, initial_value); + } else { + std::destroy(new_end, end_); + } + end_ = new_end; + } + + void reserve(size_t new_capacity) { + if (new_capacity > capacity()) Grow(new_capacity); + } + + // Clear without reverting back to inline storage. + void clear() { + std::destroy(begin_, end_); + end_ = begin_; + } + + Allocator get_allocator() const { return allocator_; } + + private: + // Grows the backing store by a factor of two. Returns the new end of the used + // storage (this reduces binary size). + V8_NOINLINE V8_PRESERVE_MOST void Grow() { Grow(0); } + + // Grows the backing store by a factor of two, and at least to {min_capacity}. + V8_NOINLINE V8_PRESERVE_MOST void Grow(size_t min_capacity) { + size_t in_use = end_ - begin_; + size_t new_capacity = dart::Utils::RoundUpToPowerOfTwo( + std::max(min_capacity, 2 * capacity())); + T* new_storage = AllocateDynamicStorage(new_capacity); + if (new_storage == nullptr) { + FATAL("OOM: base::SmallVector::Grow"); + } + std::uninitialized_move(begin_, end_, new_storage); + FreeStorage(); + begin_ = new_storage; + end_ = new_storage + in_use; + end_of_storage_ = new_storage + new_capacity; + } + + T* AllocateDynamicStorage(size_t number_of_elements) { + return allocator_.allocate(number_of_elements); + } + + V8_NOINLINE V8_PRESERVE_MOST void FreeStorage() { + std::destroy(begin_, end_); + if (is_big()) allocator_.deallocate(begin_, end_of_storage_ - begin_); + } + + // Clear and go back to inline storage. Dynamic storage is *not* freed. For + // internal use only. + void reset_to_inline_storage() { + if constexpr (!kHasTrivialElement) { + if (!is_big()) std::destroy(begin_, end_); + } + begin_ = inline_storage_begin(); + end_ = begin_; + end_of_storage_ = begin_ + kInlineSize; + } + + bool is_big() const { return begin_ != inline_storage_begin(); } + + T* inline_storage_begin() { return reinterpret_cast(inline_storage_); } + const T* inline_storage_begin() const { + return reinterpret_cast(inline_storage_); + } + + V8_NO_UNIQUE_ADDRESS Allocator allocator_; + + // Invariants: + // 1. The elements in the range between `begin_` (included) and `end_` (not + // included) will be initialized at all times. + // 2. All other elements outside the range, both in the inline storage and in + // the dynamic storage (if it exists), will be uninitialized at all times. + + T* begin_ = inline_storage_begin(); + T* end_ = begin_; + T* end_of_storage_ = begin_ + kInlineSize; + alignas(T) char inline_storage_[sizeof(T) * kInlineSize]; +}; + +} // namespace base + +#endif // V8_BASE_SMALL_VECTOR_H_ diff --git a/runtime/vm/regexp/special-case.cc b/runtime/vm/regexp/special-case.cc new file mode 100644 index 00000000000..4f4d294c66e --- /dev/null +++ b/runtime/vm/regexp/special-case.cc @@ -0,0 +1,90 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +// Automatically generated by regexp/gen-regexp-special-case.cc + +// The following functions are used to build UnicodeSets +// for special cases where the case-folding algorithm used by +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) does not match +// the algorithm defined in ECMAScript 2020 21.2.2.8.2 (Runtime +// Semantics: Canonicalize) step 3. + +#if 1 /*V8_INTL_SUPPORT*/ + +#include "vm/regexp/special-case.h" + +#include "unicode/uniset.h" +namespace dart { + +icu::UnicodeSet BuildIgnoreSet() { + icu::UnicodeSet set; + set.add(0xdf); + set.add(0x17f); + set.add(0x390); + set.add(0x3b0); + set.add(0x3f4); + set.add(0x1e9e); + set.add(0x1f80, 0x1faf); + set.add(0x1fb3); + set.add(0x1fbc); + set.add(0x1fc3); + set.add(0x1fcc); + set.add(0x1fd3); + set.add(0x1fe3); + set.add(0x1ff3); + set.add(0x1ffc); + set.add(0x2126); + set.add(0x212a, 0x212b); + set.add(0xfb05, 0xfb06); + set.freeze(); + return set; +} + +struct IgnoreSetData { + IgnoreSetData() : set(BuildIgnoreSet()) {} + const icu::UnicodeSet set; +}; + +//static +const icu::UnicodeSet& RegExpCaseFolding::IgnoreSet() { + static IgnoreSetData* set = nullptr; + if (set == nullptr) { + set = new IgnoreSetData(); + } + return set->set; +} + +icu::UnicodeSet BuildSpecialAddSet() { + icu::UnicodeSet set; + set.add(0x4b); + set.add(0x53); + set.add(0x6b); + set.add(0x73); + set.add(0xc5); + set.add(0xe5); + set.add(0x398); + set.add(0x3a9); + set.add(0x3b8); + set.add(0x3c9); + set.add(0x3d1); + set.freeze(); + return set; +} + +struct SpecialAddSetData { + SpecialAddSetData() : set(BuildSpecialAddSet()) {} + const icu::UnicodeSet set; +}; + +//static +const icu::UnicodeSet& RegExpCaseFolding::SpecialAddSet() { + static SpecialAddSetData* set = nullptr; + if (set == nullptr) { + set = new SpecialAddSetData(); + } + return set->set; +} + +} // namespace dart +#endif // V8_INTL_SUPPORT diff --git a/runtime/vm/regexp/special-case.h b/runtime/vm/regexp/special-case.h new file mode 100644 index 00000000000..ebd4e8d4767 --- /dev/null +++ b/runtime/vm/regexp/special-case.h @@ -0,0 +1,117 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_REGEXP_SPECIAL_CASE_H_ +#define V8_REGEXP_SPECIAL_CASE_H_ + +#if 1 /*V8_INTL_SUPPORT*/ +#include "platform/allocation.h" +#include "platform/globals.h" +#include "vm/regexp/base.h" + +#include "unicode/uchar.h" +#include "unicode/uniset.h" +#include "unicode/unistr.h" + +namespace dart { + +// Sets of Unicode characters that need special handling under "i" mode + +// For non-unicode ignoreCase matches (aka "i", not "iu"), ECMA 262 +// defines slightly different case-folding rules than Unicode. An +// input character should match a pattern character if the result of +// the Canonicalize algorithm is the same for both characters. +// +// Roughly speaking, for "i" regexps, Canonicalize(c) is the same as +// c.toUpperCase(), unless a) c.toUpperCase() is a multi-character +// string, or b) c is non-ASCII, and c.toUpperCase() is ASCII. See +// https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch for +// the precise definition. +// +// While compiling such regular expressions, we need to compute the +// set of characters that should match a given input character. (See +// GetCaseIndependentLetters and CharacterRange::AddCaseEquivalents.) +// For almost all characters, this can be efficiently computed using +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE). These sets represent +// the remaining special cases. +// +// For a character c, the rules are as follows: +// +// 1. If c is in neither IgnoreSet nor SpecialAddSet, then calling +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) on a UnicodeSet +// containing c will produce the set of characters that should +// match /c/i (or /[c]/i), and only those characters. +// +// 2. If c is in IgnoreSet, then the only character it should match is +// itself. However, closeOver will add additional incorrect +// matches. For example, consider SHARP S: 'ß' (U+00DF) and 'ẞ' +// (U+1E9E). Although closeOver('ß') = "ßẞ", uppercase('ß') is +// "SS". Step 3.e therefore requires that 'ß' canonicalizes to +// itself, and should not match 'ẞ'. In these cases, we can skip +// the closeOver entirely, because it will never add an equivalent +// character. +// +// 3. If c is in SpecialAddSet, then it should match at least one +// character other than itself. However, closeOver will add at +// least one additional incorrect match. For example, consider the +// letter 'k'. Closing over 'k' gives "kKK" (lowercase k, uppercase +// K, U+212A KELVIN SIGN). However, because of step 3.g, KELVIN +// SIGN should not match either of the other two characters. As a +// result, "k" and "K" are in SpecialAddSet (and KELVIN SIGN is in +// IgnoreSet). To find the correct matches for characters in +// SpecialAddSet, we closeOver the original character, but filter +// out the results that do not have the same canonical value. +// +// The contents of these sets are calculated at build time by +// src/regexp/gen-regexp-special-case.cc, which generates +// gen/src/regexp/special-case.cc. This is done by iterating over the +// result of closeOver for each BMP character, and finding sets for +// which at least one character has a different canonical value than +// another character. Characters that match no other characters in +// their equivalence class are added to IgnoreSet. Characters that +// match at least one other character are added to SpecialAddSet. + +class RegExpCaseFolding final : public AllStatic { + public: + static const icu::UnicodeSet& IgnoreSet(); + static const icu::UnicodeSet& SpecialAddSet(); + + // This implements ECMAScript 2020 21.2.2.8.2 (Runtime Semantics: + // Canonicalize) step 3, which is used to determine whether + // characters match when ignoreCase is true and unicode is false. + static UChar32 Canonicalize(UChar32 ch) { + // a. Assert: ch is a UTF-16 code unit. + CHECK_LE(ch, 0xffff); + + // b. Let s be the String value consisting of the single code unit ch. + icu::UnicodeString s(ch); + + // c. Let u be the same result produced as if by performing the algorithm + // for String.prototype.toUpperCase using s as the this value. + // d. Assert: Type(u) is String. + icu::UnicodeString& u = s.toUpper(); + + // e. If u does not consist of a single code unit, return ch. + if (u.length() != 1) { + return ch; + } + + // f. Let cu be u's single code unit element. + UChar32 cu = u.char32At(0); + + // g. If the value of ch >= 128 and the value of cu < 128, return ch. + if (ch >= 128 && cu < 128) { + return ch; + } + + // h. Return cu. + return cu; + } +}; + +} // namespace dart + +#endif // V8_INTL_SUPPORT + +#endif // V8_REGEXP_SPECIAL_CASE_H_ diff --git a/runtime/vm/regexp/unibrow-inl.h b/runtime/vm/regexp/unibrow-inl.h index a8f10379527..6243107cfaf 100644 --- a/runtime/vm/regexp/unibrow-inl.h +++ b/runtime/vm/regexp/unibrow-inl.h @@ -11,8 +11,8 @@ namespace unibrow { -template -intptr_t Mapping::get(int32_t c, int32_t n, int32_t* result) { +template +int Mapping::get(uchar c, uchar n, uchar* result) { CacheEntry entry = entries_[c & kMask]; if (entry.code_point_ == c) { if (entry.offset_ == 0) { @@ -26,10 +26,10 @@ intptr_t Mapping::get(int32_t c, int32_t n, int32_t* result) { } } -template -intptr_t Mapping::CalculateValue(int32_t c, int32_t n, int32_t* result) { +template +int Mapping::CalculateValue(uchar c, uchar n, uchar* result) { bool allow_caching = true; - intptr_t length = T::Convert(c, n, result, &allow_caching); + int length = T::Convert(c, n, result, &allow_caching); if (allow_caching) { if (length == 1) { entries_[c & kMask] = CacheEntry(c, result[0] - c); diff --git a/runtime/vm/regexp/unibrow.cc b/runtime/vm/regexp/unibrow.cc index affa6995b8d..e6b804c5abf 100644 --- a/runtime/vm/regexp/unibrow.cc +++ b/runtime/vm/regexp/unibrow.cc @@ -93,13 +93,13 @@ struct MultiCharacterSpecialCase { // offset by the distance between the match and the start. Otherwise // the result is the same as for the start point on the entire range. template -static intptr_t LookupMapping(const int32_t* table, - uint16_t size, - const MultiCharacterSpecialCase* multi_chars, - int32_t chr, - int32_t next, - int32_t* result, - bool* allow_caching_ptr) { +static int LookupMapping(const int32_t* table, + uint16_t size, + const MultiCharacterSpecialCase* multi_chars, + uchar chr, + uchar next, + uchar* result, + bool* allow_caching_ptr) { const intptr_t kEntryDist = 2; uint16_t key = chr & (kChunkBits - 1); uint16_t chunk_start = chr - key; @@ -354,7 +354,7 @@ static constexpr int32_t kLetterTable7[48] = { 1073749328, 7567, 1073749394, 7623, 1073749488, 7675, 1073749616, 7796, // NOLINT 1073749622, 7932, 1073749793, 7994, 1073749825, 8026, 1073749862, 8126, // NOLINT 1073749954, 8135, 1073749962, 8143, 1073749970, 8151, 1073749978, 8156 }; // NOLINT -bool Letter::Is(int32_t c) { +bool Letter::Is(uchar c) { intptr_t chunk_index = c >> 13; switch (chunk_index) { case 0: return LookupPredicate(kLetterTable0, @@ -624,10 +624,8 @@ static const MultiCharacterSpecialCase<1> kEcma262CanonicalizeMultiStrings7[1] = static constexpr uint16_t kEcma262CanonicalizeTable7Size = 2; // NOLINT static constexpr int32_t kEcma262CanonicalizeTable7[4] = { 1073749825, -128, 8026, -128 }; // NOLINT -intptr_t Ecma262Canonicalize::Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr) { +int Ecma262Canonicalize::Convert(uchar c, uchar n, uchar* result, + bool* allow_caching_ptr) { intptr_t chunk_index = c >> 13; switch (chunk_index) { case 0: return LookupMapping(kEcma262CanonicalizeTable0, @@ -1756,10 +1754,8 @@ static const MultiCharacterSpecialCase<2> kEcma262UnCanonicalizeMultiStrings7[3] static constexpr uint16_t kEcma262UnCanonicalizeTable7Size = 4; // NOLINT static constexpr int32_t kEcma262UnCanonicalizeTable7[8] = { 1073749793, 1, 7994, 5, 1073749825, 1, 8026, 5 }; // NOLINT -intptr_t Ecma262UnCanonicalize::Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr) { +int Ecma262UnCanonicalize::Convert(uchar c, uchar n, uchar* result, + bool* allow_caching_ptr) { intptr_t chunk_index = c >> 13; switch (chunk_index) { case 0: return LookupMapping(kEcma262UnCanonicalizeTable0, @@ -1821,10 +1817,10 @@ static constexpr int32_t kCanonicalizationRangeTable7[8] = { // clang-format on -intptr_t CanonicalizationRange::Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr) { +int CanonicalizationRange::Convert(uchar c, + uchar n, + uchar* result, + bool* allow_caching_ptr) { intptr_t chunk_index = c >> 13; switch (chunk_index) { case 0: diff --git a/runtime/vm/regexp/unibrow.h b/runtime/vm/regexp/unibrow.h index 3caada9f4ef..4a5f55476f2 100644 --- a/runtime/vm/regexp/unibrow.h +++ b/runtime/vm/regexp/unibrow.h @@ -16,55 +16,54 @@ namespace unibrow { +using uchar = unsigned int; + +/** + * The max length of the result of converting the case of a single + * character. + */ +const int kMaxMappingSize = 4; + // A cache used in case conversion. It caches the value for characters // that either have no mapping or map to a single character independent // of context. Characters that map to more than one character or that // map differently depending on context are always looked up. -template +template class Mapping { public: - inline Mapping() {} - inline intptr_t get(int32_t c, int32_t n, int32_t* result); + inline Mapping() = default; + inline int get(uchar c, uchar n, uchar* result); private: friend class Test; - intptr_t CalculateValue(int32_t c, int32_t n, int32_t* result); + int CalculateValue(uchar c, uchar n, uchar* result); struct CacheEntry { inline CacheEntry() : code_point_(kNoChar), offset_(0) {} - inline CacheEntry(int32_t code_point, signed offset) + inline CacheEntry(uchar code_point, signed offset) : code_point_(code_point), offset_(offset) {} - int32_t code_point_; + uchar code_point_; signed offset_; - static constexpr intptr_t kNoChar = (1 << 21) - 1; + static const int kNoChar = (1 << 21) - 1; }; - static constexpr intptr_t kSize = size; - static constexpr intptr_t kMask = kSize - 1; + static const int kSize = size; + static const int kMask = kSize - 1; CacheEntry entries_[kSize]; }; struct Letter { - static bool Is(int32_t c); + static bool Is(uchar c); }; struct Ecma262Canonicalize { - static constexpr intptr_t kMaxWidth = 1; - static intptr_t Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr); + static const int kMaxWidth = 1; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); }; struct Ecma262UnCanonicalize { - static constexpr intptr_t kMaxWidth = 4; - static intptr_t Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr); + static const int kMaxWidth = 4; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); }; struct CanonicalizationRange { - static constexpr intptr_t kMaxWidth = 1; - static intptr_t Convert(int32_t c, - int32_t n, - int32_t* result, - bool* allow_caching_ptr); + static const int kMaxWidth = 1; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); }; } // namespace unibrow diff --git a/runtime/vm/regexp/vector.h b/runtime/vm/regexp/vector.h new file mode 100644 index 00000000000..18a038f68e1 --- /dev/null +++ b/runtime/vm/regexp/vector.h @@ -0,0 +1,197 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_BASE_VECTOR_H_ +#define V8_BASE_VECTOR_H_ + +#include +#include +#include + +#include "platform/allocation.h" +#include "platform/assert.h" +#include "platform/utils.h" +#include "vm/regexp/base.h" + +namespace base { + +template +class Vector { + public: + using value_type = T; + using iterator = T*; + using const_iterator = const T*; + + constexpr Vector() : start_(nullptr), length_(0) {} + + constexpr Vector(T* data, size_t length) : start_(data), length_(length) { + ASSERT(length == 0 || data != nullptr); + } + + static Vector New(size_t length) { + return Vector(new T[length], length); + } + + // Returns a vector using the same backing storage as this one, + // spanning from and including 'from', to but not including 'to'. + Vector SubVector(size_t from, size_t to) const { + DCHECK_LE(from, to); + DCHECK_LE(to, length_); + return Vector(begin() + from, to - from); + } + Vector SubVectorFrom(size_t from) const { + return SubVector(from, length_); + } + + template + void OverwriteWith(Vector other) { + DCHECK_EQ(size(), other.size()); + std::copy(other.begin(), other.end(), begin()); + } + + template + void OverwriteWith(const std::array& other) { + DCHECK_EQ(size(), other.size()); + std::copy(other.begin(), other.end(), begin()); + } + + // Returns the length of the vector. Only use this if you really need an + // integer return value. Use {size()} otherwise. + int length() const { + DCHECK_GE(std::numeric_limits::max(), length_); + return static_cast(length_); + } + + // Returns the length of the vector as a size_t. + constexpr size_t size() const { return length_; } + + // Returns whether or not the vector is empty. + constexpr bool empty() const { return length_ == 0; } + + // Access individual vector elements - checks bounds in debug mode. + T& operator[](size_t index) const { + DCHECK_LT(index, length_); + return start_[index]; + } + + const T& at(size_t index) const { return operator[](index); } + + T& first() { return start_[0]; } + const T& first() const { return start_[0]; } + + T& last() { + DCHECK_LT(0, length_); + return start_[length_ - 1]; + } + const T& last() const { + DCHECK_LT(0, length_); + return start_[length_ - 1]; + } + + // Returns a pointer to the start of the data in the vector. + constexpr T* begin() const { return start_; } + constexpr const T* cbegin() const { return start_; } + + // For consistency with other containers, do also provide a {data} accessor. + constexpr T* data() const { return start_; } + + // Returns a pointer past the end of the data in the vector. + constexpr T* end() const { return start_ + length_; } + constexpr const T* cend() const { return start_ + length_; } + + constexpr std::reverse_iterator rbegin() const { + return std::make_reverse_iterator(end()); + } + constexpr std::reverse_iterator rend() const { + return std::make_reverse_iterator(begin()); + } + + // Returns a clone of this vector with a new backing store. + Vector Clone() const { + T* result = new T[length_]; + for (size_t i = 0; i < length_; i++) + result[i] = start_[i]; + return Vector(result, length_); + } + + void Truncate(size_t length) { + ASSERT(length <= length_); + length_ = length; + } + + // Releases the array underlying this vector. Once disposed the + // vector is empty. + void Dispose() { + delete[] start_; + start_ = nullptr; + length_ = 0; + } + + const Vector operator+(size_t offset) const { + DCHECK_LE(offset, length_); + return Vector(start_ + offset, length_ - offset); + } + + Vector operator+=(size_t offset) { + DCHECK_LE(offset, length_); + start_ += offset; + length_ -= offset; + return *this; + } + + // Implicit conversion from Vector to Vector if + // - T* is convertible to const U*, and + // - U and T have the same size. + // Note that this conversion is only safe for `*const* U`; writes would + // violate covariance. + template + requires std::is_convertible_v && (sizeof(U) == sizeof(T)) + operator Vector() const { + return {start_, length_}; + } + + template + static Vector cast(Vector input) { + // Casting is potentially dangerous, so be really restrictive here. This + // might be lifted once we have use cases for that. + static_assert(std::is_trivial_v && std::is_standard_layout_v); + static_assert(std::is_trivial_v && std::is_standard_layout_v); + DCHECK_EQ(0, (input.size() * sizeof(S)) % sizeof(T)); + DCHECK_EQ(0, reinterpret_cast(input.begin()) % alignof(T)); + return Vector(reinterpret_cast(input.begin()), + input.size() * sizeof(S) / sizeof(T)); + } + + bool operator==(const Vector& other) const { + return std::equal(begin(), end(), other.begin(), other.end()); + } + + template + requires(!std::is_const_v) + bool operator==(const Vector& other) const { + return std::equal(begin(), end(), other.begin(), other.end()); + } + + private: + T* start_; + size_t length_; +}; + +// For string literals, ArrayVector("foo") returns a vector ['f', 'o', 'o', \0] +// with length 4 and null-termination. +// If you want ['f', 'o', 'o'], use CStrVector("foo"). +template +inline constexpr Vector ArrayVector(T (&arr)[N]) { + return {arr, N}; +} + +// Construct a Vector from a start pointer and a size. +template +inline constexpr Vector VectorOf(T* start, size_t size) { + return {start, size}; +} + +} // namespace base + +#endif // V8_BASE_VECTOR_H_ diff --git a/runtime/vm/regexp/zone-containers.h b/runtime/vm/regexp/zone-containers.h new file mode 100644 index 00000000000..06b90f69736 --- /dev/null +++ b/runtime/vm/regexp/zone-containers.h @@ -0,0 +1,716 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ZONE_ZONE_CONTAINERS_H_ +#define V8_ZONE_ZONE_CONTAINERS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vm/regexp/base.h" +#include "vm/regexp/memcopy.h" +#include "vm/regexp/small-vector.h" +#include "vm/regexp/vector.h" +#include "vm/zone.h" + +namespace dart { + +// A drop-in replacement for std::vector that uses a Zone for its allocations, +// and (contrary to a std::vector subclass with custom allocator) gives us +// precise control over its implementation and performance characteristics. +// +// When working on this code, keep the following rules of thumb in mind: +// - Everything between {data_} and {end_} (exclusive) is a live instance of T. +// When writing to these slots, use the {CopyingOverwrite} or +// {MovingOverwrite} helpers. +// - Everything between {end_} (inclusive) and {capacity_} (exclusive) is +// considered uninitialized memory. When writing to these slots, use the +// {CopyToNewStorage} or {MoveToNewStorage} helpers. Obviously, also use +// these helpers to initialize slots in newly allocated backing stores. +// - When shrinking, call ~T on all slots between the new and the old position +// of {end_} to maintain the above invariant. Also call ~T on all slots in +// discarded backing stores. +// - The interface offered by {ZoneVector} should be a subset of +// {std::vector}'s API, so that calling code doesn't need to be aware of +// ZoneVector's implementation details and can assume standard C++ behavior. +// (It's okay if we don't support everything that std::vector supports; we +// can fill such gaps when use cases arise.) +template +class ZoneVector { + public: + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using size_type = size_t; + + // Constructs an empty vector. + explicit ZoneVector(Zone* zone) : zone_(zone) {} + + // Constructs a new vector and fills it with {size} elements, each + // constructed via the default constructor. + ZoneVector(size_t size, Zone* zone) : zone_(zone) { + data_ = size > 0 ? zone->AllocateArray(size) : nullptr; + end_ = capacity_ = data_ + size; + for (T* p = data_; p < end_; p++) + emplace_at(p); + } + + // Constructs a new vector and fills it with {size} elements, each + // having the value {def}. + ZoneVector(size_t size, T def, Zone* zone) : zone_(zone) { + data_ = size > 0 ? zone->AllocateArray(size) : nullptr; + end_ = capacity_ = data_ + size; + for (T* p = data_; p < end_; p++) + emplace_at(p, def); + } + + // Constructs a new vector and fills it with the contents of the given + // initializer list. + ZoneVector(std::initializer_list list, Zone* zone) : zone_(zone) { + size_t size = list.size(); + if (size > 0) { + data_ = zone->AllocateArray(size); + CopyToNewStorage(data_, list.begin(), list.end()); + } else { + data_ = nullptr; + } + end_ = capacity_ = data_ + size; + } + + // Constructs a new vector and fills it with the contents of the range + // [first, last). + template ::iterator_category> + ZoneVector(It first, It last, Zone* zone) : zone_(zone) { + if constexpr (std::is_base_of_v< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category>) { + size_t size = last - first; + data_ = size > 0 ? zone->AllocateArray(size) : nullptr; + end_ = capacity_ = data_ + size; + for (T* p = data_; p < end_; p++) + emplace_at(p, *first++); + } else { + while (first != last) + push_back(*first++); + } + DCHECK_EQ(first, last); + } + + ZoneVector(const ZoneVector& other) V8_NOEXCEPT : zone_(other.zone_) { + *this = other; + } + + ZoneVector(ZoneVector&& other) V8_NOEXCEPT { *this = std::move(other); } + + ~ZoneVector() { + for (T* p = data_; p < end_; p++) + p->~T(); + if (data_) zone_->DeleteArray(data_, capacity()); + } + + // Assignment operators. + ZoneVector& operator=(const ZoneVector& other) V8_NOEXCEPT { + // Self-assignment would cause undefined behavior in the !copy_assignable + // branch, but likely indicates a bug in calling code anyway. + DCHECK_NE(this, &other); + T* src = other.data_; + if (capacity() >= other.size() && zone_ == other.zone_) { + T* dst = data_; + if constexpr (std::is_trivially_copyable_v) { + size_t size = other.size(); + if (size != 0) memcpy(dst, src, size * sizeof(T)); + end_ = dst + size; + } else if constexpr (std::is_copy_assignable_v) { + while (dst < end_ && src < other.end_) + *dst++ = *src++; + while (src < other.end_) + emplace_at(dst++, *src++); + T* old_end = end_; + end_ = dst; + for (T* p = end_; p < old_end; p++) + p->~T(); + } else { + for (T* p = data_; p < end_; p++) + p->~T(); + while (src < other.end_) + emplace_at(dst++, *src++); + end_ = dst; + } + } else { + for (T* p = data_; p < end_; p++) + p->~T(); + if (data_) zone_->DeleteArray(data_, capacity()); + size_t new_cap = other.capacity(); + if (new_cap > 0) { + data_ = zone_->AllocateArray(new_cap); + CopyToNewStorage(data_, other.data_, other.end_); + } else { + data_ = nullptr; + } + capacity_ = data_ + new_cap; + end_ = data_ + other.size(); + } + return *this; + } + + ZoneVector& operator=(ZoneVector&& other) V8_NOEXCEPT { + // Self-assignment would cause undefined behavior, and is probably a bug. + DCHECK_NE(this, &other); + // Move-assigning vectors from different zones would have surprising + // lifetime semantics regardless of how we choose to implement it (keep + // the old zone? Take the new zone?). + if (zone_ == nullptr) { + zone_ = other.zone_; + } else { + DCHECK_EQ(zone_, other.zone_); + } + for (T* p = data_; p < end_; p++) + p->~T(); + if (data_) zone_->DeleteArray(data_, capacity()); + data_ = other.data_; + end_ = other.end_; + capacity_ = other.capacity_; + // {other.zone_} may stay. + other.data_ = other.end_ = other.capacity_ = nullptr; + return *this; + } + + ZoneVector& operator=(std::initializer_list ilist) { + clear(); + EnsureCapacity(ilist.size()); + CopyToNewStorage(data_, ilist.begin(), ilist.end()); + end_ = data_ + ilist.size(); + return *this; + } + + base::Vector Release() && { + base::Vector ret = base::VectorOf(*this); + data_ = end_ = capacity_ = nullptr; + return ret; + } + + void swap(ZoneVector& other) noexcept { + DCHECK_EQ(zone_, other.zone_); + std::swap(data_, other.data_); + std::swap(end_, other.end_); + std::swap(capacity_, other.capacity_); + } + + void resize(size_t new_size) { + EnsureCapacity(new_size); + T* new_end = data_ + new_size; + for (T* p = end_; p < new_end; p++) + emplace_at(p); + for (T* p = new_end; p < end_; p++) + p->~T(); + end_ = new_end; + } + + void resize(size_t new_size, const T& value) { + EnsureCapacity(new_size); + T* new_end = data_ + new_size; + for (T* p = end_; p < new_end; p++) + emplace_at(p, value); + for (T* p = new_end; p < end_; p++) + p->~T(); + end_ = new_end; + } + + void assign(size_t new_size, const T& value) { + if (capacity() >= new_size) { + T* new_end = data_ + new_size; + T* assignable = data_ + std::min(size(), new_size); + for (T* p = data_; p < assignable; p++) + CopyingOverwrite(p, &value); + for (T* p = assignable; p < new_end; p++) + CopyToNewStorage(p, &value); + for (T* p = new_end; p < end_; p++) + p->~T(); + end_ = new_end; + } else { + clear(); + EnsureCapacity(new_size); + T* new_end = data_ + new_size; + for (T* p = data_; p < new_end; p++) + emplace_at(p, value); + end_ = new_end; + } + } + + void clear() { + for (T* p = data_; p < end_; p++) + p->~T(); + end_ = data_; + } + + size_t size() const { return end_ - data_; } + bool empty() const { return end_ == data_; } + size_t capacity() const { return capacity_ - data_; } + void reserve(size_t new_cap) { EnsureCapacity(new_cap); } + T* data() { return data_; } + const T* data() const { return data_; } + Zone* zone() const { return zone_; } + + T& at(size_t pos) { + DCHECK_LT(pos, size()); + return data_[pos]; + } + const T& at(size_t pos) const { + DCHECK_LT(pos, size()); + return data_[pos]; + } + + T& operator[](size_t pos) { return at(pos); } + const T& operator[](size_t pos) const { return at(pos); } + + T& front() { + DCHECK_GT(end_, data_); + return *data_; + } + const T& front() const { + DCHECK_GT(end_, data_); + return *data_; + } + + T& back() { + DCHECK_GT(end_, data_); + return *(end_ - 1); + } + const T& back() const { + DCHECK_GT(end_, data_); + return *(end_ - 1); + } + + T* begin() V8_NOEXCEPT { return data_; } + const T* begin() const V8_NOEXCEPT { return data_; } + const T* cbegin() const V8_NOEXCEPT { return data_; } + + T* end() V8_NOEXCEPT { return end_; } + const T* end() const V8_NOEXCEPT { return end_; } + const T* cend() const V8_NOEXCEPT { return end_; } + + reverse_iterator rbegin() V8_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + const_reverse_iterator rbegin() const V8_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + const_reverse_iterator crbegin() const V8_NOEXCEPT { + return std::make_reverse_iterator(cend()); + } + reverse_iterator rend() V8_NOEXCEPT { + return std::make_reverse_iterator(begin()); + } + const_reverse_iterator rend() const V8_NOEXCEPT { + return std::make_reverse_iterator(begin()); + } + const_reverse_iterator crend() const V8_NOEXCEPT { + return std::make_reverse_iterator(cbegin()); + } + + void push_back(const T& value) { + EnsureOneMoreCapacity(); + emplace_at(end_++, value); + } + void push_back(T&& value) { emplace_back(std::move(value)); } + + void pop_back() { + DCHECK_GT(end_, data_); + (--end_)->~T(); + } + + template + T& emplace_back(Args&&... args) { + EnsureOneMoreCapacity(); + T* ptr = end_++; + new (ptr) T(std::forward(args)...); + return *ptr; + } + + template ::iterator_category> + T* insert(const T* pos, It first, It last) { + T* position; + if constexpr (std::is_base_of_v< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category>) { + DCHECK_LE(0, last - first); + size_t count = last - first; + size_t assignable; + position = PrepareForInsertion(pos, count, &assignable); + if (!base::TryTrivialCopy(first, first + count, position)) { + CopyingOverwrite(position, first, first + assignable); + CopyToNewStorage(position + assignable, first + assignable, last); + } + } else if (pos == end()) { + position = end_; + while (first != last) { + EnsureOneMoreCapacity(); + emplace_at(end_++, *first++); + } + } else { + UNIMPLEMENTED(); + // We currently have no users of this case. + // It could be implemented inefficiently as a combination of the two + // cases above: while (first != last) { PrepareForInsertion(_, 1, _); }. + // A more efficient approach would be to accumulate the input iterator's + // results into a temporary vector first, then grow {this} only once + // (by calling PrepareForInsertion(_, count, _)), then copy over the + // accumulated elements. + } + return position; + } + T* insert(const T* pos, size_t count, const T& value) { + size_t assignable; + T* position = PrepareForInsertion(pos, count, &assignable); + T* dst = position; + T* stop = dst + assignable; + while (dst < stop) { + CopyingOverwrite(dst++, &value); + } + stop = position + count; + while (dst < stop) + emplace_at(dst++, value); + return position; + } + + template + T* emplace(const T* pos, Args&&... args) { + size_t assignable; + T* dst = PrepareForInsertion(pos, 1, &assignable); + if (assignable == 1) { + dst->~T(); + } + emplace_at(dst, args...); + return dst; + } + + T* erase(const T* pos) { + DCHECK(data_ <= pos && pos <= end()); + if (pos == end()) return const_cast(pos); + return erase(pos, 1); + } + T* erase(const T* first, const T* last) { + DCHECK(data_ <= first && first <= last && last <= end()); + if (first == last) return const_cast(first); + return erase(first, last - first); + } + + private: + static constexpr size_t kMinCapacity = 2; + size_t NewCapacity(size_t minimum) { + // We can ignore possible overflow here: on 32-bit platforms, if the + // multiplication overflows, there's no better way to handle it than + // relying on the "new_capacity < minimum" check; in particular, a + // saturating multiplication would make no sense. On 64-bit platforms, + // overflow is effectively impossible anyway. + size_t new_capacity = data_ == capacity_ ? kMinCapacity : capacity() * 2; + return new_capacity < minimum ? minimum : new_capacity; + } + + V8_INLINE void EnsureOneMoreCapacity() { + if (V8_LIKELY(end_ < capacity_)) return; + Grow(capacity() + 1); + } + + V8_INLINE void EnsureCapacity(size_t minimum) { + if (V8_LIKELY(minimum <= capacity())) return; + Grow(minimum); + } + + V8_INLINE void CopyToNewStorage(T* dst, const T* src) { + emplace_at(dst, *src); + } + + V8_INLINE void MoveToNewStorage(T* dst, T* src) { + if constexpr (std::is_move_constructible_v) { + emplace_at(dst, std::move(*src)); + } else { + CopyToNewStorage(dst, src); + } + } + + V8_INLINE void CopyingOverwrite(T* dst, const T* src) { + if constexpr (std::is_copy_assignable_v) { + *dst = *src; + } else { + dst->~T(); + CopyToNewStorage(dst, src); + } + } + + V8_INLINE void MovingOverwrite(T* dst, T* src) { + if constexpr (std::is_move_assignable_v) { + *dst = std::move(*src); + } else { + CopyingOverwrite(dst, src); + } + } + + V8_INLINE void CopyToNewStorage(T* dst, const T* src, const T* src_end) { + if (base::TryTrivialCopy(src, src_end, dst)) { + return; + } + for (; src < src_end; dst++, src++) { + CopyToNewStorage(dst, src); + } + } + + V8_INLINE void MoveToNewStorage(T* dst, T* src, const T* src_end) { + if (base::TryTrivialCopy(src, src_end, dst)) { + return; + } + for (; src < src_end; dst++, src++) { + MoveToNewStorage(dst, src); + src->~T(); + } + } + + V8_INLINE void CopyingOverwrite(T* dst, const T* src, const T* src_end) { + if (base::TryTrivialMove(src, src_end, dst)) { + return; + } + for (; src < src_end; dst++, src++) { + CopyingOverwrite(dst, src); + } + } + + V8_INLINE void MovingOverwrite(T* dst, T* src, const T* src_end) { + if (base::TryTrivialMove(src, src_end, dst)) { + return; + } + for (; src < src_end; dst++, src++) { + MovingOverwrite(dst, src); + } + } + + V8_NOINLINE V8_PRESERVE_MOST void Grow(size_t minimum) { + T* old_data = data_; + T* old_end = end_; + size_t old_size = size(); + size_t new_capacity = NewCapacity(minimum); + data_ = zone_->AllocateArray(new_capacity); + end_ = data_ + old_size; + if (old_data) { + MoveToNewStorage(data_, old_data, old_end); + zone_->DeleteArray(old_data, capacity_ - old_data); + } + capacity_ = data_ + new_capacity; + } + + T* PrepareForInsertion(const T* pos, size_t count, size_t* assignable) { + DCHECK(data_ <= pos && pos <= end_); + CHECK(std::numeric_limits::max() - size() >= count); + size_t index = pos - data_; + size_t to_shift = end() - pos; + DCHECK_EQ(index + to_shift, size()); + if (capacity() < size() + count) { + *assignable = 0; // Fresh memory is not assignable (must be constructed). + T* old_data = data_; + T* old_end = end_; + size_t old_size = size(); + size_t new_capacity = NewCapacity(old_size + count); + data_ = zone_->AllocateArray(new_capacity); + end_ = data_ + old_size + count; + if (old_data) { + MoveToNewStorage(data_, old_data, pos); + MoveToNewStorage(data_ + index + count, const_cast(pos), old_end); + zone_->DeleteArray(old_data, capacity_ - old_data); + } + capacity_ = data_ + new_capacity; + } else { + // There are two interesting cases: we're inserting more elements + // than we're shifting (top), or the other way round (bottom). + // + // Old: [ABCDEFGHIJ___________] + // <--used--><--empty--> + // + // Case 1: index=7, count=8, to_shift=3 + // New: [ABCDEFGaaacccccHIJ___] + // <-><------> + // ↑ ↑ to be in-place constructed + // ↑ + // assignable_slots + // + // Case 2: index=3, count=3, to_shift=7 + // New: [ABCaaaDEFGHIJ________] + // <-----><-> + // ↑ ↑ to be in-place constructed + // ↑ + // This range can be assigned. We report the first 3 + // as {assignable_slots} to the caller, and use the other 4 + // in the loop below. + // Observe that the number of old elements that are moved to the + // new end by in-place construction always equals {assignable_slots}. + size_t assignable_slots = std::min(to_shift, count); + *assignable = assignable_slots; + if constexpr (std::is_trivially_copyable_v) { + if (to_shift > 0) { + // Add V8_ASSUME to silence gcc null check warning. + V8_ASSUME(pos != nullptr); + memmove(const_cast(pos + count), pos, to_shift * sizeof(T)); + } + end_ += count; + return data_ + index; + } + // Construct elements in previously-unused area ("HIJ" in the example + // above). This frees up assignable slots. + T* dst = end_ + count; + T* src = end_; + for (T* stop = dst - assignable_slots; dst > stop;) { + MoveToNewStorage(--dst, --src); + } + // Move (by assignment) elements into previously used area. This is + // "DEFG" in "case 2" in the example above. + DCHECK_EQ(src > pos, to_shift > count); + DCHECK_IMPLIES(src > pos, dst == end_); + while (src > pos) + MovingOverwrite(--dst, --src); + // Not destructing {src} here because that'll happen either in a + // future iteration (when that spot becomes {dst}) or in {insert()}. + end_ += count; + } + return data_ + index; + } + + T* erase(const T* first, size_t count) { + DCHECK(data_ <= first && first <= end()); + DCHECK_LE(count, end() - first); + T* position = const_cast(first); + MovingOverwrite(position, position + count, end()); + T* old_end = end(); + end_ -= count; + for (T* p = end_; p < old_end; p++) + p->~T(); + return position; + } + + template + void emplace_at(T* target, Args&&... args) { + new (target) T(std::forward(args)...); + } + + Zone* zone_{nullptr}; + T* data_{nullptr}; + T* end_{nullptr}; + T* capacity_{nullptr}; +}; + +template +bool operator==(const ZoneVector& lhs, const ZoneVector& rhs) { + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +template +bool operator!=(const ZoneVector& lhs, const ZoneVector& rhs) { + return !(lhs == rhs); +} + +template +bool operator<(const ZoneVector& lhs, const ZoneVector& rhs) { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end()); +} + +template +class ZoneAllocator { + public: + using value_type = T; + + explicit ZoneAllocator(Zone* zone) : zone_(zone) {} + template + ZoneAllocator(const ZoneAllocator& other) + : ZoneAllocator(other.zone()) {} + + T* allocate(size_t length) { return zone_->Alloc(length); } + void deallocate(T* p, size_t length) { zone_->DeleteArray(p, length); } + + bool operator==(ZoneAllocator const& other) const { + return zone_ == other.zone_; + } + bool operator!=(ZoneAllocator const& other) const { + return zone_ != other.zone_; + } + + Zone* zone() const { return zone_; } + + private: + Zone* zone_; +}; + +// A wrapper subclass for std::map to make it easy to construct one that uses +// a zone allocator. +template > +class ZoneMap + : public std::map>> { + public: + // Constructs an empty map. + explicit ZoneMap(Zone* zone) + : std::map>>( + Compare(), + ZoneAllocator>(zone)) {} +}; + +// A wrapper subclass for std::unordered_map to make it easy to construct one +// that uses a zone allocator. +template , + typename KeyEqual = std::equal_to> +class ZoneUnorderedMap + : public std::unordered_map>> { + public: + // Constructs an empty map. + explicit ZoneUnorderedMap(Zone* zone, size_t bucket_count = 0) + : std::unordered_map>>( + bucket_count, + Hash(), + KeyEqual(), + ZoneAllocator>(zone)) {} +}; + +// A wrapper subclass for base::SmallVector to make it easy to construct one +// that uses a zone allocator. +template +class SmallZoneVector : public base::SmallVector> { + public: + // Constructs an empty small vector. + explicit SmallZoneVector(Zone* zone) + : base::SmallVector>(ZoneAllocator(zone)) {} + + explicit SmallZoneVector(size_t size, Zone* zone) + : base::SmallVector>( + size, + ZoneAllocator(ZoneAllocator(zone))) {} +}; + +} // namespace dart + +#endif // V8_ZONE_ZONE_CONTAINERS_H_ diff --git a/runtime/vm/regexp/zone-list-inl.h b/runtime/vm/regexp/zone-list-inl.h new file mode 100644 index 00000000000..8491fa2beaf --- /dev/null +++ b/runtime/vm/regexp/zone-list-inl.h @@ -0,0 +1,160 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ZONE_ZONE_LIST_INL_H_ +#define V8_ZONE_ZONE_LIST_INL_H_ + +#include "vm/regexp/zone-list.h" +// Include the non-inl header before the rest of the headers. + +#include "vm/regexp/memcopy.h" + +namespace dart { + +template +void ZoneList::Add(const T& element, Zone* zone) { + if (length_ < capacity_) { + data_[length_++] = element; + } else { + ZoneList::ResizeAdd(element, zone); + } +} + +template +void ZoneList::AddAll(const ZoneList& other, Zone* zone) { + AddAll(other.ToVector(), zone); +} + +template +void ZoneList::AddAll(base::Vector other, Zone* zone) { + int length = other.length(); + if (length == 0) return; + + int result_length = length_ + length; + if (capacity_ < result_length) Resize(result_length, zone); + if (std::is_trivially_copyable_v) { + memcpy(&data_[length_], other.begin(), sizeof(T) * length); + } else { + std::copy(other.begin(), other.end(), &data_[length_]); + } + length_ = result_length; +} + +// Use two layers of inlining so that the non-inlined function can +// use the same implementation as the inlined version. +template +void ZoneList::ResizeAdd(const T& element, Zone* zone) { + ResizeAddInternal(element, zone); +} + +template +void ZoneList::ResizeAddInternal(const T& element, Zone* zone) { + DCHECK(length_ >= capacity_); + // Grow the list capacity by 100%, but make sure to let it grow + // even when the capacity is zero (possible initial case). + int new_capacity = 1 + 2 * capacity_; + // Since the element reference could be an element of the list, copy + // it out of the old backing storage before resizing. + T temp = element; + Resize(new_capacity, zone); + data_[length_++] = temp; +} + +template +void ZoneList::Resize(int new_capacity, Zone* zone) { + DCHECK_LE(length_, new_capacity); + T* new_data = zone->AllocateArray(new_capacity); + if (length_ > 0) { + if (std::is_trivially_copyable_v) { + base::MemCopy(new_data, data_, length_ * sizeof(T)); + } else { + std::copy(&data_[0], &data_[length_], &new_data[0]); + } + } + if (data_) zone->DeleteArray(data_, capacity_); + data_ = new_data; + capacity_ = new_capacity; +} + +template +base::Vector ZoneList::AddBlock(T value, int count, Zone* zone) { + int start = length_; + for (int i = 0; i < count; i++) + Add(value, zone); + return base::Vector(&data_[start], count); +} + +template +void ZoneList::Set(int index, const T& elm) { + DCHECK(index >= 0 && index <= length_); + data_[index] = elm; +} + +template +void ZoneList::InsertAt(int index, const T& elm, Zone* zone) { + DCHECK(index >= 0 && index <= length_); + Add(elm, zone); + for (int i = length_ - 1; i > index; --i) { + data_[i] = data_[i - 1]; + } + data_[index] = elm; +} + +template +T ZoneList::Remove(int i) { + T element = at(i); + length_--; + while (i < length_) { + data_[i] = data_[i + 1]; + i++; + } + return element; +} + +template +void ZoneList::Clear(Zone* zone) { + if (data_) zone->DeleteArray(data_, capacity_); + DropAndClear(); +} + +template +void ZoneList::Rewind(int pos) { + DCHECK(0 <= pos && pos <= length_); + length_ = pos; +} + +template +template +void ZoneList::Iterate(Visitor* visitor) { + for (int i = 0; i < length_; i++) + visitor->Apply(&data_[i]); +} + +template +template +void ZoneList::Sort(CompareFunction cmp) { + std::sort(begin(), end(), + [cmp](const T& a, const T& b) { return cmp(&a, &b) < 0; }); +#ifdef DEBUG + for (int i = 1; i < length_; i++) { + DCHECK_LE(cmp(&data_[i - 1], &data_[i]), 0); + } +#endif +} + +template +template +void ZoneList::StableSort(CompareFunction cmp, size_t s, size_t l) { + std::stable_sort(begin() + s, begin() + s + l, + [cmp](const T& a, const T& b) { return cmp(&a, &b) < 0; }); +#ifdef DEBUG + for (size_t i = s + 1; i < l; i++) { + DCHECK_LE(cmp(&data_[i - 1], &data_[i]), 0); + } +#endif +} + +} // namespace dart + +#endif // V8_ZONE_ZONE_LIST_INL_H_ diff --git a/runtime/vm/regexp/zone-list.h b/runtime/vm/regexp/zone-list.h new file mode 100644 index 00000000000..894d72d5180 --- /dev/null +++ b/runtime/vm/regexp/zone-list.h @@ -0,0 +1,193 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ZONE_ZONE_LIST_H_ +#define V8_ZONE_ZONE_LIST_H_ + +#include "vm/regexp/base.h" +#include "vm/zone.h" + +namespace base { +template +class Vector; +} // namespace base + +namespace dart { + +// ZoneLists are growable lists with constant-time access to the elements. +// The list itself and all its elements are supposed to be allocated in zone +// memory. Unlike ZoneVector container, the ZoneList instance has minimal +// possible size which makes it a good candidate for embedding into other +// often-allocated zone objects. +// +// Note, ZoneLists' elements cannot be deleted individually and the destructor +// intentionally does not free the backing store. Because of the latter, the +// ZoneList must not be used outsize of zone memory. Consider using ZoneVector +// or other containers instead. +template +class ZoneList final : public ZoneObject { + public: + // Construct a new ZoneList with the given capacity; the length is + // always zero. The capacity must be non-negative. + ZoneList(int capacity, Zone* zone) : capacity_(capacity) { + DCHECK_GE(capacity, 0); + if (capacity > 0) { + DCHECK_NOT_NULL(zone); + data_ = zone->AllocateArray(capacity); + } else { + data_ = nullptr; + } + } + + // Construct a new ZoneList by copying the elements of the given ZoneList. + ZoneList(const ZoneList& other, Zone* zone) + : ZoneList(other.length(), zone) { + AddAll(other, zone); + } + + // Construct a new ZoneList by copying the elements of the given vector. + ZoneList(base::Vector other, Zone* zone) + : ZoneList(other.length(), zone) { + AddAll(other, zone); + } + + ZoneList(ZoneList&& other) V8_NOEXCEPT { *this = std::move(other); } + + ZoneList(const ZoneList&) = delete; + ZoneList& operator=(const ZoneList&) = delete; + + // The ZoneList objects are usually allocated as a fields in other + // zone-allocated objects for which destructors are not called anyway, so + // we are not going to clear the memory here as well. + ~ZoneList() = default; + + ZoneList& operator=(ZoneList&& other) V8_NOEXCEPT { + // We don't have a Zone object, so we'll have to drop the data_ array. + // If this assert ever fails, consider calling Clear(Zone*) or + // DropAndClear() before the move assignment to make it explicit what's + // happenning with the lvalue. + DCHECK_NULL(data_); + data_ = other.data_; + capacity_ = other.capacity_; + length_ = other.length_; + other.DropAndClear(); + return *this; + } + + // Returns a reference to the element at index i. This reference is not safe + // to use after operations that can change the list's backing store + // (e.g. Add). + inline T& operator[](int i) const { + DCHECK_LE(0, i); + DCHECK_GT(static_cast(length_), static_cast(i)); + return data_[i]; + } + inline T& at(int i) const { return operator[](i); } + inline T& last() const { return at(length_ - 1); } + inline T& first() const { return at(0); } + + using iterator = T*; + inline iterator begin() { return &data_[0]; } + inline iterator end() { return &data_[length_]; } + + using const_iterator = const T*; + inline const_iterator begin() const { return &data_[0]; } + inline const_iterator end() const { return &data_[length_]; } + + V8_INLINE bool is_empty() const { return length_ == 0; } + V8_INLINE int length() const { return length_; } + V8_INLINE int capacity() const { return capacity_; } + + base::Vector ToVector() const { return base::Vector(data_, length_); } + base::Vector ToVector(int start, int length) const { + DCHECK_LE(start, length_); + return base::Vector(&data_[start], std::min(length_ - start, length)); + } + + base::Vector ToConstVector() const { + return base::Vector(data_, length_); + } + + // Adds a copy of the given 'element' to the end of the list, + // expanding the list if necessary. + void Add(const T& element, Zone* zone); + // Add all the elements from the argument list to this list. + void AddAll(const ZoneList& other, Zone* zone); + // Add all the elements from the vector to this list. + void AddAll(base::Vector other, Zone* zone); + // Inserts the element at the specific index. + void InsertAt(int index, const T& element, Zone* zone); + + // Added 'count' elements with the value 'value' and returns a + // vector that allows access to the elements. The vector is valid + // until the next change is made to this list. + base::Vector AddBlock(T value, int count, Zone* zone); + + // Overwrites the element at the specific index. + void Set(int index, const T& element); + + // Removes the i'th element without deleting it even if T is a + // pointer type; moves all elements above i "down". Returns the + // removed element. This function's complexity is linear in the + // size of the list. + T Remove(int i); + + // Removes the last element without deleting it even if T is a + // pointer type. Returns the removed element. + V8_INLINE T RemoveLast() { return Remove(length_ - 1); } + + // Clears the list by freeing the storage memory. If you want to keep the + // memory, use Rewind(0) instead. Be aware, that even if T is a + // pointer type, clearing the list doesn't delete the entries. + V8_INLINE void Clear(Zone* zone); + + // Clears the list but unlike Clear(), it doesn't free the storage memory. + // It's useful when the whole zone containing the backing store will be + // released but the list will be used further. + V8_INLINE void DropAndClear() { + data_ = nullptr; + capacity_ = 0; + length_ = 0; + } + + // Drops all but the first 'pos' elements from the list. + V8_INLINE void Rewind(int pos); + + inline bool Contains(const T& elm) const { + for (int i = 0; i < length_; i++) { + if (data_[i] == elm) return true; + } + return false; + } + + // Iterate through all list entries, starting at index 0. + template + void Iterate(Visitor* visitor); + + // Sort all list entries (using QuickSort) + template + void Sort(CompareFunction cmp); + template + void StableSort(CompareFunction cmp, size_t start, size_t length); + + private: + T* data_ = nullptr; + int capacity_ = 0; + int length_ = 0; + + // Increase the capacity of a full list, and add an element. + // List must be full already. + void ResizeAdd(const T& element, Zone* zone); + + // Inlined implementation of ResizeAdd, shared by inlined and + // non-inlined versions of ResizeAdd. + void ResizeAddInternal(const T& element, Zone* zone); + + // Resize the list. + void Resize(int new_capacity, Zone* zone); +}; + +} // namespace dart + +#endif // V8_ZONE_ZONE_LIST_H_ diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index f4dc869526c..a185c919024 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -209,6 +209,16 @@ void OnEveryRuntimeEntryCall(Thread* thread, #define DEFINE_RUNTIME_ENTRY_NO_LAZY_DEOPT(name, argument_count) \ DEFINE_RUNTIME_ENTRY_IMPL(name, argument_count, /*can_lazy_deopt=*/false) +#define DEFINE_LEAF_RUNTIME_ENTRY(name, argument_count, func) \ + extern const RuntimeEntry k##name##RuntimeEntry( \ + "DLRT_" #name, reinterpret_cast(func), argument_count, \ + true, false, /*can_lazy_deopt=*/false) + +#define DEFINE_FLOAT_LEAF_RUNTIME_ENTRY(name, argument_count, func) \ + extern const RuntimeEntry k##name##RuntimeEntry( \ + "DLRT_" #name, reinterpret_cast(func), argument_count, \ + true, true, /*can_lazy_deopt=*/false) + DEFINE_RUNTIME_ENTRY(RangeError, 2) { const Instance& length = Instance::CheckedHandle(zone, arguments.ArgAt(0)); const Instance& index = Instance::CheckedHandle(zone, arguments.ArgAt(1)); diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h index d15a018c6f1..c4fb110db0e 100644 --- a/runtime/vm/runtime_entry.h +++ b/runtime/vm/runtime_entry.h @@ -84,16 +84,6 @@ class RuntimeEntry : public BaseRuntimeEntry { extern const RuntimeEntry k##name##RuntimeEntry; \ extern "C" void DRT_##name(NativeArguments arguments); -#define DEFINE_LEAF_RUNTIME_ENTRY(name, argument_count, func) \ - extern const RuntimeEntry k##name##RuntimeEntry( \ - "DLRT_" #name, reinterpret_cast(func), argument_count, \ - true, false, /*can_lazy_deopt=*/false) - -#define DEFINE_FLOAT_LEAF_RUNTIME_ENTRY(name, argument_count, func) \ - extern const RuntimeEntry k##name##RuntimeEntry( \ - "DLRT_" #name, reinterpret_cast(func), argument_count, \ - true, true, /*can_lazy_deopt=*/false) - #define DECLARE_LEAF_RUNTIME_ENTRY(type, name, ...) \ extern const RuntimeEntry k##name##RuntimeEntry; \ extern "C" type DLRT_##name(__VA_ARGS__); diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h index 34bd942c074..fe175a68aaa 100644 --- a/runtime/vm/runtime_entry_list.h +++ b/runtime/vm/runtime_entry_list.h @@ -119,10 +119,6 @@ namespace dart { V(double, LibcAtan2, double, double) \ V(double, LibcExp, double) \ V(double, LibcLog, double) \ - V(uword /*BoolPtr*/, CaseInsensitiveCompareUCS2, uword /*StringPtr*/, \ - uword /*SmiPtr*/, uword /*SmiPtr*/, uword /*SmiPtr*/) \ - V(uword /*BoolPtr*/, CaseInsensitiveCompareUTF16, uword /*StringPtr*/, \ - uword /*SmiPtr*/, uword /*SmiPtr*/, uword /*SmiPtr*/) \ V(void, EnterSafepoint) \ V(void, ExitSafepoint) \ V(ApiLocalScope*, EnterHandleScope, Thread*) \ diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 2e6d0ee0825..e2120747d97 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -448,9 +448,6 @@ class ObjectPointerVisitor; V(_ffi_resolver_function, "_ffi_resolver_function") \ V(future, "future") \ V(_future, "_future") \ - V(_getRegisters, "_getRegisters") \ - V(_getBacktrackingStack, "_getBacktrackingStack") \ - V(_growBacktrackingStack, "_growBacktrackingStack") \ V(_handleException, "_handleException") \ V(_handleFinalizerMessage, "_handleFinalizerMessage") \ V(_handleMessage, "_handleMessage") \ @@ -507,7 +504,6 @@ class ObjectPointerVisitor; V(_toString, "_toString") \ V(_typedDataBase, "_typedDataBase") \ V(_varData, "_varData") \ - V(_wordCharacterMap, "_wordCharacterMap") \ V(_yieldAsyncStar, "_yieldAsyncStar") \ V(_yieldStarIterable, "_yieldStarIterable") \ V(_yieldSyncStar, "_yieldSyncStar") \ diff --git a/runtime/vm/zone.h b/runtime/vm/zone.h index 841c54d970a..b663ca542b3 100644 --- a/runtime/vm/zone.h +++ b/runtime/vm/zone.h @@ -35,6 +35,12 @@ class Zone { // size computation. template inline ElementType* Alloc(intptr_t len); + template + inline ElementType* AllocateArray(intptr_t len) { + return Alloc(len); + } + template + void DeleteArray(ElementType* data, intptr_t len) {} // Allocates an array sized to hold 'len' elements of type // 'ElementType'. The new array is initialized from the memory of diff --git a/sdk/lib/_internal/vm/lib/regexp_patch.dart b/sdk/lib/_internal/vm/lib/regexp_patch.dart index cdf6bf686c8..728cf80dd95 100644 --- a/sdk/lib/_internal/vm/lib/regexp_patch.dart +++ b/sdk/lib/_internal/vm/lib/regexp_patch.dart @@ -76,8 +76,7 @@ class RegExp { } int get _groupCount; - Iterable get _groupNames; - int _groupNameIndex(String name); + List? get _groupNameList; } class _RegExpMatch implements RegExpMatch { @@ -124,25 +123,46 @@ class _RegExpMatch implements RegExpMatch { RegExp get pattern => _regexp; String? namedGroup(String name) { - var idx = _regexp._groupNameIndex(name); - if (idx < 0) { - throw ArgumentError("Not a capture group name: ${name}"); + var exists = false; + var nameList = _regexp._groupNameList; + if (nameList != null) { + for (var i = 0; i < nameList.length; i += 2) { + var groupName = nameList[i] as String; + var groupIndex = nameList[i + 1] as int; + if (name == groupName) { + if (_start(groupIndex) >= 0) { + return group(groupIndex); + } + // Keeping looking for a duplicated name. + exists = true; + } + } } - return group(idx); + if (exists) { + // A group with that name exists, but did not match. + return null; + } + throw ArgumentError("Not a capture group name: ${name}"); } Iterable get groupNames { - return _regexp._groupNames; + var nameList = _regexp._groupNameList; + if (nameList != null) { + var names = new Set(); + for (var i = 0; i < nameList.length; i += 2) { + names.add(nameList[i] as String); + } + return names; + } + return const []; } final RegExp _regexp; final String input; - final List _match; + final Int32List _match; static const int _MATCH_PAIR = 2; } -const _initialBacktrackingStackSize = 128; - @pragma("vm:entry-point") class _RegExp implements RegExp { @pragma("vm:external-name", "RegExp_factory") @@ -219,110 +239,11 @@ class _RegExp implements RegExp { @pragma("vm:external-name", "RegExp_getGroupNameMap") external List? get _groupNameList; - Iterable get _groupNames sync* { - final nameList = _groupNameList; - if (nameList == null) return; - for (var i = 0; i < nameList.length; i += 2) { - yield nameList[i] as String; - } - } - - int _groupNameIndex(String name) { - var nameList = _groupNameList; - if (nameList == null) return -1; - for (var i = 0; i < nameList.length; i += 2) { - if (name == nameList[i]) { - return nameList[i + 1] as int; - } - } - return -1; - } - - // Byte map of one byte characters with a 0xff if the character is a word - // character (digit, letter or underscore) and 0x00 otherwise. - // Used by generated RegExp code. - static const List _wordCharacterMap = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // '0' - '7' - 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // '8' - '9' - - 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'A' - 'G' - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'H' - 'O' - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'P' - 'W' - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, // 'X' - 'Z', '_' - - 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'a' - 'g' - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'h' - 'o' - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 'p' - 'w' - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, // 'x' - 'z' - // Latin-1 range - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - - @pragma("vm:recognized", "asm-intrinsic") @pragma("vm:external-name", "RegExp_ExecuteMatch") - external List? _ExecuteMatch(String str, int start_index); + external Int32List? _ExecuteMatch(String str, int start_index); - @pragma("vm:recognized", "asm-intrinsic") @pragma("vm:external-name", "RegExp_ExecuteMatchSticky") - external List? _ExecuteMatchSticky(String str, int start_index); - - static Int32List _getRegisters(int registers_count) { - var registers = _registers.hasValue ? _registers.value : null; - if (registers == null || registers.length < registers_count) { - _registers.value = registers = Int32List(registers_count); - } - return registers; - } - - static Int32List _getBacktrackingStack() { - if (!_backtrackingStack.hasValue) { - _backtrackingStack.value = Int32List(_initialBacktrackingStackSize); - } - return _backtrackingStack.value; - } - - // TODO: Should we bound this to the same limit used by the irregexp interpreter - // for consistency? - static Int32List _growBacktrackingStack() { - final stack = _backtrackingStack.value; - final newStack = Int32List(stack.length * 2); - for (int i = 0; i < stack.length; i++) { - newStack[i] = stack[i]; - } - _backtrackingStack.value = newStack; - return newStack; - } - - @pragma("vm:shared") - static ThreadLocal _registers = ThreadLocal(); - - @pragma("vm:shared") - static ThreadLocal _backtrackingStack = ThreadLocal(); + external Int32List? _ExecuteMatchSticky(String str, int start_index); } class _AllMatchesIterable extends Iterable { diff --git a/tests/corelib/regexp/duplicate_named_capture_group_test.dart b/tests/corelib/regexp/duplicate_named_capture_group_test.dart new file mode 100644 index 00000000000..0a3b65ca83f --- /dev/null +++ b/tests/corelib/regexp/duplicate_named_capture_group_test.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "package:expect/expect.dart"; + +void main() { + var r = RegExp(r"0x(?[a-f0-9]+)|(?\d+)"); + + var m = r.firstMatch("0xabc")!; + Expect.equals(2, m.groupCount); + Expect.listEquals(["digits"], [...m.groupNames]); + Expect.equals("abc", m.namedGroup("digits")); + Expect.equals("abc", m[1]); + Expect.isNull(m[2]); + + m = r.firstMatch("123")!; + Expect.equals(2, m.groupCount); + Expect.listEquals(["digits"], [...m.groupNames]); + Expect.equals("123", m.namedGroup("digits")); + Expect.isNull(m[1]); + Expect.equals("123", m[2]); + + r = RegExp(r"(?A)|(?B)|(?C)|(?D)"); + m = r.firstMatch("D")!; + Expect.equals(4, m.groupCount); + Expect.listEquals(["unmatched", "matched"], [...m.groupNames]); + Expect.isNull(m.namedGroup("unmatched")); + Expect.equals("D", m.namedGroup("matched")); + Expect.isNull(m[1]); + Expect.isNull(m[2]); + Expect.isNull(m[3]); + Expect.equals("D", m[4]); +} diff --git a/tests/corelib/regexp/group_modifier_test.dart b/tests/corelib/regexp/group_modifier_test.dart new file mode 100644 index 00000000000..c346f36f141 --- /dev/null +++ b/tests/corelib/regexp/group_modifier_test.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "package:expect/expect.dart"; + +void main() { + var r = new RegExp(r"(?i:hello) world"); + Expect.isTrue(r.hasMatch("hello world")); + Expect.isTrue(r.hasMatch("HELLO world")); + Expect.isFalse(r.hasMatch("hello WORLD")); + Expect.isFalse(r.hasMatch("HELLO WORLD")); + + r = new RegExp(r"(?-i:hello) world", caseSensitive: false); + Expect.isTrue(r.hasMatch("hello world")); + Expect.isFalse(r.hasMatch("HELLO world")); + Expect.isTrue(r.hasMatch("hello WORLD")); + Expect.isFalse(r.hasMatch("HELLO WORLD")); +} diff --git a/tests/corelib/regexp/named_captures_2_test.dart b/tests/corelib/regexp/named_captures_2_test.dart index 40aa24e79b0..5bb1a9cd56a 100644 --- a/tests/corelib/regexp/named_captures_2_test.dart +++ b/tests/corelib/regexp/named_captures_2_test.dart @@ -28,18 +28,16 @@ import 'v8_regexp_utils.dart'; -// These test cases really belong in `named_captures_test` but they've been -// broken out because they currently fail on all web backends. void main() { - assertThrows(() => RegExp(r"(?<$𐒤>a)")); - assertThrows(() => RegExp("(?.)")); - assertThrows(() => RegExp(r"(?.)")); + RegExp(r"(?<$𐒤>a)"); + RegExp("(?.)"); + RegExp(r"(?.)"); assertThrows(() => RegExp("(?.)")); assertThrows(() => RegExp(r"(?.)")); assertThrows(() => RegExp("(?.)")); assertThrows(() => RegExp(r"(?.)")); - assertThrows(() => RegExp("(?.)")); - assertThrows(() => RegExp(r"(?.)")); + RegExp("(?.)"); + RegExp(r"(?.)"); assertThrows(() => RegExp("(?.)")); assertThrows(() => RegExp(r"(?.)")); assertThrows(() => RegExp(r"(?.)")); diff --git a/tests/standalone/regress_52691_test.dart b/tests/standalone/regress_52691_test.dart index fa83241312d..c45e7cf2f5f 100644 --- a/tests/standalone/regress_52691_test.dart +++ b/tests/standalone/regress_52691_test.dart @@ -5,7 +5,7 @@ void main() { var re = RegExp(r'[c-'); } on FormatException catch (e, s) { Expect.equals( - "FormatException: Unterminated character class [c-", + "FormatException: Unterminated character class\n[c-", e.toString(), ); }