dfa.nim 13 KB

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