Skip to content

optimization of indirect calls #44610

Open

Description

@AndyAyersMS

With the advent of function pointers we will now see an increased number of indirect call cases that the jit can optimize to direct calls. This requires a transformation similar to the one we do for devirtualization. There may be additional wrinkles, say if someone takes the address of an intrinsic method we would want intrinsic recognition to kick in too. So in the importer perhaps this needs to sit just upstream from impImportCall.

Overall this should be structured in a similar way to devirtualization -- opportunistically transforming calls during importation to enable subsequent inlining, and then perhaps retry later after inlining to at least remove overhead. Would be nice to be able to change over in the optimizer too, but that may prove challenging as we start to bake in many details in morph.

In principle we could do something similar with locally created delegates but there are a number of missing pieces that prevent us from seeing through from delegate creation to invocation.

using System;

class X
{
    static int F() => 33;
   
    public unsafe static int Main()
    {
        delegate*<int> f = &X.F;

        return f() + 67;
    }
}

produces

 Assembly listing for method X:Main():int
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 OutArgs      [V00    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;* V01 tmp1         [V01    ] (  0,  0   )    long  ->  zero-ref    "impImportIndirectCall"
;
; Lcl frame size = 40

G_M46779_IG01:              ;; offset=0000H
       4883EC28             sub      rsp, 40
                                                ;; bbWeight=1    PerfScore 0.25
G_M46779_IG02:              ;; offset=0004H
       48B830F3FDD6FC7F0000 mov      rax, 0x7FFCD6FDF330
       FFD0                 call     rax
       83C043               add      eax, 67
                                                ;; bbWeight=1    PerfScore 3.50
G_M46779_IG03:              ;; offset=0013H
       4883C428             add      rsp, 40
       C3                   ret
                                                ;; bbWeight=1    PerfScore 1.25

Not sure of the priority of this just yet, I am looking at some apps that use calli fairly heavily and need to better understand how many of these can be optimized. So marking as future for now.

This optimization will also intersect with guarded devirtualization / PGO, provided we can profile indirect targets and see biased distributions.

category:cq
theme:devirtualization
skill-level:expert
cost:medium

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Nov 12, 2020
  2. added this to the Future milestone on Nov 12, 2020
  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Nov 12, 2020
  4. modified the milestones: Future, 7.0.0 on Dec 17, 2021
  5. hez2010 commented on Dec 29, 2021

    @hez2010
    Contributor

    Since ASP.NET Core 6, delegates and lambdas are being heavily used in the new minimal-api design which uses delegates as endpoint handlers here and there. I believe if JIT can optimize delegates to direct calls, a huge performance improvement will be introduced.

  6. modified the milestones: 7.0.0, 8.0.0 on Jul 6, 2022
  7. modified the milestones: 8.0.0, Future on Mar 26, 2023
  8. 10 remaining items

  9. MichalPetryka commented on Apr 12, 2023

    @MichalPetryka
    Contributor

    And I guess being able to check whether a MethodDesc* the function returns points to a valid MethodDesc would be a solution, but I don't see how that could be checked?

  10. MichalPetryka commented on Apr 12, 2023

    @MichalPetryka
    Contributor

    It's implementation defined

    I've checked GCC, Clang and MSVC, they all seem to detect this, inline the valid case and leave the invalid one as is so that it AVs on call (same as dotnet right now).

  11. MichalPetryka commented on Apr 12, 2023

    @MichalPetryka
    Contributor

    It does not sound very pay for play. This is niche optimization, vast majority of existing function pointer uses are not going to benefit from it, but they would still need to pay for it.

    Hmm, the only solution I could think of that'd have small runtime overhead would be introducing a hidden byte/ushort/uint field with a bitmask for all static readonly managed ftn ptrs in the type and store a handle instead of the actual value in the field then instead, set a bit in the mask and then check the mask when in the helper for static readonly fields and spawn FTN_ADDR instead of CNS_INT there. But I feel like this would be too much to handle for me (and reflection would need to be handled too).

  12. jkotas commented on Apr 13, 2023

    @jkotas
    Member

    introducing a hidden byte/ushort/uint field with a bitmask for all static readonly managed ftn ptrs in the type

    This would be between impossible and very complicated. #44610 (comment) should be a lot easier than this.

  13. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Apr 22, 2023
  14. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Jun 4, 2023
  15. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Jun 5, 2023
  16. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Jul 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions