-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Extra bound checks are not eliminated for pinning #35748
Comments
I suspect this could be fixed in Roslyn: to use |
The JIT should be able to figure this out, even without the |
Updated the description. Even if I help JIT to convert |
Example: public static void Test1(int[] array)
{
fixed (int* p = array)
{
}
} IR for
@AndyAyersMS any idea why |
Suspect this happens because pinned locals are untracked, untracked locals can't be in SSA, and VN relies on SSA for locals. Thus VN doesn't know that the two appearances of V02 refer to the same value. Unwinding this a bit:
We could probably unblock optimization by allowing pinned locals to be tracked for SSA purposes but then reported as untracked for GC purposes. Not sure how hard this would be. We'd also need to be careful not to dead store any nulling writes to pinned locals. |
@AndyAyersMS - what part of GC reporting is missing for pinned registers?
There could be some other parts that I could be missing.
|
There should not be any restrictions in non-x86 GC info encoding that get in the way of optimizing pinned locals. The problem with pinned locals is that their lifetime has to be extended till end of method or till they are explicitly cleared (or assigned some other value that is pinned implicitly). The JIT takes a shortcut to achieve this lifetime extension by marking them as untracked. |
Interestingly a pinning local behaves like an address-exposed (writes to it are globally sideeffecting), although there could be nothing referencing it. |
There are various of comments in the code about this restriction -- if those are wrong-headed we should clean them up. runtime/src/coreclr/inc/gcinfo.h Lines 26 to 35 in 00474fc
runtime/src/coreclr/jit/gcencode.cpp Lines 4701 to 4715 in dac4446
From my understanding there are various issues here:
Today the two notions of tracked largely coincide, but enabling a subset of JIT optimizations only requires the first. In many/most cases the pinned local ends up not having many appearances, so one might not be giving up much by requiring the pinned local to be on the stack and untracked for gc purposes, in which case the current reporting scheme with the restrictions above is sufficient. The JIT can't reason about the liveness of the pinned local very well right now. Instead of trying to do this, perhaps it can model these locals as jit-tracked and always live (disabling dead stores and possibly other optimizations that would remove defs, similar in some ways to how we handle EH write thru), and gc-liveness-untracked (that is, forcibly initialized to null in the prolog, and on the stack). This could unblock optimizations based on the current value like bounds check elimination. Or perhaps the JIT could figure out the true liveness extents and add keep-alives or similar in the proper places and just treat these locals as (mostly) a normal tracked locals. We'd still need some kind of restriction on removing defs (eg copy prop, forward sub) so we don't lose the pin all together since it is tied to the specific local. I think this analysis is hard. We'd need to determine the maximum extent of any non-GC use based off of each pinned def. cc @dotnet/jit-contrib |
|
To elaborate on "assigned some other value", I suspect that "implicitly" means a location that is pinned by definition (e.g., pinned_var = &local), but explicitly is also a (complicated) possibility:
It's also convenient to think about |
Codegen for this looks fixed today: ; Assembly listing for method Program:Test(int[]) (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; Final local variable assignments
;
; V00 arg0 [V00,T00] ( 5, 4 ) ref -> rcx class-hnd single-def <int[]>
; V01 loc0 [V01 ] ( 3, 1.50) byref -> [rsp+0x20] must-init pinned
; V02 OutArgs [V02 ] ( 1, 1 ) struct (32) [rsp+0x00] do-not-enreg[XS] addr-exposed "OutgoingArgSpace" <UNNAMED>
; V03 tmp1 [V03,T01] ( 2, 2 ) long -> rcx "Cast away GC"
; V04 cse0 [V04,T02] ( 2, 1 ) int -> rax "CSE #01: moderate"
;
; Lcl frame size = 40
G_M55702_IG01: ;; offset=0x0000
sub rsp, 40
xor eax, eax
mov qword ptr [rsp+0x20], rax
;; size=11 bbWeight=1 PerfScore 1.50
G_M55702_IG02: ;; offset=0x000B
test rcx, rcx
je SHORT G_M55702_IG04
;; size=5 bbWeight=1 PerfScore 1.25
G_M55702_IG03: ;; offset=0x0010
mov eax, dword ptr [rcx+0x08]
test eax, eax
je SHORT G_M55702_IG04
add rcx, 16
mov bword ptr [rsp+0x20], rcx
mov rcx, bword ptr [rsp+0x20]
call [Program:DoWork(ulong)]
xor eax, eax
mov bword ptr [rsp+0x20], rax
;; size=34 bbWeight=0.50 PerfScore 4.88
G_M55702_IG04: ;; offset=0x0032
add rsp, 40
ret
;; size=5 bbWeight=1 PerfScore 1.25
; Total bytes of code 55, prolog size 11, PerfScore 8.88, instruction count 16, allocated bytes for code 55 (MethodHash=340b2669) for method Program:Test(int[]) (FullOpts)
; ============================================================ |
Nice! Wonder what exactly has fixed it |
Probably RBO? It's fixed even in ad4bdd1 which is the oldest core_root I have. |
The following "array pinning" syntax
is a С# 7(?) syntax sugar for:
As you can see, there is no way this code can throw an out of bounds exception.
Codegen (master):
Unfortunately,
Assertion prop
doesn't optimize the bound check away.category:cq
theme:pinning
skill-level:expert
cost:extra-large
impact:medium
The text was updated successfully, but these errors were encountered: