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
label: PSym joins: seq[Instr]
fixups: seq[TPosition] forks: seq[TPosition]
case isTryBlock: bool
of false:
label: PSym
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,12 +316,13 @@ when true:
Becomes: Becomes:
if cond: block:
body
if cond: if cond:
body body
if cond: if cond:
body body
if cond:
body
We still need to ensure 'break' resolves properly, so an AST to AST We still need to ensure 'break' resolves properly, so an AST to AST
translation is impossible. translation is impossible.
@ -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):
var endings: array[3, TPosition] let oldForksLen = c.blocks[^1].forks.len
for i in 0..2: var endings: array[3, TPosition]
withBlock(nil): for i in 0..2:
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)
gen(c, body) withBlock(s):
genImplicitReturn(c) gen(c, body)
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)