Merge branch 'master' of github.com:Araq/Nimrod

Conflicts:
	lib/system/gc.nim
This commit is contained in:
Araq 2013-02-11 07:11:42 +01:00
commit 68135b3b50
24 changed files with 832 additions and 201 deletions

View file

@ -1,7 +1,7 @@
# #
# #
# The Nimrod Compiler # The Nimrod Compiler
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -15,6 +15,14 @@ const
stringCaseThreshold = 8 stringCaseThreshold = 8
# above X strings a hash-switch for strings is generated # above X strings a hash-switch for strings is generated
proc registerGcRoot(p: BProc, v: PSym) =
if gSelectedGc == gcMarkAndSweep and containsGarbageCollectedRef(v.loc.t):
# we register a specialized marked proc here; this has the advantage
# that it works out of the box for thread local storage then :-)
let prc = genTraverseProcForGlobal(p.module, v)
linefmt(p.module.initProc, cpsStmts,
"#nimRegisterGlobalMarker($1);$n", prc)
proc genVarTuple(p: BProc, n: PNode) = proc genVarTuple(p: BProc, n: PNode) =
var tup, field: TLoc var tup, field: TLoc
if n.kind != nkVarTuple: InternalError(n.info, "genVarTuple") if n.kind != nkVarTuple: InternalError(n.info, "genVarTuple")
@ -28,6 +36,7 @@ proc genVarTuple(p: BProc, n: PNode) =
if sfGlobal in v.flags: if sfGlobal in v.flags:
assignGlobalVar(p, v) assignGlobalVar(p, v)
genObjectInit(p, cpsInit, v.typ, v.loc, true) genObjectInit(p, cpsInit, v.typ, v.loc, true)
registerGcRoot(p, v)
else: else:
assignLocalVar(p, v) assignLocalVar(p, v)
initLocalVar(p, v, immediateAsgn=true) initLocalVar(p, v, immediateAsgn=true)
@ -143,7 +152,7 @@ proc genSingleVar(p: BProc, a: PNode) =
# if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc) # if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc)
if sfExportc in v.flags and generatedHeader != nil: if sfExportc in v.flags and generatedHeader != nil:
genVarPrototypeAux(generatedHeader, v) genVarPrototypeAux(generatedHeader, v)
registerGcRoot(p, v)
else: else:
assignLocalVar(p, v) assignLocalVar(p, v)
initLocalVar(p, v, immediateAsgn) initLocalVar(p, v, immediateAsgn)

View file

@ -1,7 +1,7 @@
# #
# #
# The Nimrod Compiler # The Nimrod Compiler
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -13,7 +13,7 @@
# included from cgen.nim # included from cgen.nim
type type
TTraversalClosure {.pure, final.} = object TTraversalClosure = object
p: BProc p: BProc
visitorFrmt: string visitorFrmt: string
@ -127,3 +127,20 @@ proc genTraverseProc(m: BModule, typ: PType, reason: TTypeInfoReason): PRope =
m.s[cfsProcs].app(generatedProc) m.s[cfsProcs].app(generatedProc)
proc genTraverseProcForGlobal(m: BModule, s: PSym): PRope =
discard genTypeInfo(m, s.loc.t)
var c: TTraversalClosure
var p = newProc(nil, m)
result = getGlobalTempName()
c.visitorFrmt = "#nimGCvisit((void*)$1, 0);$n"
c.p = p
let header = ropef("N_NIMCALL(void, $1)()", result)
genTraverseProc(c, s.loc.r, s.loc.t)
let generatedProc = ropef("$1 {$n$2$3$4}$n",
[header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)])
m.s[cfsProcHeaders].appf("$1;$n", header)
m.s[cfsProcs].app(generatedProc)

View file

@ -1,7 +1,7 @@
# #
# #
# The Nimrod Compiler # The Nimrod Compiler
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -938,7 +938,7 @@ proc genTypeInfo(m: BModule, typ: PType): PRope =
genTupleInfo(m, fakeClosureType(t.owner), result) genTupleInfo(m, fakeClosureType(t.owner), result)
of tySequence, tyRef: of tySequence, tyRef:
genTypeInfoAux(m, t, result) genTypeInfoAux(m, t, result)
if usesNativeGC(): if gSelectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, t, tiNew) let markerProc = genTraverseProc(m, t, tiNew)
appf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc]) appf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc])
of tyPtr, tyRange: genTypeInfoAux(m, t, result) of tyPtr, tyRange: genTypeInfoAux(m, t, result)

View file

@ -142,6 +142,7 @@ proc testCompileOptionArg*(switch, arg: string, info: TLineInfo): bool =
of "boehm": result = gSelectedGC == gcBoehm of "boehm": result = gSelectedGC == gcBoehm
of "refc": result = gSelectedGC == gcRefc of "refc": result = gSelectedGC == gcRefc
of "v2": result = gSelectedGC == gcV2 of "v2": result = gSelectedGC == gcV2
of "markandsweep": result = gSelectedGC == gcMarkAndSweep
of "none": result = gSelectedGC == gcNone of "none": result = gSelectedGC == gcNone
else: LocalError(info, errNoneBoehmRefcExpectedButXFound, arg) else: LocalError(info, errNoneBoehmRefcExpectedButXFound, arg)
of "opt": of "opt":
@ -276,6 +277,9 @@ proc processSwitch(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) =
gSelectedGC = gcRefc gSelectedGC = gcRefc
of "v2": of "v2":
gSelectedGC = gcV2 gSelectedGC = gcV2
of "markandsweep":
gSelectedGC = gcMarkAndSweep
defineSymbol("gcmarkandsweep")
of "none": of "none":
gSelectedGC = gcNone gSelectedGC = gcNone
defineSymbol("nogc") defineSymbol("nogc")

View file

