dfa.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2017 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Data flow analysis for Nim.
  10. ## We transform the AST into a linear list of instructions first to
  11. ## make this easier to handle: There are only 2 different branching
  12. ## instructions: 'goto X' is an unconditional goto, 'fork X'
  13. ## is a conditional goto (either the next instruction or 'X' can be
  14. ## taken). Exhaustive case statements could be translated
  15. ## so that the last branch is transformed into an 'else' branch, but
  16. ## this is currently not done.
  17. ## ``return`` and ``break`` are all covered by 'goto'.
  18. ##
  19. ## Control flow through exception handling:
  20. ## Contrary to popular belief, exception handling doesn't cause
  21. ## many problems for this DFA representation, ``raise`` is a statement
  22. ## that ``goes to`` the outer ``finally`` or ``except`` if there is one,
  23. ## otherwise it is the same as ``return``. Every call is treated as
  24. ## a call that can potentially ``raise``. However, without a surrounding
  25. ## ``try`` we don't emit these ``fork ReturnLabel`` instructions in order
  26. ## to speed up the dataflow analysis passes.
  27. ##
  28. ## The data structures and algorithms used here are inspired by
  29. ## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen.
  30. ## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
  31. import ast, astalgo, types, intsets, tables, msgs, options, lineinfos
  32. type
  33. InstrKind* = enum
  34. goto, fork, def, use
  35. Instr* = object
  36. n*: PNode
  37. case kind*: InstrKind
  38. of def, use: sym*: PSym
  39. of goto, fork: dest*: int
  40. ControlFlowGraph* = seq[Instr]
  41. TPosition = distinct int
  42. TBlock = object
  43. label: PSym
  44. fixups: seq[TPosition]
  45. ValueKind = enum
  46. undef, value, valueOrUndef
  47. Con = object
  48. code: ControlFlowGraph
  49. inCall, inTryStmt: int
  50. blocks: seq[TBlock]
  51. tryStmtFixups: seq[TPosition]
  52. owner: PSym
  53. proc debugInfo(info: TLineInfo): string =
  54. result = $info.line #info.toFilename & ":" & $info.line
  55. proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
  56. # for debugging purposes
  57. # first iteration: compute all necessary labels:
  58. var jumpTargets = initIntSet()
  59. let last = if last < 0: c.len-1 else: min(last, c.len-1)
  60. for i in start..last:
  61. if c[i].kind in {goto, fork}:
  62. jumpTargets.incl(i+c[i].dest)
  63. var i = start
  64. while i <= last:
  65. if i in jumpTargets: result.add("L" & $i & ":\n")
  66. result.add "\t"
  67. result.add $c[i].kind
  68. result.add "\t"
  69. case c[i].kind
  70. of def, use:
  71. result.add c[i].sym.name.s
  72. of goto, fork:
  73. result.add "L"
  74. result.add c[i].dest+i
  75. result.add("\t#")
  76. result.add(debugInfo(c[i].n.info))
  77. result.add("\n")
  78. inc i
  79. if i in jumpTargets: result.add("L" & $i & ": End\n")
  80. proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
  81. ## echos the ControlFlowGraph for debugging purposes.
  82. var buf = ""
  83. codeListing(c, buf, start, last)
  84. when declared(echo):
  85. echo buf
  86. proc forkI(c: var Con; n: PNode): TPosition =
  87. result = TPosition(c.code.len)
  88. c.code.add Instr(n: n, kind: fork, dest: 0)
  89. proc gotoI(c: var Con; n: PNode): TPosition =
  90. result = TPosition(c.code.len)
  91. c.code.add Instr(n: n, kind: goto, dest: 0)
  92. proc genLabel(c: Con): TPosition =
  93. result = TPosition(c.code.len)
  94. proc jmpBack(c: var Con, n: PNode, p = TPosition(0)) =
  95. let dist = p.int - c.code.len
  96. doAssert(-0x7fff < dist and dist < 0x7fff)
  97. c.code.add Instr(n: n, kind: goto, dest: dist)
  98. proc patch(c: var Con, p: TPosition) =
  99. # patch with current index
  100. let p = p.int
  101. let diff = c.code.len - p
  102. doAssert(-0x7fff < diff and diff < 0x7fff)
  103. c.code[p].dest = diff
  104. proc popBlock(c: var Con; oldLen: int) =
  105. for f in c.blocks[oldLen].fixups:
  106. c.patch(f)
  107. c.blocks.setLen(oldLen)
  108. template withBlock(labl: PSym; body: untyped) {.dirty.} =
  109. var oldLen {.gensym.} = c.blocks.len
  110. c.blocks.add TBlock(label: labl, fixups: @[])
  111. body
  112. popBlock(c, oldLen)
  113. proc isTrue(n: PNode): bool =
  114. n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
  115. n.kind == nkIntLit and n.intVal != 0
  116. proc gen(c: var Con; n: PNode) # {.noSideEffect.}
  117. proc genWhile(c: var Con; n: PNode) =
  118. # L1:
  119. # cond, tmp
  120. # fork tmp, L2
  121. # body
  122. # jmp L1
  123. # L2:
  124. let L1 = c.genLabel
  125. withBlock(nil):
  126. if isTrue(n.sons[0]):
  127. c.gen(n.sons[1])
  128. c.jmpBack(n, L1)
  129. else:
  130. c.gen(n.sons[0])
  131. let L2 = c.forkI(n)
  132. c.gen(n.sons[1])
  133. c.jmpBack(n, L1)
  134. c.patch(L2)
  135. proc genBlock(c: var Con; n: PNode) =
  136. withBlock(n.sons[0].sym):
  137. c.gen(n.sons[1])
  138. proc genBreak(c: var Con; n: PNode) =
  139. let L1 = c.gotoI(n)
  140. if n.sons[0].kind == nkSym:
  141. #echo cast[int](n.sons[0].sym)
  142. for i in countdown(c.blocks.len-1, 0):
  143. if c.blocks[i].label == n.sons[0].sym:
  144. c.blocks[i].fixups.add L1
  145. return
  146. #globalError(n.info, "VM problem: cannot find 'break' target")
  147. else:
  148. c.blocks[c.blocks.high].fixups.add L1
  149. proc genIf(c: var Con, n: PNode) =
  150. var endings: seq[TPosition] = @[]
  151. for i in countup(0, len(n) - 1):
  152. var it = n.sons[i]
  153. c.gen(it.sons[0])
  154. if it.len == 2:
  155. let elsePos = c.forkI(it.sons[1])
  156. c.gen(it.sons[1])
  157. if i < sonsLen(n)-1:
  158. endings.add(c.gotoI(it.sons[1]))
  159. c.patch(elsePos)
  160. for endPos in endings: c.patch(endPos)
  161. proc genAndOr(c: var Con; n: PNode) =
  162. # asgn dest, a
  163. # fork L1
  164. # asgn dest, b
  165. # L1:
  166. c.gen(n.sons[1])
  167. let L1 = c.forkI(n)
  168. c.gen(n.sons[2])
  169. c.patch(L1)
  170. proc genCase(c: var Con; n: PNode) =
  171. # if (!expr1) goto L1;
  172. # thenPart
  173. # goto LEnd
  174. # L1:
  175. # if (!expr2) goto L2;
  176. # thenPart2
  177. # goto LEnd
  178. # L2:
  179. # elsePart
  180. # Lend:
  181. when false:
  182. # XXX Exhaustiveness is not yet mapped to the control flow graph as
  183. # it seems to offer no benefits for the 'last read of' question.
  184. let isExhaustive = skipTypes(n.sons[0].typ,
  185. abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or
  186. lastSon(n).kind == nkElse
  187. var endings: seq[TPosition] = @[]
  188. c.gen(n.sons[0])
  189. for i in 1 ..< n.len:
  190. let it = n.sons[i]
  191. if it.len == 1:
  192. c.gen(it.sons[0])
  193. else:
  194. let elsePos = c.forkI(it.lastSon)
  195. c.gen(it.lastSon)
  196. if i < sonsLen(n)-1:
  197. endings.add(c.gotoI(it.lastSon))
  198. c.patch(elsePos)
  199. for endPos in endings: c.patch(endPos)
  200. proc genTry(c: var Con; n: PNode) =
  201. var endings: seq[TPosition] = @[]
  202. inc c.inTryStmt
  203. var newFixups: seq[TPosition]
  204. swap(newFixups, c.tryStmtFixups)
  205. let elsePos = c.forkI(n)
  206. c.gen(n.sons[0])
  207. dec c.inTryStmt
  208. for f in newFixups:
  209. c.patch(f)
  210. swap(newFixups, c.tryStmtFixups)
  211. c.patch(elsePos)
  212. for i in 1 ..< n.len:
  213. let it = n.sons[i]
  214. if it.kind != nkFinally:
  215. var blen = len(it)
  216. let endExcept = c.forkI(it)
  217. c.gen(it.lastSon)
  218. if i < sonsLen(n)-1:
  219. endings.add(c.gotoI(it))
  220. c.patch(endExcept)
  221. for endPos in endings: c.patch(endPos)
  222. let fin = lastSon(n)
  223. if fin.kind == nkFinally:
  224. c.gen(fin.sons[0])
  225. proc genRaise(c: var Con; n: PNode) =
  226. gen(c, n.sons[0])
  227. if c.inTryStmt > 0:
  228. c.tryStmtFixups.add c.gotoI(n)
  229. else:
  230. c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
  231. proc genImplicitReturn(c: var Con) =
  232. if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
  233. gen(c, c.owner.ast.sons[resultPos])
  234. proc genReturn(c: var Con; n: PNode) =
  235. if n.sons[0].kind != nkEmpty:
  236. gen(c, n.sons[0])
  237. else:
  238. genImplicitReturn(c)
  239. c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
  240. const
  241. InterestingSyms = {skVar, skResult, skLet, skParam}
  242. proc genUse(c: var Con; n: PNode) =
  243. var n = n
  244. while n.kind in {nkDotExpr, nkCheckedFieldExpr,
  245. nkBracketExpr, nkDerefExpr, nkHiddenDeref,
  246. nkAddr, nkHiddenAddr}:
  247. n = n[0]
  248. if n.kind == nkSym and n.sym.kind in InterestingSyms:
  249. c.code.add Instr(n: n, kind: use, sym: n.sym)
  250. proc genDef(c: var Con; n: PNode) =
  251. if n.kind == nkSym and n.sym.kind in InterestingSyms:
  252. c.code.add Instr(n: n, kind: def, sym: n.sym)
  253. proc genCall(c: var Con; n: PNode) =
  254. gen(c, n[0])
  255. var t = n[0].typ
  256. if t != nil: t = t.skipTypes(abstractInst)
  257. inc c.inCall
  258. for i in 1..<n.len:
  259. gen(c, n[i])
  260. if t != nil and i < t.len and t.sons[i].kind == tyVar:
  261. genDef(c, n[i])
  262. # every call can potentially raise:
  263. if c.inTryStmt > 0:
  264. c.tryStmtFixups.add c.forkI(n)
  265. dec c.inCall
  266. proc genMagic(c: var Con; n: PNode; m: TMagic) =
  267. case m
  268. of mAnd, mOr: c.genAndOr(n)
  269. of mNew, mNewFinalize:
  270. genDef(c, n[1])
  271. for i in 2..<n.len: gen(c, n[i])
  272. of mExit:
  273. genCall(c, n)
  274. c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
  275. else:
  276. genCall(c, n)
  277. proc genVarSection(c: var Con; n: PNode) =
  278. for a in n:
  279. if a.kind == nkCommentStmt: continue
  280. if a.kind == nkVarTuple:
  281. gen(c, a.lastSon)
  282. for i in 0 .. a.len-3: genDef(c, a[i])
  283. else:
  284. gen(c, a.lastSon)
  285. if a.lastSon.kind != nkEmpty:
  286. genDef(c, a.sons[0])
  287. proc gen(c: var Con; n: PNode) =
  288. case n.kind
  289. of nkSym: genUse(c, n)
  290. of nkCallKinds:
  291. if n.sons[0].kind == nkSym:
  292. let s = n.sons[0].sym
  293. if s.magic != mNone:
  294. genMagic(c, n, s.magic)
  295. else:
  296. genCall(c, n)
  297. else:
  298. genCall(c, n)
  299. of nkCharLit..nkNilLit: discard
  300. of nkAsgn, nkFastAsgn:
  301. gen(c, n[1])
  302. genDef(c, n[0])
  303. of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr,
  304. nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr:
  305. gen(c, n[0])
  306. of nkIfStmt, nkIfExpr: genIf(c, n)
  307. of nkWhenStmt:
  308. # This is "when nimvm" node. Chose the first branch.
  309. gen(c, n.sons[0].sons[1])
  310. of nkCaseStmt: genCase(c, n)
  311. of nkWhileStmt: genWhile(c, n)
  312. of nkBlockExpr, nkBlockStmt: genBlock(c, n)
  313. of nkReturnStmt: genReturn(c, n)
  314. of nkRaiseStmt: genRaise(c, n)
  315. of nkBreakStmt: genBreak(c, n)
  316. of nkTryStmt: genTry(c, n)
  317. of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
  318. nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr:
  319. for x in n: gen(c, x)
  320. of nkPragmaBlock: gen(c, n.lastSon)
  321. of nkDiscardStmt: gen(c, n.sons[0])
  322. of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkExprColonExpr, nkExprEqExpr,
  323. nkCast:
  324. gen(c, n.sons[1])
  325. of nkObjDownConv, nkStringToCString, nkCStringToString: gen(c, n.sons[0])
  326. of nkVarSection, nkLetSection: genVarSection(c, n)
  327. of nkDefer:
  328. doAssert false, "dfa construction pass requires the elimination of 'defer'"
  329. else: discard
  330. proc dfa(code: seq[Instr]; conf: ConfigRef) =
  331. var u = newSeq[IntSet](code.len) # usages
  332. var d = newSeq[IntSet](code.len) # defs
  333. var c = newSeq[IntSet](code.len) # consumed
  334. var backrefs = initTable[int, int]()
  335. for i in 0..<code.len:
  336. u[i] = initIntSet()
  337. d[i] = initIntSet()
  338. c[i] = initIntSet()
  339. case code[i].kind
  340. of use: u[i].incl(code[i].sym.id)
  341. of def: d[i].incl(code[i].sym.id)
  342. of fork, goto:
  343. let d = i+code[i].dest
  344. backrefs.add(d, i)
  345. var w = @[0]
  346. var maxIters = 50
  347. var someChange = true
  348. var takenGotos = initIntSet()
  349. var consuming = -1
  350. while w.len > 0 and maxIters > 0: # and someChange:
  351. dec maxIters
  352. var pc = w.pop() # w[^1]
  353. var prevPc = -1
  354. # this simulates a single linear control flow execution:
  355. while pc < code.len:
  356. if prevPc >= 0:
  357. someChange = false
  358. # merge step and test for changes (we compute the fixpoints here):
  359. # 'u' needs to be the union of prevPc, pc
  360. # 'd' needs to be the intersection of 'pc'
  361. for id in u[prevPc]:
  362. if not u[pc].containsOrIncl(id):
  363. someChange = true
  364. # in (a; b) if ``a`` sets ``v`` so does ``b``. The intersection
  365. # is only interesting on merge points:
  366. for id in d[prevPc]:
  367. if not d[pc].containsOrIncl(id):
  368. someChange = true
  369. # if this is a merge point, we take the intersection of the 'd' sets:
  370. if backrefs.hasKey(pc):
  371. var intersect = initIntSet()
  372. assign(intersect, d[pc])
  373. var first = true
  374. for prevPc in backrefs.allValues(pc):
  375. for def in d[pc]:
  376. if def notin d[prevPc]:
  377. excl(intersect, def)
  378. someChange = true
  379. when defined(debugDfa):
  380. echo "Excluding ", pc, " prev ", prevPc
  381. assign d[pc], intersect
  382. if consuming >= 0:
  383. if not c[pc].containsOrIncl(consuming):
  384. someChange = true
  385. consuming = -1
  386. # our interpretation ![I!]:
  387. prevPc = pc
  388. case code[pc].kind
  389. of goto:
  390. # we must leave endless loops eventually:
  391. if not takenGotos.containsOrIncl(pc) or someChange:
  392. pc = pc + code[pc].dest
  393. else:
  394. inc pc
  395. of fork:
  396. # we follow the next instruction but push the dest onto our "work" stack:
  397. #if someChange:
  398. w.add pc + code[pc].dest
  399. inc pc
  400. of use:
  401. #if not d[prevPc].missingOrExcl():
  402. # someChange = true
  403. consuming = code[pc].sym.id
  404. inc pc
  405. of def:
  406. if not d[pc].containsOrIncl(code[pc].sym.id):
  407. someChange = true
  408. inc pc
  409. when defined(useDfa) and defined(debugDfa):
  410. for i in 0..<code.len:
  411. echo "PC ", i, ": defs: ", d[i], "; uses ", u[i], "; consumes ", c[i]
  412. # now check the condition we're interested in:
  413. for i in 0..<code.len:
  414. case code[i].kind
  415. of use:
  416. let s = code[i].sym
  417. if s.id notin d[i]:
  418. localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s)
  419. if s.id in c[i]:
  420. localError(conf, code[i].n.info, "usage of an already consumed variable: " & s.name.s)
  421. else: discard
  422. proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) =
  423. var c = Con(code: @[], blocks: @[])
  424. gen(c, body)
  425. genImplicitReturn(c)
  426. when defined(useDfa) and defined(debugDfa): echoCfg(c.code)
  427. dfa(c.code, conf)
  428. proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
  429. ## constructs a control flow graph for ``body``.
  430. var c = Con(code: @[], blocks: @[], owner: s)
  431. gen(c, body)
  432. genImplicitReturn(c)
  433. shallowCopy(result, c.code)