destroyer.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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. ##
  91. ##[
  92. From https://github.com/nim-lang/Nim/wiki/Destructors
  93. Rule Pattern Transformed into
  94. ---- ------- ----------------
  95. 1.1 var x: T; stmts var x: T; try stmts
  96. finally: `=destroy`(x)
  97. 1.2 var x: sink T; stmts var x: sink T; stmts; ensureEmpty(x)
  98. 2 x = f() `=sink`(x, f())
  99. 3 x = lastReadOf z `=sink`(x, z); wasMoved(z)
  100. 4.1 y = sinkParam `=sink`(y, sinkParam)
  101. 4.2 x = y `=`(x, y) # a copy
  102. 5.1 f_sink(g()) f_sink(g())
  103. 5.2 f_sink(y) f_sink(copy y); # copy unless we can see it's the last read
  104. 5.3 f_sink(move y) f_sink(y); wasMoved(y) # explicit moves empties 'y'
  105. 5.4 f_noSink(g()) var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
  106. Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently
  107. not allowed as a local variable.
  108. ``move`` builtin needs to be implemented.
  109. ]##
  110. import
  111. intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
  112. strutils, options, dfa, lowerings, tables, modulegraphs,
  113. lineinfos
  114. const
  115. InterestingSyms = {skVar, skResult, skLet}
  116. type
  117. Con = object
  118. owner: PSym
  119. g: ControlFlowGraph
  120. jumpTargets: IntSet
  121. tmpObj: PType
  122. tmp: PSym
  123. destroys, topLevelVars: PNode
  124. toDropBit: Table[int, PSym]
  125. graph: ModuleGraph
  126. emptyNode: PNode
  127. proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
  128. # XXX why are temps fields in an object here?
  129. let f = newSym(skField, getIdent(c.graph.cache, ":d" & $c.tmpObj.n.len), c.owner, info)
  130. f.typ = typ
  131. rawAddField c.tmpObj, f
  132. result = rawDirectAccess(c.tmp, f)
  133. proc isHarmlessVar*(s: PSym; c: Con): bool =
  134. # 's' is harmless if it used only once and its
  135. # definition/usage are not split by any labels:
  136. #
  137. # let s = foo()
  138. # while true:
  139. # a[i] = s
  140. #
  141. # produces:
  142. #
  143. # def s
  144. # L1:
  145. # use s
  146. # goto L1
  147. #
  148. # let s = foo()
  149. # if cond:
  150. # a[i] = s
  151. # else:
  152. # a[j] = s
  153. #
  154. # produces:
  155. #
  156. # def s
  157. # fork L2
  158. # use s
  159. # goto L3
  160. # L2:
  161. # use s
  162. # L3
  163. #
  164. # So this analysis is for now overly conservative, but correct.
  165. var defsite = -1
  166. var usages = 0
  167. for i in 0..<c.g.len:
  168. case c.g[i].kind
  169. of def:
  170. if c.g[i].sym == s:
  171. if defsite < 0: defsite = i
  172. else: return false
  173. of use:
  174. if c.g[i].sym == s:
  175. if defsite < 0: return false
  176. for j in defsite .. i:
  177. # not within the same basic block?
  178. if j in c.jumpTargets: return false
  179. # if we want to die after the first 'use':
  180. if usages > 1: return false
  181. inc usages
  182. of useWithinCall:
  183. if c.g[i].sym == s: return false
  184. of goto, fork:
  185. discard "we do not perform an abstract interpretation yet"
  186. template interestingSym(s: PSym): bool =
  187. s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
  188. proc patchHead(n: PNode) =
  189. if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
  190. let s = n[0].sym
  191. if s.name.s[0] == '=' and s.name.s in ["=sink", "=", "=destroy"]:
  192. if sfFromGeneric in s.flags:
  193. excl(s.flags, sfFromGeneric)
  194. patchHead(s.getBody)
  195. if n[1].typ.isNil:
  196. # XXX toptree crashes without this workaround. Figure out why.
  197. return
  198. let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
  199. template patch(op, field) =
  200. if s.name.s == op and field != nil and field != s:
  201. n.sons[0].sym = field
  202. patch "=sink", t.sink
  203. patch "=", t.assignment
  204. patch "=destroy", t.destructor
  205. for x in n:
  206. patchHead(x)
  207. proc patchHead(s: PSym) =
  208. if sfFromGeneric in s.flags:
  209. patchHead(s.ast[bodyPos])
  210. template genOp(opr, opname) =
  211. let op = opr
  212. if op == nil:
  213. globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t))
  214. elif op.ast[genericParamsPos].kind != nkEmpty:
  215. globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic")
  216. patchHead op
  217. result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest))
  218. proc genSink(c: Con; t: PType; dest: PNode): PNode =
  219. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  220. genOp(if t.sink != nil: t.sink else: t.assignment, "=sink")
  221. proc genCopy(c: Con; t: PType; dest: PNode): PNode =
  222. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  223. genOp(t.assignment, "=")
  224. proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
  225. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  226. genOp(t.destructor, "=destroy")
  227. proc addTopVar(c: var Con; v: PNode) =
  228. c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
  229. proc dropBit(c: var Con; s: PSym): PSym =
  230. result = c.toDropBit.getOrDefault(s.id)
  231. assert result != nil
  232. proc registerDropBit(c: var Con; s: PSym) =
  233. let result = newSym(skTemp, getIdent(c.graph.cache, s.name.s & "_AliveBit"), c.owner, s.info)
  234. result.typ = getSysType(c.graph, s.info, tyBool)
  235. let trueVal = newIntTypeNode(nkIntLit, 1, result.typ)
  236. c.topLevelVars.add newTree(nkIdentDefs, newSymNode result, c.emptyNode, trueVal)
  237. c.toDropBit[s.id] = result
  238. # generate:
  239. # if not sinkParam_AliveBit: `=destroy`(sinkParam)
  240. let t = s.typ.skipTypes({tyGenericInst, tyAlias, tySink})
  241. if t.destructor != nil:
  242. c.destroys.add newTree(nkIfStmt,
  243. newTree(nkElifBranch, newSymNode result, genDestroy(c, t, newSymNode s)))
  244. proc p(n: PNode; c: var Con): PNode
  245. template recurse(n, dest) =
  246. for i in 0..<n.len:
  247. dest.add p(n[i], c)
  248. proc isSinkParam(s: PSym): bool {.inline.} =
  249. result = s.kind == skParam and s.typ.kind == tySink
  250. const constrExprs = nkCallKinds+{nkObjConstr}
  251. proc destructiveMoveSink(n: PNode; c: var Con): PNode =
  252. # generate: (chckMove(sinkParam_AliveBit); sinkParam_AliveBit = false; sinkParam)
  253. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  254. let bit = newSymNode dropBit(c, n.sym)
  255. if optMoveCheck in c.owner.options:
  256. result.add callCodegenProc(c.graph, "chckMove", bit)
  257. result.add newTree(nkAsgn, bit,
  258. newIntTypeNode(nkIntLit, 0, getSysType(c.graph, n.info, tyBool)))
  259. result.add n
  260. proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
  261. result = newNodeI(nkCall, n.info)
  262. result.add(newSymNode(createMagic(c.graph, magicname, m)))
  263. result.add n
  264. proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
  265. if ri.kind in constrExprs:
  266. result = genSink(c, dest.typ, dest)
  267. # watch out and no not transform 'ri' twice if it's a call:
  268. let ri2 = copyNode(ri)
  269. recurse(ri, ri2)
  270. result.add ri2
  271. elif ri.kind == nkSym and isHarmlessVar(ri.sym, c):
  272. # Rule 3: `=sink`(x, z); wasMoved(z)
  273. var snk = genSink(c, dest.typ, dest)
  274. snk.add p(ri, c)
  275. result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
  276. elif ri.kind == nkSym and isSinkParam(ri.sym):
  277. result = genSink(c, dest.typ, dest)
  278. result.add destructiveMoveSink(ri, c)
  279. else:
  280. result = genCopy(c, dest.typ, dest)
  281. result.add p(ri, c)
  282. proc passCopyToSink(n: PNode; c: var Con): PNode =
  283. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  284. let tmp = getTemp(c, n.typ, n.info)
  285. if hasDestructor(n.typ):
  286. var m = genCopy(c, n.typ, tmp)
  287. m.add p(n, c)
  288. result.add m
  289. message(c.graph.config, n.info, hintPerformance,
  290. "passing '$1' to a sink parameter introduces an implicit copy; " &
  291. "use 'move($1)' to prevent it" % $n)
  292. else:
  293. result.add newTree(nkAsgn, tmp, p(n, c))
  294. result.add tmp
  295. proc genWasMoved(n: PNode; c: var Con): PNode =
  296. # The mWasMoved builtin does not take the address.
  297. result = genMagicCall(n, c, "wasMoved", mWasMoved)
  298. proc destructiveMoveVar(n: PNode; c: var Con): PNode =
  299. # generate: (let tmp = v; reset(v); tmp)
  300. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  301. var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
  302. var v = newNodeI(nkLetSection, n.info)
  303. let tempAsNode = newSymNode(temp)
  304. var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
  305. vpart.sons[0] = tempAsNode
  306. vpart.sons[1] = c.emptyNode
  307. vpart.sons[2] = n
  308. add(v, vpart)
  309. result.add v
  310. result.add genWasMoved(n, c)
  311. result.add tempAsNode
  312. proc p(n: PNode; c: var Con): PNode =
  313. case n.kind
  314. of nkVarSection, nkLetSection:
  315. discard "transform; var x = y to var x; x op y where op is a move or copy"
  316. result = newNodeI(nkStmtList, n.info)
  317. for i in 0..<n.len:
  318. let it = n[i]
  319. let L = it.len-1
  320. let ri = it[L]
  321. if it.kind == nkVarTuple and hasDestructor(ri.typ):
  322. let x = lowerTupleUnpacking(c.graph, it, c.owner)
  323. result.add p(x, c)
  324. elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
  325. for j in 0..L-2:
  326. let v = it[j]
  327. doAssert v.kind == nkSym
  328. # move the variable declaration to the top of the frame:
  329. c.addTopVar v
  330. # make sure it's destroyed at the end of the proc:
  331. c.destroys.add genDestroy(c, v.typ, v)
  332. if ri.kind != nkEmpty:
  333. let r = moveOrCopy(v, ri, c)
  334. result.add r
  335. else:
  336. # keep it, but transform 'ri':
  337. var varSection = copyNode(n)
  338. var itCopy = copyNode(it)
  339. for j in 0..L-1:
  340. itCopy.add it[j]
  341. itCopy.add p(ri, c)
  342. varSection.add itCopy
  343. result.add varSection
  344. of nkCallKinds:
  345. let parameters = n[0].typ
  346. let L = if parameters != nil: parameters.len else: 0
  347. for i in 1 ..< n.len:
  348. let arg = n[i]
  349. if i < L and parameters[i].kind == tySink:
  350. if arg.kind in nkCallKinds:
  351. # recurse but skip the call expression in order to prevent
  352. # destructor injections: Rule 5.1 is different from rule 5.4!
  353. let a = copyNode(arg)
  354. recurse(arg, a)
  355. n.sons[i] = a
  356. elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}:
  357. discard "object construction to sink parameter: nothing to do"
  358. elif arg.kind == nkSym and isHarmlessVar(arg.sym, c):
  359. # if x is a variable and it its last read we eliminate its
  360. # destructor invokation, but don't. We need to reset its memory
  361. # to disable its destructor which we have not elided:
  362. n.sons[i] = destructiveMoveVar(arg, c)
  363. elif arg.kind == nkSym and isSinkParam(arg.sym):
  364. # mark the sink parameter as used:
  365. n.sons[i] = destructiveMoveSink(arg, c)
  366. else:
  367. # an object that is not temporary but passed to a 'sink' parameter
  368. # results in a copy.
  369. n.sons[i] = passCopyToSink(arg, c)
  370. else:
  371. n.sons[i] = p(arg, c)
  372. if n.typ != nil and hasDestructor(n.typ):
  373. discard "produce temp creation"
  374. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  375. let tmp = getTemp(c, n.typ, n.info)
  376. var sinkExpr = genSink(c, n.typ, tmp)
  377. sinkExpr.add n
  378. result.add sinkExpr
  379. result.add tmp
  380. c.destroys.add genDestroy(c, n.typ, tmp)
  381. else:
  382. result = n
  383. of nkAsgn, nkFastAsgn:
  384. if hasDestructor(n[0].typ):
  385. result = moveOrCopy(n[0], n[1], c)
  386. else:
  387. result = copyNode(n)
  388. recurse(n, result)
  389. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  390. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  391. result = n
  392. else:
  393. result = copyNode(n)
  394. recurse(n, result)
  395. proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
  396. when defined(nimDebugDestroys):
  397. echo "injecting into ", n
  398. var c: Con
  399. c.owner = owner
  400. c.tmp = newSym(skTemp, getIdent(g.cache, ":d"), owner, n.info)
  401. c.tmpObj = createObj(g, owner, n.info)
  402. c.tmp.typ = c.tmpObj
  403. c.destroys = newNodeI(nkStmtList, n.info)
  404. c.topLevelVars = newNodeI(nkVarSection, n.info)
  405. c.toDropBit = initTable[int, PSym]()
  406. c.graph = g
  407. c.emptyNode = newNodeI(nkEmpty, n.info)
  408. let cfg = constructCfg(owner, n)
  409. shallowCopy(c.g, cfg)
  410. c.jumpTargets = initIntSet()
  411. for i in 0..<c.g.len:
  412. if c.g[i].kind in {goto, fork}:
  413. c.jumpTargets.incl(i+c.g[i].dest)
  414. if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
  415. let params = owner.typ.n
  416. for i in 1 ..< params.len:
  417. let param = params[i].sym
  418. if param.typ.kind == tySink: registerDropBit(c, param)
  419. let body = p(n, c)
  420. if c.tmp.typ.n.len > 0:
  421. c.addTopVar(newSymNode c.tmp)
  422. result = newNodeI(nkStmtList, n.info)
  423. if c.topLevelVars.len > 0:
  424. result.add c.topLevelVars
  425. if c.destroys.len > 0:
  426. result.add newTryFinally(body, c.destroys)
  427. else:
  428. result.add body
  429. when defined(nimDebugDestroys):
  430. if owner.name.s == "main" or true:
  431. echo "------------------------------------"
  432. echo owner.name.s, " transformed to: "
  433. echo result