diff options
-rw-r--r-- | compiler/ast.nim | 20 | ||||
-rw-r--r-- | compiler/astalgo.nim | 4 | ||||
-rw-r--r-- | compiler/lowerings.nim | 4 | ||||
-rw-r--r-- | compiler/msgs.nim | 10 | ||||
-rw-r--r-- | compiler/pragmas.nim | 35 | ||||
-rw-r--r-- | compiler/sempass2.nim | 112 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 2 | ||||
-rw-r--r-- | compiler/wordrecg.nim | 11 | ||||
-rw-r--r-- | doc/manual.txt | 104 | ||||
-rw-r--r-- | lib/packages/docutils/rst.nim | 4 | ||||
-rw-r--r-- | lib/posix/epoll.nim | 2 | ||||
-rw-r--r-- | lib/posix/inotify.nim | 2 | ||||
-rw-r--r-- | lib/posix/linux.nim | 5 | ||||
-rw-r--r-- | lib/posix/posix.nim | 2 | ||||
-rw-r--r-- | lib/system.nim | 114 | ||||
-rw-r--r-- | lib/system/avltree.nim | 4 | ||||
-rw-r--r-- | lib/system/cgprocs.nim | 4 | ||||
-rw-r--r-- | lib/system/excpt.nim | 12 | ||||
-rw-r--r-- | lib/system/gc.nim | 21 | ||||
-rw-r--r-- | lib/system/hti.nim | 2 | ||||
-rw-r--r-- | lib/system/syslocks.nim | 12 | ||||
-rw-r--r-- | lib/system/sysspawn.nim | 172 | ||||
-rw-r--r-- | lib/windows/winlean.nim | 2 |
23 files changed, 460 insertions, 200 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index e720d2bfa..e575f317d 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -13,7 +13,7 @@ import msgs, hashes, nversion, options, strutils, crc, ropes, idents, lists, intsets, idgen -type +type TCallingConvention* = enum ccDefault, # proc has no explicit calling convention ccStdCall, # procedure is stdcall @@ -299,7 +299,7 @@ const nkEffectList* = nkArgList # hacks ahead: an nkEffectList is a node with 4 children: exceptionEffects* = 0 # exceptions at position 0 - readEffects* = 1 # read effects at position 1 + usesEffects* = 1 # read effects at position 1 writeEffects* = 2 # write effects at position 2 tagEffects* = 3 # user defined tag ('gc', 'time' etc.) effectListLen* = 4 # list of effects list @@ -432,7 +432,7 @@ type tfAcyclic, # type is acyclic (for GC optimization) tfEnumHasHoles, # enum cannot be mapped into a range tfShallow, # type can be shallow copied on assignment - tfThread, # proc type is marked as ``thread`` + tfThread, # proc type is marked as ``thread``; alias for ``gcsafe`` tfFromGeneric, # type is an instantiation of a generic; this is needed # because for instantiations of objects, structural # type equality has to be used @@ -509,6 +509,7 @@ const tfIncompleteStruct* = tfVarargs tfUncheckedArray* = tfVarargs tfUnion* = tfNoSideEffect + tfGcSafe* = tfThread skError* = skUnknown # type flags that are essential for type equality: @@ -978,6 +979,8 @@ template `{}=`*(n: PNode, i: int, s: PNode): stmt = var emptyNode* = newNode(nkEmpty) # There is a single empty node that is shared! Do not overwrite it! +var anyGlobal* = newSym(skVar, getIdent("*"), nil, unknownLineInfo()) + proc isMetaType*(t: PType): bool = return t.kind in tyMetaTypes or (t.kind == tyStatic and t.n == nil) or @@ -1310,8 +1313,7 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType = proc propagateToOwner*(owner, elem: PType) = const HaveTheirOwnEmpty = {tySequence, tySet} - owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta, - tfHasGCedMem}) + owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta}) if tfNotNil in elem.flags: if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}: owner.flags.incl tfNotNil @@ -1328,9 +1330,11 @@ proc propagateToOwner*(owner, elem: PType) = if elem.isMetaType: owner.flags.incl tfHasMeta - if elem.kind in {tyString, tyRef, tySequence} or - elem.kind == tyProc and elem.callConv == ccClosure: - owner.flags.incl tfHasGCedMem + if owner.kind != tyProc: + if elem.kind in {tyString, tyRef, tySequence} or + elem.kind == tyProc and elem.callConv == ccClosure or + tfHasGCedMem in elem.flags: + owner.flags.incl tfHasGCedMem proc rawAddSon*(father, son: PType) = if isNil(father.sons): father.sons = @[] diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 36dd7f562..dbf13f764 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -448,9 +448,9 @@ proc debug(n: PSym) = writeln(stdout, "skUnknown") else: #writeln(stdout, ropeToStr(symToYaml(n, 0, 1))) - writeln(stdout, ropeToStr(ropef("$1_$2: $3, $4", [ + writeln(stdout, ropeToStr(ropef("$1_$2: $3, $4, $5", [ toRope(n.name.s), toRope(n.id), flagsToStr(n.flags), - flagsToStr(n.loc.flags)]))) + flagsToStr(n.loc.flags), lineInfoToStr(n.info)]))) proc debug(n: PType) = writeln(stdout, ropeToStr(debugType(n))) diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index bee3427f4..0ca07e828 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -151,6 +151,8 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode = if n.kind notin nkCallKinds or not n.typ.isEmptyType: localError(n.info, "'spawn' takes a call expression of type void") return + if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: + localError(n.info, "'spawn' takes a GC safe call expression") var threadParam = newSym(skParam, getIdent"thread", owner, n.info) argsParam = newSym(skParam, getIdent"args", owner, n.info) @@ -196,7 +198,7 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode = # we pick n's type here, which hopefully is 'tyArray' and not # 'tyOpenArray': var argType = n[i].typ.skipTypes(abstractInst) - if argType.kind == tyVar: + if i < formals.len and formals[i].typ.kind == tyVar: localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter") elif containsTyRef(argType): localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure") diff --git a/compiler/msgs.nim b/compiler/msgs.nim index a63fbca7f..7ad393b4d 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -118,7 +118,7 @@ type warnNilStatement, warnAnalysisLoophole, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, warnEachIdentIsTuple, warnShadowIdent, - warnProveInit, warnProveField, warnProveIndex, + warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnUninit, warnGcMem, warnUser, hintSuccess, hintSuccessX, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, @@ -386,6 +386,7 @@ const warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]", warnProveField: "cannot prove that field '$1' is accessible [ProveField]", warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]", + warnGcUnsafe: "not GC-safe: '$1' [GcUnsafe]", warnUninit: "'$1' might not have been initialized [Uninit]", warnGcMem: "'$1' uses GC'ed memory [GcMem]", warnUser: "$1 [User]", @@ -407,7 +408,7 @@ const hintUser: "$1 [User]"] const - WarningsToStr*: array[0..24, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr*: array[0..25, string] = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", @@ -415,7 +416,8 @@ const "CommentXIgnored", "NilStmt", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", - "ProveInit", "ProveField", "ProveIndex", "Uninit", "GcMem", "User"] + "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "Uninit", + "GcMem", "User"] HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", @@ -557,7 +559,7 @@ proc sourceLine*(i: TLineInfo): PRope var gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex} + warnProveField, warnProveIndex, warnGcUnsafe} gErrorCounter*: int = 0 # counts the number of errors gHintCounter*: int = 0 gWarnCounter*: int = 0 diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 14d155539..88fa516bf 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -24,7 +24,7 @@ const wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl, - wGensym, wInject, wRaises, wTags, wOperator, wDelegator} + wGensym, wInject, wRaises, wTags, wUses, wOperator, wDelegator, wGcSafe} converterPragmas* = procPragmas methodPragmas* = procPragmas templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty, @@ -35,7 +35,7 @@ const iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect, wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises, - wTags, wOperator} + wTags, wUses, wOperator, wGcSafe} exprPragmas* = {wLine} stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, @@ -48,7 +48,7 @@ const lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, - wRaises, wTags} + wRaises, wUses, wTags, wGcSafe} typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, @@ -64,7 +64,7 @@ const wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, - wThread, wRaises, wTags} + wThread, wRaises, wUses, wTags, wGcSafe} allRoutinePragmas* = procPragmas + iteratorPragmas + lambdaPragmas proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) @@ -513,6 +513,27 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = else: invalidPragma(n) +proc pragmaUses(c: PContext, n: PNode) = + proc processExc(c: PContext, x: PNode): PNode = + if x.kind in {nkAccQuoted, nkIdent, nkSym, + nkOpenSymChoice, nkClosedSymChoice}: + if considerAcc(x).s == "*": + return newSymNode(ast.anyGlobal) + result = c.semExpr(c, x) + if result.kind != nkSym or sfGlobal notin result.sym.flags: + localError(x.info, "'$1' is not a global variable" % result.renderTree) + result = newSymNode(ast.anyGlobal) + + if n.kind == nkExprColonExpr: + let it = n.sons[1] + if it.kind notin {nkCurly, nkBracket}: + n.sons[1] = processExc(c, it) + else: + for i in 0 .. <it.len: + it.sons[i] = processExc(c, it.sons[i]) + else: + invalidPragma(n) + proc typeBorrow(sym: PSym, n: PNode) = if n.kind == nkExprColonExpr: let it = n.sons[1] @@ -667,6 +688,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, incl(sym.flags, sfThread) incl(sym.flags, sfProcvar) if sym.typ != nil: incl(sym.typ.flags, tfThread) + of wGcSafe: + noVal(it) + incl(sym.flags, sfThread) + if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) + else: invalidPragma(it) of wPacked: noVal(it) if sym.typ == nil: invalidPragma(it) @@ -759,6 +785,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, if sym == nil: invalidPragma(it) of wLine: pragmaLine(c, it) of wRaises, wTags: pragmaRaisesOrTags(c, it) + of wUses: pragmaUses(c, it) of wOperator: if sym == nil: invalidPragma(it) else: sym.position = expectIntLit(c, it) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index a00325277..b2b91490c 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -59,15 +59,19 @@ discard """ --> we need a stack of scopes for this analysis """ +const trackGlobals = false ## we don't need it for now + type TEffects = object exc: PNode # stack of exceptions tags: PNode # list of tags + uses: PNode # list of used global variables bottom: int owner: PSym init: seq[int] # list of initialized variables guards: TModel # nested guards locked: seq[PNode] # locked locations + gcUnsafe: bool PEffects = var TEffects proc isLocalVar(a: PEffects, s: PSym): bool = @@ -89,20 +93,29 @@ proc initVarViaNew(a: PEffects, n: PNode) = # are initialized: initVar(a, n) +when trackGlobals: + proc addUse(a: PEffects, e: PNode) = + var aa = a.uses + for i in 0 .. <aa.len: + if aa[i].sym.id == e.sym.id: return + a.uses.add(e) + proc useVar(a: PEffects, n: PNode) = let s = n.sym if isLocalVar(a, s): if s.id notin a.init: if {tfNeedsInit, tfNotNil} * s.typ.flags != {}: - when true: - message(n.info, warnProveInit, s.name.s) - else: - Message(n.info, errGenerated, - "'$1' might not have been initialized" % s.name.s) + message(n.info, warnProveInit, s.name.s) else: message(n.info, warnUninit, s.name.s) # prevent superfluous warnings about the same variable: a.init.add s.id + if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar: + when trackGlobals: + a.addUse(copyNode(n)) + if tfHasGCedMem in s.typ.flags: + message(n.info, warnGcUnsafe, renderTree(n)) + a.gcUnsafe = true type TIntersection = seq[tuple[id, count: int]] # a simple count table @@ -132,6 +145,10 @@ proc createTag(n: PNode): PNode = result.typ = sysTypeFromName"TEffect" if not n.isNil: result.info = n.info +proc createAnyGlobal(n: PNode): PNode = + result = newSymNode(anyGlobal) + result.info = n.info + proc addEffect(a: PEffects, e: PNode, useLineInfo=true) = assert e.kind != nkRaiseStmt var aa = a.exc @@ -161,9 +178,17 @@ proc mergeTags(a: PEffects, b, comesFrom: PNode) = else: for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil) +when trackGlobals: + proc mergeUses(a: PEffects, b, comesFrom: PNode) = + if b.isNil: + addUse(a, createAnyGlobal(comesFrom)) + else: + for effect in items(b): addUse(a, effect) + proc listEffects(a: PEffects) = for e in items(a.exc): message(e.info, hintUser, typeToString(e.typ)) for e in items(a.tags): message(e.info, hintUser, typeToString(e.typ)) + for e in items(a.uses): message(e.info, hintUser, e.sym.name.s) proc catches(tracked: PEffects, e: PType) = let e = skipTypes(e, skipPtrs) @@ -289,6 +314,13 @@ proc documentRaises*(n: PNode) = if n.sons[namePos].kind != nkSym: return documentEffect(n, n.sons[pragmasPos], wRaises, exceptionEffects) documentEffect(n, n.sons[pragmasPos], wTags, tagEffects) + documentEffect(n, n.sons[pragmasPos], wUses, usesEffects) + +template notGcSafe(t): expr = {tfGcSafe, tfNoSideEffect} * t.flags == {} + +proc importedFromC(n: PNode): bool = + # when imported from C, we assume GC-safety. + result = n.kind == nkSym and sfImportc in n.sym.flags proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let pragma = s.ast.sons[pragmasPos] @@ -298,6 +330,14 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let tagSpec = effectSpec(pragma, wTags) mergeTags(tracked, tagSpec, n) + if notGcSafe(s.typ) and sfImportc notin s.flags: + message(n.info, warnGcUnsafe, renderTree(n)) + tracked.gcUnsafe = true + + when trackGlobals: + let usesSpec = effectSpec(pragma, wUses) + mergeUses(tracked, usesSpec, n) + proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = let n = n.skipConv if paramType != nil and tfNotNil in paramType.flags and @@ -330,9 +370,18 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = else: addEffect(tracked, createRaise(n)) addTag(tracked, createTag(n)) + when trackGlobals: addUse(tracked, createAnyGlobal(n)) + # assume GcUnsafe unless in its type: + if notGcSafe(op): + message(n.info, warnGcUnsafe, renderTree(n)) + tracked.gcUnsafe = true else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) + when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n) + if notGcSafe(op): + message(n.info, warnGcUnsafe, renderTree(n)) + tracked.gcUnsafe = true notNilCheck(tracked, n, paramType) proc breaksBlock(n: PNode): bool = @@ -451,8 +500,11 @@ proc track(tracked: PEffects, n: PNode) = # XXX: in rare situations, templates and macros will reach here after # calling getAst(templateOrMacro()). Currently, templates and macros # are indistinguishable from normal procs (both have tyProc type) and - # we can detect them only by cheking for attached nkEffectList. + # we can detect them only by checking for attached nkEffectList. if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList: + if notGcSafe(op) and not importedFromC(a): + message(n.info, warnGcUnsafe, renderTree(n)) + tracked.gcUnsafe = true var effectList = op.n.sons[0] if a.kind == nkSym and a.sym.kind == skMethod: propagateEffects(tracked, n, a.sym) @@ -462,9 +514,11 @@ proc track(tracked: PEffects, n: PNode) = elif isIndirectCall(a, tracked.owner): addEffect(tracked, createRaise(n)) addTag(tracked, createTag(n)) + when trackGlobals: addUse(tracked, createAnyGlobal(n)) else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) + when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n) for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i)) if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}: # may not look like an assignment, but it is: @@ -531,14 +585,21 @@ proc track(tracked: PEffects, n: PNode) = else: for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) -proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) = +proc subtypeRelation(spec, real: PNode): bool = + result = safeInheritanceDiff(real.excType, spec.typ) <= 0 + +proc symbolPredicate(spec, real: PNode): bool = + result = real.sym.id == spec.sym.id + +proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool; + effectPredicate: proc (a, b: PNode): bool {.nimcall.}) = # check that any real exception is listed in 'spec'; mark those as used; # report any unused exception var used = initIntSet() for r in items(real): block search: for s in 0 .. <spec.len: - if safeInheritanceDiff(r.excType, spec[s].typ) <= 0: + if effectPredicate(spec[s], r): used.incl(s) break search # XXX call graph analysis would be nice here! @@ -560,11 +621,18 @@ proc checkMethodEffects*(disp, branch: PSym) = let raisesSpec = effectSpec(p, wRaises) if not isNil(raisesSpec): checkRaisesSpec(raisesSpec, actual.sons[exceptionEffects], - "can raise an unlisted exception: ", hints=off) + "can raise an unlisted exception: ", hints=off, subtypeRelation) let tagsSpec = effectSpec(p, wTags) if not isNil(tagsSpec): checkRaisesSpec(tagsSpec, actual.sons[tagEffects], - "can have an unlisted effect: ", hints=off) + "can have an unlisted effect: ", hints=off, subtypeRelation) + let usesSpec = effectSpec(p, wUses) + if not isNil(usesSpec): + checkRaisesSpec(usesSpec, actual.sons[usesEffects], + "may use an unlisted global variable: ", hints=off, symbolPredicate) + if sfThread in disp.flags and notGcSafe(branch.typ): + localError(branch.info, "base method is GC-safe, but '$1' is not" % + branch.name.s) proc setEffectsForProcType*(t: PType, n: PNode) = var effects = t.n.sons[0] @@ -573,21 +641,26 @@ proc setEffectsForProcType*(t: PType, n: PNode) = let raisesSpec = effectSpec(n, wRaises) tagsSpec = effectSpec(n, wTags) - if not isNil(raisesSpec) or not isNil(tagsSpec): + usesSpec = effectSpec(n, wUses) + if not isNil(raisesSpec) or not isNil(tagsSpec) or not isNil(usesSpec): internalAssert effects.len == 0 newSeq(effects.sons, effectListLen) if not isNil(raisesSpec): effects.sons[exceptionEffects] = raisesSpec if not isNil(tagsSpec): effects.sons[tagEffects] = tagsSpec + if not isNil(usesSpec): + effects.sons[usesEffects] = usesSpec 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) + effects.sons[usesEffects] = newNodeI(nkArgList, s.info) t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] + t.uses = effects.sons[usesEffects] t.owner = s t.init = @[] t.guards = @[] @@ -602,7 +675,6 @@ proc trackProc*(s: PSym, body: PNode) = var t: TEffects initEffects(effects, s, t) track(t, body) - if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and s.kind in {skProc, skConverter, skMethod}: var res = s.ast.sons[resultPos].sym # get result symbol @@ -612,17 +684,27 @@ proc trackProc*(s: PSym, body: PNode) = let raisesSpec = effectSpec(p, wRaises) if not isNil(raisesSpec): checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ", - hints=on) + hints=on, subtypeRelation) # after the check, use the formal spec: effects.sons[exceptionEffects] = raisesSpec let tagsSpec = effectSpec(p, wTags) if not isNil(tagsSpec): checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ", - hints=off) + hints=off, subtypeRelation) # after the check, use the formal spec: effects.sons[tagEffects] = tagsSpec - + + when trackGlobals: + let usesSpec = effectSpec(p, wUses) + if not isNil(usesSpec): + checkRaisesSpec(usesSpec, t.uses, + "uses an unlisted global variable: ", hints=on, symbolPredicate) + effects.sons[usesEffects] = usesSpec + if sfThread in s.flags and t.gcUnsafe: + localError(s.info, "'$1' is not GC-safe" % s.name.s) + if not t.gcUnsafe: s.typ.flags.incl tfGcSafe + proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 6b85542d3..5d3ed05f7 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -402,7 +402,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags: return isNone elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}: - # noSideEffect implies ``tfThread``! XXX really? + # noSideEffect implies ``tfThread``! return isNone elif f.flags * {tfIterator} != a.flags * {tfIterator}: return isNone diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 9a74f53fc..fbe005031 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -44,7 +44,8 @@ type wImportCompilerProc, wImportc, wExportc, wIncompleteStruct, wRequiresInit, wAlign, wNodecl, wPure, wSideeffect, wHeader, - wNosideeffect, wNoreturn, wMerge, wLib, wDynlib, wCompilerproc, wProcVar, + wNosideeffect, wGcSafe, wNoreturn, wMerge, wLib, wDynlib, + wCompilerproc, wProcVar, wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef, wLinedir, wStacktrace, wLinetrace, wLink, wCompile, wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger, @@ -63,7 +64,7 @@ type wAcyclic, wShallow, wUnroll, wLinearScanEnd, wComputedGoto, wInjectStmt, wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, wAsmNoStackFrame, - wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, + wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wUses, wAuto, wBool, wCatch, wChar, wClass, wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, @@ -125,7 +126,7 @@ const "importcpp", "importobjc", "importcompilerproc", "importc", "exportc", "incompletestruct", "requiresinit", "align", "nodecl", "pure", "sideeffect", - "header", "nosideeffect", "noreturn", "merge", "lib", "dynlib", + "header", "nosideeffect", "gcsafe", "noreturn", "merge", "lib", "dynlib", "compilerproc", "procvar", "fatal", "error", "warning", "hint", "line", "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace", "link", "compile", "linksys", "deprecated", "varargs", @@ -146,7 +147,7 @@ const "computedgoto", "injectstmt", "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit", "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked", - "guard", + "guard", "uses", "auto", "bool", "catch", "char", "class", "const_cast", "default", "delete", "double", diff --git a/doc/manual.txt b/doc/manual.txt index c0d4f2627..d6b9f296e 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -5616,7 +5616,6 @@ Memory allocation requires no lock at all! This design easily scales to massive multicore processors that will become the norm in the future. - Thread pragma ------------- @@ -5626,12 +5625,32 @@ violations of the `no heap sharing restriction`:idx:\: This restriction implies that it is invalid to construct a data structure that consists of memory allocated from different (thread local) heaps. -Since the semantic checking of threads requires whole program analysis, -it is quite expensive and can be turned off with ``--threadanalysis:off`` to -improve compile times. +A thread proc is passed to ``createThread`` or ``spawn`` and invoked +indirectly; so the ``thread`` pragma implies ``procvar``. + + +GC safety +--------- + +We call a proc ``p`` `GC safe`:idx: when it doesn't access any global variable +that contains GC'ed memory (``string``, ``seq``, ``ref`` or a closure) either +directly or indirectly through a call to a GC unsafe proc. + +The `gcsafe`:idx: annotation can be used to mark a proc to be gcsafe +otherwise this property is inferred by the compiler. Note that ``noSideEfect`` +implies ``gcsafe``. The only way to create a thread is via ``spawn`` or +``createThead``. ``spawn`` is usually the preferable method. Either way +the invoked proc must not use ``var`` parameters nor must any of its parameters +contain a ``ref`` or ``closure`` type. This enforces +the *no heap sharing restriction*. + +Routines that are imported from C are always assumed to be ``gcsafe``. + +Future directions: -A thread proc is passed to ``createThread`` and invoked indirectly; so the -``thread`` pragma implies ``procvar``. +- For structured fork&join parallelism more efficient parameter passing can + be performed and much more can be proven safe. +- A shared GC'ed heap is planned. Threadvar pragma @@ -5648,78 +5667,6 @@ initialized within the ``var`` section. (Every thread local variable needs to be replicated at thread creation.) -Actor model ------------ - -**Caution**: This section is already outdated! XXX - -Nimrod supports the `actor model`:idx: of concurrency natively: - -.. code-block:: nimrod - type - TMsgKind = enum - mLine, mEof - TMsg = object - case k: TMsgKind - of mEof: nil - of mLine: data: string - - var - thr: TThread[TMsg] - printedLines = 0 - m: TMsg - - proc print() {.thread.} = - while true: - var x = recv[TMsg]() - if x.k == mEof: break - echo x.data - discard atomicInc(printedLines) - - createThread(thr, print) - - var input = open("readme.txt") - while not endOfFile(input): - m.data = input.readLine() - thr.send(m) - close(input) - m.k = mEof - thr.send(m) - joinThread(thr) - - echo printedLines - -In the actor model threads communicate only over sending messages (`send`:idx: -and `recv`:idx: built-ins), not by sharing memory. Every thread has -an `inbox`:idx: that keeps incoming messages until the thread requests a new -message via the ``recv`` operation. The inbox is an unlimited FIFO queue. - -In the above example the ``print`` thread also communicates with its -parent thread over the ``printedLines`` global variable. In general, it is -highly advisable to only read from globals, but not to write to them. In fact -a write to a global that contains GC'ed memory is always wrong, because it -violates the *no heap sharing restriction*: - -.. code-block:: nimrod - var - global: string - t: TThread[string] - - proc horrible() {.thread.} = - global = "string in thread local heap!" - - createThread(t, horrible) - joinThread(t) - -For the above code the compiler produces "Warning: write to foreign heap". This -warning might become an error message in future versions of the compiler. - -Creating a thread is an expensive operation, because a new stack and heap needs -to be created for the thread. It is therefore highly advisable that a thread -handles a large amount of work. Nimrod prefers *coarse grained* -over *fine grained* concurrency. - - Threads and exceptions ---------------------- @@ -5727,6 +5674,7 @@ The interaction between threads and exceptions is simple: A *handled* exception in one thread cannot affect any other thread. However, an *unhandled* exception in one thread terminates the whole *process*! + Taint mode ========== diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 30cc9026b..23459ade6 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -311,8 +311,8 @@ proc newSharedState(options: TRstParseOptions, result.subs = @[] result.refs = @[] result.options = options - result.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler - result.findFile = if isNil(findFile): defaultFindFile else: findFile + result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler + result.findFile = if not isNil(findFile): findFile else: defaultFindFile proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index 57a2f001f..ee04348e8 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +{.deadCodeElim:on.} + from posix import TSocketHandle const diff --git a/lib/posix/inotify.nim b/lib/posix/inotify.nim index 28dcd652f..852eb12fa 100644 --- a/lib/posix/inotify.nim +++ b/lib/posix/inotify.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +{.deadCodeElim:on.} + # Get the platform-dependent flags. # Structure describing an inotify event. type diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim index 1ed1af3b6..be591e29a 100644 --- a/lib/posix/linux.nim +++ b/lib/posix/linux.nim @@ -1,3 +1,5 @@ +{.deadCodeElim:on.} + import posix const @@ -22,4 +24,5 @@ const # fn should be of type proc (a2: pointer): void {.cdecl.} proc clone*(fn: pointer; child_stack: pointer; flags: cint; - arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "<sched.h>".} + arg: pointer; ptid: ptr TPid; tls: pointer; + ctid: ptr TPid): cint {.importc, header: "<sched.h>".} diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 131f23fdd..e206447cc 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -27,6 +27,8 @@ ## resulting C code will just ``#include <XYZ.h>`` and *not* define the ## symbols declared here. +{.deadCodeElim:on.} + from times import TTime const diff --git a/lib/system.nim b/lib/system.nim index dae418b7f..ede9eae59 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -192,6 +192,9 @@ when defined(nimNewShared): type `shared`* {.magic: "Shared".} guarded* {.magic: "Guarded".} +else: + {.pragma: gcsafe.} +#{.pragma: gcsafe.} const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \ ## "fake variables" like 'var EBADF {.importc.}: cint'. @@ -1207,20 +1210,20 @@ proc substr*(s: string, first, last: int): string {. ## or `limit`:idx: a string's length. when not defined(nimrodVM): - proc zeroMem*(p: pointer, size: int) {.importc, noDecl.} + proc zeroMem*(p: pointer, size: int) {.importc, noDecl, gcsafe.} ## overwrites the contents of the memory at ``p`` with the value 0. ## Exactly ``size`` bytes will be overwritten. Like any procedure ## dealing with raw memory this is *unsafe*. proc copyMem*(dest, source: pointer, size: int) {. - importc: "memcpy", header: "<string.h>".} + importc: "memcpy", header: "<string.h>", gcsafe.} ## copies the contents from the memory at ``source`` to the memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## regions may not overlap. Like any procedure dealing with raw ## memory this is *unsafe*. proc moveMem*(dest, source: pointer, size: int) {. - importc: "memmove", header: "<string.h>".} + importc: "memmove", header: "<string.h>", gcsafe.} ## copies the contents from the memory at ``source`` to the memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## regions may overlap, ``moveMem`` handles this case appropriately @@ -1235,14 +1238,14 @@ when not defined(nimrodVM): ## *unsafe*. when hostOS != "standalone": - proc alloc*(size: int): pointer {.noconv, rtl, tags: [].} + proc alloc*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.} ## allocates a new memory block with at least ``size`` bytes. The ## block has to be freed with ``realloc(block, 0)`` or ## ``dealloc(block)``. The block is not initialized, so reading ## from it before writing to it is undefined behaviour! ## The allocated memory belongs to its allocating thread! ## Use `allocShared` to allocate from a shared heap. - proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or ## ``free(block)``. The block is not initialized, so reading @@ -1250,14 +1253,14 @@ when not defined(nimrodVM): ## The allocated memory belongs to its allocating thread! ## Use `createSharedU` to allocate from a shared heap. cast[ptr T](alloc(T.sizeof * size)) - proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].} + proc alloc0*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.} ## allocates a new memory block with at least ``size`` bytes. The ## block has to be freed with ``realloc(block, 0)`` or ## ``dealloc(block)``. The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``alloc``. ## The allocated memory belongs to its allocating thread! ## Use `allocShared0` to allocate from a shared heap. - proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or ## ``free(block)``. The block is initialized with all bytes @@ -1265,7 +1268,8 @@ when not defined(nimrodVM): ## The allocated memory belongs to its allocating thread! ## Use `createShared` to allocate from a shared heap. cast[ptr T](alloc0(T.sizeof * size)) - proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].} + proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [], + gcsafe.} ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** @@ -1273,7 +1277,7 @@ when not defined(nimrodVM): ## be freed with ``dealloc``. ## The allocated memory belongs to its allocating thread! ## Use `reallocShared` to reallocate from a shared heap. - proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} = + proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe.} = ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not @@ -1282,7 +1286,7 @@ when not defined(nimrodVM): ## its allocating thread! ## Use `resizeShared` to reallocate from a shared heap. cast[ptr T](realloc(p, T.sizeof * newSize)) - proc dealloc*(p: pointer) {.noconv, rtl, tags: [].} + proc dealloc*(p: pointer) {.noconv, rtl, tags: [], gcsafe.} ## frees the memory allocated with ``alloc``, ``alloc0`` or ## ``realloc``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed @@ -1290,22 +1294,23 @@ when not defined(nimrodVM): ## or other memory may be corrupted. ## The freed memory must belong to its allocating thread! ## Use `deallocShared` to deallocate from a shared heap. - proc free*[T](p: ptr T) {.inline.} = + proc free*[T](p: ptr T) {.inline, gcsafe.} = dealloc(p) - proc allocShared*(size: int): pointer {.noconv, rtl.} + proc allocShared*(size: int): pointer {.noconv, rtl, gcsafe.} ## allocates a new memory block on the shared heap with at ## least ``size`` bytes. The block has to be freed with ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! - proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, + gcsafe.} = ## allocates a new memory block on the shared heap with at ## least ``T.sizeof * size`` bytes. The block has to be freed with ## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! cast[ptr T](allocShared(T.sizeof * size)) - proc allocShared0*(size: int): pointer {.noconv, rtl.} + proc allocShared0*(size: int): pointer {.noconv, rtl, gcsafe.} ## allocates a new memory block on the shared heap with at ## least ``size`` bytes. The block has to be freed with ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. @@ -1318,7 +1323,8 @@ when not defined(nimrodVM): ## The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``createSharedU``. cast[ptr T](allocShared0(T.sizeof * size)) - proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.} + proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl, + gcsafe.} ## grows or shrinks a given memory block on the heap. If p is **nil** ## then a new memory block is returned. In either way the block has at ## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** @@ -1331,13 +1337,13 @@ when not defined(nimrodVM): ## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other ## cases the block has to be freed with ``freeShared``. cast[ptr T](reallocShared(p, T.sizeof * newSize)) - proc deallocShared*(p: pointer) {.noconv, rtl.} + proc deallocShared*(p: pointer) {.noconv, rtl, gcsafe.} ## frees the memory allocated with ``allocShared``, ``allocShared0`` or ## ``reallocShared``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed ## memory (or just freeing it twice!) a core dump may happen ## or other memory may be corrupted. - proc freeShared*[T](p: ptr T) {.inline.} = + proc freeShared*[T](p: ptr T) {.inline, gcsafe.} = ## frees the memory allocated with ``createShared``, ``createSharedU`` or ## ``resizeShared``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed @@ -1898,7 +1904,7 @@ const nimrodStackTrace = compileOption("stacktrace") # of the code var - globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.} + globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall, gcsafe.} ## with this hook you can influence exception handling on a global level. ## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## application code should never set this hook! You better know what you @@ -1906,7 +1912,7 @@ var ## exception is caught and does not propagate further through the call ## stack. - localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall.} + localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall, gcsafe.} ## with this hook you can influence exception handling on a ## thread local level. ## If not nil, every 'raise' statement ends up calling this hook. Ordinary @@ -1914,7 +1920,7 @@ var ## do when setting this. If ``localRaiseHook`` returns false, the exception ## is caught and does not propagate further through the call stack. - outOfMemHook*: proc () {.nimcall, tags: [].} + outOfMemHook*: proc () {.nimcall, tags: [], gcsafe.} ## set this variable to provide a procedure that should be called ## in case of an `out of memory`:idx: event. The standard handler ## writes an error message and terminates the program. `outOfMemHook` can @@ -1965,7 +1971,7 @@ elif hostOS != "standalone": inc(i) {.pop.} -proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO].} +proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO], gcsafe.} ## special built-in that takes a variable number of arguments. Each argument ## is converted to a string via ``$``, so it works for user-defined ## types that have an overloaded ``$`` operator. @@ -2119,14 +2125,15 @@ when not defined(JS): #and not defined(NimrodVM): ## `useStdoutAsStdmsg` compile-time switch. proc open*(f: var TFile, filename: string, - mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [].} + mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [], + gcsafe.} ## Opens a file named `filename` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. ## This throws no exception if the file could not be opened. proc open*(f: var TFile, filehandle: TFileHandle, - mode: TFileMode = fmRead): bool {.tags: [].} + mode: TFileMode = fmRead): bool {.tags: [], gcsafe.} ## Creates a ``TFile`` from a `filehandle` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. @@ -2141,7 +2148,7 @@ when not defined(JS): #and not defined(NimrodVM): sysFatal(EIO, "cannot open: ", filename) proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {. - tags: [].} + tags: [], gcsafe.} ## reopens the file `f` with given `filename` and `mode`. This ## is often used to redirect the `stdin`, `stdout` or `stderr` ## file variables. @@ -2151,7 +2158,7 @@ when not defined(JS): #and not defined(NimrodVM): proc close*(f: TFile) {.importc: "fclose", header: "<stdio.h>", tags: [].} ## Closes the file. - proc endOfFile*(f: TFile): bool {.tags: [].} + proc endOfFile*(f: TFile): bool {.tags: [], gcsafe.} ## Returns true iff `f` is at the end. proc readChar*(f: TFile): char {. @@ -2161,39 +2168,40 @@ when not defined(JS): #and not defined(NimrodVM): importc: "fflush", header: "<stdio.h>", tags: [FWriteIO].} ## Flushes `f`'s buffer. - proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].} + proc readAll*(file: TFile): TaintedString {.tags: [FReadIO], gcsafe.} ## Reads all data from the stream `file`. ## ## Raises an IO exception in case of an error. It is an error if the ## current file position is not at the beginning of the file. - proc readFile*(filename: string): TaintedString {.tags: [FReadIO].} + proc readFile*(filename: string): TaintedString {.tags: [FReadIO], gcsafe.} ## Opens a file named `filename` for reading. Then calls `readAll` ## and closes the file afterwards. Returns the string. ## Raises an IO exception in case of an error. - proc writeFile*(filename, content: string) {.tags: [FWriteIO].} + proc writeFile*(filename, content: string) {.tags: [FWriteIO], gcsafe.} ## Opens a file named `filename` for writing. Then writes the ## `content` completely to the file and closes the file afterwards. ## Raises an IO exception in case of an error. - proc write*(f: TFile, r: float32) {.tags: [FWriteIO].} - proc write*(f: TFile, i: int) {.tags: [FWriteIO].} - proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO].} - proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO].} - proc write*(f: TFile, s: string) {.tags: [FWriteIO].} - proc write*(f: TFile, b: bool) {.tags: [FWriteIO].} - proc write*(f: TFile, c: char) {.tags: [FWriteIO].} - proc write*(f: TFile, c: cstring) {.tags: [FWriteIO].} - proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO].} + proc write*(f: TFile, r: float32) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, i: int) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, s: string) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, b: bool) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, c: char) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, c: cstring) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO], gcsafe.} ## Writes a value to the file `f`. May throw an IO exception. - proc readLine*(f: TFile): TaintedString {.tags: [FReadIO].} + proc readLine*(f: TFile): TaintedString {.tags: [FReadIO], gcsafe.} ## reads a line of text from the file `f`. May throw an IO exception. ## A line of text may be delimited by ``CR``, ``LF`` or ## ``CRLF``. The newline character(s) are not part of the returned string. - proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO].} + proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO], + gcsafe.} ## reads a line of text from the file `f` into `line`. `line` must not be ## ``nil``! May throw an IO exception. ## A line of text may be delimited by ``CR``, ``LF`` or @@ -2201,53 +2209,55 @@ when not defined(JS): #and not defined(NimrodVM): ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. - proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, tags: [FWriteIO].} + proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, + tags: [FWriteIO], gcsafe.} ## writes the values `x` to `f` and then writes "\n". ## May throw an IO exception. - proc getFileSize*(f: TFile): int64 {.tags: [FReadIO].} + proc getFileSize*(f: TFile): int64 {.tags: [FReadIO], gcsafe.} ## retrieves the file size (in bytes) of `f`. proc readBytes*(f: TFile, a: var openArray[int8], start, len: int): int {. - tags: [FReadIO].} + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. proc readChars*(f: TFile, a: var openArray[char], start, len: int): int {. - tags: [FReadIO].} + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. - proc readBuffer*(f: TFile, buffer: pointer, len: int): int {.tags: [FReadIO].} + proc readBuffer*(f: TFile, buffer: pointer, len: int): int {. + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer pointed to by `buffer`. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. proc writeBytes*(f: TFile, a: openArray[int8], start, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. proc writeChars*(f: TFile, a: openArray[char], start, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. proc writeBuffer*(f: TFile, buffer: pointer, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of buffer pointed to by the parameter `buffer` to the ## file `f`. Returns the number of actual written bytes, which may be less ## than `len` in case of an error. - proc setFilePos*(f: TFile, pos: int64) + proc setFilePos*(f: TFile, pos: int64) {.gcsafe.} ## sets the position of the file pointer that is used for read/write ## operations. The file's first byte has the index zero. - proc getFilePos*(f: TFile): int64 + proc getFilePos*(f: TFile): int64 {.gcsafe.} ## retrieves the current position of the file pointer that is used to ## read from the file `f`. The file's first byte has the index zero. @@ -2290,10 +2300,12 @@ when not defined(JS): #and not defined(NimrodVM): dealloc(a) when not defined(NimrodVM): - proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, discardable.} + proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, + discardable, gcsafe.} ## atomic increment of `memLoc`. Returns the value after the operation. - proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, discardable.} + proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, + discardable, gcsafe.} ## atomic decrement of `memLoc`. Returns the value after the operation. include "system/atomics" diff --git a/lib/system/avltree.nim b/lib/system/avltree.nim index fc965d6aa..bced15d6a 100644 --- a/lib/system/avltree.nim +++ b/lib/system/avltree.nim @@ -51,7 +51,7 @@ proc split(t: var PAvlNode) = t.link[0] = temp inc t.level -proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) = +proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} = if t == bottom: t = allocAvlNode(a, key, upperBound) else: @@ -64,7 +64,7 @@ proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) = skew(t) split(t) -proc del(a: var TMemRegion, t: var PAvlNode, x: int) = +proc del(a: var TMemRegion, t: var PAvlNode, x: int) {.gcsafe.} = if t == bottom: return a.last = t if x <% t.key: diff --git a/lib/system/cgprocs.nim b/lib/system/cgprocs.nim index e30cfa469..d483c61bd 100644 --- a/lib/system/cgprocs.nim +++ b/lib/system/cgprocs.nim @@ -9,7 +9,7 @@ # Headers for procs that the code generator depends on ("compilerprocs") -proc addChar(s: NimString, c: char): NimString {.compilerProc.} +proc addChar(s: NimString, c: char): NimString {.compilerProc, gcsafe.} type TLibHandle = pointer # private type @@ -21,5 +21,5 @@ proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr {.compilerproc.} proc nimLoadLibraryError(path: string) {.compilerproc, noinline.} -proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline.} +proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe.} diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index e50ba7b9f..612a9e729 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -11,7 +11,7 @@ # use the heap (and nor exceptions) do not include the GC or memory allocator. var - errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO].}) + errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO], gcsafe.}) ## Function that will be called ## instead of stdmsg.write when printing stacktrace. ## Unstable API. @@ -32,10 +32,10 @@ proc showErrorMessage(data: cstring) = else: writeToStdErr(data) -proc chckIndx(i, a, b: int): int {.inline, compilerproc.} -proc chckRange(i, a, b: int): int {.inline, compilerproc.} -proc chckRangeF(x, a, b: float): float {.inline, compilerproc.} -proc chckNil(p: pointer) {.noinline, compilerproc.} +proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.} +proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.} var framePtr {.rtlThreadVar.}: PFrame @@ -322,5 +322,5 @@ when not defined(noSignalHandler): proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: - type TSignalHandler = proc (sig: cint) {.noconv.} + type TSignalHandler = proc (sig: cint) {.noconv, gcsafe.} c_signal(SIGINT, cast[TSignalHandler](hook)) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index ec1760914..3b85fe600 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -51,7 +51,7 @@ type waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack, waCollectWhite, - TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.} + TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -152,11 +152,11 @@ template gcTrace(cell, state: expr): stmt {.immediate.} = when traceGC: traceCell(cell, state) # 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) +proc collectCT(gch: var TGcHeap) {.gcsafe.} +proc isOnStack*(p: pointer): bool {.noinline, gcsafe.} +proc forAllChildren(cell: PCell, op: TWalkOp) {.gcsafe.} +proc doOperation(p: pointer, op: TWalkOp) {.gcsafe.} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) {.gcsafe.} # we need the prototype here for debugging purposes when hasThreadSupport and hasSharedHeap: @@ -294,7 +294,7 @@ proc initGC() = when useMarkForDebug or useBackupGc: type - TGlobalMarkerProc = proc () {.nimcall.} + TGlobalMarkerProc = proc () {.nimcall, gcsafe.} var globalMarkersLen: int globalMarkers: array[0.. 7_000, TGlobalMarkerProc] @@ -311,7 +311,7 @@ proc cellsetReset(s: var TCellSet) = deinit(s) init(s) -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) {.gcsafe.} = var d = cast[TAddress](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) @@ -680,10 +680,11 @@ proc doOperation(p: pointer, op: TWalkOp) = proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = doOperation(d, TWalkOp(op)) -proc collectZCT(gch: var TGcHeap): bool +proc collectZCT(gch: var TGcHeap): bool {.gcsafe.} when useMarkForDebug or useBackupGc: - proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl.} + proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl, + gcsafe.} proc collectRoots(gch: var TGcHeap) = for s in elements(gch.cycleRoots): diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 9d8ece7df..64174e60f 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -85,7 +85,7 @@ type base: ptr TNimType node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum finalizer: pointer # the finalizer for the type - marker: proc (p: pointer, op: int) {.nimcall.} # marker proc for GC + marker: proc (p: pointer, op: int) {.nimcall, gcsafe.} # marker proc for GC PNimType = ptr TNimType # node.len may be the ``first`` element of a set diff --git a/lib/system/syslocks.nim b/lib/system/syslocks.nim index 5e3b04b7f..b8ed29cfc 100644 --- a/lib/system/syslocks.nim +++ b/lib/system/syslocks.nim @@ -52,7 +52,7 @@ when defined(Windows): proc closeHandle(hObject: THandle) {.stdcall, noSideEffect, dynlib: "kernel32", importc: "CloseHandle".} proc waitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {. - stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".} + stdcall, dynlib: "kernel32", importc: "WaitForSingleObject", noSideEffect.} proc signalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect, dynlib: "kernel32", importc: "SetEvent".} @@ -89,16 +89,16 @@ else: proc releaseSys(L: var TSysLock) {.noSideEffect, importc: "pthread_mutex_unlock", header: "<pthread.h>".} - proc deinitSys(L: var TSysLock) {. + proc deinitSys(L: var TSysLock) {.noSideEffect, importc: "pthread_mutex_destroy", header: "<pthread.h>".} proc initSysCond(cond: var TSysCond, cond_attr: pointer = nil) {. - importc: "pthread_cond_init", header: "<pthread.h>".} + importc: "pthread_cond_init", header: "<pthread.h>", noSideEffect.} proc waitSysCond(cond: var TSysCond, lock: var TSysLock) {. - importc: "pthread_cond_wait", header: "<pthread.h>".} + importc: "pthread_cond_wait", header: "<pthread.h>", noSideEffect.} proc signalSysCond(cond: var TSysCond) {. - importc: "pthread_cond_signal", header: "<pthread.h>".} + importc: "pthread_cond_signal", header: "<pthread.h>", noSideEffect.} - proc deinitSysCond(cond: var TSysCond) {. + proc deinitSysCond(cond: var TSysCond) {.noSideEffect, importc: "pthread_cond_destroy", header: "<pthread.h>".} diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim new file mode 100644 index 000000000..3a641aba6 --- /dev/null +++ b/lib/system/sysspawn.nim @@ -0,0 +1,172 @@ +# Implements Nimrod's 'spawn'. + +{.push stackTrace:off.} +include system.syslocks + +when (defined(x86) or defined(amd64)) and defined(gcc): + proc cpuRelax {.inline.} = + {.emit: """asm volatile("pause" ::: "memory");""".} +elif (defined(x86) or defined(amd64)) and defined(vcc): + proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".} +elif defined(intelc): + proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".} +else: + from os import sleep + + proc cpuRelax {.inline.} = os.sleep(1) + +when defined(windows): + proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32 + {.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.} + + proc cas(p: ptr bool; oldValue, newValue: bool): bool = + interlockedCompareExchange(p, newValue.int32, oldValue.int32) != 0 + +else: + # this is valid for GCC and Intel C++ + proc cas(p: ptr bool; oldValue, newValue: bool): bool + {.importc: "__sync_bool_compare_and_swap", nodecl.} + +# We declare our own condition variables here to get rid of the dummy lock +# on Windows: + +type + CondVar = object + c: TSysCond + when defined(posix): + stupidLock: TSysLock + +proc createCondVar(): CondVar = + initSysCond(result.c) + when defined(posix): + initSysLock(result.stupidLock) + acquireSys(result.stupidLock) + +proc await(cv: var CondVar) = + when defined(posix): + waitSysCond(cv.c, cv.stupidLock) + else: + waitSysCondWindows(cv.c) + +proc signal(cv: var CondVar) = signalSysCond(cv.c) + +type + FastCondVar = object + event, slowPath: bool + slow: CondVar + +proc createFastCondVar(): FastCondVar = + initSysCond(result.slow.c) + when defined(posix): + initSysLock(result.slow.stupidLock) + acquireSys(result.slow.stupidLock) + result.event = false + result.slowPath = false + +proc await(cv: var FastCondVar) = + #for i in 0 .. 50: + # if cas(addr cv.event, true, false): + # # this is a HIT: Triggers > 95% in my tests. + # return + # cpuRelax() + #cv.slowPath = true + await(cv.slow) + cv.event = false + +proc signal(cv: var FastCondVar) = + cv.event = true + #if cas(addr cv.slowPath, true, false): + signal(cv.slow) + +{.pop.} + +# ---------------------------------------------------------------------------- + +type + WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.} + Worker = object + taskArrived: CondVar + taskStarted: FastCondVar #\ + # task data: + f: WorkerProc + data: pointer + ready: bool # put it here for correct alignment! + +proc nimArgsPassingDone(p: pointer) {.compilerProc.} = + let w = cast[ptr Worker](p) + signal(w.taskStarted) + +var gSomeReady = createFastCondVar() + +proc slave(w: ptr Worker) {.thread.} = + while true: + w.ready = true # If we instead signal "workerReady" we need the scheduler + # to notice this. The scheduler could then optimize the + # layout of the worker threads (e.g. keep the list sorted) + # so that no search for a "ready" thread is necessary. + # This might be implemented later, but is more tricky than + # it looks because 'spawn' itself can run concurrently. + signal(gSomeReady) + await(w.taskArrived) + assert(not w.ready) + if w.data != nil: + w.f(w, w.data) + w.data = nil + +const NumThreads = 4 + +var + workers: array[NumThreads, TThread[ptr Worker]] + workersData: array[NumThreads, Worker] + +proc setup() = + for i in 0.. <NumThreads: + workersData[i].taskArrived = createCondVar() + workersData[i].taskStarted = createFastCondVar() + createThread(workers[i], slave, addr(workersData[i])) + +proc preferSpawn*(): bool = + ## Use this proc to determine quickly if a 'spawn' or a direct call is + ## preferable. If it returns 'true' a 'spawn' may make sense. In general + ## it is not necessary to call this directly; use 'spawnX' instead. + result = gSomeReady.event + +proc spawn*(call: stmt) {.magic: "Spawn".} + ## always spawns a new task, so that the 'call' is never executed on + ## the calling thread. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has 'void' as the return type. + +template spawnX*(call: stmt) = + ## spawns a new task if a CPU core is ready, otherwise executes the + ## call in the calling thread. Usually it is advised to + ## use 'spawn' in order to not block the producer for an unknown + ## amount of time. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has 'void' as the return type. + if preferSpawn(): spawn call + else: call + +proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} = + # implementation of 'spawn' that is used by the code generator. + while true: + for i in 0.. high(workers): + let w = addr(workersData[i]) + if cas(addr w.ready, true, false): + w.data = data + w.f = fn + signal(w.taskArrived) + await(w.taskStarted) + return + await(gSomeReady) + +proc sync*() = + ## a simple barrier to wait for all spawn'ed tasks. If you need more elaborate + ## waiting, you have to use an explicit barrier. + while true: + var allReady = true + for i in 0 .. high(workers): + if not allReady: break + allReady = allReady and workersData[i].ready + if allReady: break + await(gSomeReady) + +setup() diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 69a3c5c81..7024943b3 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -522,7 +522,7 @@ proc inet_addr*(cp: cstring): int32 {. stdcall, importc: "inet_addr", dynlib: ws2dll.} proc WSAFDIsSet(s: TSocketHandle, FDSet: var TFdSet): bool {. - stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll.} + stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll, noSideEffect.} proc FD_ISSET*(Socket: TSocketHandle, FDSet: var TFdSet): cint = result = if WSAFDIsSet(Socket, FDSet): 1'i32 else: 0'i32 |