[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 <lrn@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak
2026-02-23 09:54:47 -08:00
committed by Commit Queue
parent 5b44ac37e5
commit e443b89f23
110 changed files with 21314 additions and 15656 deletions
+7
View File
@@ -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
+1 -1
View File
@@ -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)
+3 -4
View File
@@ -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",
@@ -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,
+5
View File
@@ -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
+25 -29
View File
@@ -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
+2
View File
@@ -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;
+27
View File
@@ -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_
+4
View File
@@ -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 <typename T>
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) {
@@ -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'));
}
}
+45
View File
@@ -151,6 +151,51 @@ final Set<String> excludedFiles = Set<String>.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 = [
+24
View File
@@ -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 = [ ".." ]
}
+2 -2
View File
@@ -6188,7 +6188,7 @@ class RegExpSerializationCluster : public SerializationCluster {
WriteFromTo(regexp);
s->Write<int32_t>(regexp->untag()->num_one_byte_registers_);
s->Write<int32_t>(regexp->untag()->num_two_byte_registers_);
s->Write<int8_t>(regexp->untag()->type_flags_);
s->Write<uint32_t>(regexp->untag()->flags_);
}
}
@@ -6220,7 +6220,7 @@ class RegExpDeserializationCluster : public DeserializationCluster {
d.ReadFromTo(regexp);
regexp->untag()->num_one_byte_registers_ = d.Read<int32_t>();
regexp->untag()->num_two_byte_registers_ = d.Read<int32_t>();
regexp->untag()->type_flags_ = d.Read<int8_t>();
regexp->untag()->flags_ = d.Read<uint32_t>();
}
}
};
+1 -4
View File
@@ -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
+1 -1
View File
@@ -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_;
-2
View File
@@ -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"
-12
View File
@@ -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).
-4
View File
@@ -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,
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)) {
-6
View File
@@ -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);
-62
View File
@@ -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:
-22
View File
@@ -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) {
-22
View File
@@ -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) {
-25
View File
@@ -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) {
-34
View File
@@ -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) {
-22
View File
@@ -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;
@@ -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());
}
-66
View File
@@ -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<const ICData*>* 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<const ICData*>* 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.
@@ -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, \
File diff suppressed because it is too large Load Diff
-49
View File
@@ -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);
-1
View File
@@ -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, \
+25 -58
View File
@@ -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<std::memory_order_release>(array.ptr());
}
RegExpPtr RegExp::New(Zone* zone, Heap::Space space) {
const auto& result = RegExp::Handle(Object::Allocate<RegExp>(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<RegExp>(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) {
+44 -131
View File
@@ -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<OneByteStringPtr>(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<OneByteStringPtr>(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<TwoByteStringPtr>(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<int8_t, RegExType, 0, 2>;
// Must be kept in sync with RegExFlags::Flags.
using GlobalBit = BitField<int8_t, bool, TypeBits::kNextBit>;
using IgnoreCaseBit = BitField<int8_t, bool, GlobalBit::kNextBit>;
using MultiLineBit = BitField<int8_t, bool, IgnoreCaseBit::kNextBit>;
using UnicodeBit = BitField<int8_t, bool, MultiLineBit::kNextBit>;
using DotAllBit = BitField<int8_t, bool, UnicodeBit::kNextBit>;
// The portion of the bitfield container that contains all the above
// bool bits, which is passed to the constructor for RegExFlags.
using FlagsBits = BitField<int8_t,
int8_t,
TypeBits::kNextBit,
DotAllBit::kNextBit - TypeBits::kNextBit>;
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<intptr_t, std::memory_order_relaxed>(
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<std::memory_order_acquire>()
: untag()->two_byte_sticky<std::memory_order_acquire>());
return is_one_byte
? untag()->one_byte_sticky<std::memory_order_acquire>()
: untag()->two_byte_sticky<std::memory_order_acquire>();
} else {
return TypedData::RawCast(
is_one_byte ? untag()->one_byte<std::memory_order_acquire>()
: untag()->two_byte<std::memory_order_acquire>());
return is_one_byte ? untag()->one_byte<std::memory_order_acquire>()
: untag()->two_byte<std::memory_order_acquire>();
}
}
bool has_bytecode(bool is_one_byte, bool sticky) const {
if (sticky) {
if (is_one_byte) {
return Object::null() !=
untag()->one_byte_sticky<std::memory_order_relaxed>();
} else {
return Object::null() !=
untag()->two_byte_sticky<std::memory_order_relaxed>();
}
} else {
if (is_one_byte) {
return Object::null() != untag()->one_byte<std::memory_order_relaxed>();
} else {
return Object::null() != untag()->two_byte<std::memory_order_relaxed>();
}
}
}
@@ -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<GlobalBit>(true);
}
void set_is_ignore_case() const {
untag()->type_flags_.UpdateBool<IgnoreCaseBit>(true);
}
void set_is_multi_line() const {
untag()->type_flags_.UpdateBool<MultiLineBit>(true);
}
void set_is_unicode() const {
untag()->type_flags_.UpdateBool<UnicodeBit>(true);
}
void set_is_dot_all() const {
untag()->type_flags_.UpdateBool<DotAllBit>(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<intptr_t, intptr_t, std::memory_order_relaxed>(
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<FlagsBits>());
}
void set_flags(RegExpFlags flags) const {
untag()->type_flags_.Update<FlagsBits>(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<TypeBits>(type);
}
RegExType type() const { return untag()->type_flags_.Read<TypeBits>(); }
FINAL_HEAP_OBJECT_IMPLEMENTATION(RegExp, Instance);
friend class Class;
};
+13 -23
View File
@@ -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(
-14
View File
@@ -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);
-1
View File
@@ -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_; }
+6 -11
View File
@@ -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<int8_t> type_flags_;
// RegExpFlags
uint32_t flags_;
};
class UntaggedWeakProperty : public UntaggedInstance {
+29
View File
@@ -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> -> String&
- Handle<JSRegExp or RegExpData> -> RegExp&
- Handle<ByteArray> -> 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.
+214
View File
@@ -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 <cstring>
#include <limits>
#include <type_traits>
#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<int, double, float>), 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<unsigned>(c) <= 9) return c;
c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36.
if (static_cast<unsigned>(c) <= 5) return c + 10;
return -1;
}
template <typename D, typename S>
D saturated_cast(S in) {
if (in < std::numeric_limits<D>::min()) {
return std::numeric_limits<D>::min();
}
if (in > std::numeric_limits<D>::max()) {
return std::numeric_limits<D>::max();
}
return static_cast<D>(in);
}
// Checks if value is in range [lower_limit, higher_limit] using a single
// branch.
template <typename T, typename U>
requires((std::is_integral_v<T> || std::is_enum_v<T>) &&
(std::is_integral_v<U> || std::is_enum_v<U>)) &&
(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<T>;
// Use static_cast to support enum classes.
return static_cast<unsigned_T>(static_cast<unsigned_T>(value) -
static_cast<unsigned_T>(lower_limit)) <=
static_cast<unsigned_T>(static_cast<unsigned_T>(higher_limit) -
static_cast<unsigned_T>(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 <typename lchar, typename rchar>
inline bool CompareCharsEqualUnsigned(const lchar* lhs,
const rchar* rhs,
size_t chars) {
static_assert(std::is_unsigned_v<lchar>);
static_assert(std::is_unsigned_v<rchar>);
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 <typename lchar, typename rchar>
inline bool CompareCharsEqual(const lchar* lhs,
const rchar* rhs,
size_t chars) {
using ulchar = std::make_unsigned_t<lchar>;
using urchar = std::make_unsigned_t<rchar>;
return CompareCharsEqualUnsigned(reinterpret_cast<const ulchar*>(lhs),
reinterpret_cast<const urchar*>(rhs), chars);
}
} // namespace dart
#endif // RUNTIME_VM_REGEXP_BASE_H_
+187
View File
@@ -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 <typename Char>
requires(std::integral<Char> &&
std::numeric_limits<char>::max() <= std::numeric_limits<Char>::max())
inline constexpr bool IsAsciiLower(Char c) {
return base::IsInRange(c, 'a', 'z');
}
template <typename Char>
requires(std::integral<Char> &&
std::numeric_limits<char>::max() <= std::numeric_limits<Char>::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<uint8_t>(0x2028) || c == static_cast<uint8_t>(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<bool>(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<bool>(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<bool>(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<bool>(kOneByteCharFlags[c] & kIsWhiteSpaceOrLineTerminator));
return kOneByteCharFlags[c] & kIsWhiteSpaceOrLineTerminator;
}
bool IsLineTerminatorSequence(base::uc32 c, base::uc32 next) {
if (kOneByteCharFlags[static_cast<uint8_t>(c)] & kMaybeLineEnd) {
if (c == '\n') return true;
if (c == '\r') return next != '\n';
return base::IsInRange(static_cast<unsigned int>(c), 0x2028u, 0x2029u);
}
return false;
}
} // namespace dart
#endif // V8_STRINGS_CHAR_PREDICATES_INL_H_
+38
View File
@@ -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
+69
View File
@@ -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 <typename Char>
inline constexpr bool IsAsciiLower(Char ch);
template <typename Char>
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_
+138
View File
@@ -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 <cstddef>
#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 <typename EnumT,
typename BitfieldT = int,
typename BitfieldStorageT = BitfieldT>
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<mask_type>(flag)) {}
constexpr explicit Flags(mask_type mask)
: mask_(static_cast<mask_type>(mask)) {}
constexpr bool operator==(flag_type flag) const {
return mask_ == static_cast<mask_type>(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_
@@ -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 <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#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<UChar32>(kNonBmpStart); i++) {
if (i >= static_cast<UChar32>(kSurrogateStart) &&
i <= static_cast<UChar32>(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] << " <output filename>\n";
std::exit(1);
}
dart::WriteHeader(argv[1]);
return 0;
}
+107
View File
@@ -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_
+69
View File
@@ -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 <stdlib.h>
#include <atomic>
#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 <typename T>
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<T>) {
const size_t count = src_end - src_begin;
base::MemCopy(dest, src_begin, count * sizeof(T));
return true;
}
return false;
}
template <typename T>
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<T>) {
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 <typename T, typename U>
constexpr void Memset(T* destination, U value, size_t count)
requires std::is_trivially_assignable_v<T&, U>
{
for (size_t i = 0; i < count; i++) {
destination[i] = value;
}
}
// Fills `destination` with `count` `value`s.
template <typename T>
inline void Relaxed_Memset(T* destination, T value, size_t count)
requires std::is_integral_v<T>
{
for (size_t i = 0; i < count; i++) {
std::atomic_ref<T>(destination[i]).store(value, std::memory_order_relaxed);
}
}
} // namespace base
#endif // V8_BASE_MEMCOPY_H_
+269
View File
@@ -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 <algorithm>
#include <string>
#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<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* alternatives = this->alternatives();
for (int i = 0; i < alternatives->length(); i++) {
if (!alternatives->at(i)->IsAnchoredAtStart()) return false;
}
return true;
}
bool RegExpDisjunction::IsAnchoredAtEnd() {
ZoneList<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* 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<CharacterRange>* 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<RegExpTree*>* 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<CharacterRange>* ranges =
zone->template New<ZoneList<CharacterRange>>(0, zone);
RegExpClassSetOperand* op =
zone->template New<RegExpClassSetOperand>(ranges, nullptr);
ZoneList<RegExpTree*>* operands =
zone->template New<ZoneList<RegExpTree*>>(1, zone);
operands->Add(op, zone);
return zone->template New<RegExpClassSetExpression>(
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<CharacterRange>* 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
+785
View File
@@ -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 <algorithm>
#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<uint32_t>(from) <= static_cast<uint32_t>(to));
return CharacterRange(from, to);
}
static inline CharacterRange Everything() {
return CharacterRange(0, kMaxCodePoint);
}
static inline ZoneList<CharacterRange>* List(Zone* zone,
CharacterRange range) {
ZoneList<CharacterRange>* list =
zone->New<ZoneList<CharacterRange>>(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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* ranges);
// Negate the contents of a character range in canonical form.
static void Negate(const ZoneList<CharacterRange>* src,
ZoneList<CharacterRange>* dst,
Zone* zone);
// Intersect the contents of two character ranges in canonical form.
static void Intersect(const ZoneList<CharacterRange>* lhs,
const ZoneList<CharacterRange>* rhs,
ZoneList<CharacterRange>* dst,
Zone* zone);
// Subtract the contents of |to_remove| from the contents of |src|.
static void Subtract(const ZoneList<CharacterRange>* src,
const ZoneList<CharacterRange>* to_remove,
ZoneList<CharacterRange>* dst,
Zone* zone);
// Remove all ranges outside the one-byte range.
static void ClampToOneByte(ZoneList<CharacterRange>* ranges);
// Checks if two ranges (both need to be canonical) are equal.
static bool Equals(const ZoneList<CharacterRange>* lhs,
const ZoneList<CharacterRange>* 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<RegExpTree*>* 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<RegExpTree*>* alternatives() const { return alternatives_; }
private:
bool SortConsecutiveAtoms(RegExpCompiler* compiler);
void RationalizeConsecutiveAtoms(RegExpCompiler* compiler);
void FixSingleCharacterDisjunctions(RegExpCompiler* compiler);
ZoneList<RegExpTree*>* alternatives_;
int min_match_;
int max_match_;
};
class RegExpAlternative final : public RegExpTree {
public:
explicit RegExpAlternative(ZoneList<RegExpTree*>* 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<RegExpTree*>* nodes() const { return nodes_; }
private:
ZoneList<RegExpTree*>* 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<CharacterRange>* ranges) : ranges_(ranges) {}
ZoneList<CharacterRange>* 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<CharacterRange>* ranges_ = nullptr;
std::optional<StandardCharacterSet> 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<Flag>;
RegExpClassRanges(Zone* zone,
ZoneList<CharacterRange>* 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<CharacterRange>* 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<const base::uc32> lhs,
base::Vector<const base::uc32> 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<base::Vector<const uint32_t>,
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<CharacterRange>* 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<CharacterRange>* temp_ranges,
Zone* zone);
void Subtract(RegExpClassSetOperand* other,
ZoneList<CharacterRange>* temp_ranges,
Zone* zone);
bool has_strings() const { return strings_ != nullptr && !strings_->empty(); }
ZoneList<CharacterRange>* ranges() { return ranges_; }
CharacterClassStrings* strings() {
ASSERT(strings_ != nullptr);
return strings_;
}
private:
ZoneList<CharacterRange>* 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<RegExpTree*>* 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<RegExpTree*>* operands() const { return operands_; }
ZoneList<RegExpTree*>* 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<CharacterRange>* temp_ranges,
Zone* zone);
const OperationType operation_;
bool is_negated_;
const bool may_contain_strings_;
ZoneList<RegExpTree*>* operands_ = nullptr;
int max_match_;
};
class RegExpAtom final : public RegExpTree {
public:
explicit RegExpAtom(base::Vector<const uint16_t> 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<const uint16_t> data() const { return data_; }
int length() const { return data_.length(); }
private:
base::Vector<const uint16_t> 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<RegExpAtom*>(tree());
}
RegExpClassRanges* class_ranges() const {
ASSERT(text_type() == CLASS_RANGES);
return reinterpret_cast<RegExpClassRanges*>(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<TextElement>* elements() { return &elements_; }
bool StartsWithAtom() const;
RegExpAtom* FirstAtom() const;
private:
ZoneList<TextElement> 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<uint16_t>* name() const { return name_; }
void set_name(const ZoneVector<uint16_t>* 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<uint16_t>* 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<RegExpCapture*>* captures() const { return &captures_; }
void add_capture(RegExpCapture* capture, Zone* zone) {
captures_.Add(capture, zone);
}
const ZoneVector<uint16_t>* name() const { return name_; }
void set_name(const ZoneVector<uint16_t>* name) { name_ = name; }
private:
ZoneList<RegExpCapture*> captures_;
const ZoneVector<uint16_t>* 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_
@@ -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 <typename T>
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<T*>(buffer_.data() + new_pc_within_bc) = value;
#ifdef DEBUG
pc_within_bc_ = new_pc_within_bc + sizeof(T);
#endif
}
template <typename T>
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<T*>(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_
@@ -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 <limits>
#include <tuple>
#include <type_traits>
#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<RegExpBytecodeOperandType::kChar>::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 <RegExpBytecode bytecode, typename... Args>
void RegExpBytecodeWriter::Emit(Args... args) {
using Operands = RegExpBytecodeOperands<bytecode>;
static_assert(sizeof...(Args) == Operands::kCount,
"Wrong number of operands");
auto arguments_tuple = std::make_tuple(args...);
EmitBytecode(bytecode);
Operands::ForEachOperandWithIndex([&]<auto op, size_t index>() {
constexpr RegExpBytecodeOperandType type = Operands::Type(op);
constexpr int offset = Operands::Offset(op);
auto value = std::get<index>(arguments_tuple);
EmitOperand<type>(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 <typename T>
struct get_underlying_or_self {
using type = T;
};
template <typename T>
requires std::is_enum_v<T>
struct get_underlying_or_self<T> {
using type = std::underlying_type_t<T>;
};
} // namespace
template <RegExpBytecodeOperandType OperandType, typename T>
auto RegExpBytecodeWriter::GetCheckedBasicOperandValue(T value) {
static_assert(RegExpOperandTypeTraits<OperandType>::kIsBasic);
using Traits = RegExpOperandTypeTraits<OperandType>;
using EnumOrCType = Traits::kCType;
using CType = get_underlying_or_self<EnumOrCType>::type;
if constexpr (std::is_enum_v<EnumOrCType>) {
static_assert(std::is_same_v<T, EnumOrCType>);
} else {
static_assert(std::is_convertible_v<T, CType>);
}
DCHECK_GE(value, Traits::kMinValue);
DCHECK_LE(value, Traits::kMaxValue);
return static_cast<CType>(value);
}
template <RegExpBytecodeOperandType OperandType, typename T>
void RegExpBytecodeWriter::EmitOperand(T value, int offset) {
if constexpr (OperandType == RegExpBytecodeOperandType::kJumpTarget) {
jump_edges_.emplace(pc_ + offset, static_cast<int>(value));
}
Emit(GetCheckedBasicOperandValue<OperandType>(value), offset);
}
void RegExpBytecodeWriter::PatchJump(int target, int absolute_offset) {
ASSERT(jump_edges_.contains(absolute_offset));
OverwriteValue<uint32_t>(target, absolute_offset);
jump_edges_[absolute_offset] = target;
}
template <typename T>
void RegExpBytecodeWriter::EmitOperand(RegExpBytecodeOperandType type,
T value,
int offset) {
switch (type) {
#define CASE(Name, ...) \
case RegExpBytecodeOperandType::k##Name: \
return EmitOperand<ReBcOpType::k##Name>(value, offset);
BYTECODE_OPERAND_TYPE_LIST(CASE)
#undef CASE
default:
UNREACHABLE();
}
}
template <>
void RegExpBytecodeWriter::EmitOperand<ReBcOpType::kJumpTarget>(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<uint32_t>(pos, offset);
}
template <>
void RegExpBytecodeWriter::EmitOperand<ReBcOpType::kBitTable>(
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<ReBcOpType::kBitTable>(
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<const uint32_t*>(src);
static constexpr int kWordCount =
(RegExpMacroAssembler::kTableSize / (kBitsPerByte * kInt32Size));
for (int i = 0; i < kWordCount; i++) {
Emit(cursor[i], offset + i * kInt32Size);
}
}
template <RegExpBytecode bytecode, typename... Args>
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<decltype(arg), V8Label*>) {
V8Label* l = static_cast<V8Label*>(arg);
return l ? l : &backtrack_;
} else {
return arg;
}
};
RegExpBytecodeWriter::Emit<bytecode>(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<int32_t*>(buffer_.data() + fixup);
OverwriteValue<uint32_t>(pc_, fixup);
jump_edges().emplace(fixup, pc_);
}
}
l->bind_to(pc_);
}
void RegExpBytecodeGenerator::PopRegister(int register_index) {
Emit<RegExpBytecode::kPopRegister>(register_index);
}
void RegExpBytecodeGenerator::PushRegister(int register_index,
StackCheckFlag check_stack_limit) {
Emit<RegExpBytecode::kPushRegister>(register_index, check_stack_limit);
}
void RegExpBytecodeGenerator::WriteCurrentPositionToRegister(int register_index,
int cp_offset) {
Emit<RegExpBytecode::kWriteCurrentPositionToRegister>(register_index,
cp_offset);
}
void RegExpBytecodeGenerator::ClearRegisters(int reg_from, int reg_to) {
DCHECK_LE(reg_from, reg_to);
Emit<RegExpBytecode::kClearRegisters>(reg_from, reg_to);
}
void RegExpBytecodeGenerator::ReadCurrentPositionFromRegister(
int register_index) {
Emit<RegExpBytecode::kReadCurrentPositionFromRegister>(register_index);
}
void RegExpBytecodeGenerator::WriteStackPointerToRegister(int register_index) {
Emit<RegExpBytecode::kWriteStackPointerToRegister>(register_index);
}
void RegExpBytecodeGenerator::ReadStackPointerFromRegister(int register_index) {
Emit<RegExpBytecode::kReadStackPointerFromRegister>(register_index);
}
void RegExpBytecodeGenerator::SetCurrentPositionFromEnd(int by) {
Emit<RegExpBytecode::kSetCurrentPositionFromEnd>(by);
}
void RegExpBytecodeGenerator::SetRegister(int register_index, int to) {
Emit<RegExpBytecode::kSetRegister>(register_index, to);
}
void RegExpBytecodeGenerator::AdvanceRegister(int register_index, int by) {
Emit<RegExpBytecode::kAdvanceRegister>(register_index, by);
}
void RegExpBytecodeGenerator::PopCurrentPosition() {
Emit<RegExpBytecode::kPopCurrentPosition>();
}
void RegExpBytecodeGenerator::PushCurrentPosition() {
Emit<RegExpBytecode::kPushCurrentPosition>();
}
void RegExpBytecodeGenerator::Backtrack() {
int error_code = can_fallback() ? RegExpStatics::RE_FALLBACK_TO_EXPERIMENTAL
: RegExpStatics::RE_FAILURE;
Emit<RegExpBytecode::kBacktrack>(error_code);
}
void RegExpBytecodeGenerator::GoTo(V8Label* label) {
Emit<RegExpBytecode::kGoTo>(label);
}
void RegExpBytecodeGenerator::PushBacktrack(V8Label* label) {
Emit<RegExpBytecode::kPushBacktrack>(label);
}
bool RegExpBytecodeGenerator::Succeed() {
Emit<RegExpBytecode::kSucceed>();
return false; // Restart matching for global regexp not supported.
}
void RegExpBytecodeGenerator::Fail() {
Emit<RegExpBytecode::kFail>();
}
void RegExpBytecodeGenerator::AdvanceCurrentPosition(int by) {
Emit<RegExpBytecode::kAdvanceCurrentPosition>(by);
}
void RegExpBytecodeGenerator::CheckFixedLengthLoop(
V8Label* on_tos_equals_current_position) {
Emit<RegExpBytecode::kCheckFixedLengthLoop>(on_tos_equals_current_position);
}
void RegExpBytecodeGenerator::CheckPosition(int cp_offset,
V8Label* on_outside_input) {
Emit<RegExpBytecode::kCheckPosition>(cp_offset, on_outside_input);
}
void RegExpBytecodeGenerator::CheckSpecialClassRanges(StandardCharacterSet type,
V8Label* on_no_match) {
ASSERT(CanOptimizeSpecialClassRanges(type));
Emit<RegExpBytecode::kCheckSpecialClassRanges>(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<RegExpBytecode::kCheckPosition>(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<RegExpBytecode::kLoad4CurrentChars>(cp_offset, on_failure);
} else if (characters == 2) {
Emit<RegExpBytecode::kLoad2CurrentChars>(cp_offset, on_failure);
} else {
DCHECK_EQ(1, characters);
Emit<RegExpBytecode::kLoadCurrentCharacter>(cp_offset, on_failure);
}
} else {
if (characters == 4) {
Emit<RegExpBytecode::kLoad4CurrentCharsUnchecked>(cp_offset);
} else if (characters == 2) {
Emit<RegExpBytecode::kLoad2CurrentCharsUnchecked>(cp_offset);
} else {
DCHECK_EQ(1, characters);
Emit<RegExpBytecode::kLoadCurrentCharacterUnchecked>(cp_offset);
}
}
}
void RegExpBytecodeGenerator::CheckCharacterLT(uint16_t limit,
V8Label* on_less) {
Emit<RegExpBytecode::kCheckCharacterLT>(limit, on_less);
}
void RegExpBytecodeGenerator::CheckCharacterGT(uint16_t limit,
V8Label* on_greater) {
Emit<RegExpBytecode::kCheckCharacterGT>(limit, on_greater);
}
void RegExpBytecodeGenerator::CheckCharacter(uint32_t c, V8Label* on_equal) {
if (c > kMaxSingleCharValue) {
Emit<RegExpBytecode::kCheck4Chars>(c, on_equal);
} else {
Emit<RegExpBytecode::kCheckCharacter>(c, on_equal);
}
}
void RegExpBytecodeGenerator::CheckAtStart(int cp_offset,
V8Label* on_at_start) {
Emit<RegExpBytecode::kCheckAtStart>(cp_offset, on_at_start);
}
void RegExpBytecodeGenerator::CheckNotAtStart(int cp_offset,
V8Label* on_not_at_start) {
Emit<RegExpBytecode::kCheckNotAtStart>(cp_offset, on_not_at_start);
}
void RegExpBytecodeGenerator::CheckNotCharacter(uint32_t c,
V8Label* on_not_equal) {
if (c > kMaxSingleCharValue) {
Emit<RegExpBytecode::kCheckNot4Chars>(c, on_not_equal);
} else {
Emit<RegExpBytecode::kCheckNotCharacter>(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<RegExpBytecode::kAndCheck4Chars>(c, mask, on_equal);
} else {
Emit<RegExpBytecode::kCheckCharacterAfterAnd>(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<RegExpBytecode::kAndCheckNot4Chars>(c, mask, on_not_equal);
} else {
Emit<RegExpBytecode::kCheckNotCharacterAfterAnd>(c, mask, on_not_equal);
}
}
void RegExpBytecodeGenerator::CheckNotCharacterAfterMinusAnd(
uint16_t c,
uint16_t minus,
uint16_t mask,
V8Label* on_not_equal) {
Emit<RegExpBytecode::kCheckNotCharacterAfterMinusAnd>(c, minus, mask,
on_not_equal);
}
void RegExpBytecodeGenerator::CheckCharacterInRange(uint16_t from,
uint16_t to,
V8Label* on_in_range) {
Emit<RegExpBytecode::kCheckCharacterInRange>(from, to, on_in_range);
}
void RegExpBytecodeGenerator::CheckCharacterNotInRange(
uint16_t from,
uint16_t to,
V8Label* on_not_in_range) {
Emit<RegExpBytecode::kCheckCharacterNotInRange>(from, to, on_not_in_range);
}
void RegExpBytecodeGenerator::CheckBitInTable(const TypedData& table,
V8Label* on_bit_set) {
Emit<RegExpBytecode::kCheckBitInTable>(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<RegExpBytecode::kSkipUntilBitInTable>(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<RegExpBytecode::kSkipUntilCharAnd>(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<RegExpBytecode::kCheckNotBackRefBackward>(start_reg, on_not_equal);
} else {
Emit<RegExpBytecode::kCheckNotBackRef>(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<RegExpBytecode::kCheckNotBackRefNoCaseUnicodeBackward>(start_reg,
on_not_equal);
} else {
Emit<RegExpBytecode::kCheckNotBackRefNoCaseBackward>(start_reg,
on_not_equal);
}
} else {
if (unicode) {
Emit<RegExpBytecode::kCheckNotBackRefNoCaseUnicode>(start_reg,
on_not_equal);
} else {
Emit<RegExpBytecode::kCheckNotBackRefNoCase>(start_reg, on_not_equal);
}
}
}
void RegExpBytecodeGenerator::IfRegisterLT(int register_index,
int comparand,
V8Label* on_less_than) {
Emit<RegExpBytecode::kIfRegisterLT>(register_index, comparand, on_less_than);
}
void RegExpBytecodeGenerator::IfRegisterGE(int register_index,
int comparand,
V8Label* on_greater_or_equal) {
Emit<RegExpBytecode::kIfRegisterGE>(register_index, comparand,
on_greater_or_equal);
}
void RegExpBytecodeGenerator::IfRegisterEqPos(int register_index,
V8Label* on_equal) {
Emit<RegExpBytecode::kIfRegisterEqPos>(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<RegExpBytecodeOperandType::k##Name>( \
RegExpOperandTypeTraits<RegExpBytecodeOperandType::k##Name>::kCType, \
int);
BASIC_BYTECODE_OPERAND_TYPE_LIST(CASE)
BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(CASE)
#undef CASE
} // namespace dart
@@ -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 <typename T>
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<uint8_t>& buffer() { return buffer_; }
const ZoneVector<uint8_t>& buffer() const { return buffer_; }
// Code and bitmap emission.
template <typename T>
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 <RegExpBytecode bytecode, typename... Args>
void Emit(Args... args);
template <RegExpBytecodeOperandType OperandType, typename T>
void EmitOperand(T value, int offset);
template <RegExpBytecodeOperandType OperandType, typename T>
auto GetCheckedBasicOperandValue(T value);
// Runtime versions.
template <typename T>
void EmitOperand(RegExpBytecodeOperandType type, T value, int offset);
int length() const { return pc_; }
void CopyBufferTo(uint8_t* a) const;
ZoneMap<int, int>& jump_edges() { return jump_edges_; }
const ZoneMap<int, int>& 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<uint8_t> 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<int, int> 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<CharacterRange>* 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<CharacterRange>* 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 <RegExpBytecode bytecode, typename... Args>
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_
+366
View File
@@ -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 <array>
#include <limits>
#include <string_view>
#include <type_traits>
#include "vm/regexp/regexp-macro-assembler.h" // For StackCheckFlag
namespace dart {
template <RegExpBytecodeOperandType>
struct RegExpOperandTypeTraits;
#define DECLARE_BASIC_OPERAND_TYPE_TRAITS(Name, CType) \
template <> \
struct RegExpOperandTypeTraits<RegExpBytecodeOperandType::k##Name> { \
static_assert(!std::is_pointer_v<CType>); \
static constexpr uint8_t kSize = sizeof(CType); \
using kCType = CType; \
static constexpr bool kIsBasic = true; \
static constexpr kCType kMinValue = std::numeric_limits<kCType>::min(); \
static constexpr kCType kMaxValue = std::numeric_limits<kCType>::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<RegExpBytecodeOperandType::k##Name> { \
static_assert(!std::is_pointer_v<CType>); \
static constexpr uint8_t kSize = sizeof(CType); \
using kCType = CType; \
static constexpr bool kIsBasic = true; \
static_assert(std::is_enum_v<kCType> || \
MinValue >= std::numeric_limits<kCType>::min()); \
static_assert(std::is_enum_v<kCType> || \
MaxValue <= std::numeric_limits<kCType>::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<RegExpBytecodeOperandType::k##Name> { \
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 <auto... Args>
constexpr int CountOf() {
return sizeof...(Args);
}
template <size_t N>
consteval std::array<std::string_view, N> SplitNames(const char* raw_names) {
std::array<std::string_view, N> 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 <RegExpBytecodeOperandType... operand_types>
consteval auto CalculateAlignedOffsets() {
constexpr int N = sizeof...(operand_types);
constexpr std::array<uint8_t, N> kOperandSizes = {
RegExpOperandTypeTraits<operand_types>::kSize...};
constexpr std::array<uint8_t, N> kOperandAlignments = {
RegExpOperandTypeTraits<operand_types>::kAlignment...};
std::array<int, N> 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 <RegExpBytecodeOperandType... ops>
struct RegExpBytecodeOperandsTraits {
static constexpr int kOperandCount = sizeof...(ops);
static constexpr std::array<RegExpBytecodeOperandType, kOperandCount>
kOperandTypes = {ops...};
static constexpr std::array<uint8_t, kOperandCount> kOperandSizes = {
RegExpOperandTypeTraits<ops>::kSize...};
static constexpr std::array<uint8_t, kOperandCount> kOperandAlignments = {
RegExpOperandTypeTraits<ops>::kAlignment...};
static constexpr std::array<int, kOperandCount> kOperandOffsets =
CalculateAlignedOffsets<ops...>();
static constexpr int kSize = Utils::RoundUp(
kOperandCount == 0 ? sizeof(RegExpBytecode)
: kOperandOffsets.back() + kOperandSizes.back(),
kBytecodeAlignment);
};
template <RegExpBytecode bc>
struct RegExpBytecodeOperandNames;
#define DECLARE_OPERAND_NAMES(CamelName, OpNames, OpTypes) \
template <> \
struct RegExpBytecodeOperandNames<RegExpBytecode::k##CamelName> { \
enum class Operand { UNPAREN(OpNames) }; \
using enum Operand; \
static constexpr size_t kCount = detail::CountOf<UNPAREN(OpNames)>(); \
static constexpr auto kNames = detail::SplitNames<kCount>(#OpNames); \
static /*constexpr*/ std::string_view Name(Operand op) { \
return kNames[static_cast<size_t>(op)]; \
} \
};
REGEXP_BYTECODE_LIST(DECLARE_OPERAND_NAMES)
#undef DECLARE_OPERAND_NAMES
template <RegExpBytecode bc, RegExpBytecodeOperandType... OpTypes>
class RegExpBytecodeOperandsBase {
public:
static constexpr RegExpBytecode kBytecode = bc;
using Operand = RegExpBytecodeOperandNames<bc>::Operand;
using Traits = RegExpBytecodeOperandsTraits<OpTypes...>;
static constexpr int kCount = Traits::kOperandCount;
static constexpr int kTotalSize = Traits::kSize;
static constexpr int Index(Operand op) { return static_cast<uint8_t>(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<bc>::Name(op);
}
// Returns a tuple of all operands.
static consteval auto GetOperandsTuple() {
return []<size_t... Is>(std::index_sequence<Is...>) {
return std::tuple_cat([]<size_t I>() {
constexpr auto id = static_cast<Operand>(I);
return std::tuple(std::integral_constant<Operand, id>{});
}.template operator()<Is>()...);
}(std::make_index_sequence<kCount>{});
}
// Calls |f| templatized by Operand for each Operand in the Operands list.
// Example:
// using Operands = RegExpBytecodeOperands<RegExpBytecode::...>;
// size_t op_sizes = 0;
// Operands::ForEachOperand([]<auto op>() {
// 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 <typename Func>
static constexpr void ForEachOperand(Func&& f) {
constexpr auto filtered_ops = GetOperandsTuple();
std::apply([&](auto... ops) { (..., f.template operator()<ops.value>()); },
filtered_ops);
}
// Similar to ForEachOperand, but additionally provides the current index as
// a template argument. The index is a sequential index of operands.
template <typename Func>
static constexpr void ForEachOperandWithIndex(Func&& f) {
constexpr auto filtered_ops = GetOperandsTuple();
[&]<size_t... I>(std::index_sequence<I...>) {
(...,
f.template operator()<
std::tuple_element_t<I, decltype(filtered_ops)>::value /* Operand */,
I /* Index */>());
}(std::make_index_sequence<std::tuple_size_v<decltype(filtered_ops)>>{});
}
// Similar to above, but calls |f| only for operands of a given type.
template <RegExpBytecodeOperandType OpType, typename Func>
static constexpr void ForEachOperandOfType(Func&& f) {
ForEachOperand([&]<auto operand>() {
if constexpr (Type(operand) == OpType) {
f.template operator()<operand>();
}
});
}
public:
template <Operand op>
requires(RegExpOperandTypeTraits<Type(op)>::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<OperandType>::kCType;
ASSERT(Utils::IsAligned(offset, sizeof(CType)));
return *reinterpret_cast<const CType*>(pc + offset);
}
template <Operand op>
requires(RegExpOperandTypeTraits<Type(op)>::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<op>(bytecode->begin() + offset);
return Get<op>((uint8_t*)bytecode.DataAddr(offset), no_gc);
}
template <Operand op>
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 <Operand op>
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<uint8_t>(start, end, zone);
}
};
} // namespace detail
#define PACK_OPTIONAL(x, ...) x __VA_OPT__(, ) __VA_ARGS__
#define DECLARE_OPERANDS(CamelName, OpNames, OpTypes) \
template <> \
class RegExpBytecodeOperands<RegExpBytecode::k##CamelName> final \
: public detail::RegExpBytecodeOperandsBase<PACK_OPTIONAL( \
RegExpBytecode::k##CamelName, UNPAREN(OpTypes))>, \
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<RegExpBytecode::k##CamelName>::kTotalSize,
static constexpr uint8_t kBytecodeSizes[] = {
REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_SIZES)};
#undef DECLARE_BYTECODE_SIZES
#define DECLARE_OPERAND_TYPE_SIZE(Name, ...) \
RegExpOperandTypeTraits<RegExpBytecodeOperandType::k##Name>::kSize,
static constexpr uint8_t kOperandTypeSizes[] = {
BYTECODE_OPERAND_TYPE_LIST(DECLARE_OPERAND_TYPE_SIZE)};
#undef DECLARE_OPERAND_TYPE_SIZE
} // namespace detail
// static
template <typename Func>
decltype(auto) RegExpBytecodes::DispatchOnBytecode(RegExpBytecode bytecode,
Func&& f) {
switch (bytecode) {
#define CASE(CamelName, ...) \
case RegExpBytecode::k##CamelName: \
return f.template operator()<RegExpBytecode::k##CamelName>();
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<int>(type)];
}
} // namespace dart
#endif // V8_REGEXP_REGEXP_BYTECODES_INL_H_
+339
View File
@@ -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.: */ \
/* /<script|<style|<link/i. */ \
V(SkipUntilOneOfMasked3, \
(bc0_cp_offset, bc0_advance_by, bc0_table, bc1_cp_offset, bc1_on_failure, \
bc2_cp_offset, bc3_characters, bc3_mask, bc4_by, bc5_cp_offset, \
bc6_characters, bc6_mask, bc6_on_equal, bc7_characters, bc7_mask, \
bc7_on_equal, bc8_characters, bc8_mask, fallthrough_jump_target), \
(ReBcOpType::kOffset, ReBcOpType::kOffset, ReBcOpType::kBitTable, \
ReBcOpType::kOffset, ReBcOpType::kJumpTarget, ReBcOpType::kOffset, \
ReBcOpType::kUint32, ReBcOpType::kUint32, ReBcOpType::kOffset, \
ReBcOpType::kOffset, ReBcOpType::kUint32, ReBcOpType::kUint32, \
ReBcOpType::kJumpTarget, ReBcOpType::kUint32, ReBcOpType::kUint32, \
ReBcOpType::kJumpTarget, ReBcOpType::kUint32, ReBcOpType::kUint32, \
ReBcOpType::kJumpTarget))
#define REGEXP_BYTECODE_LIST(V) \
INVALID_BYTECODE_LIST(V) \
BASIC_BYTECODE_LIST(V) \
SPECIAL_BYTECODE_LIST(V) \
PEEPHOLE_BYTECODE_LIST(V)
enum class RegExpBytecode : uint8_t {
#define DECLARE_BYTECODE(CamelName, ...) k##CamelName,
REGEXP_BYTECODE_LIST(DECLARE_BYTECODE)
#undef DECLARE_BYTECODE
#define COUNT_BYTECODE(x, ...) +1
// The COUNT_BYTECODE macro will turn this into kLast = -1 +1 +1... which will
// evaluate to the same value as the last real bytecode.
kLast = -1 REGEXP_BYTECODE_LIST(COUNT_BYTECODE)
};
// Bytecode is 4-byte aligned.
// We can pack operands if multiple operands fit into 4 bytes.
static constexpr int kBytecodeAlignment = 4;
template <RegExpBytecode bc>
class RegExpBytecodeOperands;
class RegExpBytecodes final : public AllStatic {
public:
static constexpr int kCount = static_cast<uint8_t>(RegExpBytecode::kLast) + 1;
static constexpr uint8_t ToByte(RegExpBytecode bc) {
return static_cast<uint8_t>(bc);
}
static constexpr RegExpBytecode FromByte(uint8_t byte) {
ASSERT(IsValid(byte));
return static_cast<RegExpBytecode>(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<uword>(ptr), kBytecodeAlignment));
}
return FromByte(*static_cast<const uint8_t*>(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 = <runtime value>;
// DispatchOnBytecode(bc, []<RegExpBytecode bc>() { DoFancyStuff<bc>(); });
template <typename Func>
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_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+713
View File
@@ -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 <bitset>
#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<ContainedInLattice>(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<kMapSize>;
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<BoyerMoorePositionInfo*>* 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<RegExpDiagnostics> 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<RegExpNode*>* 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<RegExpDiagnostics> diagnostics_;
#endif
Isolate* isolate_;
Zone* zone_;
};
// Categorizes character ranges into BMP, non-BMP, lead, and trail surrogates.
class UnicodeRangeSplitter {
public:
UnicodeRangeSplitter(ZoneList<CharacterRange>* base);
static constexpr int kInitialSize = 8;
using CharacterRangeVector = base::SmallVector<CharacterRange, kInitialSize>;
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_
+22
View File
@@ -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<int>(error)];
}
} // namespace dart
+65
View File
@@ -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_
+84
View File
@@ -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 <optional>
#include <ostream>
#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<RegExpFlag>;
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<RegExpFlag> TryRegExpFlagFromChar(char c) {
return REGEXP_FLAG_LIST(V) std::optional<RegExpFlag>{};
}
#undef V
// clang-format on
std::ostream& operator<<(std::ostream& os, RegExpFlags flags);
} // namespace dart
#endif // V8_REGEXP_REGEXP_FLAGS_H_
File diff suppressed because it is too large Load Diff
+83
View File
@@ -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_
+585
View File
@@ -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 <limits>
#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<uint16_t*>(byte_offset1);
uint16_t* substring2 = reinterpret_cast<uint16_t*>(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<int32_t>(byte_length >> 1);
icu::UnicodeString uni_str_1(reinterpret_cast<const char16_t*>(byte_offset1),
length);
return uni_str_1.caseCompare(reinterpret_cast<const char16_t*>(byte_offset2),
length, U_FOLD_CASE_DEFAULT) == 0;
#else
uint16_t* substring1 = reinterpret_cast<uint16_t*>(byte_offset1);
uint16_t* substring2 = reinterpret_cast<uint16_t*>(byte_offset2);
size_t length = byte_length >> 1;
DCHECK_NOT_NULL(isolate);
unibrow::Mapping<unibrow::Ecma262Canonicalize>* 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<CharacterRange>* 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<uint32_t>(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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<uint16_t>::min(),
std::numeric_limits<uint16_t>::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<IrRegExpData> regexp_data,
const String\DirectHandle<String> 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<String> 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<ConsString>(subject_ptr)->second()->length());
subject_ptr = Cast<ConsString>(subject_ptr)->first();
} else if (StringShape(subject_ptr).IsSliced()) {
Tagged<SlicedString> slice = Cast<SlicedString>(subject_ptr);
subject_ptr = slice->parent();
slice_offset = slice->offset();
}
if (StringShape(subject_ptr).IsThin()) {
subject_ptr = Cast<ThinString>(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<String> input,
int start_offset,
const uint8_t* input_start,
const uint8_t* input_end,
int* output,
int output_size,
Isolate* isolate,
Tagged<JSRegExp> regexp) {
Tagged<RegExpData> data = regexp->data(isolate);
return Execute(input, start_offset, input_start, input_end, output,
output_size, isolate, SbxCast<IrRegExpData>(data));
}
// Returns a {Result} sentinel, or the number of successful matches.
int NativeRegExpMacroAssembler::Execute(
Tagged<String>
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<IrRegExpData> regexp_data) {
bool is_one_byte = String::IsOneByteRepresentationUnderneath(input);
Tagged<Code> 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<RegexpMatcherSig>::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
+463
View File
@@ -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 <string_view>
#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<CharacterRange>* ranges,
V8Label* on_in_range) = 0;
virtual bool CheckCharacterNotInRangeArray(
const ZoneList<CharacterRange>* 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<const uint8_t> 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<int>(Mode::LATIN1) == sizeof(uint8_t));
static_assert(static_cast<int>(Mode::UC16) == sizeof(uint16_t));
return static_cast<int>(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<CharacterRange>* 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<uint32_t, TypedData*> range_array_cache_;
};
} // namespace dart
#endif // V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_
+906
View File
@@ -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 <algorithm>
#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 <typename T>
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<uint8_t>(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<RegExpNode>;
friend Zone;
};
class TextNode : public SeqRegExpNode {
public:
TextNode(ZoneList<TextElement>* 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<ZoneList<TextElement>>(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<CharacterRange>* 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<CharacterRange>* trail_ranges,
bool read_backward,
RegExpNode* on_success);
static TextNode* CreateForSurrogatePair(Zone* zone,
ZoneList<CharacterRange>* 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<TextElement>* 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<TextElement>* 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<AssertionNode>(AT_END, on_success);
}
static AssertionNode* AtStart(RegExpNode* on_success) {
return on_success->zone()->New<AssertionNode>(AT_START, on_success);
}
static AssertionNode* AtBoundary(RegExpNode* on_success) {
return on_success->zone()->New<AssertionNode>(AT_BOUNDARY, on_success);
}
static AssertionNode* AtNonBoundary(RegExpNode* on_success) {
return on_success->zone()->New<AssertionNode>(AT_NON_BOUNDARY, on_success);
}
static AssertionNode* AfterNewline(RegExpNode* on_success) {
return on_success->zone()->New<AssertionNode>(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<RegExpNode>;
};
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<Guard*>* guards() const { return guards_; }
private:
RegExpNode* node_;
// TODO(pthier): There are currently no uses of multiple guards. Consider
// removing the ZoneList.
ZoneList<Guard*>* guards_;
};
class AlternativeGeneration;
class ChoiceNode : public RegExpNode {
public:
explicit ChoiceNode(int expected_size, Zone* zone)
: RegExpNode(zone),
alternatives_(
zone->New<ZoneList<GuardedAlternative>>(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<GuardedAlternative>* 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<GuardedAlternative>* alternatives_;
private:
template <typename...>
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_
File diff suppressed because it is too large Load Diff
+39
View File
@@ -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 <class CharT>
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_
+458 -5510
View File
File diff suppressed because it is too large Load Diff
+174 -1500
View File
File diff suppressed because it is too large Load Diff
-129
View File
@@ -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<StringPtr>(str_raw));
const Smi& lhs_index = Smi::Handle(static_cast<SmiPtr>(lhs_index_raw));
const Smi& rhs_index = Smi::Handle(static_cast<SmiPtr>(rhs_index_raw));
const Smi& length = Smi::Handle(static_cast<SmiPtr>(length_raw));
// TODO(zerny): Optimize as single instance. V8 has this as an
// isolate member.
unibrow::Mapping<unibrow::Ecma262Canonicalize> 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<uword>(Bool::False().ptr());
}
}
}
}
return static_cast<uword>(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<StringPtr>(str_raw));
const Smi& lhs_index = Smi::Handle(static_cast<SmiPtr>(lhs_index_raw));
const Smi& rhs_index = Smi::Handle(static_cast<SmiPtr>(rhs_index_raw));
const Smi& length = Smi::Handle(static_cast<SmiPtr>(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<uword>(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<uword>(Bool::False().ptr());
}
return static_cast<uword>(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
-269
View File
@@ -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_
@@ -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<uint8_t>* 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<int32_t*>(buffer_->data() + fixup);
*reinterpret_cast<uint32_t*>(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<int32_t>(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
@@ -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<uint8_t>* 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<HeapObject> GetCode(Handle<String> 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<uint8_t>* 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_
@@ -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<uint32_t*>(buffer_->data() + pc_) = word;
pc_ += 4;
}
void BytecodeRegExpMacroAssembler::Emit16(uint32_t word) {
ASSERT(pc_ <= buffer_->length());
if (pc_ + 1 >= buffer_->length()) {
Expand();
}
*reinterpret_cast<uint16_t*>(buffer_->data() + pc_) = word;
pc_ += 2;
}
void BytecodeRegExpMacroAssembler::Emit8(uint32_t word) {
ASSERT(pc_ <= buffer_->length());
if (pc_ == buffer_->length()) {
Expand();
}
*reinterpret_cast<unsigned char*>(buffer_->data() + pc_) = word;
pc_ += 1;
}
void BytecodeRegExpMacroAssembler::Emit32(uint32_t word) {
ASSERT(pc_ <= buffer_->length());
if (pc_ + 3 >= buffer_->length()) {
Expand();
}
*reinterpret_cast<uint32_t*>(buffer_->data() + pc_) = word;
pc_ += 4;
}
} // namespace dart
#endif // RUNTIME_VM_REGEXP_REGEXP_ASSEMBLER_BYTECODE_INL_H_
File diff suppressed because it is too large Load Diff
-447
View File
@@ -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<const ICData*>& 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<int>(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<const ICData*>& ic_data_array_;
// All created blocks are contained within this set. Used for printing
// the generated code.
GrowableArray<BlockEntryInstr*> 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_
-313
View File
@@ -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<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* 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<RegExpTree*>* 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<uint16_t>* 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<RegExpTree*>* 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<RegExpTree*>* 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
-448
View File
@@ -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<RegExpTree*>* 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<RegExpTree*>* alternatives() const { return alternatives_; }
private:
ZoneGrowableArray<RegExpTree*>* alternatives_;
intptr_t min_match_;
intptr_t max_match_;
};
class RegExpAlternative : public RegExpTree {
public:
explicit RegExpAlternative(ZoneGrowableArray<RegExpTree*>* 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<RegExpTree*>* nodes() const { return nodes_; }
private:
ZoneGrowableArray<RegExpTree*>* 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<CharacterRange>* ranges)
: ranges_(ranges), standard_set_type_(0) {}
CharacterSet(const CharacterSet& that)
: ValueObject(),
ranges_(that.ranges_),
standard_set_type_(that.standard_set_type_) {}
ZoneGrowableArray<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<CharacterRange>* 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<uint16_t>* 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<uint16_t>* 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<uint16_t>* 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<TextElement>* elements() { return &elements_; }
private:
GrowableArray<TextElement> 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<uint16_t>* name() { return name_; }
void set_name(const ZoneGrowableArray<uint16_t>* 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<uint16_t>* 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<uint16_t>* name() { return name_; }
void set_name(const ZoneGrowableArray<uint16_t>* name) { name_ = name; }
private:
RegExpCapture* capture_;
const ZoneGrowableArray<uint16_t>* 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_
-86
View File
@@ -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_
-713
View File
@@ -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 <memory>
#include <utility>
#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<unibrow::Ecma262Canonicalize> Canonicalize;
template <typename Char>
static bool BackRefMatchesNoCase(Canonicalize* interp_canonicalize,
intptr_t from,
intptr_t current,
intptr_t len,
const String& subject,
bool unicode);
template <>
bool BackRefMatchesNoCase<uint16_t>(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<BoolPtr>(DLRT_CaseInsensitiveCompareUTF16(
static_cast<uword>(subject.ptr()), static_cast<uword>(Smi::New(from)),
static_cast<uword>(Smi::New(current)),
static_cast<uword>(Smi::New(len))));
} else {
ret = static_cast<BoolPtr>(DLRT_CaseInsensitiveCompareUCS2(
static_cast<uword>(subject.ptr()), static_cast<uword>(Smi::New(from)),
static_cast<uword>(Smi::New(current)),
static_cast<uword>(Smi::New(len))));
}
return ret.value();
}
template <>
bool BackRefMatchesNoCase<uint8_t>(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<int>(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<intptr_t>(pc) & 3) == 0);
return *reinterpret_cast<const int32_t*>(pc);
}
static int32_t Load16Aligned(const uint8_t* pc) {
ASSERT((reinterpret_cast<intptr_t>(pc) & 1) == 0);
return *reinterpret_cast<const uint16_t*>(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>(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<int32_t*>(memory_->address());
}
intptr_t max_size() const { return memory_->size() / sizeof(int32_t); }
private:
std::unique_ptr<VirtualMemory> 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 <typename Char>
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<unibrow::Ecma262Canonicalize> 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<uint8_t*>(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<uint8_t*>(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<int>(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<int>(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<Char>(&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<Char>(&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<uint32_t>(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<uint8_t>(bytecode, subject, registers, start_position,
previous_char);
} else if (subject.IsTwoByteString()) {
return RawMatch<uint16_t>(bytecode, subject, registers, start_position,
previous_char);
} else {
UNREACHABLE();
return Bool::False().ptr();
}
}
} // namespace dart
-29
View File
@@ -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_
File diff suppressed because it is too large Load Diff
-267
View File
@@ -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<uint16_t>* characters_;
uint16_t pending_surrogate_;
GrowableArray<RegExpTree*> terms_;
GrowableArray<RegExpTree*> text_;
GrowableArray<RegExpTree*> 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<uint16_t>;
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<char>* name_1,
ZoneGrowableArray<char>* name_2);
// Adds the specified unicode property to the provided character range.
bool AddPropertyClassRange(ZoneGrowableArray<CharacterRange>* add_to,
bool negate,
ZoneGrowableArray<char>* name_1,
ZoneGrowableArray<char>* name_2);
// Returns a regexp node that corresponds to one of these unicode
// property sequences: "Any", "ASCII", "Assigned".
RegExpTree* GetPropertySequence(ZoneGrowableArray<char>* 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<CharacterRange>* 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 (?<name>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<RegExpCapture*>* captures_;
ZoneGrowableArray<RegExpCapture*>* named_captures_;
ZoneGrowableArray<RegExpBackReference*>* 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_
+34 -14
View File
@@ -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" ]
+13 -31
View File
@@ -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
+387
View File
@@ -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 <algorithm>
#include <memory>
#include <type_traits>
#include <utility>
#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 <typename T, size_t kSize, typename Allocator = std::allocator<T>>
class SmallVector {
// TODO(mliedtke): Remove kHasTrivialElement and replace usages with the
// proper conditions.
static constexpr bool kHasTrivialElement =
std::is_trivially_copyable<T>::value &&
std::is_trivially_destructible<T>::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<T>
: 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<T> 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<const T> 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<ptrdiff_t>(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<ptrdiff_t>(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 <typename... Args>
void emplace_back(Args&&... args) {
if (V8_UNLIKELY(end_ == end_of_storage_)) Grow();
void* storage = end_;
end_ += 1;
new (storage) T(std::forward<Args>(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<size_t>(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 <typename It>
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<const T> values) {
return insert(pos, values.begin(), values.end());
}
template <typename Container>
requires requires(const Container& v) {
std::is_same_v<decltype(std::begin(v)), decltype(std::end(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<T>
{
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<T*>(inline_storage_); }
const T* inline_storage_begin() const {
return reinterpret_cast<const T*>(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_
+90
View File
@@ -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
+117
View File
@@ -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 "kK" (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_
+5 -5
View File
@@ -11,8 +11,8 @@
namespace unibrow {
template <class T, intptr_t s>
intptr_t Mapping<T, s>::get(int32_t c, int32_t n, int32_t* result) {
template <class T, int s>
int Mapping<T, s>::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<T, s>::get(int32_t c, int32_t n, int32_t* result) {
}
}
template <class T, intptr_t s>
intptr_t Mapping<T, s>::CalculateValue(int32_t c, int32_t n, int32_t* result) {
template <class T, int s>
int Mapping<T, s>::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);
+16 -20
View File
@@ -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 <bool ranges_are_linear, intptr_t kW>
static intptr_t LookupMapping(const int32_t* table,
uint16_t size,
const MultiCharacterSpecialCase<kW>* 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<kW>* 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<true>(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<true>(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:
+24 -25
View File
@@ -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 <class T, intptr_t size = 256>
template <class T, int size = 256>
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
+197
View File
@@ -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 <algorithm>
#include <iterator>
#include <limits>
#include "platform/allocation.h"
#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/regexp/base.h"
namespace base {
template <typename T>
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<T> New(size_t length) {
return Vector<T>(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<T> SubVector(size_t from, size_t to) const {
DCHECK_LE(from, to);
DCHECK_LE(to, length_);
return Vector<T>(begin() + from, to - from);
}
Vector<T> SubVectorFrom(size_t from) const {
return SubVector(from, length_);
}
template <class U>
void OverwriteWith(Vector<U> other) {
DCHECK_EQ(size(), other.size());
std::copy(other.begin(), other.end(), begin());
}
template <class U, size_t n>
void OverwriteWith(const std::array<U, n>& 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<int>::max(), length_);
return static_cast<int>(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<T*> rbegin() const {
return std::make_reverse_iterator(end());
}
constexpr std::reverse_iterator<T*> rend() const {
return std::make_reverse_iterator(begin());
}
// Returns a clone of this vector with a new backing store.
Vector<T> Clone() const {
T* result = new T[length_];
for (size_t i = 0; i < length_; i++)
result[i] = start_[i];
return Vector<T>(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<T> operator+(size_t offset) const {
DCHECK_LE(offset, length_);
return Vector<T>(start_ + offset, length_ - offset);
}
Vector<T> operator+=(size_t offset) {
DCHECK_LE(offset, length_);
start_ += offset;
length_ -= offset;
return *this;
}
// Implicit conversion from Vector<T> to Vector<const U> 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 <typename U>
requires std::is_convertible_v<T*, const U*> && (sizeof(U) == sizeof(T))
operator Vector<const U>() const {
return {start_, length_};
}
template <typename S>
static Vector<T> cast(Vector<S> 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<S> && std::is_standard_layout_v<S>);
static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>);
DCHECK_EQ(0, (input.size() * sizeof(S)) % sizeof(T));
DCHECK_EQ(0, reinterpret_cast<uintptr_t>(input.begin()) % alignof(T));
return Vector<T>(reinterpret_cast<T*>(input.begin()),
input.size() * sizeof(S) / sizeof(T));
}
bool operator==(const Vector<T>& other) const {
return std::equal(begin(), end(), other.begin(), other.end());
}
template <typename TT = T>
requires(!std::is_const_v<TT>)
bool operator==(const Vector<const T>& 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 <typename T, size_t N>
inline constexpr Vector<T> ArrayVector(T (&arr)[N]) {
return {arr, N};
}
// Construct a Vector from a start pointer and a size.
template <typename T>
inline constexpr Vector<T> VectorOf(T* start, size_t size) {
return {start, size};
}
} // namespace base
#endif // V8_BASE_VECTOR_H_
+716
View File
@@ -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 <algorithm>
#include <deque>
#include <forward_list>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#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 <typename T>
class ZoneVector {
public:
using iterator = T*;
using const_iterator = const T*;
using reverse_iterator = std::reverse_iterator<T*>;
using const_reverse_iterator = std::reverse_iterator<const T*>;
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<T>(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<T>(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<T> list, Zone* zone) : zone_(zone) {
size_t size = list.size();
if (size > 0) {
data_ = zone->AllocateArray<T>(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 <class It,
typename = typename std::iterator_traits<It>::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<It>::iterator_category>) {
size_t size = last - first;
data_ = size > 0 ? zone->AllocateArray<T>(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<T>) {
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<T>) {
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<T>(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<T> ilist) {
clear();
EnsureCapacity(ilist.size());
CopyToNewStorage(data_, ilist.begin(), ilist.end());
end_ = data_ + ilist.size();
return *this;
}
base::Vector<T> Release() && {
base::Vector<T> ret = base::VectorOf(*this);
data_ = end_ = capacity_ = nullptr;
return ret;
}
void swap(ZoneVector<T>& 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 <typename... Args>
T& emplace_back(Args&&... args) {
EnsureOneMoreCapacity();
T* ptr = end_++;
new (ptr) T(std::forward<Args>(args)...);
return *ptr;
}
template <class It,
typename = typename std::iterator_traits<It>::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<It>::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 <typename... Args>
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<T*>(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<T*>(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<T>) {
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<T>) {
*dst = *src;
} else {
dst->~T();
CopyToNewStorage(dst, src);
}
}
V8_INLINE void MovingOverwrite(T* dst, T* src) {
if constexpr (std::is_move_assignable_v<T>) {
*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<T>(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<size_t>::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<T>(new_capacity);
end_ = data_ + old_size + count;
if (old_data) {
MoveToNewStorage(data_, old_data, pos);
MoveToNewStorage(data_ + index + count, const_cast<T*>(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<T>) {
if (to_shift > 0) {
// Add V8_ASSUME to silence gcc null check warning.
V8_ASSUME(pos != nullptr);
memmove(const_cast<T*>(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<T*>(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 <typename... Args>
void emplace_at(T* target, Args&&... args) {
new (target) T(std::forward<Args>(args)...);
}
Zone* zone_{nullptr};
T* data_{nullptr};
T* end_{nullptr};
T* capacity_{nullptr};
};
template <class T>
bool operator==(const ZoneVector<T>& lhs, const ZoneVector<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <class T>
bool operator!=(const ZoneVector<T>& lhs, const ZoneVector<T>& rhs) {
return !(lhs == rhs);
}
template <class T>
bool operator<(const ZoneVector<T>& lhs, const ZoneVector<T>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end());
}
template <typename T>
class ZoneAllocator {
public:
using value_type = T;
explicit ZoneAllocator(Zone* zone) : zone_(zone) {}
template <typename U>
ZoneAllocator(const ZoneAllocator<U>& other)
: ZoneAllocator<T>(other.zone()) {}
T* allocate(size_t length) { return zone_->Alloc<T>(length); }
void deallocate(T* p, size_t length) { zone_->DeleteArray<T>(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 <typename K, typename V, typename Compare = std::less<K>>
class ZoneMap
: public std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>> {
public:
// Constructs an empty map.
explicit ZoneMap(Zone* zone)
: std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>>(
Compare(),
ZoneAllocator<std::pair<const K, V>>(zone)) {}
};
// A wrapper subclass for std::unordered_map to make it easy to construct one
// that uses a zone allocator.
template <typename K,
typename V,
typename Hash = std::hash<K>,
typename KeyEqual = std::equal_to<K>>
class ZoneUnorderedMap
: public std::unordered_map<K,
V,
Hash,
KeyEqual,
ZoneAllocator<std::pair<const K, V>>> {
public:
// Constructs an empty map.
explicit ZoneUnorderedMap(Zone* zone, size_t bucket_count = 0)
: std::unordered_map<K,
V,
Hash,
KeyEqual,
ZoneAllocator<std::pair<const K, V>>>(
bucket_count,
Hash(),
KeyEqual(),
ZoneAllocator<std::pair<const K, V>>(zone)) {}
};
// A wrapper subclass for base::SmallVector to make it easy to construct one
// that uses a zone allocator.
template <typename T, size_t kSize>
class SmallZoneVector : public base::SmallVector<T, kSize, ZoneAllocator<T>> {
public:
// Constructs an empty small vector.
explicit SmallZoneVector(Zone* zone)
: base::SmallVector<T, kSize, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {}
explicit SmallZoneVector(size_t size, Zone* zone)
: base::SmallVector<T, kSize, ZoneAllocator<T>>(
size,
ZoneAllocator<T>(ZoneAllocator<T>(zone))) {}
};
} // namespace dart
#endif // V8_ZONE_ZONE_CONTAINERS_H_
+160
View File
@@ -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 <typename T>
void ZoneList<T>::Add(const T& element, Zone* zone) {
if (length_ < capacity_) {
data_[length_++] = element;
} else {
ZoneList<T>::ResizeAdd(element, zone);
}
}
template <typename T>
void ZoneList<T>::AddAll(const ZoneList<T>& other, Zone* zone) {
AddAll(other.ToVector(), zone);
}
template <typename T>
void ZoneList<T>::AddAll(base::Vector<const T> 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<T>) {
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 <typename T>
void ZoneList<T>::ResizeAdd(const T& element, Zone* zone) {
ResizeAddInternal(element, zone);
}
template <typename T>
void ZoneList<T>::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 <typename T>
void ZoneList<T>::Resize(int new_capacity, Zone* zone) {
DCHECK_LE(length_, new_capacity);
T* new_data = zone->AllocateArray<T>(new_capacity);
if (length_ > 0) {
if (std::is_trivially_copyable_v<T>) {
base::MemCopy(new_data, data_, length_ * sizeof(T));
} else {
std::copy(&data_[0], &data_[length_], &new_data[0]);
}
}
if (data_) zone->DeleteArray<T>(data_, capacity_);
data_ = new_data;
capacity_ = new_capacity;
}
template <typename T>
base::Vector<T> ZoneList<T>::AddBlock(T value, int count, Zone* zone) {
int start = length_;
for (int i = 0; i < count; i++)
Add(value, zone);
return base::Vector<T>(&data_[start], count);
}
template <typename T>
void ZoneList<T>::Set(int index, const T& elm) {
DCHECK(index >= 0 && index <= length_);
data_[index] = elm;
}
template <typename T>
void ZoneList<T>::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 <typename T>
T ZoneList<T>::Remove(int i) {
T element = at(i);
length_--;
while (i < length_) {
data_[i] = data_[i + 1];
i++;
}
return element;
}
template <typename T>
void ZoneList<T>::Clear(Zone* zone) {
if (data_) zone->DeleteArray<T>(data_, capacity_);
DropAndClear();
}
template <typename T>
void ZoneList<T>::Rewind(int pos) {
DCHECK(0 <= pos && pos <= length_);
length_ = pos;
}
template <typename T>
template <class Visitor>
void ZoneList<T>::Iterate(Visitor* visitor) {
for (int i = 0; i < length_; i++)
visitor->Apply(&data_[i]);
}
template <typename T>
template <typename CompareFunction>
void ZoneList<T>::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 <typename T>
template <typename CompareFunction>
void ZoneList<T>::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_
+193
View File
@@ -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 <typename T>
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 <typename T>
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<T>(capacity);
} else {
data_ = nullptr;
}
}
// Construct a new ZoneList by copying the elements of the given ZoneList.
ZoneList(const ZoneList<T>& 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<const T> other, Zone* zone)
: ZoneList(other.length(), zone) {
AddAll(other, zone);
}
ZoneList(ZoneList<T>&& 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<unsigned>(length_), static_cast<unsigned>(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<T> ToVector() const { return base::Vector<T>(data_, length_); }
base::Vector<T> ToVector(int start, int length) const {
DCHECK_LE(start, length_);
return base::Vector<T>(&data_[start], std::min(length_ - start, length));
}
base::Vector<const T> ToConstVector() const {
return base::Vector<const T>(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<T>& other, Zone* zone);
// Add all the elements from the vector to this list.
void AddAll(base::Vector<const T> 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<T> 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 <class Visitor>
void Iterate(Visitor* visitor);
// Sort all list entries (using QuickSort)
template <typename CompareFunction>
void Sort(CompareFunction cmp);
template <typename CompareFunction>
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_

Some files were not shown because too many files have changed in this diff Show More