@ -140,6 +140,8 @@ proc mangleName(s: PSym): PRope =
app(result, toRope(s.id)) app(result, toRope(s.id))
s.loc.r = result s.loc.r = result
proc makeJSString(s: string): PRope = strutils.escape(s).toRope
proc genTypeInfo(p: var TProc, typ: PType): PRope proc genTypeInfo(p: var TProc, typ: PType): PRope
proc genObjectFields(p: var TProc, typ: PType, n: PNode): PRope = proc genObjectFields(p: var TProc, typ: PType, n: PNode): PRope =
var var
@ -165,7 +167,7 @@ proc genObjectFields(p: var TProc, typ: PType, n: PNode): PRope =
s = genTypeInfo(p, field.typ) s = genTypeInfo(p, field.typ)
result = ropef("{kind: 1, offset: \"$1\", len: 0, " & result = ropef("{kind: 1, offset: \"$1\", len: 0, " &
"typ: $2, name: $3, sons: null}", "typ: $2, name: $3, sons: null}",
[mangleName(field), s, makeCString(field.name.s)]) [mangleName(field), s, makeJSString(field.name.s)])
of nkRecCase: of nkRecCase:
length = sonsLen(n) length = sonsLen(n)
if (n.sons[0].kind != nkSym): InternalError(n.info, "genObjectFields") if (n.sons[0].kind != nkSym): InternalError(n.info, "genObjectFields")
@ -193,7 +195,7 @@ proc genObjectFields(p: var TProc, typ: PType, n: PNode): PRope =
[u, genObjectFields(p, typ, lastSon(b))]) [u, genObjectFields(p, typ, lastSon(b))])
result = ropef("{kind: 3, offset: \"$1\", len: $3, " & result = ropef("{kind: 3, offset: \"$1\", len: $3, " &
"typ: $2, name: $4, sons: [$5]}", [mangleName(field), s, "typ: $2, name: $4, sons: [$5]}", [mangleName(field), s,
toRope(lengthOrd(field.typ)), makeCString(field.name.s), result]) toRope(lengthOrd(field.typ)), makeJSString(field.name.s), result])
else: internalError(n.info, "genObjectFields") else: internalError(n.info, "genObjectFields")
proc genObjectInfo(p: var TProc, typ: PType, name: PRope) = proc genObjectInfo(p: var TProc, typ: PType, name: PRope) =
@ -234,7 +236,7 @@ proc genEnumInfo(p: var TProc, typ: PType, name: PRope) =
if i > 0: app(s, ", " & tnl) if i > 0: app(s, ", " & tnl)
let extName = if field.ast == nil: field.name.s else: field.ast.strVal let extName = if field.ast == nil: field.name.s else: field.ast.strVal
appf(s, "{kind: 1, offset: $1, typ: $2, name: $3, len: 0, sons: null}", appf(s, "{kind: 1, offset: $1, typ: $2, name: $3, len: 0, sons: null}",
[toRope(field.position), name, makeCString(extName)]) [toRope(field.position), name, makeJSString(extName)])
var n = ropef("var NNI$1 = {kind: 2, offset: 0, typ: null, " & var n = ropef("var NNI$1 = {kind: 2, offset: 0, typ: null, " &
"name: null, len: $2, sons: [$3]};$n", [toRope(typ.id), toRope(length), s]) "name: null, len: $2, sons: [$3]};$n", [toRope(typ.id), toRope(length), s])
s = ropef("var $1 = {size: 0, kind: $2, base: null, node: null, " & s = ropef("var $1 = {size: 0, kind: $2, base: null, node: null, " &
@ -588,7 +590,7 @@ proc genRaiseStmt(p: var TProc, n: PNode, r: var TCompRes) =
typ = skipTypes(n.sons[0].typ, abstractPtrs) typ = skipTypes(n.sons[0].typ, abstractPtrs)
useMagic(p, "raiseException") useMagic(p, "raiseException")
appf(r.com, "raiseException($1, $2);$n", appf(r.com, "raiseException($1, $2);$n",
[a.res, makeCString(typ.sym.name.s)]) [a.res, makeJSString(typ.sym.name.s)])
else: else:
useMagic(p, "reraiseException") useMagic(p, "reraiseException")
app(r.com, "reraiseException();" & tnl) app(r.com, "reraiseException();" & tnl)
@ -626,7 +628,7 @@ proc genCaseStmt(p: var TProc, n: PNode, r: var TCompRes) =
if stringSwitch: if stringSwitch:
case e.kind case e.kind
of nkStrLit..nkTripleStrLit: appf(r.com, "case $1: ", of nkStrLit..nkTripleStrLit: appf(r.com, "case $1: ",
[makeCString(e.strVal)]) [makeJSString(e.strVal)])
else: InternalError(e.info, "ecmasgen.genCaseStmt: 2") else: InternalError(e.info, "ecmasgen.genCaseStmt: 2")
else: else:
appf(r.com, "case $1: ", [cond.res]) appf(r.com, "case $1: ", [cond.res])
@ -827,12 +829,12 @@ proc genFieldAddr(p: var TProc, n: PNode, r: var TCompRes) =
var b = if n.kind == nkHiddenAddr: n.sons[0] else: n var b = if n.kind == nkHiddenAddr: n.sons[0] else: n
gen(p, b.sons[0], a) gen(p, b.sons[0], a)
if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple: if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple:
r.res = makeCString("Field" & $getFieldPosition(b.sons[1])) r.res = makeJSString("Field" & $getFieldPosition(b.sons[1]))
else: else:
if b.sons[1].kind != nkSym: InternalError(b.sons[1].info, "genFieldAddr") if b.sons[1].kind != nkSym: InternalError(b.sons[1].info, "genFieldAddr")
var f = b.sons[1].sym var f = b.sons[1].sym
if f.loc.r == nil: f.loc.r = mangleName(f) if f.loc.r == nil: f.loc.r = mangleName(f)
r.res = makeCString(ropeToStr(f.loc.r)) r.res = makeJSString(ropeToStr(f.loc.r))
r.com = mergeExpr(a) r.com = mergeExpr(a)
proc genFieldAccess(p: var TProc, n: PNode, r: var TCompRes) = proc genFieldAccess(p: var TProc, n: PNode, r: var TCompRes) =
@ -903,7 +905,7 @@ proc genAddr(p: var TProc, n: PNode, r: var TCompRes) =
# globals are always indirect accessible # globals are always indirect accessible
r.kind = etyBaseIndex r.kind = etyBaseIndex
r.com = toRope("Globals") r.com = toRope("Globals")
r.res = makeCString(ropeToStr(s.loc.r)) r.res = makeJSString(ropeToStr(s.loc.r))
elif sfAddrTaken in s.flags: elif sfAddrTaken in s.flags:
r.kind = etyBaseIndex r.kind = etyBaseIndex
r.com = s.loc.r r.com = s.loc.r
@ -1422,8 +1424,8 @@ proc genReturnStmt(p: var TProc, n: PNode, r: var TCompRes) =
proc genProcBody(p: var TProc, prc: PSym, r: TCompRes): PRope = proc genProcBody(p: var TProc, prc: PSym, r: TCompRes): PRope =
if optStackTrace in prc.options: if optStackTrace in prc.options:
result = ropef("var F={procname:$1,prev:framePtr,filename:$2,line:0};$n" & result = ropef("var F={procname:$1,prev:framePtr,filename:$2,line:0};$n" &
"framePtr = F;$n", [makeCString(prc.owner.name.s & '.' & prc.name.s), "framePtr = F;$n", [makeJSString(prc.owner.name.s & '.' & prc.name.s),
makeCString(toFilename(prc.info))]) makeJSString(toFilename(prc.info))])
else: else:
result = nil result = nil
if p.beforeRetNeeded: if p.beforeRetNeeded:
@ -1540,9 +1542,9 @@ proc gen(p: var TProc, n: PNode, r: var TCompRes) =
of nkStrLit..nkTripleStrLit: of nkStrLit..nkTripleStrLit:
if skipTypes(n.typ, abstractVarRange).kind == tyString: if skipTypes(n.typ, abstractVarRange).kind == tyString:
useMagic(p, "cstrToNimstr") useMagic(p, "cstrToNimstr")
r.res = ropef("cstrToNimstr($1)", [makeCString(n.strVal)]) r.res = ropef("cstrToNimstr($1)", [makeJSString(n.strVal)])
else: else:
r.res = makeCString(n.strVal) r.res = makeJSString(n.strVal)
of nkFloatLit..nkFloat64Lit: of nkFloatLit..nkFloat64Lit:
f = n.floatVal f = n.floatVal
if f != f: r.res = toRope"NaN" if f != f: r.res = toRope"NaN"
@ -1611,8 +1613,8 @@ proc genModule(p: var TProc, n: PNode, r: var TCompRes) =
if optStackTrace in p.options: if optStackTrace in p.options:
r.com = ropef("var F = {procname:$1,prev:framePtr,filename:$2,line:0};$n" & r.com = ropef("var F = {procname:$1,prev:framePtr,filename:$2,line:0};$n" &
"framePtr = F;$n" & "$3" & "framePtr = framePtr.prev;$n", [ "framePtr = F;$n" & "$3" & "framePtr = framePtr.prev;$n", [
makeCString("module " & p.module.module.name.s), makeJSString("module " & p.module.module.name.s),
makeCString(toFilename(p.module.module.info)), r.com]) makeJSString(toFilename(p.module.module.info)), r.com])
proc myProcess(b: PPassContext, n: PNode): PNode = proc myProcess(b: PPassContext, n: PNode): PNode =
if passes.skipCodegen(n): return n if passes.skipCodegen(n): return n

View file

@ -72,9 +72,9 @@ proc HandleCmdLine() =
when defined(GC_setMaxPause): when defined(GC_setMaxPause):
GC_setMaxPause 2_000 GC_setMaxPause 2_000
when compileOption("gc", "v2"): when compileOption("gc", "v2") or compileOption("gc", "refc"):
# the new correct mark&sweet collector is too slow :-/ # the new correct mark&sweet collector is too slow :-/
GC_disableMarkAndSweep() GC_disableMarkAndSweep()
condsyms.InitDefines() condsyms.InitDefines()
HandleCmdLine() HandleCmdLine()
quit(options.gExitcode) quit(int8(msgs.gErrorCounter > 0))

View file

@ -81,7 +81,7 @@ type # please make sure we have under 32 options
cmdRun # run the project via TCC backend cmdRun # run the project via TCC backend
TStringSeq* = seq[string] TStringSeq* = seq[string]
TGCMode* = enum # the selected GC TGCMode* = enum # the selected GC
gcNone, gcBoehm, gcRefc, gcV2 # gcNone, gcBoehm, gcMarkAndSweep, gcRefc, gcV2
const const
ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck, ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck,
@ -269,7 +269,6 @@ proc binaryStrSearch*(x: openarray[string], y: string): int =
return mid return mid
result = - 1 result = - 1
# Can we keep this? I'm using it all the time
template nimdbg*: expr = c.module.fileIdx == gProjectMainIdx template nimdbg*: expr = c.module.fileIdx == gProjectMainIdx
template cnimdbg*: expr = p.module.module.fileIdx == gProjectMainIdx template cnimdbg*: expr = p.module.module.fileIdx == gProjectMainIdx
template pnimdbg*: expr = p.lex.fileIdx == gProjectMainIdx template pnimdbg*: expr = p.lex.fileIdx == gProjectMainIdx

View file

@ -64,7 +64,8 @@ Advanced options:
--skipUserCfg do not read the user's configuration file --skipUserCfg do not read the user's configuration file
--skipParentCfg do not read the parent dirs' configuration files --skipParentCfg do not read the parent dirs' configuration files
--skipProjCfg do not read the project's configuration file --skipProjCfg do not read the project's configuration file
--gc:refc|v2|boehm|none use Nimrod's native GC|V2|Boehm GC|no GC --gc:refc|v2|markAndSweep|boehm|none
select the GC to use; default is 'refc'
--index:on|off turn index file generation on|off --index:on|off turn index file generation on|off
--putenv:key=value set an environment variable --putenv:key=value set an environment variable
--babelPath:PATH add a path for Babel support --babelPath:PATH add a path for Babel support

View file

@ -911,7 +911,21 @@ Arrays are always bounds checked (at compile-time or at runtime). These
checks can be disabled via pragmas or invoking the compiler with the checks can be disabled via pragmas or invoking the compiler with the
``--boundChecks:off`` command line switch. ``--boundChecks:off`` command line switch.
The current implementation does not support nested open arrays.
Open arrays
-----------
Often fixed size arrays turn out to be too inflexible; procedures should
be able to deal with arrays of different sizes. The `openarray`:idx: type
allows this; it can only be used for parameters. Openarrays are always
indexed with an ``int`` starting at position 0. The ``len``, ``low``
and ``high`` operations are available for open arrays too. Any array with
a compatible base type can be passed to an openarray parameter, the index
type does not matter. In addition to arrays sequences can also be passed
to an open array parameter.
The openarray type cannot be nested: multidimensional openarrays are not
supported because this is seldom needed and cannot be done efficiently.
Varargs Varargs

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -39,7 +39,7 @@ type
len, cap: int len, cap: int
d: PCellArray d: PCellArray
# ------------------- cell set handling --------------------------------------- # ------------------- cell seq handling ---------------------------------------
proc contains(s: TCellSeq, c: PCell): bool {.inline.} = proc contains(s: TCellSeq, c: PCell): bool {.inline.} =
for i in 0 .. s.len-1: for i in 0 .. s.len-1:
@ -68,6 +68,8 @@ proc deinit(s: var TCellSeq) =
s.len = 0 s.len = 0
s.cap = 0 s.cap = 0
# ------------------- cell set handling ---------------------------------------
const const
InitCellSetSize = 1024 # must be a power of two! InitCellSetSize = 1024 # must be a power of two!
@ -196,3 +198,21 @@ iterator elements(t: TCellSet): PCell {.inline.} =
inc(i) inc(i)
r = r.next r = r.next
iterator elementsExcept(t, s: TCellSet): PCell {.inline.} =
var r = t.head
while r != nil:
let ss = CellSetGet(s, r.key)
var i = 0
while i <= high(r.bits):
var w = r.bits[i]
if ss != nil:
w = w and not ss.bits[i]
var j = 0
while w != 0:
if (w and 1) != 0:
yield cast[PCell]((r.key shl PageShift) or
(i shl IntShift +% j) *% MemAlign)
inc(j)
w = w shr 1
inc(i)
r = r.next

View file

@ -299,7 +299,17 @@ type
setAttribute*: proc (name, value: cstring) {.nimcall.} setAttribute*: proc (name, value: cstring) {.nimcall.}
setAttributeNode*: proc (attr: ref TNode) {.nimcall.} setAttributeNode*: proc (attr: ref TNode) {.nimcall.}
when defined(nodejs): when defined(kwin):
proc rawEcho {.compilerproc, nostackframe.} =
asm """
var buf = "";
for (var i = 0; i < arguments.length; ++i) {
buf += `toEcmaStr`(arguments[i]);
}
print(buf);
"""
elif defined(nodejs):
proc ewriteln(x: cstring) = log(x) proc ewriteln(x: cstring) = log(x)
proc rawEcho {.compilerproc, nostackframe.} = proc rawEcho {.compilerproc, nostackframe.} =

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -18,6 +18,9 @@
# for soft real time applications (like games). # for soft real time applications (like games).
{.push profiler:off.} {.push profiler:off.}
# XXX there is still a slight chance of leaking cycles as we don't add cycle
# candidates in 'incRef'
const const
CycleIncrease = 2 # is a multiplicative increase CycleIncrease = 2 # is a multiplicative increase
InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow
@ -33,19 +36,17 @@ when defined(memProfiler):
const const
rcIncrement = 0b1000 # so that lowest 3 bits are not touched rcIncrement = 0b1000 # so that lowest 3 bits are not touched
# NOTE: Most colors are currently unused
rcBlack = 0b000 # cell is colored black; in use or free rcBlack = 0b000 # cell is colored black; in use or free
rcGray = 0b001 # possible member of a cycle rcGray = 0b001 # possible member of a cycle
rcWhite = 0b010 # member of a garbage cycle rcWhite = 0b010 # member of a garbage cycle
rcPurple = 0b011 # possible root of a cycle rcPurple = 0b011 # possible root of a cycle
rcZct = 0b100 # in ZCT ZctFlag = 0b100 # in ZCT
rcRed = 0b101 # Candidate cycle undergoing sigma-computation
rcOrange = 0b110 # Candidate cycle awaiting epoch boundary
rcShift = 3 # shift by rcShift to get the reference counter rcShift = 3 # shift by rcShift to get the reference counter
colorMask = 0b111 colorMask = 0b011
type type
TWalkOp = enum TWalkOp = enum
waZctDecRef, waPush, waCycleDecRef waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack,
waCollectWhite
TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.} TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.}
# A ref type can have a finalizer that is called before the object's # A ref type can have a finalizer that is called before the object's
@ -88,9 +89,15 @@ template release(gch: TGcHeap) =
when hasThreadSupport and hasSharedHeap: when hasThreadSupport and hasSharedHeap:
releaseSys(HeapLock) releaseSys(HeapLock)
template gcAssert(cond: bool, msg: string) =
when defined(useGcAssert):
if not cond:
echo "[GCASSERT] ", msg
quit 1
proc addZCT(s: var TCellSeq, c: PCell) {.noinline.} = proc addZCT(s: var TCellSeq, c: PCell) {.noinline.} =
if (c.refcount and rcZct) == 0: if (c.refcount and ZctFlag) == 0:
c.refcount = c.refcount and not colorMask or rcZct c.refcount = c.refcount or ZctFlag
add(s, c) add(s, c)
proc cellToUsr(cell: PCell): pointer {.inline.} = proc cellToUsr(cell: PCell): pointer {.inline.} =
@ -115,68 +122,22 @@ proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
when BitsPerPage mod (sizeof(int)*8) != 0: when BitsPerPage mod (sizeof(int)*8) != 0:
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
when debugGC: template color(c): expr = c.refCount and colorMask
proc writeCell(msg: CString, c: PCell) = template setColor(c, col) =
when col == rcBlack:
c.refcount = c.refCount and not colorMask
else:
c.refcount = c.refCount and not colorMask or col
proc writeCell(msg: CString, c: PCell) =
var kind = -1 var kind = -1
if c.typ != nil: kind = ord(c.typ.kind) if c.typ != nil: kind = ord(c.typ.kind)
when leakDetector: when leakDetector:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n", c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
msg, c, kind, c.refcount shr rcShift, c.filename, c.line) msg, c, kind, c.refcount shr rcShift, c.filename, c.line)
else: else:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld\n", c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld; color=%ld\n",
msg, c, kind, c.refcount shr rcShift) msg, c, kind, c.refcount shr rcShift, c.color)
when traceGC:
# traceGC is a special switch to enable extensive debugging
type
TCellState = enum
csAllocated, csZctFreed, csCycFreed
var
states: array[TCellState, TCellSet]
proc traceCell(c: PCell, state: TCellState) =
case state
of csAllocated:
if c in states[csAllocated]:
writeCell("attempt to alloc an already allocated cell", c)
sysAssert(false, "traceCell 1")
excl(states[csCycFreed], c)
excl(states[csZctFreed], c)
of csZctFreed:
if c in states[csZctFreed]:
writeCell("attempt to free zct cell twice", c)
sysAssert(false, "traceCell 2")
if c in states[csCycFreed]:
writeCell("attempt to free with zct, but already freed with cyc", c)
sysAssert(false, "traceCell 3")
if c notin states[csAllocated]:
writeCell("attempt to free not an allocated cell", c)
sysAssert(false, "traceCell 4")
excl(states[csAllocated], c)
of csCycFreed:
if c notin states[csAllocated]:
writeCell("attempt to free a not allocated cell", c)
sysAssert(false, "traceCell 5")
if c in states[csCycFreed]:
writeCell("attempt to free cyc cell twice", c)
sysAssert(false, "traceCell 6")
if c in states[csZctFreed]:
writeCell("attempt to free with cyc, but already freed with zct", c)
sysAssert(false, "traceCell 7")
excl(states[csAllocated], c)
incl(states[state], c)
proc writeLeakage() =
var z = 0
var y = 0
var e = 0
for c in elements(states[csAllocated]):
inc(e)
if c in states[csZctFreed]: inc(z)
elif c in states[csCycFreed]: inc(y)
else: writeCell("leak", c)
cfprintf(cstdout, "Allocations: %ld; ZCT freed: %ld; CYC freed: %ld\n",
e, z, y)
template gcTrace(cell, state: expr): stmt {.immediate.} = template gcTrace(cell, state: expr): stmt {.immediate.} =
when traceGC: traceCell(cell, state) when traceGC: traceCell(cell, state)
@ -213,6 +174,8 @@ proc rtlAddCycleRoot(c: PCell) {.rtl, inl.} =
# we MUST access gch as a global here, because this crosses DLL boundaries! # we MUST access gch as a global here, because this crosses DLL boundaries!
when hasThreadSupport and hasSharedHeap: when hasThreadSupport and hasSharedHeap:
AcquireSys(HeapLock) AcquireSys(HeapLock)
if c.color != rcPurple:
c.setColor(rcPurple)
incl(gch.cycleRoots, c) incl(gch.cycleRoots, c)
when hasThreadSupport and hasSharedHeap: when hasThreadSupport and hasSharedHeap:
ReleaseSys(HeapLock) ReleaseSys(HeapLock)
@ -226,20 +189,22 @@ proc rtlAddZCT(c: PCell) {.rtl, inl.} =
ReleaseSys(HeapLock) ReleaseSys(HeapLock)
proc decRef(c: PCell) {.inline.} = proc decRef(c: PCell) {.inline.} =
sysAssert(isAllocatedPtr(gch.region, c), "decRef: interiorPtr") gcAssert(isAllocatedPtr(gch.region, c), "decRef: interiorPtr")
sysAssert(c.refcount >=% rcIncrement, "decRef") gcAssert(c.refcount >=% rcIncrement, "decRef")
if --c.refcount: if --c.refcount:
rtlAddZCT(c) rtlAddZCT(c)
elif canBeCycleRoot(c): elif canBeCycleRoot(c):
# unfortunately this is necessary here too, because a cycle might just # unfortunately this is necessary here too, because a cycle might just
# have been broken up and we could recycle it. # have been broken up and we could recycle it.
rtlAddCycleRoot(c) rtlAddCycleRoot(c)
#writeCell("decRef", c)
proc incRef(c: PCell) {.inline.} = proc incRef(c: PCell) {.inline.} =
sysAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr") gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr")
++c.refcount c.refcount = c.refCount +% rcIncrement and not colorMask
if canBeCycleRoot(c): #writeCell("incRef", c)
rtlAddCycleRoot(c) #if canBeCycleRoot(c):
# rtlAddCycleRoot(c)
proc nimGCref(p: pointer) {.compilerProc, inline.} = incRef(usrToCell(p)) proc nimGCref(p: pointer) {.compilerProc, inline.} = incRef(usrToCell(p))
proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p)) proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p))
@ -247,7 +212,7 @@ proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p))
proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} = proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} =
sysAssert(allocInv(gch.region), "begin nimGCunrefNoCycle") sysAssert(allocInv(gch.region), "begin nimGCunrefNoCycle")
var c = usrToCell(p) var c = usrToCell(p)
sysAssert(isAllocatedPtr(gch.region, c), "nimGCunrefNoCycle: isAllocatedPtr") gcAssert(isAllocatedPtr(gch.region, c), "nimGCunrefNoCycle: isAllocatedPtr")
if --c.refcount: if --c.refcount:
rtlAddZCT(c) rtlAddZCT(c)
sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 2") sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 2")
@ -255,7 +220,7 @@ proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} =
proc asgnRef(dest: ppointer, src: pointer) {.compilerProc, inline.} = proc asgnRef(dest: ppointer, src: pointer) {.compilerProc, inline.} =
# the code generator calls this proc! # the code generator calls this proc!
sysAssert(not isOnStack(dest), "asgnRef") gcAssert(not isOnStack(dest), "asgnRef")
# BUGFIX: first incRef then decRef! # BUGFIX: first incRef then decRef!
if src != nil: incRef(usrToCell(src)) if src != nil: incRef(usrToCell(src))
if dest[] != nil: decRef(usrToCell(dest[])) if dest[] != nil: decRef(usrToCell(dest[]))
@ -285,14 +250,14 @@ proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerProc.} =
if cast[int](dest[]) >=% PageSize: decRef(usrToCell(dest[])) if cast[int](dest[]) >=% PageSize: decRef(usrToCell(dest[]))
else: else:
# can't be an interior pointer if it's a stack location! # can't be an interior pointer if it's a stack location!
sysAssert(interiorAllocatedPtr(gch.region, dest)==nil, gcAssert(interiorAllocatedPtr(gch.region, dest) == nil,
"stack loc AND interior pointer") "stack loc AND interior pointer")
dest[] = src dest[] = src
proc initGC() = proc initGC() =
when not defined(useNimRtl): when not defined(useNimRtl):
when traceGC: when traceGC:
for i in low(TCellState)..high(TCellState): Init(states[i]) for i in low(TCellState)..high(TCellState): init(states[i])
gch.cycleThreshold = InitialCycleThreshold gch.cycleThreshold = InitialCycleThreshold
gch.stat.stackScans = 0 gch.stat.stackScans = 0
gch.stat.cycleCollections = 0 gch.stat.cycleCollections = 0
@ -303,8 +268,8 @@ proc initGC() =
# init the rt # init the rt
init(gch.zct) init(gch.zct)
init(gch.tempStack) init(gch.tempStack)
Init(gch.cycleRoots) init(gch.cycleRoots)
Init(gch.decStack) init(gch.decStack)
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) = proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
var d = cast[TAddress](dest) var d = cast[TAddress](dest)
@ -341,9 +306,9 @@ proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
else: nil else: nil
proc forAllChildren(cell: PCell, op: TWalkOp) = proc forAllChildren(cell: PCell, op: TWalkOp) =
sysAssert(cell != nil, "forAllChildren: 1") gcAssert(cell != nil, "forAllChildren: 1")
sysAssert(cell.typ != nil, "forAllChildren: 2") gcAssert(cell.typ != nil, "forAllChildren: 2")
sysAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 3" gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 3"
let marker = cell.typ.marker let marker = cell.typ.marker
if marker != nil: if marker != nil:
marker(cellToUsr(cell), op.int) marker(cellToUsr(cell), op.int)
@ -378,7 +343,7 @@ proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} =
template replaceZctEntry(i: expr) = template replaceZctEntry(i: expr) =
c = d[i] c = d[i]
if c.refcount >=% rcIncrement: if c.refcount >=% rcIncrement:
c.refcount = c.refcount and not colorMask c.refcount = c.refcount and not ZctFlag
d[i] = res d[i] = res
return return
if L > 8: if L > 8:
@ -399,7 +364,7 @@ proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} =
for i in countdown(L-1, max(0, L-8)): for i in countdown(L-1, max(0, L-8)):
var c = d[i] var c = d[i]
if c.refcount >=% rcIncrement: if c.refcount >=% rcIncrement:
c.refcount = c.refcount and not colorMask c.refcount = c.refcount and not ZctFlag
d[i] = res d[i] = res
return return
add(gch.zct, res) add(gch.zct, res)
@ -407,18 +372,19 @@ proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} =
proc rawNewObj(typ: PNimType, size: int, gch: var TGcHeap): pointer = proc rawNewObj(typ: PNimType, size: int, gch: var TGcHeap): pointer =
# generates a new object and sets its reference counter to 0 # generates a new object and sets its reference counter to 0
acquire(gch) acquire(gch)
sysAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1")
collectCT(gch) collectCT(gch)
sysAssert(allocInv(gch.region), "rawNewObj begin") sysAssert(allocInv(gch.region), "rawNewObj begin")
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(TCell))) var res = cast[PCell](rawAlloc(gch.region, size + sizeof(TCell)))
sysAssert((cast[TAddress](res) and (MemAlign-1)) == 0, "newObj: 2") gcAssert((cast[TAddress](res) and (MemAlign-1)) == 0, "newObj: 2")
# now it is buffered in the ZCT # now it is buffered in the ZCT
res.typ = typ res.typ = typ
when leakDetector and not hasThreadSupport: when leakDetector and not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil: if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename res.filename = framePtr.prev.filename
res.line = framePtr.prev.line res.line = framePtr.prev.line
res.refcount = rcZct # refcount is zero, but mark it to be in the ZCT # refcount is zero, color is black, but mark it to be in the ZCT
res.refcount = ZctFlag
sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3")
# its refcount is zero, so add it to the ZCT: # its refcount is zero, so add it to the ZCT:
addNewObjToZCT(res, gch) addNewObjToZCT(res, gch)
@ -447,7 +413,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
# generates a new object and sets its reference counter to 1 # generates a new object and sets its reference counter to 1
sysAssert(allocInv(gch.region), "newObjRC1 begin") sysAssert(allocInv(gch.region), "newObjRC1 begin")
acquire(gch) acquire(gch)
sysAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1")
collectCT(gch) collectCT(gch)
sysAssert(allocInv(gch.region), "newObjRC1 after collectCT") sysAssert(allocInv(gch.region), "newObjRC1 after collectCT")
@ -482,7 +448,7 @@ proc growObj(old: pointer, newsize: int, gch: var TGcHeap): pointer =
collectCT(gch) collectCT(gch)
var ol = usrToCell(old) var ol = usrToCell(old)
sysAssert(ol.typ != nil, "growObj: 1") sysAssert(ol.typ != nil, "growObj: 1")
sysAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2")
sysAssert(allocInv(gch.region), "growObj begin") sysAssert(allocInv(gch.region), "growObj begin")
var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(TCell))) var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(TCell)))
@ -499,7 +465,7 @@ proc growObj(old: pointer, newsize: int, gch: var TGcHeap): pointer =
# add(gch.zct, res) # add(gch.zct, res)
#else: # XXX: what to do here? #else: # XXX: what to do here?
# decRef(ol) # decRef(ol)
if (ol.refcount and colorMask) == rcZct: if (ol.refcount and ZctFlag) != 0:
var j = gch.zct.len-1 var j = gch.zct.len-1
var d = gch.zct.d var d = gch.zct.d
while j >= 0: while j >= 0:
@ -529,74 +495,122 @@ proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
# ---------------- cycle collector ------------------------------------------- # ---------------- cycle collector -------------------------------------------
proc doOperation(p: pointer, op: TWalkOp) = proc freeCyclicCell(gch: var TGcHeap, c: PCell) =
if p == nil: return
var c: PCell = usrToCell(p)
sysAssert(c != nil, "doOperation: 1")
case op # faster than function pointers because of easy prediction
of waZctDecRef:
#if not isAllocatedPtr(gch.region, c):
# return
# c_fprintf(c_stdout, "[GC] decref bug: %p", c)
sysAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
sysAssert(c.refcount >=% rcIncrement, "doOperation 2")
c.refcount = c.refcount -% rcIncrement
when logGC: writeCell("decref (from doOperation)", c)
if c.refcount <% rcIncrement: addZCT(gch.zct, c)
of waPush:
add(gch.tempStack, c)
of waCycleDecRef:
sysAssert(c.refcount >=% rcIncrement, "doOperation 3")
c.refcount = c.refcount -% rcIncrement
proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
doOperation(d, TWalkOp(op))
# we now use a much simpler and non-recursive algorithm for cycle removal
proc collectCycles(gch: var TGcHeap) =
var tabSize = 0
for c in elements(gch.cycleRoots):
inc(tabSize)
forallChildren(c, waCycleDecRef)
if tabSize == 0: return
gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize)
# restore reference counts (a depth-first traversal is needed):
var marker: TCellSet
Init(marker)
for c in elements(gch.cycleRoots):
if c.refcount >=% rcIncrement:
if not containsOrIncl(marker, c):
gch.tempStack.len = 0
forAllChildren(c, waPush)
while gch.tempStack.len > 0:
dec(gch.tempStack.len)
var d = gch.tempStack.d[gch.tempStack.len]
d.refcount = d.refcount +% rcIncrement
if d in gch.cycleRoots and not containsOrIncl(marker, d):
forAllChildren(d, waPush)
Deinit(marker)
# remove cycles:
for c in elements(gch.cycleRoots):
if c.refcount <% rcIncrement:
gch.tempStack.len = 0
forAllChildren(c, waPush)
while gch.tempStack.len > 0:
dec(gch.tempStack.len)
var d = gch.tempStack.d[gch.tempStack.len]
if d.refcount <% rcIncrement:
if d notin gch.cycleRoots: # d is leaf of c and not part of cycle
addZCT(gch.zct, d)
when logGC: writeCell("add to ZCT (from cycle collector)", d)
prepareDealloc(c) prepareDealloc(c)
gcTrace(c, csCycFreed) gcTrace(c, csCycFreed)
when logGC: writeCell("cycle collector dealloc cell", c) when logGC: writeCell("cycle collector dealloc cell", c)
when reallyDealloc: rawDealloc(gch.region, c) when reallyDealloc: rawDealloc(gch.region, c)
else: else:
sysAssert(c.typ != nil, "collectCycles") gcAssert(c.typ != nil, "freeCyclicCell")
zeroMem(c, sizeof(TCell)) zeroMem(c, sizeof(TCell))
proc markGray(s: PCell) =
if s.color != rcGray:
setColor(s, rcGray)
forAllChildren(s, waMarkGray)
proc scanBlack(s: PCell) =
s.setColor(rcBlack)
forAllChildren(s, waScanBlack)
proc scan(s: PCell) =
if s.color == rcGray:
if s.refcount >=% rcIncrement:
scanBlack(s)
else:
s.setColor(rcWhite)
forAllChildren(s, waScan)
proc collectWhite(s: PCell) =
if s.color == rcWhite and s notin gch.cycleRoots:
s.setcolor(rcBlack)
forAllChildren(s, waCollectWhite)
freeCyclicCell(gch, s)
proc MarkRoots(gch: var TGcHeap) =
var tabSize = 0
for s in elements(gch.cycleRoots):
#writeCell("markRoot", s)
inc tabSize
if s.color == rcPurple and s.refCount >=% rcIncrement:
markGray(s)
else:
excl(gch.cycleRoots, s)
# (s.color == rcBlack and rc == 0) as 1 condition:
if s.refcount == 0:
freeCyclicCell(gch, s)
gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize)
proc doOperation(p: pointer, op: TWalkOp) =
if p == nil: return
var c: PCell = usrToCell(p)
gcAssert(c != nil, "doOperation: 1")
# the 'case' should be faster than function pointers because of easy
# prediction:
case op
of waZctDecRef:
#if not isAllocatedPtr(gch.region, c):
# return
# c_fprintf(c_stdout, "[GC] decref bug: %p", c)
gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef")
gcAssert(c.refcount >=% rcIncrement, "doOperation 2")
#c.refcount = c.refcount -% rcIncrement
when logGC: writeCell("decref (from doOperation)", c)
decRef(c)
#if c.refcount <% rcIncrement: addZCT(gch.zct, c)
of waPush:
add(gch.tempStack, c)
of waCycleDecRef:
gcAssert(c.refcount >=% rcIncrement, "doOperation 3")
c.refcount = c.refcount -% rcIncrement
of waMarkGray:
gcAssert(c.refcount >=% rcIncrement, "waMarkGray")
c.refcount = c.refcount -% rcIncrement
markGray(c)
of waScan: scan(c)
of waScanBlack:
c.refcount = c.refcount +% rcIncrement
if c.color != rcBlack:
scanBlack(c)
of waCollectWhite: collectWhite(c)
proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
doOperation(d, TWalkOp(op))
proc CollectZCT(gch: var TGcHeap): bool
proc collectRoots(gch: var TGcHeap) =
for s in elements(gch.cycleRoots):
excl(gch.cycleRoots, s)
collectWhite(s)
proc collectCycles(gch: var TGcHeap) =
# ensure the ZCT 'color' is not used:
while gch.zct.len > 0: discard collectZCT(gch)
markRoots(gch)
# scanRoots:
for s in elements(gch.cycleRoots): scan(s)
collectRoots(gch)
Deinit(gch.cycleRoots) Deinit(gch.cycleRoots)
Init(gch.cycleRoots) Init(gch.cycleRoots)
# alive cycles need to be kept in 'cycleRoots' if they are referenced
# from the stack; otherwise the write barrier will add the cycle root again
# anyway:
when false:
var d = gch.decStack.d
var cycleRootsLen = 0
for i in 0..gch.decStack.len-1:
var c = d[i]
gcAssert isAllocatedPtr(gch.region, c), "addBackStackRoots"
gcAssert c.refcount >=% rcIncrement, "addBackStackRoots: dead cell"
if canBeCycleRoot(c):
#if c notin gch.cycleRoots:
inc cycleRootsLen
incl(gch.cycleRoots, c)
gcAssert c.typ != nil, "addBackStackRoots 2"
if cycleRootsLen != 0:
cfprintf(cstdout, "cycle roots: %ld\n", cycleRootsLen)
proc gcMark(gch: var TGcHeap, p: pointer) {.inline.} = proc gcMark(gch: var TGcHeap, p: pointer) {.inline.} =
# the addresses are not as cells on the stack, so turn them to cells: # the addresses are not as cells on the stack, so turn them to cells:
@ -778,9 +792,9 @@ proc CollectZCT(gch: var TGcHeap): bool =
var c = gch.zct.d[0] var c = gch.zct.d[0]
sysAssert(isAllocatedPtr(gch.region, c), "CollectZCT: isAllocatedPtr") sysAssert(isAllocatedPtr(gch.region, c), "CollectZCT: isAllocatedPtr")
# remove from ZCT: # remove from ZCT:
sysAssert((c.refcount and rcZct) == rcZct, "collectZCT") gcAssert((c.refcount and ZctFlag) == ZctFlag, "collectZCT")
c.refcount = c.refcount and not colorMask c.refcount = c.refcount and not ZctFlag
gch.zct.d[0] = gch.zct.d[L[] - 1] gch.zct.d[0] = gch.zct.d[L[] - 1]
dec(L[]) dec(L[])
when withRealtime: dec steps when withRealtime: dec steps
@ -809,7 +823,7 @@ proc CollectZCT(gch: var TGcHeap): bool =
if gch.maxPause > 0: if gch.maxPause > 0:
let duration = getticks() - t0 let duration = getticks() - t0
# the GC's measuring is not accurate and needs some cleanup actions # the GC's measuring is not accurate and needs some cleanup actions
# (stack unmarking), so subtract some short amount of time in to # (stack unmarking), so subtract some short amount of time in
# order to miss deadlines less often: # order to miss deadlines less often:
if duration >= gch.maxPause - 50_000: if duration >= gch.maxPause - 50_000:
return false return false
@ -819,12 +833,12 @@ proc unmarkStackAndRegisters(gch: var TGcHeap) =
var d = gch.decStack.d var d = gch.decStack.d
for i in 0..gch.decStack.len-1: for i in 0..gch.decStack.len-1:
sysAssert isAllocatedPtr(gch.region, d[i]), "unmarkStackAndRegisters" sysAssert isAllocatedPtr(gch.region, d[i]), "unmarkStackAndRegisters"
# decRef(d[i]) inlined: cannot create a cycle and must not acquire lock decRef(d[i])
var c = d[i] #var c = d[i]
# XXX no need for an atomic dec here: # XXX no need for an atomic dec here:
if --c.refcount: #if --c.refcount:
addZCT(gch.zct, c) # addZCT(gch.zct, c)
sysAssert c.typ != nil, "unmarkStackAndRegisters 2" #sysAssert c.typ != nil, "unmarkStackAndRegisters 2"
gch.decStack.len = 0 gch.decStack.len = 0
proc collectCTBody(gch: var TGcHeap) = proc collectCTBody(gch: var TGcHeap) =
@ -843,7 +857,7 @@ proc collectCTBody(gch: var TGcHeap) =
when cycleGC: when cycleGC:
if getOccupiedMem(gch.region) >= gch.cycleThreshold or alwaysCycleGC: if getOccupiedMem(gch.region) >= gch.cycleThreshold or alwaysCycleGC:
collectCycles(gch) collectCycles(gch)
discard collectZCT(gch) #discard collectZCT(gch)
inc(gch.stat.cycleCollections) inc(gch.stat.cycleCollections)
gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() *
cycleIncrease) cycleIncrease)
@ -929,7 +943,6 @@ when not defined(useNimRtl):
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
"[GC] max stack size: " & $gch.stat.maxStackSize & "\n" & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" &
"[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000)
when traceGC: writeLeakage()
GC_enable() GC_enable()
{.pop.} {.pop.}

