Background and motivation
Roslyn currently caches lambda delegates in static mutable fields in nested classes and initializes them lazily at the use site (guarded by a null check).
This pattern is inefficient for current runtimes, being hard to detect and optimize correctly, leading to dead code being left in generated assembly and making optimizations like delegate inlining more tricky.
Discussion here ended up with two possible solutions: creating a runtime intrinsic that'd create and cache the delegate for Roslyn or caching the delegates in static readonly fields which CoreCLR and Native AOT can analyze easily even today but that'd come with some metadata cost.
The API can create delegates closed over null for most cases, putting them on the Frozen Object Heap.
For delegates to instance methods on generic types, the runtime would be required to create an instance instead. Such support would make the implementation relatively more complicated, as such it's not decided yet whether the API should work then or throw. If it is to work, the API should forbid the owning type from having a cctor and a finalizer due to only creating instances in some cases and leaving them uninitialized.
The API can't always return the same instance due to not being able to always expand as intrinsic for shared generics, the instance count will however always be small and they'll always be value equal to each other.
In AOT scenarios, the API requires to match a specific IL pattern recognized by the runtime to work for non reflection visible methods.
cc @jkotas @EgorBo @jaredpar @CyrusNajmabadi
API Proposal
From #125901:
public class RuntimeHelpers
{
[Intrinsic]
public static TDelegate GetDelegate<TDelegate>(nint method) where TDelegate : Delegate;
}
API Usage
This would be used internally by Roslyn when emitting lambdas.
Alternative Designs
Have Roslyn store delegates in static readonly fields - would work with CoreCLR and Native AOT, not require any runtime changes, but it'd introduce either more metadata bloat or more eager delegate object allocation (we'd either need a separate nested class per lambda or calling one lambda would then create delegates for all the lambdas in the nested class).
Risks
All runtimes including Mono would need to have this implemented.
It's difficult to guarantee the same instance will be always returned on all runtimes.
Tasks:
Background and motivation
Roslyn currently caches lambda delegates in static mutable fields in nested classes and initializes them lazily at the use site (guarded by a null check).
This pattern is inefficient for current runtimes, being hard to detect and optimize correctly, leading to dead code being left in generated assembly and making optimizations like delegate inlining more tricky.
Discussion here ended up with two possible solutions: creating a runtime intrinsic that'd create and cache the delegate for Roslyn or caching the delegates in
static readonlyfields which CoreCLR and Native AOT can analyze easily even today but that'd come with some metadata cost.The API can create delegates closed over null for most cases, putting them on the Frozen Object Heap.
For delegates to instance methods on generic types, the runtime would be required to create an instance instead. Such support would make the implementation relatively more complicated, as such it's not decided yet whether the API should work then or throw. If it is to work, the API should forbid the owning type from having a cctor and a finalizer due to only creating instances in some cases and leaving them uninitialized.
The API can't always return the same instance due to not being able to always expand as intrinsic for shared generics, the instance count will however always be small and they'll always be value equal to each other.
In AOT scenarios, the API requires to match a specific IL pattern recognized by the runtime to work for non reflection visible methods.
cc @jkotas @EgorBo @jaredpar @CyrusNajmabadi
API Proposal
From #125901:
API Usage
This would be used internally by Roslyn when emitting lambdas.
Alternative Designs
Have Roslyn store delegates in
static readonlyfields - would work with CoreCLR and Native AOT, not require any runtime changes, but it'd introduce either more metadata bloat or more eager delegate object allocation (we'd either need a separate nested class per lambda or calling one lambda would then create delegates for all the lambdas in the nested class).Risks
All runtimes including Mono would need to have this implemented.
It's difficult to guarantee the same instance will be always returned on all runtimes.
Tasks:
be immutable (see below)have GC fields be immutable: Replace Delegate MethodInfo cache with the MethodDesc #99200