MSIL / CIL
The portable, stack-based bytecode of the .NET platform — known informally as MSIL and standardized as Common Intermediate Language (CIL) — that every managed language compiles to before just-in-time or ahead-of-time compilation to native code.
Created by Microsoft (Common Language Runtime team), standardized by Ecma International and ISO/IEC
MSIL / CIL is the intermediate bytecode that the entire .NET platform is built upon. When you compile a C#, F#, or Visual Basic .NET program, the compiler does not produce machine code for a particular processor. Instead it produces Intermediate Language — a compact, processor- and operating-system-neutral instruction set — and packages it together with descriptive metadata into an assembly. Only when the program actually runs does the Common Language Runtime (CLR) translate that IL into native instructions, either just-in-time (JIT) as methods are first called, or ahead-of-time (AOT) when the application is published. This two-stage model — compile once to portable IL, then compile again to native code per machine — is the defining characteristic of managed code on .NET.
One language, two names. “MSIL” and “CIL” refer to the same instruction set. Microsoft Intermediate Language was the name used during the .NET betas around 2000–2001. When the platform’s core was standardized as ECMA-335 in December 2001, the instruction set was formally christened Common Intermediate Language (CIL). The casual shorthand IL is used for both. The MSIL name lives on in tooling — the
ilasmassembler, theildasmdisassembler, and theMSILplatform target in many build systems. This page treats MSIL and CIL as interchangeable, with a slight preference for the standardized name CIL.
History and Origins
CIL emerged from Microsoft’s effort, in the late 1990s, to build a managed application platform that became .NET. The platform was previewed publicly at the 2000 Professional Developers Conference, and the runtime’s bytecode at that stage was called MSIL. The ambitions behind it were deliberately broad: a single execution engine capable of hosting many programming languages, providing automatic memory management, enforcing type safety, and running code portably across machines without recompilation from source.
To position the platform as an open technology rather than a proprietary product, Microsoft — together with Intel, Hewlett-Packard, and others — submitted the core specifications to Ecma International. In December 2001 the Ecma General Assembly ratified the first edition of ECMA-335, the Common Language Infrastructure standard. Its five partitions define, among other things, the Common Type System, the file format of assemblies, the metadata tables, and — in Partition III — the CIL instruction set itself. Ecma submitted the work to ISO/IEC through the fast-track process, and it was published as ISO/IEC 23271 (the C# language being standardized in parallel as ISO/IEC 23270).
The first production release arrived with .NET Framework 1.0 on 13 February 2002, bundling the CLR, the base class libraries, and the IL tooling. The execution model it established — emit IL, ship IL, JIT-compile to native code at load time — is essentially the same one .NET uses more than two decades later.
Design Philosophy
CIL is a stack-based virtual machine language. Rather than referencing named registers, its instructions push operands onto an evaluation stack and pop results back off. To add two numbers, code loads each operand and then issues a single add instruction that consumes the top two stack slots and pushes the sum. This stack discipline keeps the instruction encoding small and keeps the IL independent of any real processor’s register file — leaving the JIT free to map the abstract stack onto the actual registers of an x86, x64, or ARM chip.
Several principles set CIL apart from a raw assembly language:
- Typed, managed instructions. IL is not an untyped stream of bytes. It is bound to the Common Type System (CTS), and every assembly carries metadata describing its types, method signatures, and fields. That metadata is what makes garbage collection, reflection, and runtime security checks possible.
- Verifiability. A well-defined subset of IL is verifiable: before executing it, the runtime can prove the code is type-safe and cannot corrupt memory. IL can also express unverifiable operations — raw pointers,
unsafecode — but verifiability is the foundation of the safety guarantees managed code provides. - Language neutrality. The IL and CTS were designed so object-oriented, functional, and procedural languages could all target them. The Common Language Specification (CLS) defines a shared subset that guarantees interoperability between languages.
- Deferred compilation. Because IL is lowered to native code only at run time — or, increasingly, ahead of time — the same assembly can run on different architectures, and the compiler can tune its output for the exact CPU it encounters.
Key Features
A handful of elements characterize the MSIL / CIL experience:
- Assemblies and metadata. An assembly (a
.dllor.exe) bundles IL with a self-describing metadata manifest. This is what enables reflection, late binding, and tools that inspect or rewrite code with no access to source. ilasmandildasm. The IL assembler and disassembler let developers read the IL emitted by any compiler — and even hand-author IL. Round-tripping an assembly throughildasm→ edit →ilasmis a classic technique for studying and patching .NET binaries.System.Reflection.Emit. These APIs let a program build and execute new IL methods at run time, the basis for many serializers, mocking libraries, and high-performance ORMs.- Runtime generics. Unlike platforms that erase generic type information, the CLR has, since version 2.0 (2005), represented generics directly in IL and metadata — so full type information survives to run time.
A small example — the body of a method that adds two integers, written in IL assembly:
.method public static int32 Add(int32 a, int32 b) cil managed
{
.maxstack 2
ldarg.0 // push the first argument
ldarg.1 // push the second argument
add // pop two, push their sum
ret // return the top of the stack
}
Evolution
The formal standard has been stable — the most recent revision is the 6th edition of ECMA-335, published in June 2012 — but the practical IL ecosystem has evolved continuously alongside the runtimes that consume it:
- CLR 2.0 (2005) added runtime generics, extending the IL metadata and instruction set.
- Mono 1.0 (2004) demonstrated that the standardized IL could run on a wholly independent, open-source engine, executing the same assemblies produced for Microsoft’s runtime.
- .NET Core 1.0 (2016) brought an open-source, cross-platform CLR (CoreCLR) and a re-engineered JIT (RyuJIT), running IL on Linux and macOS as well as Windows.
- .NET 5 (2020) unified the .NET Framework and .NET Core lineages into a single product, all on the same IL execution model.
- Native AOT (introduced for console apps in .NET 7, 2022) compiles IL ahead of time into a self-contained native binary, removing run-time JIT compilation entirely for scenarios that demand fast startup, a small footprint, or platforms that forbid generating code at run time.
Current Relevance
MSIL / CIL remains the indispensable substrate of one of the world’s most widely used development platforms. Every .NET application — ASP.NET Core services, desktop apps, and Unity games alike — exists as IL before it ever becomes native code. The platform’s dual JIT/AOT story means IL underpins both the dynamic, reflection-heavy workloads where just-in-time compilation excels and the constrained, startup-sensitive workloads now served by Native AOT and Unity’s IL2CPP.
IL is also a lively target for tooling. Decompilers such as ILSpy and the historical dnSpy, the Mono.Cecil rewriting library, and a range of profilers and obfuscators all operate at the IL level. Because IL is comparatively easy to read and to decompile back into high-level source, it sits at the center of both .NET reverse engineering and the obfuscation industry that exists to resist it.
Why It Matters
CIL proved, at scale, that a single intermediate language could be a genuine common target for many programming languages while still delivering type safety, garbage collection, and competitive performance through JIT compilation. Standardizing it through Ecma and ISO/IEC turned a vendor bytecode into an open specification with independent implementations — Mono most prominently — and helped make the broader CLI a durable, cross-platform foundation. Alongside the JVM’s bytecode, MSIL / CIL is one of the two most influential managed intermediate representations in computing, and the compile-to-portable-IL-then-JIT-or-AOT model it popularized remains a template that later runtimes and platforms continue to follow.
This entry covers the same instruction set as the MSIL page; “CIL” is its standardized name under ECMA-335, and the two terms are used interchangeably throughout the .NET ecosystem.
Timeline
Notable Uses & Legacy
The .NET language compilers
C#, F#, and Visual Basic .NET all compile to CIL rather than to native code. The Roslyn compiler (C# and VB) and the F# compiler emit IL into assemblies that the runtime later JIT- or AOT-compiles, which is what lets these languages interoperate.
Unity and IL2CPP
The Unity game engine compiles a project's C# scripts to CIL, then its IL2CPP toolchain converts that IL into C++ that is compiled to native code for platforms — such as consoles and iOS — that require or prefer ahead-of-time compilation.
Run-time code generation
System.Reflection.Emit and expression trees let programs emit and execute CIL at run time. Serializers, ORMs, mocking frameworks, and dynamic proxies use this to build fast, specialized code paths on the fly.
Decompilers and assembly rewriters
Tools such as ILSpy, the historical dnSpy, ildasm, and the Mono.Cecil library read, edit, and decompile CIL — enabling debugging, profiling, obfuscation, security analysis, and reverse engineering of .NET assemblies.
Mono and the cross-platform CLI
Mono and its descendants execute the identical CIL assemblies produced on Windows, underpinning cross-platform development and, historically, the Xamarin mobile stack.
Language Influence
Influenced By
Running Today
Run examples using the official Docker image:
docker pull mcr.microsoft.com/dotnet/sdk:8.0Example usage:
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:8.0 dotnet build