BrainFlood: Runtime code generation via reflection in .NET
Posted 23 hours ago
Time for a bigger example!
using System.Runtime.CompilerServices;

interface Number {
    static abstract int Eval();
}

class ConstDead : Number {
    public static int Eval() {
        return 0xDEAD;
    }
}

class ConstBeef : Number {
    public static int Eval() {
        return 0xBEEF;
    }
}

class Concat<A,B> : Number
    where A: Number
    where B: Number
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int Eval() {
        return (A.Eval() << 16) + B.Eval();
    }
}

class Example {
    int Run() {
        return Concat<ConstDead,ConstBeef>.Eval();
    }
}
This combines a few ideas from the previous section: The three calls to Eval are monomorphically inlined and folded. If you throw this code in SharpLab, you'll find that Example.Run() JIT compiles to two instructions:
    L0000: mov eax, 0xdeadbeef
    L0005: ret

The [MethodImpl(MethodImplOptions.AggressiveInlining)] attribute is necessary to persuade .NET to inline the calls. Sadly, it isn't always sufficient. We need to encourage the JIT to inline and monomorphize our code. Fortunately, there's a way to do this: structs! Since structs are value types that use an unknown amount of stack space, the JIT isn't allowed to just re-use polymorphic code. Our example code becomes:
using System.Runtime.CompilerServices;

interface Number {
    int Eval();
}

struct ConstDead : Number {
    public int Eval() {
        return 0xDEAD;
    }
}

struct ConstBeef : Number {
    public int Eval() {
        return 0xBEEF;
    }
}

struct Concat<A,B> : Number
    where A: struct, Number
    where B: struct, Number
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int Eval() {
        return (default(A).Eval() << 16) + default(B).Eval();
    }
}

class Example {
    int Run() {
        return default(Concat<ConstDead,ConstBeef>).Eval();
    }
}
By some miracle, the JIT is just smart enough to optimize this the same way as the static code. Presumably it helps that the structs are empty, but I'm not sure whether it actually matters.