destroyer.nim 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  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, msgs,
  113. lineinfos, parampatterns
  114. const
  115. InterestingSyms = {skVar, skResult, skLet}
  116. type
  117. Con = object
  118. owner: PSym
  119. g: ControlFlowGraph
  120. jumpTargets: IntSet
  121. destroys, topLevelVars: PNode
  122. graph: ModuleGraph
  123. emptyNode: PNode
  124. otherRead: PNode
  125. proc isHarmlessVar*(s: PSym; c: Con): bool =
  126. # 's' is harmless if it used only once and its
  127. # definition/usage are not split by any labels:
  128. #
  129. # let s = foo()
  130. # while true:
  131. # a[i] = s
  132. #
  133. # produces:
  134. #
  135. # def s
  136. # L1:
  137. # use s
  138. # goto L1
  139. #
  140. # let s = foo()
  141. # if cond:
  142. # a[i] = s
  143. # else:
  144. # a[j] = s
  145. #
  146. # produces:
  147. #
  148. # def s
  149. # fork L2
  150. # use s
  151. # goto L3
  152. # L2:
  153. # use s
  154. # L3
  155. #
  156. # So this analysis is for now overly conservative, but correct.
  157. var defsite = -1
  158. var usages = 0
  159. for i in 0..<c.g.len:
  160. case c.g[i].kind
  161. of def:
  162. if c.g[i].sym == s:
  163. if defsite < 0: defsite = i
  164. else: return false
  165. of use:
  166. if c.g[i].sym == s:
  167. if defsite < 0: return false
  168. for j in defsite .. i:
  169. # not within the same basic block?
  170. if j in c.jumpTargets: return false
  171. # if we want to die after the first 'use':
  172. if usages > 1: return false
  173. inc usages
  174. #of useWithinCall:
  175. # if c.g[i].sym == s: return false
  176. of goto, fork:
  177. discard "we do not perform an abstract interpretation yet"
  178. result = usages <= 1
  179. proc isLastRead(n: PNode; c: var Con): bool =
  180. # first we need to search for the instruction that belongs to 'n':
  181. doAssert n.kind == nkSym
  182. c.otherRead = nil
  183. var instr = -1
  184. for i in 0..<c.g.len:
  185. if c.g[i].n == n:
  186. if instr < 0: instr = i
  187. else:
  188. # eh, we found two positions that belong to 'n'?
  189. # better return 'false' then:
  190. return false
  191. if instr < 0: return false
  192. # we go through all paths beginning from 'instr+1' and need to
  193. # ensure that we don't find another 'use X' instruction.
  194. if instr+1 >= c.g.len: return true
  195. let s = n.sym
  196. var pcs: seq[int] = @[instr+1]
  197. var takenGotos: IntSet
  198. var takenForks = initIntSet()
  199. while pcs.len > 0:
  200. var pc = pcs.pop
  201. takenGotos = initIntSet()
  202. while pc < c.g.len:
  203. case c.g[pc].kind
  204. of def:
  205. if c.g[pc].sym == s:
  206. # the path lead to a redefinition of 's' --> abandon it.
  207. when false:
  208. # Too complex thinking ahead: In reality it is enough to find
  209. # the 'def x' here on the current path to make the 'use x' valid.
  210. # but for this the definition needs to dominate the usage:
  211. var dominates = true
  212. for j in pc+1 .. instr:
  213. # not within the same basic block?
  214. if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr):
  215. #if j in c.jumpTargets:
  216. dominates = false
  217. if dominates: break
  218. break
  219. inc pc
  220. of use:
  221. if c.g[pc].sym == s:
  222. c.otherRead = c.g[pc].n
  223. return false
  224. inc pc
  225. of goto:
  226. # we must leave endless loops eventually:
  227. if not takenGotos.containsOrIncl(pc):
  228. pc = pc + c.g[pc].dest
  229. else:
  230. inc pc
  231. of fork:
  232. # we follow the next instruction but push the dest onto our "work" stack:
  233. if not takenForks.containsOrIncl(pc):
  234. pcs.add pc + c.g[pc].dest
  235. inc pc
  236. #echo c.graph.config $ n.info, " last read here!"
  237. return true
  238. template interestingSym(s: PSym): bool =
  239. s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
  240. template isUnpackedTuple(s: PSym): bool =
  241. ## we move out all elements of unpacked tuples,
  242. ## hence unpacked tuples themselves don't need to be destroyed
  243. s.kind == skTemp and s.typ.kind == tyTuple
  244. proc patchHead(n: PNode) =
  245. if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
  246. let s = n[0].sym
  247. if s.name.s[0] == '=' and s.name.s in ["=sink", "=", "=destroy"]:
  248. if sfFromGeneric in s.flags:
  249. excl(s.flags, sfFromGeneric)
  250. patchHead(s.getBody)
  251. let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
  252. template patch(op, field) =
  253. if s.name.s == op and field != nil and field != s:
  254. n.sons[0].sym = field
  255. patch "=sink", t.sink
  256. patch "=", t.assignment
  257. patch "=destroy", t.destructor
  258. for x in n:
  259. patchHead(x)
  260. proc patchHead(s: PSym) =
  261. if sfFromGeneric in s.flags:
  262. patchHead(s.ast[bodyPos])
  263. proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
  264. var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
  265. if opname == "=" and ri != nil:
  266. m.add "; requires a copy because it's not the last read of '"
  267. m.add renderTree(ri)
  268. m.add '\''
  269. if c.otherRead != nil:
  270. m.add "; another read is done here: "
  271. m.add c.graph.config $ c.otherRead.info
  272. localError(c.graph.config, ri.info, errGenerated, m)
  273. proc makePtrType(c: Con, baseType: PType): PType =
  274. result = newType(tyPtr, c.owner)
  275. addSonSkipIntLit(result, baseType)
  276. template genOp(opr, opname, ri) =
  277. let op = opr
  278. if op == nil:
  279. globalError(c.graph.config, dest.info, "internal error: '" & opname &
  280. "' operator not found for type " & typeToString(t))
  281. elif op.ast[genericParamsPos].kind != nkEmpty:
  282. globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic")
  283. patchHead op
  284. if sfError in op.flags: checkForErrorPragma(c, t, ri, opname)
  285. let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ))
  286. addrExp.add(dest)
  287. result = newTree(nkCall, newSymNode(op), addrExp)
  288. proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
  289. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  290. genOp(if t.sink != nil: t.sink else: t.assignment, "=sink", ri)
  291. proc genCopy(c: Con; t: PType; dest, ri: PNode): PNode =
  292. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  293. genOp(t.assignment, "=", ri)
  294. proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
  295. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  296. genOp(t.destructor, "=destroy", nil)
  297. proc addTopVar(c: var Con; v: PNode) =
  298. c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
  299. proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
  300. let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info)
  301. sym.typ = typ
  302. result = newSymNode(sym)
  303. c.addTopVar(result)
  304. proc p(n: PNode; c: var Con): PNode
  305. template recurse(n, dest) =
  306. for i in 0..<n.len:
  307. dest.add p(n[i], c)
  308. proc isSinkParam(s: PSym): bool {.inline.} =
  309. result = s.kind == skParam and s.typ.kind == tySink
  310. proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
  311. result = newNodeI(nkCall, n.info)
  312. result.add(newSymNode(createMagic(c.graph, magicname, m)))
  313. result.add n
  314. proc genWasMoved(n: PNode; c: var Con): PNode =
  315. # The mWasMoved builtin does not take the address.
  316. result = genMagicCall(n, c, "wasMoved", mWasMoved)
  317. proc destructiveMoveVar(n: PNode; c: var Con): PNode =
  318. # generate: (let tmp = v; reset(v); tmp)
  319. # XXX: Strictly speaking we can only move if there is a ``=sink`` defined
  320. # or if no ``=sink`` is defined and also no assignment.
  321. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  322. var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
  323. temp.typ = n.typ
  324. var v = newNodeI(nkLetSection, n.info)
  325. let tempAsNode = newSymNode(temp)
  326. var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
  327. vpart.sons[0] = tempAsNode
  328. vpart.sons[1] = c.emptyNode
  329. vpart.sons[2] = n
  330. add(v, vpart)
  331. result.add v
  332. result.add genWasMoved(n, c)
  333. result.add tempAsNode
  334. proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
  335. assert s.kind == nkSym and s.sym.kind == skParam
  336. if not isLastRead(s, c):
  337. localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
  338. "` is already consumed at " & toFileLineCol(c. graph.config, s.info))
  339. proc passCopyToSink(n: PNode; c: var Con): PNode =
  340. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  341. let tmp = getTemp(c, n.typ, n.info)
  342. if hasDestructor(n.typ):
  343. var m = genCopy(c, n.typ, tmp, n)
  344. m.add p(n, c)
  345. result.add m
  346. if isLValue(n):
  347. message(c.graph.config, n.info, hintPerformance,
  348. ("passing '$1' to a sink parameter introduces an implicit copy; " &
  349. "use 'move($1)' to prevent it") % $n)
  350. else:
  351. result.add newTree(nkAsgn, tmp, p(n, c))
  352. result.add tmp
  353. proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
  354. template pArgIfTyped(arg_part: PNode): PNode =
  355. # typ is nil if we are in if/case expr branch with noreturn
  356. if arg_part.typ == nil: p(arg_part, c)
  357. else: pArg(arg_part, c, isSink)
  358. if isSink:
  359. if arg.kind in nkCallKinds:
  360. # recurse but skip the call expression in order to prevent
  361. # destructor injections: Rule 5.1 is different from rule 5.4!
  362. result = copyNode(arg)
  363. let parameters = arg[0].typ
  364. let L = if parameters != nil: parameters.len else: 0
  365. result.add arg[0]
  366. for i in 1..<arg.len:
  367. result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink)
  368. elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkBracket, nkCharLit..nkFloat128Lit}:
  369. discard "object construction to sink parameter: nothing to do"
  370. result = arg
  371. elif arg.kind == nkSym and isSinkParam(arg.sym):
  372. # Sinked params can be consumed only once. We need to reset the memory
  373. # to disable the destructor which we have not elided
  374. sinkParamIsLastReadCheck(c, arg)
  375. result = destructiveMoveVar(arg, c)
  376. elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
  377. # it is the last read, can be sinked. We need to reset the memory
  378. # to disable the destructor which we have not elided
  379. result = destructiveMoveVar(arg, c)
  380. elif arg.kind in {nkBlockExpr, nkBlockStmt}:
  381. result = copyNode(arg)
  382. result.add arg[0]
  383. result.add pArg(arg[1], c, isSink)
  384. elif arg.kind == nkStmtListExpr:
  385. result = copyNode(arg)
  386. for i in 0..arg.len-2:
  387. result.add p(arg[i], c)
  388. result.add pArg(arg[^1], c, isSink)
  389. elif arg.kind in {nkIfExpr, nkIfStmt}:
  390. result = copyNode(arg)
  391. for i in 0..<arg.len:
  392. var branch = copyNode(arg[i])
  393. if arg[i].kind in {nkElifBranch, nkElifExpr}:
  394. branch.add p(arg[i][0], c)
  395. branch.add pArgIfTyped(arg[i][1])
  396. else:
  397. branch.add pArgIfTyped(arg[i][0])
  398. result.add branch
  399. elif arg.kind == nkCaseStmt:
  400. result = copyNode(arg)
  401. result.add p(arg[0], c)
  402. for i in 1..<arg.len:
  403. var branch: PNode
  404. if arg[i].kind == nkOfbranch:
  405. branch = arg[i] # of branch conditions are constants
  406. branch[^1] = pArgIfTyped(arg[i][^1])
  407. elif arg[i].kind in {nkElifBranch, nkElifExpr}:
  408. branch = copyNode(arg[i])
  409. branch.add p(arg[i][0], c)
  410. branch.add pArgIfTyped(arg[i][1])
  411. else:
  412. branch = copyNode(arg[i])
  413. branch.add pArgIfTyped(arg[i][0])
  414. result.add branch
  415. else:
  416. # an object that is not temporary but passed to a 'sink' parameter
  417. # results in a copy.
  418. result = passCopyToSink(arg, c)
  419. else:
  420. result = p(arg, c)
  421. proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
  422. template moveOrCopyIfTyped(ri_part: PNode): PNode =
  423. # typ is nil if we are in if/case expr branch with noreturn
  424. if ri_part.typ == nil: p(ri_part, c)
  425. else: moveOrCopy(dest, ri_part, c)
  426. case ri.kind
  427. of nkCallKinds:
  428. result = genSink(c, dest.typ, dest, ri)
  429. # watch out and no not transform 'ri' twice if it's a call:
  430. let ri2 = copyNode(ri)
  431. let parameters = ri[0].typ
  432. let L = if parameters != nil: parameters.len else: 0
  433. ri2.add ri[0]
  434. for i in 1..<ri.len:
  435. ri2.add pArg(ri[i], c, i < L and parameters[i].kind == tySink)
  436. #recurse(ri, ri2)
  437. result.add ri2
  438. of nkBracketExpr:
  439. if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
  440. # unpacking of tuple: move out the elements
  441. result = genSink(c, dest.typ, dest, ri)
  442. else:
  443. result = genCopy(c, dest.typ, dest, ri)
  444. result.add p(ri, c)
  445. of nkStmtListExpr:
  446. result = newNodeI(nkStmtList, ri.info)
  447. for i in 0..ri.len-2:
  448. result.add p(ri[i], c)
  449. result.add moveOrCopy(dest, ri[^1], c)
  450. of nkBlockExpr, nkBlockStmt:
  451. result = newNodeI(nkBlockStmt, ri.info)
  452. result.add ri[0] ## add label
  453. result.add moveOrCopy(dest, ri[1], c)
  454. of nkIfExpr, nkIfStmt:
  455. result = newNodeI(nkIfStmt, ri.info)
  456. for i in 0..<ri.len:
  457. var branch = copyNode(ri[i])
  458. if ri[i].kind in {nkElifBranch, nkElifExpr}:
  459. branch.add p(ri[i][0], c)
  460. branch.add moveOrCopyIfTyped(ri[i][1])
  461. else:
  462. branch.add moveOrCopyIfTyped(ri[i][0])
  463. result.add branch
  464. of nkCaseStmt:
  465. result = newNodeI(nkCaseStmt, ri.info)
  466. result.add p(ri[0], c)
  467. for i in 1..<ri.len:
  468. var branch: PNode
  469. if ri[i].kind == nkOfbranch:
  470. branch = ri[i] # of branch conditions are constants
  471. branch[^1] = moveOrCopyIfTyped(ri[i][^1])
  472. elif ri[i].kind in {nkElifBranch, nkElifExpr}:
  473. branch = copyNode(ri[i])
  474. branch.add p(ri[i][0], c)
  475. branch.add moveOrCopyIfTyped(ri[i][1])
  476. else:
  477. branch = copyNode(ri[i])
  478. branch.add moveOrCopyIfTyped(ri[i][0])
  479. result.add branch
  480. of nkBracket:
  481. # array constructor
  482. result = genSink(c, dest.typ, dest, ri)
  483. let ri2 = copyTree(ri)
  484. for i in 0..<ri.len:
  485. # everything that is passed to an array constructor is consumed,
  486. # so these all act like 'sink' parameters:
  487. ri2[i] = pArg(ri[i], c, isSink = true)
  488. result.add ri2
  489. of nkObjConstr:
  490. result = genSink(c, dest.typ, dest, ri)
  491. let ri2 = copyTree(ri)
  492. for i in 1..<ri.len:
  493. # everything that is passed to an object constructor is consumed,
  494. # so these all act like 'sink' parameters:
  495. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  496. result.add ri2
  497. of nkTupleConstr:
  498. result = genSink(c, dest.typ, dest, ri)
  499. let ri2 = copyTree(ri)
  500. for i in 0..<ri.len:
  501. # everything that is passed to an tuple constructor is consumed,
  502. # so these all act like 'sink' parameters:
  503. if ri[i].kind == nkExprColonExpr:
  504. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  505. else:
  506. ri2[i] = pArg(ri[i], c, isSink = true)
  507. result.add ri2
  508. of nkSym:
  509. if isSinkParam(ri.sym):
  510. # Rule 3: `=sink`(x, z); wasMoved(z)
  511. sinkParamIsLastReadCheck(c, ri)
  512. var snk = genSink(c, dest.typ, dest, ri)
  513. snk.add ri
  514. result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
  515. elif ri.sym.kind != skParam and isLastRead(ri, c):
  516. # Rule 3: `=sink`(x, z); wasMoved(z)
  517. var snk = genSink(c, dest.typ, dest, ri)
  518. snk.add ri
  519. result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
  520. else:
  521. result = genCopy(c, dest.typ, dest, ri)
  522. result.add p(ri, c)
  523. else:
  524. result = genCopy(c, dest.typ, dest, ri)
  525. result.add p(ri, c)
  526. proc p(n: PNode; c: var Con): PNode =
  527. case n.kind
  528. of nkVarSection, nkLetSection:
  529. discard "transform; var x = y to var x; x op y where op is a move or copy"
  530. result = newNodeI(nkStmtList, n.info)
  531. for i in 0..<n.len:
  532. let it = n[i]
  533. let L = it.len-1
  534. let ri = it[L]
  535. if it.kind == nkVarTuple and hasDestructor(ri.typ):
  536. let x = lowerTupleUnpacking(c.graph, it, c.owner)
  537. result.add p(x, c)
  538. elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
  539. for j in 0..L-2:
  540. let v = it[j]
  541. doAssert v.kind == nkSym
  542. # move the variable declaration to the top of the frame:
  543. c.addTopVar v
  544. # make sure it's destroyed at the end of the proc:
  545. if not isUnpackedTuple(it[0].sym):
  546. c.destroys.add genDestroy(c, v.typ, v)
  547. if ri.kind != nkEmpty:
  548. let r = moveOrCopy(v, ri, c)
  549. result.add r
  550. else:
  551. # keep it, but transform 'ri':
  552. var varSection = copyNode(n)
  553. var itCopy = copyNode(it)
  554. for j in 0..L-1:
  555. itCopy.add it[j]
  556. itCopy.add p(ri, c)
  557. varSection.add itCopy
  558. result.add varSection
  559. of nkCallKinds:
  560. let parameters = n[0].typ
  561. let L = if parameters != nil: parameters.len else: 0
  562. for i in 1 ..< n.len:
  563. n.sons[i] = pArg(n[i], c, i < L and parameters[i].kind == tySink)
  564. if n.typ != nil and hasDestructor(n.typ):
  565. discard "produce temp creation"
  566. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  567. let tmp = getTemp(c, n.typ, n.info)
  568. var sinkExpr = genSink(c, n.typ, tmp, n)
  569. sinkExpr.add n
  570. result.add sinkExpr
  571. result.add tmp
  572. c.destroys.add genDestroy(c, n.typ, tmp)
  573. else:
  574. result = n
  575. of nkAsgn, nkFastAsgn:
  576. if hasDestructor(n[0].typ):
  577. result = moveOrCopy(n[0], n[1], c)
  578. else:
  579. result = copyNode(n)
  580. recurse(n, result)
  581. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  582. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  583. result = n
  584. else:
  585. result = copyNode(n)
  586. recurse(n, result)
  587. proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
  588. when false: # defined(nimDebugDestroys):
  589. echo "injecting into ", n
  590. var c: Con
  591. c.owner = owner
  592. c.destroys = newNodeI(nkStmtList, n.info)
  593. c.topLevelVars = newNodeI(nkVarSection, n.info)
  594. c.graph = g
  595. c.emptyNode = newNodeI(nkEmpty, n.info)
  596. let cfg = constructCfg(owner, n)
  597. shallowCopy(c.g, cfg)
  598. c.jumpTargets = initIntSet()
  599. for i in 0..<c.g.len:
  600. if c.g[i].kind in {goto, fork}:
  601. c.jumpTargets.incl(i+c.g[i].dest)
  602. #if owner.name.s == "test0p1":
  603. # echoCfg(c.g)
  604. if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
  605. let params = owner.typ.n
  606. for i in 1 ..< params.len:
  607. let param = params[i].sym
  608. if param.typ.kind == tySink and hasDestructor(param.typ):
  609. c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
  610. let body = p(n, c)
  611. result = newNodeI(nkStmtList, n.info)
  612. if c.topLevelVars.len > 0:
  613. result.add c.topLevelVars
  614. if c.destroys.len > 0:
  615. result.add newTryFinally(body, c.destroys)
  616. else:
  617. result.add body
  618. when defined(nimDebugDestroys):
  619. if true:
  620. echo "------------------------------------"
  621. echo owner.name.s, " transformed to: "
  622. echo result