Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: potential GC hole with collectible code in delegate #105082

Open
huoyaoyuan opened this issue Jul 18, 2024 · 3 comments
Open

JIT: potential GC hole with collectible code in delegate #105082

huoyaoyuan opened this issue Jul 18, 2024 · 3 comments
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged Priority:2 Work that is important, but not critical for the release
Milestone

Comments

@huoyaoyuan
Copy link
Member

huoyaoyuan commented Jul 18, 2024

Originally discovered in #104731 (comment)

When a delegate references collectible code (dynamic method or collectible assembly), and being the only thing keeping the code alive, the code may be collected before invoked.

Consider the following method:

        [MethodImpl(MethodImplOptions.NoInlining)]
        static void Test(Action<int> action, int counts)
        {
            int sum = 0;
            for (int i = 0; i < counts; i++)
            {
                sum += i;
            }
            action(sum);
        }

Current codegen on arm64:

; Assembly listing for method CSPlayground.Program:Test(System.Action`1[int],int) (FullOpts)
; Emitting BLENDED_CODE for generic ARM64 - Windows
; FullOpts code
; optimized code
; fp based frame
; fully interruptible
; No PGO data
; invoked as altjit
; Final local variable assignments
;
;  V00 arg0         [V00,T03] (  4,  4   )     ref  ->   x2         class-hnd single-def <System.Action`1[int]>
;  V01 arg1         [V01,T02] (  4,  7   )     int  ->   x1         single-def
;  V02 loc0         [V02,T01] (  4, 10   )     int  ->   x0        
;  V03 loc1         [V03,T00] (  5, 17   )     int  ->   x3        
;# V04 OutArgs      [V04    ] (  1,  1   )  struct ( 0) [sp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
;
; Lcl frame size = 0

G_M16562_IG01:        ; bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IG
            stp     fp, lr, [sp, #-0x10]!
            mov     fp, sp
            mov     x2, x0
                             ; gcrRegs +[x2]
						;; size=12 bbWeight=1 PerfScore 2.00
G_M16562_IG02:        ; bbWeight=1, gcrefRegs=0004 {x2}, byrefRegs=0000 {}, byref, isz, align
            mov     w0, wzr
            mov     w3, wzr
            cmp     w1, #0
            ble     G_M16562_IG04
            align   [4 bytes for IG03]
            align   [0 bytes]
            align   [0 bytes]
            align   [0 bytes]
						;; size=20 bbWeight=1 PerfScore 3.00
G_M16562_IG03:        ; bbWeight=4, gcrefRegs=0004 {x2}, byrefRegs=0000 {}, loop=IG03, byref, isz
            add     w0, w0, w3
            add     w3, w3, #1
            cmp     w3, w1
            blt     G_M16562_IG03
						;; size=16 bbWeight=4 PerfScore 10.00
G_M16562_IG04:        ; bbWeight=1, gcrefRegs=0004 {x2}, byrefRegs=0000 {}, byref
            mov     w1, w0
            ldr     x0, [x2, #0x08]
                             ; gcrRegs +[x0]
            ldr     x2, [x2, #0x18]
                             ; gcrRegs -[x2]
            blr     x2
                             ; gcrRegs -[x0]
                             ; gcr arg pop 0
						;; size=16 bbWeight=1 PerfScore 7.50
G_M16562_IG05:        ; bbWeight=1, epilog, nogc, extend
            ldp     fp, lr, [sp], #0x10
            ret     lr
						;; size=8 bbWeight=1 PerfScore 2.00

; Total bytes of code 72, prolog size 12, PerfScore 24.50, instruction count 21, allocated bytes for code 72 (MethodHash=da6bbf4d) for method CSPlayground.Program:Test(System.Action`1[int],int) (FullOpts)

The ldr x2, [x2, #0x18] instruction loads the function pointer from the delegate object. Now no register holds the delegate object, and GC is not aware of the function pointer in x2. If GC happens before the next blr x2 instruction, it may collect the delegate object and associated code.

The loop in front of the invocation makes the method fully interruptible, and thus GC can happen at any instruction except prolog/epilog.

All RISC architectures should be suffered from this issue. In xarch, loading the function pointer can be contained in the call instruction like call [rax+0x18], so GC won't lose the track.

@huoyaoyuan huoyaoyuan added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 18, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Jul 18, 2024
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@EgorBo EgorBo self-assigned this Jul 18, 2024
@EgorBo EgorBo removed the untriaged New issue has not been triaged by the area owner label Jul 18, 2024
@EgorBo EgorBo added this to the 9.0.0 milestone Jul 18, 2024
@EgorBo
Copy link
Member

EgorBo commented Jul 18, 2024

Thanks for noticing the issue!

@huoyaoyuan
Copy link
Member Author

An ideal fix would be making GC info aware of the function pointer in register.

GC should treat it like some IP that appears in a frame of the stack, to keep the code alive. It's not a brand new concept, just need to properly report it.

@EgorBo EgorBo added the Priority:2 Work that is important, but not critical for the release label Jul 22, 2024
@EgorBo EgorBo added the in-pr There is an active PR which will close this issue when it is merged label Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI in-pr There is an active PR which will close this issue when it is merged Priority:2 Work that is important, but not critical for the release
Projects
None yet
Development

No branches or pull requests

2 participants