varpartitions.nim 34 KB

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