-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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: enable removal of try/catch if the try can't throw. #110273
Conversation
If no tree in the try region of a try/catch can throw, then we can remove the try and delete the catch. If no tree in the try region of a try/finally can throw, we can remove the try and inline the finally. This slightly generalizes the empty-try/finally opt we have been doing for a long time. (We should do something similar for try/fault, but don't, yet). Since these optimization passes are cheap, and opportunities for them arise after other optimizations and unblock subsequent optimizations, run them early, middle, and late. Resolves dotnet#107191. I expect we'll see more of these cases in the future, say if we unblock cloning of loops with EH.
@EgorBo PTAL Note we are trusting |
src/coreclr/jit/fgehopt.cpp
Outdated
if (hadDfs) | ||
{ | ||
fgInvalidateDfsTree(); | ||
m_dfsTree = fgComputeDfs(); | ||
if (hadLoops) | ||
{ | ||
m_loops = FlowGraphNaturalLoops::Find(m_dfsTree); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems unnecessary to recompute this in each of the phases. Maybe combine it into one phase, or make the actual phase that needs these annotations compute them if they aren't computed when we get there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved recomputation later (to doms for dfs, vn for loops).
Just wondering - We used to have some special meaning for code in finally with respect to ThreadAbort. Is that no longer a thing or still somehow maintained when finally is inlined? |
AFAIR all the handling for that was removed. |
That is only an issue for .NET Framework. Removal of empty-try / finally has been in .NET Core since 2.x, this just extends it a bit further to some trys that are not empty. |
I remember long time ago we could do similar optimizations in Roslyn, in theory, but in practice we could not. |
On x86 we have callfinallys within the try. In this case inside the try/finally there is a try/catch that returns from the catch, so must invoke the outer finally, thus there is a callfinally pair in the catch.
Here T0 is empty and we're trying to remove it and H0. But there is a pred edge into the middle of H0/BB15 from an outside region (BB07), which is not something my simple-minded handler removal code supports yet. |
/azp run runtime-coreclr jitstress |
Azure Pipelines successfully started running 1 pipeline(s). |
If no tree in the try region of a try/catch can throw, then we can remove the try and delete the catch. If no tree in the try region of a try/finally can throw, we can remove the try and inline the finally. This slightly generalizes the empty-try/finally opt we have been doing for a long time. (We should do something similar for try/fault, but don't, yet). Since these optimization passes are cheap, and opportunities for them arise after other optimizations and unblock subsequent optimizations, run them early, middle, and late. Resolves dotnet#107191. I expect we'll see more of these cases in the future, say if we unblock cloning of loops with EH.
With this both void Test1()
{
var x = 42;
var z = 52;
try
{
var y = 14;
z = x + y;
}
finally
{
Console.WriteLine(z);
}
}
void Test2()
{
var x = 42;
var z = 52;
try
{
var y = 14;
z = x + y;
}
catch { }
Console.WriteLine(z);
} codegen: G_M27646_IG01: ;; offset=0x0000
;; size=0 bbWeight=1 PerfScore 0.00
G_M27646_IG02: ;; offset=0x0000
mov edi, 56
;; size=5 bbWeight=1 PerfScore 0.25
G_M27646_IG03: ;; offset=0x0005
tail.jmp [System.Console:WriteLine(int)]
;; size=6 bbWeight=1 PerfScore 2.00 But the codegen of void Test3()
{
var x = 42;
var z = 52;
try
{
var y = 14;
z = x + y;
}
catch { }
finally
{
Console.WriteLine(z);
}
} codegen: G_M27646_IG01: ;; offset=0x0000
push rbp
sub rsp, 16
lea rbp, [rsp+0x10]
mov qword ptr [rbp-0x10], rsp
;; size=14 bbWeight=1 PerfScore 2.75
G_M27646_IG02: ;; offset=0x000E
mov dword ptr [rbp-0x04], 52
;; size=7 bbWeight=1 PerfScore 1.00
G_M27646_IG03: ;; offset=0x0015
mov dword ptr [rbp-0x04], 56
;; size=7 bbWeight=1 PerfScore 1.00
G_M27646_IG04: ;; offset=0x001C
mov edi, 56
call [System.Console:WriteLine(int)]
nop
;; size=12 bbWeight=1 PerfScore 3.50
G_M27646_IG05: ;; offset=0x0028
add rsp, 16
pop rbp
ret
;; size=6 bbWeight=1 PerfScore 1.75
G_M27646_IG06: ;; offset=0x002E
push rbp
sub rsp, 16
mov rbp, qword ptr [rdi]
mov qword ptr [rsp], rbp
lea rbp, [rbp+0x10]
;; size=16 bbWeight=0 PerfScore 0.00
G_M27646_IG07: ;; offset=0x003E
mov edi, dword ptr [rbp-0x04]
call [System.Console:WriteLine(int)]
nop
;; size=10 bbWeight=0 PerfScore 0.00
G_M27646_IG08: ;; offset=0x0048
add rsp, 16
pop rbp
ret
;; size=6 bbWeight=0 PerfScore 0.00 /cc: @AndyAyersMS |
This turns into a try-catch inside a try-finally. We remove the try-catch and convert the try-finally to a try-fault. But we haven't yet implemented (empty)try-fault removal. |
Seems like this is actually simple to handle; I'll put up a PR shortly. |
If no tree in the try region of a try/catch can throw, then we can remove the try and delete the catch. If no tree in the try region of a try/finally can throw, we can remove the try and inline the finally. This slightly generalizes the empty-try/finally opt we have been doing for a long time. (We should do something similar for try/fault, but don't, yet). Since these optimization passes are cheap, and opportunities for them arise after other optimizations and unblock subsequent optimizations, run them early, middle, and late. Resolves dotnet#107191. I expect we'll see more of these cases in the future, say if we unblock cloning of loops with EH.
If no tree in the try region of a try/catch can throw, then we can remove the try and delete the catch.
If no tree in the try region of a try/finally can throw, we can remove the try and inline the finally. This slightly generalizes the empty-try/finally opt we have been doing for a long time. (We should do something similar for try/fault, but don't, yet).
Since these optimization passes are cheap, and opportunities for them arise after other optimizations and unblock subsequent optimizations, run them early, middle, and late.
Resolves #107191.
I expect we'll see more of these cases in the future, say if we unblock cloning of loops with EH.