Skip to content

Instantly share code, notes, and snippets.

@gfoidl
Created January 22, 2018 10:14
Show Gist options
  • Save gfoidl/14b07dfe8ee5cb093f216f8a85759d88 to your computer and use it in GitHub Desktop.
Save gfoidl/14b07dfe8ee5cb093f216f8a85759d88 to your computer and use it in GitHub Desktop.
Struct readonly field
using System;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var foo1 = new Foo1();
Console.WriteLine(foo1.Struct());
Console.WriteLine(foo1.ReadOnlyStruct());
var foo2 = new Foo2();
Console.WriteLine(foo2.Struct());
Console.WriteLine(foo2.ReadOnlyStruct());
}
}
public class Foo1
{
private Point1 _point1 = new Point1(1, 2);
private Point2 _point2 = new Point2(3, 4);
public int Struct() => _point1.X + _point1.Y;
public int ReadOnlyStruct() => _point2.X + _point2.Y;
}
public class Foo2
{
private readonly Point1 _point1 = new Point1(1, 2);
private readonly Point2 _point2 = new Point2(3, 4);
public int Struct() => _point1.X + _point1.Y;
public int ReadOnlyStruct() => _point2.X + _point2.Y;
}
public struct Point1
{
public int X { get; }
public int Y { get; }
public Point1(int x, int y)
{
this.X = x;
this.Y = y;
}
}
public readonly struct Point2
{
public int X { get; }
public int Y { get; }
public Point2(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
.class public auto ansi beforefieldinit ConsoleApp2.Foo1
extends [System.Runtime]System.Object
{
// Fields
.field private valuetype ConsoleApp2.Point1 _point1
.field private valuetype ConsoleApp2.Point2 _point2
// Methods
.method public hidebysig
instance int32 Struct () cil managed
{
// Method begins at RVA 0x208e
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1
IL_0006: call instance int32 ConsoleApp2.Point1::get_X()
IL_000b: ldarg.0
IL_000c: ldflda valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1
IL_0011: call instance int32 ConsoleApp2.Point1::get_Y()
IL_0016: add
IL_0017: ret
} // end of method Foo1::Struct
.method public hidebysig
instance int32 ReadOnlyStruct () cil managed
{
// Method begins at RVA 0x20a7
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2
IL_0006: call instance int32 ConsoleApp2.Point2::get_X()
IL_000b: ldarg.0
IL_000c: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2
IL_0011: call instance int32 ConsoleApp2.Point2::get_Y()
IL_0016: add
IL_0017: ret
} // end of method Foo1::ReadOnlyStruct
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x20c0
// Code size 33 (0x21)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: ldc.i4.2
IL_0003: newobj instance void ConsoleApp2.Point1::.ctor(int32, int32)
IL_0008: stfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo1::_point1
IL_000d: ldarg.0
IL_000e: ldc.i4.3
IL_000f: ldc.i4.4
IL_0010: newobj instance void ConsoleApp2.Point2::.ctor(int32, int32)
IL_0015: stfld valuetype ConsoleApp2.Point2 ConsoleApp2.Foo1::_point2
IL_001a: ldarg.0
IL_001b: call instance void [System.Runtime]System.Object::.ctor()
IL_0020: ret
} // end of method Foo1::.ctor
} // end of class ConsoleApp2.Foo1
.class public auto ansi beforefieldinit ConsoleApp2.Foo2
extends [System.Runtime]System.Object
{
// Fields
.field private initonly valuetype ConsoleApp2.Point1 _point1
.field private initonly valuetype ConsoleApp2.Point2 _point2
// Methods
.method public hidebysig
instance int32 Struct () cil managed
{
// Method begins at RVA 0x20e4
// Code size 30 (0x1e)
.maxstack 2
.locals init (
[0] valuetype ConsoleApp2.Point1
)
IL_0000: ldarg.0
IL_0001: ldfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1
IL_0006: stloc.0
IL_0007: ldloca.s 0
IL_0009: call instance int32 ConsoleApp2.Point1::get_X()
IL_000e: ldarg.0
IL_000f: ldfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1
IL_0014: stloc.0
IL_0015: ldloca.s 0
IL_0017: call instance int32 ConsoleApp2.Point1::get_Y()
IL_001c: add
IL_001d: ret
} // end of method Foo2::Struct
.method public hidebysig
instance int32 ReadOnlyStruct () cil managed
{
// Method begins at RVA 0x210e
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2
IL_0006: call instance int32 ConsoleApp2.Point2::get_X()
IL_000b: ldarg.0
IL_000c: ldflda valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2
IL_0011: call instance int32 ConsoleApp2.Point2::get_Y()
IL_0016: add
IL_0017: ret
} // end of method Foo2::ReadOnlyStruct
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2127
// Code size 33 (0x21)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: ldc.i4.2
IL_0003: newobj instance void ConsoleApp2.Point1::.ctor(int32, int32)
IL_0008: stfld valuetype ConsoleApp2.Point1 ConsoleApp2.Foo2::_point1
IL_000d: ldarg.0
IL_000e: ldc.i4.3
IL_000f: ldc.i4.4
IL_0010: newobj instance void ConsoleApp2.Point2::.ctor(int32, int32)
IL_0015: stfld valuetype ConsoleApp2.Point2 ConsoleApp2.Foo2::_point2
IL_001a: ldarg.0
IL_001b: call instance void [System.Runtime]System.Object::.ctor()
IL_0020: ret
} // end of method Foo2::.ctor
} // end of class ConsoleApp2.Foo2
; Assembly listing for method ConsoleApp2.Foo1:Struct():int:this
; Emitting BLENDED_CODE for X64 CPU with AVX
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00]
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax
;
; Lcl frame size = 0
G_M35460_IG01:
G_M35460_IG02:
488D4708 lea rax, bword ptr [rdi+8]
488BF8 mov rdi, rax
8B3F mov edi, dword ptr [rdi]
037804 add edi, dword ptr [rax+4]
8BC7 mov eax, edi
G_M35460_IG03:
C3 ret
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo1:Struct():int:this
; ============================================================
; Assembly listing for method ConsoleApp2.Foo1:ReadOnlyStruct():int:this
; Emitting BLENDED_CODE for X64 CPU with AVX
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00]
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax
;
; Lcl frame size = 0
G_M43334_IG01:
G_M43334_IG02:
488D4710 lea rax, bword ptr [rdi+16]
488BF8 mov rdi, rax
8B3F mov edi, dword ptr [rdi]
037804 add edi, dword ptr [rax+4]
8BC7 mov eax, edi
G_M43334_IG03:
C3 ret
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo1:ReadOnlyStruct():int:this
; ============================================================
; Assembly listing for method ConsoleApp2.Foo2:Struct():int:this
; Emitting BLENDED_CODE for X64 CPU with AVX
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
; V00 this [V00,T02] ( 8, 8 ) ref -> rdi this class-hnd
;* V01 loc0 [V01 ] ( 0, 0 ) struct ( 8) zero-ref ld-addr-op
; V02 tmp0 [V02,T08] ( 2, 4 ) int -> rdi
; V03 tmp1 [V03,T06] ( 5, 5 ) int -> rdi V01.<X>k__BackingField(offs=0x00) P-INDEP
; V04 tmp2 [V04,T07] ( 5, 5 ) int -> rax V01.<Y>k__BackingField(offs=0x04) P-INDEP
; V05 tmp3 [V05,T00] ( 12, 24 ) byref -> rax
; V06 tmp4 [V06,T03] ( 4, 8 ) byref -> [rsp+0x00]
;# V07 OutArgs [V07 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00]
; V08 cse0 [V08,T01] ( 11, 11 ) int -> rdi
; V09 cse1 [V09,T04] ( 8, 8 ) int -> rax
; V10 cse2 [V10,T05] ( 5, 5 ) byref -> rax
;
; Lcl frame size = 8
G_M35363_IG01:
50 push rax
G_M35363_IG02:
488D4708 lea rax, bword ptr [rdi+8]
8B38 mov edi, dword ptr [rax] ; copy to registers
8B4004 mov eax, dword ptr [rax+4]
03C7 add eax, edi
G_M35363_IG03:
4883C408 add rsp, 8
C3 ret
; Total bytes of code 17, prolog size 1 for method ConsoleApp2.Foo2:Struct():int:this
; ============================================================
; Assembly listing for method ConsoleApp2.Foo2:ReadOnlyStruct():int:this
; Emitting BLENDED_CODE for X64 CPU with AVX
; o7
ptimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
; V00 this [V00,T01] ( 5, 5 ) ref -> rdi this class-hnd
; V01 tmp0 [V01,T04] ( 2, 4 ) int -> rdi
; V02 tmp1 [V02,T00] ( 4, 8 ) byref -> rdi
; V03 tmp2 [V03,T03] ( 3, 6 ) byref -> rax
;# V04 OutArgs [V04 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00]
; V05 cse0 [V05,T02] ( 6, 6 ) byref -> rax
;
; Lcl frame size = 0
G_M43497_IG01:
G_M43497_IG02:
488D4710 lea rax, bword ptr [rdi+16]
488BF8 mov rdi, rax
8B3F mov edi, dword ptr [rdi]
037804 add edi, dword ptr [rax+4]
8BC7 mov eax, edi
G_M43497_IG03:
C3 ret
; Total bytes of code 15, prolog size 0 for method ConsoleApp2.Foo2:ReadOnlyStruct():int:this
; ============================================================
@gfoidl
Copy link
Author

gfoidl commented Jan 22, 2018

Discussion

Here it is not about performance, but on value-type semantics and copy-behavior.

Non-readonly field

The produced code is straight-forward, the compiler can assume a mutable struct.

Note: in the IL there is ldflda

Readonly field

Readonly struct

The compiler knows a immutable struct, hence can produce optimized code with ldflda.

Normal / Mutable struct

The compiler must assume that the struct is mutated, but this is not allowed by the readonly modifier for the field. Hence the compiler needs to make a copy and use ldfld. The produced code is equivalent to:

public int Struct()
{
    Point1 point = this._point1;
    int x        = point.X;
    point        = this._point1;
    return x + point.Y;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment