Skip to content

Commit a96012a

Browse files
committed
8261495: Shenandoah: reconsider update references memory ordering
Reviewed-by: zgu, rkennke
1 parent 23d2996 commit a96012a

File tree

7 files changed

+115
-37
lines changed

7 files changed

+115
-37
lines changed

src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ inline oop ShenandoahBarrierSet::load_reference_barrier_mutator(oop obj, T* load
7070

7171
if (load_addr != NULL && fwd != obj) {
7272
// Since we are here and we know the load address, update the reference.
73-
ShenandoahHeap::cas_oop(fwd, load_addr, obj);
73+
ShenandoahHeap::atomic_update_oop(fwd, load_addr, obj);
7474
}
7575

7676
return fwd;
@@ -130,7 +130,7 @@ inline oop ShenandoahBarrierSet::load_reference_barrier(oop obj, T* load_addr) {
130130
oop fwd = load_reference_barrier(obj);
131131
if (ShenandoahSelfFixing && load_addr != NULL && fwd != obj) {
132132
// Since we are here and we know the load address, update the reference.
133-
ShenandoahHeap::cas_oop(fwd, load_addr, obj);
133+
ShenandoahHeap::atomic_update_oop(fwd, load_addr, obj);
134134
}
135135

136136
return fwd;
@@ -349,7 +349,7 @@ void ShenandoahBarrierSet::arraycopy_work(T* src, size_t count) {
349349
fwd = _heap->evacuate_object(obj, thread);
350350
}
351351
assert(obj != fwd || _heap->cancelled_gc(), "must be forwarded");
352-
oop witness = ShenandoahHeap::cas_oop(fwd, elem_ptr, o);
352+
ShenandoahHeap::atomic_update_oop(fwd, elem_ptr, o);
353353
obj = fwd;
354354
}
355355
if (ENQUEUE && !ctx->is_marked_strong(obj)) {

src/hotspot/share/gc/shenandoah/shenandoahBarrierSetClone.inline.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class ShenandoahUpdateRefsForOopClosure: public BasicOopIterateClosure {
5454
fwd = _heap->evacuate_object(obj, _thread);
5555
}
5656
assert(obj != fwd || _heap->cancelled_gc(), "must be forwarded");
57-
ShenandoahHeap::cas_oop(fwd, p, o);
57+
ShenandoahHeap::atomic_update_oop(fwd, p, o);
5858
obj = fwd;
5959
}
6060
if (ENQUEUE) {

src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ void ShenandoahEvacuateUpdateRootsClosure::do_oop_work(T* p, Thread* t) {
161161
if (resolved == obj) {
162162
resolved = _heap->evacuate_object(obj, t);
163163
}
164-
_heap->cas_oop(resolved, p, o);
164+
ShenandoahHeap::atomic_update_oop(resolved, p, o);
165165
}
166166
}
167167
}
@@ -207,7 +207,7 @@ void ShenandoahCleanUpdateWeakOopsClosure<CONCURRENT, IsAlive, KeepAlive>::do_oo
207207
_keep_alive->do_oop(p);
208208
} else {
209209
if (CONCURRENT) {
210-
Atomic::cmpxchg(p, obj, oop());
210+
ShenandoahHeap::atomic_clear_oop(p, obj);
211211
} else {
212212
RawAccess<IS_NOT_NULL>::oop_store(p, oop());
213213
}

src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,13 +686,13 @@ void ShenandoahEvacUpdateCleanupOopStorageRootsClosure::do_oop(oop* p) {
686686
if (!CompressedOops::is_null(obj)) {
687687
if (!_mark_context->is_marked(obj)) {
688688
shenandoah_assert_correct(p, obj);
689-
Atomic::cmpxchg(p, obj, oop(NULL));
689+
ShenandoahHeap::atomic_clear_oop(p, obj);
690690
} else if (_evac_in_progress && _heap->in_collection_set(obj)) {
691691
oop resolved = ShenandoahBarrierSet::resolve_forwarded_not_null(obj);
692692
if (resolved == obj) {
693693
resolved = _heap->evacuate_object(obj, _thread);
694694
}
695-
Atomic::cmpxchg(p, obj, resolved);
695+
ShenandoahHeap::atomic_update_oop(resolved, p, obj);
696696
assert(_heap->cancelled_gc() ||
697697
_mark_context->is_marked(resolved) && !_heap->in_collection_set(resolved),
698698
"Sanity");

src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,17 @@ class ShenandoahHeap : public CollectedHeap {
635635
template <class T>
636636
inline void update_with_forwarded(T* p);
637637

638-
static inline oop cas_oop(oop n, narrowOop* addr, oop c);
639-
static inline oop cas_oop(oop n, oop* addr, oop c);
640-
static inline oop cas_oop(oop n, narrowOop* addr, narrowOop c);
638+
static inline void atomic_update_oop(oop update, oop* addr, oop compare);
639+
static inline void atomic_update_oop(oop update, narrowOop* addr, oop compare);
640+
static inline void atomic_update_oop(oop update, narrowOop* addr, narrowOop compare);
641+
642+
static inline bool atomic_update_oop_check(oop update, oop* addr, oop compare);
643+
static inline bool atomic_update_oop_check(oop update, narrowOop* addr, oop compare);
644+
static inline bool atomic_update_oop_check(oop update, narrowOop* addr, narrowOop compare);
645+
646+
static inline void atomic_clear_oop( oop* addr, oop compare);
647+
static inline void atomic_clear_oop(narrowOop* addr, oop compare);
648+
static inline void atomic_clear_oop(narrowOop* addr, narrowOop compare);
641649

642650
void trash_humongous_region_at(ShenandoahHeapRegion *r);
643651

src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,29 +132,110 @@ inline void ShenandoahHeap::conc_update_with_forwarded(T* p) {
132132

133133
// Either we succeed in updating the reference, or something else gets in our way.
134134
// We don't care if that is another concurrent GC update, or another mutator update.
135-
// We only check that non-NULL store still updated with non-forwarded reference.
136-
oop witness = cas_oop(fwd, p, obj);
137-
shenandoah_assert_not_forwarded_except(p, witness, (witness == NULL) || (witness == obj));
135+
atomic_update_oop(fwd, p, obj);
138136
}
139137
}
140138
}
141139

142-
inline oop ShenandoahHeap::cas_oop(oop n, oop* addr, oop c) {
140+
// Atomic updates of heap location. This is only expected to work with updating the same
141+
// logical object with its forwardee. The reason why we need stronger-than-relaxed memory
142+
// ordering has to do with coordination with GC barriers and mutator accesses.
143+
//
144+
// In essence, stronger CAS access is required to maintain the transitive chains that mutator
145+
// accesses build by themselves. To illustrate this point, consider the following example.
146+
//
147+
// Suppose "o" is the object that has a field "x" and the reference to "o" is stored
148+
// to field at "addr", which happens to be Java volatile field. Normally, the accesses to volatile
149+
// field at "addr" would be matched with release/acquire barriers. This changes when GC moves
150+
// the object under mutator feet.
151+
//
152+
// Thread 1 (Java)
153+
// // --- previous access starts here
154+
// ...
155+
// T1.1: store(&o.x, 1, mo_relaxed)
156+
// T1.2: store(&addr, o, mo_release) // volatile store
157+
//
158+
// // --- new access starts here
159+
// // LRB: copy and install the new copy to fwdptr
160+
// T1.3: var copy = copy(o)
161+
// T1.4: cas(&fwd, t, copy, mo_release) // pointer-mediated publication
162+
// <access continues>
163+
//
164+
// Thread 2 (GC updater)
165+
// T2.1: var f = load(&fwd, mo_{consume|acquire}) // pointer-mediated acquisition
166+
// T2.2: cas(&addr, o, f, mo_release) // this method
167+
//
168+
// Thread 3 (Java)
169+
// T3.1: var o = load(&addr, mo_acquire) // volatile read
170+
// T3.2: if (o != null)
171+
// T3.3: var r = load(&o.x, mo_relaxed)
172+
//
173+
// r is guaranteed to contain "1".
174+
//
175+
// Without GC involvement, there is synchronizes-with edge from T1.2 to T3.1,
176+
// which guarantees this. With GC involvement, when LRB copies the object and
177+
// another thread updates the reference to it, we need to have the transitive edge
178+
// from T1.4 to T2.1 (that one is guaranteed by forwarding accesses), plus the edge
179+
// from T2.2 to T3.1 (which is brought by this CAS).
180+
//
181+
// Note that we do not need to "acquire" in these methods, because we do not read the
182+
// failure witnesses contents on any path, and "release" is enough.
183+
//
184+
185+
inline void ShenandoahHeap::atomic_update_oop(oop update, oop* addr, oop compare) {
143186
assert(is_aligned(addr, HeapWordSize), "Address should be aligned: " PTR_FORMAT, p2i(addr));
144-
return (oop) Atomic::cmpxchg(addr, c, n);
187+
Atomic::cmpxchg(addr, compare, update, memory_order_release);
145188
}
146189

147-
inline oop ShenandoahHeap::cas_oop(oop n, narrowOop* addr, narrowOop c) {
190+
inline void ShenandoahHeap::atomic_update_oop(oop update, narrowOop* addr, narrowOop compare) {
148191
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
149-
narrowOop val = CompressedOops::encode(n);
150-
return CompressedOops::decode(Atomic::cmpxchg(addr, c, val));
192+
narrowOop u = CompressedOops::encode(update);
193+
Atomic::cmpxchg(addr, compare, u, memory_order_release);
151194
}
152195

153-
inline oop ShenandoahHeap::cas_oop(oop n, narrowOop* addr, oop c) {
196+
inline void ShenandoahHeap::atomic_update_oop(oop update, narrowOop* addr, oop compare) {
154197
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
155-
narrowOop cmp = CompressedOops::encode(c);
156-
narrowOop val = CompressedOops::encode(n);
157-
return CompressedOops::decode(Atomic::cmpxchg(addr, cmp, val));
198+
narrowOop c = CompressedOops::encode(compare);
199+
narrowOop u = CompressedOops::encode(update);
200+
Atomic::cmpxchg(addr, c, u, memory_order_release);
201+
}
202+
203+
inline bool ShenandoahHeap::atomic_update_oop_check(oop update, oop* addr, oop compare) {
204+
assert(is_aligned(addr, HeapWordSize), "Address should be aligned: " PTR_FORMAT, p2i(addr));
205+
return (oop) Atomic::cmpxchg(addr, compare, update, memory_order_release) == compare;
206+
}
207+
208+
inline bool ShenandoahHeap::atomic_update_oop_check(oop update, narrowOop* addr, narrowOop compare) {
209+
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
210+
narrowOop u = CompressedOops::encode(update);
211+
return (narrowOop) Atomic::cmpxchg(addr, compare, u, memory_order_release) == compare;
212+
}
213+
214+
inline bool ShenandoahHeap::atomic_update_oop_check(oop update, narrowOop* addr, oop compare) {
215+
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
216+
narrowOop c = CompressedOops::encode(compare);
217+
narrowOop u = CompressedOops::encode(update);
218+
return CompressedOops::decode(Atomic::cmpxchg(addr, c, u, memory_order_release)) == compare;
219+
}
220+
221+
// The memory ordering discussion above does not apply for methods that store NULLs:
222+
// then, there is no transitive reads in mutator (as we see NULLs), and we can do
223+
// relaxed memory ordering there.
224+
225+
inline void ShenandoahHeap::atomic_clear_oop(oop* addr, oop compare) {
226+
assert(is_aligned(addr, HeapWordSize), "Address should be aligned: " PTR_FORMAT, p2i(addr));
227+
Atomic::cmpxchg(addr, compare, oop(), memory_order_relaxed);
228+
}
229+
230+
inline void ShenandoahHeap::atomic_clear_oop(narrowOop* addr, oop compare) {
231+
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
232+
narrowOop cmp = CompressedOops::encode(compare);
233+
Atomic::cmpxchg(addr, cmp, narrowOop(), memory_order_relaxed);
234+
}
235+
236+
inline void ShenandoahHeap::atomic_clear_oop(narrowOop* addr, narrowOop compare) {
237+
assert(is_aligned(addr, sizeof(narrowOop)), "Address should be aligned: " PTR_FORMAT, p2i(addr));
238+
Atomic::cmpxchg(addr, compare, narrowOop(), memory_order_relaxed);
158239
}
159240

160241
inline bool ShenandoahHeap::cancelled_gc() const {

src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,9 @@ void reference_set_discovered<narrowOop>(oop reference, oop discovered) {
117117
}
118118

119119
template<typename T>
120-
static bool reference_cas_discovered(oop reference, oop discovered);
121-
122-
template<>
123-
bool reference_cas_discovered<narrowOop>(oop reference, oop discovered) {
124-
volatile narrowOop* addr = reinterpret_cast<volatile narrowOop*>(java_lang_ref_Reference::discovered_addr_raw(reference));
125-
narrowOop compare = CompressedOops::encode(NULL);
126-
narrowOop exchange = CompressedOops::encode(discovered);
127-
return Atomic::cmpxchg(addr, compare, exchange) == compare;
128-
}
129-
130-
template<>
131-
bool reference_cas_discovered<oop>(oop reference, oop discovered) {
132-
volatile oop* addr = reinterpret_cast<volatile oop*>(java_lang_ref_Reference::discovered_addr_raw(reference));
133-
return Atomic::cmpxchg(addr, oop(NULL), discovered) == NULL;
120+
static bool reference_cas_discovered(oop reference, oop discovered) {
121+
T* addr = reinterpret_cast<T *>(java_lang_ref_Reference::discovered_addr_raw(reference));
122+
return ShenandoahHeap::atomic_update_oop_check(discovered, addr, NULL);
134123
}
135124

136125
template <typename T>

0 commit comments

Comments
 (0)