123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2020 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- #[
- A Cycle breaker for Nim
- -----------------------
- Instead of "collecting" cycles with all of its pitfalls we will break cycles.
- We exploit that every 'ref' can be 'nil' for this and so get away without
- a distinction between weak and strong pointers. The required runtime
- mechanisms are the same though: We need to be able to traverse the graph.
- This design has the tremendous benefit that it doesn't require a dedicated
- 'rawDispose' operation and that it plays well with Nim's cost model.
- The cost of freeing a subgraph with cycles is 2 * N rather than N, that's all.
- Cycles do not have to be prepared via .acyclic, there are not multiple
- pointless traversals, only a single proc, `breakCycles` is exposed as a
- separate module.
- Algorithm
- ---------
- We traverse the graph and notice the nodes we've already traversed. If we
- marked the node already, we set the pointer that leads to this node to 'nil'
- and decrement the reference count of the cell we pointed at.
- We notice that multiple paths to the same object do not mean
- we found a cycle, it only means the node is shared.
- a -------> b <----- c
- | ^ ^
- +----------+ |
- | |
- +-------------------+
- If we simply remove all links to already processed nodes we end up with:
- a -------> b c
- | ^
- + |
- | |
- +-------------------+
- That seems acceptable, no leak is produced. This implies that the standard
- depth-first traversal suffices.
- ]#
- include cellseqs_v2
- const
- colGreen = 0b000
- colYellow = 0b001
- colRed = 0b010
- colorMask = 0b011
- type
- TraceProc = proc (p, env: pointer) {.nimcall, benign.}
- DisposeProc = proc (p: pointer) {.nimcall, benign.}
- template color(c): untyped = c.rc and colorMask
- template setColor(c, col) =
- c.rc = c.rc and not colorMask or col
- proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} =
- let h = head(p)
- inc h.rc, rcIncrement
- proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} = discard
- type
- GcEnv = object
- traceStack: CellSeq[ptr pointer]
- proc trace(p: pointer; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
- when false:
- cprintf("[Trace] desc: %p %p\n", desc, p)
- cprintf("[Trace] trace: %p\n", desc.traceImpl)
- if desc.traceImpl != nil:
- cast[TraceProc](desc.traceImpl)(p, addr(j))
- proc nimTraceRef(q: pointer; desc: PNimTypeV2; env: pointer) {.compilerRtl.} =
- let p = cast[ptr pointer](q)
- when traceCollector:
- cprintf("[Trace] raw: %p\n", p)
- cprintf("[Trace] deref: %p\n", p[])
- if p[] != nil:
- var j = cast[ptr GcEnv](env)
- j.traceStack.add(p, desc)
- proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl.} =
- let p = cast[ptr pointer](q)
- when traceCollector:
- cprintf("[TraceDyn] raw: %p\n", p)
- cprintf("[TraceDyn] deref: %p\n", p[])
- if p[] != nil:
- var j = cast[ptr GcEnv](env)
- j.traceStack.add(p, cast[ptr PNimTypeV2](p[])[])
- var markerGeneration: int
- proc breakCycles(s: Cell; desc: PNimTypeV2) =
- let markerColor = if (markerGeneration and 1) == 0: colRed
- else: colYellow
- atomicInc markerGeneration
- when traceCollector:
- cprintf("[BreakCycles] starting: %p %s RC %ld trace proc %p\n",
- s, desc.name, s.rc shr rcShift, desc.traceImpl)
- var j: GcEnv
- init j.traceStack
- s.setColor markerColor
- trace(s +! sizeof(RefHeader), desc, j)
- while j.traceStack.len > 0:
- let (u, desc) = j.traceStack.pop()
- let p = u[]
- let t = head(p)
- if t.color != markerColor:
- t.setColor markerColor
- trace(p, desc, j)
- when traceCollector:
- cprintf("[BreakCycles] followed: %p RC %ld\n", t, t.rc shr rcShift)
- else:
- if (t.rc shr rcShift) > 0:
- dec t.rc, rcIncrement
- # mark as a link that the produced destructor does not have to follow:
- u[] = nil
- when traceCollector:
- cprintf("[BreakCycles] niled out: %p RC %ld\n", t, t.rc shr rcShift)
- else:
- # anyhow as a link that the produced destructor does not have to follow:
- u[] = nil
- when traceCollector:
- cprintf("[Bug] %p %s RC %ld\n", t, desc.name, t.rc shr rcShift)
- deinit j.traceStack
- proc thinout*[T](x: ref T) {.inline.} =
- ## turn the subgraph starting with `x` into its spanning tree by
- ## `nil`'ing out any pointers that would harm the spanning tree
- ## structure. Any back pointers that introduced cycles
- ## and thus would keep the graph from being freed are `nil`'ed.
- ## This is a form of cycle collection that works well with Nim's ARC
- ## and its associated cost model.
- proc getDynamicTypeInfo[T](x: T): PNimTypeV2 {.magic: "GetTypeInfoV2", noSideEffect.}
- breakCycles(head(cast[pointer](x)), getDynamicTypeInfo(x[]))
- proc thinout*[T: proc](x: T) {.inline.} =
- proc rawEnv[T: proc](x: T): pointer {.noSideEffect, inline.} =
- {.emit: """
- `result` = `x`.ClE_0;
- """.}
- let p = rawEnv(x)
- breakCycles(head(p), cast[ptr PNimTypeV2](p)[])
- proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
- if p != nil:
- var cell = head(p)
- if (cell.rc and not rcMask) == 0:
- result = true
- #cprintf("[DESTROY] %p\n", p)
- else:
- dec cell.rc, rcIncrement
- # According to Lins it's correct to do nothing else here.
- #cprintf("[DeCREF] %p\n", p)
- proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimTypeV2): bool {.compilerRtl, inl.} =
- if p != nil:
- var cell = head(p)
- if (cell.rc and not rcMask) == 0:
- result = true
- #cprintf("[DESTROY] %p %s\n", p, desc.name)
- else:
- dec cell.rc, rcIncrement
- #cprintf("[DeCREF] %p %s %ld\n", p, desc.name, cell.rc)
|