504
lib/system/gc_ms.nim Normal file
View file

@ -0,0 +1,504 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# A simple mark&sweep garbage collector for Nimrod.
{.push profiler:off.}
const
InitialThreshold = 4*1024*1024 # X MB because marking&sweeping is slow
template mulThreshold(x): expr {.immediate.} = x * 2
when defined(memProfiler):
proc nimProfile(requestedSize: int)
type
TWalkOp = enum
waMarkGlobal, # we need to mark conservatively for global marker procs
# as these may refer to a global var and not to a thread
# local
waMarkPrecise # fast precise marking
TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.}
# A ref type can have a finalizer that is called before the object's
# storage is freed.
TGlobalMarkerProc = proc () {.nimcall.}
TGcStat = object
collections: int # number of performed full collections
maxThreshold: int # max threshold that has been set
maxStackSize: int # max stack size
freedObjects: int # max entries in cycle table
TGcHeap = object # this contains the zero count and
# non-zero count table
stackBottom: pointer
cycleThreshold: int
allocated, marked: TCellSet
tempStack: TCellSeq # temporary stack for recursion elimination
recGcLock: int # prevent recursion via finalizers; no thread lock
region: TMemRegion # garbage collected region
stat: TGcStat
var
gch {.rtlThreadVar.}: TGcHeap
when not defined(useNimRtl):
InstantiateForRegion(gch.region)
template acquire(gch: TGcHeap) =
when hasThreadSupport and hasSharedHeap:
AcquireSys(HeapLock)
template release(gch: TGcHeap) =
when hasThreadSupport and hasSharedHeap:
releaseSys(HeapLock)
template gcAssert(cond: bool, msg: string) =
when defined(useGcAssert):
if not cond:
echo "[GCASSERT] ", msg
quit 1
proc cellToUsr(cell: PCell): pointer {.inline.} =
# convert object (=pointer to refcount) to pointer to userdata
result = cast[pointer](cast[TAddress](cell)+%TAddress(sizeof(TCell)))
proc usrToCell(usr: pointer): PCell {.inline.} =
# convert pointer to userdata to object (=pointer to refcount)
result = cast[PCell](cast[TAddress](usr)-%TAddress(sizeof(TCell)))
proc canbeCycleRoot(c: PCell): bool {.inline.} =
result = ntfAcyclic notin c.typ.flags
proc extGetCellType(c: pointer): PNimType {.compilerproc.} =
# used for code generation concerning debugging
result = usrToCell(c).typ
proc unsureAsgnRef(dest: ppointer, src: pointer) {.inline.} =
dest[] = src
proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
result = 0
var
globalMarkersLen: int
globalMarkers: array[0.. 7_000, TGlobalMarkerProc]
proc nimRegisterGlobalMarker(markerProc: TGlobalMarkerProc) {.compilerProc.} =
if globalMarkersLen <= high(globalMarkers):
globalMarkers[globalMarkersLen] = markerProc
inc globalMarkersLen
else:
echo "[GC] cannot register global variable; too many global variables"
quit 1
# this that has to equals zero, otherwise we have to round up UnitsPerPage:
when BitsPerPage mod (sizeof(int)*8) != 0:
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
# forward declarations:
proc collectCT(gch: var TGcHeap)
proc IsOnStack*(p: pointer): bool {.noinline.}
proc forAllChildren(cell: PCell, op: TWalkOp)
proc doOperation(p: pointer, op: TWalkOp)
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp)
# we need the prototype here for debugging purposes
proc prepareDealloc(cell: PCell) =
if cell.typ.finalizer != nil:
# the finalizer could invoke something that
# allocates memory; this could trigger a garbage
# collection. Since we are already collecting we
# prevend recursive entering here by a lock.
# XXX: we should set the cell's children to nil!
inc(gch.recGcLock)
(cast[TFinalizer](cell.typ.finalizer))(cellToUsr(cell))
dec(gch.recGcLock)
proc nimGCref(p: pointer) {.compilerProc, inline.} =
# we keep it from being collected by pretending it's not even allocated:
excl(gch.allocated, usrToCell(p))
proc nimGCunref(p: pointer) {.compilerProc, inline.} =
incl(gch.allocated, usrToCell(p))
proc initGC() =
when not defined(useNimRtl):
gch.cycleThreshold = InitialThreshold
gch.stat.collections = 0
gch.stat.maxThreshold = 0
gch.stat.maxStackSize = 0
init(gch.tempStack)
Init(gch.allocated)
init(gch.marked)
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
var d = cast[TAddress](dest)
case n.kind
of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op)
of nkList:
for i in 0..n.len-1:
forAllSlotsAux(dest, n.sons[i], op)
of nkCase:
var m = selectBranch(dest, n)
if m != nil: forAllSlotsAux(dest, m, op)
of nkNone: sysAssert(false, "forAllSlotsAux")
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
var d = cast[TAddress](dest)
if dest == nil: return # nothing to do
if ntfNoRefs notin mt.flags:
case mt.Kind
of tyRef, tyString, tySequence: # leaf:
doOperation(cast[ppointer](d)[], op)
of tyObject, tyTuple:
forAllSlotsAux(dest, mt.node, op)
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)
else: nil
proc forAllChildren(cell: PCell, op: TWalkOp) =
gcAssert(cell != nil, "forAllChildren: 1")
gcAssert(cell.typ != nil, "forAllChildren: 2")
gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 3"
let marker = cell.typ.marker
if marker != nil:
marker(cellToUsr(cell), op.int)
else:
case cell.typ.Kind
of tyRef: # common case
forAllChildrenAux(cellToUsr(cell), cell.typ.base, op)
of tySequence:
var d = cast[TAddress](cellToUsr(cell))
var s = cast[PGenericSeq](d)
if s != nil:
for i in 0..s.len-1:
forAllChildrenAux(cast[pointer](d +% i *% cell.typ.base.size +%
GenericSeqSize), cell.typ.base, op)
else: nil
proc rawNewObj(typ: PNimType, size: int, gch: var TGcHeap): pointer =
# generates a new object and sets its reference counter to 0
acquire(gch)
gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1")
collectCT(gch)
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(TCell)))
gcAssert((cast[TAddress](res) and (MemAlign-1)) == 0, "newObj: 2")
# now it is buffered in the ZCT
res.typ = typ
when leakDetector and not hasThreadSupport:
if framePtr != nil and framePtr.prev != nil:
res.filename = framePtr.prev.filename
res.line = framePtr.prev.line
res.refcount = 0
release(gch)
incl(gch.allocated, res)
result = cellToUsr(res)
{.pop.}
proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(typ, size, gch)
zeroMem(result, size)
when defined(memProfiler): nimProfile(size)
proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
# `newObj` already uses locks, so no need for them here.
let size = addInt(mulInt(len, typ.base.size), GenericSeqSize)
result = newObj(typ, size)
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
when defined(memProfiler): nimProfile(size)
proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(typ, size, gch)
zeroMem(result, size)
when defined(memProfiler): nimProfile(size)
proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
let size = addInt(mulInt(len, typ.base.size), GenericSeqSize)
result = newObj(typ, size)
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
when defined(memProfiler): nimProfile(size)
proc growObj(old: pointer, newsize: int, gch: var TGcHeap): pointer =
acquire(gch)
collectCT(gch)
var ol = usrToCell(old)
sysAssert(ol.typ != nil, "growObj: 1")
gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2")
var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(TCell)))
var elemSize = 1
if ol.typ.kind != tyString: elemSize = ol.typ.base.size
var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize
copyMem(res, ol, oldsize + sizeof(TCell))
zeroMem(cast[pointer](cast[TAddress](res)+% oldsize +% sizeof(TCell)),
newsize-oldsize)
sysAssert((cast[TAddress](res) and (MemAlign-1)) == 0, "growObj: 3")
excl(gch.allocated, ol)
when reallyDealloc: rawDealloc(gch.region, ol)
else:
zeroMem(ol, sizeof(TCell))
incl(gch.allocated, res)
release(gch)
result = cellToUsr(res)
when defined(memProfiler): nimProfile(newsize-oldsize)
proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
result = growObj(old, newsize, gch)
{.push profiler:off.}
# ----------------- collector -----------------------------------------------
proc mark(gch: var TGcHeap, c: PCell) =
incl(gch.marked, c)
gcAssert gch.tempStack.len == 0, "stack not empty!"
forAllChildren(c, waMarkPrecise)
while gch.tempStack.len > 0:
dec gch.tempStack.len
var d = gch.tempStack.d[gch.tempStack.len]
if not containsOrIncl(gch.marked, d):
forAllChildren(d, waMarkPrecise)
proc doOperation(p: pointer, op: TWalkOp) =
if p == nil: return
var c: PCell = usrToCell(p)
gcAssert(c != nil, "doOperation: 1")
case op
of waMarkGlobal:
when hasThreadSupport:
# could point to a cell which we don't own and don't want to touch/trace
if isAllocatedPtr(gch.region, c):
mark(gch, c)
else:
mark(gch, c)
of waMarkPrecise: add(gch.tempStack, c)
proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
doOperation(d, TWalkOp(op))
proc freeCyclicCell(gch: var TGcHeap, c: PCell) =
inc gch.stat.freedObjects
prepareDealloc(c)
when reallyDealloc: rawDealloc(gch.region, c)
else:
gcAssert(c.typ != nil, "freeCyclicCell")
zeroMem(c, sizeof(TCell))
proc sweep(gch: var TGcHeap) =
for c in gch.allocated.elementsExcept(gch.marked):
gch.allocated.excl(c)
freeCyclicCell(gch, c)
proc markGlobals(gch: var TGcHeap) =
for i in 0 .. < globalMarkersLen: globalMarkers[i]()
proc gcMark(gch: var TGcHeap, p: pointer) {.inline.} =
# the addresses are not as cells on the stack, so turn them to cells:
var cell = usrToCell(p)
var c = cast[TAddress](cell)
if c >% PageSize:
# fast check: does it look like a cell?
var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell))
if objStart != nil:
mark(gch, objStart)
# ----------------- stack management --------------------------------------
# inspired from Smart Eiffel
when defined(sparc):
const stackIncreases = false
elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820):
const stackIncreases = true
else:
const stackIncreases = false
when not defined(useNimRtl):
{.push stack_trace: off.}
proc setStackBottom(theStackBottom: pointer) =
#c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom)
# the first init must be the one that defines the stack bottom:
if gch.stackBottom == nil: gch.stackBottom = theStackBottom
else:
var a = cast[TAddress](theStackBottom) # and not PageMask - PageSize*2
var b = cast[TAddress](gch.stackBottom)
#c_fprintf(c_stdout, "old: %p new: %p;\n",gch.stackBottom,theStackBottom)
when stackIncreases:
gch.stackBottom = cast[pointer](min(a, b))
else:
gch.stackBottom = cast[pointer](max(a, b))
{.pop.}
proc stackSize(): int {.noinline.} =
var stackTop {.volatile.}: pointer
result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom))
when defined(sparc): # For SPARC architecture.
proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer
stackTop = addr(stackTop)
var b = cast[TAddress](gch.stackBottom)
var a = cast[TAddress](stackTop)
var x = cast[TAddress](p)
result = a <=% x and x <=% b
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
when defined(sparcv9):
asm """"flushw \n" """
else:
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
var
max = gch.stackBottom
sp: PPointer
stackTop: array[0..1, pointer]
sp = addr(stackTop[0])
# Addresses decrease as the stack grows.
while sp <= max:
gcMark(gch, sp[])
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
elif defined(ELATE):
{.error: "stack marking code is to be written for this architecture".}
elif stackIncreases:
# ---------------------------------------------------------------------------
# Generic code for architectures where addresses increase as the stack grows.
# ---------------------------------------------------------------------------
proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer
stackTop = addr(stackTop)
var a = cast[TAddress](gch.stackBottom)
var b = cast[TAddress](stackTop)
var x = cast[TAddress](p)
result = a <=% x and x <=% b
var
jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int
# a little hack to get the size of a TJmpBuf in the generated C code
# in a platform independant way
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
var registers: C_JmpBuf
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
var max = cast[TAddress](gch.stackBottom)
var sp = cast[TAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer)
# 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(gch, cast[ppointer](sp)[])
sp = sp -% sizeof(pointer)
else:
# ---------------------------------------------------------------------------
# Generic code for architectures where addresses decrease as the stack grows.
# ---------------------------------------------------------------------------
proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer
stackTop = addr(stackTop)
var b = cast[TAddress](gch.stackBottom)
var a = cast[TAddress](stackTop)
var x = cast[TAddress](p)
result = a <=% x and x <=% b
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
# We use a jmp_buf buffer that is in the C stack.
# Used to traverse the stack and registers assuming
# that 'setjmp' will save registers in the C stack.
type PStackSlice = ptr array [0..7, pointer]
var registers: C_JmpBuf
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
var max = cast[TAddress](gch.stackBottom)
var sp = cast[TAddress](addr(registers))
# loop unrolled:
while sp <% max - 8*sizeof(pointer):
gcMark(gch, cast[PStackSlice](sp)[0])
gcMark(gch, cast[PStackSlice](sp)[1])
gcMark(gch, cast[PStackSlice](sp)[2])
gcMark(gch, cast[PStackSlice](sp)[3])
gcMark(gch, cast[PStackSlice](sp)[4])
gcMark(gch, cast[PStackSlice](sp)[5])
gcMark(gch, cast[PStackSlice](sp)[6])
gcMark(gch, cast[PStackSlice](sp)[7])
sp = sp +% sizeof(pointer)*8
# last few entries:
while sp <=% max:
gcMark(gch, cast[ppointer](sp)[])
sp = sp +% sizeof(pointer)
# ----------------------------------------------------------------------------
# end of non-portable code
# ----------------------------------------------------------------------------
proc collectCTBody(gch: var TGcHeap) =
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
prepareForInteriorPointerChecking(gch.region)
markStackAndRegisters(gch)
markGlobals(gch)
sweep(gch)
inc(gch.stat.collections)
deinit(gch.marked)
init(gch.marked)
gch.cycleThreshold = max(InitialThreshold, getOccupiedMem().mulThreshold)
gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold)
sysAssert(allocInv(gch.region), "collectCT: end")
proc collectCT(gch: var TGcHeap) =
if getOccupiedMem(gch.region) >= gch.cycleThreshold and gch.recGcLock == 0:
collectCTBody(gch)
when not defined(useNimRtl):
proc GC_disable() =
when hasThreadSupport and hasSharedHeap:
atomicInc(gch.recGcLock, 1)
else:
inc(gch.recGcLock)
proc GC_enable() =
if gch.recGcLock > 0:
when hasThreadSupport and hasSharedHeap:
atomicDec(gch.recGcLock, 1)
else:
dec(gch.recGcLock)
proc GC_setStrategy(strategy: TGC_Strategy) = nil
proc GC_enableMarkAndSweep() =
gch.cycleThreshold = InitialThreshold
proc GC_disableMarkAndSweep() =
gch.cycleThreshold = high(gch.cycleThreshold)-1
# set to the max value to suppress the cycle detector
proc GC_fullCollect() =
acquire(gch)
var oldThreshold = gch.cycleThreshold
gch.cycleThreshold = 0 # forces cycle collection
collectCT(gch)
gch.cycleThreshold = oldThreshold
release(gch)
proc GC_getStatistics(): string =
GC_disable()
result = "[GC] total memory: " & $getTotalMem() & "\n" &
"[GC] occupied memory: " & $getOccupiedMem() & "\n" &
"[GC] collections: " & $gch.stat.collections & "\n" &
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
"[GC] freed objects: " & $gch.stat.freedObjects & "\n" &
"[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
GC_enable()
{.pop.}

