Managed execution
The CLR
Two languages, one intermediate language, one machine to run it.
The Common Language Runtime is .NET's virtual machine. F# source does not run directly; it is compiled to a portable intermediate language packaged in an assembly, and the runtime turns that into native code at the moment it is needed. The same machine runs C#. This dive follows a tiny function down through that pipeline, then names the two ideas that make the shared machine possible: a common type system, and the metadata that lets one assembly call another.
From source to native: the managed pipeline
"Managed" means the runtime, not the program, owns the low-level work — memory, type safety, and the final
translation to machine code. The path from text to execution has a fixed shape. A compiler reads F# (or C#)
source and emits CIL — Common Intermediate Language, once called MSIL — a stack-based
instruction set that no CPU runs directly. The CIL, plus type metadata, is packaged in an
assembly: a .dll or .exe file. At run time
the JIT compiler translates each method's CIL into native instructions the first time it is
called, and the garbage collector manages the heap underneath.
Step the example through the stages below. The source is a one-line F# function that adds two integers; the same function written in C# compiles to the same CIL, because both target the one runtime.
The managed execution pipeline · step through it
let add x y = x + y. Plain text, not yet runnable; the same logic in C# is int Add(int x, int y) => x + y;.
The CIL below uses four real, well-known opcodes for a trivial two-argument add:
ldarg.0 and ldarg.1 push the two arguments onto the evaluation
stack, add pops them and pushes the sum, and ret returns the
value on top. The exact bytes a real compiler emits include extra metadata; this is the shape, not a literal
transcript.
// the body of `add`, in CIL — four opcodes ldarg.0 // push argument 0 (x) onto the stack ldarg.1 // push argument 1 (y) onto the stack add // pop x and y, push x + y ret // return the value on top of the stack
The compiler's job ends at CIL. From there the runtime takes over — JIT to native, run, collect. That hand-off is what "managed" names.
One type system: the CTS
The shared machine needs a shared notion of "type". That is the Common Type System (CTS): the rules for what a type is, how values are laid out, and how methods are called, defined once for the whole platform. An F# record, an F# tuple, an F# function value — each is a .NET type under the CTS, the same kind of thing a C# class or struct is. Because there is one type system, an F# value crossing into C# code is not translated or wrapped; it is already a .NET object.
A narrower contract sits inside the CTS: the Common Language Specification (CLS), the subset of features every .NET language is expected to understand. A library that stays within the CLS can be consumed from any .NET language. This is the mechanism behind the module's headline — F# can call a C# library, and C# can call an F# library, because both speak the CTS.
The idea
One type system for the whole platform. A value has one .NET type, whatever language declared it.
In F#
An F# record is a CTS reference type; an int is
System.Int32. C# code receives them as ordinary .NET objects.
"F# is a .NET language" is not a slogan. It is a statement about the type system: F#'s values are the platform's types.
The standard, and real generics
The runtime, the intermediate language, and the type system are not Microsoft-internal details. They are standardised as the Common Language Infrastructure (CLI), published as ECMA-335 (also ISO/IEC 23271). ECMA-335 defines CIL, the CTS, the metadata format, and the virtual execution system — the contract the CLR implements. (C# the language has its own standard, ECMA-334.)
One feature of that type system matters especially for F#: generics. The CLR gained real generic types in .NET 2.0 (2005), with runtime support — generic instantiations are reified, carrying their type arguments at run time, not erased. That work came from Microsoft Research, by Don Syme and Andrew Kennedy. F#'s parametric polymorphism maps onto these real CLR generics, which is one reason F#'s functional style runs without a translation penalty: a generic F# function is a generic .NET method.
- CLR
- Common Language Runtime — the virtual machine that JIT-compiles CIL and runs it under garbage collection.
- CIL
- Common Intermediate Language (once MSIL) — the stack-based instruction set every .NET compiler emits.
- assembly
- A
.dll/.exepackaging CIL plus type metadata; the unit of deployment. - CTS / CLS
- Common Type System and its cross-language subset — the shared type vocabulary that makes interop possible.
- ECMA-335
- The Common Language Infrastructure standard (also ISO/IEC 23271): CIL, the CTS, metadata, the execution model.
- generics
- Reified runtime generics, added in .NET 2.0 (2005) from MSR work by Don Syme and Andrew Kennedy.
The CLR is a published standard with reified generics — which is why F#'s polymorphic functions compile to ordinary, generic .NET methods.
References
Sources
- Microsoft — .NET documentation — the CLR, managed execution, the JIT, and garbage collection.
- Microsoft — F# documentation — how F# types map to the .NET type system.
- Don Syme et al. — The Early History of F# — the account of generics and the CLR as F#'s foundation.
Related in this course
- C0.2 · .NET & the CLR — the module hub.
- C0.2.2 — The evolution of .NET — the platform that ships this runtime.
- C0 · History — the chapter landing.
- Elixir F0.2 — the evolution of the runtime — the BEAM, a different managed runtime, for contrast.