3

I'm writing some custom IL and need something equivalent to return SomeStaticField != null;. This was my natural conclusion:

volatile.ldsfld ...SomeStaticField //volatile is needed here for unrelated reasons
ldnull
ceq
not
ret

However, this doesn't seem to work. I confirmed that SomeStaticField is null, but yet this function will end up returning true. I know C# uses branches for such a construct, and I could use it too, but it baffles me as to why this wouldn't have the expected behavior

A complete and verifiable example (as a library):

.assembly extern /*23000001*/ mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly 'BareMetal'
{
  .custom instance void class [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::'.ctor'() =  (
                01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01       ) // ceptionThrows.

  .hash algorithm 0x00008004
  .ver  1:0:0:0
}
.module BareMetal.dll 


.namespace Earlz.BareMetal
{
  .class public auto ansi abstract sealed beforefieldinit BareMetal
        extends [mscorlib]System.Object
  {
    .method public static hidebysig
           default bool FooTest ()  cil managed
    {
        .maxstack 2
        ldnull
        ldnull
        ceq
        not
        ret
    }

  }
}
Earlz
  • 62,085
  • 98
  • 303
  • 499
  • It should work, [ceq is definitely used for null checks](http://stackoverflow.com/questions/24023705/when-i-use-is-operator-why-there-is-only-a-null-check-in-il-code). Please provide a [mcve]. – Heinzi Aug 02 '16 at 15:50
  • 1
    `not` is not it. Always best to let the C# compiler generate the msil first and look at it with ildasm.exe. Just about nobody thinks to use `cgt.un`. – Hans Passant Aug 02 '16 at 16:01
  • @Heinzi I updated to add a complete example (replacing the ldsfld with a ldnull for simplicity) – Earlz Aug 02 '16 at 16:02
  • @HansPassant is cgt safe to use against null and reference types? I assumed that was only valid for numerics – Earlz Aug 02 '16 at 16:03
  • You want to file a bug report against the C# compiler? Hehe. It is safe. – Hans Passant Aug 02 '16 at 16:05

1 Answers1

6

not computes the bitwise complement of its operand: ~1 is -2, which, being non-zero, is true. The canonical way of negating a boolean is ldc.i4.0 ; ceq.

And, as @HansPassant pointed out, the canonical way of doing a != null comparison directly is cgt.un -- all valid references are "greater than zero" when interpreted as unsigned values. That such comparisons are safe is explicitly documented in ECMA-335, section I.12.1.5:

In particular, object references can be:

...

  1. Created as a null reference (ldnull)

...

Managed pointers have several additional base operations.

...

  1. Unsigned comparison and conditional branches based on two managed pointers (bge.un, bge.un.s, bgt.un, bgt.un.s, ble.un, ble.un.s, blt.un, blt.un.s, cgt.un, clt.un).
Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • I've always used `brtrue` or `brfalse` for doing a null check since I don't have to do a `ldnull` for the comparison. Is there a difference in this case that makes the comparison versions preferable? – Kyle Aug 02 '16 at 16:24
  • 1
    @Kyle: no. This is just how you'd do it if you need the result of the comparison as a boolean, for whatever reason (maybe to combine expressions and save on branches later). If you want to branch immediately, `brtrue` and `brfalse` are indeed the common way to do it (so common that `brinst` and `brnull` are aliases, in fact). Of course you could also branch and load a constant boolean; what's better depends on what you're doing and what the JIT compiler ultimately generates. – Jeroen Mostert Aug 02 '16 at 17:51