Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARC: cycle detector #12823

Merged
merged 34 commits into from
Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c22a1ec
first implementation of the =trace and =dispose hooks for the cycle c…
Araq Dec 5, 2019
edf8bcc
a cycle collector for ARC: progress
Araq Dec 6, 2019
8d16996
wip
Araq Dec 6, 2019
7c7a5ac
wip
Araq Dec 7, 2019
25a8ff1
fixes a silly typo
Araq Dec 7, 2019
2305b24
yet another silly bugfix
Araq Dec 7, 2019
a1ebcd8
it helps when the cycle collector is allowed to free memory...
Araq Dec 7, 2019
12d8d34
manual: the .acyclic pragma is a thing once again
Araq Dec 7, 2019
4a0b8ac
progress
Araq Dec 8, 2019
7335a3b
progress
Araq Dec 8, 2019
d04f409
ARC: added more test cases
Araq Dec 8, 2019
6d87e9c
ARC: yet another terrible bugfix
Araq Dec 9, 2019
0d09db4
wip
Araq Dec 9, 2019
28dded3
fixed merge conflict
Araq Dec 9, 2019
c3280be
Merge branch 'devel' into araq-cycle-detector
Araq Dec 11, 2019
b135577
Merge branch 'araq-cycle-detector' of github.com:nim-lang/Nim into ar…
Araq Dec 11, 2019
7fc189f
cycle detetor bugfix
Araq Dec 11, 2019
4e13270
gcbench: adaptations for --gc:arc
Araq Dec 11, 2019
a56ad7f
enable valgrind tests for the strutils tests
Araq Dec 12, 2019
939e129
testament: better valgrind support
Araq Dec 12, 2019
f6fac6a
another attempt to make valgrind work
Araq Dec 12, 2019
e1b700a
newruntime: bugfixes
Araq Dec 12, 2019
ce5e097
Merge branch 'araq-cycle-detector' of github.com:nim-lang/Nim into ar…
Araq Dec 12, 2019
c09c581
wip: tcycle2 test works
Araq Dec 13, 2019
3c93cdc
thavlak: don't leak memory in principle
Araq Dec 13, 2019
1bcce96
wip
Araq Dec 15, 2019
8d5ba8f
ARC refactoring: growable jumpstacks
Araq Dec 15, 2019
d113d80
ARC cycle detector: non-recursive algorithm
Araq Dec 15, 2019
f24999b
ARC: adapted thavlak test
Araq Dec 15, 2019
6af11b1
bugfix
Araq Dec 16, 2019
8dc8976
ARC: added markCyclic
Araq Dec 17, 2019
fbd8509
moved and renamed core/ files back to system/
Araq Dec 17, 2019
60b2cde
refactoring: --gc:arc vs --gc:orc since 'orc' is even more experiment…
Araq Dec 17, 2019
9b0516a
Merge branch 'devel' into araq-cycle-detector
Araq Dec 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 7 additions & 3 deletions compiler/ast.nim
Expand Up @@ -478,7 +478,7 @@ type
nfExecuteOnReload # A top-level statement that will be executed during reloads

TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: ~38)
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: ~39)
tfVarargs, # procedure has C styled varargs
# tyArray type represeting a varargs list
tfNoSideEffect, # procedure type does not allow side effects
Expand Down Expand Up @@ -537,6 +537,7 @@ type
tfContravariant # contravariant generic param
tfCheckedForDestructor # type was checked for having a destructor.
# If it has one, t.destructor is not nil.
tfAcyclic # object type was annotated as .acyclic

TTypeFlags* = set[TTypeFlag]

Expand Down Expand Up @@ -635,7 +636,7 @@ type
mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
mNewString, mNewStringOfCap, mParseBiggestFloat,
mMove, mWasMoved, mDestroy,
mDefault, mUnown, mAccessEnv, mReset,
mDefault, mUnown, mAccessEnv, mAccessTypeInfo, mReset,
mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
mOrdinal,
Expand Down Expand Up @@ -870,6 +871,8 @@ type
attachedDestructor,
attachedAsgn,
attachedSink,
attachedTrace,
attachedDispose,
attachedDeepCopy

