orc.nim 17 KB

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