dotnet/runtime

[API Proposal]: Introduce an intrinsic for more efficient lambda generation

Open

#85.014 aperta il 18 apr 2023

Vedi su GitHub
 (30 commenti) (11 reazioni) (0 assegnatari)C# (5445 fork)batch import
api-suggestionarea-System.Runtime.CompilerServiceshelp wantedin-pr

Metriche repository

Star
 (17.886 star)
Metriche merge PR
 (Merge medio 12g 11h) (661 PR mergiate in 30 g)

Descrizione

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:

  • Refactor delegates to be immutable (see below) have GC fields be immutable: #99200
  • Build a functional-enough prototype: #125901
  • Submit an API proposal for review, incorporating learnings from the prototype

Guida contributor