too many changes to list
This commit is contained in:
parent
916c25f9a7
commit
07d5a8085b
200 changed files with 24344 additions and 17809 deletions
630
lib/gc.nim
630
lib/gc.nim
|
|
@ -1,7 +1,7 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2006 Andreas Rumpf
|
||||
# (c) Copyright 2008 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
|
|
@ -13,33 +13,39 @@
|
|||
# For a description of the algorithms used here see:
|
||||
# intern.html
|
||||
|
||||
#{.define: debugGC.} # we wish to debug the GC...
|
||||
{.define: debugGC.} # we wish to debug the GC...
|
||||
|
||||
#when defined(debugGC):
|
||||
# {.define: logGC.} # define if the GC should log some of its activities
|
||||
|
||||
{.define: cycleGC.}
|
||||
|
||||
const
|
||||
traceGC = false # extensive debugging
|
||||
reallyDealloc = true # for debugging purposes this can be set to false
|
||||
|
||||
# Guess the page size of the system; if it is the
|
||||
# wrong value, performance may be worse (this is not
|
||||
# for sure though), but GC still works; must be a power of two!
|
||||
const
|
||||
PageSize = 1024 * sizeof(int)
|
||||
RC_Increase = 7 * PageSize # is an additive increase
|
||||
PageShift = if sizeof(pointer) == 4: 12 else: 13
|
||||
PageSize = 1 shl PageShift # on 32 bit systems 4096
|
||||
CycleIncrease = 2 # is a multiplicative increase
|
||||
|
||||
InitialCycleThreshold = 8*1024*1024 # X MB because cycle checking is slow
|
||||
ZctThreshold = 512 # we collect garbage if the ZCT's size
|
||||
# reaches this threshold
|
||||
# this needs benchmarking...
|
||||
|
||||
when defined(debugGC):
|
||||
const InitialThreshold = 64*1024
|
||||
const stressGC = True # GC is debugged; no need to stress it
|
||||
const stressGC = False
|
||||
else:
|
||||
const stressGC = False
|
||||
const InitialThreshold = RC_Increase
|
||||
# this may need benchmarking...
|
||||
|
||||
# things the System module thinks should be available:
|
||||
when defined(useDL) or defined(nativeDL):
|
||||
type
|
||||
TMallocInfo {.importc: "struct mallinfo", nodecl.} = record
|
||||
TMallocInfo {.importc: "struct mallinfo", nodecl, final.} = object
|
||||
arena: cint # non-mmapped space allocated from system
|
||||
ordblks: cint # number of free chunks
|
||||
smblks: cint # number of fastbin blocks
|
||||
|
|
@ -68,8 +74,7 @@ else: # not available:
|
|||
proc getTotalMem(): int = return -1
|
||||
|
||||
var
|
||||
rcThreshold: int = InitialThreshold
|
||||
cycleThreshold: int = InitialThreshold
|
||||
cycleThreshold: int = InitialCycleThreshold
|
||||
|
||||
memUsed: int = 0 # we have to keep track how much we have allocated
|
||||
|
||||
|
|
@ -113,11 +118,12 @@ type
|
|||
waNone, waRelease, waZctDecRef, waCycleDecRef, waCycleIncRef, waDebugIncRef
|
||||
|
||||
TCollectorData = int
|
||||
TCell = record
|
||||
TCell {.final.} = object
|
||||
refcount: TCollectorData # the refcount and bit flags
|
||||
typ: PNimType
|
||||
stackcount: int # stack counter for debugging
|
||||
drefc: int # real reference counter for debugging
|
||||
when stressGC:
|
||||
stackcount: int # stack counter for debugging
|
||||
drefc: int # real reference counter for debugging
|
||||
|
||||
PCell = ptr TCell
|
||||
|
||||
|
|
@ -145,6 +151,7 @@ proc extGetCellType(c: pointer): PNimType {.compilerproc.} =
|
|||
|
||||
proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
|
||||
result = int(usrToCell(p).refcount)
|
||||
if result < 0: result = 0
|
||||
|
||||
proc gcAlloc(size: int): pointer =
|
||||
result = alloc0(size)
|
||||
|
|
@ -162,7 +169,7 @@ proc GC_setStrategy(strategy: TGC_Strategy) =
|
|||
of gcOptimizeTime: nil
|
||||
|
||||
proc GC_enableMarkAndSweep() =
|
||||
cycleThreshold = InitialThreshold
|
||||
cycleThreshold = InitialCycleThreshold
|
||||
|
||||
proc GC_disableMarkAndSweep() =
|
||||
cycleThreshold = high(cycleThreshold)-1
|
||||
|
|
@ -174,7 +181,7 @@ proc nextTry(h, maxHash: int): int {.inline.} =
|
|||
# generates each int in range(maxHash) exactly once (see any text on
|
||||
# random-number generation for proof).
|
||||
|
||||
# ------------------ Zero count table (ZCT) and any table (AT) -------------
|
||||
# ------------------ Any table (AT) -------------
|
||||
|
||||
# these values are for DL-malloc known for sure (and other allocators
|
||||
# can only be worse):
|
||||
|
|
@ -200,8 +207,7 @@ when BitsPerPage mod BitsPerUnit != 0:
|
|||
|
||||
# ------------------- cell set handling ------------------------------
|
||||
# A cellset consists of a hash table of page descriptors. A page
|
||||
# descriptor has a bit for
|
||||
# every Memalignment'th byte in the page.
|
||||
# descriptor has a bit for every Memalignment'th byte in the page.
|
||||
# However, only bits corresponding to addresses that start memory blocks
|
||||
# are set.
|
||||
# Page descriptors are also linked to a list; the list
|
||||
|
|
@ -217,17 +223,59 @@ type
|
|||
|
||||
TBitIndex = range[0..UnitsPerPage-1]
|
||||
|
||||
TPageDesc = record
|
||||
TPageDesc {.final.} = object
|
||||
next: PPageDesc # all nodes are connected with this pointer
|
||||
key: TAddress # start address at bit 0
|
||||
bits: array[TBitIndex, int] # a bit vector
|
||||
|
||||
PPageDescArray = ptr array[0..1000_000, PPageDesc]
|
||||
TCellSet = record
|
||||
TCellSet {.final.} = object
|
||||
counter, max: int
|
||||
head: PPageDesc
|
||||
data: PPageDescArray
|
||||
|
||||
PCellArray = ptr array[0..100_000_000, PCell]
|
||||
TCellSeq {.final.} = object
|
||||
len, cap: int
|
||||
d: PCellArray
|
||||
|
||||
TSlowSet {.final.} = object # used for debugging purposes only
|
||||
L: int # current length
|
||||
cap: int # capacity
|
||||
d: PCellArray
|
||||
|
||||
TGcHeap {.final.} = object # this contains the zero count and
|
||||
# non-zero count table
|
||||
mask: TAddress # mask for fast pointer detection
|
||||
zct: TCellSeq # the zero count table
|
||||
at: TCellSet # a table that contains all references
|
||||
newAT: TCellSet
|
||||
stackCells: TCellSeq # cells that need to be decremented because they
|
||||
# are in the hardware stack; a cell may occur
|
||||
# several times in this data structure
|
||||
|
||||
var
|
||||
stackBottom: pointer
|
||||
gch: TGcHeap
|
||||
|
||||
proc add(s: var TCellSeq, c: PCell) {.inline.} =
|
||||
if s.len >= s.cap:
|
||||
s.cap = s.cap * 3 div 2
|
||||
s.d = cast[PCellArray](realloc(s.d, s.cap * sizeof(PCell)))
|
||||
if s.d == nil: raiseOutOfMem()
|
||||
s.d[s.len] = c
|
||||
inc(s.len)
|
||||
|
||||
proc inOperator(s: TCellSeq, c: PCell): bool {.inline.} =
|
||||
for i in 0 .. s.len-1:
|
||||
if s.d[i] == c: return True
|
||||
return False
|
||||
|
||||
proc init(s: var TCellSeq, cap: int = 1024) =
|
||||
s.len = 0
|
||||
s.cap = cap
|
||||
s.d = cast[PCellArray](gcAlloc(cap * sizeof(PCell)))
|
||||
|
||||
const
|
||||
InitCellSetSize = 1024 # must be a power of two!
|
||||
|
||||
|
|
@ -284,7 +332,8 @@ proc CellSetPut(t: var TCellSet, key: TAddress): PPageDesc =
|
|||
if x.key == key: return x
|
||||
h = nextTry(h, t.max)
|
||||
|
||||
if (t.max+1) * 2 < t.counter * 3: CellSetEnlarge(t)
|
||||
if ((t.max+1)*2 < t.counter*3) or ((t.max+1)-t.counter < 4):
|
||||
CellSetEnlarge(t)
|
||||
inc(t.counter)
|
||||
h = cast[int](key) and t.max
|
||||
while t.data[h] != nil: h = nextTry(h, t.max)
|
||||
|
|
@ -303,7 +352,7 @@ proc in_Operator(s: TCellSet, cell: PCell): bool =
|
|||
u: TAddress
|
||||
t: PPageDesc
|
||||
u = cast[TAddress](cell)
|
||||
t = CellSetGet(s, u /% PageSize)
|
||||
t = CellSetGet(s, u shr PageShift)
|
||||
if t != nil:
|
||||
u = (u %% PageSize) /% MemAlignment
|
||||
result = (t.bits[u /% BitsPerUnit] and (1 shl (u %% BitsPerUnit))) != 0
|
||||
|
|
@ -315,7 +364,7 @@ proc incl(s: var TCellSet, cell: PCell) =
|
|||
u: TAddress
|
||||
t: PPageDesc
|
||||
u = cast[TAddress](cell)
|
||||
t = CellSetPut(s, u /% PageSize)
|
||||
t = CellSetPut(s, u shr PageShift)
|
||||
u = (u %% PageSize) /% MemAlignment
|
||||
t.bits[u /% BitsPerUnit] = t.bits[u /% BitsPerUnit] or
|
||||
(1 shl (u %% BitsPerUnit))
|
||||
|
|
@ -325,7 +374,7 @@ proc excl(s: var TCellSet, cell: PCell) =
|
|||
u: TAddress
|
||||
t: PPageDesc
|
||||
u = cast[TAddress](cell)
|
||||
t = CellSetGet(s, u /% PageSize)
|
||||
t = CellSetGet(s, u shr PageShift)
|
||||
if t != nil:
|
||||
u = (u %% PageSize) /% MemAlignment
|
||||
t.bits[u /% BitsPerUnit] = (t.bits[u /% BitsPerUnit] and
|
||||
|
|
@ -342,7 +391,7 @@ iterator elements(t: TCellSet): PCell {.inline.} =
|
|||
var j = 0
|
||||
while w != 0: # test all remaining bits for zero
|
||||
if (w and 1) != 0: # the bit is set!
|
||||
yield cast[PCell]((r.key *% PageSize) +%
|
||||
yield cast[PCell]((r.key shl PageShift) or # +%
|
||||
(i*%BitsPerUnit+%j) *% MemAlignment)
|
||||
inc(j)
|
||||
w = w shr 1
|
||||
|
|
@ -354,68 +403,121 @@ iterator elements(t: TCellSet): PCell {.inline.} =
|
|||
proc testPageDescs() =
|
||||
var root: TCellSet
|
||||
CellSetInit(root)
|
||||
var u = 10_000
|
||||
while u <= 20_000:
|
||||
incl(root, cast[PCell](u))
|
||||
inc(u, 8)
|
||||
for cell in elements(root):
|
||||
c_fprintf(c_stdout, "%ld\n", cast[int](cell))
|
||||
#var u = 10_000
|
||||
#while u <= 20_000:
|
||||
# incl(root, cast[PCell](u))
|
||||
# inc(u, 8)
|
||||
|
||||
# testPageDescs()
|
||||
incl(root, cast[PCell](0x81cdfb8))
|
||||
for cell in elements(root):
|
||||
c_fprintf(c_stdout, "%p\n", cast[int](cell))
|
||||
|
||||
#testPageDescs()
|
||||
|
||||
when defined(debugGC):
|
||||
proc writeCell(msg: CString, c: PCell) =
|
||||
c_fprintf(c_stdout, "%s: %p\n", msg, c)
|
||||
if c.typ != nil:
|
||||
if c.typ.kind == tyString:
|
||||
c_fprintf(c_stdout, "%s\n", cast[TAddress](cellToUsr(c)) + sizeof(int)*2)
|
||||
c_fprintf(c_stdout, "%s: %p %d\n", msg, c, c.typ.kind)
|
||||
else: c_fprintf(c_stdout, "%s: %p (nil type)\n", msg, c)
|
||||
proc writePtr(msg: CString, p: Pointer) =
|
||||
c_fprintf(c_stdout, "%s: %p\n", msg, p)
|
||||
|
||||
|
||||
when traceGC:
|
||||
# traceGC is a special switch to enable extensive debugging
|
||||
type
|
||||
TCellState = enum
|
||||
csAllocated, csZctFreed, csCycFreed
|
||||
|
||||
proc cellSetInit(s: var TSlowSet) =
|
||||
s.L = 0
|
||||
s.cap = 4096
|
||||
s.d = cast[PCellArray](gcAlloc(s.cap * sizeof(PCell)))
|
||||
|
||||
proc cellSetDeinit(s: var TSlowSet) =
|
||||
s.L = 0
|
||||
s.cap = 0
|
||||
dealloc(s.d)
|
||||
|
||||
proc incl(s: var TSlowSet, c: PCell) =
|
||||
if s.L >= s.cap:
|
||||
s.cap = s.cap * 3 div 2
|
||||
s.d = cast[PCellArray](realloc(s.d, s.cap * sizeof(PCell)))
|
||||
if s.d == nil: raiseOutOfMem()
|
||||
s.d[s.L] = c
|
||||
inc(s.L)
|
||||
|
||||
proc excl(s: var TSlowSet, c: PCell) =
|
||||
var i = 0
|
||||
while i < s.L:
|
||||
if s.d[i] == c:
|
||||
s.d[i] = s.d[s.L-1]
|
||||
dec(s.L)
|
||||
break
|
||||
inc(i)
|
||||
|
||||
proc inOperator(s: TSlowSet, c: PCell): bool =
|
||||
var i = 0
|
||||
while i < s.L:
|
||||
if s.d[i] == c: return true
|
||||
inc(i)
|
||||
|
||||
iterator elements(s: TSlowSet): PCell =
|
||||
var i = 0
|
||||
while i < s.L:
|
||||
yield s.d[i]
|
||||
inc(i)
|
||||
|
||||
var
|
||||
states: array[TCellState, TSlowSet] # TCellSet]
|
||||
|
||||
proc traceCell(c: PCell, state: TCellState) =
|
||||
case state
|
||||
of csAllocated:
|
||||
if c in states[csAllocated]:
|
||||
writeCell("attempt to alloc a already allocated cell", c)
|
||||
assert(false)
|
||||
excl(states[csCycFreed], c)
|
||||
excl(states[csZctFreed], c)
|
||||
of csZctFreed:
|
||||
if c notin states[csAllocated]:
|
||||
writeCell("attempt to free a not allocated cell", c)
|
||||
assert(false)
|
||||
if c in states[csZctFreed]:
|
||||
writeCell("attempt to free zct cell twice", c)
|
||||
assert(false)
|
||||
if c in states[csCycFreed]:
|
||||
writeCell("attempt to free with zct, but already freed with cyc", c)
|
||||
assert(false)
|
||||
excl(states[csAllocated], c)
|
||||
of csCycFreed:
|
||||
if c notin states[csAllocated]:
|
||||
writeCell("attempt to free a not allocated cell", c)
|
||||
assert(false)
|
||||
if c in states[csCycFreed]:
|
||||
writeCell("attempt to free cyc cell twice", c)
|
||||
assert(false)
|
||||
if c in states[csZctFreed]:
|
||||
writeCell("attempt to free with cyc, but already freed with zct", c)
|
||||
assert(false)
|
||||
excl(states[csAllocated], c)
|
||||
incl(states[state], c)
|
||||
|
||||
template gcTrace(cell, state: expr): stmt =
|
||||
when traceGC: traceCell(cell, state)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
type
|
||||
PStackCells = ptr array[0..1000_0000, PCell]
|
||||
TCountTables = record # this contains the zero count and
|
||||
# non-zero count table
|
||||
mask: TAddress # mask for fast pointer detection
|
||||
zct: TCellSet # the zero count table
|
||||
at: TCellSet # a table that contains all references
|
||||
newAT: TCellSet
|
||||
newZCT: TCellSet
|
||||
stackCells: PStackCells # cells that need to be decremented because they
|
||||
# are in the hardware stack; a cell may occur
|
||||
# several times in this data structure
|
||||
stackLen, stackMax: int # for managing the stack cells
|
||||
# forward declarations:
|
||||
proc collectCT(gch: var TGcHeap)
|
||||
proc IsOnStack(p: pointer): bool
|
||||
proc forAllChildren(cell: PCell, op: TWalkOp)
|
||||
proc collectCycles(gch: var TGcHeap)
|
||||
|
||||
proc addStackCell(ct: var TCountTables, cell: PCell) =
|
||||
if ct.stackLen >= ct.stackMax:
|
||||
ct.stackMax = ct.stackMax * 3 div 2
|
||||
ct.stackCells = cast[PStackCells](realloc(ct.stackCells, ct.stackMax *
|
||||
sizeof(PCell)))
|
||||
if ct.stackCells == nil: raiseOutOfMem()
|
||||
ct.stackCells[ct.stackLen] = cell
|
||||
inc(ct.stackLen)
|
||||
|
||||
var
|
||||
stackBottom: pointer
|
||||
ct: TCountTables
|
||||
|
||||
proc GC_invariant(): bool =
|
||||
result = True
|
||||
when stressGC:
|
||||
if recGcLock == 0:
|
||||
GC_disable()
|
||||
for cell in elements(ct.at):
|
||||
var t = cell.typ # getCellType(cell)
|
||||
if t == nil or t.kind notin {tySequence, tyString, tyRef}:
|
||||
writeCell("corrupt cell?", cell)
|
||||
result = false
|
||||
GC_enable()
|
||||
|
||||
when stressGC:
|
||||
proc GCdebugHook() =
|
||||
if not GC_invariant():
|
||||
assert(false)
|
||||
|
||||
dbgLineHook = GCdebugHook
|
||||
proc reprAny(p: pointer, typ: PNimType): string {.compilerproc.}
|
||||
# we need the prototype here for debugging purposes
|
||||
|
||||
proc prepareDealloc(cell: PCell) =
|
||||
if cell.typ.finalizer != nil:
|
||||
|
|
@ -433,79 +535,95 @@ proc prepareDealloc(cell: PCell) =
|
|||
else:
|
||||
memUsed = memUsed - cell.typ.size
|
||||
|
||||
proc checkZCT(): bool =
|
||||
if recGcLock >= 1: return true # prevent endless recursion
|
||||
inc(recGcLock)
|
||||
result = true
|
||||
for i in 0..gch.zct.len-1:
|
||||
var c = gch.zct.d[i]
|
||||
if c.refcount > 0: # should be in the ZCT!
|
||||
writeCell("wrong ZCT entry", c)
|
||||
result = false
|
||||
elif gch.zct.d[-c.refcount] != c:
|
||||
writeCell("wrong ZCT position", c)
|
||||
result = false
|
||||
dec(recGcLock)
|
||||
|
||||
proc GC_invariant(): bool =
|
||||
if recGcLock >= 1: return true # prevent endless recursion
|
||||
inc(recGcLock)
|
||||
result = True
|
||||
block checks:
|
||||
if not checkZCT():
|
||||
result = false
|
||||
break checks
|
||||
# set counters back to zero:
|
||||
for c in elements(gch.AT):
|
||||
var t = c.typ
|
||||
if t == nil or t.kind notin {tySequence, tyString, tyRef}:
|
||||
writeCell("corrupt cell?", c)
|
||||
result = false
|
||||
break checks
|
||||
when stressGC: c.drefc = 0
|
||||
for c in elements(gch.AT):
|
||||
forAllChildren(c, waDebugIncRef)
|
||||
when stressGC:
|
||||
for c in elements(gch.AT):
|
||||
var rc = c.refcount
|
||||
if rc < 0: rc = 0
|
||||
if c.drefc > rc + c.stackcount:
|
||||
result = false # failed
|
||||
c_fprintf(c_stdout,
|
||||
"broken cell: %p, refc: %ld, stack: %ld, real: %ld\n",
|
||||
c, c.refcount, c.stackcount, c.drefc)
|
||||
break checks
|
||||
dec(recGcLock)
|
||||
|
||||
when stressGC:
|
||||
proc GCdebugHook() =
|
||||
if not GC_invariant():
|
||||
assert(false)
|
||||
|
||||
dbgLineHook = GCdebugHook
|
||||
|
||||
proc setStackBottom(theStackBottom: pointer) {.compilerproc.} =
|
||||
stackBottom = theStackBottom
|
||||
|
||||
proc initGC() =
|
||||
when traceGC:
|
||||
for i in low(TCellState)..high(TCellState): CellSetInit(states[i])
|
||||
# init the rt
|
||||
CellSetInit(ct.zct)
|
||||
CellSetInit(ct.at)
|
||||
ct.stackLen = 0
|
||||
ct.stackMax = 255
|
||||
ct.stackCells = cast[PStackCells](gcAlloc((ct.stackMax+1) * sizeof(PCell)))
|
||||
ct.mask = 0
|
||||
init(gch.zct)
|
||||
CellSetInit(gch.at)
|
||||
init(gch.stackCells)
|
||||
gch.mask = 0
|
||||
new(gOutOfMem) # reserve space for the EOutOfMemory exception here!
|
||||
assert(GC_invariant())
|
||||
|
||||
# forward declarations:
|
||||
proc collectCT(ct: var TCountTables)
|
||||
proc IsOnStack(p: pointer): bool
|
||||
proc forAllChildren(cell: PCell, op: TWalkOp)
|
||||
proc collectCycles()
|
||||
|
||||
proc reprAny(p: pointer, typ: PNimType): string {.compilerproc.}
|
||||
# we need the prototype here for debugging purposes
|
||||
|
||||
proc outputCell(c: PCell) =
|
||||
inc(recGcLock)
|
||||
write(stdout, reprAny(cellToUsr(c), c.typ))
|
||||
dec(recGcLock)
|
||||
|
||||
proc writeGraph() =
|
||||
{.checkpoint.}
|
||||
block:
|
||||
inc(recGcLock)
|
||||
for c in elements(ct.AT): outputCell(c)
|
||||
dec(recGcLock)
|
||||
|
||||
proc checkRefc(): bool =
|
||||
if recGcLock >= 1: return true # prevent endless recursion
|
||||
inc(recGcLock)
|
||||
result = True
|
||||
# set counters back to zero:
|
||||
for c in elements(ct.AT):
|
||||
c.drefc = 0
|
||||
for c in elements(ct.AT):
|
||||
forAllChildren(c, waDebugIncRef)
|
||||
for c in elements(ct.AT):
|
||||
if c.drefc > c.refcount - c.stackcount:
|
||||
result = false # failed
|
||||
c_fprintf(c_stdout,
|
||||
"broken cell: %p, refc: %ld, stack: %ld, real: %ld\n",
|
||||
c, c.refcount, c.stackcount, c.drefc)
|
||||
dec(recGcLock)
|
||||
|
||||
proc seqCheck(cell: PCell): bool =
|
||||
assert(cell.typ != nil)
|
||||
if cell.typ.kind in {tySequence, tyString}:
|
||||
result = cell.refcount - cell.stackcount <= 1
|
||||
else:
|
||||
result = true
|
||||
|
||||
proc decRef(cell: PCell) {.inline.} =
|
||||
assert(cell in ct.AT)
|
||||
when defined(debugGC):
|
||||
if cell.refcount == 0:
|
||||
writePtr("decref broken", cellToUsr(cell))
|
||||
assert(cell.refcount > 0) # this should be the case!
|
||||
assert(seqCheck(cell))
|
||||
when stressGC: assert(cell in gch.AT)
|
||||
dec(cell.refcount)
|
||||
if cell.refcount == 0:
|
||||
incl(ct.zct, cell)
|
||||
cell.refcount = -gch.zct.len
|
||||
when stressGC: assert(cell notin gch.zct)
|
||||
add(gch.zct, cell)
|
||||
when stressGC: assert(checkZCT())
|
||||
|
||||
proc incRef(cell: PCell) {.inline.} =
|
||||
assert(seqCheck(cell))
|
||||
inc(cell.refcount)
|
||||
var rc = cell.refcount
|
||||
if rc <= 0:
|
||||
# remove from zero count table:
|
||||
when stressGC: assert(gch.zct.len > -rc)
|
||||
when stressGC: assert(gch.zct.d[-rc] == cell)
|
||||
gch.zct.d[-rc] = gch.zct.d[gch.zct.len-1]
|
||||
gch.zct.d[-rc].refcount = rc
|
||||
dec(gch.zct.len)
|
||||
cell.refcount = 1
|
||||
when stressGC: assert(checkZCT())
|
||||
else:
|
||||
inc(cell.refcount)
|
||||
when stressGC: assert(checkZCT())
|
||||
|
||||
proc asgnRef(dest: ppointer, src: pointer) =
|
||||
# the code generator calls this proc!
|
||||
|
|
@ -514,18 +632,18 @@ proc asgnRef(dest: ppointer, src: pointer) =
|
|||
if src != nil: incRef(usrToCell(src))
|
||||
if dest^ != nil: decRef(usrToCell(dest^))
|
||||
dest^ = src
|
||||
#assert(checkRefc())
|
||||
when stressGC: assert(GC_invariant())
|
||||
|
||||
proc unsureAsgnRef(dest: ppointer, src: pointer) =
|
||||
if not IsOnStack(dest):
|
||||
if src != nil: incRef(usrToCell(src))
|
||||
if dest^ != nil: decRef(usrToCell(dest^))
|
||||
dest^ = src
|
||||
#assert(checkRefc())
|
||||
when stressGC: assert(GC_invariant())
|
||||
|
||||
proc restore(cell: PCell) =
|
||||
if cell notin ct.newAT:
|
||||
incl(ct.newAT, Cell)
|
||||
if cell notin gch.newAT:
|
||||
incl(gch.newAT, Cell)
|
||||
forAllChildren(cell, waCycleIncRef)
|
||||
|
||||
proc doOperation(p: pointer, op: TWalkOp) =
|
||||
|
|
@ -536,19 +654,15 @@ proc doOperation(p: pointer, op: TWalkOp) =
|
|||
of waNone: assert(false)
|
||||
of waRelease: decRef(cell) # DEAD CODE!
|
||||
of waZctDecRef:
|
||||
assert(cell.refcount > 0)
|
||||
assert(seqCheck(cell))
|
||||
dec(cell.refcount)
|
||||
if cell.refcount == 0:
|
||||
incl(ct.newZCT, cell)
|
||||
decRef(cell)
|
||||
of waCycleDecRef:
|
||||
assert(cell.refcount != 0)
|
||||
assert(cell.refcount > 0)
|
||||
dec(cell.refcount)
|
||||
of waCycleIncRef:
|
||||
inc(cell.refcount) # restore proper reference counts!
|
||||
restore(cell)
|
||||
of waDebugIncRef:
|
||||
inc(cell.drefc)
|
||||
when stressGC: inc(cell.drefc)
|
||||
|
||||
type
|
||||
TByteArray = array[0..1000_0000, byte]
|
||||
|
|
@ -590,16 +704,20 @@ proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
|
|||
if m != nil: forAllSlotsAux(dest, m, op)
|
||||
|
||||
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
|
||||
const
|
||||
handledTypes = {tyArray, tyArrayConstr, tyOpenArray, tyRef,
|
||||
tyString, tySequence, tyObject, tyPureObject, tyTuple}
|
||||
var
|
||||
d = cast[TAddress](dest)
|
||||
if dest == nil: return # nothing to do
|
||||
case mt.Kind
|
||||
of tyArray, tyArrayConstr, tyOpenArray:
|
||||
for i in 0..(mt.size div mt.base.size)-1:
|
||||
forAllChildrenAux(cast[pointer](d +% i *% mt.base.size), mt.base, op)
|
||||
if mt.base.kind in handledTypes:
|
||||
for i in 0..(mt.size div mt.base.size)-1:
|
||||
forAllChildrenAux(cast[pointer](d +% i *% mt.base.size), mt.base, op)
|
||||
of tyRef, tyString, tySequence: # leaf:
|
||||
doOperation(cast[ppointer](d)^, op)
|
||||
of tyRecord, tyObject, tyTuple:
|
||||
of tyObject, tyTuple, tyPureObject:
|
||||
forAllSlotsAux(dest, mt.node, op)
|
||||
else: nil
|
||||
|
||||
|
|
@ -608,6 +726,13 @@ proc forAllChildren(cell: PCell, op: TWalkOp) =
|
|||
when defined(debugGC):
|
||||
if cell.typ == nil:
|
||||
writeCell("cell has no type descriptor", cell)
|
||||
when traceGC:
|
||||
if cell notin states[csAllocated]:
|
||||
writeCell("cell has never been allocated!", cell)
|
||||
if cell in states[csCycFreed]:
|
||||
writeCell("cell has been freed by Cyc", cell)
|
||||
if cell in states[csZctFreed]:
|
||||
writeCell("cell has been freed by Zct", cell)
|
||||
assert(cell.typ != nil)
|
||||
case cell.typ.Kind
|
||||
of tyRef: # common case
|
||||
|
|
@ -625,36 +750,36 @@ proc forAllChildren(cell: PCell, op: TWalkOp) =
|
|||
proc checkCollection() {.inline.} =
|
||||
# checks if a collection should be done
|
||||
if recGcLock == 0:
|
||||
if memUsed >= rcThreshold or stressGC:
|
||||
collectCT(ct)
|
||||
when defined(debugGC):
|
||||
write(stdout, "threshold is now: ")
|
||||
writeln(stdout, rcThreshold)
|
||||
collectCT(gch)
|
||||
|
||||
proc newObj(typ: PNimType, size: int): pointer =
|
||||
# generates a new object and sets its reference counter to 0
|
||||
var
|
||||
res: PCell
|
||||
when stressGC: assert(checkZCT())
|
||||
assert(typ.kind in {tyRef, tyString, tySequence})
|
||||
# check if we have to collect:
|
||||
checkCollection()
|
||||
res = cast[PCell](Alloc0(size + sizeof(TCell)))
|
||||
when stressGC: assert((cast[TAddress](res) and (MemAlignment-1)) == 0)
|
||||
if res == nil: raiseOutOfMem()
|
||||
when defined(nimSize):
|
||||
memUsed = memUsed + nimSize(res)
|
||||
else:
|
||||
memUsed = memUsed + size
|
||||
|
||||
res.refcount = 0
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
incl(ct.zct, res) # its refcount is zero, so add it to the ZCT
|
||||
incl(ct.at, res) # add it to the any table too
|
||||
ct.mask = ct.mask or cast[TAddress](res)
|
||||
res.refcount = -gch.zct.len
|
||||
add(gch.zct, res) # its refcount is zero, so add it to the ZCT
|
||||
incl(gch.at, res) # add it to the any table too
|
||||
gch.mask = gch.mask or cast[TAddress](res)
|
||||
when defined(debugGC):
|
||||
writeCell("new cell", res)
|
||||
assert(gcInvariant())
|
||||
when defined(logGC): writeCell("new cell", res)
|
||||
gcTrace(res, csAllocated)
|
||||
result = cellToUsr(res)
|
||||
assert(res.typ == typ)
|
||||
when stressGC: assert(checkZCT())
|
||||
|
||||
proc newSeq(typ: PNimType, len: int): pointer =
|
||||
# XXX: overflow checks!
|
||||
|
|
@ -665,16 +790,19 @@ proc newSeq(typ: PNimType, len: int): pointer =
|
|||
proc growObj(old: pointer, newsize: int): pointer =
|
||||
var
|
||||
res, ol: PCell
|
||||
when stressGC: assert(checkZCT())
|
||||
checkCollection()
|
||||
ol = usrToCell(old)
|
||||
assert(ol.typ.kind in {tyString, tySequence})
|
||||
assert(seqCheck(ol))
|
||||
when defined(nimSize):
|
||||
memUsed = memUsed - nimSize(ol)
|
||||
else:
|
||||
memUsed = memUsed - ol.size # this is not exact
|
||||
# pity that we don't know the old size
|
||||
# pity that we don't know the old size
|
||||
res = cast[PCell](realloc(ol, newsize + sizeof(TCell)))
|
||||
#res = cast[PCell](gcAlloc(newsize + sizeof(TCell)))
|
||||
#copyMem(res, ol, nimSize(ol))
|
||||
assert((cast[TAddress](res) and (MemAlignment-1)) == 0)
|
||||
when defined(nimSize):
|
||||
memUsed = memUsed + nimSize(res)
|
||||
else:
|
||||
|
|
@ -682,74 +810,69 @@ proc growObj(old: pointer, newsize: int): pointer =
|
|||
|
||||
if res != ol:
|
||||
if res == nil: raiseOutOfMem()
|
||||
excl(ct.zct, ol) # remove old pointer in any case:
|
||||
# It may have a refcount > 0 and is still in the ZCT.
|
||||
# So do it safe here and remove it anyway.
|
||||
excl(ct.at, ol)
|
||||
if res.refcount == 0:
|
||||
# store new pointer in ZCT, if refcount == 0:
|
||||
incl(ct.zct, res)
|
||||
incl(ct.at, res)
|
||||
ct.mask = ct.mask or cast[TAddress](res)
|
||||
when defined(debugGC):
|
||||
if res.refcount <= 0:
|
||||
assert(gch.zct.d[-res.refcount] == ol)
|
||||
gch.zct.d[-res.refcount] = res
|
||||
excl(gch.at, ol)
|
||||
incl(gch.at, res)
|
||||
gch.mask = gch.mask or cast[TAddress](res)
|
||||
when defined(logGC):
|
||||
writeCell("growObj old cell", ol)
|
||||
writeCell("growObj new cell", res)
|
||||
gcTrace(ol, csZctFreed)
|
||||
gcTrace(res, csAllocated)
|
||||
result = cellToUsr(res)
|
||||
#assert(checkRefc())
|
||||
when stressGC: assert(checkZCT())
|
||||
|
||||
proc collectCycles() =
|
||||
when defined(debugGC):
|
||||
echo("collecting cycles!\n")
|
||||
proc collectCycles(gch: var TGcHeap) =
|
||||
when defined(logGC):
|
||||
c_fprintf(c_stdout, "collecting cycles!\n")
|
||||
|
||||
# step 1: pretend that any node is dead
|
||||
for c in elements(ct.at):
|
||||
for c in elements(gch.at):
|
||||
forallChildren(c, waCycleDecRef)
|
||||
CellSetInit(ct.newAt)
|
||||
CellSetInit(gch.newAt)
|
||||
# step 2: restore life cells
|
||||
for c in elements(ct.at):
|
||||
for c in elements(gch.at):
|
||||
if c.refcount > 0: restore(c)
|
||||
# step 3: free dead cells:
|
||||
for cell in elements(ct.at):
|
||||
for cell in elements(gch.at):
|
||||
if cell.refcount == 0:
|
||||
assert(cell notin ct.zct)
|
||||
# We free an object that is part of a cycle here. Its children
|
||||
# may have been freed already. Thus the finalizer could access
|
||||
# garbage. To handle this case properly we need two passes for
|
||||
# freeing here which is too expensive. We just don't call the
|
||||
# finalizer for now. YYY: Any better ideas?
|
||||
prepareDealloc(cell)
|
||||
dealloc(cell)
|
||||
when defined(debugGC):
|
||||
gcTrace(cell, csCycFreed)
|
||||
when defined(logGC):
|
||||
writeCell("cycle collector dealloc cell", cell)
|
||||
CellSetDeinit(ct.at)
|
||||
ct.at = ct.newAt
|
||||
#ct.newAt = nil
|
||||
when reallyDealloc: dealloc(cell)
|
||||
CellSetDeinit(gch.at)
|
||||
gch.at = gch.newAt
|
||||
|
||||
proc gcMark(p: pointer) =
|
||||
proc gcMark(gch: var TGcHeap, p: pointer) =
|
||||
# the addresses are not as objects on the stack, so turn them to objects:
|
||||
var cell = usrToCell(p)
|
||||
var c = cast[TAddress](cell)
|
||||
if ((c and ct.mask) == c) and cell in ct.at:
|
||||
if ((c and gch.mask) == c) and cell in gch.at:
|
||||
# is the page that p "points to" in the AT? (All allocated pages are
|
||||
# always in the AT)
|
||||
inc(cell.refcount)
|
||||
inc(cell.stackcount)
|
||||
addStackCell(ct, cell)
|
||||
incRef(cell)
|
||||
when stressGC: inc(cell.stackcount)
|
||||
add(gch.stackCells, cell)
|
||||
|
||||
proc unmarkStackAndRegisters() =
|
||||
for i in 0 .. ct.stackLen-1:
|
||||
var cell = ct.stackCells[i]
|
||||
proc unmarkStackAndRegisters(gch: var TGcHeap) =
|
||||
when stressGC: assert(checkZCT())
|
||||
for i in 0 .. gch.stackCells.len-1:
|
||||
var cell = gch.stackCells.d[i]
|
||||
assert(cell.refcount > 0)
|
||||
when defined(debugGC):
|
||||
if cell.stackcount == 0:
|
||||
writeGraph()
|
||||
writePtr("broken stackcount", cellToUsr(cell))
|
||||
assert(cell.stackcount > 0)
|
||||
dec(cell.refcount)
|
||||
dec(cell.stackcount)
|
||||
if cell.refcount == 0:
|
||||
incl(ct.zct, cell)
|
||||
ct.stackLen = 0 # reset to zero
|
||||
when stressGC:
|
||||
assert(cell.stackcount > 0)
|
||||
dec(cell.stackcount)
|
||||
decRef(cell)
|
||||
gch.stackCells.len = 0 # reset to zero
|
||||
when stressGC: assert(checkZCT())
|
||||
|
||||
# ----------------- stack management --------------------------------------
|
||||
# inspired from Smart Eiffel (c)
|
||||
|
|
@ -765,7 +888,7 @@ when defined(sparc): # For SPARC architecture.
|
|||
stackTop: array[0..1, pointer]
|
||||
result = p >= addr(stackTop[0]) and p <= stackBottom
|
||||
|
||||
proc markStackAndRegisters() =
|
||||
proc markStackAndRegisters(gch: var TGcHeap) =
|
||||
when defined(sparcv9):
|
||||
asm " flushw"
|
||||
else:
|
||||
|
|
@ -780,7 +903,7 @@ when defined(sparc): # For SPARC architecture.
|
|||
sp = addr(stackTop[0])
|
||||
# Addresses decrease as the stack grows.
|
||||
while sp <= max:
|
||||
gcMark(sp^)
|
||||
gcMark(gch, sp^)
|
||||
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
|
||||
|
||||
elif defined(ELATE):
|
||||
|
|
@ -802,7 +925,7 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
|
|||
# a little hack to get the size of a TJmpBuf in the generated C code
|
||||
# in a platform independant way
|
||||
|
||||
proc markStackAndRegisters() =
|
||||
proc markStackAndRegisters(gch: var TGcHeap) =
|
||||
var
|
||||
max = stackBottom
|
||||
registers: C_JmpBuf # The jmp_buf buffer is in the C stack.
|
||||
|
|
@ -814,7 +937,7 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
|
|||
# sp will traverse the JMP_BUF as well (jmp_buf size is added,
|
||||
# otherwise sp would be below the registers structure).
|
||||
while sp >= max:
|
||||
gcMark(sp^)
|
||||
gcMark(gch, sp^)
|
||||
sp = cast[ppointer](cast[TAddress](sp) -% sizeof(pointer))
|
||||
|
||||
else:
|
||||
|
|
@ -826,7 +949,7 @@ else:
|
|||
stackTop: array [0..1, pointer]
|
||||
result = p >= addr(stackTop[0]) and p <= stackBottom
|
||||
|
||||
proc markStackAndRegisters() =
|
||||
proc markStackAndRegisters(gch: var TGcHeap) =
|
||||
var
|
||||
max = stackBottom
|
||||
registers: C_JmpBuf # The jmp_buf buffer is in the C stack.
|
||||
|
|
@ -835,63 +958,58 @@ else:
|
|||
c_setjmp(registers) # To fill the C stack with registers.
|
||||
sp = cast[ppointer](addr(registers))
|
||||
while sp <= max:
|
||||
gcMark(sp^)
|
||||
gcMark(gch, sp^)
|
||||
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# end of non-portable code
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
proc CollectZCT =
|
||||
CellSetInit(ct.newZCT)
|
||||
for c in elements(ct.zct):
|
||||
if c.refcount == 0:
|
||||
# if != 0 the reference count has been increased, so this does not
|
||||
# belong to the ZCT. We simply do nothing - it won't appear in the newZCT
|
||||
# anyway.
|
||||
# We are about to free the object, call the finalizer BEFORE its
|
||||
# children are deleted as well, because otherwise the finalizer may
|
||||
# access invalid memory. This is done by prepareDealloc():
|
||||
prepareDealloc(c)
|
||||
forAllChildren(c, waZctDecRef)
|
||||
assert(c.refcount == 0) # should still be zero
|
||||
excl(ct.at, c)
|
||||
excl(ct.newZCT, c) # BUGFIX
|
||||
when defined(debugGC):
|
||||
writeCell("zct dealloc cell", c)
|
||||
dealloc(c)
|
||||
CellSetDeinit(ct.zct)
|
||||
ct.zct = ct.newZCT
|
||||
#ct.newZCT = nil
|
||||
proc CollectZCT(gch: var TGcHeap) =
|
||||
while gch.zct.len > 0:
|
||||
var c = gch.zct.d[0]
|
||||
assert(c.refcount <= 0)
|
||||
# remove from ZCT:
|
||||
gch.zct.d[0] = gch.zct.d[gch.zct.len-1]
|
||||
gch.zct.d[0].refcount = 0
|
||||
dec(gch.zct.len)
|
||||
# We are about to free the object, call the finalizer BEFORE its
|
||||
# children are deleted as well, because otherwise the finalizer may
|
||||
# access invalid memory. This is done by prepareDealloc():
|
||||
gcTrace(c, csZctFreed)
|
||||
prepareDealloc(c)
|
||||
forAllChildren(c, waZctDecRef)
|
||||
excl(gch.at, c)
|
||||
when defined(logGC):
|
||||
writeCell("zct dealloc cell", c)
|
||||
#when defined(debugGC) and defined(nimSize): zeroMem(c, nimSize(c))
|
||||
when reallyDealloc: dealloc(c)
|
||||
|
||||
proc collectCT(ct: var TCountTables) =
|
||||
when defined(debugGC):
|
||||
proc collectCT(gch: var TGcHeap) =
|
||||
when defined(logGC):
|
||||
c_fprintf(c_stdout, "collecting zero count table; stack size: %ld\n",
|
||||
stackSize())
|
||||
markStackAndRegisters()
|
||||
assert(GC_invariant())
|
||||
while True:
|
||||
collectZCT()
|
||||
if ct.zct.counter == 0: break
|
||||
# ``counter`` counts the pages, but zero pages means zero cells
|
||||
|
||||
when defined(cycleGC):
|
||||
# still over the cycle threshold?
|
||||
if memUsed >= cycleThreshold or stressGC:
|
||||
# collect the cyclic things:
|
||||
assert(ct.zct.counter == 0)
|
||||
assert(GC_invariant())
|
||||
collectCycles()
|
||||
|
||||
# recompute the thresholds:
|
||||
rcThreshold = (memUsed div RC_increase + 1) * RC_Increase
|
||||
cycleThreshold = memUsed * cycleIncrease
|
||||
|
||||
assert(GC_invariant())
|
||||
unmarkStackAndRegisters()
|
||||
when stressGC: assert(checkZCT())
|
||||
if gch.zct.len >= ZctThreshold or memUsed >= cycleThreshold or stressGC:
|
||||
markStackAndRegisters(gch)
|
||||
when stressGC: assert(GC_invariant())
|
||||
collectZCT(gch)
|
||||
when stressGC: assert(GC_invariant())
|
||||
assert(gch.zct.len == 0)
|
||||
when defined(cycleGC):
|
||||
if memUsed >= cycleThreshold or stressGC:
|
||||
when defined(logGC):
|
||||
c_fprintf(c_stdout, "collecting cycles; memory used: %ld\n", memUsed)
|
||||
collectCycles(gch)
|
||||
cycleThreshold = max(InitialCycleThreshold, memUsed * cycleIncrease)
|
||||
when defined(logGC):
|
||||
c_fprintf(c_stdout, "now used: %ld; threshold: %ld\n",
|
||||
memUsed, cycleThreshold)
|
||||
unmarkStackAndRegisters(gch)
|
||||
when stressGC: assert(GC_invariant())
|
||||
|
||||
proc GC_fullCollect() =
|
||||
var oldThreshold = cycleThreshold
|
||||
cycleThreshold = 0 # forces cycle collection
|
||||
collectCT(ct)
|
||||
collectCT(gch)
|
||||
cycleThreshold = oldThreshold
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue