RFR: Eliminate extra forwarding pointer per object

Roman Kennke rkennke at redhat.com
Wed May 8 10:07:14 UTC 2019


>>> *) Maybe we should fork introducing new tmp to CAS? That would make the patch easier to backport, I
>>> think.
>>
>> Not sure. The change would make the register allocators free one more register but then not use it.
>> I left it out of this iteration for now.
> 
> Yeah, the extra cleanness in the final patch might not worth the effort of doing the CAS change
> beforehand. Maybe we should break some overly long lines in AArch64 CAS code (compare sh_aarch64.ad
> and sh_x86_64.ad), while we are at it.
> 
> 
>>> I actually think that we can keep ShenandoahBrooksPointer and put all that util code there. It would
>>> still be doing its job: abstracting away the forwarding data handling.
>>
>> I reinstated the ShenandoahForwarding utility class. However, it is hard to abstract the protocol
>> because the two parts strictly belong together. However, this seems all the more reason to abstract
>> it properly. The only thing that happens in between the two blocks is actually allocating and
>> copying the object. So we could call into, say, ShenandoahForwarding::evacuate() which would do the
>> prefix and suffix, and call back to say copy = ShHeap::allocate_and_copy(obj); or such for the
>> payload. What do you think?
> 
> I don't understand off-hand why the protocol is different. For example, why do you need to drag
> old_mark in SH:evacuate_object? You can still have try_update_forwardee to check that "old" mark is
> not forwarded yet, and use that?

But that would complicate the protocol, wouldn't it?
Currently we check if object has been forwarded before we actually do 
the copy. If we see it is already forwarded, we bail out right away. If 
we didn't 'remember' the old_mark at this point, we wouldn't know what 
to compare with when attempting the CAS. Yes, we probably could re-load 
it afterwards but that would require that we check again if another 
thread beat us just like we did at the start. It seems more efficient 
(fewer mark-loads) and cleaner to me to enclose the actual copy with the 
protocol like I did.

> Pseudocode for try_update_forwardee:
> 
> // returns "true" on success
> bool try_update_forwardee(p, copy_val):
>    markOop old = p->mark_raw();
>    if (old->is_marked()):
>      return false;
>    markOop new_mark = markOopDesc::encode_pointer_as_mark(copy_val);
>    markOop prev_mark = p->cas_set_mark_raw(new_mark, old_mark);
>    return prev_mark == old_mark;
> 
> ...then:
> 
> if (try_update_forwardee(p, copy_val)):
>    shenandoah_assert_correct(NULL, copy_val);
>    return copy_val;
> } else {
>    return ShenandoahForwarding::get_forwardee(p);
> }

Yes, that would work, but doesn't look very efficient. The CAS already 
gives us the offending new mark-word, we don't actually need to re-load 
it. And also, we don't need to re-load it between head and tail of the 
protocol either. Let me try to come up with a cleaner expression of the 
protocol.

>>> *) In ShenandoahMarkCompact, do these need to be public?
>>>
>>>     76 public:
>>>     77   void preserve_mark(oop obj);
>>>     78   void restore_marks();
>>>     79   void adjust_marks();
>>
>> I forgot to mention ShenandoahMarkCompact. This completely overhauls how ShMarkCompact stores
>> forwarding information. It's changed to basically work like other GCs and stores the forwarding in
>> the mark-word. However, the mark word must sometimes be preserved. Therefore we store it to the
>> side. The 3 methods above handle that. They are public because they are called from closures.
>> Alternatively, we could make the closures friends of ShMarkCompact. ?
> 
> Yes, befriending closures would look cleaner, I think.

Ok, will do.

>> Updated patch:
>> http://cr.openjdk.java.net/~rkennke/eliminate-fwdptr/webrev.01/
> 
> Other nits:
> 
>   *) The dance like that needs some rationale/pseudo-code?
> 
>   326   Label done;
>   327   __ movptr(tmp, Address(dst, oopDesc::mark_offset_in_bytes()));
>   328   __ notptr(tmp);
>   329   __ testb(tmp, markOopDesc::marked_value);
>   330   __ jccb(Assembler::notZero, done);
>   331   __ orptr(tmp, markOopDesc::marked_value);
>   332   __ notptr(tmp);
>   333   __ mov(dst, tmp);
>   334   __ bind(done);

Ah yeah. It is surprisingly awkward to write efficient asm for the 
decoding. I'll explain it in comments.

>   *) In here, you can call get_forwardee_raw_unchecked to have only one shenandoah_assert_correct assert:
> 
>    45 inline oop ShenandoahForwarding::get_forwardee(oop obj) {
>    46   shenandoah_assert_correct(NULL, obj);
>    47   return oop(get_forwardee_raw(obj));
>    48 }

Ok, will do.

Thanks,
Roman


More information about the shenandoah-dev mailing list