Fix the DFA for "unstructured controlflow" (#14263)

* Fix the DFA for "unstructured controlflow"

* Add testcase from #14233
This commit is contained in:
Clyybber 2020-05-07 21:41:55 +02:00 • committed by GitHub
commit 42db75c970
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 81 deletions

View file

@ -45,16 +45,22 @@ type
ControlFlowGraph* = seq[Instr] ControlFlowGraph* = seq[Instr]
TPosition = distinct int TPosition = distinct int
TBlock = object TBlock = object
joins: seq[Instr]
forks: seq[TPosition]
case isTryBlock: bool
of false:
label: PSym label: PSym
fixups: seq[TPosition] breakFixups: seq[(TPosition, seq[PNode])] #Contains the gotos for the breaks along with their pending finales
of true:
finale: PNode
raiseFixups: seq[TPosition] #Contains the gotos for the raises
Con = object Con = object
code: ControlFlowGraph code: ControlFlowGraph
inCall, inTryStmt: int inCall, inTryStmt: int
blocks: seq[TBlock] blocks: seq[TBlock]
tryStmtFixups: seq[TPosition]
forks: seq[TPosition]
owner: PSym owner: PSym
proc debugInfo(info: TLineInfo): string = proc debugInfo(info: TLineInfo): string =
@ -96,7 +102,7 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
proc forkI(c: var Con; n: PNode): TPosition = proc forkI(c: var Con; n: PNode): TPosition =
result = TPosition(c.code.len) result = TPosition(c.code.len)
c.code.add Instr(n: n, kind: fork, dest: 0) c.code.add Instr(n: n, kind: fork, dest: 0)
c.forks.add result c.blocks[^1].forks.add result
proc gotoI(c: var Con; n: PNode): TPosition = proc gotoI(c: var Con; n: PNode): TPosition =
result = TPosition(c.code.len) result = TPosition(c.code.len)
@ -271,14 +277,27 @@ proc patch(c: var Con, p: TPosition) =
doAssert(low(int) div 2 + 1 < diff and diff < high(int) div 2) doAssert(low(int) div 2 + 1 < diff and diff < high(int) div 2)
c.code[p].dest = diff c.code[p].dest = diff
proc gen(c: var Con; n: PNode) # {.noSideEffect.}
proc popBlock(c: var Con; oldLen: int) = proc popBlock(c: var Con; oldLen: int) =
for f in c.blocks[oldLen].fixups: var exits: seq[TPosition]
c.patch(f) exits.add c.gotoI(newNode(nkEmpty))
for f in c.blocks[oldLen].breakFixups:
c.patch(f[0])
for finale in f[1]:
c.gen(finale)
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) c.blocks.setLen(oldLen)
template withBlock(labl: PSym; body: untyped) {.dirty.} = template withBlock(labl: PSym; body: untyped) {.dirty.} =
var oldLen {.gensym.} = c.blocks.len var oldLen {.gensym.} = c.blocks.len
c.blocks.add TBlock(label: labl, fixups: @[]) c.blocks.add TBlock(isTryBlock: false, label: labl)
body body
popBlock(c, oldLen) popBlock(c, oldLen)
@ -286,8 +305,6 @@ proc isTrue(n: PNode): bool =
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
n.kind == nkIntLit and n.intVal != 0 n.kind == nkIntLit and n.intVal != 0
proc gen(c: var Con; n: PNode) # {.noSideEffect.}
when true: when true:
proc genWhile(c: var Con; n: PNode) = proc genWhile(c: var Con; n: PNode) =
# We unroll every loop 3 times. We emulate 0, 1, 2 iterations # We unroll every loop 3 times. We emulate 0, 1, 2 iterations
@ -299,6 +316,7 @@ when true:
Becomes: Becomes:
block:
if cond: if cond:
body body
if cond: if cond:
@ -330,22 +348,22 @@ when true:
if isTrue(n[0]): if isTrue(n[0]):
# 'while true' is an idiom in Nim and so we produce # 'while true' is an idiom in Nim and so we produce
# better code for it: # better code for it:
for i in 0..2:
withBlock(nil): withBlock(nil):
for i in 0..2:
c.gen(n[1]) c.gen(n[1])
else: else:
let oldForksLen = c.forks.len withBlock(nil):
let oldForksLen = c.blocks[^1].forks.len
var endings: array[3, TPosition] var endings: array[3, TPosition]
for i in 0..2: for i in 0..2:
withBlock(nil):
c.gen(n[0]) c.gen(n[0])
endings[i] = c.forkI(n) endings[i] = c.forkI(n)
c.gen(n[1]) c.gen(n[1])
for i in countdown(endings.high, 0): for i in countdown(endings.high, 0):
let endPos = endings[i] let endPos = endings[i]
c.patch(endPos) c.patch(endPos)
c.joinI(c.forks.pop(), n) c.joinI(c.blocks[^1].forks.pop(), n)
doAssert(c.forks.len == oldForksLen) doAssert(c.blocks[^1].forks.len == oldForksLen)
else: else:
@ -356,7 +374,7 @@ else:
# body # body
# jmp lab1 # jmp lab1
# lab2: # lab2:
let oldForksLen = c.forks.len let oldForksLen = c.blocks[^1].forks.len
let lab1 = c.genLabel let lab1 = c.genLabel
withBlock(nil): withBlock(nil):
if isTrue(n[0]): if isTrue(n[0]):
@ -368,35 +386,15 @@ else:
c.gen(n[1]) c.gen(n[1])
c.jmpBack(n, lab1) c.jmpBack(n, lab1)
c.patch(lab2) c.patch(lab2)
setLen(c.forks, oldForksLen) setLen(c.blocks[^1].forks, oldForksLen)
proc genBlock(c: var Con; n: PNode) =
withBlock(n[0].sym):
c.gen(n[1])
proc genJoins(c: var Con; n: PNode) =
for i in countdown(c.forks.high, 0): joinI(c, c.forks[i], n)
proc genBreak(c: var Con; n: PNode) =
genJoins(c, n)
let lab1 = c.gotoI(n)
if n[0].kind == nkSym:
#echo cast[int](n[0].sym)
for i in countdown(c.blocks.len-1, 0):
if c.blocks[i].label == n[0].sym:
c.blocks[i].fixups.add lab1
return
#globalError(n.info, "VM problem: cannot find 'break' target")
else:
c.blocks[c.blocks.high].fixups.add lab1
template forkT(n, body) = template forkT(n, body) =
let oldLen = c.forks.len let oldLen = c.blocks[^1].forks.len
let lab1 = c.forkI(n) let lab1 = c.forkI(n)
body body
c.patch(lab1) c.patch(lab1)
c.joinI(lab1, n) c.joinI(lab1, n)
setLen(c.forks, oldLen) setLen(c.blocks[^1].forks, oldLen)
proc genIf(c: var Con, n: PNode) = proc genIf(c: var Con, n: PNode) =
#[ #[
@ -435,7 +433,7 @@ proc genIf(c: var Con, n: PNode) =
join F1 join F1
]# ]#
let oldLen = c.forks.len let oldLen = c.blocks[^1].forks.len
var endings: seq[TPosition] = @[] var endings: seq[TPosition] = @[]
for i in 0..<n.len: for i in 0..<n.len:
var it = n[i] var it = n[i]
@ -448,8 +446,8 @@ proc genIf(c: var Con, n: PNode) =
for i in countdown(endings.high, 0): for i in countdown(endings.high, 0):
let endPos = endings[i] let endPos = endings[i]
c.patch(endPos) c.patch(endPos)
c.joinI(c.forks.pop(), n) c.joinI(c.blocks[^1].forks.pop(), n)
doAssert(c.forks.len == oldLen) doAssert(c.blocks[^1].forks.len == oldLen)
proc genAndOr(c: var Con; n: PNode) = proc genAndOr(c: var Con; n: PNode) =
# asgn dest, a # asgn dest, a
@ -476,7 +474,7 @@ proc genCase(c: var Con; n: PNode) =
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
var endings: seq[TPosition] = @[] var endings: seq[TPosition] = @[]
let oldLen = c.forks.len let oldLen = c.blocks[^1].forks.len
c.gen(n[0]) c.gen(n[0])
for i in 1..<n.len: for i in 1..<n.len:
let it = n[i] let it = n[i]
@ -493,27 +491,64 @@ proc genCase(c: var Con; n: PNode) =
for i in countdown(endings.high, 0): for i in countdown(endings.high, 0):
let endPos = endings[i] let endPos = endings[i]
c.patch(endPos) c.patch(endPos)
c.joinI(c.forks.pop(), n) c.joinI(c.blocks[^1].forks.pop(), n)
doAssert(c.forks.len == oldLen) doAssert(c.blocks[^1].forks.len == oldLen)
proc genBlock(c: var Con; n: PNode) =
withBlock(n[0].sym):
c.gen(n[1])
proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
let lab1 = c.gotoI(n)
if c.blocks[i].isTryBlock:
c.blocks[i].raiseFixups.add lab1
else:
var trailingFinales: seq[PNode]
if c.inTryStmt > 0: #Ok, we are in a try, lets see which (if any) try's we break out from:
for b in countdown(c.blocks.high, i):
if c.blocks[b].isTryBlock:
trailingFinales.add c.blocks[b].finale
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)
for i in countdown(c.blocks.high, 0):
if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym:
genBreakOrRaiseAux(c, i, n)
return
#globalError(n.info, "VM problem: cannot find 'break' target")
else:
for i in countdown(c.blocks.high, 0):
if not c.blocks[i].isTryBlock:
genBreakOrRaiseAux(c, i, n)
return
proc genTry(c: var Con; n: PNode) = proc genTry(c: var Con; n: PNode) =
let oldLen = c.forks.len let oldForksLen = c.blocks[^1].forks.len
var endings: seq[TPosition] = @[] var endings: seq[TPosition] = @[]
inc c.inTryStmt
let oldFixups = c.tryStmtFixups.len
let oldLen = c.blocks.len
c.blocks.add TBlock(isTryBlock: true, finale: if n[^1].kind == nkFinally: n[^1] else: newNode(nkEmpty))
inc c.inTryStmt
#let elsePos = c.forkI(n) #let elsePos = c.forkI(n)
c.gen(n[0]) c.gen(n[0])
dec c.inTryStmt dec c.inTryStmt
for i in oldFixups..c.tryStmtFixups.high:
let f = c.tryStmtFixups[i]
c.patch(f)
# we also need to produce join instructions
# for the 'fork' that might precede the goto instruction
if f.int-1 >= 0 and c.code[f.int-1].kind == fork:
c.joinI(TPosition(f.int-1), n)
setLen(c.tryStmtFixups, oldFixups) 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
#c.patch(elsePos) #c.patch(elsePos)
for i in 1..<n.len: for i in 1..<n.len:
@ -526,15 +561,15 @@ proc genTry(c: var Con; n: PNode) =
for i in countdown(endings.high, 0): for i in countdown(endings.high, 0):
let endPos = endings[i] let endPos = endings[i]
c.patch(endPos) c.patch(endPos)
c.joinI(c.forks.pop(), n) c.joinI(c.blocks[^1].forks.pop(), n)
# join the 'elsePos' forkI instruction: # join the 'elsePos' forkI instruction:
#c.joinI(c.forks.pop(), n) #c.joinI(c.blocks[^1].forks.pop(), n)
let fin = lastSon(n) let fin = lastSon(n)
if fin.kind == nkFinally: if fin.kind == nkFinally:
c.gen(fin[0]) c.gen(fin[0])
doAssert(c.forks.len == oldLen) doAssert(c.blocks[^1].forks.len == oldForksLen)
template genNoReturn(c: var Con; n: PNode) = template genNoReturn(c: var Con; n: PNode) =
# leave the graph # leave the graph
@ -542,9 +577,12 @@ template genNoReturn(c: var Con; n: PNode) =
proc genRaise(c: var Con; n: PNode) = proc genRaise(c: var Con; n: PNode) =
gen(c, n[0]) gen(c, n[0])
genJoins(c, n)
if c.inTryStmt > 0: if c.inTryStmt > 0:
c.tryStmtFixups.add c.gotoI(n) for i in countdown(c.blocks.high, 0):
if c.blocks[i].isTryBlock:
genBreakOrRaiseAux(c, i, n)
return
assert false #Unreachable
else: else:
genNoReturn(c, n) genNoReturn(c, n)
@ -557,8 +595,7 @@ proc genReturn(c: var Con; n: PNode) =
gen(c, n[0]) gen(c, n[0])
else: else:
genImplicitReturn(c) genImplicitReturn(c)
genJoins(c, n) genBreakOrRaiseAux(c, 0, n)
genNoReturn(c, n)
const const
InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp} InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp}
@ -708,9 +745,12 @@ proc genCall(c: var Con; n: PNode) =
# lab1: # lab1:
# join F1 # join F1
let endGoto = c.forkI(n) let endGoto = c.forkI(n)
c.tryStmtFixups.add c.gotoI(n) for i in countdown(c.blocks.high, 0):
if c.blocks[i].isTryBlock:
genBreakOrRaiseAux(c, i, n)
break
c.patch(endGoto) c.patch(endGoto)
c.joinI(c.forks.pop(), n) c.joinI(c.blocks[^1].forks.pop(), n)
dec c.inCall dec c.inCall
proc genMagic(c: var Con; n: PNode; m: TMagic) = proc genMagic(c: var Con; n: PNode; m: TMagic) =
@ -784,6 +824,7 @@ proc gen(c: var Con; n: PNode) =
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph = proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
## constructs a control flow graph for ``body``. ## constructs a control flow graph for ``body``.
var c = Con(code: @[], blocks: @[], owner: s) var c = Con(code: @[], blocks: @[], owner: s)
withBlock(s):
gen(c, body) gen(c, body)
genImplicitReturn(c) genImplicitReturn(c)
shallowCopy(result, c.code) shallowCopy(result, c.code)

