forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfgehopt.cpp
3174 lines (2757 loc) · 113 KB
/
fgehopt.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif
// Flowgraph EH Optimizations
//------------------------------------------------------------------------
// fgRemoveEmptyFinally: Remove try/finallys where the finally is empty
//
// Returns:
// PhaseStatus indicating what, if anything, was changed.
//
// Notes:
// Removes all try/finallys in the method with empty finallys.
// These typically arise from inlining empty Dispose methods.
//
// Converts callfinally to a jump to the finally continuation.
// Removes the finally, and reparents all blocks in the try to the
// enclosing try or method region.
//
// Currently limited to trivially empty finallys: those with one basic
// block containing only single RETFILT statement. It is possible but
// not likely that more complex-looking finallys will eventually become
// empty (from say subsequent optimization). An SPMI run with
// just the "detection" part of this phase run after optimization
// found only one example where a new empty finally was detected.
//
PhaseStatus Compiler::fgRemoveEmptyFinally()
{
// We need to do this transformation before funclets are created.
assert(!fgFuncletsCreated);
// We need to update the bbPreds lists.
assert(fgPredsComputed);
if (compHndBBtabCount == 0)
{
JITDUMP("No EH in this method, nothing to remove.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.MinOpts())
{
JITDUMP("Method compiled with MinOpts, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.compDbgCode)
{
JITDUMP("Method compiled with debug codegen, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
#ifdef DEBUG
if (verbose)
{
printf("\n*************** Before fgRemoveEmptyFinally()\n");
fgDispBasicBlocks();
fgDispHandlerTab();
printf("\n");
}
#endif // DEBUG
// Look for finallys or faults that are empty.
unsigned finallyCount = 0;
unsigned emptyCount = 0;
unsigned XTnum = 0;
while (XTnum < compHndBBtabCount)
{
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
// Check if this is a try/finally or try/fault.
//
if (!HBtab->HasFinallyOrFaultHandler())
{
JITDUMP("EH#%u is not a try-finally or try-fault; skipping.\n", XTnum);
XTnum++;
continue;
}
finallyCount++;
// Look at blocks involved.
BasicBlock* const firstBlock = HBtab->ebdHndBeg;
BasicBlock* const lastBlock = HBtab->ebdHndLast;
// Limit for now to handlers that are single blocks.
if (firstBlock != lastBlock)
{
JITDUMP("EH#%u handler has multiple basic blocks; skipping.\n", XTnum);
XTnum++;
continue;
}
// If the handler's block jumps back to itself, then it is not empty.
if (firstBlock->KindIs(BBJ_ALWAYS) && firstBlock->TargetIs(firstBlock))
{
JITDUMP("EH#%u handler has basic block that jumps to itself; skipping.\n", XTnum);
XTnum++;
continue;
}
// Limit for now to finallys that contain only a GT_RETFILT.
bool isEmpty = true;
for (Statement* const stmt : firstBlock->Statements())
{
GenTree* stmtExpr = stmt->GetRootNode();
if (stmtExpr->gtOper != GT_RETFILT)
{
isEmpty = false;
break;
}
}
if (!isEmpty)
{
JITDUMP("EH#%u handler is not empty; skipping.\n", XTnum);
XTnum++;
continue;
}
// Note we may see single empty BBJ_THROW handler blocks for EH regions
// deemed unreachable.
//
assert(lastBlock->KindIs(BBJ_EHFINALLYRET, BBJ_EHFAULTRET, BBJ_THROW));
JITDUMP("EH#%u has empty handler, removing the region.\n", XTnum);
if (HBtab->HasFinallyHandler())
{
// Find all the call finallys that invoke this finally,
// and modify them to jump to the return point.
BasicBlock* firstCallFinallyRangeBlock = nullptr;
BasicBlock* lastCallFinallyRangeBlock = nullptr;
ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &lastCallFinallyRangeBlock);
BasicBlock* currentBlock = firstCallFinallyRangeBlock;
BasicBlock* const endCallFinallyRangeBlock = lastCallFinallyRangeBlock->Next();
while (currentBlock != endCallFinallyRangeBlock)
{
BasicBlock* nextBlock = currentBlock->Next();
if (currentBlock->KindIs(BBJ_CALLFINALLY) && currentBlock->TargetIs(firstBlock))
{
// Retarget the call finally to jump to the return point.
//
// We don't expect to see retless finallys here, since
// the finally is empty.
noway_assert(currentBlock->isBBCallFinallyPair());
BasicBlock* const leaveBlock = currentBlock->Next();
BasicBlock* const postTryFinallyBlock = leaveBlock->GetFinallyContinuation();
JITDUMP("Modifying callfinally " FMT_BB " leave " FMT_BB " finally " FMT_BB " continuation " FMT_BB
"\n",
currentBlock->bbNum, leaveBlock->bbNum, firstBlock->bbNum, postTryFinallyBlock->bbNum);
JITDUMP("so that " FMT_BB " jumps to " FMT_BB "; then remove " FMT_BB "\n", currentBlock->bbNum,
postTryFinallyBlock->bbNum, leaveBlock->bbNum);
// Remove the `leaveBlock` first.
nextBlock = leaveBlock->Next();
fgPrepareCallFinallyRetForRemoval(leaveBlock);
fgRemoveBlock(leaveBlock, /* unreachable */ true);
// Ref count updates.
fgRedirectTargetEdge(currentBlock, postTryFinallyBlock);
currentBlock->SetKind(BBJ_ALWAYS);
currentBlock->RemoveFlags(BBF_RETLESS_CALL); // no longer a BBJ_CALLFINALLY
// Cleanup the postTryFinallyBlock
fgCleanupContinuation(postTryFinallyBlock);
// Make sure iteration isn't going off the deep end.
assert(leaveBlock != endCallFinallyRangeBlock);
}
currentBlock = nextBlock;
}
}
JITDUMP("Remove now-unreachable handler " FMT_BB "\n", firstBlock->bbNum);
// Handler block should now be unreferenced, since the only
// explicit references to it were in call finallys.
firstBlock->bbRefs = 0;
// Remove the handler block.
firstBlock->RemoveFlags(BBF_DONT_REMOVE);
constexpr bool unreachable = true;
fgRemoveBlock(firstBlock, unreachable);
// Find enclosing try region for the try, if any, and update
// the try region. Note the handler region (if any) won't
// change.
BasicBlock* const firstTryBlock = HBtab->ebdTryBeg;
BasicBlock* const lastTryBlock = HBtab->ebdTryLast;
assert(firstTryBlock->getTryIndex() == XTnum);
for (BasicBlock* const block : Blocks(firstTryBlock, lastTryBlock))
{
// Look for blocks directly contained in this try, and
// update the try region appropriately.
//
// Try region for blocks transitively contained (say in a
// child try) will get updated by the subsequent call to
// fgRemoveEHTableEntry.
if (block->getTryIndex() == XTnum)
{
if (firstBlock->hasTryIndex())
{
block->setTryIndex(firstBlock->getTryIndex());
}
else
{
block->clearTryIndex();
}
}
}
// Update any impacted ACDs.
//
fgUpdateACDsBeforeEHTableEntryRemoval(XTnum);
// Remove the try-finally EH region. This will compact the EH table
// so XTnum now points at the next entry.
fgRemoveEHTableEntry(XTnum);
// First block of the former try no longer needs special protection.
firstTryBlock->RemoveFlags(BBF_DONT_REMOVE);
emptyCount++;
}
if (emptyCount > 0)
{
JITDUMP("fgRemoveEmptyFinally() removed %u try-finally/fault clauses from %u finally/fault(s)\n", emptyCount,
finallyCount);
fgInvalidateDfsTree();
#ifdef DEBUG
if (verbose)
{
printf("\n*************** After fgRemoveEmptyFinally()\n");
fgDispBasicBlocks();
fgDispHandlerTab();
printf("\n");
}
#endif // DEBUG
}
return (emptyCount > 0) ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
}
//------------------------------------------------------------------------
// fgUpdateACDsBeforeEHTableEntryRemoval: delete, move, or merge ACDs within
// an EH region we're about to remove.
//
// Arguments:
// XTNum -- eh region being removed
//
// Notes:
// XTnum must be the innermost mutual protect region, for a try-catch.
//
// We assume that the ACDs in the try/handler regions might still be needed
// (callers may "promote" these blocks to their enclosing regions). If the
// caller is actually removing the region code instead of merging it to the
// enclosing region, it is ok to have extra ACDs around.
//
// ACDs in filter regions are removed.
//
void Compiler::fgUpdateACDsBeforeEHTableEntryRemoval(unsigned XTnum)
{
if (!fgHasAddCodeDscMap())
{
// No ACDs to worry about at this time
//
return;
}
EHblkDsc* const ebd = ehGetDsc(XTnum);
AddCodeDscMap* const map = fgGetAddCodeDscMap();
for (AddCodeDsc* const add : AddCodeDscMap::ValueIteration(map))
{
JITDUMP("Considering ");
JITDUMPEXEC(add->Dump());
// Remember the old lookup key
//
AddCodeDscKey oldKey(add);
const bool inHnd = add->acdHndIndex > 0;
const bool inTry = add->acdTryIndex > 0;
const bool inThisHnd = inHnd && ((unsigned)(add->acdHndIndex - 1) == XTnum);
const bool inThisFlt = inHnd && ((unsigned)(add->acdHndIndex - 1) == XTnum);
const bool inThisTry = inTry && ((unsigned)(add->acdTryIndex - 1) == XTnum);
// If this ACD is in the filter of this region, it is no longer needed
//
if (inThisFlt && (add->acdKeyDsg == AcdKeyDesignator::KD_FLT))
{
bool const removed = map->Remove(oldKey);
assert(removed);
JITDUMP("ACD%u was in EH#%u filter region: removing\n", add->acdNum, XTnum);
JITDUMPEXEC(add->Dump());
continue;
}
// Note any ACDs in enclosed regions are updated when the region
// itself is removed.
//
if (!inThisTry && !inThisHnd)
{
JITDUMP("ACD%u not affected\n", add->acdNum);
continue;
}
bool rekey = false;
// If this ACD is in the handler of this region, update the
// enclosing handler index.
//
if (inThisHnd)
{
if (ebd->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX)
{
add->acdHndIndex = 0;
}
else
{
add->acdHndIndex = ebd->ebdEnclosingHndIndex + 1;
}
rekey = (add->acdKeyDsg == AcdKeyDesignator::KD_HND);
}
// If this ACD is in the try of this region, update the
// enclosing try index.
//
if (inThisTry)
{
if (ebd->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
{
add->acdTryIndex = 0;
}
else
{
add->acdTryIndex = ebd->ebdEnclosingTryIndex + 1;
}
rekey = (add->acdKeyDsg == AcdKeyDesignator::KD_TRY);
}
if (!rekey)
{
// If we didn't change the enclosing region for the ACD,
// the modifications above didn't change the key.
//
JITDUMP("ACD%u non-enclosing region updated; key remains the same\n", add->acdNum);
JITDUMPEXEC(add->Dump());
continue;
}
// Update the ACD key designator (note it may change).
//
// Then see if there is already an equivalent ACD in
// the new enclosing region, and if so, "merge" this ACD into
// that one (by removing this ACD from the map).
//
// If there is no equivalent ACD, re-add this current ACD
// with an updated key.
//
add->UpdateKeyDesignator(this);
// Remove the ACD from the map via its old key
//
bool const removed = map->Remove(oldKey);
assert(removed);
// Compute the new key an see if there's an existing
// ACD with that key.
//
AddCodeDscKey newKey(add);
AddCodeDsc* existing = nullptr;
if (map->Lookup(newKey, &existing))
{
// If so, this ACD is now redundant
//
JITDUMP("ACD%u merged into ACD%u\n", add->acdNum, existing->acdNum);
JITDUMPEXEC(existing->Dump());
}
else
{
// If not, re-enter this ACD in the map with the updated key
//
JITDUMP("ACD%u updated with new key\n", add->acdNum);
map->Set(newKey, add);
JITDUMPEXEC(add->Dump());
}
}
}
//------------------------------------------------------------------------
// fgRemoveEmptyTry: Optimize try/finallys where the try is empty,
// or cannot throw any exceptions
//
// Returns:
// PhaseStatus indicating what, if anything, was changed.
//
// Notes:
// In runtimes where thread abort is not possible, `try {} finally {S}`
// can be optimized to simply `S`. This method looks for such
// cases and removes the try-finally from the EH table, making
// suitable flow, block flag, statement, and region updates.
//
// This optimization is not legal in runtimes that support thread
// abort because those runtimes ensure that a finally is completely
// executed before continuing to process the thread abort. With
// this optimization, the code block `S` can lose special
// within-finally status and so complete execution is no longer
// guaranteed.
//
PhaseStatus Compiler::fgRemoveEmptyTry()
{
JITDUMP("\n*************** In fgRemoveEmptyTry()\n");
// We need to do this transformation before funclets are created.
assert(!fgFuncletsCreated);
// We need to update the bbPreds lists.
assert(fgPredsComputed);
bool enableRemoveEmptyTry = true;
#ifdef DEBUG
// Allow override to enable/disable.
enableRemoveEmptyTry = (JitConfig.JitEnableRemoveEmptyTry() == 1);
#endif // DEBUG
if (!enableRemoveEmptyTry)
{
JITDUMP("Empty try removal disabled.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (compHndBBtabCount == 0)
{
JITDUMP("No EH in this method, nothing to remove.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.MinOpts())
{
JITDUMP("Method compiled with MinOpts, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.compDbgCode)
{
JITDUMP("Method compiled with debug codegen, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
#ifdef DEBUG
if (verbose)
{
printf("\n*************** Before fgRemoveEmptyTry()\n");
fgDispBasicBlocks();
fgDispHandlerTab();
printf("\n");
}
#endif // DEBUG
// Look for try-finallys where the try is empty.
unsigned emptyCount = 0;
unsigned XTnum = 0;
while (XTnum < compHndBBtabCount)
{
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
// Check if this is a try/finally. We could also look for empty
// try/fault but presumably those are rare.
if (!HBtab->HasFinallyHandler())
{
JITDUMP("EH#%u is not a try-finally; skipping.\n", XTnum);
XTnum++;
continue;
}
// Examine the try region
BasicBlock* const firstTryBlock = HBtab->ebdTryBeg;
BasicBlock* const lastTryBlock = HBtab->ebdTryLast;
BasicBlock* const firstHandlerBlock = HBtab->ebdHndBeg;
BasicBlock* const lastHandlerBlock = HBtab->ebdHndLast;
BasicBlock* callFinally;
assert(firstTryBlock->getTryIndex() == XTnum);
// Assume the try is empty
//
bool canThrow = false;
// Limit for now to trys that contain only a callfinally pair
// or branch to same (we check this later). So we only need
// check the first block.
//
if (!firstTryBlock->isEmpty())
{
// Walk statements to see if any can throw an exception.
//
for (Statement* const stmt : firstTryBlock->Statements())
{
// Not clear when we can trust GTF_EXCEPT alone.
// GTF_CALL is too broad, but safe.
//
if ((stmt->GetRootNode()->gtFlags & (GTF_EXCEPT | GTF_CALL)) != 0)
{
canThrow = true;
break;
}
}
}
if (canThrow)
{
JITDUMP("EH#%u first try block " FMT_BB " can throw exception; skipping.\n", XTnum, firstTryBlock->bbNum);
XTnum++;
continue;
}
if (UsesCallFinallyThunks())
{
// Look for blocks that are always jumps to a call finally
// pair that targets the finally
if (!firstTryBlock->KindIs(BBJ_ALWAYS))
{
JITDUMP("EH#%u first try block " FMT_BB " not jump to a callfinally; skipping.\n", XTnum,
firstTryBlock->bbNum);
XTnum++;
continue;
}
callFinally = firstTryBlock->GetTarget();
// Look for call finally pair. Note this will also disqualify
// empty try removal in cases where the finally doesn't
// return.
if (!callFinally->isBBCallFinallyPair() || !callFinally->TargetIs(firstHandlerBlock))
{
JITDUMP("EH#%u first try block " FMT_BB " always jumps but not to a callfinally; skipping.\n", XTnum,
firstTryBlock->bbNum);
XTnum++;
continue;
}
// Try itself must be a single block.
if (firstTryBlock != lastTryBlock)
{
JITDUMP("EH#%u first try block " FMT_BB " not only block in try; skipping.\n", XTnum,
firstTryBlock->Next()->bbNum);
XTnum++;
continue;
}
}
else
{
// Look for call finally pair within the try itself. Note this
// will also disqualify empty try removal in cases where the
// finally doesn't return.
if (!firstTryBlock->isBBCallFinallyPair() || !firstTryBlock->TargetIs(firstHandlerBlock))
{
JITDUMP("EH#%u first try block " FMT_BB " not a callfinally; skipping.\n", XTnum, firstTryBlock->bbNum);
XTnum++;
continue;
}
callFinally = firstTryBlock;
// Try must be a callalways pair of blocks.
if (!firstTryBlock->NextIs(lastTryBlock))
{
JITDUMP("EH#%u block " FMT_BB " not last block in try; skipping.\n", XTnum,
firstTryBlock->Next()->bbNum);
XTnum++;
continue;
}
}
JITDUMP("EH#%u has empty try, removing the try region and promoting the finally.\n", XTnum);
// There should be just one callfinally that invokes this
// finally, the one we found above. Verify this.
BasicBlock* firstCallFinallyRangeBlock = nullptr;
BasicBlock* lastCallFinallyRangeBlock = nullptr;
bool verifiedSingleCallfinally = true;
ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &lastCallFinallyRangeBlock);
for (BasicBlock* const block : Blocks(firstCallFinallyRangeBlock, lastCallFinallyRangeBlock))
{
if (block->KindIs(BBJ_CALLFINALLY) && block->TargetIs(firstHandlerBlock))
{
assert(block->isBBCallFinallyPair());
if (block != callFinally)
{
JITDUMP("EH#%u found unexpected callfinally " FMT_BB "; skipping.\n", XTnum, block->bbNum);
verifiedSingleCallfinally = false;
break;
}
}
}
if (!verifiedSingleCallfinally)
{
JITDUMP("EH#%u -- unexpectedly -- has multiple callfinallys; skipping.\n", XTnum);
XTnum++;
assert(verifiedSingleCallfinally);
continue;
}
// Time to optimize.
//
// Identify the leave block and the continuation
BasicBlock* const leave = callFinally->Next();
BasicBlock* const continuation = leave->GetFinallyContinuation();
// (1) Find enclosing try region for the try, if any, and
// update the try region for the blocks in the try. Note the
// handler region (if any) won't change.
//
// Kind of overkill to loop here, but hey.
for (BasicBlock* const block : Blocks(firstTryBlock))
{
// Look for blocks directly contained in this try, and
// update the try region appropriately.
//
// The try region for blocks transitively contained (say in a
// child try) will get updated by the subsequent call to
// fgRemoveEHTableEntry.
if (block->getTryIndex() == XTnum)
{
if (firstHandlerBlock->hasTryIndex())
{
block->setTryIndex(firstHandlerBlock->getTryIndex());
}
else
{
block->clearTryIndex();
}
}
if (block == lastTryBlock)
{
break;
}
}
// (2) Cleanup the leave. Don't do this earlier, as removing the block might remove the
// last block of the `try`, and that could affect the block iteration above.
fgPrepareCallFinallyRetForRemoval(leave);
fgRemoveBlock(leave, /* unreachable */ true);
// Remove profile weight into the continuation block
if (continuation->hasProfileWeight())
{
continuation->decreaseBBProfileWeight(leave->bbWeight);
}
// (3) Convert the callfinally to a normal jump to the handler
assert(callFinally->HasInitializedTarget());
callFinally->SetKind(BBJ_ALWAYS);
callFinally->RemoveFlags(BBF_RETLESS_CALL); // no longer a BBJ_CALLFINALLY
// (4) Cleanup the continuation
fgCleanupContinuation(continuation);
// (5) Update the directly contained handler blocks' handler index.
// Handler index of any nested blocks will update when we
// remove the EH table entry. Change handler exits to jump to
// the continuation. Clear catch type on handler entry.
// Decrement nesting level of enclosed GT_END_LFINs.
for (BasicBlock* const block : Blocks(firstHandlerBlock, lastHandlerBlock))
{
if (block == firstHandlerBlock)
{
block->bbCatchTyp = BBCT_NONE;
}
if (block->getHndIndex() == XTnum)
{
if (firstTryBlock->hasHndIndex())
{
block->setHndIndex(firstTryBlock->getHndIndex());
}
else
{
block->clearHndIndex();
}
if (block->KindIs(BBJ_EHFINALLYRET))
{
Statement* finallyRet = block->lastStmt();
GenTree* finallyRetExpr = finallyRet->GetRootNode();
assert(finallyRetExpr->gtOper == GT_RETFILT);
fgRemoveStmt(block, finallyRet);
FlowEdge* const newEdge = fgAddRefPred(continuation, block);
block->SetKindAndTargetEdge(BBJ_ALWAYS, newEdge);
// Propagate profile weight into the continuation block
if (continuation->hasProfileWeight())
{
continuation->increaseBBProfileWeight(block->bbWeight);
}
}
}
#if defined(FEATURE_EH_WINDOWS_X86)
if (!UsesFunclets())
{
// If we're in a non-funclet model, decrement the nesting
// level of any GT_END_LFIN we find in the handler region,
// since we're removing the enclosing handler.
for (Statement* const stmt : block->Statements())
{
GenTree* expr = stmt->GetRootNode();
if (expr->gtOper == GT_END_LFIN)
{
const size_t nestLevel = expr->AsVal()->gtVal1;
assert(nestLevel > 0);
expr->AsVal()->gtVal1 = nestLevel - 1;
}
}
}
#endif // FEATURE_EH_WINDOWS_X86
}
// (6) Update any impacted ACDs.
//
fgUpdateACDsBeforeEHTableEntryRemoval(XTnum);
// (7) Remove the try-finally EH region. This will compact the
// EH table so XTnum now points at the next entry and will update
// the EH region indices of any nested EH in the (former) handler.
//
fgRemoveEHTableEntry(XTnum);
// (8) The handler entry has an artificial extra ref count. Remove it.
// There also should be one normal ref, from the try, and the handler
// may contain internal branches back to its start. So the ref count
// should currently be at least 2.
//
assert(firstHandlerBlock->bbRefs >= 2);
firstHandlerBlock->bbRefs -= 1;
// (8) The old try entry no longer needs special protection.
firstTryBlock->RemoveFlags(BBF_DONT_REMOVE);
// Another one bites the dust...
emptyCount++;
}
if (emptyCount > 0)
{
JITDUMP("fgRemoveEmptyTry() optimized %u empty-try try-finally clauses\n", emptyCount);
fgInvalidateDfsTree();
return PhaseStatus::MODIFIED_EVERYTHING;
}
return PhaseStatus::MODIFIED_NOTHING;
}
//------------------------------------------------------------------------
// fgRemoveEmptyTryCatchOrTryFault: Optimize try/catch or try/fault where
// the try is empty, or cannot throw any exceptions
//
// Returns:
// PhaseStatus indicating what, if anything, was changed.
//
PhaseStatus Compiler::fgRemoveEmptyTryCatchOrTryFault()
{
JITDUMP("\n*************** In fgRemoveEmptyTryCatchOrTryFault()\n");
// We need to do this transformation before funclets are created.
assert(!fgFuncletsCreated);
bool enableRemoveEmptyTryCatchOrTryFault = true;
#ifdef DEBUG
// Allow override to enable/disable.
enableRemoveEmptyTryCatchOrTryFault = (JitConfig.JitEnableRemoveEmptyTryCatchOrTryFault() == 1);
#endif // DEBUG
if (!enableRemoveEmptyTryCatchOrTryFault)
{
JITDUMP("Empty try/catch/fault removal disabled.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (compHndBBtabCount == 0)
{
JITDUMP("No EH in this method, nothing to remove.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.MinOpts())
{
JITDUMP("Method compiled with MinOpts, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
if (opts.compDbgCode)
{
JITDUMP("Method compiled with debug codegen, no removal.\n");
return PhaseStatus::MODIFIED_NOTHING;
}
#ifdef DEBUG
if (verbose)
{
printf("\n*************** Before fgRemoveEmptyTryCatchOrTryFault()\n");
fgDispBasicBlocks();
fgDispHandlerTab();
printf("\n");
}
#endif // DEBUG
// Look for try-catches where the try is empty.
unsigned emptyCount = 0;
unsigned XTnum = 0;
while (XTnum < compHndBBtabCount)
{
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
// Check if this is a try/catch.
if (HBtab->HasFinallyHandler())
{
JITDUMP("EH#%u is not a try-catch or try-fault; skipping.\n", XTnum);
XTnum++;
continue;
}
// Examine the try region
//
BasicBlock* const firstTryBlock = HBtab->ebdTryBeg;
BasicBlock* const lastTryBlock = HBtab->ebdTryLast;
// Assume the try is empty
//
bool canThrow = false;
// Walk all blocks in the try. Since we are walking
// try regions inner/outer, if we find an enclosed
// try, we assume it must be able to throw.
//
for (BasicBlock* const tryBlock : Blocks(firstTryBlock, lastTryBlock))
{
if (tryBlock->getTryIndex() != XTnum)
{
JITDUMP("EH#%u try block " FMT_BB " is nested try entry; skipping.\n", XTnum, tryBlock->bbNum);
canThrow = true;
break;
}
// Walk statements to see if any can throw an exception.
//
for (Statement* const stmt : tryBlock->Statements())
{
// Not clear when we can trust GTF_EXCEPT alone.
// GTF_CALL is perhaps too broad, but safe.
//
if ((stmt->GetRootNode()->gtFlags & (GTF_EXCEPT | GTF_CALL)) != 0)
{
JITDUMP("EH#%u " FMT_STMT " in " FMT_BB " can throw; skipping.\n", XTnum, stmt->GetID(),
tryBlock->bbNum);
canThrow = true;
break;
}
}
if (canThrow)
{
break;
}
}
if (canThrow)
{
// We could accelerate a bit by skipping to the first non-mutual protect region.
//
XTnum++;
continue;
}
JITDUMP("EH#%u try has no statements that can throw\n", XTnum);
// Since there are no tested trys, XTnum should be the try index of
// all blocks in the try region.
//
assert(firstTryBlock->getTryIndex() == XTnum);
assert(lastTryBlock->getTryIndex() == XTnum);
// Examine the handler blocks. If we see an enclosed try, we bail out for now.
// We could handle this, with a bit more work.
//
BasicBlock* const firstHndBlock = HBtab->ebdHndBeg;
BasicBlock* const lastHndBlock = HBtab->ebdHndLast;
bool handlerEnclosesTry = false;
for (BasicBlock* const handlerBlock : Blocks(firstHndBlock, lastHndBlock))
{
if (bbIsTryBeg(handlerBlock))
{
JITDUMP("EH#%u handler block " FMT_BB " is nested try entry; skipping.\n", XTnum, handlerBlock->bbNum);
handlerEnclosesTry = true;
break;
}
}
if (handlerEnclosesTry)
{
// We could accelerate a bit by skipping to the first non-mutual protect region.
//
XTnum++;
continue;
}
// Time to optimize.
//
unsigned const enclosingTryIndex = HBtab->ebdEnclosingTryIndex;
// (1) Find enclosing try region for the try, if any, and
// update the try region for the blocks in the try. Note the
// handler region (if any) won't change.
//
for (BasicBlock* const tryBlock : Blocks(firstTryBlock, lastTryBlock))
{
// Look for blocks directly contained in this try, and
// update the try region appropriately.
//
// The try region for blocks transitively contained (say in a
// child try) will get updated by the subsequent call to
// fgRemoveEHTableEntry.
//
if (tryBlock->getTryIndex() == XTnum)
{
if (enclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
{
tryBlock->clearTryIndex();
}
else
{
tryBlock->setTryIndex(enclosingTryIndex);
}
}
}
// (2) Remove any filter blocks
// The first filter block has an artificial ref count
//
if (HBtab->HasFilter())
{
BasicBlock* const firstFltBlock = HBtab->ebdFilter;
assert(firstFltBlock->bbRefs == 1);
firstFltBlock->bbRefs = 0;
BasicBlock* const afterLastFltBlock = HBtab->BBFilterLast()->Next();
// Must do this in two passes to handle loops or lexically
// backwards references.
//
for (BasicBlock* filterBlock = firstFltBlock; filterBlock != afterLastFltBlock;
filterBlock = filterBlock->Next())
{
fgRemoveBlockAsPred(filterBlock);
filterBlock->SetKind(BBJ_THROW);
}
for (BasicBlock* filterBlock = firstFltBlock; filterBlock != afterLastFltBlock;
filterBlock = filterBlock->Next())
{
filterBlock->RemoveFlags(BBF_DONT_REMOVE);
fgRemoveBlock(filterBlock, /* unreachable */ true);
}
}
// (3) Remove any handler blocks.
// The first handler block has an artificial ref count
//
assert(firstHndBlock->bbRefs == 1);
firstHndBlock->bbRefs = 0;
BasicBlock* const afterLastHndBlock = lastHndBlock->Next();
// Must do this in two passes to handle loops or lexically