If you use R# to code generate, make sure to regenerate if things change

At work the other day when I was looking through a pull request. I noticed what looked like a very strange equality method on a C# struct, shown here as if it were named Foo.

public override bool Equals(object obj)
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Foo) obj);

The code looks very wasteful – you cannot inherit from structs, so rather than the GetType malarkey you’d surely expect just an ‘is’. In the end it turned out that the Equals method had been generated by R#, but at the time it was generated, the containing type had been a class and this was later converted to a struct.

For a struct, R# would have generated the much more reasonable looking

public override bool Equals(object obj)
if (ReferenceEquals(null, obj)) return false;
return obj is Foo && Equals((Foo) obj);

While I was checking my understanding of what was going on, I spent a little time looking at the x86 code that was generated by the various methods.

A cut down version of the code using is,

public bool Test2(object obj)
if (!(obj is A)) return false;
return true;

generates the code

0:004> u 002f00f8 002f0119
002f00f8 55              push    ebp
002f00f9 8bec            mov     ebp,esp
002f00fb 85d2            test    edx,edx                                               <<<< check null
002f00fd 740c            je      002f010b
002f00ff 813a2c381200    cmp     dword ptr [edx],12382Ch     <<<< check type handle
002f0105 7502            jne     002f0109
002f0107 eb02            jmp     002f010b
002f0109 33d2            xor     edx,edx                                            <<<< generate less than optimal code
002f010b 85d2            test    edx,edx
002f010d 7504            jne     002f0113
002f010f 33c0            xor     eax,eax
002f0111 5d              pop     ebp
002f0112 c3              ret
002f0113 b801000000      mov     eax,1
002f0118 5d              pop     ebp
002f0119 c3              ret

The C# ‘is’ can simply generate check that the type handle (vtable) for the object is the desired type.

The longer form code, simplified to

public bool Test1(object obj)
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != GetType()) return false;
return true;

is a little more complicated.

0:004> u 002f0098 002f00e7
002f0098 55              push    ebp
002f0099 8bec            mov     ebp,esp
002f009b 57              push    edi
002f009c 56              push    esi
002f009d 50              push    eax
002f009e 8bf9            mov     edi,ecx
002f00a0 8bf2            mov     esi,edx
002f00a2 85f6            test    esi,esi     <<<<<<< null   check
002f00a4 7507            jne     002f00ad
002f00a6 33c0            xor     eax,eax
002f00a8 59              pop     ecx
002f00a9 5e              pop     esi
002f00aa 5f              pop     edi
002f00ab 5d              pop     ebp
002f00ac c3              ret
002f00ad b92c381200      mov     ecx,12382Ch             <<<< type handle for the structure type
002f00b2 e84920e2ff      call    00112100   <<<<<< ***** See below for this!!!!!!!!!!
002f00b7 8945f4          mov     dword ptr [ebp-0Ch],eax
002f00ba 8bce            mov     ecx,esi
002f00bc e86bad2472      call    mscorlib_ni+0x27ae2c (7253ae2c)    <<<<<< GetType
002f00c1 8bf0            mov     esi,eax
002f00c3 0fbe07          movsx   eax,byte ptr [edi]
002f00c6 8b55f4          mov     edx,dword ptr [ebp-0Ch]
002f00c9 884204          mov     byte ptr [edx+4],al
002f00cc 8bca            mov     ecx,edx
002f00ce e859ad2472      call    mscorlib_ni+0x27ae2c (7253ae2c)   <<<<<<<< GetType
002f00d3 3bf0            cmp     esi,eax
002f00d5 7407            je      002f00de
002f00d7 33c0            xor     eax,eax
002f00d9 59              pop     ecx
002f00da 5e              pop     esi
002f00db 5f              pop     edi
002f00dc 5d              pop     ebp
002f00dd c3              ret
002f00de b801000000      mov     eax,1
002f00e3 59              pop     ecx
002f00e4 5e              pop     esi
002f00e5 5f              pop     edi
002f00e6 5d              pop     ebp
002f00e7 c3              ret

In the **** line we allocate a boxed instance of Foo. The first line gets the type handle of the struct type, and this is passed as the first argument to the called method.
mov     ecx,12382Ch
call    00112100

Addresses differ as this is a different run, but the called method is doing allocation of a new boxed instance.

0:000>  u 00162100 0016211b
00162100 8b4104          mov     eax,dword ptr [ecx+4]
00162103 648b15380e0000  mov     edx,dword ptr fs:[0E38h]  <<< Use fast allocation buffer
0016210a 034240          add     eax,dword ptr [edx+40h]
0016210d 3b4244          cmp     eax,dword ptr [edx+44h]
00162110 7709            ja      0016211b
00162112 894240          mov     dword ptr [edx+40h],eax
00162115 2b4104          sub     eax,dword ptr [ecx+4]
00162118 8908            mov     dword ptr [eax],ecx   <<<<< Set the object header
0016211a c3              ret
0016211b e915788b73      jmp     clr!JIT_New (73a19935)   <<<< fast path not available so punt

I thought it was quite interesting to see the boxing required to call GetType on ‘this’ when ‘this’ is a struct instance, and it is also interesting to see the bump allocation that happens on the fast path allocation.

The key point however, is that code generation is all very well, but it is useful to record the assumptions behind the generation so that you can regenerate if things change.

This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s