varpartitions.nim 36 KB

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