orc.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. #
  2. #
  3. # Nim's Runtime Library
  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. # Cycle collector based on
  10. # https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
  11. # And ideas from Lins' in 2008 by the notion of "critical links", see
  12. # "Cyclic reference counting" by Rafael Dueire Lins
  13. # R.D. Lins / Information Processing Letters 109 (2008) 71–78
  14. #
  15. type PT = Cell
  16. include cellseqs_v2
  17. const
  18. colBlack = 0b000
  19. colGray = 0b001
  20. colWhite = 0b010
  21. maybeCycle = 0b100 # possibly part of a cycle; this has to be a "sticky" bit
  22. jumpStackFlag = 0b1000
  23. colorMask = 0b011
  24. logOrc = defined(nimArcIds)
  25. type
  26. TraceProc = proc (p, env: pointer) {.nimcall, benign.}
  27. DisposeProc = proc (p: pointer) {.nimcall, benign.}
  28. template color(c): untyped = c.rc and colorMask
  29. template setColor(c, col) =
  30. when col == colBlack:
  31. c.rc = c.rc and not colorMask
  32. else:
  33. c.rc = c.rc and not colorMask or col
  34. const
  35. optimizedOrc = false # not defined(nimOldOrc)
  36. # XXX Still incorrect, see tests/arc/tdestroy_in_loopcond
  37. proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} =
  38. let h = head(p)
  39. inc h.rc, rcIncrement
  40. when optimizedOrc:
  41. if cyclic:
  42. h.rc = h.rc or maybeCycle
  43. proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} =
  44. when optimizedOrc:
  45. if p != nil:
  46. let h = head(p)
  47. h.rc = h.rc or maybeCycle
  48. proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
  49. # This is only used by the old RTTI mechanism and we know
  50. # that 'dest[]' is nil and needs no destruction. Which is really handy
  51. # as we cannot destroy the object reliably if it's an object of unknown
  52. # compile-time type.
  53. dest[] = src
  54. if src != nil: nimIncRefCyclic(src, true)
  55. const
  56. useJumpStack = false # for thavlak the jump stack doesn't improve the performance at all
  57. type
  58. GcEnv = object
  59. traceStack: CellSeq
  60. when useJumpStack:
  61. jumpStack: CellSeq # Lins' jump stack in order to speed up traversals
  62. toFree: CellSeq
  63. freed, touched, edges, rcSum: int
  64. keepThreshold: bool
  65. proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
  66. if desc.traceImpl != nil:
  67. var p = s +! sizeof(RefHeader)
  68. cast[TraceProc](desc.traceImpl)(p, addr(j))
  69. when logOrc:
  70. proc writeCell(msg: cstring; s: Cell; desc: PNimTypeV2) =
  71. cfprintf(cstderr, "%s %s %ld root index: %ld; RC: %ld; color: %ld\n",
  72. msg, desc.name, s.refId, s.rootIdx, s.rc shr rcShift, s.color)
  73. proc free(s: Cell; desc: PNimTypeV2) {.inline.} =
  74. when traceCollector:
  75. cprintf("[From ] %p rc %ld color %ld\n", s, s.rc shr rcShift, s.color)
  76. let p = s +! sizeof(RefHeader)
  77. when logOrc: writeCell("free", s, desc)
  78. if desc.disposeImpl != nil:
  79. cast[DisposeProc](desc.disposeImpl)(p)
  80. when false:
  81. cstderr.rawWrite desc.name
  82. cstderr.rawWrite " "
  83. if desc.disposeImpl == nil:
  84. cstderr.rawWrite "lacks dispose"
  85. if desc.traceImpl != nil:
  86. cstderr.rawWrite ", but has trace\n"
  87. else:
  88. cstderr.rawWrite ", and lacks trace\n"
  89. else:
  90. cstderr.rawWrite "has dispose!\n"
  91. nimRawDispose(p, desc.align)
  92. proc nimTraceRef(q: pointer; desc: PNimTypeV2; env: pointer) {.compilerRtl, inline.} =
  93. let p = cast[ptr pointer](q)
  94. if p[] != nil:
  95. var j = cast[ptr GcEnv](env)
  96. j.traceStack.add(head p[], desc)
  97. proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl, inline.} =
  98. let p = cast[ptr pointer](q)
  99. if p[] != nil:
  100. var j = cast[ptr GcEnv](env)
  101. j.traceStack.add(head p[], cast[ptr PNimTypeV2](p[])[])
  102. template orcAssert(cond, msg) =
  103. when logOrc:
  104. if not cond:
  105. cfprintf(cstderr, "[Bug!] %s\n", msg)
  106. quit 1
  107. var
  108. roots {.threadvar.}: CellSeq
  109. proc unregisterCycle(s: Cell) =
  110. # swap with the last element. O(1)
  111. let idx = s.rootIdx-1
  112. when false:
  113. if idx >= roots.len or idx < 0:
  114. cprintf("[Bug!] %ld\n", idx)
  115. quit 1
  116. roots.d[idx] = roots.d[roots.len-1]
  117. roots.d[idx][0].rootIdx = idx+1
  118. dec roots.len
  119. s.rootIdx = 0
  120. proc scanBlack(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  121. #[
  122. proc scanBlack(s: Cell) =
  123. setColor(s, colBlack)
  124. for t in sons(s):
  125. t.rc = t.rc + rcIncrement
  126. if t.color != colBlack:
  127. scanBlack(t)
  128. ]#
  129. s.setColor colBlack
  130. let until = j.traceStack.len
  131. trace(s, desc, j)
  132. when logOrc: writeCell("root still alive", s, desc)
  133. while j.traceStack.len > until:
  134. let (t, desc) = j.traceStack.pop()
  135. inc t.rc, rcIncrement
  136. if t.color != colBlack:
  137. t.setColor colBlack
  138. trace(t, desc, j)
  139. when logOrc: writeCell("child still alive", t, desc)
  140. proc markGray(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  141. #[
  142. proc markGray(s: Cell) =
  143. if s.color != colGray:
  144. setColor(s, colGray)
  145. for t in sons(s):
  146. t.rc = t.rc - rcIncrement
  147. if t.color != colGray:
  148. markGray(t)
  149. ]#
  150. if s.color != colGray:
  151. s.setColor colGray
  152. inc j.touched
  153. # keep in mind that refcounts are zero based so add 1 here:
  154. inc j.rcSum, (s.rc shr rcShift) + 1
  155. orcAssert(j.traceStack.len == 0, "markGray: trace stack not empty")
  156. trace(s, desc, j)
  157. while j.traceStack.len > 0:
  158. let (t, desc) = j.traceStack.pop()
  159. dec t.rc, rcIncrement
  160. inc j.edges
  161. when useJumpStack:
  162. if (t.rc shr rcShift) >= 0 and (t.rc and jumpStackFlag) == 0:
  163. t.rc = t.rc or jumpStackFlag
  164. when traceCollector:
  165. cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
  166. j.jumpStack.add(t, desc)
  167. if t.color != colGray:
  168. t.setColor colGray
  169. inc j.touched
  170. # we already decremented its refcount so account for that:
  171. inc j.rcSum, (t.rc shr rcShift) + 2
  172. trace(t, desc, j)
  173. proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  174. #[
  175. proc scan(s: Cell) =
  176. if s.color == colGray:
  177. if s.rc > 0:
  178. scanBlack(s)
  179. else:
  180. s.setColor(colWhite)
  181. for t in sons(s): scan(t)
  182. ]#
  183. if s.color == colGray:
  184. if (s.rc shr rcShift) >= 0:
  185. scanBlack(s, desc, j)
  186. # XXX this should be done according to Lins' paper but currently breaks
  187. #when useJumpStack:
  188. # s.setColor colPurple
  189. else:
  190. when useJumpStack:
  191. # first we have to repair all the nodes we have seen
  192. # that are still alive; we also need to mark what they
  193. # refer to as alive:
  194. while j.jumpStack.len > 0:
  195. let (t, desc) = j.jumpStack.pop
  196. # not in jump stack anymore!
  197. t.rc = t.rc and not jumpStackFlag
  198. if t.color == colGray and (t.rc shr rcShift) >= 0:
  199. scanBlack(t, desc, j)
  200. # XXX this should be done according to Lins' paper but currently breaks
  201. #t.setColor colPurple
  202. when traceCollector:
  203. cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
  204. orcAssert(j.traceStack.len == 0, "scan: trace stack not empty")
  205. s.setColor(colWhite)
  206. trace(s, desc, j)
  207. while j.traceStack.len > 0:
  208. let (t, desc) = j.traceStack.pop()
  209. if t.color == colGray:
  210. if (t.rc shr rcShift) >= 0:
  211. scanBlack(t, desc, j)
  212. else:
  213. when useJumpStack:
  214. # first we have to repair all the nodes we have seen
  215. # that are still alive; we also need to mark what they
  216. # refer to as alive:
  217. while j.jumpStack.len > 0:
  218. let (t, desc) = j.jumpStack.pop
  219. # not in jump stack anymore!
  220. t.rc = t.rc and not jumpStackFlag
  221. if t.color == colGray and (t.rc shr rcShift) >= 0:
  222. scanBlack(t, desc, j)
  223. # XXX this should be done according to Lins' paper but currently breaks
  224. #t.setColor colPurple
  225. when traceCollector:
  226. cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
  227. t.setColor(colWhite)
  228. trace(t, desc, j)
  229. when false:
  230. proc writeCell(msg: cstring; s: Cell) =
  231. cfprintf(cstderr, "%s %p root index: %ld; RC: %ld; color: %ld\n",
  232. msg, s, s.rootIdx, s.rc shr rcShift, s.color)
  233. proc collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) =
  234. #[
  235. was: 'collectWhite'.
  236. proc collectWhite(s: Cell) =
  237. if s.color == colWhite and not buffered(s):
  238. s.setColor(colBlack)
  239. for t in sons(s):
  240. collectWhite(t)
  241. free(s) # watch out, a bug here!
  242. ]#
  243. if s.color == col and s.rootIdx == 0:
  244. orcAssert(j.traceStack.len == 0, "collectWhite: trace stack not empty")
  245. s.setColor(colBlack)
  246. j.toFree.add(s, desc)
  247. trace(s, desc, j)
  248. while j.traceStack.len > 0:
  249. let (t, desc) = j.traceStack.pop()
  250. if t.color == col and t.rootIdx == 0:
  251. j.toFree.add(t, desc)
  252. t.setColor(colBlack)
  253. trace(t, desc, j)
  254. proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
  255. # pretty direct translation from
  256. # https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
  257. # Fig. 2. Synchronous Cycle Collection
  258. #[
  259. for s in roots:
  260. markGray(s)
  261. for s in roots:
  262. scan(s)
  263. for s in roots:
  264. remove s from roots
  265. s.buffered = false
  266. collectWhite(s)
  267. ]#
  268. let last = roots.len - 1
  269. when logOrc:
  270. for i in countdown(last, lowMark):
  271. writeCell("root", roots.d[i][0], roots.d[i][1])
  272. for i in countdown(last, lowMark):
  273. markGray(roots.d[i][0], roots.d[i][1], j)
  274. var colToCollect = colWhite
  275. if j.rcSum == j.edges:
  276. # short-cut: we know everything is garbage:
  277. colToCollect = colGray
  278. # remember the fact that we got so lucky:
  279. j.keepThreshold = true
  280. else:
  281. for i in countdown(last, lowMark):
  282. scan(roots.d[i][0], roots.d[i][1], j)
  283. init j.toFree
  284. for i in 0 ..< roots.len:
  285. let s = roots.d[i][0]
  286. s.rootIdx = 0
  287. collectColor(s, roots.d[i][1], colToCollect, j)
  288. for i in 0 ..< j.toFree.len:
  289. free(j.toFree.d[i][0], j.toFree.d[i][1])
  290. inc j.freed, j.toFree.len
  291. deinit j.toFree
  292. #roots.len = 0
  293. const
  294. defaultThreshold = when defined(nimFixedOrc): 10_000 else: 128
  295. when defined(nimStressOrc):
  296. const rootsThreshold = 10 # broken with -d:nimStressOrc: 10 and for havlak iterations 1..8
  297. else:
  298. var rootsThreshold = defaultThreshold
  299. proc partialCollect(lowMark: int) =
  300. when false:
  301. if roots.len < 10 + lowMark: return
  302. when logOrc:
  303. cfprintf(cstderr, "[partialCollect] begin\n")
  304. var j: GcEnv
  305. init j.traceStack
  306. collectCyclesBacon(j, lowMark)
  307. when logOrc:
  308. cfprintf(cstderr, "[partialCollect] end; freed %ld touched: %ld work: %ld\n", j.freed, j.touched,
  309. roots.len - lowMark)
  310. roots.len = lowMark
  311. deinit j.traceStack
  312. proc collectCycles() =
  313. ## Collect cycles.
  314. when logOrc:
  315. cfprintf(cstderr, "[collectCycles] begin\n")
  316. var j: GcEnv
  317. init j.traceStack
  318. when useJumpStack:
  319. init j.jumpStack
  320. collectCyclesBacon(j, 0)
  321. while j.jumpStack.len > 0:
  322. let (t, desc) = j.jumpStack.pop
  323. # not in jump stack anymore!
  324. t.rc = t.rc and not jumpStackFlag
  325. deinit j.jumpStack
  326. else:
  327. collectCyclesBacon(j, 0)
  328. deinit j.traceStack
  329. deinit roots
  330. when not defined(nimStressOrc):
  331. # compute the threshold based on the previous history
  332. # of the cycle collector's effectiveness:
  333. # we're effective when we collected 50% or more of the nodes
  334. # we touched. If we're effective, we can reset the threshold:
  335. if j.keepThreshold and rootsThreshold <= defaultThreshold:
  336. discard
  337. elif j.freed * 2 >= j.touched:
  338. when not defined(nimFixedOrc):
  339. rootsThreshold = max(rootsThreshold div 3 * 2, 16)
  340. else:
  341. rootsThreshold = defaultThreshold
  342. #cfprintf(cstderr, "[collectCycles] freed %ld, touched %ld new threshold %ld\n", j.freed, j.touched, rootsThreshold)
  343. elif rootsThreshold < high(int) div 4:
  344. rootsThreshold = rootsThreshold * 3 div 2
  345. when logOrc:
  346. cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld rcSum: %ld edges: %ld\n", j.freed, rootsThreshold, j.touched,
  347. getOccupiedMem(), j.rcSum, j.edges)
  348. proc registerCycle(s: Cell; desc: PNimTypeV2) =
  349. s.rootIdx = roots.len+1
  350. if roots.d == nil: init(roots)
  351. add(roots, s, desc)
  352. if roots.len >= rootsThreshold:
  353. collectCycles()
  354. when logOrc:
  355. writeCell("[added root]", s, desc)
  356. proc GC_runOrc* =
  357. ## Forces a cycle collection pass.
  358. collectCycles()
  359. proc GC_enableOrc*() =
  360. ## Enables the cycle collector subsystem of `--gc:orc`. This is a `--gc:orc`
  361. ## specific API. Check with `when defined(gcOrc)` for its existence.
  362. when not defined(nimStressOrc):
  363. rootsThreshold = defaultThreshold
  364. proc GC_disableOrc*() =
  365. ## Disables the cycle collector subsystem of `--gc:orc`. This is a `--gc:orc`
  366. ## specific API. Check with `when defined(gcOrc)` for its existence.
  367. when not defined(nimStressOrc):
  368. rootsThreshold = high(int)
  369. proc GC_prepareOrc*(): int {.inline.} = roots.len
  370. proc GC_partialCollect*(limit: int) =
  371. partialCollect(limit)
  372. proc GC_fullCollect* =
  373. ## Forces a full garbage collection pass. With `--gc:orc` triggers the cycle
  374. ## collector. This is an alias for `GC_runOrc`.
  375. collectCycles()
  376. proc GC_enableMarkAndSweep*() =
  377. ## For `--gc:orc` an alias for `GC_enableOrc`.
  378. GC_enableOrc()
  379. proc GC_disableMarkAndSweep*() =
  380. ## For `--gc:orc` an alias for `GC_disableOrc`.
  381. GC_disableOrc()
  382. when optimizedOrc:
  383. template markedAsCyclic(s: Cell): bool = (s.rc and maybeCycle) != 0
  384. else:
  385. template markedAsCyclic(s: Cell): bool = true
  386. proc rememberCycle(isDestroyAction: bool; s: Cell; desc: PNimTypeV2) {.noinline.} =
  387. if isDestroyAction:
  388. if s.rootIdx > 0:
  389. unregisterCycle(s)
  390. else:
  391. # do not call 'rememberCycle' again unless this cell
  392. # got an 'incRef' event:
  393. if s.rootIdx == 0 and markedAsCyclic(s):
  394. s.setColor colBlack
  395. registerCycle(s, desc)
  396. proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
  397. if p != nil:
  398. var cell = head(p)
  399. if (cell.rc and not rcMask) == 0:
  400. result = true
  401. #cprintf("[DESTROY] %p\n", p)
  402. else:
  403. dec cell.rc, rcIncrement
  404. #if cell.color == colPurple:
  405. rememberCycle(result, cell, cast[ptr PNimTypeV2](p)[])
  406. proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimTypeV2): bool {.compilerRtl, inl.} =
  407. if p != nil:
  408. var cell = head(p)
  409. if (cell.rc and not rcMask) == 0:
  410. result = true
  411. #cprintf("[DESTROY] %p %s\n", p, desc.name)
  412. else:
  413. dec cell.rc, rcIncrement
  414. #if cell.color == colPurple:
  415. rememberCycle(result, cell, desc)