Runtime: Optimize out write barriers for fields in ref-like structs

Created on 5 Jan 2018  路  9Comments  路  Source: dotnet/runtime

From https://github.com/dotnet/coreclr/pull/15745#issuecomment-355481958 :

Hmm, I wonder if there are sufficient guarantees that by ref like types only live on the stack for the JIT to be able to eliminate GC write barriers when writing to fields of such types.

Yes, there are. It would be nice optimization to have.

cc @mikedn

category:cq
theme:barriers
skill-level:expert
cost:small

JitUntriaged area-CodeGen-coreclr enhancement optimization tenet-performance

Most helpful comment

I tried a quick and dirty implementation to see what happens. I wasn't expecting to see anything in the jit-diffs but it turns out that there's already a case like this in corelib. A bunch of GC write barriers disappeared from DateTimeParse.ParseByFormat due to DateTimeResult being a ref struct.

All 9 comments

And does a byref to a ref-like type serve any useful purpose?

It seems that it could be converted to an unmanaged pointer because it doesn't need to be reported to the GC. Though it looks like attempting to write an object reference to an unmanaged pointer makes the JIT emit a checked write barrier rather than no barrier, hmm...

An example containing 2 cases where the GC write barrier could be eliminated if the write location is known to be on the stack:
```C#
ref struct RefLike
{
public object o;

[MethodImpl(MethodImplOptions.NoInlining)]
public void NotInlined(object o)
{
    this.o = o; // GC WB, the JIT currently assumes that `this` can be on the GC heap
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inlined(object o)
{
    this.o = o; // No GC WB if the method is inlined and `this` is on the stack.
}

}

public static void Main()
{
RefLike r = new RefLike();
Test(new object(), ref r);
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void Test(object o, ref RefLike ar)
{
RefLike lr = new RefLike();
lr.o = o; // No GC WB
lr.Inlined(o); // No GC WB
lr.NotInlined(o); // GC WB inside NotInlined
ar.o = o; // GC WB, the JIT currently assumes that ar can point into the GC heap
}
```

I tried a quick and dirty implementation to see what happens. I wasn't expecting to see anything in the jit-diffs but it turns out that there's already a case like this in corelib. A bunch of GC write barriers disappeared from DateTimeParse.ParseByFormat due to DateTimeResult being a ref struct.

Another example - a non ref struct inside a ref struct:
```
struct NotRefLike
{
public object o;
}

ref struct RefLike
{
public object o;
public NotRefLike n;
...
}
...
lr.n.o = o; // No GC WB already, it's known to be a local variable
ar.n.o = o; // No GC WB with my hack. Fortunately the JIT transforms ar.n.o into a single indir.

lr.n.FooBar(); // Of course, any not inlined method of NotRefLike will have GC WBs.

ref object ro = ref lr.n.o;
Console.WriteLine();
ro = o; // GC WB unfortunately. Not directly related to ref-like stuff,
// the JIT just doesn't figure out that ro points into the stack.

ref struct BufferReader<TSequence> in Pipelines/Buffers and its 2x non ref struct Position is an example where this would help https://github.com/dotnet/corefxlab/issues/2046#issuecomment-357116669

Thanks for the example, copying structs like Position is not something that I tried yet.

Yep, copying structs require a bit more hacking. Currently copying a struct like Position generates something like

       488D7118             lea      rsi, bword ptr [rcx+24]
       488D7908             lea      rdi, bword ptr [rcx+8]
       E8A1316B5F           call     CORINFO_HELP_ASSIGN_BYREF
       48A5                 movsq

I can change it to

       488D7118             lea      rsi, bword ptr [rcx+24]
       488D7908             lea      rdi, bword ptr [rcx+8]
       48A5                 movsq
       48A5                 movsq

I'm not sure if the use of 2 movsq to copy 16 bytes is a good idea, these instructions do not have a reputation for performance. But they surely keep the code small and avoid issues with GC. The ideal solution to copy 16 bytes would probably use SSE load/store but GC won't be happy with it since it cannot "see" the object reference stored in the first 8 bytes of the temporary XMM register.

Example https://github.com/aspnet/AspNetCore/pull/7674 resolved via inlining

Even uses CORINFO_HELP_CHECKED_ASSIGN_REF when the ref struct is fully inlined (https://github.com/dotnet/corefx/pull/35267/commits/a3cb00f9c43a7a53934c2cd7a3366e011380184f) e.g.

private ref struct ValueSegmentList
{
    public BufferSegment Segment00;
    public BufferSegment Segment01;
    public BufferSegment Segment02;
    public BufferSegment Segment03;
    public BufferSegment Segment04;
    public BufferSegment Segment05;
    public BufferSegment Segment06;
    public BufferSegment Segment07;
    public BufferSegment Segment08;
    public BufferSegment Segment09;
    public BufferSegment Segment10;
    public BufferSegment Segment11;
    public BufferSegment Segment12;
    public BufferSegment Segment13;
    public BufferSegment Segment14;
    public BufferSegment Segment15;
}

with

ValueSegmentList segmentsToReturn = default;
ref var startSegment = ref segmentsToReturn.Segment00;
int count = 0;
do
{
    BufferSegment next = from.NextSegment;
    Debug.Assert(next != null || toExclusive == null);

    from.ResetMemory();

    if ((uint)count < (uint)SegmentPoolSize)
    {
        // Store in temporary list while preforming expensive resets
        Unsafe.Add(ref startSegment, count) = from;
        count++;
    }

    from = next;
} while (from != toExclusive);
G_M48697_IG02:
       lea      rcx, bword ptr [rbp-B8H]
       vxorps   xmm0, xmm0
       vmovdqu  qword ptr [rcx], xmm0
       vmovdqu  qword ptr [rcx+16], xmm0
       vmovdqu  qword ptr [rcx+32], xmm0
       vmovdqu  qword ptr [rcx+48], xmm0
       vmovdqu  qword ptr [rcx+64], xmm0
       vmovdqu  qword ptr [rcx+80], xmm0
       vmovdqu  qword ptr [rcx+96], xmm0
       vmovdqu  qword ptr [rcx+112], xmm0
       lea      r14, bword ptr [rbp-B8H]
       xor      r15d, r15d

G_M48697_IG03:
       mov      r12, gword ptr [rsi+40]
       mov      rcx, rsi
       call     BufferSegment:ResetMemory():this
       cmp      r15d, 16
       jae      SHORT G_M48697_IG04
       movsxd   rdx, r15d
       lea      rcx, bword ptr [r14+8*rdx]
       mov      rdx, rsi
       call     CORINFO_HELP_CHECKED_ASSIGN_REF  ; checked assign
       inc      r15d

G_M48697_IG04:
       mov      rsi, r12
       cmp      rsi, rdi
       jne      SHORT G_M48697_IG03
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Timovzl picture Timovzl  路  3Comments

btecu picture btecu  路  3Comments

nalywa picture nalywa  路  3Comments

omajid picture omajid  路  3Comments

aggieben picture aggieben  路  3Comments