View file

@ -87,9 +87,7 @@ proc isLastRead(location: PNode; c: var Con; pc, comesFrom: int): int =
if variantA < 0: return -1 if variantA < 0: return -1
var variantB = isLastRead(location, c, pc + c.g[pc].dest, pc) var variantB = isLastRead(location, c, pc + c.g[pc].dest, pc)
if variantB < 0: return -1 if variantB < 0: return -1
elif variantB == high(int): pc = min(variantA, variantB)
variantB = variantA
pc = variantB
of InstrKind.join: of InstrKind.join:
let dest = pc + c.g[pc].dest let dest = pc + c.g[pc].dest
if dest == comesFrom: return pc + 1 if dest == comesFrom: return pc + 1
@ -140,9 +138,7 @@ proc isFirstWrite(location: PNode; c: var Con; pc, comesFrom: int; instr: int):
if variantA < 0: return -1 if variantA < 0: return -1
var variantB = isFirstWrite(location, c, pc + c.g[pc].dest, pc, instr + c.g[pc].dest) var variantB = isFirstWrite(location, c, pc + c.g[pc].dest, pc, instr + c.g[pc].dest)
if variantB < 0: return -1 if variantB < 0: return -1
elif variantB == high(int): pc = min(variantA, variantB)
variantB = variantA
pc = variantB
of InstrKind.join: of InstrKind.join:
let dest = pc + c.g[pc].dest let dest = pc + c.g[pc].dest
if dest == comesFrom: return pc + 1 if dest == comesFrom: return pc + 1

View file

@ -10,6 +10,7 @@ end false
begin true begin true
if if
end true end true
7
''' '''
cmd: "nim c --gc:arc -d:danger $file" cmd: "nim c --gc:arc -d:danger $file"
disabled: "true" disabled: "true"
@ -53,3 +54,26 @@ proc orIsHard(cond: bool) =
orIsHard(false) orIsHard(false)
orIsHard(true) orIsHard(true)
type
Control = ref object
x: int
MouseEvent = ref object
control: Control
button: int
proc run(data: Control) =
var evt = MouseEvent(button: 1)
evt.control = data
if evt.button == 1:
discard
else:
return
echo data.x
var c = Control(x: 7)
run(c)