While it is an implementation detail, it is still interesting for me how .NET runtime passes type arguments to generic functions.
Consider following C# code:
using System;
void PrintT<T>() => Console.WriteLine(typeof(T));
PrintT<int>();
PrintT<string>();
PrintT<object>();
It prints correct (expected) output, because there is no type erasure:
System.Int32
System.String
System.Object
If we inspect CIL, we see following opcodes:
// in PrintT<T>
IL_0000: ldtoken !!T
IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
IL_000a: call void [System.Console]System.Console::WriteLine(object)
IL_000f: nop
// at invocation point
call void Program::'<<Main>$>g__PrintT|0_0'<int32>()
As I can understand, type handles are somehow passed to function PrintT<T>, and question is how it is implemented in interpreter and compiled code? And to be more precies what is the calling convention of some sort, if we assume that no inlining occurred.
I would expect either passing them just as other arguments (prepending or appending them in the .NET managed-code-calling-convention), or pushing them to another separate stack; both of which occur at the call site. However, I failed to find any talks, blog posts or papers on this subject.