dfa.nim 13 KB

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