destroyer.nim 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. ## Injects destructor calls into Nim code as well as
  10. ## an optimizer that optimizes copies to moves. This is implemented as an
  11. ## AST to AST transformation so that every backend benefits from it.
  12. ## Rules for destructor injections:
  13. ##
  14. ## foo(bar(X(), Y()))
  15. ## X and Y get destroyed after bar completes:
  16. ##
  17. ## foo( (tmpX = X(); tmpY = Y(); tmpBar = bar(tmpX, tmpY);
  18. ## destroy(tmpX); destroy(tmpY);
  19. ## tmpBar))
  20. ## destroy(tmpBar)
  21. ##
  22. ## var x = f()
  23. ## body
  24. ##
  25. ## is the same as:
  26. ##
  27. ## var x;
  28. ## try:
  29. ## move(x, f())
  30. ## finally:
  31. ## destroy(x)
  32. ##
  33. ## But this really just an optimization that tries to avoid to
  34. ## introduce too many temporaries, the 'destroy' is caused by
  35. ## the 'f()' call. No! That is not true for 'result = f()'!
  36. ##
  37. ## x = y where y is read only once
  38. ## is the same as: move(x, y)
  39. ##
  40. ## Actually the more general rule is: The *last* read of ``y``
  41. ## can become a move if ``y`` is the result of a construction.
  42. ##
  43. ## We also need to keep in mind here that the number of reads is
  44. ## control flow dependent:
  45. ## let x = foo()
  46. ## while true:
  47. ## y = x # only one read, but the 2nd iteration will fail!
  48. ## This also affects recursions! Only usages that do not cross
  49. ## a loop boundary (scope) and are not used in function calls
  50. ## are safe.
  51. ##
  52. ##
  53. ## x = f() is the same as: move(x, f())
  54. ##
  55. ## x = y
  56. ## is the same as: copy(x, y)
  57. ##
  58. ## Reassignment works under this scheme:
  59. ## var x = f()
  60. ## x = y
  61. ##
  62. ## is the same as:
  63. ##
  64. ## var x;
  65. ## try:
  66. ## move(x, f())
  67. ## copy(x, y)
  68. ## finally:
  69. ## destroy(x)
  70. ##
  71. ## result = f() must not destroy 'result'!
  72. ##
  73. ## The produced temporaries clutter up the code and might lead to
  74. ## inefficiencies. A better strategy is to collect all the temporaries
  75. ## in a single object that we put into a single try-finally that
  76. ## surrounds the proc body. This means the code stays quite efficient
  77. ## when compiled to C. In fact, we do the same for variables, so
  78. ## destructors are called when the proc returns, not at scope exit!
  79. ## This makes certains idioms easier to support. (Taking the slice
  80. ## of a temporary object.)
  81. ##
  82. ## foo(bar(X(), Y()))
  83. ## X and Y get destroyed after bar completes:
  84. ##
  85. ## var tmp: object
  86. ## foo( (move tmp.x, X(); move tmp.y, Y(); tmp.bar = bar(tmpX, tmpY);
  87. ## tmp.bar))
  88. ## destroy(tmp.bar)
  89. ## destroy(tmp.x); destroy(tmp.y)
  90. import
  91. intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
  92. strutils, options, dfa, lowerings, rodread
  93. const
  94. InterestingSyms = {skVar, skResult, skLet}
  95. type
  96. Con = object
  97. owner: PSym
  98. g: ControlFlowGraph
  99. jumpTargets: IntSet
  100. tmpObj: PType
  101. tmp: PSym
  102. destroys, topLevelVars: PNode
  103. proc isHarmlessVar*(s: PSym; c: Con): bool =
  104. # 's' is harmless if it used only once and its
  105. # definition/usage are not split by any labels:
  106. #
  107. # let s = foo()
  108. # while true:
  109. # a[i] = s
  110. #
  111. # produces:
  112. #
  113. # def s
  114. # L1:
  115. # use s
  116. # goto L1
  117. #
  118. # let s = foo()
  119. # if cond:
  120. # a[i] = s
  121. # else:
  122. # a[j] = s
  123. #
  124. # produces:
  125. #
  126. # def s
  127. # fork L2
  128. # use s
  129. # goto L3
  130. # L2:
  131. # use s
  132. # L3
  133. #
  134. # So this analysis is for now overly conservative, but correct.
  135. var defsite = -1
  136. var usages = 0
  137. for i in 0..<c.g.len:
  138. case c.g[i].kind
  139. of def:
  140. if c.g[i].sym == s:
  141. if defsite < 0: defsite = i
  142. else: return false
  143. of use:
  144. if c.g[i].sym == s:
  145. if defsite < 0: return false
  146. for j in defsite .. i:
  147. # not within the same basic block?
  148. if j in c.jumpTargets: return false
  149. # if we want to die after the first 'use':
  150. if usages > 1: return false
  151. inc usages
  152. of useWithinCall:
  153. if c.g[i].sym == s: return false
  154. of goto, fork:
  155. discard "we do not perform an abstract interpretation yet"
  156. template interestingSym(s: PSym): bool =
  157. s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
  158. proc patchHead(n: PNode) =
  159. if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
  160. let s = n[0].sym
  161. if sfFromGeneric in s.flags and s.name.s[0] == '=' and
  162. s.name.s in ["=sink", "=", "=destroy"]:
  163. excl(s.flags, sfFromGeneric)
  164. patchHead(s.getBody)
  165. let t = n[1].typ.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred})
  166. template patch(op, field) =
  167. if s.name.s == op and field != nil and field != s:
  168. n.sons[0].sym = field
  169. patch "=sink", t.sink
  170. patch "=", t.assignment
  171. patch "=destroy", t.destructor
  172. for x in n:
  173. patchHead(x)
  174. proc genSink(t: PType; dest: PNode): PNode =
  175. let t = t.skipTypes({tyGenericInst, tyAlias})
  176. let op = if t.sink != nil: t.sink else: t.assignment
  177. assert op != nil
  178. patchHead op.ast[bodyPos]
  179. result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest))
  180. proc genCopy(t: PType; dest: PNode): PNode =
  181. let t = t.skipTypes({tyGenericInst, tyAlias})
  182. assert t.assignment != nil
  183. patchHead t.assignment.ast[bodyPos]
  184. result = newTree(nkCall, newSymNode(t.assignment), newTree(nkHiddenAddr, dest))
  185. proc genDestroy(t: PType; dest: PNode): PNode =
  186. let t = t.skipTypes({tyGenericInst, tyAlias})
  187. assert t.destructor != nil
  188. patchHead t.destructor.ast[bodyPos]
  189. result = newTree(nkCall, newSymNode(t.destructor), newTree(nkHiddenAddr, dest))
  190. proc addTopVar(c: var Con; v: PNode) =
  191. c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, emptyNode)
  192. proc p(n: PNode; c: var Con): PNode
  193. template recurse(n, dest) =
  194. for i in 0..<n.len:
  195. dest.add p(n[i], c)
  196. proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
  197. if ri.kind in nkCallKinds:
  198. result = genSink(ri.typ, dest)
  199. # watch out and no not transform 'ri' twice if it's a call:
  200. let ri2 = copyNode(ri)
  201. recurse(ri, ri2)
  202. result.add ri2
  203. elif ri.kind == nkSym and isHarmlessVar(ri.sym, c):
  204. result = genSink(ri.typ, dest)
  205. result.add p(ri, c)
  206. else:
  207. result = genCopy(ri.typ, dest)
  208. result.add p(ri, c)
  209. proc p(n: PNode; c: var Con): PNode =
  210. case n.kind
  211. of nkVarSection, nkLetSection:
  212. discard "transform; var x = y to var x; x op y where op is a move or copy"
  213. result = newNodeI(nkStmtList, n.info)
  214. for i in 0..<n.len:
  215. let it = n[i]
  216. let L = it.len-1
  217. let ri = it[L]
  218. if it.kind == nkVarTuple and hasDestructor(ri.typ):
  219. let x = lowerTupleUnpacking(it, c.owner)
  220. result.add p(x, c)
  221. elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
  222. for j in 0..L-2:
  223. let v = it[j]
  224. doAssert v.kind == nkSym
  225. # move the variable declaration to the top of the frame:
  226. c.addTopVar v
  227. # make sure it's destroyed at the end of the proc:
  228. c.destroys.add genDestroy(v.typ, v)
  229. if ri.kind != nkEmpty:
  230. let r = moveOrCopy(v, ri, c)
  231. result.add r
  232. else:
  233. # keep it, but transform 'ri':
  234. var varSection = copyNode(n)
  235. var itCopy = copyNode(it)
  236. for j in 0..L-1:
  237. itCopy.add it[j]
  238. itCopy.add p(ri, c)
  239. varSection.add itCopy
  240. result.add varSection
  241. of nkCallKinds:
  242. if n.typ != nil and hasDestructor(n.typ):
  243. discard "produce temp creation"
  244. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  245. let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info)
  246. f.typ = n.typ
  247. rawAddField c.tmpObj, f
  248. var m = genSink(n.typ, rawDirectAccess(c.tmp, f))
  249. var call = copyNode(n)
  250. recurse(n, call)
  251. m.add call
  252. result.add m
  253. result.add rawDirectAccess(c.tmp, f)
  254. c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f))
  255. else:
  256. result = copyNode(n)
  257. recurse(n, result)
  258. of nkAsgn, nkFastAsgn:
  259. if hasDestructor(n[0].typ):
  260. result = moveOrCopy(n[0], n[1], c)
  261. else:
  262. result = copyNode(n)
  263. recurse(n, result)
  264. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  265. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  266. result = n
  267. else:
  268. result = copyNode(n)
  269. recurse(n, result)
  270. proc injectDestructorCalls*(owner: PSym; n: PNode): PNode =
  271. var c: Con
  272. c.owner = owner
  273. c.tmp = newSym(skTemp, getIdent":d", owner, n.info)
  274. c.tmpObj = createObj(owner, n.info)
  275. c.tmp.typ = c.tmpObj
  276. c.destroys = newNodeI(nkStmtList, n.info)
  277. c.topLevelVars = newNodeI(nkVarSection, n.info)
  278. let cfg = constructCfg(owner, n)
  279. shallowCopy(c.g, cfg)
  280. c.jumpTargets = initIntSet()
  281. for i in 0..<c.g.len:
  282. if c.g[i].kind in {goto, fork}:
  283. c.jumpTargets.incl(i+c.g[i].dest)
  284. let body = p(n, c)
  285. if c.tmp.typ.n.len > 0:
  286. c.addTopVar(newSymNode c.tmp)
  287. result = newNodeI(nkStmtList, n.info)
  288. if c.topLevelVars.len > 0:
  289. result.add c.topLevelVars
  290. if c.destroys.len > 0:
  291. result.add newTryFinally(body, c.destroys)
  292. else:
  293. result.add body
  294. when defined(nimDebugDestroys):
  295. if owner.name.s == "createSeq":
  296. echo "------------------------------------"
  297. echo owner.name.s, " transformed to: "
  298. echo result