varpartitions.nim 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Partition variables into different graphs. Used for
  10. ## Nim's write tracking, borrow checking and also for the
  11. ## cursor inference.
  12. ## The algorithm is a reinvention / variation of Steensgaard's
  13. ## algorithm.
  14. ## The used data structure is "union find" with path compression.
  15. ## We perform two passes over the AST:
  16. ## - Pass one (``computeLiveRanges``): collect livetimes of local
  17. ## variables and whether they are potentially re-assigned.
  18. ## - Pass two (``traverse``): combine local variables to abstract "graphs".
  19. ## Strict func checking: Ensure that graphs that are connected to
  20. ## const parameters are not mutated.
  21. ## Cursor inference: Ensure that potential cursors are not
  22. ## borrowed from locations that are connected to a graph
  23. ## that is mutated during the liveness of the cursor.
  24. ## (We track all possible mutations of a graph.)
  25. ##
  26. ## See https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm
  27. ## for a high-level description of how borrow checking works.
  28. import ast, types, lineinfos, options, msgs, renderer, typeallowed
  29. from trees import getMagic, isNoSideEffectPragma, stupidStmtListExpr
  30. from isolation_check import canAlias
  31. type
  32. AbstractTime = distinct int
  33. const
  34. MaxTime = AbstractTime high(int)
  35. MinTime = AbstractTime(-1)
  36. proc `<=`(a, b: AbstractTime): bool {.borrow.}
  37. proc `<`(a, b: AbstractTime): bool {.borrow.}
  38. proc inc(x: var AbstractTime; diff = 1) {.borrow.}
  39. proc dec(x: var AbstractTime; diff = 1) {.borrow.}
  40. proc `$`(x: AbstractTime): string {.borrow.}
  41. type
  42. SubgraphFlag = enum
  43. isMutated, # graph might be mutated
  44. isMutatedDirectly, # graph is mutated directly by a non-var parameter.
  45. connectsConstParam # graph is connected to a non-var parameter.
  46. VarFlag = enum
  47. ownsData,
  48. preventCursor,
  49. isReassigned,
  50. viewDoesMutate,
  51. viewBorrowsFromConst
  52. VarIndexKind = enum
  53. isEmptyRoot,
  54. dependsOn,
  55. isRootOf
  56. Connection = object
  57. case kind: VarIndexKind
  58. of isEmptyRoot: discard
  59. of dependsOn: parent: int
  60. of isRootOf: graphIndex: int
  61. VarIndex = object
  62. con: Connection
  63. flags: set[VarFlag]
  64. sym: PSym
  65. reassignedTo: int
  66. aliveStart, aliveEnd: AbstractTime # the range for which the variable is alive.
  67. borrowsFrom: seq[int] # indexes into Partitions.s
  68. MutationInfo* = object
  69. param: PSym
  70. mutatedHere, connectedVia: TLineInfo
  71. flags: set[SubgraphFlag]
  72. maxMutation, minConnection: AbstractTime
  73. mutations: seq[AbstractTime]
  74. Goal* = enum
  75. constParameters,
  76. borrowChecking,
  77. cursorInference
  78. Partitions* = object
  79. abstractTime: AbstractTime
  80. s: seq[VarIndex]
  81. graphs: seq[MutationInfo]
  82. goals: set[Goal]
  83. unanalysableMutation: bool
  84. inAsgnSource, inConstructor, inNoSideEffectSection: int
  85. owner: PSym
  86. config: ConfigRef
  87. proc mutationAfterConnection(g: MutationInfo): bool {.inline.} =
  88. #echo g.maxMutation.int, " ", g.minConnection.int, " ", g.param
  89. g.maxMutation > g.minConnection
  90. proc `$`*(config: ConfigRef; g: MutationInfo): string =
  91. result = ""
  92. if g.flags * {isMutated, connectsConstParam} == {isMutated, connectsConstParam}:
  93. result.add "\nan object reachable from '"
  94. result.add g.param.name.s
  95. result.add "' is potentially mutated"
  96. if g.mutatedHere != unknownLineInfo:
  97. result.add "\n"
  98. result.add config $ g.mutatedHere
  99. result.add " the mutation is here"
  100. if g.connectedVia != unknownLineInfo:
  101. result.add "\n"
  102. result.add config $ g.connectedVia
  103. result.add " is the statement that connected the mutation to the parameter"
  104. proc hasSideEffect*(c: var Partitions; info: var MutationInfo): bool =
  105. for g in mitems c.graphs:
  106. if g.flags * {isMutated, connectsConstParam} == {isMutated, connectsConstParam} and
  107. (mutationAfterConnection(g) or isMutatedDirectly in g.flags):
  108. info = g
  109. return true
  110. return false
  111. template isConstParam(a): bool = a.kind == skParam and a.typ.kind notin {tyVar, tySink}
  112. proc variableId(c: Partitions; x: PSym): int =
  113. for i in 0 ..< c.s.len:
  114. if c.s[i].sym == x: return i
  115. return -1
  116. proc registerResult(c: var Partitions; n: PNode) =
  117. if n.kind == nkSym:
  118. c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: n.sym, reassignedTo: 0,
  119. aliveStart: MaxTime, aliveEnd: c.abstractTime)
  120. proc registerParam(c: var Partitions; n: PNode) =
  121. assert n.kind == nkSym
  122. if isConstParam(n.sym):
  123. c.s.add VarIndex(con: Connection(kind: isRootOf, graphIndex: c.graphs.len),
  124. sym: n.sym, reassignedTo: 0,
  125. aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
  126. c.graphs.add MutationInfo(param: n.sym, mutatedHere: unknownLineInfo,
  127. connectedVia: unknownLineInfo, flags: {connectsConstParam},
  128. maxMutation: MinTime, minConnection: MaxTime,
  129. mutations: @[])
  130. else:
  131. c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: n.sym, reassignedTo: 0,
  132. aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
  133. proc registerVariable(c: var Partitions; n: PNode) =
  134. if n.kind == nkSym and variableId(c, n.sym) < 0:
  135. c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: n.sym, reassignedTo: 0,
  136. aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
  137. proc root(v: var Partitions; start: int): int =
  138. result = start
  139. var depth = 0
  140. while v.s[result].con.kind == dependsOn:
  141. result = v.s[result].con.parent
  142. inc depth
  143. if depth > 0:
  144. # path compression:
  145. var it = start
  146. while v.s[it].con.kind == dependsOn:
  147. let next = v.s[it].con.parent
  148. v.s[it].con = Connection(kind: dependsOn, parent: result)
  149. it = next
  150. proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
  151. let id = variableId(v, s)
  152. if id >= 0:
  153. let r = root(v, id)
  154. let flags = if s.kind == skParam and isConstParam(s):
  155. {isMutated, isMutatedDirectly}
  156. else:
  157. {isMutated}
  158. case v.s[r].con.kind
  159. of isEmptyRoot:
  160. v.s[r].con = Connection(kind: isRootOf, graphIndex: v.graphs.len)
  161. v.graphs.add MutationInfo(param: if isConstParam(s): s else: nil, mutatedHere: info,
  162. connectedVia: unknownLineInfo, flags: flags,
  163. maxMutation: v.abstractTime, minConnection: MaxTime,
  164. mutations: @[v.abstractTime])
  165. of isRootOf:
  166. let g = addr v.graphs[v.s[r].con.graphIndex]
  167. if g.param == nil and isConstParam(s):
  168. g.param = s
  169. if v.abstractTime > g.maxMutation:
  170. g.mutatedHere = info
  171. g.maxMutation = v.abstractTime
  172. g.flags.incl flags
  173. g.mutations.add v.abstractTime
  174. else:
  175. assert false, "cannot happen"
  176. else:
  177. v.unanalysableMutation = true
  178. proc connect(v: var Partitions; a, b: PSym; info: TLineInfo) =
  179. let aid = variableId(v, a)
  180. if aid < 0:
  181. return
  182. let bid = variableId(v, b)
  183. if bid < 0:
  184. return
  185. let ra = root(v, aid)
  186. let rb = root(v, bid)
  187. if ra != rb:
  188. var param = PSym(nil)
  189. if isConstParam(a): param = a
  190. elif isConstParam(b): param = b
  191. let paramFlags =
  192. if param != nil:
  193. {connectsConstParam}
  194. else:
  195. {}
  196. # for now we always make 'rb' the slave and 'ra' the master:
  197. var rbFlags: set[SubgraphFlag] = {}
  198. var mutatedHere = unknownLineInfo
  199. var mut = AbstractTime 0
  200. var con = v.abstractTime
  201. var gb: ptr MutationInfo = nil
  202. if v.s[rb].con.kind == isRootOf:
  203. gb = addr v.graphs[v.s[rb].con.graphIndex]
  204. if param == nil: param = gb.param
  205. mutatedHere = gb.mutatedHere
  206. rbFlags = gb.flags
  207. mut = gb.maxMutation
  208. con = min(con, gb.minConnection)
  209. v.s[rb].con = Connection(kind: dependsOn, parent: ra)
  210. case v.s[ra].con.kind
  211. of isEmptyRoot:
  212. v.s[ra].con = Connection(kind: isRootOf, graphIndex: v.graphs.len)
  213. v.graphs.add MutationInfo(param: param, mutatedHere: mutatedHere,
  214. connectedVia: info, flags: paramFlags + rbFlags,
  215. maxMutation: mut, minConnection: con,
  216. mutations: if gb != nil: gb.mutations else: @[])
  217. of isRootOf:
  218. var g = addr v.graphs[v.s[ra].con.graphIndex]
  219. if g.param == nil: g.param = param
  220. if g.mutatedHere == unknownLineInfo: g.mutatedHere = mutatedHere
  221. g.minConnection = min(g.minConnection, con)
  222. g.connectedVia = info
  223. g.flags.incl paramFlags + rbFlags
  224. if gb != nil:
  225. g.mutations.add gb.mutations
  226. else:
  227. assert false, "cannot happen"
  228. proc borrowFromConstExpr(n: PNode): bool =
  229. case n.kind
  230. of nkCharLit..nkNilLit:
  231. result = true
  232. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv,
  233. nkCast, nkObjUpConv, nkObjDownConv:
  234. result = borrowFromConstExpr(n.lastSon)
  235. of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkRange:
  236. result = true
  237. for i in ord(n.kind == nkObjConstr)..<n.len:
  238. if not borrowFromConstExpr(n[i]): return false
  239. of nkCallKinds:
  240. if getMagic(n) == mArrToSeq:
  241. result = true
  242. for i in 1..<n.len:
  243. if not borrowFromConstExpr(n[i]): return false
  244. else: discard
  245. proc pathExpr(node: PNode; owner: PSym): PNode =
  246. #[ From the spec:
  247. - ``source`` itself is a path expression.
  248. - Container access like ``e[i]`` is a path expression.
  249. - Tuple access ``e[0]`` is a path expression.
  250. - Object field access ``e.field`` is a path expression.
  251. - ``system.toOpenArray(e, ...)`` is a path expression.
  252. - Pointer dereference ``e[]`` is a path expression.
  253. - An address ``addr e``, ``unsafeAddr e`` is a path expression.
  254. - A type conversion ``T(e)`` is a path expression.
  255. - A cast expression ``cast[T](e)`` is a path expression.
  256. - ``f(e, ...)`` is a path expression if ``f``'s return type is a view type.
  257. Because the view can only have been borrowed from ``e``, we then know
  258. that owner of ``f(e, ...)`` is ``e``.
  259. Returns the owner of the path expression. Returns ``nil``
  260. if it is not a valid path expression.
  261. ]#
  262. var n = node
  263. result = nil
  264. while true:
  265. case n.kind
  266. of nkSym:
  267. case n.sym.kind
  268. of skParam, skTemp, skResult, skForVar:
  269. if n.sym.owner == owner: result = n
  270. of skVar:
  271. if n.sym.owner == owner or sfThread in n.sym.flags: result = n
  272. of skLet, skConst:
  273. if n.sym.owner == owner or {sfThread, sfGlobal} * n.sym.flags != {}:
  274. result = n
  275. else:
  276. discard
  277. break
  278. of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref,
  279. nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
  280. n = n[0]
  281. of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast,
  282. nkObjUpConv, nkObjDownConv:
  283. n = n.lastSon
  284. of nkStmtList, nkStmtListExpr:
  285. if n.len > 0 and stupidStmtListExpr(n):
  286. n = n.lastSon
  287. else:
  288. break
  289. of nkCallKinds:
  290. if n.len > 1:
  291. if (n.typ != nil and classifyViewType(n.typ) != noView) or getMagic(n) == mSlice:
  292. n = n[1]
  293. else:
  294. break
  295. else:
  296. break
  297. else:
  298. break
  299. # borrowFromConstExpr(n) is correct here because we need 'node'
  300. # stripped off the path suffixes:
  301. if result == nil and borrowFromConstExpr(n):
  302. result = n
  303. proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
  304. case n.kind
  305. of nkSym:
  306. if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}:
  307. result.add(n.sym)
  308. of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref,
  309. nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
  310. if followDotExpr:
  311. allRoots(n[0], result, followDotExpr)
  312. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv,
  313. nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkCast,
  314. nkObjUpConv, nkObjDownConv:
  315. if n.len > 0:
  316. allRoots(n.lastSon, result, followDotExpr)
  317. of nkCaseStmt, nkObjConstr:
  318. for i in 1..<n.len:
  319. allRoots(n[i].lastSon, result, followDotExpr)
  320. of nkIfStmt, nkIfExpr:
  321. for i in 0..<n.len:
  322. allRoots(n[i].lastSon, result, followDotExpr)
  323. of nkBracket, nkTupleConstr, nkPar:
  324. for i in 0..<n.len:
  325. allRoots(n[i], result, followDotExpr)
  326. of nkCallKinds:
  327. if n.typ != nil and n.typ.kind in {tyVar, tyLent}:
  328. if n.len > 1:
  329. allRoots(n[1], result, followDotExpr)
  330. else:
  331. let m = getMagic(n)
  332. case m
  333. of mNone:
  334. if n[0].typ.isNil: return
  335. var typ = n[0].typ
  336. if typ != nil:
  337. typ = skipTypes(typ, abstractInst)
  338. if typ.kind != tyProc: typ = nil
  339. else: assert(typ.len == typ.n.len)
  340. for i in 1 ..< n.len:
  341. let it = n[i]
  342. if typ != nil and i < typ.len:
  343. assert(typ.n[i].kind == nkSym)
  344. let paramType = typ.n[i].typ
  345. if not paramType.isCompileTimeOnly and not typ.sons[0].isEmptyType and
  346. canAlias(paramType, typ.sons[0]):
  347. allRoots(it, result, followDotExpr)
  348. else:
  349. allRoots(it, result, followDotExpr)
  350. of mSlice:
  351. allRoots(n[1], result, followDotExpr)
  352. else:
  353. discard "harmless operation"
  354. else:
  355. discard "nothing to do"
  356. proc destMightOwn(c: var Partitions; dest: var VarIndex; n: PNode) =
  357. ## Analyse if 'n' is an expression that owns the data, if so mark 'dest'
  358. ## with 'ownsData'.
  359. if n.typ == nil: return
  360. case n.kind
  361. of nkEmpty, nkCharLit..nkNilLit:
  362. # primitive literals including the empty are harmless:
  363. discard
  364. of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
  365. destMightOwn(c, dest, n[1])
  366. of nkIfStmt, nkIfExpr:
  367. for i in 0..<n.len:
  368. destMightOwn(c, dest, n[i].lastSon)
  369. of nkCaseStmt:
  370. for i in 1..<n.len:
  371. destMightOwn(c, dest, n[i].lastSon)
  372. of nkStmtList, nkStmtListExpr:
  373. if n.len > 0:
  374. destMightOwn(c, dest, n[^1])
  375. of nkClosure:
  376. for i in 1..<n.len:
  377. destMightOwn(c, dest, n[i])
  378. # you must destroy a closure:
  379. dest.flags.incl ownsData
  380. of nkObjConstr:
  381. for i in 1..<n.len:
  382. destMightOwn(c, dest, n[i])
  383. if hasDestructor(n.typ):
  384. # you must destroy a ref object:
  385. dest.flags.incl ownsData
  386. of nkCurly, nkBracket, nkPar, nkTupleConstr:
  387. inc c.inConstructor
  388. for son in n:
  389. destMightOwn(c, dest, son)
  390. dec c.inConstructor
  391. if n.typ.skipTypes(abstractInst).kind == tySequence:
  392. # you must destroy a sequence:
  393. dest.flags.incl ownsData
  394. of nkSym:
  395. if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
  396. if n.sym.flags * {sfThread, sfGlobal} != {}:
  397. # aliasing a global is inherently dangerous:
  398. dest.flags.incl ownsData
  399. else:
  400. # otherwise it's just a dependency, nothing to worry about:
  401. connect(c, dest.sym, n.sym, n.info)
  402. # but a construct like ``[symbol]`` is dangerous:
  403. if c.inConstructor > 0: dest.flags.incl ownsData
  404. of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
  405. nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
  406. destMightOwn(c, dest, n[0])
  407. of nkCallKinds:
  408. if hasDestructor(n.typ):
  409. # calls do construct, what we construct must be destroyed,
  410. # so dest cannot be a cursor:
  411. dest.flags.incl ownsData
  412. elif n.typ.kind in {tyLent, tyVar}:
  413. # we know the result is derived from the first argument:
  414. var roots: seq[PSym]
  415. allRoots(n[1], roots)
  416. for r in roots:
  417. connect(c, dest.sym, r, n[1].info)
  418. else:
  419. let magic = if n[0].kind == nkSym: n[0].sym.magic else: mNone
  420. # this list is subtle, we try to answer the question if after 'dest = f(src)'
  421. # there is a connection betwen 'src' and 'dest' so that mutations to 'src'
  422. # also reflect 'dest':
  423. if magic in {mNone, mMove, mSlice, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mArrToSeq}:
  424. for i in 1..<n.len:
  425. # we always have to assume a 'select(...)' like mechanism.
  426. # But at least we do filter out simple POD types from the
  427. # list of dependencies via the 'hasDestructor' check for
  428. # the root's symbol.
  429. if hasDestructor(n[i].typ.skipTypes({tyVar, tySink, tyLent, tyGenericInst, tyAlias})):
  430. destMightOwn(c, dest, n[i])
  431. else:
  432. # something we cannot handle:
  433. dest.flags.incl preventCursor
  434. proc noCursor(c: var Partitions, s: PSym) =
  435. let vid = variableId(c, s)
  436. if vid >= 0:
  437. c.s[vid].flags.incl preventCursor
  438. proc pretendOwnsData(c: var Partitions, s: PSym) =
  439. let vid = variableId(c, s)
  440. if vid >= 0:
  441. c.s[vid].flags.incl ownsData
  442. const
  443. explainCursors = false
  444. proc isConstSym(s: PSym): bool =
  445. result = s.kind in {skConst, skLet} or isConstParam(s)
  446. proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) =
  447. const
  448. url = "see https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm-path-expressions for details"
  449. let s = pathExpr(src, c.owner)
  450. if s == nil:
  451. localError(c.config, src.info, "cannot borrow from " & $src & ", it is not a path expression; " & url)
  452. elif s.kind == nkSym:
  453. if dest.kind == skResult:
  454. if s.sym.kind != skParam or s.sym.position != 0:
  455. localError(c.config, src.info, "'result' must borrow from the first parameter")
  456. let vid = variableId(c, dest)
  457. if vid >= 0:
  458. var sourceIdx = variableId(c, s.sym)
  459. if sourceIdx < 0:
  460. sourceIdx = c.s.len
  461. c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: s.sym, reassignedTo: 0,
  462. aliveStart: MinTime, aliveEnd: MaxTime)
  463. c.s[vid].borrowsFrom.add sourceIdx
  464. if isConstSym(s.sym):
  465. c.s[vid].flags.incl viewBorrowsFromConst
  466. else:
  467. let vid = variableId(c, dest)
  468. if vid >= 0:
  469. c.s[vid].flags.incl viewBorrowsFromConst
  470. #discard "a valid borrow location that is a deeply constant expression so we have nothing to track"
  471. proc borrowingCall(c: var Partitions; destType: PType; n: PNode; i: int) =
  472. let v = pathExpr(n[i], c.owner)
  473. if v != nil and v.kind == nkSym:
  474. when false:
  475. let isView = directViewType(destType) == immutableView
  476. if n[0].kind == nkSym and n[0].sym.name.s == "[]=":
  477. localError(c.config, n[i].info, "attempt to mutate an immutable view")
  478. for j in i+1..<n.len:
  479. if getMagic(n[j]) == mSlice:
  480. borrowFrom(c, v.sym, n[j])
  481. else:
  482. localError(c.config, n[i].info, "cannot determine the target of the borrow")
  483. proc borrowingAsgn(c: var Partitions; dest, src: PNode) =
  484. proc mutableParameter(n: PNode): bool {.inline.} =
  485. result = n.kind == nkSym and n.sym.kind == skParam and n.sym.typ.kind == tyVar
  486. if dest.kind == nkSym:
  487. if directViewType(dest.typ) != noView:
  488. borrowFrom(c, dest.sym, src)
  489. elif dest.kind in {nkHiddenDeref, nkDerefExpr, nkBracketExpr}:
  490. case directViewType(dest[0].typ)
  491. of mutableView:
  492. # we do not borrow, but we use the view to mutate the borrowed
  493. # location:
  494. let viewOrigin = pathExpr(dest, c.owner)
  495. if viewOrigin.kind == nkSym:
  496. let vid = variableId(c, viewOrigin.sym)
  497. if vid >= 0:
  498. c.s[vid].flags.incl viewDoesMutate
  499. of immutableView:
  500. if dest.kind == nkBracketExpr and dest[0].kind == nkHiddenDeref and
  501. mutableParameter(dest[0][0]):
  502. discard "remains a mutable location anyhow"
  503. else:
  504. localError(c.config, dest.info, "attempt to mutate a borrowed location from an immutable view")
  505. of noView: discard "nothing to do"
  506. proc containsPointer(t: PType): bool =
  507. proc wrap(t: PType): bool {.nimcall.} = t.kind in {tyRef, tyPtr}
  508. result = types.searchTypeFor(t, wrap)
  509. proc deps(c: var Partitions; dest, src: PNode) =
  510. if borrowChecking in c.goals:
  511. borrowingAsgn(c, dest, src)
  512. var targets, sources: seq[PSym]
  513. allRoots(dest, targets)
  514. allRoots(src, sources)
  515. let destIsComplex = containsPointer(dest.typ)
  516. for t in targets:
  517. if dest.kind != nkSym and c.inNoSideEffectSection == 0:
  518. potentialMutation(c, t, dest.info)
  519. if destIsComplex:
  520. for s in sources:
  521. connect(c, t, s, dest.info)
  522. if cursorInference in c.goals and src.kind != nkEmpty:
  523. let d = pathExpr(dest, c.owner)
  524. if d != nil and d.kind == nkSym:
  525. let vid = variableId(c, d.sym)
  526. if vid >= 0:
  527. destMightOwn(c, c.s[vid], src)
  528. for s in sources:
  529. if s == d.sym:
  530. discard "assignments like: it = it.next are fine"
  531. elif {sfGlobal, sfThread} * s.flags != {} or hasDisabledAsgn(s.typ):
  532. # do not borrow from a global variable or from something with a
  533. # disabled assignment operator.
  534. c.s[vid].flags.incl preventCursor
  535. when explainCursors: echo "A not a cursor: ", d.sym, " ", s
  536. else:
  537. let srcid = variableId(c, s)
  538. if srcid >= 0:
  539. if s.kind notin {skResult, skParam} and (
  540. c.s[srcid].aliveEnd < c.s[vid].aliveEnd):
  541. # you cannot borrow from a local that lives shorter than 'vid':
  542. when explainCursors: echo "B not a cursor ", d.sym, " ", c.s[srcid].aliveEnd, " ", c.s[vid].aliveEnd
  543. c.s[vid].flags.incl preventCursor
  544. elif {isReassigned, preventCursor} * c.s[srcid].flags != {}:
  545. # you cannot borrow from something that is re-assigned:
  546. when explainCursors: echo "C not a cursor ", d.sym, " ", c.s[srcid].flags, " reassignedTo ", c.s[srcid].reassignedTo
  547. c.s[vid].flags.incl preventCursor
  548. elif c.s[srcid].reassignedTo != 0 and c.s[srcid].reassignedTo != d.sym.id:
  549. when explainCursors: echo "D not a cursor ", d.sym, " reassignedTo ", c.s[srcid].reassignedTo
  550. c.s[vid].flags.incl preventCursor
  551. const
  552. nodesToIgnoreSet = {nkNone..pred(nkSym), succ(nkSym)..nkNilLit,
  553. nkTypeSection, nkProcDef, nkConverterDef,
  554. nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
  555. nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
  556. nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr}
  557. proc potentialMutationViaArg(c: var Partitions; n: PNode; callee: PType) =
  558. if constParameters in c.goals and tfNoSideEffect in callee.flags:
  559. discard "we know there are no hidden mutations through an immutable parameter"
  560. elif c.inNoSideEffectSection == 0 and containsPointer(n.typ):
  561. var roots: seq[PSym]
  562. allRoots(n, roots)
  563. for r in roots: potentialMutation(c, r, n.info)
  564. proc traverse(c: var Partitions; n: PNode) =
  565. inc c.abstractTime
  566. case n.kind
  567. of nkLetSection, nkVarSection:
  568. for child in n:
  569. let last = lastSon(child)
  570. traverse(c, last)
  571. if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
  572. if child.len-2 != last.len: return
  573. for i in 0..<child.len-2:
  574. #registerVariable(c, child[i])
  575. deps(c, child[i], last[i])
  576. else:
  577. for i in 0..<child.len-2:
  578. #registerVariable(c, child[i])
  579. deps(c, child[i], last)
  580. of nkAsgn, nkFastAsgn:
  581. traverse(c, n[0])
  582. inc c.inAsgnSource
  583. traverse(c, n[1])
  584. dec c.inAsgnSource
  585. deps(c, n[0], n[1])
  586. of nkSym:
  587. dec c.abstractTime
  588. of nodesToIgnoreSet:
  589. dec c.abstractTime
  590. discard "do not follow the construct"
  591. of nkCallKinds:
  592. for child in n: traverse(c, child)
  593. let parameters = n[0].typ
  594. let L = if parameters != nil: parameters.len else: 0
  595. let m = getMagic(n)
  596. for i in 1..<n.len:
  597. let it = n[i]
  598. if i < L:
  599. let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
  600. if not paramType.isCompileTimeOnly and paramType.kind in {tyVar, tySink, tyOwned}:
  601. var roots: seq[PSym]
  602. allRoots(it, roots)
  603. if paramType.kind == tyVar:
  604. if c.inNoSideEffectSection == 0:
  605. for r in roots: potentialMutation(c, r, it.info)
  606. for r in roots: noCursor(c, r)
  607. if borrowChecking in c.goals:
  608. # a call like 'result.add toOpenArray()' can also be a borrow
  609. # operation. We know 'paramType' is a tyVar and we really care if
  610. # 'paramType[0]' is still a view type, this is not a typo!
  611. if directViewType(paramType[0]) == noView and classifyViewType(paramType[0]) != noView:
  612. borrowingCall(c, paramType[0], n, i)
  613. elif borrowChecking in c.goals and m == mNone:
  614. potentialMutationViaArg(c, n[i], parameters)
  615. of nkAddr, nkHiddenAddr:
  616. traverse(c, n[0])
  617. when false:
  618. # XXX investigate if this is required, it doesn't look
  619. # like it is!
  620. var roots: seq[PSym]
  621. allRoots(n[0], roots)
  622. for r in roots:
  623. potentialMutation(c, r, it.info)
  624. of nkTupleConstr, nkBracket:
  625. for child in n: traverse(c, child)
  626. if c.inAsgnSource > 0:
  627. for i in 0..<n.len:
  628. if n[i].kind == nkSym:
  629. # we assume constructions with cursors are better without
  630. # the cursors because it's likely we can move then, see
  631. # test arc/topt_no_cursor.nim
  632. pretendOwnsData(c, n[i].sym)
  633. of nkObjConstr:
  634. for child in n: traverse(c, child)
  635. if c.inAsgnSource > 0:
  636. for i in 1..<n.len:
  637. let it = n[i].skipColon
  638. if it.kind == nkSym:
  639. # we assume constructions with cursors are better without
  640. # the cursors because it's likely we can move then, see
  641. # test arc/topt_no_cursor.nim
  642. pretendOwnsData(c, it.sym)
  643. of nkPragmaBlock:
  644. let pragmaList = n[0]
  645. var enforceNoSideEffects = 0
  646. for i in 0..<pragmaList.len:
  647. if isNoSideEffectPragma(pragmaList[i]):
  648. enforceNoSideEffects = 1
  649. break
  650. inc c.inNoSideEffectSection, enforceNoSideEffects
  651. traverse(c, n.lastSon)
  652. dec c.inNoSideEffectSection, enforceNoSideEffects
  653. of nkWhileStmt, nkForStmt, nkParForStmt:
  654. for child in n: traverse(c, child)
  655. # analyse loops twice so that 'abstractTime' suffices to detect cases
  656. # like:
  657. # while cond:
  658. # mutate(graph)
  659. # connect(graph, cursorVar)
  660. for child in n: traverse(c, child)
  661. else:
  662. for child in n: traverse(c, child)
  663. proc computeLiveRanges(c: var Partitions; n: PNode) =
  664. # first pass: Compute live ranges for locals.
  665. # **Watch out!** We must traverse the tree like 'traverse' does
  666. # so that the 'c.abstractTime' is consistent.
  667. inc c.abstractTime
  668. case n.kind
  669. of nkLetSection, nkVarSection:
  670. for child in n:
  671. let last = lastSon(child)
  672. computeLiveRanges(c, last)
  673. if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
  674. if child.len-2 != last.len: return
  675. for i in 0..<child.len-2:
  676. registerVariable(c, child[i])
  677. #deps(c, child[i], last[i])
  678. else:
  679. for i in 0..<child.len-2:
  680. registerVariable(c, child[i])
  681. #deps(c, child[i], last)
  682. of nkAsgn, nkFastAsgn:
  683. computeLiveRanges(c, n[0])
  684. computeLiveRanges(c, n[1])
  685. if n[0].kind == nkSym:
  686. let vid = variableId(c, n[0].sym)
  687. if vid >= 0:
  688. if n[1].kind == nkSym and (c.s[vid].reassignedTo == 0 or c.s[vid].reassignedTo == n[1].sym.id):
  689. c.s[vid].reassignedTo = n[1].sym.id
  690. else:
  691. c.s[vid].flags.incl isReassigned
  692. of nkSym:
  693. dec c.abstractTime
  694. if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
  695. let id = variableId(c, n.sym)
  696. if id >= 0:
  697. c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
  698. if n.sym.kind == skResult:
  699. c.s[id].aliveStart = min(c.s[id].aliveStart, c.abstractTime)
  700. of nodesToIgnoreSet:
  701. dec c.abstractTime
  702. discard "do not follow the construct"
  703. of nkCallKinds:
  704. for child in n: computeLiveRanges(c, child)
  705. let parameters = n[0].typ
  706. let L = if parameters != nil: parameters.len else: 0
  707. for i in 1..<n.len:
  708. let it = n[i]
  709. if it.kind == nkSym and i < L:
  710. let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
  711. if not paramType.isCompileTimeOnly and paramType.kind == tyVar:
  712. let vid = variableId(c, it.sym)
  713. if vid >= 0:
  714. c.s[vid].flags.incl isReassigned
  715. of nkAddr, nkHiddenAddr:
  716. computeLiveRanges(c, n[0])
  717. if n[0].kind == nkSym:
  718. let vid = variableId(c, n[0].sym)
  719. if vid >= 0:
  720. c.s[vid].flags.incl preventCursor
  721. of nkPragmaBlock:
  722. computeLiveRanges(c, n.lastSon)
  723. of nkWhileStmt, nkForStmt, nkParForStmt:
  724. for child in n: computeLiveRanges(c, child)
  725. # analyse loops twice so that 'abstractTime' suffices to detect cases
  726. # like:
  727. # while cond:
  728. # mutate(graph)
  729. # connect(graph, cursorVar)
  730. for child in n: computeLiveRanges(c, child)
  731. else:
  732. for child in n: computeLiveRanges(c, child)
  733. proc computeGraphPartitions*(s: PSym; n: PNode; config: ConfigRef; goals: set[Goal]): Partitions =
  734. result = Partitions(owner: s, config: config, goals: goals)
  735. if s.kind notin {skModule, skMacro}:
  736. let params = s.typ.n
  737. for i in 1..<params.len:
  738. registerParam(result, params[i])
  739. if resultPos < s.ast.safeLen:
  740. registerResult(result, s.ast[resultPos])
  741. computeLiveRanges(result, n)
  742. # restart the timer for the second pass:
  743. result.abstractTime = AbstractTime 0
  744. traverse(result, n)
  745. proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
  746. #echo "range ", v.aliveStart, " .. ", v.aliveEnd, " ", v.sym
  747. if isMutated in g.flags:
  748. for m in g.mutations:
  749. #echo "mutation ", m
  750. if m in v.aliveStart..v.aliveEnd:
  751. return true
  752. return false
  753. proc cannotBorrow(config: ConfigRef; s: PSym; g: MutationInfo) =
  754. var m = "cannot borrow " & s.name.s &
  755. "; what it borrows from is potentially mutated"
  756. if g.mutatedHere != unknownLineInfo:
  757. m.add "\n"
  758. m.add config $ g.mutatedHere
  759. m.add " the mutation is here"
  760. if g.connectedVia != unknownLineInfo:
  761. m.add "\n"
  762. m.add config $ g.connectedVia
  763. m.add " is the statement that connected the mutation to the parameter"
  764. localError(config, s.info, m)
  765. proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef) =
  766. for i in 0 ..< par.s.len:
  767. let v = par.s[i].sym
  768. if v.kind != skParam and classifyViewType(v.typ) != noView:
  769. let rid = root(par, i)
  770. if rid >= 0:
  771. var constViolation = false
  772. for b in par.s[rid].borrowsFrom:
  773. let sid = root(par, b)
  774. if sid >= 0:
  775. if par.s[sid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[sid].con.graphIndex], par.s[i]):
  776. cannotBorrow(config, v, par.graphs[par.s[sid].con.graphIndex])
  777. if par.s[sid].sym.kind != skParam and par.s[sid].aliveEnd < par.s[rid].aliveEnd:
  778. localError(config, v.info, "'" & v.name.s & "' borrows from location '" & par.s[sid].sym.name.s &
  779. "' which does not live long enough")
  780. if viewDoesMutate in par.s[rid].flags and isConstSym(par.s[sid].sym):
  781. localError(config, v.info, "'" & v.name.s & "' borrows from the immutable location '" &
  782. par.s[sid].sym.name.s & "' and attempts to mutate it")
  783. constViolation = true
  784. if {viewDoesMutate, viewBorrowsFromConst} * par.s[rid].flags == {viewDoesMutate, viewBorrowsFromConst} and
  785. not constViolation:
  786. # we do not track the constant expressions we allow to borrow from so
  787. # we can only produce a more generic error message:
  788. localError(config, v.info, "'" & v.name.s &
  789. "' borrows from an immutable location and attempts to mutate it")
  790. #if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
  791. # cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
  792. proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
  793. var par = computeGraphPartitions(s, n, config, {cursorInference})
  794. for i in 0 ..< par.s.len:
  795. let v = addr(par.s[i])
  796. if v.flags * {ownsData, preventCursor} == {} and v.sym.kind notin {skParam, skResult} and
  797. v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
  798. v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned:
  799. let rid = root(par, i)
  800. if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
  801. discard "cannot cursor into a graph that is mutated"
  802. else:
  803. v.sym.flags.incl sfCursor
  804. #echo "this is now a cursor ", v.sym, " ", par.s[rid].flags, " ", config $ v.sym.info