TType* {.acyclic.} = object of TIdObj # \
Expand Down Expand Up @@ -1298,7 +1301,8 @@ const
UnspecifiedLockLevel* = TLockLevel(-1'i16)
MaxLockLevel* = 1000'i16
UnknownLockLevel* = TLockLevel(1001'i16)
AttachedOpToStr*: array[TTypeAttachedOp, string] = ["=destroy", "=", "=sink", "=deepcopy"]
AttachedOpToStr*: array[TTypeAttachedOp, string] = [
"=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"]

proc `$`*(x: TLockLevel): string =
if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"
Expand Down
42 changes: 26 additions & 16 deletions compiler/ccgexprs.nim
Expand Up @@ -1210,7 +1210,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
genAssignment(p, a, b, {})
else:
let ti = genTypeInfo(p.module, typ, a.lode.info)
if bt.destructor != nil and not trivialDestructor(bt.destructor):
if bt.destructor != nil and not isTrivialProc(bt.destructor):
# the prototype of a destructor is ``=destroy(x: var T)`` and that of a
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least:
Expand Down Expand Up @@ -1584,6 +1584,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
gcUsage(p.config, e)

proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
discard cgsym(p.module, "TNimType")
let t = e[1].typ
putIntoDest(p, d, e, genTypeInfo(p.module, t, e.info))

Expand Down Expand Up @@ -2077,6 +2078,21 @@ proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) =
n[0] = newSymNode(toStrProc)
expr(p, n, d)

proc rdMType(p: BProc; a: TLoc; nilCheck: var Rope): Rope =
result = rdLoc(a)
var t = skipTypes(a.t, abstractInst)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = result
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
result = "(*$1)" % [result]
t = skipTypes(t.lastSon, abstractInst)
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t[0] != nil:
result.add(".Sup")
t = skipTypes(t[0], skipPtrs)
result.add ".m_type"

proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
case op
of mOr, mAnd: genAndOr(p, e, d, op)
Expand Down Expand Up @@ -2260,6 +2276,11 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mMove: genMove(p, e, d)
of mDestroy: genDestroy(p, e)
of mAccessEnv: unaryExpr(p, e, d, "$1.ClE_0")
of mAccessTypeInfo:
var a: TLoc
var dummy: Rope
initLocExpr(p, e[1], a)
putIntoDest(p, d, e, rdMType(p, a, dummy))
of mSlice:
localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
"'toOpenArray' is only valid within a call expression")
Expand Down Expand Up @@ -2407,28 +2428,17 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
initLocExpr(p, n[0], a)
let dest = skipTypes(n.typ, abstractPtrs)
if optObjCheck in p.options and not isObjLackingTypeField(dest):
var r = rdLoc(a)
var nilCheck: Rope = nil
var t = skipTypes(a.t, abstractInst)
while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
if t.kind notin {tyVar, tyLent}: nilCheck = r
if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
r = "(*$1)" % [r]
t = skipTypes(t.lastSon, abstractInst)
discard getTypeDesc(p.module, t)
if not p.module.compileToCpp:
while t.kind == tyObject and t[0] != nil:
r.add(".Sup")
t = skipTypes(t[0], skipPtrs)
var nilCheck = Rope(nil)
let r = rdMType(p, a, nilCheck)
let checkFor = if optTinyRtti in p.config.globalOptions:
genTypeInfo2Name(p.module, dest)
else:
genTypeInfo(p.module, dest, n.info)
if nilCheck != nil:
linefmt(p, cpsStmts, "if ($1) #chckObj($2.m_type, $3);$n",
linefmt(p, cpsStmts, "if ($1) #chckObj($2, $3);$n",
[nilCheck, r, checkFor])
else:
linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n",
linefmt(p, cpsStmts, "#chckObj($1, $2);$n",
[r, checkFor])
if n[0].typ.kind != tyObject:
putIntoDest(p, d, n,
Expand Down
126 changes: 71 additions & 55 deletions compiler/ccgtypes.nim
Expand Up @@ -1273,30 +1273,44 @@ proc genTypeInfo2Name(m: BModule; t: PType): Rope =
it = it[0]
result = makeCString(res)

proc trivialDestructor(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0
proc isTrivialProc(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0

proc genObjectInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) =
assert t.kind == tyObject
if incompleteType(t):
localError(m.config, info, "request for RTTI generation for incomplete object: " &
typeToString(t))

var d: Rope
if t.destructor != nil and not trivialDestructor(t.destructor):
proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp): Rope =
let theProc = t.attachedOps[op]
if theProc != nil and not isTrivialProc(theProc):
# the prototype of a destructor is ``=destroy(x: var T)`` and that of a
# finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
# convention at least:
if t.destructor.typ == nil or t.destructor.typ.callConv != ccDefault:
if theProc.typ == nil or theProc.typ.callConv != ccDefault:
localError(m.config, info,
"the destructor that is turned into a finalizer needs " &
"to have the 'nimcall' calling convention")
genProc(m, t.destructor)
d = t.destructor.loc.r
theProc.name.s & " needs to have the 'nimcall' calling convention")

genProc(m, theProc)
result = theProc.loc.r
else:
result = rope("NIM_NIL")

proc genTypeInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) =
var typeName: Rope
if t.kind == tyObject:
if incompleteType(t):
localError(m.config, info, "request for RTTI generation for incomplete object: " &
typeToString(t))
typeName = genTypeInfo2Name(m, t)
else:
d = rope("NIM_NIL")
typeName = rope("NIM_NIL")

