Commit d432b218 authored by Santiago Aboy Solanes's avatar Santiago Aboy Solanes Committed by Commit Bot

[runtime] Add thread-safety to TransitionAccessor via shared mutex

What we need is a multiple readers single writer (MRSW) lock. The
main thread is the only one that is going to be writing, while the
readers might be either the main thread or background threads.

The shared_mutex is in the isolate itself, so that different isolates
will not block each other.

Bug: v8:7790
Change-Id: Idd6bb1826bd0cc6279df1c0694a84e00d53a7eae
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2241513
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68639}
parent dbb8a842
...@@ -769,7 +769,8 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition( ...@@ -769,7 +769,8 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition(
Handle<Map> map, Handle<Name> name, MaybeHandle<JSObject> holder) const { Handle<Map> map, Handle<Name> name, MaybeHandle<JSObject> holder) const {
// Check if the {map} has a data transition with the given {name}. // Check if the {map} has a data transition with the given {name}.
Map transition = Map transition =
TransitionsAccessor(isolate(), map).SearchTransition(*name, kData, NONE); TransitionsAccessor(isolate(), map, broker()->is_concurrent_inlining())
.SearchTransition(*name, kData, NONE);
if (transition.is_null()) { if (transition.is_null()) {
return PropertyAccessInfo::Invalid(zone()); return PropertyAccessInfo::Invalid(zone());
} }
...@@ -777,7 +778,7 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition( ...@@ -777,7 +778,7 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition(
Handle<Map> transition_map(transition, isolate()); Handle<Map> transition_map(transition, isolate());
InternalIndex const number = transition_map->LastAdded(); InternalIndex const number = transition_map->LastAdded();
PropertyDetails const details = PropertyDetails const details =
transition_map->instance_descriptors().GetDetails(number); transition_map->synchronized_instance_descriptors().GetDetails(number);
// Don't bother optimizing stores to read-only properties. // Don't bother optimizing stores to read-only properties.
if (details.IsReadOnly()) { if (details.IsReadOnly()) {
return PropertyAccessInfo::Invalid(zone()); return PropertyAccessInfo::Invalid(zone());
...@@ -812,7 +813,9 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition( ...@@ -812,7 +813,9 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition(
// Extract the field type from the property details (make sure its // Extract the field type from the property details (make sure its
// representation is TaggedPointer to reflect the heap object case). // representation is TaggedPointer to reflect the heap object case).
Handle<FieldType> descriptors_field_type( Handle<FieldType> descriptors_field_type(
transition_map->instance_descriptors().GetFieldType(number), isolate()); transition_map->synchronized_instance_descriptors().GetFieldType(
number),
isolate());
if (descriptors_field_type->IsNone()) { if (descriptors_field_type->IsNone()) {
// Store is not safe if the field type was cleared. // Store is not safe if the field type was cleared.
return PropertyAccessInfo::Invalid(zone()); return PropertyAccessInfo::Invalid(zone());
......
...@@ -608,6 +608,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -608,6 +608,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
// Mutex for serializing access to break control structures. // Mutex for serializing access to break control structures.
base::RecursiveMutex* break_access() { return &break_access_; } base::RecursiveMutex* break_access() { return &break_access_; }
// Shared mutex for allowing concurrent read/writes to TransitionArrays.
base::SharedMutex* transition_array_access() {
return &transition_array_access_;
}
Address get_address_from_id(IsolateAddressId id); Address get_address_from_id(IsolateAddressId id);
// Access to top context (where the current function object was created). // Access to top context (where the current function object was created).
...@@ -1640,6 +1645,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -1640,6 +1645,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
CompilationCache* compilation_cache_ = nullptr; CompilationCache* compilation_cache_ = nullptr;
std::shared_ptr<Counters> async_counters_; std::shared_ptr<Counters> async_counters_;
base::RecursiveMutex break_access_; base::RecursiveMutex break_access_;
base::SharedMutex transition_array_access_;
Logger* logger_ = nullptr; Logger* logger_ = nullptr;
StubCache* load_stub_cache_ = nullptr; StubCache* load_stub_cache_ = nullptr;
StubCache* store_stub_cache_ = nullptr; StubCache* store_stub_cache_ = nullptr;
......
...@@ -45,7 +45,8 @@ SYNCHRONIZED_ACCESSORS(Map, synchronized_instance_descriptors, DescriptorArray, ...@@ -45,7 +45,8 @@ SYNCHRONIZED_ACCESSORS(Map, synchronized_instance_descriptors, DescriptorArray,
SYNCHRONIZED_ACCESSORS_CHECKED(Map, layout_descriptor, LayoutDescriptor, SYNCHRONIZED_ACCESSORS_CHECKED(Map, layout_descriptor, LayoutDescriptor,
kLayoutDescriptorOffset, kLayoutDescriptorOffset,
FLAG_unbox_double_fields) FLAG_unbox_double_fields)
WEAK_ACCESSORS(Map, raw_transitions, kTransitionsOrPrototypeInfoOffset) SYNCHRONIZED_WEAK_ACCESSORS(Map, raw_transitions,
kTransitionsOrPrototypeInfoOffset)
ACCESSORS_CHECKED2(Map, prototype, HeapObject, kPrototypeOffset, true, ACCESSORS_CHECKED2(Map, prototype, HeapObject, kPrototypeOffset, true,
value.IsNull() || value.IsJSReceiver()) value.IsNull() || value.IsJSReceiver())
......
...@@ -196,6 +196,27 @@ ...@@ -196,6 +196,27 @@
#define WEAK_ACCESSORS(holder, name, offset) \ #define WEAK_ACCESSORS(holder, name, offset) \
WEAK_ACCESSORS_CHECKED(holder, name, offset, true) WEAK_ACCESSORS_CHECKED(holder, name, offset, true)
#define SYNCHRONIZED_WEAK_ACCESSORS_CHECKED2(holder, name, offset, \
get_condition, set_condition) \
DEF_GETTER(holder, name, MaybeObject) { \
MaybeObject value = \
TaggedField<MaybeObject, offset>::Acquire_Load(isolate, *this); \
DCHECK(get_condition); \
return value; \
} \
void holder::set_##name(MaybeObject value, WriteBarrierMode mode) { \
DCHECK(set_condition); \
TaggedField<MaybeObject, offset>::Release_Store(*this, value); \
CONDITIONAL_WEAK_WRITE_BARRIER(*this, offset, value, mode); \
}
#define SYNCHRONIZED_WEAK_ACCESSORS_CHECKED(holder, name, offset, condition) \
SYNCHRONIZED_WEAK_ACCESSORS_CHECKED2(holder, name, offset, condition, \
condition)
#define SYNCHRONIZED_WEAK_ACCESSORS(holder, name, offset) \
SYNCHRONIZED_WEAK_ACCESSORS_CHECKED(holder, name, offset, true)
// Getter that returns a Smi as an int and writes an int as a Smi. // Getter that returns a Smi as an int and writes an int as a Smi.
#define SMI_ACCESSORS_CHECKED(holder, name, offset, condition) \ #define SMI_ACCESSORS_CHECKED(holder, name, offset, condition) \
int holder::name() const { \ int holder::name() const { \
......
...@@ -181,13 +181,17 @@ int TransitionArray::SearchName(Name name, int* out_insertion_index) { ...@@ -181,13 +181,17 @@ int TransitionArray::SearchName(Name name, int* out_insertion_index) {
TransitionsAccessor::TransitionsAccessor(Isolate* isolate, Map map, TransitionsAccessor::TransitionsAccessor(Isolate* isolate, Map map,
DisallowHeapAllocation* no_gc) DisallowHeapAllocation* no_gc)
: isolate_(isolate), map_(map) { : isolate_(isolate), map_(map), concurrent_access_(false) {
Initialize(); Initialize();
USE(no_gc); USE(no_gc);
} }
TransitionsAccessor::TransitionsAccessor(Isolate* isolate, Handle<Map> map) TransitionsAccessor::TransitionsAccessor(Isolate* isolate, Handle<Map> map,
: isolate_(isolate), map_handle_(map), map_(*map) { bool concurrent_access)
: isolate_(isolate),
map_handle_(map),
map_(*map),
concurrent_access_(concurrent_access) {
Initialize(); Initialize();
} }
......
...@@ -35,6 +35,7 @@ bool TransitionsAccessor::HasSimpleTransitionTo(Map map) { ...@@ -35,6 +35,7 @@ bool TransitionsAccessor::HasSimpleTransitionTo(Map map) {
void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target, void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
SimpleTransitionFlag flag) { SimpleTransitionFlag flag) {
DCHECK(!concurrent_access_);
DCHECK(!map_handle_.is_null()); DCHECK(!map_handle_.is_null());
DCHECK_NE(kPrototypeInfo, encoding()); DCHECK_NE(kPrototypeInfo, encoding());
target->SetBackPointer(map_); target->SetBackPointer(map_);
...@@ -47,16 +48,17 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target, ...@@ -47,16 +48,17 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
} }
// If the flag requires a full TransitionArray, allocate one. // If the flag requires a full TransitionArray, allocate one.
Handle<TransitionArray> result = Handle<TransitionArray> result =
isolate_->factory()->NewTransitionArray(0, 1); isolate_->factory()->NewTransitionArray(1, 0);
result->Set(0, *name, HeapObjectReference::Weak(*target));
ReplaceTransitions(MaybeObject::FromObject(*result)); ReplaceTransitions(MaybeObject::FromObject(*result));
Reload(); Reload();
DCHECK_EQ(kFullTransitionArray, encoding()); DCHECK_EQ(kFullTransitionArray, encoding());
return;
} }
// If the map has a simple transition, check if it should be overwritten. if (encoding() == kWeakRef) {
Map simple_transition = GetSimpleTransition(); Map simple_transition = GetSimpleTransition();
if (!simple_transition.is_null()) { DCHECK(!simple_transition.is_null());
DCHECK_EQ(kWeakRef, encoding());
if (flag == SIMPLE_PROPERTY_TRANSITION) { if (flag == SIMPLE_PROPERTY_TRANSITION) {
Name key = GetSimpleTransitionKey(simple_transition); Name key = GetSimpleTransitionKey(simple_transition);
...@@ -76,16 +78,46 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target, ...@@ -76,16 +78,46 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
// Reload state; allocations might have caused it to be cleared. // Reload state; allocations might have caused it to be cleared.
Reload(); Reload();
simple_transition = GetSimpleTransition(); simple_transition = GetSimpleTransition();
if (!simple_transition.is_null()) { if (simple_transition.is_null()) {
DCHECK_EQ(*map, simple_transition); result->Set(0, *name, HeapObjectReference::Weak(*target));
DCHECK_EQ(kWeakRef, encoding()); ReplaceTransitions(MaybeObject::FromObject(*result));
result->Set(0, GetSimpleTransitionKey(simple_transition), Reload();
HeapObjectReference::Weak(simple_transition)); DCHECK_EQ(kFullTransitionArray, encoding());
return;
}
// Insert the original transition in index 0.
result->Set(0, GetSimpleTransitionKey(simple_transition),
HeapObjectReference::Weak(simple_transition));
// Search for the correct index to insert the new transition.
int insertion_index;
int index;
if (flag == SPECIAL_TRANSITION) {
index = result->SearchSpecial(Symbol::cast(*name), &insertion_index);
} else { } else {
result->SetNumberOfTransitions(0); PropertyDetails details = GetTargetDetails(*name, *target);
index = result->Search(details.kind(), *name, details.attributes(),
&insertion_index);
} }
DCHECK_EQ(index, kNotFound);
USE(index);
result->SetNumberOfTransitions(2);
if (insertion_index == 0) {
// If the new transition will be inserted in index 0, move the original
// transition to index 1.
result->Set(1, GetSimpleTransitionKey(simple_transition),
HeapObjectReference::Weak(simple_transition));
}
result->SetKey(insertion_index, *name);
result->SetRawTarget(insertion_index, HeapObjectReference::Weak(*target));
SLOW_DCHECK(result->IsSortedNoDuplicates());
ReplaceTransitions(MaybeObject::FromObject(*result)); ReplaceTransitions(MaybeObject::FromObject(*result));
Reload(); Reload();
DCHECK_EQ(kFullTransitionArray, encoding());
return;
} }
// At this point, we know that the map has a full TransitionArray. // At this point, we know that the map has a full TransitionArray.
...@@ -112,6 +144,8 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target, ...@@ -112,6 +144,8 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
&insertion_index); &insertion_index);
// If an existing entry was found, overwrite it and return. // If an existing entry was found, overwrite it and return.
if (index != kNotFound) { if (index != kNotFound) {
base::SharedMutexGuard<base::kExclusive> shared_mutex_guard(
isolate_->transition_array_access());
array.SetRawTarget(index, HeapObjectReference::Weak(*target)); array.SetRawTarget(index, HeapObjectReference::Weak(*target));
return; return;
} }
...@@ -123,6 +157,8 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target, ...@@ -123,6 +157,8 @@ void TransitionsAccessor::Insert(Handle<Name> name, Handle<Map> target,
// If there is enough capacity, insert new entry into the existing array. // If there is enough capacity, insert new entry into the existing array.
if (new_nof <= array.Capacity()) { if (new_nof <= array.Capacity()) {
base::SharedMutexGuard<base::kExclusive> shared_mutex_guard(
isolate_->transition_array_access());
array.SetNumberOfTransitions(new_nof); array.SetNumberOfTransitions(new_nof);
for (int i = number_of_transitions; i > insertion_index; --i) { for (int i = number_of_transitions; i > insertion_index; --i) {
array.SetKey(i, array.GetKey(i - 1)); array.SetKey(i, array.GetKey(i - 1));
...@@ -194,7 +230,11 @@ Map TransitionsAccessor::SearchTransition(Name name, PropertyKind kind, ...@@ -194,7 +230,11 @@ Map TransitionsAccessor::SearchTransition(Name name, PropertyKind kind,
return map; return map;
} }
case kFullTransitionArray: { case kFullTransitionArray: {
return transitions().SearchAndGetTarget(kind, name, attributes); if (concurrent_access_) isolate_->transition_array_access()->LockShared();
Map result = transitions().SearchAndGetTarget(kind, name, attributes);
if (concurrent_access_)
isolate_->transition_array_access()->UnlockShared();
return result;
} }
} }
UNREACHABLE(); UNREACHABLE();
......
...@@ -38,9 +38,14 @@ namespace internal { ...@@ -38,9 +38,14 @@ namespace internal {
// cleared when the map they refer to is not otherwise reachable. // cleared when the map they refer to is not otherwise reachable.
class V8_EXPORT_PRIVATE TransitionsAccessor { class V8_EXPORT_PRIVATE TransitionsAccessor {
public: public:
// For concurrent access, use the other constructor.
inline TransitionsAccessor(Isolate* isolate, Map map, inline TransitionsAccessor(Isolate* isolate, Map map,
DisallowHeapAllocation* no_gc); DisallowHeapAllocation* no_gc);
inline TransitionsAccessor(Isolate* isolate, Handle<Map> map); // {concurrent_access} signals that the TransitionsAccessor will only be used
// in background threads. It acquires a reader lock for critical paths, as
// well as blocking the accessor from modifying the TransitionsArray.
inline TransitionsAccessor(Isolate* isolate, Handle<Map> map,
bool concurrent_access = false);
// Insert a new transition into |map|'s transition array, extending it // Insert a new transition into |map|'s transition array, extending it
// as necessary. // as necessary.
// Requires the constructor that takes a Handle<Map> to have been used. // Requires the constructor that takes a Handle<Map> to have been used.
...@@ -182,6 +187,7 @@ class V8_EXPORT_PRIVATE TransitionsAccessor { ...@@ -182,6 +187,7 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
Map map_; Map map_;
MaybeObject raw_transitions_; MaybeObject raw_transitions_;
Encoding encoding_; Encoding encoding_;
bool concurrent_access_;
#if DEBUG #if DEBUG
bool needs_reload_; bool needs_reload_;
#endif #endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment