From 47dcc5b05b7d41ceadf0a7b10a5f6867ff110df2 Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 10 Nov 2019 16:11:38 +0000 Subject: [PATCH] * patch by J. Gareth Moreton, issue #36271, part 1: general rework of the jump optimizer git-svn-id: trunk@43439 - --- compiler/aoptobj.pas | 1134 +++++++++++++++++++++++++++++++++--------- 1 file changed, 893 insertions(+), 241 deletions(-) diff --git a/compiler/aoptobj.pas b/compiler/aoptobj.pas index 95a43e61a1..c6ebd4683f 100644 --- a/compiler/aoptobj.pas +++ b/compiler/aoptobj.pas @@ -248,8 +248,9 @@ Unit AoptObj; { label numbers } LabelInfo: PLabelInfo; - { Start and end of the block that is currently being optimized } - BlockStart, BlockEnd: Tai; + { Start and end of the block that is currently being optimized, and + a selected start point after the start of the block } + BlockStart, BlockEnd, StartPoint: Tai; DFA: TAOptDFA; @@ -269,6 +270,12 @@ Unit AoptObj; Procedure ClearUsedRegs; Procedure UpdateUsedRegs(p : Tai); class procedure UpdateUsedRegs(var Regs: TAllUsedRegs; p: Tai); + + { If UpdateUsedRegsAndOptimize has read ahead, the result is one before + the next valid entry (so "p.Next" returns what's expected). If no + reading ahead happened, then the result is equal to p. } + function UpdateUsedRegsAndOptimize(p : Tai): Tai; + Function CopyUsedRegs(var dest : TAllUsedRegs) : boolean; procedure RestoreUsedRegs(const Regs : TAllUsedRegs); procedure TransferUsedRegs(var dest: TAllUsedRegs); @@ -363,6 +370,34 @@ Unit AoptObj; function PeepHoleOptPass2Cpu(var p: tai): boolean; virtual; function PostPeepHoleOptsCpu(var p: tai): boolean; virtual; + { Output debug message to console - null function if EXTDEBUG is not defined } + class procedure DebugWrite(Message: string); static; inline; + + { Removes all instructions between an unconditional jump and the next label } + procedure RemoveDeadCodeAfterJump(p: taicpu); + + { If hp is a label, strip it if its reference count is zero. Repeat until + a non-label is found, or a label with a non-zero reference count. + True is returned if something was stripped } + function StripDeadLabels(hp: tai; var NextValid: tai): Boolean; + + { Strips a label and any aligns that appear before it (if hp points to + them rather than the label). Only call this procedure on a label that + you already know is no longer referenced } + procedure StripLabelFast(hp: tai); {$ifdef USEINLINE}inline;{$endif USEINLINE} + + { Checks and removes "jmp @@lbl; @lbl". Returns True if the jump was removed } + function CollapseZeroDistJump(var p: tai; hp1: tai; ThisLabel: TAsmLabel): Boolean; + + { If a group of labels are clustered, change the jump to point to the last one that is still referenced } + function CollapseLabelCluster(jump: tai; var lbltai: tai): TAsmLabel; +{$ifndef JVM} + function OptimizeConditionalJump(CJLabel: TAsmLabel; var p: tai; hp1: tai; var stoploop: Boolean): Boolean; +{$endif JVM} + + { Jump/label optimisation entry method } + function DoJumpOptimizations(var p: tai; var stoploop: Boolean): Boolean; + { insert debug comments about which registers are read and written by each instruction. Useful for debugging the InstructionLoadsFromReg and other similar functions. } @@ -910,6 +945,82 @@ Unit AoptObj; end; + { If UpdateUsedRegsAndOptimize has read ahead, the result is one before + the next valid entry (so "p.Next" returns what's expected). If no + reading ahead happened, then the result is equal to p. } + function TAOptObj.UpdateUsedRegsAndOptimize(p : Tai): Tai; + var + NotFirst: Boolean; + begin + { this code is based on TUsedRegs.Update to avoid multiple passes through the asmlist, + the code is duplicated here } + + Result := p; + if (p.typ in [ait_instruction, ait_label]) then + begin + if (p.next <> BlockEnd) and (tai(p.next).typ <> ait_instruction) then + begin + { Advance one, otherwise the routine exits immediately and wastes time } + p := tai(p.Next); + NotFirst := True; + end + else + { If the next entry is an instruction, nothing will be updated or + optimised here, so exit now to save time } + Exit; + end + else + NotFirst := False; + + repeat + while assigned(p) and + ((p.typ in (SkipInstr + [ait_align, ait_label] - [ait_RegAlloc])) or + ((p.typ = ait_marker) and + (tai_Marker(p).Kind in [mark_AsmBlockEnd,mark_NoLineInfoStart,mark_NoLineInfoEnd]))) do + begin + { Here's the optimise part } + if (p.typ in [ait_align, ait_label]) then + begin + if StripDeadLabels(p, p) then + begin + { Note, if the first instruction is stripped and is + the only one that gets removed, Result will now + contain a dangling pointer, so compensate for this. } + if not NotFirst then + Result := tai(p.Previous); + + Continue; + end; + + if ((p.typ = ait_label) and not labelCanBeSkipped(tai_label(p))) then + Break; + end; + + Result := p; + p := tai(p.next); + end; + while assigned(p) and + (p.typ=ait_RegAlloc) Do + begin + case tai_regalloc(p).ratype of + ra_alloc : + Include(UsedRegs[getregtype(tai_regalloc(p).reg)].UsedRegs, getsupreg(tai_regalloc(p).reg)); + ra_dealloc : + Exclude(UsedRegs[getregtype(tai_regalloc(p).reg)].UsedRegs, getsupreg(tai_regalloc(p).reg)); + else + { Do nothing }; + end; + Result := p; + p := tai(p.next); + end; + NotFirst := True; + until not(assigned(p)) or + (not(p.typ in SkipInstr + [ait_align]) and + not((p.typ = ait_label) and + labelCanBeSkipped(tai_label(p)))); + end; + + procedure TAOptObj.UpdateUsedRegs(p : Tai); begin { this code is based on TUsedRegs.Update to avoid multiple passes through the asmlist, @@ -1346,18 +1457,34 @@ Unit AoptObj; end; - function FindAnyLabel(hp: tai; var l: tasmlabel): Boolean; + function FindLiveLabel(hp: tai; var l: tasmlabel): Boolean; + var + next: tai; begin - FindAnyLabel := false; - while assigned(hp.next) and - (tai(hp.next).typ in (SkipInstr+[ait_align])) Do - hp := tai(hp.next); - if assigned(hp.next) and - (tai(hp.next).typ = ait_label) then + FindLiveLabel := false; + + while True do begin - FindAnyLabel := true; - l := tai_label(hp.next).labsym; - end + while assigned(hp.next) and + (tai(hp.next).typ in (SkipInstr+[ait_align])) Do + hp := tai(hp.next); + + next := tai(hp.next); + if assigned(next) and + (tai(next).typ = ait_label) then + begin + l := tai_label(next).labsym; + if not l.is_used then + begin + { Unsafe label } + hp := next; + Continue; + end; + + FindLiveLabel := true; + end; + Exit; + end; end; @@ -1385,11 +1512,9 @@ Unit AoptObj; {$if defined(arm) or defined(aarch64)} (hp.condition=c_None) and {$endif arm or aarch64} -{$if defined(riscv32) or defined(riscv64)} (hp.ops>0) and +{$if defined(riscv32) or defined(riscv64)} (hp.oper[0]^.reg=NR_X0) and -{$else riscv} - (hp.ops>0) and {$endif riscv} (JumpTargetOp(hp)^.typ = top_ref) and (JumpTargetOp(hp)^.ref^.symbol is TAsmLabel); @@ -1424,110 +1549,750 @@ Unit AoptObj; end; + { Output debug message to console - null function if EXTDEBUG is not defined } + class procedure TAOptObj.DebugWrite(Message: string); inline; + begin +{$ifdef EXTDEBUG} + WriteLn(Message); +{$else EXTDEBUG} + { Do nothing } +{$endif EXTDEBUG} + end; + + { Removes all instructions between an unconditional jump and the next label } + procedure TAOptObj.RemoveDeadCodeAfterJump(p: taicpu); + var + hp1, hp2: tai; + begin + { the following code removes all code between a jmp and the next label, + because it can never be executed + } + while GetNextInstruction(p, hp1) and + (hp1 <> BlockEnd) and + (hp1.typ <> ait_label) +{$ifdef JVM} + and (hp1.typ <> ait_jcatch) +{$endif} + do + if not(hp1.typ in ([ait_label]+skipinstr)) then + begin + if (hp1.typ = ait_instruction) and + taicpu(hp1).is_jmp and + (JumpTargetOp(taicpu(hp1))^.typ = top_ref) and + (JumpTargetOp(taicpu(hp1))^.ref^.symbol is TAsmLabel) then + TAsmLabel(JumpTargetOp(taicpu(hp1))^.ref^.symbol).decrefs; + { don't kill start/end of assembler block, + no-line-info-start/end etc } + if (hp1.typ <> ait_marker) then + begin +{$ifdef cpudelayslot} + if (hp1.typ=ait_instruction) and (taicpu(hp1).is_jmp) then + RemoveDelaySlot(hp1); +{$endif cpudelayslot} + if (hp1.typ = ait_align) then + begin + { Only remove the align if a label doesn't immediately follow } + if GetNextInstruction(hp1, hp2) and (hp2.typ = ait_label) then + { The label is unskippable } + Exit; + end; + asml.remove(hp1); + hp1.free; + end + else + p:=taicpu(hp1); + end + else + Break; + end; + + { If hp is a label, strip it if its reference count is zero. Repeat until + a non-label is found, or a label with a non-zero reference count. + True is returned if something was stripped } + function TAOptObj.StripDeadLabels(hp: tai; var NextValid: tai): Boolean; + var + tmp, tmpNext: tai; + hp1: tai; + CurrentAlign: tai; + begin + CurrentAlign := nil; + Result := False; + hp1 := hp; + NextValid := hp; + + { Stop if hp is an instruction, for example } + while (hp1 <> BlockEnd) and (hp1.typ in [ait_label,ait_align]) do + begin + case hp1.typ of + ait_label: + begin + with tai_label(hp1).labsym do + if is_used or (bind <> AB_LOCAL) or (labeltype <> alt_jump) then + begin + { Valid label } + if Result then + NextValid := hp1; + + DebugWrite('JUMP DEBUG: Last label in cluster:' + tostr(labelnr)); + + Exit; + end; + + DebugWrite('JUMP DEBUG: Removed label ' + tostr(TAsmLabel(tai_label(hp1).labsym).labelnr)); + + { Set tmp to the next valid entry } + tmp := tai(hp1.Next); + { Remove label } + AsmL.Remove(hp1); + hp1.Free; + + hp1 := tmp; + + Result := True; + Continue; + end; + { Also remove the align if it comes before an unused label } + ait_align: + begin + tmp := tai(hp1.Next); + if tmp = BlockEnd then + { End of block } + Exit; + + if (cs_debuginfo in current_settings.moduleswitches) or + (cs_use_lineinfo in current_settings.globalswitches) then + { Don't remove aligns if debuginfo is present } + begin + if (tmp.typ in [ait_label,ait_align]) then + begin + hp1 := tmp; + Continue; + end + else + Break; + end; + + repeat + + case tmp.typ of + ait_align: { Merge the aligns if permissible } + begin + { Check the maxbytes field though, since this may result in the + alignment being ignored } + if ((tai_align_abstract(hp1).maxbytes = 0) and (tai_align_abstract(tmp).maxbytes = 0)) or + { If a maxbytes field is present, only merge if the aligns have the same granularity } + ((tai_align_abstract(hp1).aligntype = tai_align_abstract(tmp).aligntype)) then + begin + with tai_align_abstract(hp1) do + begin + aligntype := max(aligntype, tai_align_abstract(tmp).aligntype); + maxbytes := max(maxbytes, tai_align_abstract(tmp).maxbytes); + fillsize := max(fillsize, tai_align_abstract(tmp).fillsize); + use_op := use_op or tai_align_abstract(tmp).use_op; + + if use_op and (tai_align_abstract(tmp).fillop <> 0) then + fillop := tai_align_abstract(tmp).fillop; + end; + + tmpNext := tai(tmp.Next); + AsmL.Remove(tmp); + tmp.Free; + Result := True; + tmp := tmpNext; + end + else + tmp := tai(tmp.Next); + + Continue; + end; + ait_label: + begin + { Signal that we can possibly delete this align entry } + CurrentAlign := hp1; + + repeat + with tai_label(tmp).labsym do + if is_used or (bind <> AB_LOCAL) or (labeltype <> alt_jump) then + begin + { Valid label } + if Result then + NextValid := tmp; + + DebugWrite('JUMP DEBUG: Last label in cluster:' + tostr(labelnr)); + + Exit; + end; + + DebugWrite('JUMP DEBUG: Removed label ' + tostr(TAsmLabel(tai_label(tmp).labsym).labelnr)); + + { Remove label } + tmpNext := tai(tmp.Next); + AsmL.Remove(tmp); + tmp.Free; + Result := True; + tmp := tmpNext; + + { Loop here for a minor performance gain } + until (tmp = BlockEnd) or (tmp.typ <> ait_label); + + { Re-evaluate the align and see what follows } + Continue; + end + else + begin + { Set hp1 to the instruction after the align, because the + align might get deleted later and hence set NextValid + to a dangling pointer. [Kit] } + hp1 := tmp; + Break; + end; + end; + until (tmp = BlockEnd); + + { Break out of the outer loop if the above Break is called } + if (hp1 = tmp) then + Break; + end + else + Break; + end; + hp1 := tai(hp1.Next); + end; + + { hp1 will be the next valid entry } + NextValid := hp1; + + { Remove the alignment field (but only if the next valid entry is not a live label) } + while Assigned(CurrentAlign) and (CurrentAlign.typ = ait_align) do + begin + DebugWrite('JUMP DEBUG: Alignment field removed'); + + tmp := tai(CurrentAlign.next); + + AsmL.Remove(CurrentAlign); + CurrentAlign.Free; + + CurrentAlign := tmp; + end; + end; + + + { Strips a label and any aligns that appear before it (if hp points to + them rather than the label). Only call this procedure on a label that + you already know is no longer referenced } + procedure TAOptObj.StripLabelFast(hp: tai); {$ifdef USEINLINE}inline;{$endif USEINLINE} + var + tmp: tai; + begin + repeat + case hp.typ of + ait_align: + begin + tmp := tai(hp.Next); + asml.Remove(hp); + hp.Free; + hp := tmp; + { Control flow will now return to 'repeat' } + end; + ait_label: + begin +{$ifdef EXTDEBUG} + { When not in debug mode, deleting a live label will cause an + access violation later on. [Kit] } + if tai_label(hp).labsym.getrefs <> 0 then + InternalError(2019110802); +{$endif EXTDEBUG} + asml.Remove(hp); + hp.Free; + Exit; + end; + else + InternalError(2019110801); + end; + until False; + end; + + { If a group of labels are clustered, change the jump to point to the last one + that is still referenced } + function TAOptObj.CollapseLabelCluster(jump: tai; var lbltai: tai): TAsmLabel; + var + LastLabel: TAsmLabel; + hp2: tai; + begin + Result := tai_label(lbltai).labsym; + LastLabel := Result; + hp2 := tai(lbltai.next); + + while (hp2 <> BlockEnd) and (hp2.typ in SkipInstr + [ait_align, ait_label]) do + begin + + if (hp2.typ = ait_label) and + (tai_label(hp2).labsym.is_used) and + (tai_label(hp2).labsym.labeltype = alt_jump) then + LastLabel := tai_label(hp2).labsym; + + hp2 := tai(hp2.next); + end; + + if (Result <> LastLabel) then + begin + Result.decrefs; + JumpTargetOp(taicpu(jump))^.ref^.symbol := LastLabel; + LastLabel.increfs; + Result := LastLabel; + lbltai := hp2; + end; + end; + +{$ifndef JVM} + function TAOptObj.OptimizeConditionalJump(CJLabel: TAsmLabel; var p: tai; hp1: tai; var stoploop: Boolean): Boolean; + var + hp2: tai; + NCJLabel: TAsmLabel; + begin + Result := False; + while (hp1 <> BlockEnd) do + begin + StripDeadLabels(hp1, hp1); + + if (hp1 <> BlockEnd) and + (tai(hp1).typ=ait_instruction) and + IsJumpToLabel(taicpu(hp1)) then + begin + NCJLabel := TAsmLabel(JumpTargetOp(taicpu(hp1))^.ref^.symbol); + + if IsJumpToLabelUncond(taicpu(hp1)) then + begin + { Do it now to get it out of the way and to aid optimisations + later on in this method } + RemoveDeadCodeAfterJump(taicpu(hp1)); + + { GetNextInstruction could be factored out, but hp2 might be + different after "RemoveDeadCodeAfterJump" } + GetNextInstruction(hp1, hp2); + + { Check for: + jmp @Lbl + jmp @Lbl + } + if (CJLabel = NCJLabel) then + begin + DebugWrite('JUMP DEBUG: Short-circuited conditional jump'); + { Both jumps go to the same label } + CJLabel.decrefs; +{$ifdef cpudelayslot} + RemoveDelaySlot(p); +{$endif cpudelayslot} + UpdateUsedRegs(tai(p.Next)); + AsmL.Remove(p); + p.Free; + p := hp1; + + Result := True; + Exit; + end; + + if FindLabel(CJLabel, hp2) then + begin + { change the following jumps: + jmp CJLabel jmp NCJLabel + jmp NCJLabel >>> + CJLabel: NCJLabel: + + NCJLabel: + } +{$if defined(arm) or defined(aarch64)} + if (taicpu(p).condition<>C_None) +{$if defined(aarch64)} + { can't have conditional branches to + global labels on AArch64, because the + offset may become too big } + and (NCJLabel.bind=AB_LOCAL) +{$endif aarch64} + then + begin +{$endif arm or aarch64} + DebugWrite('JUMP DEBUG: Conditional jump inversion'); + + taicpu(p).condition:=inverse_cond(taicpu(p).condition); + CJLabel.decrefs; + + CJLabel := NCJLabel; + JumpTargetOp(taicpu(p))^.ref^.symbol := NCJLabel; + + { when freeing hp1, the reference count + isn't decreased, so don't increase } +{$ifdef cpudelayslot} + RemoveDelaySlot(hp1); +{$endif cpudelayslot} + asml.remove(hp1); + hp1.free; + + hp1 := tai(p.Next); + + { Attempt another iteration in case more jumps follow } + Continue; +{$if defined(arm) or defined(aarch64)} + end; +{$endif arm or aarch64} + end + else if Assigned(hp2) and CollapseZeroDistJump(hp1, hp2, NCJLabel) then + begin + { Attempt another iteration in case more jumps follow } + Continue; + end; + end + else + begin + GetNextInstruction(hp1, hp2); + + { Check for: + jmp @Lbl1 + jmp @Lbl2 + + Remove 2nd jump if conditions are equal or cond2 is fully covered by cond1 + } + + if condition_in(taicpu(hp1).condition, taicpu(p).condition) then + begin + DebugWrite('JUMP DEBUG: Dominated conditional jump'); + + NCJLabel.decrefs; + AsmL.Remove(hp1); + hp1.Free; + hp1 := tai(p.Next); + + { Flag another pass in case @Lbl2 appeared earlier in the procedure and is now a dead label } + stoploop := False; + { Attempt another iteration in case more jumps follow } + Continue; + end; + + { Check for: + jmp @Lbl1 + jmp @Lbl2 + + And inv(cond2) is a subset of cond1 (e.g. je followed by jne, or jae followed by jbe) ) + } + if condition_in(inverse_cond(taicpu(hp1).condition), taicpu(p).condition) then + begin + { If @lbl1 immediately follows jmp, we can remove + the first jump completely } + if FindLabel(CJLabel, hp2) then + begin + DebugWrite('JUMP DEBUG: jmp before jmp - removed first jump'); + + CJLabel.decrefs; +{$ifdef cpudelayslot} + RemoveDelaySlot(p); +{$endif cpudelayslot} + UpdateUsedRegs(tai(p.Next)); + AsmL.Remove(p); + p.Free; + p := hp1; + + Result := True; + Exit; + +{$if not defined(avr) and not defined(riscv32) and not defined(riscv64)} + end + else + { NOTE: There is currently no watertight, cross-platform way to create + an unconditional jump without access to the cg object. If anyone can + improve this particular optimisation to work on AVR and RISC-V, + please do. [Kit]} + begin + { Since cond1 is a subset of inv(cond2), jmp will always branch if + jmp does not, so change jmp to an unconditional jump. } + + DebugWrite('JUMP DEBUG: jmp before jmp - made second jump unconditional'); + +{$if defined(powerpc) or defined(powerpc64)} + taicpu(hp2).condition.cond := C_None; + taicpu(hp2).condition.simple := True; +{$else powerpc} + taicpu(hp2).condition := C_None; +{$endif powerpc} + taicpu(hp2).opcode := aopt_uncondjmp; + + { NOTE: Changing the jump to unconditional won't open up new opportunities + for GetFinalDestination on earlier jumps because there's no live label + between the two jump instructions, so setting 'stoploop' to False only + wastes time. [Kit] } + + { See if more optimisations are possible } + Continue; +{$endif} + end; + end; + end; + end; + + if GetFinalDestination(taicpu(p),0) then + stoploop := False; + + Exit; + end; + + end; +{$endif JVM} + + function TAOptObj.CollapseZeroDistJump(var p: tai; hp1: tai; ThisLabel: TAsmLabel): Boolean; + var + tmp: tai; + begin + Result := False; + + { remove jumps to labels coming right after them } + if FindLabel(ThisLabel, hp1) and + { Cannot remove the first instruction } + (p<>StartPoint) then + begin + ThisLabel.decrefs; + + tmp := tai(p.Next); { Might be an align before the label } +{$ifdef cpudelayslot} + RemoveDelaySlot(p); +{$endif cpudelayslot} + asml.remove(p); + p.free; + + StripDeadLabels(tmp, p); + + if p.typ <> ait_instruction then + GetNextInstruction(UpdateUsedRegsAndOptimize(p), p); + + Result := True; + end; + + end; + + + function TAOptObj.DoJumpOptimizations(var p: tai; var stoploop: Boolean): Boolean; + var + hp1, hp2: tai; + ThisLabel: TAsmLabel; + ThisPassResult: Boolean; + begin + Result := False; + if (p.typ <> ait_instruction) or not IsJumpToLabel(taicpu(p)) then + Exit; + + repeat + ThisPassResult := False; + + if GetNextInstruction(p, hp1) and (hp1 <> BlockEnd) then + begin + SkipEntryExitMarker(hp1,hp1); + if (hp1 = BlockEnd) then + Exit; + + ThisLabel := TAsmLabel(JumpTargetOp(taicpu(p))^.ref^.symbol); + + hp2 := getlabelwithsym(ThisLabel); + + { getlabelwithsym returning nil occurs if a label is in a + different block (e.g. on the other side of an asm...end pair). } + if Assigned(hp2) then + begin + { If there are multiple labels in a row, change the destination to the last one + in order to aid optimisation later } + ThisLabel := CollapseLabelCluster(p, hp2); + + if CollapseZeroDistJump(p, hp1, ThisLabel) then + begin + stoploop := False; + Result := True; + Exit; + end; + + if IsJumpToLabelUncond(taicpu(p)) then + begin + { Remove unreachable code between the jump and the next label } + RemoveDeadCodeAfterJump(taicpu(p)); + + ThisPassResult := GetFinalDestination(taicpu(p), 0); + + if ThisPassResult then + { Might have caused some earlier labels to become dead } + stoploop := False; + end +{$ifndef JVM} + else if (taicpu(p).opcode = aopt_condjmp) then + ThisPassResult := OptimizeConditionalJump(ThisLabel, p, hp1, stoploop) +{$endif JVM} + ; + end; + + end; + + Result := Result or ThisPassResult; + until not (ThisPassResult and (p.typ = ait_instruction) and IsJumpToLabel(taicpu(p))); + + end; + + function TAOptObj.GetFinalDestination(hp: taicpu; level: longint): boolean; {traces sucessive jumps to their final destination and sets it, e.g. - je l1 je l3 - + je l1 je l3 l1: becomes l1: je l2 je l3 l2: l2: jmp l3 jmp l3 - the level parameter denotes how deeep we have already followed the jump, + the level parameter denotes how deep we have already followed the jump, to avoid endless loops with constructs such as "l5: ; jmp l5" } var p1: tai; - {$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} p2: tai; - l: tasmlabel; - {$endif} +{$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} + p3: tai; +{$endif} + ThisLabel, l: tasmlabel; begin - GetfinalDestination := false; + GetFinalDestination := false; if level > 20 then exit; - p1 := getlabelwithsym(tasmlabel(JumpTargetOp(hp)^.ref^.symbol)); + + ThisLabel := TAsmLabel(JumpTargetOp(hp)^.ref^.symbol); + p1 := getlabelwithsym(ThisLabel); if assigned(p1) then begin SkipLabels(p1,p1); - if (tai(p1).typ = ait_instruction) and + if (p1.typ = ait_instruction) and (taicpu(p1).is_jmp) then - if { the next instruction after the label where the jump hp arrives} - { is unconditional or of the same type as hp, so continue } - IsJumpToLabelUncond(taicpu(p1)) -{$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} -{ for MIPS, it isn't enough to check the condition; first operands must be same, too. } - or - conditions_equal(taicpu(p1).condition,hp.condition) or + begin + p2 := tai(p1.Next); + + { Collapse any zero distance jumps we stumble across } + while (p1<>StartPoint) and CollapseZeroDistJump(p1, p2, TAsmLabel(JumpTargetOp(taicpu(p1))^.ref^.symbol)) do + begin + { Note: Cannot remove the first instruction } + if (p1.typ = ait_label) then + SkipLabels(p1, p1); + + if not Assigned(p1) then + { No more valid commands } + Exit; + + { Check to see that we are actually still at a jump } + if not ((tai(p1).typ = ait_instruction) and (taicpu(p1).is_jmp)) then + begin + { Required to ensure recursion works properly, but to also + return false if a jump isn't modified. [Kit] } + if level > 0 then GetFinalDestination := True; + Exit; + end; + + p2 := tai(p1.Next); + if p2 = BlockEnd then + Exit; + end; - { the next instruction after the label where the jump hp arrives - is the opposite of hp (so this one is never taken), but after - that one there is a branch that will be taken, so perform a - little hack: set p1 equal to this instruction (that's what the - last SkipLabels is for, only works with short bool evaluation)} - (conditions_equal(taicpu(p1).condition,inverse_cond(hp.condition)) and - SkipLabels(p1,p2) and - (p2.typ = ait_instruction) and - (taicpu(p2).is_jmp) and - (IsJumpToLabelUncond(taicpu(p2)) or - (conditions_equal(taicpu(p2).condition,hp.condition))) and - SkipLabels(p1,p1)) -{$endif not MIPS and not RV64 and not RV32 and not JVM} - then - begin - { quick check for loops of the form "l5: ; jmp l5 } - if (tasmlabel(JumpTargetOp(taicpu(p1))^.ref^.symbol).labelnr = - tasmlabel(JumpTargetOp(hp)^.ref^.symbol).labelnr) then - exit; - if not GetFinalDestination(taicpu(p1),succ(level)) then - exit; -{$if defined(aarch64)} - { can't have conditional branches to - global labels on AArch64, because the - offset may become too big } - if not(taicpu(hp).condition in [C_None,C_AL,C_NV]) and - (tasmlabel(JumpTargetOp(taicpu(p1))^.ref^.symbol).bind<>AB_LOCAL) then - exit; -{$endif aarch64} - tasmlabel(JumpTargetOp(hp)^.ref^.symbol).decrefs; - JumpTargetOp(hp)^.ref^.symbol:=JumpTargetOp(taicpu(p1))^.ref^.symbol; - tasmlabel(JumpTargetOp(hp)^.ref^.symbol).increfs; - end {$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} - else - if conditions_equal(taicpu(p1).condition,inverse_cond(hp.condition)) then - if not FindAnyLabel(p1,l) then + p3 := p2; +{$endif not MIPS and not RV64 and not RV32 and not JVM} + + if { the next instruction after the label where the jump hp arrives} + { is unconditional or of the same type as hp, so continue } + IsJumpToLabelUncond(taicpu(p1)) + + { TODO: For anyone with experience with MIPS or RISC-V, please add support for tracing + conditional jumps. [Kit] } + +{$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} + { for MIPS, it isn't enough to check the condition; first operands must be same, too. } + or + condition_in(hp.condition, taicpu(p1).condition) or + + { the next instruction after the label where the jump hp arrives + is the opposite of hp (so this one is never taken), but after + that one there is a branch that will be taken, so perform a + little hack: set p1 equal to this instruction } + (condition_in(hp.condition, inverse_cond(taicpu(p1).condition)) and + SkipLabels(p3,p2) and + (p2.typ = ait_instruction) and + (taicpu(p2).is_jmp) and + (IsJumpToLabelUncond(taicpu(p2)) or + (condition_in(hp.condition, taicpu(p2).condition)) + ) and + SetAndTest(p2,p1) + ) +{$endif not MIPS and not RV64 and not RV32 and not JVM} + then + begin + { quick check for loops of the form "l5: ; jmp l5" } + if (TAsmLabel(JumpTargetOp(taicpu(p1))^.ref^.symbol).labelnr = ThisLabel.labelnr) then + exit; + if not GetFinalDestination(taicpu(p1),succ(level)) then + exit; + + { NOTE: Do not move this before the "l5: ; jmp l5" check, + because GetFinalDestination may change the destination + label of p1. [Kit] } + + l := tasmlabel(JumpTargetOp(taicpu(p1))^.ref^.symbol); + +{$if defined(aarch64)} + { can't have conditional branches to + global labels on AArch64, because the + offset may become too big } + if not(taicpu(hp).condition in [C_None,C_AL,C_NV]) and + (l.bind<>AB_LOCAL) then + exit; +{$endif aarch64} + ThisLabel.decrefs; + JumpTargetOp(hp)^.ref^.symbol:=l; + l.increfs; + GetFinalDestination := True; + Exit; + end +{$if not defined(MIPS) and not defined(riscv64) and not defined(riscv32) and not defined(JVM)} + else + if condition_in(inverse_cond(hp.condition), taicpu(p1).condition) then begin - {$ifdef finaldestdebug} - insertllitem(asml,p1,p1.next,tai_comment.Create( - strpnew('previous label inserted')))); - {$endif finaldestdebug} - current_asmdata.getjumplabel(l); - insertllitem(p1,p1.next,tai_label.Create(l)); - tasmlabel(JumpTargetOp(hp)^.ref^.symbol).decrefs; - JumpTargetOp(hp)^.ref^.symbol := l; - l.increfs; - { this won't work, since the new label isn't in the labeltable } - { so it will fail the rangecheck. Labeltable should become a } - { hashtable to support this: } - { GetFinalDestination(asml, hp); } - end - else - begin - {$ifdef finaldestdebug} - insertllitem(asml,p1,p1.next,tai_comment.Create( - strpnew('next label reused')))); - {$endif finaldestdebug} - l.increfs; - tasmlabel(JumpTargetOp(hp)^.ref^.symbol).decrefs; - JumpTargetOp(hp)^.ref^.symbol := l; - if not GetFinalDestination(hp,succ(level)) then - exit; + if not FindLiveLabel(p1,l) then + begin +{$ifdef finaldestdebug} + insertllitem(asml,p1,p1.next,tai_comment.Create( + strpnew('previous label inserted')))); +{$endif finaldestdebug} + current_asmdata.getjumplabel(l); + insertllitem(p1,p1.next,tai_label.Create(l)); + + ThisLabel.decrefs; + JumpTargetOp(hp)^.ref^.symbol := l; + l.increfs; + GetFinalDestination := True; + { this won't work, since the new label isn't in the labeltable } + { so it will fail the rangecheck. Labeltable should become a } + { hashtable to support this: } + { GetFinalDestination(asml, hp); } + end + else + begin +{$ifdef finaldestdebug} + insertllitem(asml,p1,p1.next,tai_comment.Create( + strpnew('next label reused')))); +{$endif finaldestdebug} + l.increfs; + ThisLabel.decrefs; + JumpTargetOp(hp)^.ref^.symbol := l; + if not GetFinalDestination(hp,succ(level)) then + exit; + end; + GetFinalDestination := True; + Exit; end; {$endif not MIPS and not RV64 and not RV32 and not JVM} + end; end; - GetFinalDestination := true; + + { Required to ensure recursion works properly, but to also + return false if a jump isn't modified. [Kit] } + if level > 0 then GetFinalDestination := True; end; @@ -1554,14 +2319,20 @@ Unit AoptObj; procedure TAOptObj.PeepHoleOptPass1; var p,hp1,hp2,hp3 : tai; - stoploop:boolean; + stoploop, FirstInstruction: boolean; begin + StartPoint := BlockStart; + repeat stoploop:=true; - p := BlockStart; + p := StartPoint; + FirstInstruction := True; ClearUsedRegs; - while (p <> BlockEnd) Do + + while Assigned(p) and (p <> BlockEnd) Do begin + prefetch(p.Next); + { I'am not sure why this is done, UsedRegs should reflect the register usage before the instruction If an instruction needs the information of this, it can easily create a TempUsedRegs (FK) UpdateUsedRegs(tai(p.next)); @@ -1570,155 +2341,39 @@ Unit AoptObj; if p.Typ=ait_instruction then InsertLLItem(tai(p.Previous),p,tai_comment.create(strpnew(GetAllocationString(UsedRegs)))); {$endif DEBUG_OPTALLOC} + + { Handle Jmp Optimizations first } + if DoJumpOptimizations(p, stoploop) then + begin + UpdateUsedRegs(p); + if FirstInstruction then + { Update StartPoint, since the old p was removed; + don't set FirstInstruction to False though, as + the new p might get removed too. } + StartPoint := p; + + if (p.typ = ait_instruction) and IsJumpToLabel(taicpu(p)) then + Continue; + end; + if PeepHoleOptPass1Cpu(p) then begin stoploop:=false; UpdateUsedRegs(p); + + if FirstInstruction then + { Update StartPoint, since the old p was modified; + don't set FirstInstruction to False though, as + the new p might get modified too. } + StartPoint := p; + continue; end; - case p.Typ Of - ait_instruction: - begin - { Handle Jmp Optimizations } - if taicpu(p).is_jmp then - begin - { the following if-block removes all code between a jmp and the next label, - because it can never be executed - } - if IsJumpToLabelUncond(taicpu(p)) then - begin - hp2:=p; - while GetNextInstruction(hp2, hp1) and - (hp1.typ <> ait_label) -{$ifdef JVM} - and (hp1.typ <> ait_jcatch) -{$endif} - do - if not(hp1.typ in ([ait_label]+skipinstr)) then - begin - if (hp1.typ = ait_instruction) and - taicpu(hp1).is_jmp and - (JumpTargetOp(taicpu(hp1))^.typ = top_ref) and - (JumpTargetOp(taicpu(hp1))^.ref^.symbol is TAsmLabel) then - TAsmLabel(JumpTargetOp(taicpu(hp1))^.ref^.symbol).decrefs; - { don't kill start/end of assembler block, - no-line-info-start/end, cfi end, etc } - if not(hp1.typ in [ait_align,ait_marker]) and - ((hp1.typ<>ait_cfi) or - (tai_cfi_base(hp1).cfityp<>cfi_endproc)) then - begin -{$ifdef cpudelayslot} - if (hp1.typ=ait_instruction) and (taicpu(hp1).is_jmp) then - RemoveDelaySlot(hp1); -{$endif cpudelayslot} - asml.remove(hp1); - hp1.free; - stoploop:=false; - end - else - hp2:=hp1; - end - else break; - end; - if GetNextInstruction(p, hp1) then - begin - SkipEntryExitMarker(hp1,hp1); - { remove unconditional jumps to a label coming right after them } - if IsJumpToLabelUncond(taicpu(p)) and - FindLabel(tasmlabel(JumpTargetOp(taicpu(p))^.ref^.symbol), hp1) and - { TODO: FIXME removing the first instruction fails} - (p<>blockstart) then - begin - tasmlabel(JumpTargetOp(taicpu(p))^.ref^.symbol).decrefs; -{$ifdef cpudelayslot} - RemoveDelaySlot(p); -{$endif cpudelayslot} - hp2:=tai(hp1.next); - asml.remove(p); - p.free; - p:=hp2; - stoploop:=false; - continue; - end - else if assigned(hp1) then - begin - { change the following jumps: - jmp lab_1 jmp lab_2 - jmp lab_2 >>> - lab_1: lab_2: - - lab_2: - } - hp3:=hp1; - if hp1.typ = ait_label then - SkipLabels(hp1,hp1); - if (tai(hp1).typ=ait_instruction) and - IsJumpToLabelUncond(taicpu(hp1)) and - GetNextInstruction(hp1, hp2) and - IsJumpToLabel(taicpu(p)) and - FindLabel(tasmlabel(JumpTargetOp(taicpu(p))^.ref^.symbol), hp2) and - { prevent removal of infinite loop from - jmp lab_1 - lab_2: - jmp lab2 - .. - lab_1: - } - not FindLabel(tasmlabel(JumpTargetOp(taicpu(hp1))^.ref^.symbol), hp3) then - begin - if (taicpu(p).opcode=aopt_condjmp) -{$if defined(arm) or defined(aarch64)} - and (taicpu(p).condition<>C_None) -{$endif arm or aarch64} -{$if defined(aarch64)} - { can't have conditional branches to - global labels on AArch64, because the - offset may become too big } - and (tasmlabel(JumpTargetOp(taicpu(hp1))^.ref^.symbol).bind=AB_LOCAL) -{$endif aarch64} - then - begin - taicpu(p).condition:=inverse_cond(taicpu(p).condition); - tai_label(hp2).labsym.decrefs; - JumpTargetOp(taicpu(p))^.ref^.symbol:=JumpTargetOp(taicpu(hp1))^.ref^.symbol; - { when freeing hp1, the reference count - isn't decreased, so don't increase - taicpu(p).oper[0]^.ref^.symbol.increfs; - } -{$ifdef cpudelayslot} - RemoveDelaySlot(hp1); -{$endif cpudelayslot} - asml.remove(hp1); - hp1.free; - stoploop:=false; - GetFinalDestination(taicpu(p),0); - end - else - begin - GetFinalDestination(taicpu(p),0); - p:=tai(p.next); - continue; - end; - end - else if IsJumpToLabel(taicpu(p)) then - GetFinalDestination(taicpu(p),0); - end; - end; - end - else - { All other optimizes } - begin - end; { if is_jmp } - end; - else - ; - end; + FirstInstruction := False; if assigned(p) then - begin - UpdateUsedRegs(p); - p:=tai(p.next); - end; + p := tai(UpdateUsedRegsAndOptimize(p).Next); + end; until stoploop or not(cs_opt_level3 in current_settings.optimizerswitches); end; @@ -1735,10 +2390,7 @@ Unit AoptObj; if PeepHoleOptPass2Cpu(p) then continue; if assigned(p) then - begin - UpdateUsedRegs(p); - p:=tai(p.next); - end; + p := tai(UpdateUsedRegsAndOptimize(p).Next); end; end;