[vm/regexp] Copy, rather than share RegExp objects between isolates.
RegExp code objects keep backtracking and registers stacks in object pools, can't be shared between isolates.
BUG=https://github.com/dart-lang/sdk/issues/50082
TEST=one_regexp_many_workers
Change-Id: Ic7db8d7a75a0951178b2f4800f96224d52506545
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273480
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
index 4a2a72f..76e5679 100644
--- a/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
@@ -73,7 +73,6 @@
const [1, 2, 3],
const {1: 1, 2: 2, 3: 2},
const {1, 2, 3},
- RegExp('a'),
Isolate.current.pauseCapability,
Int32x4(1, 2, 3, 4),
StackTrace.current,
diff --git a/runtime/tests/vm/dart/isolates/one_regexp_many_workers_test.dart b/runtime/tests/vm/dart/isolates/one_regexp_many_workers_test.dart
new file mode 100644
index 0000000..42a99e1
--- /dev/null
+++ b/runtime/tests/vm/dart/isolates/one_regexp_many_workers_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, 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.
+//
+// Verifies that many isolate workers can use one RegExp.
+
+import 'dart:isolate';
+import "package:async_helper/async_helper.dart";
+
+worker(List<dynamic> args) {
+ final re = args[0] as RegExp;
+ final i = args[1] as int;
+ print('worker $i');
+ final sendPort = args[2] as SendPort;
+ final sw = Stopwatch()..start();
+ while (sw.elapsedMilliseconds < 2000) {
+ re.firstMatch('h' * i * 1000);
+ }
+ sendPort.send(true);
+}
+
+main() {
+ asyncStart();
+
+ int nWorkers = 5;
+ final r = RegExp(r'(?<=\W|\b|^)(a.? b c.?) ?(\(.*\))?$');
+ final rps = List<ReceivePort>.generate(nWorkers, (_) => ReceivePort());
+
+ for (int i = 0; i < nWorkers; i++) {
+ Isolate.spawn(worker, <dynamic>[r, i, rps[i].sendPort]);
+ }
+
+ Future.wait(List<Future<dynamic>>.generate(nWorkers, (i) => rps[i].first))
+ .whenComplete(() {
+ rps.forEach((rp) => rp.close());
+ asyncEnd();
+ });
+}
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
index 99d8e26..0c48bc0 100644
--- a/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
@@ -74,7 +74,6 @@
const [1, 2, 3],
const {1: 1, 2: 2, 3: 2},
const {1, 2, 3},
- RegExp('a'),
Isolate.current.pauseCapability,
Int32x4(1, 2, 3, 4),
StackTrace.current,
diff --git a/runtime/tests/vm/dart_2/isolates/one_regexp_many_workers_test.dart b/runtime/tests/vm/dart_2/isolates/one_regexp_many_workers_test.dart
new file mode 100644
index 0000000..a035a0c
--- /dev/null
+++ b/runtime/tests/vm/dart_2/isolates/one_regexp_many_workers_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, 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.
+//
+// @dart = 2.9
+//
+// Verifies that many isolate workers can use one RegExp.
+
+import 'dart:isolate';
+import "package:async_helper/async_helper.dart";
+
+worker(List<dynamic> args) {
+ final re = args[0] as RegExp;
+ final i = args[1] as int;
+ print('worker $i');
+ final sendPort = args[2] as SendPort;
+ final sw = Stopwatch()..start();
+ while (sw.elapsedMilliseconds < 2000) {
+ re.firstMatch('h' * i * 1000);
+ }
+ sendPort.send(true);
+}
+
+main() {
+ asyncStart();
+
+ int nWorkers = 5;
+ final r = RegExp(r'(?<=\W|\b|^)(a.? b c.?) ?(\(.*\))?$');
+ final rps = List<ReceivePort>.generate(nWorkers, (_) => ReceivePort());
+
+ for (int i = 0; i < nWorkers; i++) {
+ Isolate.spawn(worker, <dynamic>[r, i, rps[i].sendPort]);
+ }
+
+ Future.wait(List<Future<dynamic>>.generate(nWorkers, (i) => rps[i].first))
+ .whenComplete(() {
+ rps.forEach((rp) => rp.close());
+ asyncEnd();
+ });
+}
diff --git a/runtime/vm/object_graph_copy.cc b/runtime/vm/object_graph_copy.cc
index 6305567..b6e102d 100644
--- a/runtime/vm/object_graph_copy.cc
+++ b/runtime/vm/object_graph_copy.cc
@@ -10,6 +10,7 @@
#include "vm/longjump.h"
#include "vm/object.h"
#include "vm/object_store.h"
+#include "vm/regexp.h"
#include "vm/snapshot.h"
#include "vm/symbols.h"
#include "vm/timeline.h"
@@ -68,7 +69,6 @@
V(Pointer) \
V(ReceivePort) \
V(RecordType) \
- V(RegExp) \
V(Script) \
V(Sentinel) \
V(SendPort) \
@@ -170,7 +170,13 @@
if (cid == kInt32x4Cid) return true; // No field guards here.
if (cid == kSendPortCid) return true;
if (cid == kCapabilityCid) return true;
+
+ // Generated code for regexp can't be shared.
+#if defined(DART_PRECOMPILED_RUNTIME)
if (cid == kRegExpCid) return true;
+#else
+ if (FLAG_interpret_irregexp && cid == kRegExpCid) return true;
+#endif
if (cid == kClosureCid) {
// We can share a closure iff it doesn't close over any state.
@@ -214,7 +220,11 @@
// a map or a value in a set, they will already have the identity hash code
// set.
if (cid == kImmutableArrayCid) return false;
+#if defined(DART_PRECOMPILED_RUNTIME)
if (cid == kRegExpCid) return false;
+#else
+ if (FLAG_interpret_irregexp && cid == kRegExpCid) return false;
+#endif
if (cid == kInt32x4Cid) return false;
// We copy those (instead of sharing them) - see [CanShareObjct]. They rely
@@ -661,6 +671,7 @@
void AddExternalTypedData(ExternalTypedDataPtr to) {
raw_external_typed_data_to_.Add(to);
}
+ void AddRegExp(RegExpPtr to) { raw_reg_exp_to_.Add(to); }
void AddObjectToRehash(ObjectPtr to) { raw_objects_to_rehash_.Add(to); }
void AddExpandoToRehash(ObjectPtr to) { raw_expandos_to_rehash_.Add(to); }
@@ -672,6 +683,7 @@
IdentityMap* map_;
GrowableArray<ObjectPtr> raw_from_to_;
GrowableArray<TransferableTypedDataPtr> raw_transferables_from_to_;
+ GrowableArray<RegExpPtr> raw_reg_exp_to_;
GrowableArray<ExternalTypedDataPtr> raw_external_typed_data_to_;
GrowableArray<ObjectPtr> raw_objects_to_rehash_;
GrowableArray<ObjectPtr> raw_expandos_to_rehash_;
@@ -714,6 +726,7 @@
transferables_from_to_.Add(&TransferableTypedData::Handle(from.ptr()));
transferables_from_to_.Add(&TransferableTypedData::Handle(to.ptr()));
}
+ void AddRegExp(const RegExp& to) { reg_exps_.Add(&RegExp::Handle(to.ptr())); }
void AddWeakProperty(const WeakProperty& from) {
weak_properties_.Add(&WeakProperty::Handle(from.ptr()));
}
@@ -740,6 +753,29 @@
}
}
+ void FinalizeRegExps() {
+ if (FLAG_interpret_irregexp) {
+ return;
+ }
+ if (reg_exps_.length() == 0) {
+ return;
+ }
+ const Library& lib = Library::Handle(zone_, Library::CoreLibrary());
+ const Class& owner =
+ Class::Handle(zone_, lib.LookupClass(Symbols::RegExp()));
+
+ for (intptr_t i = 0; i < reg_exps_.length(); i++) {
+ auto regexp = reg_exps_[i];
+ for (intptr_t cid = kOneByteStringCid; cid <= kExternalTwoByteStringCid;
+ cid++) {
+ CreateSpecializedFunction(thread_, zone_, *regexp, cid,
+ /*sticky=*/false, owner);
+ CreateSpecializedFunction(thread_, zone_, *regexp, cid,
+ /*sticky=*/true, owner);
+ }
+ }
+ }
+
void FinalizeExternalTypedData() {
for (intptr_t i = 0; i < external_typed_data_.length(); i++) {
auto to = external_typed_data_[i];
@@ -756,6 +792,7 @@
GrowableArray<const PassiveObject*> from_to_transition_;
GrowableObjectArray& from_to_;
GrowableArray<const TransferableTypedData*> transferables_from_to_;
+ GrowableArray<const RegExp*> reg_exps_;
GrowableArray<const ExternalTypedData*> external_typed_data_;
GrowableArray<const Object*> objects_to_rehash_;
GrowableArray<const Object*> expandos_to_rehash_;
@@ -1030,6 +1067,7 @@
TransferableTypedDataPtr to) {
fast_forward_map_.AddTransferable(from, to);
}
+ void EnqueueRegExp(RegExpPtr to) { fast_forward_map_.AddRegExp(to); }
void EnqueueWeakProperty(WeakPropertyPtr from) {
fast_forward_map_.AddWeakProperty(from);
}
@@ -1235,6 +1273,7 @@
const TransferableTypedData& to) {
slow_forward_map_.AddTransferable(from, to);
}
+ void EnqueueRegExp(const RegExp& to) { slow_forward_map_.AddRegExp(to); }
void EnqueueWeakProperty(const WeakProperty& from) {
slow_forward_map_.AddWeakProperty(from);
}
@@ -1447,6 +1486,49 @@
Record::field_offset(0) + Record::kBytesPerElement * num_fields);
}
+ void CopyRegExp(typename Types::RegExp from, typename Types::RegExp to) {
+ Base::StoreCompressedPointers(from, to,
+ OFFSET_OF(UntaggedRegExp, capture_name_map_),
+ OFFSET_OF(UntaggedRegExp, pattern_));
+
+ UntagRegExp(to)->num_bracket_expressions_ =
+ UntagRegExp(from)->num_bracket_expressions_;
+ UntagRegExp(to)->num_one_byte_registers_ =
+ UntagRegExp(from)->num_one_byte_registers_;
+ UntagRegExp(to)->num_two_byte_registers_ =
+ UntagRegExp(from)->num_two_byte_registers_;
+ UntagRegExp(to)->type_flags_ = UntagRegExp(from)->type_flags_;
+ Base::StoreCompressedPointerNoBarrier(Types::GetRegExpPtr(to),
+ OFFSET_OF(UntaggedRegExp, one_byte_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(Types::GetRegExpPtr(to),
+ OFFSET_OF(UntaggedRegExp, one_byte_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(Types::GetRegExpPtr(to),
+ OFFSET_OF(UntaggedRegExp, two_byte_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to), OFFSET_OF(UntaggedRegExp, external_one_byte_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to), OFFSET_OF(UntaggedRegExp, external_two_byte_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to), OFFSET_OF(UntaggedRegExp, one_byte_sticky_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to), OFFSET_OF(UntaggedRegExp, two_byte_sticky_),
+ Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to),
+ OFFSET_OF(UntaggedRegExp, external_one_byte_sticky_), Object::null());
+ Base::StoreCompressedPointerNoBarrier(
+ Types::GetRegExpPtr(to),
+ OFFSET_OF(UntaggedRegExp, external_two_byte_sticky_), Object::null());
+
+ Base::EnqueueRegExp(to);
+ }
+
template <intptr_t one_for_set_two_for_map, typename T>
void CopyLinkedHashBase(T from,
T to,
@@ -2066,6 +2148,7 @@
// The copy was successful, then detach transferable data from the sender
// and attach to the copied graph.
slow_object_copy_.slow_forward_map_.FinalizeTransferables();
+ slow_object_copy_.slow_forward_map_.FinalizeRegExps();
return result.ptr();
}
@@ -2111,6 +2194,7 @@
result_array.SetAt(2, fast_object_copy_.tmp_);
HandlifyExternalTypedData();
HandlifyTransferables();
+ HandlifyRegExp();
allocated_bytes_ =
fast_object_copy_.fast_forward_map_.allocated_bytes;
copied_objects_ =
@@ -2172,6 +2256,7 @@
HandlifyWeakProperties();
HandlifyWeakReferences();
HandlifyExternalTypedData();
+ HandlifyRegExp();
HandlifyObjectsToReHash();
HandlifyExpandosToReHash();
HandlifyFromToObjects();
@@ -2218,6 +2303,10 @@
Handlify(&fast_object_copy_.fast_forward_map_.raw_external_typed_data_to_,
&slow_object_copy_.slow_forward_map_.external_typed_data_);
}
+ void HandlifyRegExp() {
+ Handlify(&fast_object_copy_.fast_forward_map_.raw_reg_exp_to_,
+ &slow_object_copy_.slow_forward_map_.reg_exps_);
+ }
void HandlifyObjectsToReHash() {
Handlify(&fast_object_copy_.fast_forward_map_.raw_objects_to_rehash_,
&slow_object_copy_.slow_forward_map_.objects_to_rehash_);