dfa.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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 3 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), 'loop X' is the only jump that jumps back.
  15. ##
  16. ## Exhaustive case statements are translated
  17. ## so that the last branch is transformed into an 'else' branch.
  18. ## ``return`` and ``break`` are all covered by 'goto'.
  19. ##
  20. ## The data structures and algorithms used here are inspired by
  21. ## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen.
  22. ## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
  23. import ast, lineinfos, renderer, aliasanalysis
  24. import std/private/asciitables
  25. import std/intsets
  26. when defined(nimPreviewSlimSystem):
  27. import std/assertions
  28. type
  29. InstrKind* = enum
  30. goto, loop, fork, def, use
  31. Instr* = object
  32. case kind*: InstrKind
  33. of goto, fork, loop: dest*: int
  34. of def, use:
  35. n*: PNode # contains the def/use location.
  36. ControlFlowGraph* = seq[Instr]
  37. TPosition = distinct int
  38. TBlock = object
  39. case isTryBlock: bool
  40. of false:
  41. label: PSym
  42. breakFixups: seq[(TPosition, seq[PNode])] # Contains the gotos for the breaks along with their pending finales
  43. of true:
  44. finale: PNode
  45. raiseFixups: seq[TPosition] # Contains the gotos for the raises
  46. Con = object
  47. code: ControlFlowGraph
  48. inTryStmt, interestingInstructions: int
  49. blocks: seq[TBlock]
  50. owner: PSym
  51. root: PSym
  52. proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string =
  53. # for debugging purposes
  54. # first iteration: compute all necessary labels:
  55. result = ""
  56. var jumpTargets = initIntSet()
  57. let last = if last < 0: c.len-1 else: min(last, c.len-1)
  58. for i in start..last:
  59. if c[i].kind in {goto, fork, loop}:
  60. jumpTargets.incl(i+c[i].dest)
  61. var i = start
  62. while i <= last:
  63. if i in jumpTargets: result.add("L" & $i & ":\n")
  64. result.add "\t"
  65. result.add ($i & " " & $c[i].kind)
  66. result.add "\t"
  67. case c[i].kind
  68. of def, use:
  69. result.add renderTree(c[i].n)
  70. result.add("\t#")
  71. result.add($c[i].n.info.line)
  72. result.add("\n")
  73. of goto, fork, loop:
  74. result.add "L"
  75. result.addInt c[i].dest+i
  76. inc i
  77. if i in jumpTargets: result.add("L" & $i & ": End\n")
  78. proc echoCfg*(c: ControlFlowGraph; start = 0; last = -1) {.deprecated.} =
  79. ## echos the ControlFlowGraph for debugging purposes.
  80. echo codeListing(c, start, last).alignTable
  81. proc forkI(c: var Con): TPosition =
  82. result = TPosition(c.code.len)
  83. c.code.add Instr(kind: fork, dest: 0)
  84. proc gotoI(c: var Con): TPosition =
  85. result = TPosition(c.code.len)
  86. c.code.add Instr(kind: goto, dest: 0)
  87. proc genLabel(c: Con): TPosition = TPosition(c.code.len)
  88. template checkedDistance(dist): int =
  89. doAssert low(int) div 2 + 1 < dist and dist < high(int) div 2
  90. dist
  91. proc jmpBack(c: var Con, p = TPosition(0)) =
  92. c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len))
  93. proc patch(c: var Con, p: TPosition) =
  94. # patch with current index
  95. c.code[p.int].dest = checkedDistance(c.code.len - p.int)
  96. proc gen(c: var Con; n: PNode)
  97. proc popBlock(c: var Con; oldLen: int) =
  98. var exits: seq[TPosition] = @[]
  99. exits.add c.gotoI()
  100. for f in c.blocks[oldLen].breakFixups:
  101. c.patch(f[0])
  102. for finale in f[1]:
  103. c.gen(finale)
  104. exits.add c.gotoI()
  105. for e in exits:
  106. c.patch e
  107. c.blocks.setLen(oldLen)
  108. template withBlock(labl: PSym; body: untyped) =
  109. let oldLen = c.blocks.len
  110. c.blocks.add TBlock(isTryBlock: false, label: labl)
  111. body
  112. popBlock(c, oldLen)
  113. template forkT(body) =
  114. let lab1 = c.forkI()
  115. body
  116. c.patch(lab1)
  117. proc genWhile(c: var Con; n: PNode) =
  118. # lab1:
  119. # cond, tmp
  120. # fork tmp, lab2
  121. # body
  122. # jmp lab1
  123. # lab2:
  124. let lab1 = c.genLabel
  125. withBlock(nil):
  126. if isTrue(n[0]):
  127. c.gen(n[1])
  128. c.jmpBack(lab1)
  129. else:
  130. c.gen(n[0])
  131. forkT:
  132. c.gen(n[1])
  133. c.jmpBack(lab1)
  134. proc genIf(c: var Con, n: PNode) =
  135. #[
  136. if cond:
  137. A
  138. elif condB:
  139. B
  140. elif condC:
  141. C
  142. else:
  143. D
  144. cond
  145. fork lab1
  146. A
  147. goto Lend
  148. lab1:
  149. condB
  150. fork lab2
  151. B
  152. goto Lend2
  153. lab2:
  154. condC
  155. fork L3
  156. C
  157. goto Lend3
  158. L3:
  159. D
  160. ]#
  161. var endings: seq[TPosition] = @[]
  162. let oldInteresting = c.interestingInstructions
  163. let oldLen = c.code.len
  164. for i in 0..<n.len:
  165. let it = n[i]
  166. c.gen(it[0])
  167. if it.len == 2:
  168. forkT:
  169. c.gen(it.lastSon)
  170. endings.add c.gotoI()
  171. if oldInteresting == c.interestingInstructions:
  172. setLen c.code, oldLen
  173. else:
  174. for i in countdown(endings.high, 0):
  175. c.patch(endings[i])
  176. proc genAndOr(c: var Con; n: PNode) =
  177. # asgn dest, a
  178. # fork lab1
  179. # asgn dest, b
  180. # lab1:
  181. c.gen(n[1])
  182. forkT:
  183. c.gen(n[2])
  184. proc genCase(c: var Con; n: PNode) =
  185. # if (!expr1) goto lab1;
  186. # thenPart
  187. # goto LEnd
  188. # lab1:
  189. # if (!expr2) goto lab2;
  190. # thenPart2
  191. # goto LEnd
  192. # lab2:
  193. # elsePart
  194. # Lend:
  195. let isExhaustive = skipTypes(n[0].typ,
  196. abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
  197. var endings: seq[TPosition] = @[]
  198. c.gen(n[0])
  199. let oldInteresting = c.interestingInstructions
  200. let oldLen = c.code.len
  201. for i in 1..<n.len:
  202. let it = n[i]
  203. if it.len == 1 or (i == n.len-1 and isExhaustive):
  204. # treat the last branch as 'else' if this is an exhaustive case statement.
  205. c.gen(it.lastSon)
  206. else:
  207. forkT:
  208. c.gen(it.lastSon)
  209. endings.add c.gotoI()
  210. if oldInteresting == c.interestingInstructions:
  211. setLen c.code, oldLen
  212. else:
  213. for i in countdown(endings.high, 0):
  214. c.patch(endings[i])
  215. proc genBlock(c: var Con; n: PNode) =
  216. withBlock(n[0].sym):
  217. c.gen(n[1])
  218. proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
  219. let lab1 = c.gotoI()
  220. if c.blocks[i].isTryBlock:
  221. c.blocks[i].raiseFixups.add lab1
  222. else:
  223. var trailingFinales: seq[PNode] = @[]
  224. if c.inTryStmt > 0:
  225. # Ok, we are in a try, lets see which (if any) try's we break out from:
  226. for b in countdown(c.blocks.high, i):
  227. if c.blocks[b].isTryBlock:
  228. trailingFinales.add c.blocks[b].finale
  229. c.blocks[i].breakFixups.add (lab1, trailingFinales)
  230. proc genBreak(c: var Con; n: PNode) =
  231. inc c.interestingInstructions
  232. if n[0].kind == nkSym:
  233. for i in countdown(c.blocks.high, 0):
  234. if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym:
  235. genBreakOrRaiseAux(c, i, n)
  236. return
  237. #globalError(n.info, "VM problem: cannot find 'break' target")
  238. else:
  239. for i in countdown(c.blocks.high, 0):
  240. if not c.blocks[i].isTryBlock:
  241. genBreakOrRaiseAux(c, i, n)
  242. return
  243. proc genTry(c: var Con; n: PNode) =
  244. var endings: seq[TPosition] = @[]
  245. let oldLen = c.blocks.len
  246. c.blocks.add TBlock(isTryBlock: true, finale: if n[^1].kind == nkFinally: n[^1] else: newNode(nkEmpty))
  247. inc c.inTryStmt
  248. c.gen(n[0])
  249. dec c.inTryStmt
  250. for f in c.blocks[oldLen].raiseFixups:
  251. c.patch(f)
  252. c.blocks.setLen oldLen
  253. for i in 1..<n.len:
  254. let it = n[i]
  255. if it.kind != nkFinally:
  256. forkT:
  257. c.gen(it.lastSon)
  258. endings.add c.gotoI()
  259. for i in countdown(endings.high, 0):
  260. c.patch(endings[i])
  261. let fin = lastSon(n)
  262. if fin.kind == nkFinally:
  263. c.gen(fin[0])
  264. template genNoReturn(c: var Con) =
  265. # leave the graph
  266. c.code.add Instr(kind: goto, dest: high(int) - c.code.len)
  267. proc genRaise(c: var Con; n: PNode) =
  268. inc c.interestingInstructions
  269. gen(c, n[0])
  270. if c.inTryStmt > 0:
  271. for i in countdown(c.blocks.high, 0):
  272. if c.blocks[i].isTryBlock:
  273. genBreakOrRaiseAux(c, i, n)
  274. return
  275. assert false # Unreachable
  276. else:
  277. genNoReturn(c)
  278. proc genImplicitReturn(c: var Con) =
  279. if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
  280. gen(c, c.owner.ast[resultPos])
  281. proc genReturn(c: var Con; n: PNode) =
  282. inc c.interestingInstructions
  283. if n[0].kind != nkEmpty:
  284. gen(c, n[0])
  285. else:
  286. genImplicitReturn(c)
  287. genBreakOrRaiseAux(c, 0, n)
  288. const
  289. InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp}
  290. proc skipTrivials(c: var Con, n: PNode): PNode =
  291. result = n
  292. while true:
  293. case result.kind
  294. of PathKinds0 - {nkBracketExpr}:
  295. result = result[0]
  296. of nkBracketExpr:
  297. gen(c, result[1])
  298. result = result[0]
  299. of PathKinds1:
  300. result = result[1]
  301. else: break
  302. proc genUse(c: var Con; orig: PNode) =
  303. let n = c.skipTrivials(orig)
  304. if n.kind == nkSym:
  305. if n.sym.kind in InterestingSyms and n.sym == c.root:
  306. c.code.add Instr(kind: use, n: orig)
  307. inc c.interestingInstructions
  308. else:
  309. gen(c, n)
  310. proc genDef(c: var Con; orig: PNode) =
  311. let n = c.skipTrivials(orig)
  312. if n.kind == nkSym and n.sym.kind in InterestingSyms:
  313. if n.sym == c.root:
  314. c.code.add Instr(kind: def, n: orig)
  315. inc c.interestingInstructions
  316. proc genCall(c: var Con; n: PNode) =
  317. gen(c, n[0])
  318. var t = n[0].typ
  319. if t != nil: t = t.skipTypes(abstractInst)
  320. for i in 1..<n.len:
  321. gen(c, n[i])
  322. if t != nil and i < t.signatureLen and isOutParam(t[i]):
  323. # Pass by 'out' is a 'must def'. Good enough for a move optimizer.
  324. genDef(c, n[i])
  325. # every call can potentially raise:
  326. if c.inTryStmt > 0 and canRaiseConservative(n[0]):
  327. inc c.interestingInstructions
  328. # we generate the instruction sequence:
  329. # fork lab1
  330. # goto exceptionHandler (except or finally)
  331. # lab1:
  332. forkT:
  333. for i in countdown(c.blocks.high, 0):
  334. if c.blocks[i].isTryBlock:
  335. genBreakOrRaiseAux(c, i, n)
  336. break
  337. proc genMagic(c: var Con; n: PNode; m: TMagic) =
  338. case m
  339. of mAnd, mOr: c.genAndOr(n)
  340. of mNew, mNewFinalize:
  341. genDef(c, n[1])
  342. for i in 2..<n.len: gen(c, n[i])
  343. else:
  344. genCall(c, n)
  345. proc genVarSection(c: var Con; n: PNode) =
  346. for a in n:
  347. if a.kind == nkCommentStmt:
  348. discard
  349. elif a.kind == nkVarTuple:
  350. gen(c, a.lastSon)
  351. for i in 0..<a.len-2: genDef(c, a[i])
  352. else:
  353. gen(c, a.lastSon)
  354. if a.lastSon.kind != nkEmpty:
  355. genDef(c, a[0])
  356. proc gen(c: var Con; n: PNode) =
  357. case n.kind
  358. of nkSym: genUse(c, n)
  359. of nkCallKinds:
  360. if n[0].kind == nkSym:
  361. let s = n[0].sym
  362. if s.magic != mNone:
  363. genMagic(c, n, s.magic)
  364. else:
  365. genCall(c, n)
  366. if sfNoReturn in n[0].sym.flags:
  367. genNoReturn(c)
  368. else:
  369. genCall(c, n)
  370. of nkCharLit..nkNilLit: discard
  371. of nkAsgn, nkFastAsgn, nkSinkAsgn:
  372. gen(c, n[1])
  373. if n[0].kind in PathKinds0:
  374. let a = c.skipTrivials(n[0])
  375. if a.kind in nkCallKinds:
  376. gen(c, a)
  377. # watch out: 'obj[i].f2 = value' sets 'f2' but
  378. # "uses" 'i'. But we are only talking about builtin array indexing so
  379. # it doesn't matter and 'x = 34' is NOT a usage of 'x'.
  380. genDef(c, n[0])
  381. of PathKinds0 - {nkObjDownConv, nkObjUpConv}:
  382. genUse(c, n)
  383. of nkIfStmt, nkIfExpr: genIf(c, n)
  384. of nkWhenStmt:
  385. # This is "when nimvm" node. Chose the first branch.
  386. gen(c, n[0][1])
  387. of nkCaseStmt: genCase(c, n)
  388. of nkWhileStmt: genWhile(c, n)
  389. of nkBlockExpr, nkBlockStmt: genBlock(c, n)
  390. of nkReturnStmt: genReturn(c, n)
  391. of nkRaiseStmt: genRaise(c, n)
  392. of nkBreakStmt: genBreak(c, n)
  393. of nkTryStmt, nkHiddenTryStmt: genTry(c, n)
  394. of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
  395. nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr, nkYieldStmt:
  396. for x in n: gen(c, x)
  397. of nkPragmaBlock: gen(c, n.lastSon)
  398. of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
  399. gen(c, n[0])
  400. of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
  401. gen(c, n[1])
  402. of nkVarSection, nkLetSection: genVarSection(c, n)
  403. of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"
  404. else: discard
  405. when false:
  406. proc optimizeJumps(c: var ControlFlowGraph) =
  407. for i in 0..<c.len:
  408. case c[i].kind
  409. of goto, fork:
  410. var pc = i + c[i].dest
  411. if pc < c.len and c[pc].kind == goto:
  412. while pc < c.len and c[pc].kind == goto:
  413. let newPc = pc + c[pc].dest
  414. if newPc > pc:
  415. pc = newPc
  416. else:
  417. break
  418. c[i].dest = pc - i
  419. of loop, def, use: discard
  420. proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
  421. ## constructs a control flow graph for ``body``.
  422. var c = Con(code: @[], blocks: @[], owner: s, root: root)
  423. withBlock(s):
  424. gen(c, body)
  425. if root.kind == skResult:
  426. genImplicitReturn(c)
  427. when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
  428. result = c.code # will move
  429. else:
  430. shallowCopy(result, c.code)
  431. when false:
  432. optimizeJumps result