injectdestructors.nim 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  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, sighashes
  131. const
  132. InterestingSyms = {skVar, skResult, skLet, skForVar, skTemp}
  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. const toDebug = ""
  145. template dbg(body) =
  146. when toDebug.len > 0:
  147. if c.owner.name.s == toDebug or toDebug == "always":
  148. body
  149. proc isLastRead(location: PNode; c: var Con; pc, comesFrom: int): int =
  150. var pc = pc
  151. while pc < c.g.len:
  152. case c.g[pc].kind
  153. of def:
  154. if defInstrTargets(c.g[pc], location):
  155. # the path lead to a redefinition of 's' --> abandon it.
  156. return high(int)
  157. inc pc
  158. of use:
  159. if useInstrTargets(c.g[pc], location):
  160. c.otherRead = c.g[pc].n
  161. return -1
  162. inc pc
  163. of goto:
  164. pc = pc + c.g[pc].dest
  165. of fork:
  166. # every branch must lead to the last read of the location:
  167. let variantA = isLastRead(location, c, pc+1, pc)
  168. if variantA < 0: return -1
  169. var variantB = isLastRead(location, c, pc + c.g[pc].dest, pc)
  170. if variantB < 0: return -1
  171. elif variantB == high(int):
  172. variantB = variantA
  173. pc = variantB
  174. of InstrKind.join:
  175. let dest = pc + c.g[pc].dest
  176. if dest == comesFrom: return pc + 1
  177. inc pc
  178. return pc
  179. proc isLastRead(n: PNode; c: var Con): bool =
  180. # first we need to search for the instruction that belongs to 'n':
  181. c.otherRead = nil
  182. var instr = -1
  183. let m = dfa.skipConvDfa(n)
  184. for i in 0..<c.g.len:
  185. # This comparison is correct and MUST not be ``instrTargets``:
  186. if c.g[i].kind == use and c.g[i].n == m:
  187. if instr < 0:
  188. instr = i
  189. break
  190. dbg:
  191. echo "starting point for ", n, " is ", instr, " ", n.kind
  192. if instr < 0: return false
  193. # we go through all paths beginning from 'instr+1' and need to
  194. # ensure that we don't find another 'use X' instruction.
  195. if instr+1 >= c.g.len: return true
  196. result = isLastRead(n, c, instr+1, -1) >= 0
  197. dbg:
  198. echo "ugh ", c.otherRead.isNil, " ", result
  199. when false:
  200. let s = n.sym
  201. var pcs: seq[int] = @[instr+1]
  202. var takenGotos: IntSet
  203. var takenForks = initIntSet()
  204. while pcs.len > 0:
  205. var pc = pcs.pop
  206. takenGotos = initIntSet()
  207. while pc < c.g.len:
  208. case c.g[pc].kind
  209. of def:
  210. if c.g[pc].sym == s:
  211. # the path lead to a redefinition of 's' --> abandon it.
  212. break
  213. inc pc
  214. of use:
  215. if c.g[pc].sym == s:
  216. c.otherRead = c.g[pc].n
  217. return false
  218. inc pc
  219. of goto:
  220. # we must leave endless loops eventually:
  221. if not takenGotos.containsOrIncl(pc):
  222. pc = pc + c.g[pc].dest
  223. else:
  224. inc pc
  225. of fork:
  226. # we follow the next instruction but push the dest onto our "work" stack:
  227. if not takenForks.containsOrIncl(pc):
  228. pcs.add pc + c.g[pc].dest
  229. inc pc
  230. of InstrKind.join:
  231. inc pc
  232. #echo c.graph.config $ n.info, " last read here!"
  233. return true
  234. proc initialized(code: ControlFlowGraph; pc: int,
  235. init, uninit: var IntSet; comesFrom: int): int =
  236. ## Computes the set of definitely initialized variables accross all code paths
  237. ## as an IntSet of IDs.
  238. var pc = pc
  239. while pc < code.len:
  240. case code[pc].kind
  241. of goto:
  242. pc = pc + code[pc].dest
  243. of fork:
  244. let target = pc + code[pc].dest
  245. var initA = initIntSet()
  246. var initB = initIntSet()
  247. let pcA = initialized(code, pc+1, initA, uninit, pc)
  248. discard initialized(code, target, initB, uninit, pc)
  249. # we add vars if they are in both branches:
  250. for v in initA:
  251. if v in initB:
  252. init.incl v
  253. pc = pcA+1
  254. of InstrKind.join:
  255. let target = pc + code[pc].dest
  256. if comesFrom == target: return pc
  257. inc pc
  258. of use:
  259. let v = code[pc].sym
  260. if v.kind != skParam and v.id notin init:
  261. # attempt to read an uninit'ed variable
  262. uninit.incl v.id
  263. inc pc
  264. of def:
  265. let v = code[pc].sym
  266. init.incl v.id
  267. inc pc
  268. return pc
  269. template interestingSym(s: PSym): bool =
  270. s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
  271. template isUnpackedTuple(s: PSym): bool =
  272. ## we move out all elements of unpacked tuples,
  273. ## hence unpacked tuples themselves don't need to be destroyed
  274. s.kind == skTemp and s.typ.kind == tyTuple
  275. proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
  276. var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
  277. if opname == "=" and ri != nil:
  278. m.add "; requires a copy because it's not the last read of '"
  279. m.add renderTree(ri)
  280. m.add '\''
  281. if c.otherRead != nil:
  282. m.add "; another read is done here: "
  283. m.add c.graph.config $ c.otherRead.info
  284. elif ri.kind == nkSym and ri.sym.kind == skParam and not isSinkType(ri.sym.typ):
  285. m.add "; try to make "
  286. m.add renderTree(ri)
  287. m.add " a 'sink' parameter"
  288. localError(c.graph.config, ri.info, errGenerated, m)
  289. proc makePtrType(c: Con, baseType: PType): PType =
  290. result = newType(tyPtr, c.owner)
  291. addSonSkipIntLit(result, baseType)
  292. proc genOp(c: Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
  293. var op = t.attachedOps[kind]
  294. if op == nil:
  295. # give up and find the canonical type instead:
  296. let h = sighashes.hashType(t, {CoType, CoConsiderOwned, CoDistinct})
  297. let canon = c.graph.canonTypes.getOrDefault(h)
  298. if canon != nil:
  299. op = canon.attachedOps[kind]
  300. if op == nil:
  301. globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
  302. "' operator not found for type " & typeToString(t))
  303. elif op.ast[genericParamsPos].kind != nkEmpty:
  304. globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
  305. "' operator is generic")
  306. if sfError in op.flags: checkForErrorPragma(c, t, ri, AttachedOpToStr[kind])
  307. let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ))
  308. addrExp.add(dest)
  309. result = newTree(nkCall, newSymNode(op), addrExp)
  310. proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
  311. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  312. let k = if t.attachedOps[attachedSink] != nil: attachedSink
  313. else: attachedAsgn
  314. if t.attachedOps[k] != nil:
  315. result = genOp(c, t, k, dest, ri)
  316. else:
  317. # in rare cases only =destroy exists but no sink or assignment
  318. # (see Pony object in tmove_objconstr.nim)
  319. # we generate a fast assignment in this case:
  320. result = newTree(nkFastAsgn, dest)
  321. proc genCopy(c: var Con; t: PType; dest, ri: PNode): PNode =
  322. if tfHasOwned in t.flags:
  323. # try to improve the error message here:
  324. if c.otherRead == nil: discard isLastRead(ri, c)
  325. checkForErrorPragma(c, t, ri, "=")
  326. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  327. result = genOp(c, t, attachedAsgn, dest, ri)
  328. proc genCopyNoCheck(c: Con; t: PType; dest, ri: PNode): PNode =
  329. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  330. result = genOp(c, t, attachedAsgn, dest, ri)
  331. proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
  332. let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
  333. result = genOp(c, t, attachedDestructor, dest, nil)
  334. proc addTopVar(c: var Con; v: PNode) =
  335. c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
  336. proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
  337. let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info)
  338. sym.typ = typ
  339. result = newSymNode(sym)
  340. c.addTopVar(result)
  341. proc p(n: PNode; c: var Con): PNode
  342. template recurse(n, dest) =
  343. for i in 0..<n.len:
  344. dest.add p(n[i], c)
  345. proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
  346. result = newNodeI(nkCall, n.info)
  347. result.add(newSymNode(createMagic(c.graph, magicname, m)))
  348. result.add n
  349. proc genWasMoved(n: PNode; c: var Con): PNode =
  350. # The mWasMoved builtin does not take the address.
  351. result = genMagicCall(n, c, "wasMoved", mWasMoved)
  352. proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
  353. result = newNodeI(nkCall, info)
  354. result.add(newSymNode(createMagic(c.graph, "default", mDefault)))
  355. result.typ = t
  356. proc destructiveMoveVar(n: PNode; c: var Con): PNode =
  357. # generate: (let tmp = v; reset(v); tmp)
  358. # XXX: Strictly speaking we can only move if there is a ``=sink`` defined
  359. # or if no ``=sink`` is defined and also no assignment.
  360. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  361. var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
  362. temp.typ = n.typ
  363. var v = newNodeI(nkLetSection, n.info)
  364. let tempAsNode = newSymNode(temp)
  365. var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
  366. vpart.sons[0] = tempAsNode
  367. vpart.sons[1] = c.emptyNode
  368. vpart.sons[2] = n
  369. add(v, vpart)
  370. result.add v
  371. result.add genWasMoved(skipConv(n), c)
  372. result.add tempAsNode
  373. proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
  374. assert s.kind == nkSym and s.sym.kind == skParam
  375. if not isLastRead(s, c):
  376. localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
  377. "` is already consumed at " & toFileLineCol(c. graph.config, s.info))
  378. proc isSinkTypeForParam(t: PType): bool =
  379. # a parameter like 'seq[owned T]' must not be used only once, but its
  380. # elements must, so we detect this case here:
  381. result = t.skipTypes({tyGenericInst, tyAlias}).kind in {tySink, tyOwned}
  382. when false:
  383. if isSinkType(t):
  384. if t.skipTypes({tyGenericInst, tyAlias}).kind in {tyArray, tyVarargs, tyOpenArray, tySequence}:
  385. result = false
  386. else:
  387. result = true
  388. proc passCopyToSink(n: PNode; c: var Con): PNode =
  389. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  390. let tmp = getTemp(c, n.typ, n.info)
  391. # XXX This is only required if we are in a loop. Since we move temporaries
  392. # out of loops we need to mark it as 'wasMoved'.
  393. result.add genWasMoved(tmp, c)
  394. if hasDestructor(n.typ):
  395. var m = genCopy(c, n.typ, tmp, n)
  396. m.add p(n, c)
  397. result.add m
  398. if isLValue(n):
  399. message(c.graph.config, n.info, hintPerformance,
  400. ("passing '$1' to a sink parameter introduces an implicit copy; " &
  401. "use 'move($1)' to prevent it") % $n)
  402. else:
  403. result.add newTree(nkAsgn, tmp, p(n, c))
  404. result.add tmp
  405. proc isDangerousSeq(t: PType): bool {.inline.} =
  406. let t = t.skipTypes(abstractInst)
  407. result = t.kind == tySequence and tfHasOwned notin t.sons[0].flags
  408. proc containsConstSeq(n: PNode): bool =
  409. if n.kind == nkBracket and n.len > 0 and n.typ != nil and isDangerousSeq(n.typ):
  410. return true
  411. result = false
  412. case n.kind
  413. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
  414. result = containsConstSeq(n[1])
  415. of nkObjConstr, nkClosure:
  416. for i in 1 ..< n.len:
  417. if containsConstSeq(n[i]): return true
  418. of nkCurly, nkBracket, nkPar, nkTupleConstr:
  419. for i in 0 ..< n.len:
  420. if containsConstSeq(n[i]): return true
  421. else: discard
  422. proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
  423. template pArgIfTyped(argPart: PNode): PNode =
  424. # typ is nil if we are in if/case expr branch with noreturn
  425. if argPart.typ == nil: p(argPart, c)
  426. else: pArg(argPart, c, isSink)
  427. if isSink:
  428. if arg.kind in nkCallKinds:
  429. # recurse but skip the call expression in order to prevent
  430. # destructor injections: Rule 5.1 is different from rule 5.4!
  431. result = copyNode(arg)
  432. let parameters = arg[0].typ
  433. let L = if parameters != nil: parameters.len else: 0
  434. result.add arg[0]
  435. for i in 1..<arg.len:
  436. result.add pArg(arg[i], c, i < L and isSinkTypeForParam(parameters[i]))
  437. elif arg.containsConstSeq:
  438. # const sequences are not mutable and so we need to pass a copy to the
  439. # sink parameter (bug #11524). Note that the string implemenation is
  440. # different and can deal with 'const string sunk into var'.
  441. result = passCopyToSink(arg, c)
  442. elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkCharLit..nkTripleStrLit}:
  443. discard "object construction to sink parameter: nothing to do"
  444. result = arg
  445. elif arg.kind == nkSym and isSinkParam(arg.sym):
  446. # Sinked params can be consumed only once. We need to reset the memory
  447. # to disable the destructor which we have not elided
  448. sinkParamIsLastReadCheck(c, arg)
  449. result = destructiveMoveVar(arg, c)
  450. elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
  451. # it is the last read, can be sinked. We need to reset the memory
  452. # to disable the destructor which we have not elided
  453. result = destructiveMoveVar(arg, c)
  454. elif arg.kind in {nkBlockExpr, nkBlockStmt}:
  455. result = copyNode(arg)
  456. result.add arg[0]
  457. result.add pArg(arg[1], c, isSink)
  458. elif arg.kind == nkStmtListExpr:
  459. result = copyNode(arg)
  460. for i in 0..arg.len-2:
  461. result.add p(arg[i], c)
  462. result.add pArg(arg[^1], c, isSink)
  463. elif arg.kind in {nkIfExpr, nkIfStmt}:
  464. result = copyNode(arg)
  465. for i in 0..<arg.len:
  466. var branch = copyNode(arg[i])
  467. if arg[i].kind in {nkElifBranch, nkElifExpr}:
  468. branch.add p(arg[i][0], c)
  469. branch.add pArgIfTyped(arg[i][1])
  470. else:
  471. branch.add pArgIfTyped(arg[i][0])
  472. result.add branch
  473. elif arg.kind == nkCaseStmt:
  474. result = copyNode(arg)
  475. result.add p(arg[0], c)
  476. for i in 1..<arg.len:
  477. var branch: PNode
  478. if arg[i].kind == nkOfbranch:
  479. branch = arg[i] # of branch conditions are constants
  480. branch[^1] = pArgIfTyped(arg[i][^1])
  481. elif arg[i].kind in {nkElifBranch, nkElifExpr}:
  482. branch = copyNode(arg[i])
  483. branch.add p(arg[i][0], c)
  484. branch.add pArgIfTyped(arg[i][1])
  485. else:
  486. branch = copyNode(arg[i])
  487. branch.add pArgIfTyped(arg[i][0])
  488. result.add branch
  489. elif isAnalysableFieldAccess(arg, c.owner) and isLastRead(arg, c):
  490. result = destructiveMoveVar(arg, c)
  491. else:
  492. # an object that is not temporary but passed to a 'sink' parameter
  493. # results in a copy.
  494. result = passCopyToSink(arg, c)
  495. else:
  496. result = p(arg, c)
  497. proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
  498. # unfortunately, this needs to be kept consistent with the cases
  499. # we handle in the 'case of' statement below:
  500. const movableNodeKinds = (nkCallKinds + {nkSym, nkTupleConstr, nkObjConstr,
  501. nkBracket, nkBracketExpr, nkNilLit})
  502. template moveOrCopyIfTyped(riPart: PNode): PNode =
  503. # typ is nil if we are in if/case expr branch with noreturn
  504. if riPart.typ == nil: p(riPart, c)
  505. else: moveOrCopy(dest, riPart, c)
  506. case ri.kind
  507. of nkCallKinds:
  508. result = genSink(c, dest.typ, dest, ri)
  509. # watch out and no not transform 'ri' twice if it's a call:
  510. let ri2 = copyNode(ri)
  511. let parameters = ri[0].typ
  512. let L = if parameters != nil: parameters.len else: 0
  513. ri2.add ri[0]
  514. for i in 1..<ri.len:
  515. ri2.add pArg(ri[i], c, i < L and isSinkTypeForParam(parameters[i]))
  516. #recurse(ri, ri2)
  517. result.add ri2
  518. of nkBracketExpr:
  519. if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
  520. # unpacking of tuple: move out the elements
  521. result = genSink(c, dest.typ, dest, ri)
  522. result.add p(ri, c)
  523. elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
  524. # Rule 3: `=sink`(x, z); wasMoved(z)
  525. var snk = genSink(c, dest.typ, dest, ri)
  526. snk.add ri
  527. result = newTree(nkStmtList, snk, genWasMoved(ri, c))
  528. else:
  529. result = genCopy(c, dest.typ, dest, ri)
  530. result.add p(ri, c)
  531. of nkStmtListExpr:
  532. result = newNodeI(nkStmtList, ri.info)
  533. for i in 0..ri.len-2:
  534. result.add p(ri[i], c)
  535. result.add moveOrCopy(dest, ri[^1], c)
  536. of nkBlockExpr, nkBlockStmt:
  537. result = newNodeI(nkBlockStmt, ri.info)
  538. result.add ri[0] ## add label
  539. result.add moveOrCopy(dest, ri[1], c)
  540. of nkIfExpr, nkIfStmt:
  541. result = newNodeI(nkIfStmt, ri.info)
  542. for i in 0..<ri.len:
  543. var branch = copyNode(ri[i])
  544. if ri[i].kind in {nkElifBranch, nkElifExpr}:
  545. branch.add p(ri[i][0], c)
  546. branch.add moveOrCopyIfTyped(ri[i][1])
  547. else:
  548. branch.add moveOrCopyIfTyped(ri[i][0])
  549. result.add branch
  550. of nkCaseStmt:
  551. result = newNodeI(nkCaseStmt, ri.info)
  552. result.add p(ri[0], c)
  553. for i in 1..<ri.len:
  554. var branch: PNode
  555. if ri[i].kind == nkOfbranch:
  556. branch = ri[i] # of branch conditions are constants
  557. branch[^1] = moveOrCopyIfTyped(ri[i][^1])
  558. elif ri[i].kind in {nkElifBranch, nkElifExpr}:
  559. branch = copyNode(ri[i])
  560. branch.add p(ri[i][0], c)
  561. branch.add moveOrCopyIfTyped(ri[i][1])
  562. else:
  563. branch = copyNode(ri[i])
  564. branch.add moveOrCopyIfTyped(ri[i][0])
  565. result.add branch
  566. of nkBracket:
  567. # array constructor
  568. if ri.len > 0 and isDangerousSeq(ri.typ):
  569. result = genCopy(c, dest.typ, dest, ri)
  570. else:
  571. result = genSink(c, dest.typ, dest, ri)
  572. let ri2 = copyTree(ri)
  573. for i in 0..<ri.len:
  574. # everything that is passed to an array constructor is consumed,
  575. # so these all act like 'sink' parameters:
  576. ri2[i] = pArg(ri[i], c, isSink = true)
  577. result.add ri2
  578. of nkObjConstr:
  579. result = genSink(c, dest.typ, dest, ri)
  580. let ri2 = copyTree(ri)
  581. for i in 1..<ri.len:
  582. # everything that is passed to an object constructor is consumed,
  583. # so these all act like 'sink' parameters:
  584. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  585. result.add ri2
  586. of nkTupleConstr, nkClosure:
  587. result = genSink(c, dest.typ, dest, ri)
  588. let ri2 = copyTree(ri)
  589. for i in ord(ri.kind == nkClosure)..<ri.len:
  590. # everything that is passed to an tuple constructor is consumed,
  591. # so these all act like 'sink' parameters:
  592. if ri[i].kind == nkExprColonExpr:
  593. ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
  594. else:
  595. ri2[i] = pArg(ri[i], c, isSink = true)
  596. result.add ri2
  597. of nkNilLit:
  598. result = genSink(c, dest.typ, dest, ri)
  599. result.add ri
  600. of nkSym:
  601. if isSinkParam(ri.sym):
  602. # Rule 3: `=sink`(x, z); wasMoved(z)
  603. sinkParamIsLastReadCheck(c, ri)
  604. var snk = genSink(c, dest.typ, dest, ri)
  605. snk.add ri
  606. result = newTree(nkStmtList, snk, genWasMoved(ri, c))
  607. elif ri.sym.kind != skParam and ri.sym.owner == c.owner and isLastRead(ri, c):
  608. # Rule 3: `=sink`(x, z); wasMoved(z)
  609. var snk = genSink(c, dest.typ, dest, ri)
  610. snk.add ri
  611. result = newTree(nkStmtList, snk, genWasMoved(ri, c))
  612. else:
  613. result = genCopy(c, dest.typ, dest, ri)
  614. result.add p(ri, c)
  615. of nkHiddenSubConv, nkHiddenStdConv:
  616. if sameType(ri.typ, ri[1].typ):
  617. result = moveOrCopy(dest, ri[1], c)
  618. elif ri[1].kind in movableNodeKinds:
  619. result = moveOrCopy(dest, ri[1], c)
  620. var b = newNodeIT(ri.kind, ri.info, ri.typ)
  621. b.add ri[0] # add empty node
  622. let L = result.len-1
  623. b.add result[L]
  624. result[L] = b
  625. else:
  626. result = genCopy(c, dest.typ, dest, ri)
  627. result.add p(ri, c)
  628. of nkObjDownConv, nkObjUpConv:
  629. if ri[0].kind in movableNodeKinds:
  630. result = moveOrCopy(dest, ri[0], c)
  631. var b = newNodeIT(ri.kind, ri.info, ri.typ)
  632. let L = result.len-1
  633. b.add result[L]
  634. result[L] = b
  635. else:
  636. result = genCopy(c, dest.typ, dest, ri)
  637. result.add p(ri, c)
  638. else:
  639. if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
  640. # Rule 3: `=sink`(x, z); wasMoved(z)
  641. var snk = genSink(c, dest.typ, dest, ri)
  642. snk.add ri
  643. result = newTree(nkStmtList, snk, genWasMoved(ri, c))
  644. else:
  645. # XXX At least string literals can be moved?
  646. result = genCopy(c, dest.typ, dest, ri)
  647. result.add p(ri, c)
  648. proc computeUninit(c: var Con) =
  649. if not c.uninitComputed:
  650. c.uninitComputed = true
  651. c.uninit = initIntSet()
  652. var init = initIntSet()
  653. discard initialized(c.g, pc = 0, init, c.uninit, comesFrom = -1)
  654. proc injectDefaultCalls(n: PNode, c: var Con) =
  655. case n.kind
  656. of nkVarSection, nkLetSection:
  657. for i in 0..<n.len:
  658. let it = n[i]
  659. let L = it.len-1
  660. let ri = it[L]
  661. if it.kind == nkIdentDefs and ri.kind == nkEmpty:
  662. computeUninit(c)
  663. for j in 0..L-2:
  664. let v = it[j]
  665. doAssert v.kind == nkSym
  666. if c.uninit.contains(v.sym.id):
  667. it[L] = genDefaultCall(v.sym.typ, c, v.info)
  668. break
  669. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  670. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  671. discard
  672. else:
  673. for i in 0..<safeLen(n):
  674. injectDefaultCalls(n[i], c)
  675. proc isCursor(n: PNode): bool {.inline.} =
  676. result = n.kind == nkSym and sfCursor in n.sym.flags
  677. proc keepVar(n, it: PNode, c: var Con): PNode =
  678. # keep the var but transform 'ri':
  679. result = copyNode(n)
  680. var itCopy = copyNode(it)
  681. for j in 0..it.len-2:
  682. itCopy.add it[j]
  683. itCopy.add p(it[it.len-1], c)
  684. result.add itCopy
  685. proc p(n: PNode; c: var Con): PNode =
  686. case n.kind
  687. of nkVarSection, nkLetSection:
  688. discard "transform; var x = y to var x; x op y where op is a move or copy"
  689. result = newNodeI(nkStmtList, n.info)
  690. for i in 0..<n.len:
  691. let it = n[i]
  692. let L = it.len
  693. let ri = it[L-1]
  694. if it.kind == nkVarTuple and hasDestructor(ri.typ):
  695. let x = lowerTupleUnpacking(c.graph, it, c.owner)
  696. result.add p(x, c)
  697. elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]):
  698. for j in 0..L-3:
  699. let v = it[j]
  700. if v.kind == nkSym:
  701. if sfCompileTime in v.sym.flags: continue
  702. # move the variable declaration to the top of the frame:
  703. c.addTopVar v
  704. # make sure it's destroyed at the end of the proc:
  705. if not isUnpackedTuple(it[0].sym):
  706. c.destroys.add genDestroy(c, v.typ, v)
  707. if ri.kind != nkEmpty:
  708. let r = moveOrCopy(v, ri, c)
  709. result.add r
  710. else:
  711. result.add keepVar(n, it, c)
  712. of nkCallKinds:
  713. let parameters = n[0].typ
  714. let L = if parameters != nil: parameters.len else: 0
  715. for i in 1 ..< n.len:
  716. n.sons[i] = pArg(n[i], c, i < L and isSinkTypeForParam(parameters[i]))
  717. if n.typ != nil and hasDestructor(n.typ):
  718. discard "produce temp creation"
  719. result = newNodeIT(nkStmtListExpr, n.info, n.typ)
  720. let tmp = getTemp(c, n.typ, n.info)
  721. var sinkExpr = genSink(c, n.typ, tmp, n)
  722. sinkExpr.add n
  723. result.add sinkExpr
  724. result.add tmp
  725. c.destroys.add genDestroy(c, n.typ, tmp)
  726. else:
  727. result = n
  728. of nkAsgn, nkFastAsgn:
  729. if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda}:
  730. result = moveOrCopy(n[0], n[1], c)
  731. else:
  732. result = copyNode(n)
  733. recurse(n, result)
  734. of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
  735. nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
  736. result = n
  737. of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv:
  738. result = copyNode(n)
  739. # Destination type
  740. result.add n[0]
  741. # Analyse the inner expression
  742. result.add p(n[1], c)
  743. of nkWhen:
  744. # This should be a "when nimvm" node.
  745. result = copyTree(n)
  746. result[1][0] = p(result[1][0], c)
  747. of nkRaiseStmt:
  748. if optNimV2 in c.graph.config.globalOptions and n[0].kind != nkEmpty:
  749. if n[0].kind in nkCallKinds:
  750. let call = copyNode(n[0])
  751. recurse(n[0], call)
  752. result = copyNode(n)
  753. result.add call
  754. else:
  755. let t = n[0].typ
  756. let tmp = getTemp(c, t, n.info)
  757. var m = genCopyNoCheck(c, t, tmp, n[0])
  758. m.add p(n[0], c)
  759. result = newTree(nkStmtList, genWasMoved(tmp, c), m)
  760. var toDisarm = n[0]
  761. if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon
  762. if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner:
  763. result.add genWasMoved(toDisarm, c)
  764. result.add newTree(nkRaiseStmt, tmp)
  765. else:
  766. result = copyNode(n)
  767. recurse(n, result)
  768. else:
  769. result = copyNode(n)
  770. recurse(n, result)
  771. proc extractDestroysForTemporaries(c: Con, destroys: PNode): PNode =
  772. result = newNodeI(nkStmtList, destroys.info)
  773. for i in 0 ..< destroys.len:
  774. if destroys[i][1][0].sym.kind == skTemp:
  775. result.add destroys[i]
  776. destroys[i] = c.emptyNode
  777. proc reverseDestroys(destroys: PNode) =
  778. var reversed: seq[PNode]
  779. for i in countdown(destroys.len - 1, 0):
  780. reversed.add(destroys[i])
  781. destroys.sons = reversed
  782. proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
  783. if sfGeneratedOp in owner.flags or isInlineIterator(owner): return n
  784. var c: Con
  785. c.owner = owner
  786. c.destroys = newNodeI(nkStmtList, n.info)
  787. c.topLevelVars = newNodeI(nkVarSection, n.info)
  788. c.graph = g
  789. c.emptyNode = newNodeI(nkEmpty, n.info)
  790. let cfg = constructCfg(owner, n)
  791. shallowCopy(c.g, cfg)
  792. c.jumpTargets = initIntSet()
  793. for i in 0..<c.g.len:
  794. if c.g[i].kind in {goto, fork}:
  795. c.jumpTargets.incl(i+c.g[i].dest)
  796. dbg:
  797. echo "injecting into ", n
  798. echoCfg(c.g)
  799. if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
  800. let params = owner.typ.n
  801. for i in 1 ..< params.len:
  802. let param = params[i].sym
  803. if isSinkTypeForParam(param.typ) and hasDestructor(param.typ.skipTypes({tySink})):
  804. c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
  805. #if optNimV2 in c.graph.config.globalOptions:
  806. # injectDefaultCalls(n, c)
  807. let body = p(n, c)
  808. result = newNodeI(nkStmtList, n.info)
  809. if c.topLevelVars.len > 0:
  810. result.add c.topLevelVars
  811. if c.destroys.len > 0:
  812. reverseDestroys(c.destroys)
  813. if owner.kind == skModule:
  814. result.add newTryFinally(body, extractDestroysForTemporaries(c, c.destroys))
  815. g.globalDestructors.add c.destroys
  816. else:
  817. result.add newTryFinally(body, c.destroys)
  818. else:
  819. result.add body
  820. dbg:
  821. echo "------------------------------------"
  822. echo owner.name.s, " transformed to: "
  823. echo result