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, 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. var jumpTargets = initIntSet()
  55. let last = if last < 0: c.len-1 else: min(last, c.len-1)
  56. for i in start..last:
  57. if c[i].kind in {goto, fork, loop}:
  58. jumpTargets.incl(i+c[i].dest)
  59. var i = start
  60. while i <= last:
  61. if i in jumpTargets: result.add("L" & $i & ":\n")
  62. result.add "\t"
  63. result.add ($i & " " & $c[i].kind)
  64. result.add "\t"
  65. case c[i].kind
  66. of def, use:
  67. result.add renderTree(c[i].n)
  68. result.add("\t#")
  69. result.add($c[i].n.info.line)
  70. result.add("\n")
  71. of goto, fork, loop:
  72. result.add "L"
  73. result.addInt c[i].dest+i
  74. inc i
  75. if i in jumpTargets: result.add("L" & $i & ": End\n")
  76. proc echoCfg*(c: ControlFlowGraph; start = 0; last = -1) {.deprecated.} =
  77. ## echos the ControlFlowGraph for debugging purposes.
  78. echo codeListing(c, start, last).alignTable
  79. proc forkI(c: var Con): TPosition =
  80. result = TPosition(c.code.len)
  81. c.code.add Instr(kind: fork, dest: 0)
  82. proc gotoI(c: var Con): TPosition =
  83. result = TPosition(c.code.len)
  84. c.code.add Instr(kind: goto, dest: 0)
  85. proc genLabel(c: Con): TPosition = TPosition(c.code.len)
  86. template checkedDistance(dist): int =
  87. doAssert low(int) div 2 + 1 < dist and dist < high(int) div 2
  88. dist
  89. proc jmpBack(c: var Con, p = TPosition(0)) =
  90. c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len))
  91. proc patch(c: var Con, p: TPosition) =
  92. # patch with current index
  93. c.code[p.int].dest = checkedDistance(c.code.len - p.int)
  94. proc gen(c: var Con; n: PNode)
  95. proc popBlock(c: var Con; oldLen: int) =
  96. var exits: seq[TPosition]
  97. exits.add c.gotoI()
  98. for f in c.blocks[oldLen].breakFixups:
  99. c.patch(f[0])
  100. for finale in f[1]:
  101. c.gen(finale)
  102. exits.add c.gotoI()
  103. for e in exits:
  104. c.patch e
  105. c.blocks.setLen(oldLen)
  106. template withBlock(labl: PSym; body: untyped) =
  107. let oldLen = c.blocks.len
  108. c.blocks.add TBlock(isTryBlock: false, label: labl)
  109. body
  110. popBlock(c, oldLen)
  111. proc isTrue(n: PNode): bool =
  112. n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
  113. n.kind == nkIntLit and n.intVal != 0
  114. template forkT(body) =
  115. let lab1 = c.forkI()
  116. body
  117. c.patch(lab1)
  118. proc genWhile(c: var Con; n: PNode) =
  119. # lab1:
  120. # cond, tmp
  121. # fork tmp, lab2
  122. # body
  123. # jmp lab1
  124. # lab2:
  125. let lab1 = c.genLabel
  126. withBlock(nil):
  127. if isTrue(n[0]):
  128. c.gen(n[1])
  129. c.jmpBack(lab1)
  130. else:
  131. c.gen(n[0])
  132. forkT:
  133. c.gen(n[1])
  134. c.jmpBack(lab1)
  135. proc genIf(c: var Con, n: PNode) =
  136. #[
  137. if cond:
  138. A
  139. elif condB:
  140. B
  141. elif condC:
  142. C
  143. else:
  144. D
  145. cond
  146. fork lab1
  147. A
  148. goto Lend
  149. lab1:
  150. condB
  151. fork lab2
  152. B
  153. goto Lend2
  154. lab2:
  155. condC
  156. fork L3
  157. C
  158. goto Lend3
  159. L3:
  160. D
  161. goto Lend3 # not eliminated to simplify the join generation
  162. Lend3:
  163. join F3
  164. Lend2:
  165. join F2
  166. Lend:
  167. join F1
  168. ]#
  169. var endings: seq[TPosition] = @[]
  170. let oldInteresting = c.interestingInstructions
  171. let oldLen = c.code.len
  172. for i in 0..<n.len:
  173. let it = n[i]
  174. c.gen(it[0])
  175. if it.len == 2:
  176. forkT:
  177. c.gen(it.lastSon)
  178. endings.add c.gotoI()
  179. if oldInteresting == c.interestingInstructions:
  180. setLen c.code, oldLen
  181. else:
  182. for i in countdown(endings.high, 0):
  183. c.patch(endings[i])
  184. proc genAndOr(c: var Con; n: PNode) =
  185. # asgn dest, a
  186. # fork lab1
  187. # asgn dest, b
  188. # lab1:
  189. # join F1
  190. c.gen(n[1])
  191. forkT:
  192. c.gen(n[2])
  193. proc genCase(c: var Con; n: PNode) =
  194. # if (!expr1) goto lab1;
  195. # thenPart
  196. # goto LEnd
  197. # lab1:
  198. # if (!expr2) goto lab2;
  199. # thenPart2
  200. # goto LEnd
  201. # lab2:
  202. # elsePart
  203. # Lend:
  204. let isExhaustive = skipTypes(n[0].typ,
  205. abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
  206. var endings: seq[TPosition] = @[]
  207. c.gen(n[0])
  208. let oldInteresting = c.interestingInstructions
  209. let oldLen = c.code.len
  210. for i in 1..<n.len:
  211. let it = n[i]
  212. if it.len == 1 or (i == n.len-1 and isExhaustive):
  213. # treat the last branch as 'else' if this is an exhaustive case statement.
  214. c.gen(it.lastSon)
  215. else:
  216. forkT:
  217. c.gen(it.lastSon)
  218. endings.add c.gotoI()
  219. if oldInteresting == c.interestingInstructions:
  220. setLen c.code, oldLen
  221. else:
  222. for i in countdown(endings.high, 0):
  223. c.patch(endings[i])
  224. proc genBlock(c: var Con; n: PNode) =
  225. withBlock(n[0].sym):
  226. c.gen(n[1])
  227. proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
  228. let lab1 = c.gotoI()
  229. if c.blocks[i].isTryBlock:
  230. c.blocks[i].raiseFixups.add lab1
  231. else:
  232. var trailingFinales: seq[PNode]
  233. if c.inTryStmt > 0: #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.len 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 false: # c.inTryStmt > 0 and canRaiseConservative(n[0]):
  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:
  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: doAssert false, "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):
  436. result = c.code # will move
  437. else:
  438. shallowCopy(result, c.code)
  439. when false:
  440. optimizeJumps result