diff options
-rw-r--r-- | compiler/ast.nim | 8 | ||||
-rw-r--r-- | compiler/sempass2.nim | 59 | ||||
-rw-r--r-- | tests/parallel/tarray_of_channels.nim | 26 | ||||
-rw-r--r-- | tests/parallel/tgc_unsafe.nim | 32 |
4 files changed, 93 insertions, 32 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 0c9f4bffc..274a49b52 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1335,8 +1335,12 @@ proc propagateToOwner*(owner, elem: PType) = if elem.isMetaType: owner.flags.incl tfHasMeta - if owner.kind != tyProc: - if elem.isGCedMem or tfHasGCedMem in elem.flags: + if owner.kind notin {tyProc, tyGenericInst, tyGenericBody, + tyGenericInvocation}: + let elemB = elem.skipTypes({tyGenericInst}) + if elemB.isGCedMem or tfHasGCedMem in elemB.flags: + # for simplicity, we propagate this flag even to generics. We then + # ensure this doesn't bite us in sempass2. owner.flags.incl tfHasGCedMem proc rawAddSon*(father, son: PType) = diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 14644a8d6..b8820d85c 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -8,12 +8,12 @@ # import - intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, + intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, wordrecg, strutils, options, guards # Second semantic checking pass over the AST. Necessary because the old # way had some inherent problems. Performs: -# +# # * effect+exception tracking # * "usage before definition" checking # * checks for invalid usages of compiletime magics (not implemented) @@ -23,7 +23,7 @@ import # Predefined effects: # io, time (time dependent), gc (performs GC'ed allocation), exceptions, # side effect (accesses global), store (stores into *type*), -# store_unknown (performs some store) --> store(any)|store(x) +# store_unknown (performs some store) --> store(any)|store(x) # load (loads from *type*), recursive (recursive call), unsafe, # endless (has endless loops), --> user effects are defined over *patterns* # --> a TR macro can annotate the proc with user defined annotations @@ -31,31 +31,31 @@ import # Load&Store analysis is performed on *paths*. A path is an access like # obj.x.y[i].z; splitting paths up causes some problems: -# +# # var x = obj.x # var z = x.y[i].z # # Alias analysis is affected by this too! A good solution is *type splitting*: -# T becomes T1 and T2 if it's known that T1 and T2 can't alias. -# +# T becomes T1 and T2 if it's known that T1 and T2 can't alias. +# # An aliasing problem and a race condition are effectively the same problem. # Type based alias analysis is nice but not sufficient; especially splitting # an array and filling it in parallel should be supported but is not easily # done: It essentially requires a built-in 'indexSplit' operation and dependent # typing. - + # ------------------------ exception and tag tracking ------------------------- discard """ exception tracking: - + a() # raises 'x', 'e' try: b() # raises 'e' except e: # must not undo 'e' here; hrm c() - + --> we need a stack of scopes for this analysis # XXX enhance the algorithm to care about 'dirty' expressions: @@ -209,8 +209,7 @@ proc useVar(a: PEffects, n: PNode) = a.init.add s.id if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind in {skVar, skLet}: if s.guard != nil: guardGlobal(a, n, s.guard) - if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem) and - tfGcSafe notin s.typ.flags: + if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem): if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n) markGcUnsafe(a) @@ -321,7 +320,7 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = dec tracked.inTryStmt for i in oldState.. <tracked.init.len: addToIntersection(inter, tracked.init[i]) - + var branches = 1 var hasFinally = false for i in 1 .. < n.len: @@ -345,7 +344,7 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = setLen(tracked.init, oldState) track(tracked, b.sons[blen-1]) hasFinally = true - + tracked.bottom = oldBottom if not hasFinally: setLen(tracked.init, oldState) @@ -356,7 +355,7 @@ proc isIndirectCall(n: PNode, owner: PSym): bool = # we don't count f(...) as an indirect call if 'f' is an parameter. # Instead we track expressions of type tyProc too. See the manual for # details: - if n.kind != nkSym: + if n.kind != nkSym: result = true elif n.sym.kind == skParam: result = owner != n.sym.owner or owner == nil @@ -366,13 +365,13 @@ proc isIndirectCall(n: PNode, owner: PSym): bool = proc isForwardedProc(n: PNode): bool = result = n.kind == nkSym and sfForward in n.sym.flags -proc trackPragmaStmt(tracked: PEffects, n: PNode) = - for i in countup(0, sonsLen(n) - 1): +proc trackPragmaStmt(tracked: PEffects, n: PNode) = + for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] if whichPragma(it) == wEffects: # list the computed effects up to here: listEffects(tracked) - + proc effectSpec(n: PNode, effectType: TSpecialWord): PNode = for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] @@ -387,12 +386,12 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode = let spec = effectSpec(x, effectType) if isNil(spec): let s = n.sons[namePos].sym - + let actual = s.typ.n.sons[0] if actual.len != effectListLen: return let real = actual.sons[idx] - - # warning: hack ahead: + + # warning: hack ahead: var effects = newNodeI(nkBracket, n.info, real.len) for i in 0 .. <real.len: var t = typeToString(real[i].typ) @@ -409,7 +408,7 @@ proc documentRaises*(n: PNode) = let pragmas = n.sons[pragmasPos] let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects) let p2 = documentEffect(n, pragmas, wTags, tagEffects) - + if p1 != nil or p2 != nil: if pragmas.kind == nkEmpty: n.sons[pragmasPos] = newNodeI(nkPragma, n.info) @@ -445,7 +444,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let pragma = s.ast.sons[pragmasPos] let spec = effectSpec(pragma, wRaises) mergeEffects(tracked, spec, n) - + let tagSpec = effectSpec(pragma, wTags) mergeTags(tracked, tagSpec, n) @@ -456,7 +455,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = let n = n.skipConv - if paramType != nil and tfNotNil in paramType.flags and + if paramType != nil and tfNotNil in paramType.flags and n.typ != nil and tfNotNil notin n.typ.flags: if n.kind == nkAddr: # addr(x[]) can't be proven, but addr(x) can: @@ -466,7 +465,7 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = return case impliesNotNil(tracked.guards, n) of impUnknown: - message(n.info, errGenerated, + message(n.info, errGenerated, "cannot prove '$1' is not nil" % n.renderTree) of impNo: message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree) @@ -517,7 +516,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = proc breaksBlock(n: PNode): bool = case n.kind of nkStmtList, nkStmtListExpr: - for c in n: + for c in n: if breaksBlock(c): return true of nkBreakStmt, nkReturnStmt, nkRaiseStmt: return true @@ -545,7 +544,7 @@ proc trackCase(tracked: PEffects, n: PNode) = if not breaksBlock(branch.lastSon): inc toCover for i in oldState.. <tracked.init.len: addToIntersection(inter, tracked.init[i]) - + let exh = case skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind of tyFloat..tyFloat128, tyString: lastSon(n).kind == nkElse @@ -590,7 +589,7 @@ proc trackIf(tracked: PEffects, n: PNode) = if count >= toCover: tracked.init.add id # else we can't merge as it is not exhaustive setLen(tracked.guards, oldFacts) - + proc trackBlock(tracked: PEffects, n: PNode) = if n.kind in {nkStmtList, nkStmtListExpr}: var oldState = -1 @@ -782,7 +781,7 @@ proc checkMethodEffects*(disp, branch: PSym) = checkRaisesSpec(tagsSpec, actual.sons[tagEffects], "can have an unlisted effect: ", hints=off, subtypeRelation) if sfThread in disp.flags and notGcSafe(branch.typ): - localError(branch.info, "base method is GC-safe, but '$1' is not" % + localError(branch.info, "base method is GC-safe, but '$1' is not" % branch.name.s) if branch.typ.lockLevel > disp.typ.lockLevel: when true: @@ -814,14 +813,14 @@ proc initEffects(effects: PNode; s: PSym; t: var TEffects) = newSeq(effects.sons, effectListLen) effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info) effects.sons[tagEffects] = newNodeI(nkArgList, s.info) - + t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] t.owner = s t.init = @[] t.guards = @[] t.locked = @[] - + proc trackProc*(s: PSym, body: PNode) = var effects = s.typ.n.sons[0] internalAssert effects.kind == nkEffectList diff --git a/tests/parallel/tarray_of_channels.nim b/tests/parallel/tarray_of_channels.nim new file mode 100644 index 000000000..11b523401 --- /dev/null +++ b/tests/parallel/tarray_of_channels.nim @@ -0,0 +1,26 @@ +# bug #2257 +import threadpool + +type StringChannel = TChannel[string] +var channels: array[1..3, StringChannel] + +type + MyObject[T] = object + x: T + +var global: MyObject[string] +var globalB: MyObject[float] + +proc consumer(ix : int) {.thread.} = + echo channels[ix].recv() ###### not GC-safe: 'channels' + echo globalB + +proc main = + for ix in 1..3: channels[ix].open() + for ix in 1..3: spawn consumer(ix) + for ix in 1..3: channels[ix].send("test") + sync() + for ix in 1..3: channels[ix].close() + +when isMainModule: + main() diff --git a/tests/parallel/tgc_unsafe.nim b/tests/parallel/tgc_unsafe.nim new file mode 100644 index 000000000..6548bbec8 --- /dev/null +++ b/tests/parallel/tgc_unsafe.nim @@ -0,0 +1,32 @@ +discard """ + errormsg: "'consumer' is not GC-safe" + line: 19 +""" + +# bug #2257 +import threadpool + +type StringChannel = TChannel[string] +var channels: array[1..3, StringChannel] + +type + MyObject[T] = object + x: T + +var global: MyObject[string] +var globalB: MyObject[float] + +proc consumer(ix : int) {.thread.} = + echo channels[ix].recv() ###### not GC-safe: 'channels' + echo global + echo globalB + +proc main = + for ix in 1..3: channels[ix].open() + for ix in 1..3: spawn consumer(ix) + for ix in 1..3: channels[ix].send("test") + sync() + for ix in 1..3: channels[ix].close() + +when isMainModule: + main() |