destroyer.nim 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  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. 2 x = f() `=sink`(x, f())
  98. 3 x = lastReadOf z `=sink`(x, z); wasMoved(z)
  99. 3.2 x = path z; body ``x = bitwiseCopy(path z);``
  100. do not emit `=destroy(x)`. Note: body
  101. must not mutate ``z`` nor ``x``. All
  102. assignments to ``x`` must be of the form
  103. ``path z`` but the ``z`` can differ.
  104. Neither ``z`` nor ``x`` can have the
  105. flag ``sfAddrTaken`` to ensure no other
  106. aliasing is going on.
  107. 4.1 y = sinkParam `=sink`(y, sinkParam)
  108. 4.2 x = y `=`(x, y) # a copy
  109. 5.1 f_sink(g()) f_sink(g())
  110. 5.2 f_sink(y) f_sink(copy y); # copy unless we can see it's the last read
  111. 5.3 f_sink(move y) f_sink(y); wasMoved(y) # explicit moves empties 'y'
  112. 5.4 f_noSink(g()) var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
  113. Rule 3.2 describes a "cursor" variable, a variable that is only used as a
  114. view into some data structure. See ``compiler/cursors.nim`` for details.
  115. Note: In order to avoid the very common combination ``reset(x); =sink(x, y)`` for
  116. variable definitions we must turn "the first sink/assignment" operation into a
  117. copyMem. This is harder than it looks:
  118. while true:
  119. try:
  120. if cond: break # problem if we run destroy(x) here :-/
  121. var x = f()
  122. finally:
  123. destroy(x)
  124. And the C++ optimizers don't sweat to optimize it for us, so we don't have
  125. to do it.
  126. ]##
  127. import
  128. intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
  129. strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
  130. lineinfos, parampatterns
  131. const
  132. InterestingSyms = {skVar, skResult, skLet}
  133. type
  134. Con = object
  135. owner: PSym
  136. g: ControlFlowGraph
  137. jumpTargets: IntSet
  138. destroys, topLevelVars: PNode
  139. graph: ModuleGraph
  140. emptyNode: PNode
  141. otherRead: PNode
  142. uninit: IntSet # set of uninit'ed vars
  143. uninitComputed: bool
  144. proc isLastRead(s: PSym; c: var Con; pc, comesFrom: int): int =
  145. var pc = pc
  146. while pc < c.g.len:
  147. case c.g[pc].kind
  148. of def:
  149. if c.g[pc].sym == s:
  150. # the path lead to a redefinition of 's' --> abandon it.
  151. return high(int)
  152. inc pc
  153. of use:
  154. if c.g[pc].sym == s:
  155. c.otherRead = c.g[pc].n
  156. return -1
  157. inc pc
  158. of goto:
  159. pc = pc + c.g[pc].dest
  160. of fork:
  161. # every branch must lead to the last read of the location:
  162. var variantA = isLastRead(s, c, pc+1, pc)
  163. if variantA < 0: return -1
  164. let variantB = isLastRead(s, c, pc + c.g[pc].dest, pc)
  165. if variantB < 0: return -1
  166. elif variantA == high(int):
  167. variantA = variantB
  168. pc = variantA
  169. of InstrKind.join:
  170. let dest = pc + c.g[pc].dest
  171. if dest == comesFrom: return pc + 1
  172. inc pc
  173. return pc
  174. proc isLastRead(n: PNode; c: var Con): bool =
  175. # first we need to search for the instruction that belongs to 'n':
  176. doAssert n.kind == nkSym
  177. c.otherRead = nil
  178. var instr = -1
  179. for i in 0..<c.g.len:
  180. if c.g[i].n == n:
  181. if instr < 0:
  182. instr = i
  183. break
  184. if instr < 0: return false
  185. # we go through all paths beginning from 'instr+1' and need to
  186. # ensure that we don't find another 'use X' instruction.
  187. if instr+1 >= c.g.len: return true
  188. when true:
  189. result = isLastRead(n.sym, c, instr+1, -1) >= 0
  190. else:
  191. let s = n.sym
  192. var pcs: seq[int] = @[instr+1]
  193. var takenGotos: IntSet
  194. var takenForks = initIntSet()
  195. while pcs.len > 0:
  196. var pc = pcs.pop
  197. takenGotos = initIntSet()
  198. while pc < c.g.len:
  199. case c.g[pc].kind
  200. of def:
  201. if c.g[pc].sym == s:
  202. # the path lead to a redefinition of 's' --> abandon it.
  203. break
  204. inc pc
  205. of use:
  206. if c.g[pc].sym == s:
  207. c.otherRead = c.g[pc].n
  208. return false
  209. inc pc
  210. of goto:
  211. # we must leave endless loops eventually:
  212. if not takenGotos.containsOrIncl(pc):
  213. pc = pc + c.g[pc].dest
  214. else:
  215. inc pc
  216. of fork:
  217. # we follow the next instruction but push the dest onto our "work" stack:
  218. if not takenForks.containsOrIncl(pc):
  219. pcs.add pc + c.g[pc].dest
  220. inc pc
  221. of InstrKind.join:
  222. inc pc
  223. #echo c.graph.config $ n.info, " last read here!"
  224. return true
  225. proc initialized(code: ControlFlowGraph; pc: int,
  226. init, uninit: var IntSet; comesFrom: int): int =
  227. ## Computes the set of definitely initialized variables accross all code paths
  228. ## as an IntSet of IDs.
  229. var pc = pc
  230. while pc < code.len:
  231. case code[pc].kind
  232. of goto:
  233. pc = pc + code[pc].dest
  234. of fork:
  235. let target = pc + code[pc].dest
  236. var initA = initIntSet()
  237. var initB = initIntSet()
  238. let pcA = initialized(code, pc+1, initA, uninit, pc)
  239. discard initialized(code, target, initB, uninit, pc)
  240. # we add vars if they are in both branches:
  241. for v in initA:
  242. if v in initB:
  243. init.incl v
  244. pc = pcA+1
  245. of InstrKind.join:
  246. let target = pc + code[pc].dest
  247. if comesFrom == target: return pc
  248. inc pc
  249. of use:
  250. let v = code[pc].sym
  251. if v.kind != skParam and v.id notin init:
  252. # attempt to read an uninit'ed variable
  253. uninit.incl v.id
  254. inc pc
  255. of def:
  256. let v = code[pc].sym
  257. init.incl v.id
  258. inc pc
  259. return pc
  260. template interestingSym(s: PSym): bool =
  261. s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
  262. template isUnpackedTuple(s: PSym): bool =
  263. ## we move out all elements of unpacked tuples,
  264. ## hence unpacked tuples themselves don't need to be destroyed
  265. s.kind == skTemp and s.typ.kind == tyTuple
  266. proc patchHead(n: PNode) =
  267. if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
  268. let s = n[0].sym
  269. if s.name.s[0] == '=' and s.name.s in ["=sink", "=", "=destroy"]:
  270. if sfFromGeneric in s.flags:
  271. excl(s.flags, sfFromGeneric)
  272. patchHead(s.getBody)
  273. let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
  274. template patch(op, field) =
  275. if s.name.s == op and field != nil and field != s:
  276. n.sons[0].sym = field
  277. patch "=sink", t.sink
  278. patch "=", t.assignment
  279. patch "=destroy", t.destructor
  280. for x in n:
  281. patchHead(x)
  282. proc patchHead(s: PSym) =
  283. if sfFromGeneric in s.flags:
  284. # do not patch the builtin type bound operators for seqs:
  285. let dest = s.typ.sons[1].skipTypes(abstractVar)
  286. if dest.kind != tySequence:
  287. patchHead(s.ast[bodyPos])
  288. proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
  289. var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
  290. if opname == "=" and ri != nil:
  291. m.add "; requires a copy because it's not the last read of '"
  292. m.add renderTree(ri)
  293. m.add '\''
  294. if c.otherRead != nil:
  295. m.add "; another read is done here: "
  296. m.add c.graph.config $ c.otherRead.info
  297. localError(c.graph.config, ri.info, errGenerated, m)
  298. proc makePtrType(c: Con, baseType: PType): PType =
  299. result = newType(tyPtr, c.owner)
  300. addSonSkipIntLit(result, baseType)
  301. template genOp(opr, opname, ri) =
  302. let op = opr
  303. if op == nil:
  304. globalError(c.graph.config, dest.info, "internal error: '" & opname &
  305. "' operator not found for type " & typeToString(t))
  306. elif op.ast[genericParamsPos].kind != nkEmpty:
  307. globalError(c.graph.config, dest.info, "internal error: '" & opname &
  308. "' operator is generic")
  309. patchHead op
  310. if sfError in op.flags: checkForErrorPragma(c, t, ri, opname)
  311. let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ))
  312. addrExp.add(dest)
  313. result = newTree(nkCall, newSymNode(op), addrExp)
  314. proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
  315. when false:
  316. if t.kind != tyString:
  317. echo "this one ", c.graph.config$dest.info, " for ", typeToString(t, preferDesc)
  318. debug t.sink.typ.sons[2]
  319. echo t.sink.id, " owner ", t.id
  320. quit 1
  321. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  322. genOp(if t.sink != nil: t.sink else: t.assignment, "=sink", ri)
  323. proc genCopy(c: Con; t: PType; dest, ri: PNode): PNode =
  324. if tfHasOwned in t.flags:
  325. checkForErrorPragma(c, t, ri, "=")
  326. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  327. genOp(t.assignment, "=", ri)
  328. proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
  329. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  330. genOp(t.destructor, "=destroy", nil)
  331. proc addTopVar(c: var Con; v: PNode) =
  332. c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
  333. proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
  334. let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info)
  335. sym.typ = typ
  336. result = newSymNode(sym)
  337. c.addTopVar(result)
  338. proc p(n: PNode; c: var Con): PNode
  339. template recurse(n, dest) =
  340. for i in 0..<n.len:
  341. dest.add p(n[i], c)
  342. proc isSinkParam(s: PSym): bool {.inline.} =
  343. result = s.kind == skParam and s.typ.kind == tySink
  344. proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
  345. result = newNodeI(nkCall, n.info)
  346. result.add(newSymNode(createMagic(c.graph, magicname, m)))
  347. result.add n
  348. proc genWasMoved(n: PNode; c: var Con): PNode =
  349. # The mWasMoved builtin does not take the address.
  350. result = genMagicCall(n, c, "wasMoved", mWasMoved)
  351. proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
  352. result = newNodeI(nkCall, info)
  353. result.add(newSymNode(createMagic(c.graph, "default", mDefault)))
  354. result.typ = t
  355. proc destructiveMoveVar(n: PNode; c: var Con): PNode =
  356. # generate: (let tmp = v; reset(v); tmp)
  357. # XXX: Strictly speaking we can only move if there is a ``=sink`` defined
  358. # or if no ``=sink`` is defined and also no assignment.
  359. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  360. var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
  361. temp.typ = n.typ
  362. var v = newNodeI(nkLetSection, n.info)
  363. let tempAsNode = newSymNode(temp)
  364. var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
  365. vpart.sons[0] = tempAsNode
  366. vpart.sons[1] = c.emptyNode
  367. vpart.sons[2] = n
  368. add(v, vpart)
  369. result.add v
  370. result.add genWasMoved(n, c)
  371. result.add tempAsNode
  372. proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
  373. assert s.kind == nkSym and s.sym.kind == skParam
  374. if not isLastRead(s, c):
  375. localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
  376. "` is already consumed at " & toFileLineCol(c. graph.config, s.info))
  377. proc passCopyToSink(n: PNode; c: var Con): PNode =
  378. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  379. let tmp = getTemp(c, n.typ, n.info)
  380. if hasDestructor(n.typ):
  381. var m = genCopy(c, n.typ, tmp, n)
  382. m.add p(n, c)
  383. result.add m
  384. if isLValue(n):
  385. message(c.graph.config, n.info, hintPerformance,
  386. ("passing '$1' to a sink parameter introduces an implicit copy; " &
  387. "use 'move($1)' to prevent it") % $n)
  388. else:
  389. result.add newTree(nkAsgn, tmp, p(n, c))
  390. result.add tmp
  391. proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
  392. template pArgIfTyped(arg_part: PNode): PNode =
  393. # typ is nil if we are in if/case expr branch with noreturn
  394. if arg_part.typ == nil: p(arg_part, c)
  395. else: pArg(arg_part, c, isSink)
  396. if isSink:
  397. if arg.kind in nkCallKinds:
  398. # recurse but skip the call expression in order to prevent
  399. # destructor injections: Rule 5.1 is different from rule 5.4!
  400. result = copyNode(arg)
  401. let parameters = arg[0].typ
  402. let L = if parameters != nil: parameters.len else: 0
  403. result.add arg[0]
  404. for i in 1..<arg.len:
  405. result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink)
  406. elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkBracket, nkCharLit..nkFloat128Lit}:
  407. discard "object construction to sink parameter: nothing to do"
  408. result = arg
  409. elif arg.kind == nkSym and isSinkParam(arg.sym):
  410. # Sinked params can be consumed only once. We need to reset the memory
  411. # to disable the destructor which we have not elided
  412. sinkParamIsLastReadCheck(c, arg)
  413. result = destructiveMoveVar(arg, c)
  414. elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
  415. # it is the last read, can be sinked. We need to reset the memory
  416. # to disable the destructor which we have not elided
  417. result = destructiveMoveVar(arg, c)
  418. elif arg.kind in {nkBlockExpr, nkBlockStmt}:
  419. result = copyNode(arg)
  420. result.add arg[0]
  421. result.add pArg(arg[1], c, isSink)
  422. elif arg.kind == nkStmtListExpr:
  423. result = copyNode(arg)
  424. for i in 0..arg.len-2:
  425. result.add p(arg[i], c)
  426. result.add pArg(arg[^1], c, isSink)
  427. elif arg.kind in {nkIfExpr, nkIfStmt}:
  428. result = copyNode(arg)
  429. for i in 0..<arg.len:
  430. var branch = copyNode(arg[i])
  431. if arg[i].kind in {nkElifBranch, nkElifExpr}:
  432. branch.add p(arg[i][0], c)
  433. branch.add pArgIfTyped(arg[i][1])
  434. else:
  435. branch.add pArgIfTyped(arg[i][0])
  436. result.add branch
  437. elif arg.kind == nkCaseStmt:
  438. result = copyNode(arg)
  439. result.add p(arg[0], c)
  440. for i in 1..<arg.len:
  441. var branch: PNode
  442. if arg[i].kind == nkOfbranch:
  443. branch = arg[i] # of branch conditions are constants
  444. branch[^1] = pArgIfTyped(arg[i][^1])
  445. elif arg[i].kind in {nkElifBranch, nkElifExpr}:
  446. branch = copyNode(arg[i])
  447. branch.add p(arg[i][0], c)
  448. branch.add pArgIfTyped(arg[i][1])
  449. else:
  450. branch = copyNode(arg[i])
  451. branch.add pArgIfTyped(arg[i][0])
  452. result.add branch
  453. else:
  454. # an object that is not temporary but passed to a 'sink' parameter
  455. # results in a copy.
  456. result = passCopyToSink(arg, c)
  457. else:
  458. result = p(arg, c)
  459. proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
  460. template moveOrCopyIfTyped(riPart: PNode): PNode =
  461. # typ is nil if we are in if/case expr branch with noreturn
  462. if riPart.typ == nil: p(riPart, c)
  463. else: moveOrCopy(dest, riPart, c)
  464. case ri.kind
  465. of nkCallKinds:
  466. result = genSink(c, dest.typ, dest, ri)
  467. # watch out and no not transform 'ri' twice if it's a call:
  468. let ri2 = copyNode(ri)
  469. let parameters = ri[0].typ
  470. let L = if parameters != nil: parameters.len else: 0
  471. ri2.add ri[0]
  472. for i in 1..<ri.len:
  473. ri2.add pArg(ri[i], c, i < L and parameters[i].kind == tySink)
  474. #recurse(ri, ri2)
  475. result.add ri2
  476. of nkBracketExpr:
  477. if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
  478. # unpacking of tuple: move out the elements
  479. result = genSink(c, dest.typ, dest, ri)
  480. else:
  481. result = genCopy(c, dest.typ, dest, ri)
  482. result.add p(ri, c)
  483. of nkStmtListExpr:
  484. result = newNodeI(nkStmtList, ri.info)
  485. for i in 0..ri.len-2:
  486. result.add p(ri[i], c)
  487. result.add moveOrCopy(dest, ri[^1], c)
  488. of nkBlockExpr, nkBlockStmt:
  489. result = newNodeI(nkBlockStmt, ri.info)
  490. result.add ri[0] ## add label
  491. result.add moveOrCopy(dest, ri[1], c)
  492. of nkIfExpr, nkIfStmt:
  493. result = newNodeI(nkIfStmt, ri.info)
  494. for i in 0..<ri.len:
  495. var branch = copyNode(ri[i])
  496. if ri[i].kind in {nkElifBranch, nkElifExpr}:
  497. branch.add p(ri[i][0], c)
  498. branch.add moveOrCopyIfTyped(ri[i][1])
  499. else:
  500. branch.add moveOrCopyIfTyped(ri[i][0])
  501. result.add branch
  502. of nkCaseStmt:
  503. result = newNodeI(nkCaseStmt, ri.info)
  504. result.add p(ri[0], c)
  505. for i in 1..<ri.len:
  506. var branch: PNode
  507. if ri[i].kind == nkOfbranch:
  508. branch = ri[i] # of branch conditions are constants
  509. branch[^1] = moveOrCopyIfTyped(ri[i][^1])
  510. elif ri[i].kind in {nkElifBranch, nkElifExpr}:
  511. branch = copyNode(ri[i])
  512. branch.add p(ri[i][0], c)
  513. branch.add moveOrCopyIfTyped(ri[i][1])
  514. else:
  515. branch = copyNode(ri[i])
  516. branch.add moveOrCopyIfTyped(ri[i][0])
  517. result.add branch
  518. of nkBracket:
  519. # array constructor
  520. result = genSink(c, dest.typ, dest, ri)
  521. let ri2 = copyTree(ri)
  522. for i in 0..<ri.len:
  523. # everything that is passed to an array constructor is consumed,
  524. # so these all act like 'sink' parameters:
  525. ri2[i] = pArg(ri[i], c, isSink = true)
  526. result.add ri2
  527. of nkObjConstr:
  528. result = genSink(c, dest.typ, dest, ri)
  529. let ri2 = copyTree(ri)
  530. for i in 1..<ri.len:
  531. # everything that is passed to an object constructor is consumed,
  532. # so these all act like 'sink' parameters:
  533. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  534. result.add ri2
  535. of nkTupleConstr:
  536. result = genSink(c, dest.typ, dest, ri)
  537. let ri2 = copyTree(ri)
  538. for i in 0..<ri.len:
  539. # everything that is passed to an tuple constructor is consumed,
  540. # so these all act like 'sink' parameters:
  541. if ri[i].kind == nkExprColonExpr:
  542. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  543. else:
  544. ri2[i] = pArg(ri[i], c, isSink = true)
  545. result.add ri2
  546. of nkSym:
  547. if isSinkParam(ri.sym):
  548. # Rule 3: `=sink`(x, z); wasMoved(z)
  549. sinkParamIsLastReadCheck(c, ri)
  550. var snk = genSink(c, dest.typ, dest, ri)
  551. snk.add ri
  552. result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
  553. elif ri.sym.kind != skParam and isLastRead(ri, c):
  554. # Rule 3: `=sink`(x, z); wasMoved(z)
  555. var snk = genSink(c, dest.typ, dest, ri)
  556. snk.add ri
  557. result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
  558. else:
  559. result = genCopy(c, dest.typ, dest, ri)
  560. result.add p(ri, c)
  561. else:
  562. result = genCopy(c, dest.typ, dest, ri)
  563. result.add p(ri, c)
  564. proc computeUninit(c: var Con) =
  565. if not c.uninitComputed:
  566. c.uninitComputed = true
  567. c.uninit = initIntSet()
  568. var init = initIntSet()
  569. discard initialized(c.g, pc = 0, init, c.uninit, comesFrom = -1)
  570. proc injectDefaultCalls(n: PNode, c: var Con) =
  571. case n.kind
  572. of nkVarSection, nkLetSection:
  573. for i in 0..<n.len:
  574. let it = n[i]
  575. let L = it.len-1
  576. let ri = it[L]
  577. if it.kind == nkIdentDefs and ri.kind == nkEmpty:
  578. computeUninit(c)
  579. for j in 0..L-2:
  580. let v = it[j]
  581. doAssert v.kind == nkSym
  582. if c.uninit.contains(v.sym.id):
  583. it[L] = genDefaultCall(v.sym.typ, c, v.info)
  584. break
  585. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  586. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  587. discard
  588. else:
  589. for i in 0..<safeLen(n):
  590. injectDefaultCalls(n[i], c)
  591. proc isCursor(n: PNode): bool {.inline.} =
  592. result = n.kind == nkSym and sfCursor in n.sym.flags
  593. proc p(n: PNode; c: var Con): PNode =
  594. case n.kind
  595. of nkVarSection, nkLetSection:
  596. discard "transform; var x = y to var x; x op y where op is a move or copy"
  597. result = newNodeI(nkStmtList, n.info)
  598. for i in 0..<n.len:
  599. let it = n[i]
  600. let L = it.len-1
  601. let ri = it[L]
  602. if it.kind == nkVarTuple and hasDestructor(ri.typ):
  603. let x = lowerTupleUnpacking(c.graph, it, c.owner)
  604. result.add p(x, c)
  605. elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]):
  606. for j in 0..L-2:
  607. let v = it[j]
  608. doAssert v.kind == nkSym
  609. # move the variable declaration to the top of the frame:
  610. c.addTopVar v
  611. # make sure it's destroyed at the end of the proc:
  612. if not isUnpackedTuple(it[0].sym):
  613. c.destroys.add genDestroy(c, v.typ, v)
  614. if ri.kind != nkEmpty:
  615. let r = moveOrCopy(v, ri, c)
  616. result.add r
  617. else:
  618. # keep it, but transform 'ri':
  619. var varSection = copyNode(n)
  620. var itCopy = copyNode(it)
  621. for j in 0..L-1:
  622. itCopy.add it[j]
  623. itCopy.add p(ri, c)
  624. varSection.add itCopy
  625. result.add varSection
  626. of nkCallKinds:
  627. let parameters = n[0].typ
  628. let L = if parameters != nil: parameters.len else: 0
  629. for i in 1 ..< n.len:
  630. n.sons[i] = pArg(n[i], c, i < L and parameters[i].kind == tySink)
  631. if n.typ != nil and hasDestructor(n.typ):
  632. discard "produce temp creation"
  633. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  634. let tmp = getTemp(c, n.typ, n.info)
  635. var sinkExpr = genSink(c, n.typ, tmp, n)
  636. sinkExpr.add n
  637. result.add sinkExpr
  638. result.add tmp
  639. c.destroys.add genDestroy(c, n.typ, tmp)
  640. else:
  641. result = n
  642. of nkAsgn, nkFastAsgn:
  643. if hasDestructor(n[0].typ):
  644. result = moveOrCopy(n[0], n[1], c)
  645. else:
  646. result = copyNode(n)
  647. recurse(n, result)
  648. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  649. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  650. result = n
  651. of nkDiscardStmt:
  652. result = n
  653. if n[0].typ != nil and hasDestructor(n[0].typ):
  654. result = genDestroy(c, n[0].typ, n[0])
  655. of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv:
  656. result = copyNode(n)
  657. # Destination type
  658. result.add n[0]
  659. # Analyse the inner expression
  660. result.add p(n[1], c)
  661. else:
  662. result = copyNode(n)
  663. recurse(n, result)
  664. proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
  665. when false: # defined(nimDebugDestroys):
  666. echo "injecting into ", n
  667. var c: Con
  668. c.owner = owner
  669. c.destroys = newNodeI(nkStmtList, n.info)
  670. c.topLevelVars = newNodeI(nkVarSection, n.info)
  671. c.graph = g
  672. c.emptyNode = newNodeI(nkEmpty, n.info)
  673. let cfg = constructCfg(owner, n)
  674. shallowCopy(c.g, cfg)
  675. c.jumpTargets = initIntSet()
  676. for i in 0..<c.g.len:
  677. if c.g[i].kind in {goto, fork}:
  678. c.jumpTargets.incl(i+c.g[i].dest)
  679. #if owner.name.s == "test0p1":
  680. # echoCfg(c.g)
  681. if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
  682. let params = owner.typ.n
  683. for i in 1 ..< params.len:
  684. let param = params[i].sym
  685. if param.typ.kind == tySink and hasDestructor(param.typ.sons[0]):
  686. c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
  687. if optNimV2 in c.graph.config.globalOptions:
  688. injectDefaultCalls(n, c)
  689. let body = p(n, c)
  690. result = newNodeI(nkStmtList, n.info)
  691. if c.topLevelVars.len > 0:
  692. result.add c.topLevelVars
  693. if c.destroys.len > 0:
  694. result.add newTryFinally(body, c.destroys)
  695. else:
  696. result.add body
  697. when defined(nimDebugDestroys):
  698. if true:
  699. echo "------------------------------------"
  700. echo owner.name.s, " transformed to: "
  701. echo result