mirror of
https://github.com/Smaug123/static-site-pipeline
synced 2025-10-05 16:28:41 +00:00
.NET runtime learnings
This commit is contained in:
52
hugo/content/posts/2025-06-20-dotnet-runtime-learnings.md
Normal file
52
hugo/content/posts/2025-06-20-dotnet-runtime-learnings.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
lastmod: "2025-06-20T00:00:00.0000000+01:00"
|
||||
author: patrick
|
||||
categories:
|
||||
- programming
|
||||
date: "2025-06-20T00:00:00.0000000+01:00"
|
||||
title: Things I've learned about the .NET runtime
|
||||
summary: "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](https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L467-L471) 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:
|
||||
|
||||
* [Locking](https://github.com/dotnet/runtime/blob/4314ca34f3c620c180ef8f7fc28973af7ff47ad4/src/libraries/System.Private.CoreLib/src/System/SR.cs#L52)
|
||||
* [`Ldtoken` and type lookup](https://github.com/dotnet/runtime/blob/4314ca34f3c620c180ef8f7fc28973af7ff47ad4/src/libraries/System.Private.CoreLib/src/System/SR.cs#L84)
|
||||
* [Exception handling](https://github.com/dotnet/runtime/blob/4314ca34f3c620c180ef8f7fc28973af7ff47ad4/src/libraries/System.Private.CoreLib/src/System/SR.cs#L50)
|
||||
* [Byrefs](https://github.com/dotnet/runtime/blob/4314ca34f3c620c180ef8f7fc28973af7ff47ad4/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L174)
|
||||
* [Delegates](https://github.com/dotnet/runtime/blob/4314ca34f3c620c180ef8f7fc28973af7ff47ad4/src/libraries/System.Private.CoreLib/src/System/Type.cs#L737)
|
||||
|
||||
# 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](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/exceptions#214-how-exceptions-are-handled), though it doesn't appear to be documented elsewhere.
|
||||
|
||||
That implementation-defined behaviour means my programs basically all contain this boilerplate:
|
||||
|
||||
```fsharp
|
||||
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](https://github.com/Smaug123/WoofWare.PawPrint/blob/c747d6eb3a2d138debce9c6cca763287bb21a7a9/WoofWare.PawPrint/EvalStack.fs#L60) nor [as a CLI intrinsic type](https://github.com/Smaug123/WoofWare.PawPrint/blob/c747d6eb3a2d138debce9c6cca763287bb21a7a9/WoofWare.PawPrint/BasicCliType.fs#L39) are `uint32` or `uint64` modelled; they're stored as `int32` and `int64` with two's complement.
|
Reference in New Issue
Block a user