Questo codice, assolutamente simmetrico, l'ho scritto apposta per essere compilato sia in C# che in J#, senza modifiche:
// foo.txt
class Test
{
static int i = 0;
// J# entry point
// vjc foo.txt
public static void main(System.String[] args)
{
i++;
System.Console.WriteLine(i);
Main(args);
}
// C# entry point
// csc foo.txt
public static void Main(System.String[] args)
{
i++;
System.Console.WriteLine(i);
main(args);
}
}
Eseguendo il foo.exe, arriva la sorpresa: il numero di cicli che la variante J# riesce a fare (159830), supera il numero di cicli della variante C# (159781), fino al messaggio:
Process is terminated due to StackOverflowException.
Incuriosito da questa strana vittoria del J# :-) ho guardato il codice IL generato. Quando compiliamo con C#, il codice IL per i corpi dei metodi main e Main è identico. Quando compiliamo con J# invece, il corpo dell'entry point, main, è quello di Main inquadrato da:
ldtoken [vjslib]com.ms.vjsharp.lang.ObjectImpl
call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::RunClassConstructor(valuetype [mscorlib]System.RuntimeTypeHandle)
// qui il corpo del Main...
all'inizio e:
// qui il corpo del Main...
call void [vjslib]com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
alla fine.
Cos'ho provato a fare allora? Ho aggiunto nel metodo Main, cioè nell'entry point di C# seguendo il modello dell'entry point di J#, l'equivalente (C# e J#) di questi due pezzi in IL. Lo snippet è risultato così:
// foo.txt
class Test
{
static int i = 0;
// J# entry point
// vjc foo.txt
public static void main(System.String[] args)
{
i++;
System.Console.WriteLine(i);
Main(args);
}
// C# entry point
// csc /r:vjslib.dll foo.txt
public static void Main(System.String[] args)
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(com.ms.vjsharp.lang.ObjectImpl).TypeHandle);
i++;
System.Console.WriteLine(i);
main(args);
com.ms.vjsharp.util.Utilities.cleanupAfterMainReturns();
}
}
Ricompilando adesso in C# e in J# e rieseguendo foo, otteniamo lo stesso numero di cicli: 159830!
Si vede però come RunClassConstructor mangia un po' di stack. Se lo togliamo via lasciando solo cleanupAfterMainReturns, otteniamo addiritura un incremento del numero di cicli nella variante C#: 159914! Dal suo nome sembra che veramente faccia qualche clean up after main returns... :-) Eppure si trova dopo la chiamata di main...