bring #21802 back; fixes #21753 [backport] (#21815)

* bring #21802 back; fixes #21753 [backport]

* adds tests and multiple fixes

* add test cases

* refactor and remove startId

* fixes custom hooks and adds tests

* handle tyUncheckedArray better

(cherry picked from commit 71dc929ad7)
This commit is contained in:
ringabout
2023-05-11 16:29:11 +08:00
committed by narimiran
parent 285a18d683
commit 249551dbfa
7 changed files with 173 additions and 25 deletions

View File

@@ -1361,7 +1361,7 @@ proc rawGenNew(p: BProc, a: var TLoc, sizeExpr: Rope; needsInit: bool) =
p.module.s[cfsTypeInit3].addf("$1->finalizer = (void*)$2;$n", [ti, rdLoc(f)])
if a.storage == OnHeap and usesWriteBarrier(p.config):
if canFormAcycle(a.t):
if canFormAcycle(p.module.g.graph, a.t):
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", [a.rdLoc])
else:
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", [a.rdLoc])
@@ -1398,7 +1398,7 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) =
var call: TLoc
initLoc(call, locExpr, dest.lode, OnHeap)
if dest.storage == OnHeap and usesWriteBarrier(p.config):
if canFormAcycle(dest.t):
if canFormAcycle(p.module.g.graph, dest.t):
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", [dest.rdLoc])
else:
linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", [dest.rdLoc])

View File

