-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathLuaDecompCF.inc
More file actions
4246 lines (3995 loc) · 174 KB
/
Copy pathLuaDecompCF.inc
File metadata and controls
4246 lines (3995 loc) · 174 KB
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
function TDecompiler.IsLoopHeader(BIdx: Integer): Boolean;
var
I, P: Integer;
begin
Result := False;
for I := 0 to High(FSF^.Blocks[BIdx].Preds) do begin
P := FSF^.Blocks[BIdx].Preds[I];
{ A back-edge exists if a predecessor has a higher or equal index AND
that predecessor hasn't already been consumed by another structural
construct (e.g. a for-loop's FORLOOP block). }
if (P >= BIdx) and not FEmitted[P] then begin
Result := True; Exit;
end;
end;
end;
function TDecompiler.FindLoopExit(HeaderIdx: Integer): Integer;
var
I, S: Integer;
begin
{ The exit block is the successor of the loop header that is NOT dominated
by the header (or is a later block). Simple heuristic: take the successor
with the highest index that is > HeaderIdx. }
Result := -1;
for I := 0 to High(FSF^.Blocks[HeaderIdx].Succs) do begin
S := FSF^.Blocks[HeaderIdx].Succs[I];
if S > HeaderIdx then
if (Result = -1) or (S > Result) then
Result := S;
end;
end;
{ IsRepeatUntilLoop - returns True if the loop at HeaderIdx is repeat-until
rather than while.
A while loop has its condition branch at the header, with one successor
being the loop exit. A repeat-until loop has its condition at the TAIL
(the block before the back-edge JMP), and the header's branch (if any)
is an inner if-statement - both of its successors stay inside the loop. }
function TDecompiler.IsRepeatUntilLoop(HeaderIdx: Integer): Boolean;
var
I, J, P, S, MaxBackEdge: Integer;
HasBranch, IsJmpOnly: Boolean;
begin
Result := False;
{ Find the maximum back-edge source for this loop header.
Prefer reachable back-edges (blocks with predecessors) to avoid inflating
the loop span with dead code. Fall back to unreachable back-edges if
they are the only ones (e.g., dead loop body after unconditional break). }
MaxBackEdge := -1;
for I := 0 to High(FSF^.Blocks[HeaderIdx].Preds) do begin
P := FSF^.Blocks[HeaderIdx].Preds[I];
if (P >= HeaderIdx) and not FEmitted[P] and
((P = 0) or (Length(FSF^.Blocks[P].Preds) > 0)) then
if P > MaxBackEdge then MaxBackEdge := P;
end;
if MaxBackEdge < 0 then begin
{ No reachable back-edge; try unreachable ones (dead code back-edges) }
for I := 0 to High(FSF^.Blocks[HeaderIdx].Preds) do begin
P := FSF^.Blocks[HeaderIdx].Preds[I];
if (P >= HeaderIdx) and not FEmitted[P] then
if P > MaxBackEdge then MaxBackEdge := P;
end;
end;
if MaxBackEdge < 0 then Exit; { No back-edge = not a loop }
{ Check if header has a BRANCH }
HasBranch := False;
for I := 0 to High(FSF^.Blocks[HeaderIdx].Nodes) do
if FSF^.Blocks[HeaderIdx].Nodes[I]^.Kind = SSA_BRANCH then begin
HasBranch := True; Break;
end;
if not HasBranch then begin
{ No branch in header --> definitely repeat-until (condition at tail) }
Result := True;
Exit;
end;
{ Header has a branch. Check if either successor exits
the loop. The loop body spans [HeaderIdx..MaxBackEdge]. A successor
"exits" if it goes outside this range:
(a) its index is > MaxBackEdge (forward exit), OR
(b) its index is < HeaderIdx (backward exit to outer scope), OR
(c) it's a JMP-only trampoline whose target satisfies (a) or (b).
If any successor exits the loop, it's a while loop.
If both successors stay inside the loop body, it's repeat-until. }
for I := 0 to High(FSF^.Blocks[HeaderIdx].Succs) do begin
S := FSF^.Blocks[HeaderIdx].Succs[I];
if (S > MaxBackEdge) or (S < HeaderIdx) then
Exit; { One successor exits the loop --> while }
{ Follow JMP-only trampolines to check if they ultimately exit }
if (S >= 0) and (S < Length(FSF^.Blocks)) and (S <> HeaderIdx) then begin
IsJmpOnly := True;
for J := 0 to High(FSF^.Blocks[S].Nodes) do
if not (FSF^.Blocks[S].Nodes[J]^.Kind in [SSA_JUMP, SSA_PHI, SSA_NOP]) then begin
IsJmpOnly := False; Break;
end;
if IsJmpOnly and (Length(FSF^.Blocks[S].Succs) = 1) then begin
P := FSF^.Blocks[S].Succs[0];
if (P > MaxBackEdge) or (P < HeaderIdx) then
Exit; { JMP-only trampoline exits the loop --> while }
end;
end;
end;
{ Both successors are inside the loop --> repeat-until }
Result := True;
end;
{ FindRepeatUntilExit - find the exit block for a repeat-until loop.
The exit is NOT a successor of the header. Instead, find the tail block
(the block that branches back to the header or to the JMP-only back-edge),
and the exit is the OTHER successor of that tail block. }
function TDecompiler.FindRepeatUntilExit(HeaderIdx: Integer): Integer;
var
I, P, S, MaxBackEdge, TailBlk: Integer;
begin
Result := -1;
{ Find the maximum back-edge source.
Prefer reachable back-edges; fall back to unreachable if needed. }
MaxBackEdge := -1;
for I := 0 to High(FSF^.Blocks[HeaderIdx].Preds) do begin
P := FSF^.Blocks[HeaderIdx].Preds[I];
if (P >= HeaderIdx) and not FEmitted[P] and
((P = 0) or (Length(FSF^.Blocks[P].Preds) > 0)) then
if P > MaxBackEdge then MaxBackEdge := P;
end;
if MaxBackEdge < 0 then begin
for I := 0 to High(FSF^.Blocks[HeaderIdx].Preds) do begin
P := FSF^.Blocks[HeaderIdx].Preds[I];
if (P >= HeaderIdx) and not FEmitted[P] then
if P > MaxBackEdge then MaxBackEdge := P;
end;
end;
if MaxBackEdge < 0 then Exit;
{ The MaxBackEdge is typically a JMP-only block. Find the block that
branches TO MaxBackEdge (the real tail with the condition). }
TailBlk := -1;
for I := 0 to High(FSF^.Blocks[MaxBackEdge].Preds) do begin
P := FSF^.Blocks[MaxBackEdge].Preds[I];
if (P >= HeaderIdx) and (P < MaxBackEdge) then begin
{ This is the tail block that branches to the back-edge }
TailBlk := P;
Break;
end;
end;
if TailBlk < 0 then begin
{ MaxBackEdge itself might be the tail block (direct back-edge) }
TailBlk := MaxBackEdge;
end;
{ Find the exit: the successor of TailBlk that goes beyond MaxBackEdge }
for I := 0 to High(FSF^.Blocks[TailBlk].Succs) do begin
S := FSF^.Blocks[TailBlk].Succs[I];
if (S <> MaxBackEdge) and (S <> HeaderIdx) then begin
if (Result = -1) or (S > Result) then
Result := S;
end;
end;
end;
{ Find the merge block (post-dominator) for an if branch }
function TDecompiler.FindMerge(BranchBlk, TrueBlk, FalseBlk: Integer): Integer;
{ Find the merge block (immediate post-dominator) for an if-branch.
Uses BFS reachability with distance tracking from both branches.
Merge candidates must:
1. Be reachable from both branches
2. Not be strictly inside one branch's dominator subtree
3. Postdominate the branch block (every path from BranchBlk to a
function exit passes through the candidate)
Selection: closest candidate by max(TrueDist, FalseDist). }
function IsJmpOnlyBlock(Blk: Integer): Boolean;
var J: Integer;
begin
Result := True;
if (Blk < 0) or (Blk >= Length(FSF^.Blocks)) then begin Result := False; Exit; end;
for J := 0 to High(FSF^.Blocks[Blk].Nodes) do
if not (FSF^.Blocks[Blk].Nodes[J]^.Kind in [SSA_JUMP, SSA_PHI, SSA_NOP]) then begin
Result := False; Exit;
end;
end;
function IsDomBy(Blk, Dom: Integer): Boolean;
{ Check if Blk is strictly dominated by Dom (walk idom chain). }
var B, Safety: Integer;
begin
Result := False;
if Blk = Dom then Exit; { Not strict domination }
B := Blk;
Safety := 0;
while (B >= 0) and (B < Length(FSF^.Blocks)) and (Safety < 200) do begin
if B = Dom then begin Result := True; Exit; end;
if FSF^.Blocks[B].IDom = B then Exit; { root }
B := FSF^.Blocks[B].IDom;
Inc(Safety);
end;
end;
function IsSemanticExit(Blk: Integer): Boolean;
{ True if block contains SSA_RETURN or SSA_TAILCALL.
More robust than checking Succs=0 alone. }
var EI: Integer;
begin
Result := False;
if (Blk < 0) or (Blk >= Length(FSF^.Blocks)) then Exit;
for EI := 0 to High(FSF^.Blocks[Blk].Nodes) do
if FSF^.Blocks[Blk].Nodes[EI]^.Kind in [SSA_RETURN, SSA_TAILCALL] then begin
Result := True; Exit;
end;
end;
function PostDominates(M, B: Integer): Boolean;
{ True if every path from B to a function exit passes through M.
Phase 1: BFS from B with M excluded. If any semantic exit
(SSA_RETURN/SSA_TAILCALL) is reachable -> M does NOT postdominate B.
Phase 2: verify M itself can reach a semantic exit. If not, reject
(conservatively handles no-exit/infinite-loop subgraphs). }
var
PDVisited: array of Boolean;
PDQueue: array of Integer;
QH, QT, Cur, Si, PDSucc: Integer;
FoundExit: Boolean;
NumBlks: Integer;
begin
Result := False;
NumBlks := Length(FSF^.Blocks);
if M = B then Exit; { can't postdominate itself for merge purposes }
if (M < 0) or (M >= NumBlks) then Exit;
if (B < 0) or (B >= NumBlks) then Exit;
SetLength(PDVisited, NumBlks);
SetLength(PDQueue, NumBlks);
{ Phase 1: BFS from B excluding M - full CFG traversal }
for Cur := 0 to NumBlks - 1 do PDVisited[Cur] := False;
PDVisited[M] := True; { exclude M from traversal }
QH := 0; QT := 0;
PDQueue[QT] := B; Inc(QT); PDVisited[B] := True;
while QH < QT do begin
Cur := PDQueue[QH]; Inc(QH);
{ Semantic exit check - block has RETURN/TAILCALL }
if IsSemanticExit(Cur) then Exit; { exit reachable without M -> False }
for Si := 0 to High(FSF^.Blocks[Cur].Succs) do begin
PDSucc := FSF^.Blocks[Cur].Succs[Si];
if (PDSucc >= 0) and (PDSucc < NumBlks) and (not PDVisited[PDSucc]) then begin
PDVisited[PDSucc] := True;
PDQueue[QT] := PDSucc; Inc(QT);
end;
end;
end;
{ Phase 1 passed: no exit reachable from B without M.
Phase 2: verify M can reach a semantic exit. }
if IsSemanticExit(M) then begin Result := True; Exit; end;
for Cur := 0 to NumBlks - 1 do PDVisited[Cur] := False;
QH := 0; QT := 0;
PDQueue[QT] := M; Inc(QT); PDVisited[M] := True;
FoundExit := False;
while QH < QT do begin
Cur := PDQueue[QH]; Inc(QH);
if IsSemanticExit(Cur) then begin FoundExit := True; Break; end;
for Si := 0 to High(FSF^.Blocks[Cur].Succs) do begin
PDSucc := FSF^.Blocks[Cur].Succs[Si];
if (PDSucc >= 0) and (PDSucc < NumBlks) and (not PDVisited[PDSucc]) then begin
PDVisited[PDSucc] := True;
PDQueue[QT] := PDSucc; Inc(QT);
end;
end;
end;
Result := FoundExit;
end;
var
NBlks, I, S: Integer;
TrueEnd, FalseEnd: Integer;
TrueDist, FalseDist: array of Integer;
Queue: array of Integer;
QHead, QTail: Integer;
Blk, Succ: Integer;
Cur: Integer;
TrueOnly, FalseOnly: Boolean;
BestMerge, BestScore, Score: Integer;
begin
Result := -1;
NBlks := Length(FSF^.Blocks);
if NBlks = 0 then begin Result := Max(TrueBlk, FalseBlk) + 1; Exit; end;
{ Follow JMP-only blocks to find real endpoints }
TrueEnd := TrueBlk;
if IsJmpOnlyBlock(TrueBlk) and (Length(FSF^.Blocks[TrueBlk].Succs) = 1) then
TrueEnd := FSF^.Blocks[TrueBlk].Succs[0];
FalseEnd := FalseBlk;
if IsJmpOnlyBlock(FalseBlk) and (Length(FSF^.Blocks[FalseBlk].Succs) = 1) then
FalseEnd := FSF^.Blocks[FalseBlk].Succs[0];
{ Quick check: both resolve to same block }
if (TrueEnd = FalseEnd) and (TrueEnd > BranchBlk) then begin
Result := TrueEnd; Exit;
end;
{ BFS from True branch - forward-only traversal (Succ > BranchBlk)
to prevent back-edges from creating cross-loop-boundary merges.
PostDominates uses full CFG separately for correctness.
Distances track BFS hops; reachability = Dist >= 0. }
SetLength(TrueDist, NBlks);
SetLength(FalseDist, NBlks);
for I := 0 to NBlks - 1 do begin TrueDist[I] := -1; FalseDist[I] := -1; end;
SetLength(Queue, NBlks);
QHead := 0; QTail := 0;
if (TrueEnd >= 0) and (TrueEnd < NBlks) then begin
TrueDist[TrueEnd] := 0;
Queue[QTail] := TrueEnd; Inc(QTail);
end;
while QHead < QTail do begin
Blk := Queue[QHead]; Inc(QHead);
for S := 0 to High(FSF^.Blocks[Blk].Succs) do begin
Succ := FSF^.Blocks[Blk].Succs[S];
if (Succ > BranchBlk) and (Succ < NBlks) and (TrueDist[Succ] < 0) then begin
TrueDist[Succ] := TrueDist[Blk] + 1;
Queue[QTail] := Succ; Inc(QTail);
end;
end;
end;
{ BFS from False branch - forward-only traversal }
QHead := 0; QTail := 0;
if (FalseEnd >= 0) and (FalseEnd < NBlks) then begin
FalseDist[FalseEnd] := 0;
Queue[QTail] := FalseEnd; Inc(QTail);
end;
while QHead < QTail do begin
Blk := Queue[QHead]; Inc(QHead);
for S := 0 to High(FSF^.Blocks[Blk].Succs) do begin
Succ := FSF^.Blocks[Blk].Succs[S];
if (Succ > BranchBlk) and (Succ < NBlks) and (FalseDist[Succ] < 0) then begin
FalseDist[Succ] := FalseDist[Blk] + 1;
Queue[QTail] := Succ; Inc(QTail);
end;
end;
end;
{ Find merge candidate: reachable from both branches, not inside one
branch's dominator subtree, postdominates the branch block.
Select closest by max(TrueDist, FalseDist).
Only forward candidates (I > BranchBlk) - backward merges are loops. }
BestMerge := -1;
BestScore := MaxInt;
for I := 0 to NBlks - 1 do begin
if (I <= BranchBlk) then Continue; { only forward merge candidates }
if (TrueDist[I] < 0) or (FalseDist[I] < 0) then Continue; { not common }
{ Dominance filter: skip blocks inside one branch's subtree }
TrueOnly := IsDomBy(I, TrueEnd) and (not IsDomBy(I, FalseEnd));
FalseOnly := IsDomBy(I, FalseEnd) and (not IsDomBy(I, TrueEnd));
if TrueOnly or FalseOnly then Continue;
{ Postdomination filter }
if not PostDominates(I, BranchBlk) then Continue;
{ BFS-distance score: closest common block }
Score := TrueDist[I];
if FalseDist[I] > Score then Score := FalseDist[I];
if Score < BestScore then begin
BestScore := Score;
BestMerge := I;
end;
end;
if BestMerge >= 0 then begin
Result := BestMerge; Exit;
end;
{ Both branches return - genuine if/else with no merge point.
Keep both as parallel branches (emit else). }
if (TrueEnd >= 0) and (TrueEnd < NBlks) and
(Length(FSF^.Blocks[TrueEnd].Succs) = 0) and
(FalseEnd >= 0) and (FalseEnd < NBlks) and
(Length(FSF^.Blocks[FalseEnd].Succs) = 0) then begin
Result := Max(TrueEnd, FalseEnd) + 1; Exit;
end;
{ One branch returns, the other continues.
When one branch ends with RETURN (no successors), the merge is simply
the start of the other branch - there is no else, just continuation
after "end". }
if (TrueEnd >= 0) and (TrueEnd < NBlks) and
(Length(FSF^.Blocks[TrueEnd].Succs) = 0) then begin
Result := FalseEnd; Exit;
end;
if (FalseEnd >= 0) and (FalseEnd < NBlks) and
(Length(FSF^.Blocks[FalseEnd].Succs) = 0) then begin
Result := TrueEnd; Exit;
end;
{ Enhanced all-paths-terminate check for complex branches.
When a branch body contains a nested if where ALL paths return,
TrueEnd/FalseEnd still have successors (the nested BRANCH), so the
simple Length(Succs)=0 check above misses them. We detect this by
verifying the BFS reach set is "closed": every reachable block's
forward successors are also reachable, meaning no path escapes
the branch body. This handles switch_pattern-style elseif chains
where one branch has a nested if with all-returning paths. }
for I := 0 to 1 do begin { 0=check true, 1=check false }
Cur := 0; { reuse: acts as "has any reachable block" flag }
TrueOnly := True; { reuse: acts as "all paths terminate" flag }
for S := BranchBlk + 1 to NBlks - 1 do begin
if I = 0 then begin
if TrueDist[S] < 0 then Continue;
end else begin
if FalseDist[S] < 0 then Continue;
end;
Cur := 1; { found at least one reachable block }
for Blk := 0 to High(FSF^.Blocks[S].Succs) do begin
Succ := FSF^.Blocks[S].Succs[Blk];
if Succ <= BranchBlk then begin
{ Back-edge to loop header - this path continues, doesn't terminate }
TrueOnly := False; Break;
end;
if (Succ > BranchBlk) and (Succ < NBlks) then begin
if I = 0 then begin
if TrueDist[Succ] < 0 then begin TrueOnly := False; Break; end;
end else begin
if FalseDist[Succ] < 0 then begin TrueOnly := False; Break; end;
end;
end;
end;
if not TrueOnly then Break;
end;
if (Cur = 1) and TrueOnly then begin
{ This branch all-returns - merge is the other branch's start }
if I = 0 then begin Result := FalseEnd; Exit; end
else begin Result := TrueEnd; Exit; end;
end;
end;
{ Fallback }
if Result = -1 then
Result := Max(TrueBlk, FalseBlk) + 1;
end;
{ ======================================================================
Numeric / generic for detection
====================================================================== }
function TDecompiler.EmitForNumericLoop(BIdx: Integer): Integer;
var
Blk : PBasicBlock;
N : PSSANode;
PrepNode, LoopNode: PSSANode;
StartNode, LimitNode, StepNode: PSSANode;
IterReg, LimitReg, StepReg, VarReg: Integer;
StartExpr, LimitExpr, StepExpr, VarName: AnsiString;
ExitBlk: Integer;
I: Integer;
LoopBlkIdx: Integer;
BodyStart: Integer;
BodyPC: Integer;
S: Integer;
ForOpenIdx: Integer;
begin
Result := -1;
if FOpts.Debug then
WriteLn(StdErr, 'EmitForNumericLoop: FORPREP block=BB', BIdx);
Blk := @FSF^.Blocks[BIdx];
{ Find FORPREP node in this block }
PrepNode := nil;
for I := 0 to High(Blk^.Nodes) do
if Blk^.Nodes[I]^.Kind = SSA_FORPREP then begin
PrepNode := Blk^.Nodes[I]; Break;
end;
if PrepNode = nil then Exit;
IterReg := PrepNode^.ImmA;
LimitReg := IterReg + 1;
StepReg := IterReg + 2;
{ Lua 5.1-5.4: 3 internal vars (init/limit/step), user var at R(A+3)
Lua 5.5: 2 internal vars (count/step), user var at R(A+2) }
if FSF^.LuaVersion >= $55 then
VarReg := IterReg + 2
else
VarReg := IterReg + 3;
{ Determine the FORLOOP block (jump target of FORPREP) }
LoopBlkIdx := -1;
for I := 0 to High(Blk^.Succs) do begin
LoopBlkIdx := Blk^.Succs[I]; Break;
end;
if LoopBlkIdx < 0 then Exit;
{ Find FORLOOP node }
LoopNode := nil;
for I := 0 to High(FSF^.Blocks[LoopBlkIdx].Nodes) do
if FSF^.Blocks[LoopBlkIdx].Nodes[I]^.Kind = SSA_FORLOOP then begin
LoopNode := FSF^.Blocks[LoopBlkIdx].Nodes[I]; Break;
end;
if LoopNode = nil then Exit;
ExitBlk := FindLoopExit(LoopBlkIdx);
if FOpts.Debug then
WriteLn(StdErr, 'EmitForNumericLoop: FORPREP=BB', BIdx,
' FORLOOP=BB', LoopBlkIdx, ' ExitBlk=BB', ExitBlk);
{ Find the SSA nodes that define the init/limit/step registers in this block.
Also search predecessor blocks if not found, since the compiler may place
the start/limit/step definitions in blocks that feed into the FORPREP block.
NOTE: do NOT search predecessors for registers that have PHI nodes in BIdx,
because the PHI expression (e.g. "count or 1") should be used instead of
the individual predecessor definitions. }
StartNode := FindDefNode(BIdx, IterReg);
LimitNode := FindDefNode(BIdx, LimitReg);
StepNode := FindDefNode(BIdx, StepReg);
{ Check for PHI nodes first: they take priority over predecessor search.
When a for-control register is defined by a PHI (e.g. "count or 1"),
mark the PHI's source blocks as emitted so TryEmitOrAnd doesn't
produce a spurious standalone assignment like "t4 = count or 1". }
for I := 0 to High(Blk^.Nodes) do begin
N := Blk^.Nodes[I];
if N^.Kind <> SSA_PHI then Continue;
if (StartNode = nil) and (N^.Dest.Reg = IterReg) then begin
StartNode := N;
for S := 0 to High(N^.PhiBlocks) do
if (N^.PhiBlocks[S] >= 0) and (N^.PhiBlocks[S] < Length(FSF^.Blocks)) then
FEmitted[N^.PhiBlocks[S]] := True;
end;
if (LimitNode = nil) and (N^.Dest.Reg = LimitReg) then begin
LimitNode := N;
for S := 0 to High(N^.PhiBlocks) do
if (N^.PhiBlocks[S] >= 0) and (N^.PhiBlocks[S] < Length(FSF^.Blocks)) then
FEmitted[N^.PhiBlocks[S]] := True;
end;
if (StepNode = nil) and (N^.Dest.Reg = StepReg) then begin
StepNode := N;
for S := 0 to High(N^.PhiBlocks) do
if (N^.PhiBlocks[S] >= 0) and (N^.PhiBlocks[S] < Length(FSF^.Blocks)) then
FEmitted[N^.PhiBlocks[S]] := True;
end;
end;
{ Search earlier blocks for registers still not found.
Walk backward from BIdx to find the defining instruction. }
if (StartNode = nil) or (LimitNode = nil) or (StepNode = nil) then begin
for S := BIdx - 1 downto 0 do begin
if StartNode = nil then StartNode := FindDefNode(S, IterReg);
if LimitNode = nil then LimitNode := FindDefNode(S, LimitReg);
if StepNode = nil then StepNode := FindDefNode(S, StepReg);
if (StartNode <> nil) and (LimitNode <> nil) and (StepNode <> nil) then
Break;
end;
end;
{ Emit any non-for-setup statements in the prep block before the for line.
Nodes that define init/limit/step or are structural (FORPREP/PHI/NOP) are skipped.
Other nodes (e.g. intermediate computations) must be emitted first. }
I := 0;
while I <= High(Blk^.Nodes) do begin
N := Blk^.Nodes[I];
if N^.Kind in [SSA_FORPREP, SSA_PHI, SSA_NOP, SSA_JUMP, SSA_SETLIST] then begin
Inc(I); Continue;
end;
{ Skip nodes that define the for-control registers }
if (N = StartNode) or (N = LimitNode) or (N = StepNode) then begin
Inc(I); Continue;
end;
{ Try table constructor pattern (NEWTABLE + values + SETLIST) }
if (N^.Kind = SSA_NEWTABLE) and TryEmitTableConstructor(BIdx, I, S) then begin
Inc(I, S); Continue;
end;
{ Skip nodes whose result feeds into a for-control def (intermediates).
We detect this by checking if the node's dest reg is used only by
the for-control defining nodes. For safety, skip any node whose
dest.Reg is one of the for-control regs (shouldn't happen) or
whose dest is consumed entirely by for-setup. }
EmitNode(N, BIdx);
Inc(I);
end;
{ Build loop bound expressions from the defining nodes.
Strip outer parens since for-header already provides clear context. }
if StartNode <> nil then
StartExpr := StripOuterParens(NodeExpr(StartNode))
else
StartExpr := LocalName(IterReg, Blk^.StartPC);
if LimitNode <> nil then
LimitExpr := StripOuterParens(NodeExpr(LimitNode))
else
LimitExpr := LocalName(LimitReg, Blk^.StartPC);
if StepNode <> nil then
StepExpr := StripOuterParens(NodeExpr(StepNode))
else
StepExpr := LocalName(StepReg, Blk^.StartPC);
{ Get loop variable name from inside the body (where LocVar "i" is in scope) }
BodyStart := BIdx + 1;
if (BodyStart < Length(FSF^.Blocks)) and
(BodyStart <= LoopBlkIdx) then
BodyPC := FSF^.Blocks[BodyStart].StartPC
else
BodyPC := Blk^.StartPC;
VarName := LocalName(VarReg, BodyPC);
{ If still a generated name, try the debug LocVar directly }
if (Length(VarName) > 0) and (VarName[1] = 't') then begin
for I := 0 to High(FProto^.LocVars) do
if (FProto^.LocVars[I].StartPC = PrepNode^.PC + 1) and
(FProto^.LocVars[I].Name <> '') and
(FProto^.LocVars[I].Name[1] <> '(') then begin
VarName := FProto^.LocVars[I].Name;
Break;
end;
end;
ForOpenIdx := FOutput.Count;
if (PrepNode <> nil) then FAnnotatePC := PrepNode^.PC;
if StepExpr = '1' then
EmitLine('for ' + VarName + ' = ' + StartExpr + ', ' + LimitExpr + ' do')
else
EmitLine('for ' + VarName + ' = ' + StartExpr + ', ' + LimitExpr + ', ' + StepExpr + ' do');
FAnnotatePC := -1;
Indent;
{ Emit body: blocks between FORPREP-block and FORLOOP block (exclusive) }
FEmitted[BIdx] := True;
FEmitted[LoopBlkIdx] := True;
PushLoopExit(ExitBlk);
if BodyStart < LoopBlkIdx then
EmitRegion(BodyStart, LoopBlkIdx)
else if BodyStart = LoopBlkIdx then begin
{ Body is between FORLOOP successors: emit the body block }
for I := 0 to High(FSF^.Blocks[LoopBlkIdx].Succs) do begin
S := FSF^.Blocks[LoopBlkIdx].Succs[I];
if (S <> ExitBlk) and (S < LoopBlkIdx) then begin
EmitRegion(S, LoopBlkIdx);
Break;
end;
end;
end;
PopLoopExit;
Dedent;
EmitLine('end');
{ Try single-line collapse: "for i = start, limit do body end" }
if PrepNode <> nil then
TryCollapseSingleLineBlock(ForOpenIdx, PrepNode^.PC,
LoopNode^.PC);
FEmitted[BIdx] := True;
FEmitted[LoopBlkIdx] := True;
Result := ExitBlk;
end;
function TDecompiler.EmitForGenericLoop(BIdx: Integer): Integer;
var
Blk : PBasicBlock;
I, J : Integer;
TForNode, CallNode, N: PSSANode;
BaseReg: Integer;
VarNames, IterExpr, FuncExpr, Args: AnsiString;
ExitBlk: Integer;
R: Integer;
BodyStart: Integer;
VarBase: Integer;
PredBlk: Integer;
ArgI: Integer;
CallIdx: Integer;
ForOpenIdx: Integer;
begin
Result := -1;
if FOpts.Debug then
WriteLn(StdErr, 'EmitForGenericLoop: TFORLOOP block=BB', BIdx);
Blk := @FSF^.Blocks[BIdx];
TForNode := nil;
for I := 0 to High(Blk^.Nodes) do
if Blk^.Nodes[I]^.Kind = SSA_TFORLOOP then begin
TForNode := Blk^.Nodes[I]; Break;
end;
if TForNode = nil then Exit;
BaseReg := TForNode^.ImmA;
{ Try to find the CALL that initializes the for-loop registers.
The CALL writes to BaseReg (the generator function) and is in a
predecessor block. We use its expression as the iterator.
If no direct CALL to BaseReg, look for a MOVE to BaseReg whose source
comes from a CALL or user local (e.g., "for a,b in gen do"). }
IterExpr := '';
CallNode := nil;
for I := 0 to High(Blk^.Preds) do begin
PredBlk := Blk^.Preds[I];
for J := High(FSF^.Blocks[PredBlk].Nodes) downto 0 do begin
N := FSF^.Blocks[PredBlk].Nodes[J];
if (N^.Kind = SSA_CALL) and (N^.ImmA = BaseReg) then begin
CallNode := N;
CallIdx := J;
Break;
end;
end;
if CallNode <> nil then Break;
end;
{ If no CALL directly to BaseReg, look for MOVE to BaseReg and trace source }
if CallNode = nil then begin
for I := 0 to High(Blk^.Preds) do begin
PredBlk := Blk^.Preds[I];
for J := High(FSF^.Blocks[PredBlk].Nodes) downto 0 do begin
N := FSF^.Blocks[PredBlk].Nodes[J];
if (N^.Kind = SSA_MOVE) and (N^.Dest.Reg = BaseReg) then begin
{ Found MOVE to BaseReg - use the source expression as IterExpr }
IterExpr := ExprOf(N^.OpA, N^.PC);
{ Inline the MOVE so it's not emitted as a standalone statement }
R := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (R >= 0) and (R < Length(FInlined)) then
FInlined[R] := True;
Break;
end;
end;
if IterExpr <> '' then Break;
end;
end;
{ If no CALL and no MOVE found, look for direct setup of BaseReg..BaseReg+2.
This handles "for k,v in next, tbl do" where registers are set individually. }
if (CallNode = nil) and (IterExpr = '') then begin
for I := 0 to High(Blk^.Preds) do begin
PredBlk := Blk^.Preds[I];
{ Find node writing to BaseReg (the iterator function) }
for J := High(FSF^.Blocks[PredBlk].Nodes) downto 0 do begin
N := FSF^.Blocks[PredBlk].Nodes[J];
if (N^.Dest.Reg = BaseReg) and
(N^.Kind in [SSA_GETGLOBAL, SSA_GETTABLE, SSA_GETUPVAL, SSA_LOADK,
SSA_CLOSURE, SSA_MOVE]) then begin
IterExpr := ExprOf(N^.Dest, N^.PC);
{ Mark it inlined }
R := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (R >= 0) and (R < Length(FInlined)) then
FInlined[R] := True;
{ Now check for state (BaseReg+1) and initial (BaseReg+2) }
for R := BaseReg + 1 to BaseReg + 2 do begin
for ArgI := High(FSF^.Blocks[PredBlk].Nodes) downto 0 do begin
N := FSF^.Blocks[PredBlk].Nodes[ArgI];
if (N^.Dest.Reg = R) and
(N^.Kind <> SSA_PHI) and (N^.Kind <> SSA_NOP) then begin
{ Skip trailing nil values - "next, tbl" omits the nil }
if N^.Kind = SSA_LOADNIL then begin
{ Inline LOADNIL regardless }
CallIdx := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (CallIdx >= 0) and (CallIdx < Length(FInlined)) then
FInlined[CallIdx] := True;
end else begin
IterExpr := IterExpr + ', ' + ExprOf(N^.Dest, N^.PC);
CallIdx := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (CallIdx >= 0) and (CallIdx < Length(FInlined)) then
FInlined[CallIdx] := True;
end;
Break;
end;
end;
end;
Break;
end;
end;
if IterExpr <> '' then Break;
end;
end;
if CallNode <> nil then begin
{ Build the call expression: func(args) }
FuncExpr := ExprOf(CallNode^.OpA, CallNode^.PC);
Args := '';
ArgI := 0;
{ Check if it's a SELF-based call (skip first implicit arg) }
if (CallNode^.OpA.Reg >= 0) and (CallNode^.OpA.Reg <> SSA_CONST_REG) then begin
for I := 0 to High(FSF^.AllNodes) do
if RefEqual(FSF^.AllNodes[I]^.Dest, CallNode^.OpA) and
(FSF^.AllNodes[I]^.Kind = SSA_SELF) then begin
ArgI := 1; Break;
end;
end;
while ArgI <= High(CallNode^.ArgRefs) do begin
if Args <> '' then Args := Args + ', ';
Args := Args + ExprOf(CallNode^.ArgRefs[ArgI], CallNode^.PC);
Inc(ArgI);
end;
IterExpr := FuncExpr + '(' + Args + ')';
{ If the CALL returns exactly 1 value (ImmC=2), the source used (f()) to
adjust to a single value. Wrap in extra parens to preserve semantics.
Normal for-in initializers use ImmC=0 (variable) or ImmC=4 (3 values). }
if CallNode^.ImmC = 2 then
IterExpr := '(' + IterExpr + ')';
{ Mark the CALL node as inlined so it won't be emitted as a standalone statement }
I := NodeIdx(CallNode^.Dest.Reg, CallNode^.Dest.Version);
if (I >= 0) and (I < Length(FInlined)) then
FInlined[I] := True;
{ Also inline any setup nodes in the predecessor block that are part of the
for-loop initialization. In 5.4, after the CALL, a LOADNIL initializes
the state/control/to-be-closed variables (R(A+1)..R(A+3)). In 5.1-5.3,
these are covered by the CALL's multi-return. Mark them as inlined. }
for I := 0 to High(Blk^.Preds) do begin
PredBlk := Blk^.Preds[I];
for J := 0 to High(FSF^.Blocks[PredBlk].Nodes) do begin
N := FSF^.Blocks[PredBlk].Nodes[J];
{ LOADNIL to registers between BaseReg and BaseReg+3 (or +4 for 5.4) }
if (N^.Kind = SSA_LOADNIL) and
(N^.Dest.Reg >= BaseReg) and (N^.Dest.Reg <= BaseReg + 3) then begin
R := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (R >= 0) and (R < Length(FInlined)) then
FInlined[R] := True;
end;
{ Also inline GETGLOBAL/GETTABUP that set up the CALL arguments }
if (N^.Kind in [SSA_GETGLOBAL, SSA_GETTABLE, SSA_GETUPVAL, SSA_LOADK]) and
(N^.Dest.Reg >= BaseReg) and (N^.Dest.Reg <= BaseReg + 3) then begin
R := NodeIdx(N^.Dest.Reg, N^.Dest.Version);
if (R >= 0) and (R < Length(FInlined)) then
FInlined[R] := True;
end;
end;
end;
end;
if IterExpr = '' then
IterExpr := LocalName(BaseReg, Blk^.StartPC);
{ Build variable names from LocVars. The loop variables (R(A+3) etc.)
are only in scope inside the body block, not at the TFORLOOP instruction.
The TFORLOOP block's first successor is a JMP block that leads to the body.
Follow the JMP to find the actual body block's start PC. }
BodyStart := -1;
if Length(Blk^.Succs) > 0 then begin
for I := 0 to High(Blk^.Succs) do begin
J := Blk^.Succs[I];
if (J >= 0) and (J < Length(FSF^.Blocks)) and
(Length(FSF^.Blocks[J].Nodes) = 1) and
{ In 5.1-5.3, the intermediate block is a JMP; in 5.4+, it's a FORLOOP }
(FSF^.Blocks[J].Nodes[0]^.Kind in [SSA_JUMP, SSA_FORLOOP]) and
(Length(FSF^.Blocks[J].Succs) > 0) then begin
{ This is the intermediate block; find body among its successors }
ExitBlk := FindLoopExit(BIdx);
for R := 0 to High(FSF^.Blocks[J].Succs) do begin
if FSF^.Blocks[J].Succs[R] <> ExitBlk then begin
BodyStart := FSF^.Blocks[FSF^.Blocks[J].Succs[R]].StartPC;
Break;
end;
end;
if BodyStart < 0 then
BodyStart := FSF^.Blocks[FSF^.Blocks[J].Succs[0]].StartPC;
Break;
end;
end;
end;
if BodyStart < 0 then BodyStart := Blk^.StartPC;
{ User-visible loop variables.
Lua 5.1-5.3, 5.5: R(A+3)..R(A+2+C), 3 internal vars
Lua 5.4 only: R(A+4)..R(A+3+C), 4 internal vars (to-be-closed slot added) }
if FSF^.LuaVersion = $54 then
VarBase := BaseReg + 4
else
VarBase := BaseReg + 3;
VarNames := '';
for R := 0 to TForNode^.ImmC - 1 do begin
if VarNames <> '' then VarNames := VarNames + ', ';
VarNames := VarNames + LocalName(VarBase + R, BodyStart);
end;
ForOpenIdx := FOutput.Count;
if (TForNode <> nil) then FAnnotatePC := TForNode^.PC;
EmitLine('for ' + VarNames + ' in ' + IterExpr + ' do');
FAnnotatePC := -1;
Indent;
ExitBlk := FindLoopExit(BIdx);
{ Emit body blocks.
In Lua 5.1, the TFORLOOP successor is a JMP block --> body block.
In Lua 5.4+, the TFORLOOP successor is a FORLOOP block --> body block.
The body blocks typically have LOWER indices than the TFORLOOP block
(because the setup code jumps forward past the body to the TFORLOOP,
with the body blocks in between).
Follow the successor chain to find the actual body start block. }
FEmitted[BIdx] := True;
{ Find body start by following TFORLOOP --> successor --> body }
BodyStart := -1;
for I := 0 to High(Blk^.Succs) do begin
J := Blk^.Succs[I];
if (J >= 0) and (J < Length(FSF^.Blocks)) and (J <> ExitBlk) then begin
{ This is the intermediate block (JMP in 5.1, FORLOOP in 5.4+) }
FEmitted[J] := True;
{ Follow its successors to find the body block (not the exit) }
for R := 0 to High(FSF^.Blocks[J].Succs) do begin
if FSF^.Blocks[J].Succs[R] <> ExitBlk then begin
BodyStart := FSF^.Blocks[J].Succs[R];
Break;
end;
end;
{ If the intermediate block has only one successor that IS the body }
if (BodyStart < 0) and (Length(FSF^.Blocks[J].Succs) = 1) then
BodyStart := FSF^.Blocks[J].Succs[0];
Break;
end;
end;
if BodyStart < 0 then BodyStart := BIdx + 1;
PushLoopExit(ExitBlk);
if (BodyStart >= 0) and (BodyStart < Length(FSF^.Blocks)) and (BodyStart <> ExitBlk) then begin
{ Body blocks are between BodyStart and the TFORLOOP block (BIdx).
Emit from BodyStart up to ExitBlk, but mark BIdx as already emitted
so the walk stops before re-entering the TFORLOOP. }
if BodyStart < BIdx then
EmitRegion(BodyStart, ExitBlk)
else
EmitRegion(BodyStart, ExitBlk);
end;
PopLoopExit;
Dedent;
EmitLine('end');
{ Try single-line collapse: "for k, v in pairs(t) do body end"
StartPC = body start (or TFORLOOP if no body), EndPC = JMP after TFORLOOP.
Covers the body instructions + loop control to verify same source line. }
if TForNode <> nil then begin
if (BodyStart >= 0) and (BodyStart < Length(FSF^.Blocks)) then
I := FSF^.Blocks[BodyStart].StartPC
else
I := TForNode^.PC;
TryCollapseSingleLineBlock(ForOpenIdx, I,
Min(TForNode^.PC + 1, Length(FProto^.LineInfo) - 1));
end;
Result := ExitBlk;
end;
function TDecompiler.TryBuildCompoundCond(BranchBlk: Integer;
out CondExpr: AnsiString;
out TrueTarget, FalseTarget: Integer): Boolean;
{ Check if a block is branch-only (only BRANCH + PHI/NOP/JUMP + optional LOADKs) }
function IsBranchOnlyBlock(Blk: Integer): Boolean;
var J, BranchCnt: Integer; Nd: PSSANode;
begin
Result := False;
if (Blk < 0) or (Blk >= Length(FSF^.Blocks)) then Exit;
if Length(FSF^.Blocks[Blk].Succs) < 2 then Exit;
BranchCnt := 0;
for J := 0 to High(FSF^.Blocks[Blk].Nodes) do begin
Nd := FSF^.Blocks[Blk].Nodes[J];
if Nd^.Kind = SSA_BRANCH then Inc(BranchCnt)
else if not (Nd^.Kind in [SSA_PHI, SSA_NOP, SSA_JUMP, SSA_LOADK,
SSA_LOADBOOL, SSA_LOADNIL]) then
Exit; { has real statements - not branch-only }
end;
Result := (BranchCnt = 1);
end;
{ Check if a block is a condition-evaluation block: it evaluates a SINGLE
expression and branches on the result. The BRANCH must test the register
that was DEFINED by the last non-BRANCH instruction in the block.
This catches patterns like: GETUPVAL R0 + CALL R0 + BRANCH R0
(function call used as condition) but rejects: GETUPVAL R3 + LOADK R4 +
CALL R3 (side-effect call) + BRANCH R0 (branch on different register). }
function IsCondEvalBlock(Blk: Integer): Boolean;
var J, BranchCnt, CallCnt, DefCnt, DefsAfterCall: Integer;
Nd, BrNode: PSSANode;
BranchReg, LastDefReg: Integer;
SeenCall: Boolean;
begin
Result := False;
if (Blk < 0) or (Blk >= Length(FSF^.Blocks)) then Exit;
if Length(FSF^.Blocks[Blk].Succs) < 2 then Exit;
BranchCnt := 0;
CallCnt := 0;
DefCnt := 0;
DefsAfterCall := 0;
BrNode := nil;
LastDefReg := -1;
SeenCall := False;
for J := 0 to High(FSF^.Blocks[Blk].Nodes) do begin
Nd := FSF^.Blocks[Blk].Nodes[J];
case Nd^.Kind of
SSA_BRANCH: begin Inc(BranchCnt); BrNode := Nd; end;
SSA_PHI, SSA_NOP, SSA_JUMP: { harmless };
SSA_LOADK, SSA_LOADBOOL, SSA_LOADNIL: begin
{ If this defines a user-local register, it's a body statement
(e.g., "local x = 3") not just condition computation }
if IsUserLocal(Nd^.Dest.Reg, Nd^.PC) or
IsUserLocal(Nd^.Dest.Reg, Nd^.PC + 1) or
ShouldEmitAsLocal(Nd^.Dest.Reg, Nd^.PC) then
Exit;
Inc(DefCnt);
LastDefReg := Nd^.Dest.Reg;
if SeenCall then Inc(DefsAfterCall);
end;
SSA_GETUPVAL, SSA_GETGLOBAL, SSA_GETTABLE,
SSA_SELF, SSA_MOVE, SSA_BINOP, SSA_UNOP, SSA_CONCAT: begin
Inc(DefCnt);