My Authors
Read all threads
1/N I thought it might be interesting to look at how generics are compiled to native code in .NET. A thread.
2/N When generic code is stored in IL on disk (as produced by e.g. the C#/VB/F# compiler), single generic method has a single method body. This method body is only runnable on the abstract CPU that the .NET compilers target. To run it on real hardware, it has to be translated.
3/N Real CPUs have pesky details that the abstract virtual machine doesn't care about - like registers and calling conventions.
4/N To generate the most efficient code, the code generator needs to take those details into account. Different T's have different physical representations. What was a single method body in your C#/VB/F# source code (and IL), might become multiple method bodies at runtime.
5/N For example, real CPUs can only efficiently deal with floating point numbers if they're stored in dedicated registers. To efficiently pass floating point numbers to your generic methods, these rules have to be followed by the code generator.
6/N Some T's will be so big that they don't fit in a physical register. Instead of the method taking the arguments by value as specified in the source code, it will secretly take them by reference:
7/N The runtime/codegen tries to make sharing of code possible when it's reasonable. For example, reference types are "mostly the same" and the runtime/codegen generates a shareable body for all reference type instantiations (there's a minimal perf penalty for that):
8/N But structs have different shapes and sharing is impossible. This is not a problem for JITted runtimes: it will just JIT more. AOT compilers have to compile more though - normally not an issue unless you do generic recursion or MakeGenericType/Method.
9/N .NET Native solved it by generating a single "universal shared code" method body as a backup if one of the above happens. It serves as a fallback when the efficient code wasn't possible to pregenerate. One may call this code "universal shitty code":
10/N Mono AOT has a similar universal implementation (the one in .NET Native is closed source and undocumented): mono-project.com/docs/advanced/…
11/N To add insult to injury because universal code has a "weird" calling convention, when normal code calls universal, an interpreter is involved. The interpreter translates the efficient calling convention (e.g. xmm0 reg in PickOne<double>) to the universal one (pass by ref).
12/N Basically, if you're going to do AOT __and care about perf__, avoid MakeGenericType/MakeGenericMethod and deep generic recursion. Laws of native world apply to the managed world too. Such code will work, but it's not pretty.
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with Michal Strehovský

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!