dfa.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. goto Lend3 # not eliminated to simplify the join generation
  161. Lend3:
  162. join F3
  163. Lend2:
  164. join F2
  165. Lend:
  166. join F1
  167. ]#
  168. var endings: seq[TPosition] = @[]
  169. let oldInteresting = c.interestingInstructions
  170. let oldLen = c.code.len
  171. for i in 0..<n.len:
  172. let it = n[i]
  173. c.gen(it[0])
  174. if it.len == 2:
  175. forkT:
  176. c.gen(it.lastSon)
  177. endings.add c.gotoI()
  178. if oldInteresting == c.interestingInstructions:
  179. setLen c.code, oldLen
  180. else:
  181. for i in countdown(endings.high, 0):
  182. c.patch(endings[i])
  183. proc genAndOr(c: var Con; n: PNode) =
  184. # asgn dest, a
  185. # fork lab1
  186. # asgn dest, b
  187. # lab1:
  188. # join F1
  189. c.gen(n[1])
  190. forkT:
  191. c.gen(n[2])
  192. proc genCase(c: var Con; n: PNode) =
  193. # if (!expr1) goto lab1;
  194. # thenPart
  195. # goto LEnd
  196. # lab1:
  197. # if (!expr2) goto lab2;
  198. # thenPart2
  199. # goto LEnd
  200. # lab2:
  201. # elsePart
  202. # Lend:
  203. let isExhaustive = skipTypes(n[0].typ,
  204. abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
  205. var endings: seq[TPosition] = @[]
  206. c.gen(n[0])
  207. let oldInteresting = c.interestingInstructions
  208. let oldLen = c.code.len
  209. for i in 1..<n.len:
  210. let it = n[i]
  211. if it.len == 1 or (i == n.len-1 and isExhaustive):
  212. # treat the last branch as 'else' if this is an exhaustive case statement.
  213. c.gen(it.lastSon)
  214. else:
  215. forkT:
  216. c.gen(it.lastSon)
  217. endings.add c.gotoI()
  218. if oldInteresting == c.interestingInstructions:
  219. setLen c.code, oldLen
  220. else:
  221. for i in countdown(endings.high, 0):
  222. c.patch(endings[i])
  223. proc genBlock(c: var Con; n: PNode) =
  224. withBlock(n[0].sym):
  225. c.gen(n[1])
  226. proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
  227. let lab1 = c.gotoI()
  228. if c.blocks[i].isTryBlock:
  229. c.blocks[i].raiseFixups.add lab1
  230. else:
  231. var trailingFinales: seq[PNode] = @[]
  232. if c.inTryStmt > 0:
  233. # Ok, we are in a try, lets see which (if any) try's we break out from:
  234. for b in countdown(c.blocks.high, i):
  235. if c.blocks[b].isTryBlock:
  236. trailingFinales.add c.blocks[b].finale
  237. c.blocks[i].breakFixups.add (lab1, trailingFinales)
  238. proc genBreak(c: var Con; n: PNode) =
  239. inc c.interestingInstructions
  240. if n[0].kind == nkSym:
  241. for i in countdown(c.blocks.high, 0):
  242. if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym:
  243. genBreakOrRaiseAux(c, i, n)
  244. return
  245. #globalError(n.info, "VM problem: cannot find 'break' target")
  246. else:
  247. for i in countdown(c.blocks.high, 0):
  248. if not c.blocks[i].isTryBlock:
  249. genBreakOrRaiseAux(c, i, n)
  250. return
  251. proc genTry(c: var Con; n: PNode) =
  252. var endings: seq[TPosition] = @[]
  253. let oldLen = c.blocks.len
  254. c.blocks.add TBlock(isTryBlock: true, finale: if n[^1].kind == nkFinally: n[^1] else: newNode(nkEmpty))
  255. inc c.inTryStmt
  256. c.gen(n[0])
  257. dec c.inTryStmt
  258. for f in c.blocks[oldLen].raiseFixups:
  259. c.patch(f)
  260. c.blocks.setLen oldLen
  261. for i in 1..<n.len:
  262. let it = n[i]
  263. if it.kind != nkFinally:
  264. forkT:
  265. c.gen(it.lastSon)
  266. endings.add c.gotoI()
  267. for i in countdown(endings.high, 0):
  268. c.patch(endings[i])
  269. let fin = lastSon(n)
  270. if fin.kind == nkFinally:
  271. c.gen(fin[0])
  272. template genNoReturn(c: var Con) =
  273. # leave the graph
  274. c.code.add Instr(kind: goto, dest: high(int) - c.code.len)
  275. proc genRaise(c: var Con; n: PNode) =
  276. inc c.interestingInstructions
  277. gen(c, n[0])
  278. if c.inTryStmt > 0:
  279. for i in countdown(c.blocks.high, 0):
  280. if c.blocks[i].isTryBlock:
  281. genBreakOrRaiseAux(c, i, n)
  282. return
  283. assert false #Unreachable
  284. else:
  285. genNoReturn(c)
  286. proc genImplicitReturn(c: var Con) =
  287. if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
  288. gen(c, c.owner.ast[resultPos])
  289. proc genReturn(c: var Con; n: PNode) =
  290. inc c.interestingInstructions
  291. if n[0].kind != nkEmpty:
  292. gen(c, n[0])
  293. else:
  294. genImplicitReturn(c)
  295. genBreakOrRaiseAux(c, 0, n)
  296. const
  297. InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp}
  298. proc skipTrivials(c: var Con, n: PNode): PNode =
  299. result = n
  300. while true:
  301. case result.kind
  302. of PathKinds0 - {nkBracketExpr}:
  303. result = result[0]
  304. of nkBracketExpr:
  305. gen(c, result[1])
  306. result = result[0]
  307. of PathKinds1:
  308. result = result[1]
  309. else: break
  310. proc genUse(c: var Con; orig: PNode) =
  311. let n = c.skipTrivials(orig)
  312. if n.kind == nkSym:
  313. if n.sym.kind in InterestingSyms and n.sym == c.root:
  314. c.code.add Instr(kind: use, n: orig)
  315. inc c.interestingInstructions
  316. else:
  317. gen(c, n)
  318. proc genDef(c: var Con; orig: PNode) =
  319. let n = c.skipTrivials(orig)
  320. if n.kind == nkSym and n.sym.kind in InterestingSyms:
  321. if n.sym == c.root:
  322. c.code.add Instr(kind: def, n: orig)
  323. inc c.interestingInstructions
  324. proc genCall(c: var Con; n: PNode) =
  325. gen(c, n[0])
  326. var t = n[0].typ
  327. if t != nil: t = t.skipTypes(abstractInst)
  328. for i in 1..<n.len:
  329. gen(c, n[i])
  330. if t != nil and i < t.signatureLen and isOutParam(t[i]):
  331. # Pass by 'out' is a 'must def'. Good enough for a move optimizer.
  332. genDef(c, n[i])
  333. # every call can potentially raise:
  334. if c.inTryStmt > 0 and canRaiseConservative(n[0]):
  335. inc c.interestingInstructions
  336. # we generate the instruction sequence:
  337. # fork lab1
  338. # goto exceptionHandler (except or finally)
  339. # lab1:
  340. # join F1
  341. forkT:
  342. for i in countdown(c.blocks.high, 0):
  343. if c.blocks[i].isTryBlock:
  344. genBreakOrRaiseAux(c, i, n)
  345. break
  346. proc genMagic(c: var Con; n: PNode; m: TMagic) =
  347. case m
  348. of mAnd, mOr: c.genAndOr(n)
  349. of mNew, mNewFinalize:
  350. genDef(c, n[1])
  351. for i in 2..<n.len: gen(c, n[i])
  352. else:
  353. genCall(c, n)
  354. proc genVarSection(c: var Con; n: PNode) =
  355. for a in n:
  356. if a.kind == nkCommentStmt:
  357. discard
  358. elif a.kind == nkVarTuple:
  359. gen(c, a.lastSon)
  360. for i in 0..<a.len-2: genDef(c, a[i])
  361. else:
  362. gen(c, a.lastSon)
  363. if a.lastSon.kind != nkEmpty:
  364. genDef(c, a[0])
  365. proc gen(c: var Con; n: PNode) =
  366. case n.kind
  367. of nkSym: genUse(c, n)
  368. of nkCallKinds:
  369. if n[0].kind == nkSym:
  370. let s = n[0].sym
  371. if s.magic != mNone:
  372. genMagic(c, n, s.magic)
  373. else:
  374. genCall(c, n)
  375. if sfNoReturn in n[0].sym.flags:
  376. genNoReturn(c)
  377. else:
  378. genCall(c, n)
  379. of nkCharLit..nkNilLit: discard
  380. of nkAsgn, nkFastAsgn, nkSinkAsgn:
  381. gen(c, n[1])
  382. if n[0].kind in PathKinds0:
  383. let a = c.skipTrivials(n[0])
  384. if a.kind in nkCallKinds:
  385. gen(c, a)
  386. # watch out: 'obj[i].f2 = value' sets 'f2' but
  387. # "uses" 'i'. But we are only talking about builtin array indexing so
  388. # it doesn't matter and 'x = 34' is NOT a usage of 'x'.
  389. genDef(c, n[0])
  390. of PathKinds0 - {nkObjDownConv, nkObjUpConv}:
  391. genUse(c, n)
  392. of nkIfStmt, nkIfExpr: genIf(c, n)
  393. of nkWhenStmt:
  394. # This is "when nimvm" node. Chose the first branch.
  395. gen(c, n[0][1])
  396. of nkCaseStmt: genCase(c, n)
  397. of nkWhileStmt: genWhile(c, n)
  398. of nkBlockExpr, nkBlockStmt: genBlock(c, n)
  399. of nkReturnStmt: genReturn(c, n)
  400. of nkRaiseStmt: genRaise(c, n)
  401. of nkBreakStmt: genBreak(c, n)
  402. of nkTryStmt, nkHiddenTryStmt: genTry(c, n)
  403. of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
  404. nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr, nkYieldStmt:
  405. for x in n: gen(c, x)
  406. of nkPragmaBlock: gen(c, n.lastSon)
  407. of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
  408. gen(c, n[0])
  409. of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
  410. gen(c, n[1])
  411. of nkVarSection, nkLetSection: genVarSection(c, n)
  412. of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"
  413. else: discard
  414. when false:
  415. proc optimizeJumps(c: var ControlFlowGraph) =
  416. for i in 0..<c.len:
  417. case c[i].kind
  418. of goto, fork:
  419. var pc = i + c[i].dest
  420. if pc < c.len and c[pc].kind == goto:
  421. while pc < c.len and c[pc].kind == goto:
  422. let newPc = pc + c[pc].dest
  423. if newPc > pc:
  424. pc = newPc
  425. else:
  426. break
  427. c[i].dest = pc - i
  428. of loop, def, use: discard
  429. proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph =
  430. ## constructs a control flow graph for ``body``.
  431. var c = Con(code: @[], blocks: @[], owner: s, root: root)
  432. withBlock(s):
  433. gen(c, body)
  434. if root.kind == skResult:
  435. genImplicitReturn(c)
  436. when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
  437. result = c.code # will move
  438. else:
  439. shallowCopy(result, c.code)
  440. when false:
  441. optimizeJumps result