Managed C++ codegen for new, array manipulation, delete

Managed C++ (MC++) code generation is a cool accomplishment. I think it's another good testimony to the CLR's cross-language charter (and IL's flexibility) that we can support C++.
(Not too mention our support for more dynamic languages like F#, SML.Net, and IronPython that also compile to IL)

As the most basic example, I find it interesting to compare the IL generated for a simple program that allocates a buffer and does some manipulation when written in MC++ and C#.

MC+++ C#
 #include "stdafx.h"using namespace System;int main(){    Console::WriteLine(L"Multiplication table");    int size = 10;    int * table= new int [size];        for(int i = 0; i < size; i ++)    {        table[i] = i * i; // unsafe pointer math!    }        delete [] table; // explicit free memory    return 0;}
using System;class Program{ static void Main(string[] args) { Console.WriteLine("Multiplication table"); int size = 10; int [] table= new int [size]; for(int i = 0; i < size; i++) { table[i] = i * i; } // table will be freed by GC }}

The thing I find that causes a double-take for the most people is how MC++ can have explicit memory management (eg, "new" and "delete") and pointer arithmetic when it runs on the CLR; which does garbage collection (instead of explicit memory management) and safe references (instead of raw pointers). 
The programs do the same thing (initialize a table of squares). Here's a side-by-side comparison of the IL (with source inline) between the C# and MC++. I've made some annotations and shaded the rows corresponding to interesting differences.

description MC++ C#
declare function and locals .method assembly static int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)         main() cil managed{  .vtentry 1 : 1  // Code size       73 (0x49)  .maxstack  3  .locals ([0] int32 i,           [1] int32* table,            [2] uint32 V_2,           [3] int32 size,           [4] int32 V_4)  .method private hidebysig static void  Main(string[] args) cil managed{  .entrypoint  // Code size       47 (0x2f)  .maxstack  4  .locals init ([0] int32 size,            [1] int32[] table,            [2] int32 i,           [3] bool CS$4$0000) 
initialization   IL_0000:  ldc.i4.0  IL_0001:  stloc.s    V_4 //000006:     {  IL_0000:  nop
Write into //000006:     Console::WriteLine(L"Multiplication table");  IL_0003:  ldstr      "Multiplication table"  IL_0008:  call       void [mscorlib]System.Console::WriteLine(string) //000007:         Console.WriteLine("Multiplication table");  IL_0001:  ldstr "Multiplication table"   IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)  IL_000b:  nop
assign size=10 //000008:     int size = 10;  IL_000d:  ldc.i4.s   10  IL_000f:  stloc.3 //000009:         int size = 10;  IL_000c:  ldc.i4.s   10  IL_000e:  stloc.0
allocate table //000009:     int * table= new int [size];   IL_0010:  ldloc.3 // size  IL_0011:  stloc.2  // V_2  IL_0012:  ldloc.2  // V_2  IL_0013:  ldc.i4     0x3fffffff  IL_0018:  bgt.un.s   IL_001f  IL_001a:  ldloc.2 // V_2  IL_001b:  ldc.i4.4  IL_001c:  mul  IL_001d:  br.s       IL_0020  IL_001f:  ldc.i4.m1// we've computed number of bytes // to allocate (size * sizeof(int)). // We now pinvoke to: msvcr80*.dll!operator new(unsigned int)// The return result is raw pointer value (a void *) just like in C++.   IL_0020:  call void* modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) new(uint32)  IL_0025:  stloc.1  // storing return result as raw pointer into table  //000010:         int [] table= new int [size];   IL_000f:  ldloc.0 // size// have GC allocate array of Int32.// return result is a managed object reference  IL_0010:  newarr [mscorlib]System.Int32  IL_0015:  stloc.1  // store reference into table. 
begin for //000011:     for(int i = 0; i < size; i ++)  IL_0026:  ldc.i4.0  IL_0027:  stloc.0  IL_0028:  br.s       IL_002e  IL_002a:  ldloc.0 // jump target for loop back  IL_002b:  ldc.i4.1  IL_002c:  add  IL_002d:  stloc.0  IL_002e:  ldloc.0 // jump target   IL_002f:  ldloc.3  IL_0030:  bge.s      IL_003d // conditional exit at loop termination//000012:     { //000011: //000012:         for(int i = 0; i < size; i++)  IL_0016:  ldc.i4.0  IL_0017:  stloc.2  IL_0018:  br.s       IL_0026//000013:         {  IL_001a:  nop 
array index and assignment //000013:         table[i] = i * i; // unsafe pointer math!  IL_0032:  ldloc.1  // stack: table  IL_0033:  ldloc.0  // stack: table, i   IL_0034:  ldc.i4.4  // stack: table, i, 4      (note 4 =sizeof(int))  IL_0035:  mul   // stack: table, (i*4)  IL_0036:  add  // stack: (table + i*4)  IL_0037:  ldloc.0  // stack: (table + i*4), i  IL_0038:  dup // stack: (table + i*4), i, i  IL_0039:  mul  // stack: (table + i*4), (i*i)// stind.i4 will assign 1st (top-most) stack arg, as i4 sized data, // to address specified by 2nd stack arg.  IL_003a:  stind.i4 // blind pointer assignment, no boundary checks.  //000014:             table[i] = i * i;   IL_001b:  ldloc.1 // stack: table  IL_001c:  ldloc.2 // stack: table, i  IL_001d:  ldloc.2 // stack: table, i , i  IL_001e:  ldloc.2 // stack: table, i, i, i  IL_001f:  mul // stack: table, i, (i * i) // stelem.i4 will assign 1st (top-most) stack arg to index // from 2nd stack arg into array specified by 3rd arg.  IL_0020:  stelem.i4 // explicit array assignment, allows boundary checks. 
end for //000014:     }      IL_003b:  br.s       IL_002a  //000015:         }      IL_0021:  nop//000012:         for(int i = 0; i < size; i++)  IL_0022:  ldloc.2  IL_0023:  ldc.i4.1  IL_0024:  add  IL_0025:  stloc.2  IL_0026:  ldloc.2 // jump target from loop start  IL_0027:  ldloc.0  IL_0028:  clt  IL_002a:  stloc.3  IL_002b:  ldloc.3  IL_002c:  brtrue.s   IL_001a // loop back to continue 
free table //000016:     delete [] table;  IL_003d:  ldloc.1  IL_003e:  call       void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) delete(void*)  //000016:         // table will be freed by GC
end of functionand return
 //000017:     return 0;  IL_0043:  ldc.i4.0  IL_0044:  stloc.s    V_4//000018: }  IL_0046:  ldloc.s    V_4  IL_0048:  ret} // end of method 'Global Functions'::main
 //000017:     }//000018: }  IL_002e:  ret} // end of method Program::Main

For trivia points, I'll point out there are some minor cosmetic differences including:
- they generate the for-loop differently.  MC++ has the comparison first; C# has the comparison at the end. You can verify for yourself they're result in the same behavior.
- C# places a sequence point on the opening '{', whereas MC++ doesn't. Practically, this means when you first step into 'main', in C# you stop at '{', where as in MC++, you stop at the first line after '{'.
- MC++ needs to distinguish between managed and unmanaged strings. (update: MC++ 2005 will use context to decided, see section 5.1 in the translation guide.) Unmanaged strings are just a pointer (RVA) address to the raw string data (as they are in C++). In MC++, managed strings get prefixed with 'S', (like  S"Hello"). Managed strings are loaded with the 'ldstr' opcode.  Since all strings in C# are managed, C# doesn't need a special string prefix.
There are also some differences that help MC++ interoperate better with native code (like using the Cdecl calling convention on Main and that .vtentry thing).

For now, the interesting differences are around allocating 'table', doing the assignment "table[i] = i *i", and freeing the memory. I've shaded the rows corresponding to these actions.

The declaration of the 'table' local:
It turns out the languages compile the variable 'table' differently:
- MC++ compiles it to a "int32 *", which the runtimes views as a raw pointer. This is essentially opaque data to the CLR and may as well be a pointer-sized int.  (It corresponds to a CorElementType of ELEMENT_TYPE_PTR)
- C# compiles it to "int32[]", which the runtimes views as a managed array. This is a managed reference owned by the CLR, and lives on the GC heap. (It corresponds to a CorElementType of ELEMENT_TYPE_SZARRAY).

One significant consequence of this is debuggability. The native debugger can't do much with inspecting a int32* because it's just a raw pointer. It doesn't know it's actually pointing to an array. And it sure doesn't know how big that array is, so it couldn't display the bounds even if it wanted to. In contrast, an int32[] is far more descriptive to the debugger because managed arrays have rich bound information (see ICorDebugArrayValue in CorDebug.idl). Thus in the C# case, you can expand 'table' and see the full contents.

Allocating the buffer:
C# uses the "newarr" IL opcode to initial 'table'. Since 'table' is a managed array, and 'newarr' allocates a managed array, that should all make sense.
In contrast, MC++ calls a method 'new' which will eventually pinvoke out to msvcr80*.dll!operator new(unsigned int), and that will return a raw pointer value to allocated memory, just as you'd expect in unmanaged C++. You can step into the call in VS and see for yourself that you land in msvcr80!new.  It's the same story with the call to delete.  This is all consistent with MC++'s 'table' being a raw pointer.

Making assignments:
In the C# case, we've got a strongly typed array, and stelem.* is an il opcode for explicitly assigning into an array. This opcode has the semantics to let the jit do array boundary checks.
In the MC++ case, we're dealing with raw pointers. The stind.* IL opcode allows direct memory assignment.  You can see the raw pointer manipulation, just as we'd have in unmanaged C++, for the pointer arithmetic  It's still IL, but it's not verifiable. That means the code loses a lot of the protection, such as the explicit boundary checks from the stelem opcode.

C#  has an unsafe code regions which lets it do the same pointer operations that MC++ is doing here.

In conclusion:
While this is perhaps a laughably basic example (especially in retrospect), these sorts of qualities for MC++ codegen are a preview of the sort of techniques MC++ uses to codegen more complex constructs (like multiple inheritance, which is not supported in C#).