m.s[cfsData].addf("TNimType $1;$n", [name])
m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3); $1.name = $4;$n", [
name, d, getTypeDesc(m, t), genTypeInfo2Name(m, t)])
let destroyImpl = genHook(m, t, info, attachedDestructor)
let traceImpl = genHook(m, t, info, attachedTrace)
let disposeImpl = genHook(m, t, info, attachedDispose)

m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3);$n" &
"$1.name = $4;$n" &
"$1.traceImpl = (void*)$5;$n" &
"$1.disposeImpl = (void*)$6;$n", [
name, destroyImpl, getTypeDesc(m, t), typeName,
traceImpl, disposeImpl])

proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
let origType = t
Expand Down Expand Up @@ -1333,49 +1347,51 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
return prefixTI.rope & result & ")".rope

m.g.typeInfoMarker[sig] = (str: result, owner: owner)
case t.kind
of tyEmpty, tyVoid: result = rope"0"
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
genTypeInfoAuxBase(m, t, t, result, rope"0", info)
of tyStatic:
if t.n != nil: result = genTypeInfo(m, lastSon t, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tyUserTypeClasses:
internalAssert m.config, t.isResolvedUserTypeClass
return genTypeInfo(m, t.lastSon, info)
of tyProc:
if t.callConv != ccClosure:

if optTinyRtti in m.config.globalOptions:
genTypeInfoV2(m, t, origType, result, info)
else:
case t.kind
of tyEmpty, tyVoid: result = rope"0"
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
genTypeInfoAuxBase(m, t, t, result, rope"0", info)
else:
let x = fakeClosureType(m, t.owner)
genTupleInfo(m, x, x, result, info)
of tySequence:
genTypeInfoAux(m, t, t, result, info)
if optSeqDestructors notin m.config.globalOptions:
of tyStatic:
if t.n != nil: result = genTypeInfo(m, lastSon t, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tyUserTypeClasses:
internalAssert m.config, t.isResolvedUserTypeClass
return genTypeInfo(m, t.lastSon, info)
of tyProc:
if t.callConv != ccClosure:
genTypeInfoAuxBase(m, t, t, result, rope"0", info)
else:
let x = fakeClosureType(m, t.owner)
genTupleInfo(m, x, x, result, info)
of tySequence:
genTypeInfoAux(m, t, t, result, info)
if optSeqDestructors notin m.config.globalOptions:
if m.config.selectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
of tyRef:
genTypeInfoAux(m, t, t, result, info)
if m.config.selectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
of tyRef:
genTypeInfoAux(m, t, t, result, info)
if m.config.selectedGC >= gcMarkAndSweep:
let markerProc = genTraverseProc(m, origType, sig)
m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
of tyArray: genArrayInfo(m, t, result, info)
of tySet: genSetInfo(m, t, result, info)
of tyEnum: genEnumInfo(m, t, result, info)
of tyObject:
if optTinyRtti in m.config.globalOptions:
genObjectInfoV2(m, t, origType, result, info)
else:
of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
of tyArray: genArrayInfo(m, t, result, info)
of tySet: genSetInfo(m, t, result, info)
of tyEnum: genEnumInfo(m, t, result, info)
of tyObject:
genObjectInfo(m, t, origType, result, info)
of tyTuple:
# if t.n != nil: genObjectInfo(m, t, result)
# else:
# BUGFIX: use consistently RTTI without proper field names; otherwise
# results are not deterministic!
genTupleInfo(m, t, origType, result, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
of tyTuple:
# if t.n != nil: genObjectInfo(m, t, result)
# else:
# BUGFIX: use consistently RTTI without proper field names; otherwise
# results are not deterministic!
genTupleInfo(m, t, origType, result, info)
else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')

if t.attachedOps[attachedDeepCopy] != nil:
genDeepCopyProc(m, t.attachedOps[attachedDeepCopy], result)
elif origType.attachedOps[attachedDeepCopy] != nil:
Expand Down
15 changes: 13 additions & 2 deletions compiler/commands.nim
Expand Up @@ -229,7 +229,8 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
of "v2": result = false
of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
of "generational": result = false
of "destructors", "arc": result = conf.selectedGC == gcDestructors
of "destructors", "arc": result = conf.selectedGC == gcArc
of "orc": result = conf.selectedGC == gcOrc
of "hooks": result = conf.selectedGC == gcHooks
of "go": result = conf.selectedGC == gcGo
of "none": result = conf.selectedGC == gcNone
Expand Down Expand Up @@ -455,8 +456,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
conf.selectedGC = gcMarkAndSweep
defineSymbol(conf.symbols, "gcmarkandsweep")
of "destructors", "arc":
conf.selectedGC = gcDestructors
conf.selectedGC = gcArc
defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcarc")
incl conf.globalOptions, optSeqDestructors
incl conf.globalOptions, optTinyRtti
if pass in {passCmd2, passPP}:
defineSymbol(conf.symbols, "nimSeqsV2")
defineSymbol(conf.symbols, "nimV2")
of "orc":
conf.selectedGC = gcOrc
defineSymbol(conf.symbols, "gcdestructors")
defineSymbol(conf.symbols, "gcorc")
incl conf.globalOptions, optSeqDestructors
incl conf.globalOptions, optTinyRtti
if pass in {passCmd2, passPP}:
Expand Down
6 changes: 3 additions & 3 deletions compiler/injectdestructors.nim
Expand Up @@ -218,7 +218,7 @@ proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =

proc genCopy(c: var Con; dest, ri: PNode): PNode =
let t = dest.typ
if tfHasOwned in t.flags:
if tfHasOwned in t.flags and ri.kind != nkNilLit:
# try to improve the error message here:
if c.otherRead == nil: discard isLastRead(ri, c)
checkForErrorPragma(c, t, ri, "=")
Expand Down Expand Up @@ -409,7 +409,7 @@ proc isCursor(n: PNode): bool =
result = false

proc cycleCheck(n: PNode; c: var Con) =
if c.graph.config.selectedGC != gcDestructors: return
if c.graph.config.selectedGC != gcArc: return
var value = n[1]
if value.kind == nkClosure:
value = value[1]
Expand Down Expand Up @@ -512,7 +512,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
result[i] = p(n[i], c, normal)
if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
result[0] = copyTree(n[0])
if c.graph.config.selectedGC in {gcHooks, gcDestructors}:
if c.graph.config.selectedGC in {gcHooks, gcArc, gcOrc}:
let destroyOld = genDestroy(c, result[1])
result = newTree(nkStmtList, destroyOld, result)
else:
Expand Down
1 change: 1 addition & 0 deletions compiler/lambdalifting.nim
Expand Up @@ -353,6 +353,7 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) =
# with cycles properly, so it's better to produce a weak ref (=ptr) here.
# This seems to be generally correct but since it's a bit risky it's disabled
# for now.
# XXX This is wrong for the 'hamming' test, so remove this logic again.
let fieldType = if isDefined(c.graph.config, "nimCycleBreaker"):
c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ
else:
Expand Down