View file

@ -309,6 +309,9 @@ else:
sysAssert(sizeof(TCell) == sizeof(TFreeCell), "sizeof TFreeCell") sysAssert(sizeof(TCell) == sizeof(TFreeCell), "sizeof TFreeCell")
when compileOption("gc", "v2"): when compileOption("gc", "v2"):
include "system/gc2" include "system/gc2"
elif defined(gcMarkAndSweep):
# XXX use 'compileOption' here
include "system/gc_ms"
else: else:
include "system/gc" include "system/gc"

View file

@ -202,7 +202,8 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {.
GenericSeqSize)) GenericSeqSize))
elif newLen < result.len: elif newLen < result.len:
# we need to decref here, otherwise the GC leaks! # we need to decref here, otherwise the GC leaks!
when not defined(boehmGC) and not defined(nogc): when not defined(boehmGC) and not defined(nogc) and
not defined(gcMarkAndSweep):
when compileOption("gc", "v2"): when compileOption("gc", "v2"):
for i in newLen..result.len-1: for i in newLen..result.len-1:
let len0 = gch.tempStack.len let len0 = gch.tempStack.len

View file

@ -5,37 +5,51 @@ discard """
type type
Module = object Module = object
nodes*: seq[PNode] nodes*: seq[PNode]
id: int
PModule = ref Module PModule = ref Module
Node = object Node = object
owner*: PModule owner*: PModule
data*: array[0..200, char] # some fat to drain memory faster data*: array[0..200, char] # some fat to drain memory faster
id: int
PNode = ref Node PNode = ref Node
var
gid: int
when false:
proc finalizeNode(x: PNode) =
echo "node id: ", x.id
proc finalizeModule(x: PModule) =
echo "module id: ", x.id
proc newNode(owner: PModule): PNode = proc newNode(owner: PModule): PNode =
new(result) new(result)
result.owner = owner result.owner = owner
inc gid
result.id = gid
proc compileModule: PModule = proc compileModule: PModule =
new(result) new(result)
result.nodes = @[] result.nodes = @[]
for i in 0..100: for i in 0..100:
result.nodes.add newNode(result) result.nodes.add newNode(result)
inc gid
result.id = gid
var gModuleCache: PModule var gModuleCache: PModule
proc loop = proc loop =
for i in 0..10000: for i in 0..1000:
gModuleCache = compileModule() gModuleCache = compileModule()
gModuleCache = nil gModuleCache = nil
GC_fullCollect() GC_fullCollect()
if getOccupiedMem() > 300_000: if getOccupiedMem() > 9_000_000:
echo "still a leak! ", getOccupiedMem() echo "still a leak! ", getOccupiedMem()
quit(1) quit(1)
else:
echo "no leak: ", getOccupiedMem() echo "no leak: ", getOccupiedMem()
loop() loop()

View file

@ -12,7 +12,9 @@ type
proc MakeObj(): TTestObj = proc MakeObj(): TTestObj =
result.x = "Hello" result.x = "Hello"
for i in 1 .. 100_000_000: for i in 1 .. 1_000_000:
when defined(gcMarkAndSweep):
GC_fullcollect()
var obj = MakeObj() var obj = MakeObj()
if getOccupiedMem() > 300_000: quit("still a leak!") if getOccupiedMem() > 300_000: quit("still a leak!")
# echo GC_getstatistics() # echo GC_getstatistics()

View file

@ -15,7 +15,9 @@ proc MakeObj(): TTestObj =
result.s = @[1,2,3] result.s = @[1,2,3]
proc inProc() = proc inProc() =
for i in 1 .. 100_000_000: for i in 1 .. 1_000_000:
when defined(gcMarkAndSweep):
GC_fullcollect()
var obj: TTestObj var obj: TTestObj
obj = MakeObj() obj = MakeObj()
if getOccupiedMem() > 300_000: quit("still a leak!") if getOccupiedMem() > 300_000: quit("still a leak!")

View file

@ -40,6 +40,8 @@ proc main =
for i in 0 .. s.high: for i in 0 .. s.high:
s[i] = register(create()) s[i] = register(create())
# test that we have at least 80% unreachable weak objects by now: # test that we have at least 80% unreachable weak objects by now:
when defined(gcMarkAndSweep):
GC_fullcollect()
var unreachable = 0 var unreachable = 0
for i in 0 .. s.high: for i in 0 .. s.high:
if access(s[i]) == nil: inc unreachable if access(s[i]) == nil: inc unreachable

View file

@ -1,5 +1,5 @@
discard """ discard """
file: "trecincb.nim" file: "tests/reject/trecincb.nim"
line: 9 line: 9
errormsg: "recursive dependency: 'trecincb.nim'" errormsg: "recursive dependency: 'trecincb.nim'"
""" """

View file

@ -1,7 +1,7 @@
discard """ discard """
file: "trecincb.nim" file: "trecincb.nim"
line: 9 line: 9
errormsg: "recursive dependency: 'trecincb.nim'" errormsg: "recursive dependency: 'tests/reject/trecincb.nim'"
""" """
# Test recursive includes # Test recursive includes

View file

@ -119,6 +119,10 @@ proc runGcTests(r: var TResults, options: string) =
runSingleTest(r, "tests/gc" / filename, options & " -d:release") runSingleTest(r, "tests/gc" / filename, options & " -d:release")
runSingleTest(r, "tests/gc" / filename, options & runSingleTest(r, "tests/gc" / filename, options &
" -d:release -d:useRealtimeGC") " -d:release -d:useRealtimeGC")
runSingleTest(r, "tests/gc" / filename, options &
" --gc:markAndSweep")
runSingleTest(r, "tests/gc" / filename, options &
" -d:release --gc:markAndSweep")
test "gcbench" test "gcbench"
test "gcleak" test "gcleak"

View file

@ -4,6 +4,8 @@ version 0.9.2
- implement constructors + full 'not nil' checking - implement constructors + full 'not nil' checking
- ``restrict`` pragma + backend support - ``restrict`` pragma + backend support
- fix: 'result' is not properly cleaned for NRVO - fix: 'result' is not properly cleaned for NRVO
- document NimMain and check whether it works for threading
version 0.9.4 version 0.9.4
============= =============

View file

@ -10,6 +10,12 @@ Version 0.9.2 has been released! Get it `here <download.html>`_.
Bugfixes Bugfixes
-------- --------
- The old GC never collected cycles correctly. Fixed but it can cause
performance regressions. However you can deactivate the cycle collector
with ``GC_disableMarkAndSweep`` and run it explicitly at an appropriate time
or not at all. There is also a new GC you can activate
with ``--gc:markAndSweep`` which does not have this problem but is slower in
general.
Library Additions Library Additions
@ -18,6 +24,8 @@ Library Additions
- Added ``system.onRaise`` to support a condition system. - Added ``system.onRaise`` to support a condition system.
- Added ``macros.quote`` for AST quasi-quoting. - Added ``macros.quote`` for AST quasi-quoting.
- Added ``system.unsafeNew`` to support hacky variable length objects. - Added ``system.unsafeNew`` to support hacky variable length objects.
- There is a new experimental mark&sweep GC which can be faster (or much
slower) than the default GC. Enable with ``--gc:markAndSweep``.
Changes affecting backwards compatibility Changes affecting backwards compatibility