3.0 KiB
lastmod, author, categories, date, title, summary
lastmod | author | categories | date | title | summary | |
---|---|---|---|---|---|---|
2025-06-20T00:00:00.0000000+01:00 | patrick |
|
2025-06-20T00:00:00.0000000+01:00 | Things I've learned about the .NET runtime | While writing an MSIL interpreter, I discovered a bunch of unexpected things about the .NET runtime. |
The JIT is actually required
(Or, in NativeAOT, other mechanisms that aren't just what's in System.Private.CoreLib.)
Some methods are JIT intrinsics, but I had previously thought the JIT was just an optimisation. This is false! For example, Type.TypeHandle throws and must be swapped out by the JIT. I don't know how this works on NativeAOT.
Console.WriteLine
is incredibly complicated
Console.WriteLine
exercises at least the following:
Top-level no-exception-handler behaviour change in net9
In earlier versions of .NET, finally
blocks and Dispose
methods were liable not to run when an unhandled exception terminates program execution.
This was documented as implementation-defined behaviour in the C# language spec, though it doesn't appear to be documented elsewhere.
That implementation-defined behaviour means my programs basically all contain this boilerplate:
let reallyMain argv =
0
let main argv =
try
reallyMain argv
with
| _ -> reraise ()
However, in .NET 9 (I think) this behaviour appears to have been changed, and now the .NET runtime does the obvious thing - Dispose
methods run and finally
blocks execute even when an exception causes shutdown!
The .NET runtime doesn't actually model uint32
Neither on the eval stack nor as a CLI intrinsic type are uint32
or uint64
modelled; they're stored as int32
and int64
with two's complement.