compiler: cleanup dfa.nim

This commit is contained in:
Andreas Rumpf
2018-10-12 19:56:51 +02:00
parent dcc3ac74f4
commit 2fecf4f36a

View File

@@ -7,18 +7,22 @@
# distribution, for details about the copyright.
#
## Data flow analysis for Nim. For now the task is to prove that every
## usage of a local variable 'v' is covered by an initialization to 'v'
## first.
## Data flow analysis for Nim.
## We transform the AST into a linear list of instructions first to
## make this easier to handle: There are only 2 different branching
## instructions: 'goto X' is an unconditional goto, 'fork X'
## is a conditional goto (either the next instruction or 'X' can be
## taken). Exhaustive case statements are translated
## so that the last branch is transformed into an 'else' branch.
## taken). Exhaustive case statements could be translated
## so that the last branch is transformed into an 'else' branch, but
## this is currently not done.
## ``return`` and ``break`` are all covered by 'goto'.
## The case to detect is ``use v`` that is not dominated by
## a ``def v``.
##
## Control flow through exception handling:
## Contrary to popular belief, exception handling doesn't cause
## many problems for this DFA representation, ``raise`` is a statement
## that ``goes to`` the outer ``finally`` or ``except`` if there is one,
## otherwise it is the same as ``return``.
##
## The data structures and algorithms used here are inspired by
## "A GraphFree Approach to DataFlow Analysis" by Markus Mohnen.
## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
@@ -27,15 +31,11 @@ import ast, astalgo, types, intsets, tables, msgs, options, lineinfos
type
InstrKind* = enum
goto, fork, def, use,
useWithinCall # this strange special case is used to get more
# move optimizations out of regular code
# XXX This is still overly pessimistic in
# call(let x = foo; bar(x))
goto, fork, def, use
Instr* = object
n*: PNode
case kind*: InstrKind
of def, use, useWithinCall: sym*: PSym
of def, use: sym*: PSym
of goto, fork: dest*: int
ControlFlowGraph* = seq[Instr]
@@ -71,7 +71,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
result.add $c[i].kind
result.add "\t"
case c[i].kind
of def, use, useWithinCall:
of def, use:
result.add c[i].sym.name.s
of goto, fork:
result.add "L"
@@ -199,6 +199,13 @@ proc genCase(c: var Con; n: PNode) =
# L2:
# elsePart
# Lend:
when false:
# XXX Exhaustiveness is not yet mapped to the control flow graph as
# it seems to offer no benefits for the 'last read of' question.
let isExhaustive = skipTypes(n.sons[0].typ,
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or
lastSon(n).kind == nkElse
var endings: seq[TPosition] = @[]
c.gen(n.sons[0])
for i in 1 ..< n.len:
@@ -250,10 +257,7 @@ proc genUse(c: var Con; n: PNode) =
nkAddr, nkHiddenAddr}:
n = n[0]
if n.kind == nkSym and n.sym.kind in InterestingSyms:
if c.inCall > 0:
c.code.add Instr(n: n, kind: useWithinCall, sym: n.sym)
else:
c.code.add Instr(n: n, kind: use, sym: n.sym)
c.code.add Instr(n: n, kind: use, sym: n.sym)
proc genDef(c: var Con; n: PNode) =
if n.kind == nkSym and n.sym.kind in InterestingSyms:
@@ -345,7 +349,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) =
d[i] = initIntSet()
c[i] = initIntSet()
case code[i].kind
of use, useWithinCall: u[i].incl(code[i].sym.id)
of use: u[i].incl(code[i].sym.id)
of def: d[i].incl(code[i].sym.id)
of fork, goto:
let d = i+code[i].dest
@@ -407,7 +411,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) =
#if someChange:
w.add pc + code[pc].dest
inc pc
of use, useWithinCall:
of use:
#if not d[prevPc].missingOrExcl():
# someChange = true
consuming = code[pc].sym.id
@@ -424,7 +428,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) =
# now check the condition we're interested in:
for i in 0..<code.len:
case code[i].kind
of use, useWithinCall:
of use:
let s = code[i].sym
if s.id notin d[i]:
localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s)