I believe there are some differences between what .NET does and what mainstream Java does. For instance, objects can be stack allocated even if they can't be turned into collections of scalars. This allows the JIT to stack allocate small known-sized arrays.
Linq contains a goodly number of hand-crafted special-case enumerators for common collections, or collections with certain interfaces, or span projections that are really nice optimizations but can complicate things for the JIT.
There indeed is a regression if the method is only called a few times. But not if it is called frequently.
With BenchmarkDotNet it may not be obvious which scenario you intend to measure and which one you end up measuring. BDN runs the benchmark method enough times to exceed some overall "goal" time for measuring (250 ms I think). This may require many calls or may just require one.
Long-running methods (like the one here) transition mid-execution to more optimized versions, via on-stack replacement (OSR), after roughly 50K iterations. So you end up running optimized code either if the method is called a lot or loops frequently.
The OSR transition happens here, but between .net8 and .net9 some aspects of loop optimizations in OSR code regressed.
In .NET, even in optimized methods, there can be "untracked" lifetimes where a stack slot is reported live to GC throughout the extent of a method, so presumably these can lead to the "over-reporting" cases mentioned.
The number of trackable lifetimes was 64 in .NET Framework but has been steadily increased in modern .NET and is now 1024, so it's rarely a capacity issue; but there are cases where we can't effectively reason about lifetimes.
For us another big drawback to conservative scanning is that any object referred to by a conservative reference cannot be relocated, since the reference might be live and is not guaranteed to be a GC reference; these objects are (in our parlance) effectively pinned, and this causes additional overhead.
There are a few interesting places where Galois Theory touches on compilation/programming.
Abstract interpretation models a potentially infinite set of program behaviors onto a simpler (often finite) model that is (soundly) approximate and easier to reason about (via Galois connections); here the analogy is to Galois Theory connecting infinite fields with finite groups. I often think about this when working on Value Numbering for instance.
Also (perhaps a bit of stretch) it's interesting to think of extending a computational domain (say integers) with additional values (say an error value) as a kind of field extension, and as with field extensions, sometimes (perhaps unexpectedly) complications arise (eg loss of unique factorization :: LLVM's poison & undef, or NaNs).
Something closer to a "pure codegen/runtime" example perhaps: I have data showing Roslyn (the C# compiler, itself written in C#) speeds up between ~2x and ~3x running on .NET 8 vs .NET 4.7.1. Roslyn is built so that it can run either against full framework or core, so it's largely the same application IL.
We are a small tight-knit team, happy to both teach and learn new ways of making code run faster.
If you're curious about the kind of work we have been doing recently, check out the JIT section of https://devblogs.microsoft.com/dotnet/performance-improvemen... and or https://github.com/dotnet/runtime/blob/main/docs/design/core...