New "ping-pong" DFA (#14322)
* New ping-pong analysis * Add testcase for #13456 * Remove debugging leftover * Unquote "unstructured controlflow" * Fix typo * Fix exponential complexity in edge cases * Add sanity testcase * Fix
This commit is contained in:
parent
13cfaf5fd5
commit
9f78f116b2
3 changed files with 163 additions and 73 deletions
|
|
@ -35,11 +35,11 @@ from patterns import sameTrees
|
|||
|
||||
type
|
||||
InstrKind* = enum
|
||||
goto, fork, join, def, use
|
||||
goto, fork, def, use
|
||||
Instr* = object
|
||||
n*: PNode # contains the def/use location.
|
||||
case kind*: InstrKind
|
||||
of goto, fork, join: dest*: int
|
||||
of goto, fork: dest*: int
|
||||
else: discard
|
||||
|
||||
ControlFlowGraph* = seq[Instr]
|
||||
|
|
@ -47,8 +47,6 @@ type
|
|||
TPosition = distinct int
|
||||
|
||||
TBlock = object
|
||||
joins: seq[Instr]
|
||||
forks: seq[TPosition]
|
||||
case isTryBlock: bool
|
||||
of false:
|
||||
label: PSym
|
||||
|
|
@ -72,7 +70,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
|
|||
var jumpTargets = initIntSet()
|
||||
let last = if last < 0: c.len-1 else: min(last, c.len-1)
|
||||
for i in start..last:
|
||||
if c[i].kind in {goto, fork, join}:
|
||||
if c[i].kind in {goto, fork}:
|
||||
jumpTargets.incl(i+c[i].dest)
|
||||
var i = start
|
||||
while i <= last:
|
||||
|
|
@ -83,7 +81,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
|
|||
case c[i].kind
|
||||
of def, use:
|
||||
result.add renderTree(c[i].n)
|
||||
of goto, fork, join:
|
||||
of goto, fork:
|
||||
result.add "L"
|
||||
result.addInt c[i].dest+i
|
||||
result.add("\t#")
|
||||
|
|
@ -102,7 +100,6 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
|
|||
proc forkI(c: var Con; n: PNode): TPosition =
|
||||
result = TPosition(c.code.len)
|
||||
c.code.add Instr(n: n, kind: fork, dest: 0)
|
||||
c.blocks[^1].forks.add result
|
||||
|
||||
proc gotoI(c: var Con; n: PNode): TPosition =
|
||||
result = TPosition(c.code.len)
|
||||
|
|
@ -110,6 +107,24 @@ proc gotoI(c: var Con; n: PNode): TPosition =
|
|||
|
||||
#[
|
||||
|
||||
Join is no more
|
||||
===============
|
||||
Instead of generating join instructions we adapt our traversal of the CFG.
|
||||
|
||||
When encountering a fork we split into two paths, we follow the path
|
||||
starting at "pc + 1" until it encounters the joinpoint: "pc + forkInstr.dest".
|
||||
If we encounter gotos that would jump further than the current joinpoint,
|
||||
as can happen with gotos generated by unstructured controlflow such as break, raise or return,
|
||||
we simply suspend following the current path, and follow the other path until the new joinpoint
|
||||
which is simply the instruction pointer returned to us by the now suspended path.
|
||||
If the path we are following now, also encounters a goto that exceeds the joinpoint
|
||||
we repeat the process; suspending the current path and evaluating the other one with a new joinpoint.
|
||||
If we eventually reach a common joinpoint we join the two paths.
|
||||
This new "ping-pong" approach has the obvious advantage of not requiring join instructions, as such
|
||||
cutting down on the CFG size but is also mandatory for correctly handling complicated cases
|
||||
of unstructured controlflow.
|
||||
|
||||
|
||||
Design of join
|
||||
==============
|
||||
|
||||
|
|
@ -258,10 +273,6 @@ duplicate the 'join' instructions on breaks and return exits!
|
|||
|
||||
]#
|
||||
|
||||
proc joinI(c: var Con; fromFork: TPosition; n: PNode) =
|
||||
let dist = fromFork.int - c.code.len
|
||||
c.code.add Instr(n: n, kind: join, dest: dist)
|
||||
|
||||
proc genLabel(c: Con): TPosition =
|
||||
result = TPosition(c.code.len)
|
||||
|
||||
|
|
@ -289,10 +300,6 @@ proc popBlock(c: var Con; oldLen: int) =
|
|||
exits.add c.gotoI(newNode(nkEmpty))
|
||||
for e in exits:
|
||||
c.patch e
|
||||
for j in c.blocks[oldLen].joins:
|
||||
var patchedJ = j
|
||||
patchedJ.dest -= c.code.len
|
||||
c.code.add patchedJ
|
||||
c.blocks.setLen(oldLen)
|
||||
|
||||
template withBlock(labl: PSym; body: untyped) {.dirty.} =
|
||||
|
|
@ -353,7 +360,6 @@ when true:
|
|||
c.gen(n[1])
|
||||
else:
|
||||
withBlock(nil):
|
||||
let oldForksLen = c.blocks[^1].forks.len
|
||||
var endings: array[3, TPosition]
|
||||
for i in 0..2:
|
||||
c.gen(n[0])
|
||||
|
|
@ -362,8 +368,6 @@ when true:
|
|||
for i in countdown(endings.high, 0):
|
||||
let endPos = endings[i]
|
||||
c.patch(endPos)
|
||||
c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
doAssert(c.blocks[^1].forks.len == oldForksLen)
|
||||
|
||||
else:
|
||||
|
||||
|
|
@ -374,7 +378,6 @@ else:
|
|||
# body
|
||||
# jmp lab1
|
||||
# lab2:
|
||||
let oldForksLen = c.blocks[^1].forks.len
|
||||
let lab1 = c.genLabel
|
||||
withBlock(nil):
|
||||
if isTrue(n[0]):
|
||||
|
|
@ -386,15 +389,11 @@ else:
|
|||
c.gen(n[1])
|
||||
c.jmpBack(n, lab1)
|
||||
c.patch(lab2)
|
||||
setLen(c.blocks[^1].forks, oldForksLen)
|
||||
|
||||
template forkT(n, body) =
|
||||
let oldLen = c.blocks[^1].forks.len
|
||||
let lab1 = c.forkI(n)
|
||||
body
|
||||
c.patch(lab1)
|
||||
c.joinI(lab1, n)
|
||||
setLen(c.blocks[^1].forks, oldLen)
|
||||
|
||||
proc genIf(c: var Con, n: PNode) =
|
||||
#[
|
||||
|
|
@ -433,7 +432,6 @@ proc genIf(c: var Con, n: PNode) =
|
|||
join F1
|
||||
|
||||
]#
|
||||
let oldLen = c.blocks[^1].forks.len
|
||||
var endings: seq[TPosition] = @[]
|
||||
for i in 0..<n.len:
|
||||
var it = n[i]
|
||||
|
|
@ -446,8 +444,6 @@ proc genIf(c: var Con, n: PNode) =
|
|||
for i in countdown(endings.high, 0):
|
||||
let endPos = endings[i]
|
||||
c.patch(endPos)
|
||||
c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
doAssert(c.blocks[^1].forks.len == oldLen)
|
||||
|
||||
proc genAndOr(c: var Con; n: PNode) =
|
||||
# asgn dest, a
|
||||
|
|
@ -474,7 +470,6 @@ proc genCase(c: var Con; n: PNode) =
|
|||
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
|
||||
|
||||
var endings: seq[TPosition] = @[]
|
||||
let oldLen = c.blocks[^1].forks.len
|
||||
c.gen(n[0])
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
|
|
@ -491,8 +486,6 @@ proc genCase(c: var Con; n: PNode) =
|
|||
for i in countdown(endings.high, 0):
|
||||
let endPos = endings[i]
|
||||
c.patch(endPos)
|
||||
c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
doAssert(c.blocks[^1].forks.len == oldLen)
|
||||
|
||||
proc genBlock(c: var Con; n: PNode) =
|
||||
withBlock(n[0].sym):
|
||||
|
|
@ -511,10 +504,6 @@ proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
|
|||
|
||||
c.blocks[i].breakFixups.add (lab1, trailingFinales)
|
||||
|
||||
for b in countdown(c.blocks.high, i):
|
||||
for f in countdown(c.blocks[b].forks.high, 0):
|
||||
c.blocks[i].joins.add Instr(n: n, kind: join, dest: c.blocks[b].forks[f].int)
|
||||
|
||||
proc genBreak(c: var Con; n: PNode) =
|
||||
if n[0].kind == nkSym:
|
||||
#echo cast[int](n[0].sym)
|
||||
|
|
@ -530,7 +519,6 @@ proc genBreak(c: var Con; n: PNode) =
|
|||
return
|
||||
|
||||
proc genTry(c: var Con; n: PNode) =
|
||||
let oldForksLen = c.blocks[^1].forks.len
|
||||
var endings: seq[TPosition] = @[]
|
||||
|
||||
let oldLen = c.blocks.len
|
||||
|
|
@ -543,10 +531,6 @@ proc genTry(c: var Con; n: PNode) =
|
|||
|
||||
for f in c.blocks[oldLen].raiseFixups:
|
||||
c.patch(f)
|
||||
for j in c.blocks[oldLen].joins:
|
||||
var patchedJ = j
|
||||
patchedJ.dest -= c.code.len
|
||||
c.code.add patchedJ
|
||||
|
||||
c.blocks.setLen oldLen
|
||||
|
||||
|
|
@ -561,7 +545,6 @@ proc genTry(c: var Con; n: PNode) =
|
|||
for i in countdown(endings.high, 0):
|
||||
let endPos = endings[i]
|
||||
c.patch(endPos)
|
||||
c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
|
||||
# join the 'elsePos' forkI instruction:
|
||||
#c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
|
|
@ -569,7 +552,6 @@ proc genTry(c: var Con; n: PNode) =
|
|||
let fin = lastSon(n)
|
||||
if fin.kind == nkFinally:
|
||||
c.gen(fin[0])
|
||||
doAssert(c.blocks[^1].forks.len == oldForksLen)
|
||||
|
||||
template genNoReturn(c: var Con; n: PNode) =
|
||||
# leave the graph
|
||||
|
|
@ -751,7 +733,6 @@ proc genCall(c: var Con; n: PNode) =
|
|||
genBreakOrRaiseAux(c, i, n)
|
||||
break
|
||||
c.patch(endGoto)
|
||||
c.joinI(c.blocks[^1].forks.pop(), n)
|
||||
dec c.inCall
|
||||
|
||||
proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue