varpartitions.nim 36 KB

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