@@ -1026,7 +1026,7 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType;
# compute type flags for GC optimization
var flags = 0
if not containsGarbageCollectedRef(typ): flags = flags or 1
if not canFormAcycle(typ): flags = flags or 2
if not canFormAcycle(m.g.graph, typ): flags = flags or 2
#else echo("can contain a cycle: " & typeToString(typ))
if flags != 0:
m.s[cfsTypeInit3].addf("$1.flags = $2;$n", [nameHcr, rope(flags)])
@@ -1312,7 +1312,7 @@ proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp): Rope =
result = theProc.loc.r
when false:
if not canFormAcycle(t) and op == attachedTrace:
if not canFormAcycle(m.g.graph, t) and op == attachedTrace:
echo "ayclic but has this =trace ", t, " ", theProc.ast
else:
when false:
@@ -1339,7 +1339,7 @@ proc genTypeInfoV2Impl(m: BModule, t, origType: PType, name: Rope; info: TLineIn
let traceImpl = genHook(m, t, info, attachedTrace)
var flags = 0
if not canFormAcycle(t): flags = flags or 1
if not canFormAcycle(m.g.graph, t): flags = flags or 1
addf(m.s[cfsTypeInit3], "$1.destructor = (void*)$2; $1.size = sizeof($3); $1.align = NIM_ALIGNOF($3); $1.name = $4;$n; $1.traceImpl = (void*)$5; $1.flags = $6;", [
name, destroyImpl, getTypeDesc(m, t), typeName,

View File

@@ -345,13 +345,13 @@ proc isCriticalLink(dest: PNode): bool {.inline.} =
proc finishCopy(c: var Con; result, dest: PNode; isFromSink: bool) =
if c.graph.config.selectedGC == gcOrc:
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
if cyclicType(t):
if cyclicType(c.graph, t):
result.add boolLit(c.graph, result.info, isFromSink or isCriticalLink(dest))
proc genMarkCyclic(c: var Con; result, dest: PNode) =
if c.graph.config.selectedGC == gcOrc:
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
if cyclicType(t):
if cyclicType(c.graph, t):
if t.kind == tyRef:
result.add callCodegenProc(c.graph, "nimMarkCyclic", dest.info, dest)
else:

View File

@@ -518,7 +518,7 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
forallElements(c, t, body, x, y)
body.add genBuiltin(c, mDestroy, "destroy", x)
of attachedTrace:
if canFormAcycle(t.elemType):
if canFormAcycle(c.g, t.elemType):
# follow all elements:
forallElements(c, t, body, x, y)
@@ -553,7 +553,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
doAssert t.destructor != nil
body.add destructorCall(c, t.destructor, x)
of attachedTrace:
if t.kind != tyString and canFormAcycle(t.elemType):
if t.kind != tyString and canFormAcycle(c.g, t.elemType):
let op = getAttachedOp(c.g, t, c.kind)
if op == nil:
return # protect from recursion
@@ -574,9 +574,9 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
of attachedTrace:
discard "strings are atomic and have no inner elements that are to trace"
proc cyclicType*(t: PType): bool =
proc cyclicType*(g: ModuleGraph, t: PType): bool =
case t.kind
of tyRef: result = types.canFormAcycle(t.lastSon)
of tyRef: result = types.canFormAcycle(g, t.lastSon)
of tyProc: result = t.callConv == ccClosure
else: result = false
@@ -604,7 +604,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
let elemType = t.lastSon
createTypeBoundOps(c.g, c.c, elemType, c.info, c.idgen)
let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(elemType)
let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(c.g, elemType)
let tmp =
if isCyclic and c.kind in {attachedAsgn, attachedSink}:
@@ -929,7 +929,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
result.typ.addParam src
if kind == attachedAsgn and g.config.selectedGC == gcOrc and
cyclicType(typ.skipTypes(abstractInst)):
cyclicType(g, typ.skipTypes(abstractInst)):
let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
nextSymId(idgen), result, info)
cycleParam.typ = getSysType(g, info, tyBool)

View File

@@ -195,6 +195,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
if not rec: break
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
of "isCyclic":
var operand = operand.skipTypes({tyGenericInst})
let isCyclic = canFormAcycle(c.graph, operand)
result = newIntNodeT(toInt128(ord(isCyclic)), traitCall, c.idgen, c.graph)
else:
localError(c.config, traitCall.info, "unknown trait: " & s)
result = newNodeI(nkEmpty, traitCall.info)

View File

@@ -368,41 +368,62 @@ proc containsHiddenPointer*(typ: PType): bool =
# that need to be copied deeply)
result = searchTypeFor(typ, isHiddenPointer)
proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool
proc canFormAcycleNode(marker: var IntSet, n: PNode, startId: int): bool =
proc canFormAcycleAux(g: ModuleGraph; marker: var IntSet, typ: PType, orig: PType, withRef: bool, hasTrace: bool): bool
proc canFormAcycleNode(g: ModuleGraph; marker: var IntSet, n: PNode, orig: PType, withRef: bool, hasTrace: bool): bool =
result = false
if n != nil:
result = canFormAcycleAux(marker, n.typ, startId)
result = canFormAcycleAux(g, marker, n.typ, orig, withRef, hasTrace)
if not result:
case n.kind
of nkNone..nkNilLit:
discard
else:
for i in 0..<n.len:
result = canFormAcycleNode(marker, n[i], startId)
result = canFormAcycleNode(g, marker, n[i], orig, withRef, hasTrace)
if result: return
proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool =
proc sameBackendType*(x, y: PType): bool
proc canFormAcycleAux(g: ModuleGraph, marker: var IntSet, typ: PType, orig: PType, withRef: bool, hasTrace: bool): bool =
result = false
if typ == nil: return
if tfAcyclic in typ.flags: return
var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
if tfAcyclic in t.flags: return
case t.kind
of tyTuple, tyObject, tyRef, tySequence, tyArray, tyOpenArray, tyVarargs:
if t.id == startId:
of tyRef, tyPtr, tyUncheckedArray:
if t.kind == tyRef or hasTrace:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
for i in 0..<t.len:
result = canFormAcycleAux(g, marker, t[i], orig, withRef or t.kind != tyUncheckedArray, hasTrace)
if result: return
of tyObject:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
var hasTrace = hasTrace
let op = getAttachedOp(g, t.skipTypes({tyRef}), attachedTrace)
if op != nil and sfOverriden in op.flags:
hasTrace = true
for i in 0..<t.len:
result = canFormAcycleAux(marker, t[i], startId)
result = canFormAcycleAux(g, marker, t[i], orig, withRef, hasTrace)
if result: return
if t.n != nil: result = canFormAcycleNode(marker, t.n, startId)
if t.n != nil: result = canFormAcycleNode(g, marker, t.n, orig, withRef, hasTrace)
# Inheritance can introduce cyclic types, however this is not relevant
# as the type that is passed to 'new' is statically known!
# er but we use it also for the write barrier ...
if t.kind == tyObject and tfFinal notin t.flags:
if tfFinal notin t.flags:
# damn inheritance may introduce cycles:
result = true
of tyTuple, tySequence, tyArray, tyOpenArray, tyVarargs:
if withRef and sameBackendType(t, orig):
result = true
elif not containsOrIncl(marker, t.id):
for i in 0..<t.len:
result = canFormAcycleAux(g, marker, t[i], orig, withRef, hasTrace)
if result: return
of tyProc: result = typ.callConv == ccClosure
else: discard
@@ -410,10 +431,10 @@ proc isFinal*(t: PType): bool =
let t = t.skipTypes(abstractInst)
result = t.kind != tyObject or tfFinal in t.flags or isPureObject(t)
proc canFormAcycle*(typ: PType): bool =
proc canFormAcycle*(g: ModuleGraph, typ: PType): bool =
var marker = initIntSet()
let t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
result = canFormAcycleAux(marker, t, t.id)
result = canFormAcycleAux(g, marker, t, t, false, false)
proc mutateTypeAux(marker: var IntSet, t: PType, iter: TTypeMutator,
closure: RootRef): PType

123
tests/types/tcyclic.nim Normal file
View File

@@ -0,0 +1,123 @@
## todo publish the `isCyclic` when it's mature.
proc isCyclic(t: typedesc): bool {.magic: "TypeTrait".} =
## Returns true if the type can potentially form a cyclic type
template cyclicYes(x: typed) =
doAssert isCyclic(x)
template cyclicNo(x: typed) =
doAssert not isCyclic(x)
# atomic types are not cyclic
cyclicNo(int)
cyclicNo(float)
cyclicNo(string)
cyclicNo(char)
cyclicNo(void)
type
Object = object
Ref = ref object
cyclicNo(Object)
cyclicNo(Ref)
type
Data1 = ref object
Data2 = ref object
id: Data1
cyclicNo(Data2)
type
Cyclone = ref object
data: Cyclone
Alias = Cyclone
Acyclic {.acyclic.} = ref object
data: Acyclic
LinkedNode = object
next: ref LinkedNode
LinkedNodeWithCursor = object
next {.cursor.} : ref LinkedNodeWithCursor
cyclicYes(Cyclone)
cyclicYes(Alias)
cyclicNo(seq[Cyclone])
cyclicNo((Cyclone, ))
cyclicNo(Acyclic)
cyclicYes(LinkedNode)
when false:
# todo fix me
cyclicNo(LinkedNodeWithCursor)
type
ObjectWithoutCycles = object
data: seq[ObjectWithoutCycles]
cyclicNo(ObjectWithoutCycles)
block:
type
Try = object
id: Best
Best = object
name: ref Try
Best2 = ref Best
cyclicYes(Best)
cyclicYes(Try)
cyclicNo(Best2)
type
Base = object
data: ref seq[Base]
Base2 = ref Base
cyclicYes(Base)
cyclicNo(Base2)
type
Base3 = ref object
id: Base3
Base4 = object
id: ref Base4
cyclicYes(Base3)
cyclicYes(Base4)
cyclicYes(ref Base4)
block:
type Cyclic2 = object
x: ref (Cyclic2, int)
cyclicYes (Cyclic2, int)
cyclicYes (ref (Cyclic2, int))
block:
type
myseq[T] = object
data: ptr UncheckedArray[T]
Node = ref object
kids: myseq[Node]
cyclicNo(Node)
block:
type
myseq[T] = object
data: ptr UncheckedArray[T]
Node = ref object
kids: myseq[Node]
proc `=trace`(x: var myseq[Node]; env: pointer) = discard
cyclicYes(Node)