writetracking.nim 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements the write tracking analysis. Read my block post for
  10. ## a basic description of the algorithm and ideas.
  11. ## The algorithm operates in 2 phases:
  12. ##
  13. ## * Collecting information about assignments (and pass-by-var calls).
  14. ## * Computing an aliasing relation based on the assignments. This relation
  15. ## is then used to compute the 'writes' and 'escapes' effects.
  16. import intsets, idents, ast, astalgo, trees, renderer, msgs, types, options,
  17. lineinfos
  18. const
  19. debug = false
  20. type
  21. AssignToResult = enum
  22. asgnNil, # 'nil' is fine
  23. asgnNew, # 'new(result)'
  24. asgnOther # result = fooBar # not a 'new' --> 'result' might not 'new'
  25. NewLocation = enum
  26. newNone,
  27. newLit,
  28. newCall
  29. RootInfo = enum
  30. rootIsResultOrParam,
  31. rootIsHeapAccess,
  32. rootIsSym,
  33. markAsWrittenTo,
  34. markAsEscaping
  35. Assignment = object # \
  36. # Note that the transitive closures MUST be computed in
  37. # phase 2 of the algorithm.
  38. dest, src: seq[ptr TSym] # we use 'ptr' here to save RC ops and GC cycles
  39. destNoTc, srcNoTc: int # length of 'dest', 'src' without the
  40. # transitive closure
  41. destInfo: set[RootInfo]
  42. info: TLineInfo
  43. W = object # WriteTrackContext
  44. owner: PSym
  45. returnsNew: AssignToResult # assignments to 'result'
  46. assignments: seq[Assignment] # list of all assignments in this proc
  47. proc allRoots(n: PNode; result: var seq[ptr TSym]; info: var set[RootInfo]) =
  48. case n.kind
  49. of nkSym:
  50. if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}:
  51. if n.sym.kind in {skResult, skParam}: incl(info, rootIsResultOrParam)
  52. result.add(cast[ptr TSym](n.sym))
  53. of nkHiddenDeref, nkDerefExpr:
  54. incl(info, rootIsHeapAccess)
  55. allRoots(n.sons[0], result, info)
  56. of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr,
  57. nkHiddenAddr, nkObjUpConv, nkObjDownConv:
  58. allRoots(n.sons[0], result, info)
  59. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv,
  60. nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch,
  61. nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast:
  62. allRoots(n.lastSon, result, info)
  63. of nkCallKinds:
  64. if getMagic(n) == mSlice:
  65. allRoots(n.sons[1], result, info)
  66. else:
  67. # we do significantly better here by using the available escape
  68. # information:
  69. if n.sons[0].typ.isNil: return
  70. var typ = n.sons[0].typ
  71. if typ != nil:
  72. typ = skipTypes(typ, abstractInst)
  73. if typ.kind != tyProc: typ = nil
  74. else: assert(sonsLen(typ) == sonsLen(typ.n))
  75. for i in 1 ..< n.len:
  76. let it = n.sons[i]
  77. if typ != nil and i < sonsLen(typ):
  78. assert(typ.n.sons[i].kind == nkSym)
  79. let paramType = typ.n.sons[i]
  80. if paramType.typ.isCompileTimeOnly: continue
  81. if sfEscapes in paramType.sym.flags or paramType.typ.kind == tyVar:
  82. allRoots(it, result, info)
  83. else:
  84. allRoots(it, result, info)
  85. else:
  86. for i in 0..<n.safeLen:
  87. allRoots(n.sons[i], result, info)
  88. proc addAsgn(a: var Assignment; dest, src: PNode; destInfo: set[RootInfo]) =
  89. a.dest = @[]
  90. a.src = @[]
  91. a.destInfo = destInfo
  92. allRoots(dest, a.dest, a.destInfo)
  93. if dest.kind == nkSym: incl(a.destInfo, rootIsSym)
  94. if src != nil:
  95. var dummy: set[RootInfo]
  96. allRoots(src, a.src, dummy)
  97. a.destNoTc = a.dest.len
  98. a.srcNoTc = a.src.len
  99. a.info = dest.info
  100. #echo "ADDING ", dest.info, " ", a.destInfo
  101. proc srcHasSym(a: Assignment; x: ptr TSym): bool =
  102. for i in 0 ..< a.srcNoTc:
  103. if a.src[i] == x: return true
  104. proc returnsNewExpr*(n: PNode): NewLocation =
  105. case n.kind
  106. of nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
  107. nkFloatLit..nkFloat64Lit, nkNilLit:
  108. result = newLit
  109. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv,
  110. nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch,
  111. nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast:
  112. result = returnsNewExpr(n.lastSon)
  113. of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure,
  114. nkIfExpr, nkIfStmt, nkWhenStmt, nkCaseStmt, nkTryStmt:
  115. result = newLit
  116. for i in ord(n.kind == nkObjConstr) ..< n.len:
  117. let x = returnsNewExpr(n.sons[i])
  118. case x
  119. of newNone: return newNone
  120. of newLit: discard
  121. of newCall: result = newCall
  122. of nkCallKinds:
  123. if n.sons[0].typ != nil and tfReturnsNew in n.sons[0].typ.flags:
  124. result = newCall
  125. else:
  126. result = newNone
  127. proc deps(w: var W; dest, src: PNode; destInfo: set[RootInfo]) =
  128. # let x = (localA, localB)
  129. # compute 'returnsNew' property:
  130. let retNew = if src.isNil: newNone else: returnsNewExpr(src)
  131. if dest.kind == nkSym and dest.sym.kind == skResult:
  132. if retNew != newNone:
  133. if w.returnsNew != asgnOther: w.returnsNew = asgnNew
  134. else:
  135. w.returnsNew = asgnOther
  136. # mark the dependency, but
  137. # rule out obviously innocent assignments like 'somebool = true'
  138. if dest.kind == nkSym and retNew == newLit: discard
  139. else:
  140. let L = w.assignments.len
  141. w.assignments.setLen(L+1)
  142. addAsgn(w.assignments[L], dest, src, destInfo)
  143. proc depsArgs(w: var W; n: PNode) =
  144. if n.sons[0].typ.isNil: return
  145. var typ = skipTypes(n.sons[0].typ, abstractInst)
  146. if typ.kind != tyProc: return
  147. # echo n.info, " ", n, " ", w.owner.name.s, " ", typeToString(typ)
  148. assert(sonsLen(typ) == sonsLen(typ.n))
  149. for i in 1 ..< n.len:
  150. let it = n.sons[i]
  151. if i < sonsLen(typ):
  152. assert(typ.n.sons[i].kind == nkSym)
  153. let paramType = typ.n.sons[i]
  154. if paramType.typ.isCompileTimeOnly: continue
  155. var destInfo: set[RootInfo] = {}
  156. if sfWrittenTo in paramType.sym.flags or paramType.typ.kind == tyVar:
  157. # p(f(x, y), X, g(h, z))
  158. destInfo.incl markAsWrittenTo
  159. if sfEscapes in paramType.sym.flags:
  160. destInfo.incl markAsEscaping
  161. if destInfo != {}:
  162. deps(w, it, nil, destInfo)
  163. proc deps(w: var W; n: PNode) =
  164. case n.kind
  165. of nkLetSection, nkVarSection:
  166. for child in n:
  167. let last = lastSon(child)
  168. if last.kind == nkEmpty: continue
  169. if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
  170. if child.len-2 != last.len: return
  171. for i in 0 .. child.len-3:
  172. deps(w, child.sons[i], last.sons[i], {})
  173. else:
  174. for i in 0 .. child.len-3:
  175. deps(w, child.sons[i], last, {})
  176. of nkAsgn, nkFastAsgn:
  177. deps(w, n.sons[0], n.sons[1], {})
  178. else:
  179. for i in 0 ..< n.safeLen:
  180. deps(w, n.sons[i])
  181. if n.kind in nkCallKinds:
  182. if getMagic(n) in {mNew, mNewFinalize, mNewSeq}:
  183. # may not look like an assignment, but it is:
  184. deps(w, n.sons[1], newNodeIT(nkObjConstr, n.info, n.sons[1].typ), {})
  185. else:
  186. depsArgs(w, n)
  187. proc possibleAliases(w: var W; result: var seq[ptr TSym]) =
  188. # this is an expensive fixpoint iteration. We could speed up this analysis
  189. # by a smarter data-structure but we wait until profiling shows us it's
  190. # expensive. Usually 'w.assignments' is small enough.
  191. var alreadySeen = initIntSet()
  192. template addNoDup(x) =
  193. if not alreadySeen.containsOrIncl(x.id): result.add x
  194. for x in result: alreadySeen.incl x.id
  195. var todo = 0
  196. while todo < result.len:
  197. let x = result[todo]
  198. inc todo
  199. for i in 0..<len(w.assignments):
  200. let a = addr(w.assignments[i])
  201. #if a.srcHasSym(x):
  202. # # y = f(..., x, ...)
  203. # for i in 0 ..< a.destNoTc: addNoDup a.dest[i]
  204. if a.destNoTc > 0 and a.dest[0] == x and rootIsSym in a.destInfo:
  205. # x = f(..., y, ....)
  206. for i in 0 ..< a.srcNoTc: addNoDup a.src[i]
  207. proc markWriteOrEscape(w: var W; conf: ConfigRef) =
  208. ## Both 'writes' and 'escapes' effects ultimately only care
  209. ## about *parameters*.
  210. ## However, due to aliasing, even locals that might not look as parameters
  211. ## have to count as parameters if they can alias a parameter:
  212. ##
  213. ## .. code-block:: nim
  214. ## proc modifies(n: Node) {.writes: [n].} =
  215. ## let x = n
  216. ## x.data = "abc"
  217. ##
  218. ## We call a symbol *parameter-like* if it is a parameter or can alias a
  219. ## parameter.
  220. ## Let ``p``, ``q`` be *parameter-like* and ``x``, ``y`` be general
  221. ## expressions.
  222. ##
  223. ## A write then looks like ``p[] = x``.
  224. ## An escape looks like ``p[] = q`` or more generally
  225. ## like ``p[] = f(q)`` where ``f`` can forward ``q``.
  226. for i in 0..<len(w.assignments):
  227. let a = addr(w.assignments[i])
  228. if a.destInfo != {}:
  229. possibleAliases(w, a.dest)
  230. if {rootIsHeapAccess, markAsWrittenTo} * a.destInfo != {}:
  231. for p in a.dest:
  232. if p.kind == skParam and p.owner == w.owner:
  233. incl(p.flags, sfWrittenTo)
  234. if w.owner.kind == skFunc and p.typ.kind != tyVar:
  235. localError(conf, a.info, "write access to non-var parameter: " & p.name.s)
  236. if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}:
  237. var destIsParam = false
  238. for p in a.dest:
  239. if p.kind in {skResult, skParam} and p.owner == w.owner:
  240. destIsParam = true
  241. break
  242. if destIsParam:
  243. possibleAliases(w, a.src)
  244. for p in a.src:
  245. if p.kind == skParam and p.owner == w.owner:
  246. incl(p.flags, sfEscapes)
  247. proc trackWrites*(owner: PSym; body: PNode; conf: ConfigRef) =
  248. var w: W
  249. w.owner = owner
  250. w.assignments = @[]
  251. # Phase 1: Collect and preprocess any assignments in the proc body:
  252. deps(w, body)
  253. # Phase 2: Compute the 'writes' and 'escapes' effects:
  254. markWriteOrEscape(w, conf)
  255. if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and
  256. containsGarbageCollectedRef(owner.typ.sons[0]):
  257. incl(owner.typ.flags, tfReturnsNew)