New concurrency model: next steps

This commit is contained in:
Araq 2014-04-19 22:24:43 +02:00
commit 3b69a8d27a
23 changed files with 460 additions and 200 deletions

View file

@ -299,7 +299,7 @@ const
nkEffectList* = nkArgList nkEffectList* = nkArgList
# hacks ahead: an nkEffectList is a node with 4 children: # hacks ahead: an nkEffectList is a node with 4 children:
exceptionEffects* = 0 # exceptions at position 0 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 writeEffects* = 2 # write effects at position 2
tagEffects* = 3 # user defined tag ('gc', 'time' etc.) tagEffects* = 3 # user defined tag ('gc', 'time' etc.)
effectListLen* = 4 # list of effects list effectListLen* = 4 # list of effects list
@ -432,7 +432,7 @@ type
tfAcyclic, # type is acyclic (for GC optimization) tfAcyclic, # type is acyclic (for GC optimization)
tfEnumHasHoles, # enum cannot be mapped into a range tfEnumHasHoles, # enum cannot be mapped into a range
tfShallow, # type can be shallow copied on assignment 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 tfFromGeneric, # type is an instantiation of a generic; this is needed
# because for instantiations of objects, structural # because for instantiations of objects, structural
# type equality has to be used # type equality has to be used
@ -509,6 +509,7 @@ const
tfIncompleteStruct* = tfVarargs tfIncompleteStruct* = tfVarargs
tfUncheckedArray* = tfVarargs tfUncheckedArray* = tfVarargs
tfUnion* = tfNoSideEffect tfUnion* = tfNoSideEffect
tfGcSafe* = tfThread
skError* = skUnknown skError* = skUnknown
# type flags that are essential for type equality: # type flags that are essential for type equality:
@ -978,6 +979,8 @@ template `{}=`*(n: PNode, i: int, s: PNode): stmt =
var emptyNode* = newNode(nkEmpty) var emptyNode* = newNode(nkEmpty)
# There is a single empty node that is shared! Do not overwrite it! # 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 = proc isMetaType*(t: PType): bool =
return t.kind in tyMetaTypes or return t.kind in tyMetaTypes or
(t.kind == tyStatic and t.n == nil) 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) = proc propagateToOwner*(owner, elem: PType) =
const HaveTheirOwnEmpty = {tySequence, tySet} const HaveTheirOwnEmpty = {tySequence, tySet}
owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta, owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta})
tfHasGCedMem})
if tfNotNil in elem.flags: if tfNotNil in elem.flags:
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}: if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}:
owner.flags.incl tfNotNil owner.flags.incl tfNotNil
@ -1328,8 +1330,10 @@ proc propagateToOwner*(owner, elem: PType) =
if elem.isMetaType: if elem.isMetaType:
owner.flags.incl tfHasMeta owner.flags.incl tfHasMeta
if owner.kind != tyProc:
if elem.kind in {tyString, tyRef, tySequence} or if elem.kind in {tyString, tyRef, tySequence} or
elem.kind == tyProc and elem.callConv == ccClosure: elem.kind == tyProc and elem.callConv == ccClosure or
tfHasGCedMem in elem.flags:
owner.flags.incl tfHasGCedMem owner.flags.incl tfHasGCedMem
proc rawAddSon*(father, son: PType) = proc rawAddSon*(father, son: PType) =

View file

@ -448,9 +448,9 @@ proc debug(n: PSym) =
writeln(stdout, "skUnknown") writeln(stdout, "skUnknown")
else: else:
#writeln(stdout, ropeToStr(symToYaml(n, 0, 1))) #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), 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) = proc debug(n: PType) =
writeln(stdout, ropeToStr(debugType(n))) writeln(stdout, ropeToStr(debugType(n)))

View file

@ -151,6 +151,8 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode =
if n.kind notin nkCallKinds or not n.typ.isEmptyType: if n.kind notin nkCallKinds or not n.typ.isEmptyType:
localError(n.info, "'spawn' takes a call expression of type void") localError(n.info, "'spawn' takes a call expression of type void")
return return
if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}:
localError(n.info, "'spawn' takes a GC safe call expression")
var var
threadParam = newSym(skParam, getIdent"thread", owner, n.info) threadParam = newSym(skParam, getIdent"thread", owner, n.info)
argsParam = newSym(skParam, getIdent"args", 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 # we pick n's type here, which hopefully is 'tyArray' and not
# 'tyOpenArray': # 'tyOpenArray':
var argType = n[i].typ.skipTypes(abstractInst) 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") localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter")
elif containsTyRef(argType): elif containsTyRef(argType):
localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure") localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure")

View file

@ -118,7 +118,7 @@ type
warnNilStatement, warnAnalysisLoophole, warnNilStatement, warnAnalysisLoophole,
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
warnEachIdentIsTuple, warnShadowIdent, warnEachIdentIsTuple, warnShadowIdent,
warnProveInit, warnProveField, warnProveIndex, warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe,
warnUninit, warnGcMem, warnUser, warnUninit, warnGcMem, warnUser,
hintSuccess, hintSuccessX, hintSuccess, hintSuccessX,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, 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]", 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]", warnProveField: "cannot prove that field '$1' is accessible [ProveField]",
warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]", warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]",
warnGcUnsafe: "not GC-safe: '$1' [GcUnsafe]",
warnUninit: "'$1' might not have been initialized [Uninit]", warnUninit: "'$1' might not have been initialized [Uninit]",
warnGcMem: "'$1' uses GC'ed memory [GcMem]", warnGcMem: "'$1' uses GC'ed memory [GcMem]",
warnUser: "$1 [User]", warnUser: "$1 [User]",
@ -407,7 +408,7 @@ const
hintUser: "$1 [User]"] hintUser: "$1 [User]"]
const const
WarningsToStr*: array[0..24, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr*: array[0..25, string] = ["CannotOpenFile", "OctalEscape",
"XIsNeverRead", "XmightNotBeenInit", "XIsNeverRead", "XmightNotBeenInit",
"Deprecated", "ConfigDeprecated", "Deprecated", "ConfigDeprecated",
"SmallLshouldNotBeUsed", "UnknownMagic", "SmallLshouldNotBeUsed", "UnknownMagic",
@ -415,7 +416,8 @@ const
"CommentXIgnored", "NilStmt", "CommentXIgnored", "NilStmt",
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent",
"ProveInit", "ProveField", "ProveIndex", "Uninit", "GcMem", "User"] "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "Uninit",
"GcMem", "User"]
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
@ -557,7 +559,7 @@ proc sourceLine*(i: TLineInfo): PRope
var var
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
{warnShadowIdent, warnUninit, {warnShadowIdent, warnUninit,
warnProveField, warnProveIndex} warnProveField, warnProveIndex, warnGcUnsafe}
gErrorCounter*: int = 0 # counts the number of errors gErrorCounter*: int = 0 # counts the number of errors
gHintCounter*: int = 0 gHintCounter*: int = 0
gWarnCounter*: int = 0 gWarnCounter*: int = 0

View file

@ -24,7 +24,7 @@ const
wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC,
wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl, wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl,
wGensym, wInject, wRaises, wTags, wOperator, wDelegator} wGensym, wInject, wRaises, wTags, wUses, wOperator, wDelegator, wGcSafe}
converterPragmas* = procPragmas converterPragmas* = procPragmas
methodPragmas* = procPragmas methodPragmas* = procPragmas
templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty, templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty,
@ -35,7 +35,7 @@ const
iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect, iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect,
wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern, wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern,
wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises,
wTags, wOperator} wTags, wUses, wOperator, wGcSafe}
exprPragmas* = {wLine} exprPragmas* = {wLine}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
@ -48,7 +48,7 @@ const
lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader,
wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame,
wRaises, wTags} wRaises, wUses, wTags, wGcSafe}
typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
@ -64,7 +64,7 @@ const
wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject} wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject}
letPragmas* = varPragmas letPragmas* = varPragmas
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect,
wThread, wRaises, wTags} wThread, wRaises, wUses, wTags, wGcSafe}
allRoutinePragmas* = procPragmas + iteratorPragmas + lambdaPragmas allRoutinePragmas* = procPragmas + iteratorPragmas + lambdaPragmas
proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
@ -513,6 +513,27 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) =
else: else:
invalidPragma(n) 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) = proc typeBorrow(sym: PSym, n: PNode) =
if n.kind == nkExprColonExpr: if n.kind == nkExprColonExpr:
let it = n.sons[1] 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, sfThread)
incl(sym.flags, sfProcvar) incl(sym.flags, sfProcvar)
if sym.typ != nil: incl(sym.typ.flags, tfThread) 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: of wPacked:
noVal(it) noVal(it)
if sym.typ == nil: invalidPragma(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) if sym == nil: invalidPragma(it)
of wLine: pragmaLine(c, it) of wLine: pragmaLine(c, it)
of wRaises, wTags: pragmaRaisesOrTags(c, it) of wRaises, wTags: pragmaRaisesOrTags(c, it)
of wUses: pragmaUses(c, it)
of wOperator: of wOperator:
if sym == nil: invalidPragma(it) if sym == nil: invalidPragma(it)
else: sym.position = expectIntLit(c, it) else: sym.position = expectIntLit(c, it)

View file

@ -59,15 +59,19 @@ discard """
--> we need a stack of scopes for this analysis --> we need a stack of scopes for this analysis
""" """
const trackGlobals = false ## we don't need it for now
type type
TEffects = object TEffects = object
exc: PNode # stack of exceptions exc: PNode # stack of exceptions
tags: PNode # list of tags tags: PNode # list of tags
uses: PNode # list of used global variables
bottom: int bottom: int
owner: PSym owner: PSym
init: seq[int] # list of initialized variables init: seq[int] # list of initialized variables
guards: TModel # nested guards guards: TModel # nested guards
locked: seq[PNode] # locked locations locked: seq[PNode] # locked locations
gcUnsafe: bool
PEffects = var TEffects PEffects = var TEffects
proc isLocalVar(a: PEffects, s: PSym): bool = proc isLocalVar(a: PEffects, s: PSym): bool =
@ -89,20 +93,29 @@ proc initVarViaNew(a: PEffects, n: PNode) =
# are initialized: # are initialized:
initVar(a, n) 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) = proc useVar(a: PEffects, n: PNode) =
let s = n.sym let s = n.sym
if isLocalVar(a, s): if isLocalVar(a, s):
if s.id notin a.init: if s.id notin a.init:
if {tfNeedsInit, tfNotNil} * s.typ.flags != {}: if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
when true:
message(n.info, warnProveInit, s.name.s) message(n.info, warnProveInit, s.name.s)
else:
Message(n.info, errGenerated,
"'$1' might not have been initialized" % s.name.s)
else: else:
message(n.info, warnUninit, s.name.s) message(n.info, warnUninit, s.name.s)
# prevent superfluous warnings about the same variable: # prevent superfluous warnings about the same variable:
a.init.add s.id 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 type
TIntersection = seq[tuple[id, count: int]] # a simple count table TIntersection = seq[tuple[id, count: int]] # a simple count table
@ -132,6 +145,10 @@ proc createTag(n: PNode): PNode =
result.typ = sysTypeFromName"TEffect" result.typ = sysTypeFromName"TEffect"
if not n.isNil: result.info = n.info 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) = proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
assert e.kind != nkRaiseStmt assert e.kind != nkRaiseStmt
var aa = a.exc var aa = a.exc
@ -161,9 +178,17 @@ proc mergeTags(a: PEffects, b, comesFrom: PNode) =
else: else:
for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil) 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) = proc listEffects(a: PEffects) =
for e in items(a.exc): message(e.info, hintUser, typeToString(e.typ)) 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.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) = proc catches(tracked: PEffects, e: PType) =
let e = skipTypes(e, skipPtrs) let e = skipTypes(e, skipPtrs)
@ -289,6 +314,13 @@ proc documentRaises*(n: PNode) =
if n.sons[namePos].kind != nkSym: return if n.sons[namePos].kind != nkSym: return
documentEffect(n, n.sons[pragmasPos], wRaises, exceptionEffects) documentEffect(n, n.sons[pragmasPos], wRaises, exceptionEffects)
documentEffect(n, n.sons[pragmasPos], wTags, tagEffects) 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) = proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let pragma = s.ast.sons[pragmasPos] let pragma = s.ast.sons[pragmasPos]
@ -298,6 +330,14 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let tagSpec = effectSpec(pragma, wTags) let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n) 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) = proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
let n = n.skipConv let n = n.skipConv
if paramType != nil and tfNotNil in paramType.flags and if paramType != nil and tfNotNil in paramType.flags and
@ -330,9 +370,18 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
else: else:
addEffect(tracked, createRaise(n)) addEffect(tracked, createRaise(n))
addTag(tracked, createTag(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: else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], 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) notNilCheck(tracked, n, paramType)
proc breaksBlock(n: PNode): bool = 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 # XXX: in rare situations, templates and macros will reach here after
# calling getAst(templateOrMacro()). Currently, templates and macros # calling getAst(templateOrMacro()). Currently, templates and macros
# are indistinguishable from normal procs (both have tyProc type) and # 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 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] var effectList = op.n.sons[0]
if a.kind == nkSym and a.sym.kind == skMethod: if a.kind == nkSym and a.sym.kind == skMethod:
propagateEffects(tracked, n, a.sym) propagateEffects(tracked, n, a.sym)
@ -462,9 +514,11 @@ proc track(tracked: PEffects, n: PNode) =
elif isIndirectCall(a, tracked.owner): elif isIndirectCall(a, tracked.owner):
addEffect(tracked, createRaise(n)) addEffect(tracked, createRaise(n))
addTag(tracked, createTag(n)) addTag(tracked, createTag(n))
when trackGlobals: addUse(tracked, createAnyGlobal(n))
else: else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], 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)) 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}: if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
# may not look like an assignment, but it is: # may not look like an assignment, but it is:
@ -531,14 +585,21 @@ proc track(tracked: PEffects, n: PNode) =
else: else:
for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) 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; # check that any real exception is listed in 'spec'; mark those as used;
# report any unused exception # report any unused exception
var used = initIntSet() var used = initIntSet()
for r in items(real): for r in items(real):
block search: block search:
for s in 0 .. <spec.len: for s in 0 .. <spec.len:
if safeInheritanceDiff(r.excType, spec[s].typ) <= 0: if effectPredicate(spec[s], r):
used.incl(s) used.incl(s)
break search break search
# XXX call graph analysis would be nice here! # XXX call graph analysis would be nice here!
@ -560,11 +621,18 @@ proc checkMethodEffects*(disp, branch: PSym) =
let raisesSpec = effectSpec(p, wRaises) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):
checkRaisesSpec(raisesSpec, actual.sons[exceptionEffects], 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) let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
checkRaisesSpec(tagsSpec, actual.sons[tagEffects], 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) = proc setEffectsForProcType*(t: PType, n: PNode) =
var effects = t.n.sons[0] var effects = t.n.sons[0]
@ -573,21 +641,26 @@ proc setEffectsForProcType*(t: PType, n: PNode) =
let let
raisesSpec = effectSpec(n, wRaises) raisesSpec = effectSpec(n, wRaises)
tagsSpec = effectSpec(n, wTags) 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 internalAssert effects.len == 0
newSeq(effects.sons, effectListLen) newSeq(effects.sons, effectListLen)
if not isNil(raisesSpec): if not isNil(raisesSpec):
effects.sons[exceptionEffects] = raisesSpec effects.sons[exceptionEffects] = raisesSpec
if not isNil(tagsSpec): if not isNil(tagsSpec):
effects.sons[tagEffects] = tagsSpec effects.sons[tagEffects] = tagsSpec
if not isNil(usesSpec):
effects.sons[usesEffects] = usesSpec
proc initEffects(effects: PNode; s: PSym; t: var TEffects) = proc initEffects(effects: PNode; s: PSym; t: var TEffects) =
newSeq(effects.sons, effectListLen) newSeq(effects.sons, effectListLen)
effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info) effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
effects.sons[tagEffects] = newNodeI(nkArgList, s.info) effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
effects.sons[usesEffects] = newNodeI(nkArgList, s.info)
t.exc = effects.sons[exceptionEffects] t.exc = effects.sons[exceptionEffects]
t.tags = effects.sons[tagEffects] t.tags = effects.sons[tagEffects]
t.uses = effects.sons[usesEffects]
t.owner = s t.owner = s
t.init = @[] t.init = @[]
t.guards = @[] t.guards = @[]
@ -602,7 +675,6 @@ proc trackProc*(s: PSym, body: PNode) =
var t: TEffects var t: TEffects
initEffects(effects, s, t) initEffects(effects, s, t)
track(t, body) track(t, body)
if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and
s.kind in {skProc, skConverter, skMethod}: s.kind in {skProc, skConverter, skMethod}:
var res = s.ast.sons[resultPos].sym # get result symbol 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) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):
checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ", checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ",
hints=on) hints=on, subtypeRelation)
# after the check, use the formal spec: # after the check, use the formal spec:
effects.sons[exceptionEffects] = raisesSpec effects.sons[exceptionEffects] = raisesSpec
let tagsSpec = effectSpec(p, wTags) let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec): if not isNil(tagsSpec):
checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ", checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ",
hints=off) hints=off, subtypeRelation)
# after the check, use the formal spec: # after the check, use the formal spec:
effects.sons[tagEffects] = tagsSpec 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) = proc trackTopLevelStmt*(module: PSym; n: PNode) =
if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef,
nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:

View file

@ -402,7 +402,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags: if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
return isNone return isNone
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}: elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
# noSideEffect implies ``tfThread``! XXX really? # noSideEffect implies ``tfThread``!
return isNone return isNone
elif f.flags * {tfIterator} != a.flags * {tfIterator}: elif f.flags * {tfIterator} != a.flags * {tfIterator}:
return isNone return isNone

View file

@ -1,7 +1,7 @@
# #
# #
# The Nimrod Compiler # The Nimrod Compiler
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2014 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -44,7 +44,8 @@ type
wImportCompilerProc, wImportCompilerProc,
wImportc, wExportc, wIncompleteStruct, wRequiresInit, wImportc, wExportc, wIncompleteStruct, wRequiresInit,
wAlign, wNodecl, wPure, wSideeffect, wHeader, 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, wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef,
wLinedir, wStacktrace, wLinetrace, wLink, wCompile, wLinedir, wStacktrace, wLinetrace, wLink, wCompile,
wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger, wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger,
@ -63,7 +64,7 @@ type
wAcyclic, wShallow, wUnroll, wLinearScanEnd, wComputedGoto, wInjectStmt, wAcyclic, wShallow, wUnroll, wLinearScanEnd, wComputedGoto, wInjectStmt,
wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit,
wAsmNoStackFrame, wAsmNoStackFrame,
wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wUses,
wAuto, wBool, wCatch, wChar, wClass, wAuto, wBool, wCatch, wChar, wClass,
wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@ -125,7 +126,7 @@ const
"importcpp", "importobjc", "importcpp", "importobjc",
"importcompilerproc", "importc", "exportc", "incompletestruct", "importcompilerproc", "importc", "exportc", "incompletestruct",
"requiresinit", "align", "nodecl", "pure", "sideeffect", "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", "compilerproc", "procvar", "fatal", "error", "warning", "hint", "line",
"push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace", "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace",
"link", "compile", "linksys", "deprecated", "varargs", "link", "compile", "linksys", "deprecated", "varargs",
@ -146,7 +147,7 @@ const
"computedgoto", "injectstmt", "computedgoto", "injectstmt",
"write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit", "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
"asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked", "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
"guard", "guard", "uses",
"auto", "bool", "catch", "char", "class", "auto", "bool", "catch", "char", "class",
"const_cast", "default", "delete", "double", "const_cast", "default", "delete", "double",

View file

@ -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. multicore processors that will become the norm in the future.
Thread pragma 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 that it is invalid to construct a data structure that consists of memory
allocated from different (thread local) heaps. allocated from different (thread local) heaps.
Since the semantic checking of threads requires whole program analysis, A thread proc is passed to ``createThread`` or ``spawn`` and invoked
it is quite expensive and can be turned off with ``--threadanalysis:off`` to indirectly; so the ``thread`` pragma implies ``procvar``.
improve compile times.
A thread proc is passed to ``createThread`` 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:
- 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 Threadvar pragma
@ -5648,78 +5667,6 @@ initialized within the ``var`` section. (Every thread local variable needs to
be replicated at thread creation.) 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 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* in one thread cannot affect any other thread. However, an *unhandled*
exception in one thread terminates the whole *process*! exception in one thread terminates the whole *process*!
Taint mode Taint mode
========== ==========

View file

@ -311,8 +311,8 @@ proc newSharedState(options: TRstParseOptions,
result.subs = @[] result.subs = @[]
result.refs = @[] result.refs = @[]
result.options = options result.options = options
result.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler
result.findFile = if isNil(findFile): defaultFindFile else: findFile result.findFile = if not isNil(findFile): findFile else: defaultFindFile
proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,

View file

@ -7,6 +7,8 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
{.deadCodeElim:on.}
from posix import TSocketHandle from posix import TSocketHandle
const const

View file

@ -7,6 +7,8 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
{.deadCodeElim:on.}
# Get the platform-dependent flags. # Get the platform-dependent flags.
# Structure describing an inotify event. # Structure describing an inotify event.
type type

View file

@ -1,3 +1,5 @@
{.deadCodeElim:on.}
import posix import posix
const const
@ -22,4 +24,5 @@ const
# fn should be of type proc (a2: pointer): void {.cdecl.} # fn should be of type proc (a2: pointer): void {.cdecl.}
proc clone*(fn: pointer; child_stack: pointer; flags: cint; 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>".}

View file

@ -27,6 +27,8 @@
## resulting C code will just ``#include <XYZ.h>`` and *not* define the ## resulting C code will just ``#include <XYZ.h>`` and *not* define the
## symbols declared here. ## symbols declared here.
{.deadCodeElim:on.}
from times import TTime from times import TTime
const const

View file

@ -192,6 +192,9 @@ when defined(nimNewShared):
type type
`shared`* {.magic: "Shared".} `shared`* {.magic: "Shared".}
guarded* {.magic: "Guarded".} guarded* {.magic: "Guarded".}
else:
{.pragma: gcsafe.}
#{.pragma: gcsafe.}
const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \ const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \
## "fake variables" like 'var EBADF {.importc.}: cint'. ## "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. ## or `limit`:idx: a string's length.
when not defined(nimrodVM): 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. ## overwrites the contents of the memory at ``p`` with the value 0.
## Exactly ``size`` bytes will be overwritten. Like any procedure ## Exactly ``size`` bytes will be overwritten. Like any procedure
## dealing with raw memory this is *unsafe*. ## dealing with raw memory this is *unsafe*.
proc copyMem*(dest, source: pointer, size: int) {. 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 ## copies the contents from the memory at ``source`` to the memory
## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory
## regions may not overlap. Like any procedure dealing with raw ## regions may not overlap. Like any procedure dealing with raw
## memory this is *unsafe*. ## memory this is *unsafe*.
proc moveMem*(dest, source: pointer, size: int) {. 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 ## copies the contents from the memory at ``source`` to the memory
## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory
## regions may overlap, ``moveMem`` handles this case appropriately ## regions may overlap, ``moveMem`` handles this case appropriately
@ -1235,14 +1238,14 @@ when not defined(nimrodVM):
## *unsafe*. ## *unsafe*.
when hostOS != "standalone": 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 ## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or ## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is not initialized, so reading ## ``dealloc(block)``. The block is not initialized, so reading
## from it before writing to it is undefined behaviour! ## from it before writing to it is undefined behaviour!
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `allocShared` to allocate from a shared heap. ## 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`` ## allocates a new memory block with at least ``T.sizeof * size``
## bytes. The block has to be freed with ``resize(block, 0)`` or ## bytes. The block has to be freed with ``resize(block, 0)`` or
## ``free(block)``. The block is not initialized, so reading ## ``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! ## The allocated memory belongs to its allocating thread!
## Use `createSharedU` to allocate from a shared heap. ## Use `createSharedU` to allocate from a shared heap.
cast[ptr T](alloc(T.sizeof * size)) 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 ## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or ## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is initialized with all bytes ## ``dealloc(block)``. The block is initialized with all bytes
## containing zero, so it is somewhat safer than ``alloc``. ## containing zero, so it is somewhat safer than ``alloc``.
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `allocShared0` to allocate from a shared heap. ## 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`` ## allocates a new memory block with at least ``T.sizeof * size``
## bytes. The block has to be freed with ``resize(block, 0)`` or ## bytes. The block has to be freed with ``resize(block, 0)`` or
## ``free(block)``. The block is initialized with all bytes ## ``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! ## The allocated memory belongs to its allocating thread!
## Use `createShared` to allocate from a shared heap. ## Use `createShared` to allocate from a shared heap.
cast[ptr T](alloc0(T.sizeof * size)) 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 ## 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 ## memory block is returned. In either way the block has at least
## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** ## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
@ -1273,7 +1277,7 @@ when not defined(nimrodVM):
## be freed with ``dealloc``. ## be freed with ``dealloc``.
## The allocated memory belongs to its allocating thread! ## The allocated memory belongs to its allocating thread!
## Use `reallocShared` to reallocate from a shared heap. ## 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 ## 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 ## memory block is returned. In either way the block has at least
## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not
@ -1282,7 +1286,7 @@ when not defined(nimrodVM):
## its allocating thread! ## its allocating thread!
## Use `resizeShared` to reallocate from a shared heap. ## Use `resizeShared` to reallocate from a shared heap.
cast[ptr T](realloc(p, T.sizeof * newSize)) 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 ## frees the memory allocated with ``alloc``, ``alloc0`` or
## ``realloc``. This procedure is dangerous! If one forgets to ## ``realloc``. This procedure is dangerous! If one forgets to
## free the memory a leak occurs; if one tries to access freed ## 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. ## or other memory may be corrupted.
## The freed memory must belong to its allocating thread! ## The freed memory must belong to its allocating thread!
## Use `deallocShared` to deallocate from a shared heap. ## 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) 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 ## allocates a new memory block on the shared heap with at
## least ``size`` bytes. The block has to be freed with ## least ``size`` bytes. The block has to be freed with
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
## is not initialized, so reading from it before writing to it is ## is not initialized, so reading from it before writing to it is
## undefined behaviour! ## 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 ## allocates a new memory block on the shared heap with at
## least ``T.sizeof * size`` bytes. The block has to be freed with ## least ``T.sizeof * size`` bytes. The block has to be freed with
## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block ## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block
## is not initialized, so reading from it before writing to it is ## is not initialized, so reading from it before writing to it is
## undefined behaviour! ## undefined behaviour!
cast[ptr T](allocShared(T.sizeof * size)) 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 ## allocates a new memory block on the shared heap with at
## least ``size`` bytes. The block has to be freed with ## least ``size`` bytes. The block has to be freed with
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. ## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
@ -1318,7 +1323,8 @@ when not defined(nimrodVM):
## The block is initialized with all bytes ## The block is initialized with all bytes
## containing zero, so it is somewhat safer than ``createSharedU``. ## containing zero, so it is somewhat safer than ``createSharedU``.
cast[ptr T](allocShared0(T.sizeof * size)) 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** ## 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 ## 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** ## 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 ## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other
## cases the block has to be freed with ``freeShared``. ## cases the block has to be freed with ``freeShared``.
cast[ptr T](reallocShared(p, T.sizeof * newSize)) 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 ## frees the memory allocated with ``allocShared``, ``allocShared0`` or
## ``reallocShared``. This procedure is dangerous! If one forgets to ## ``reallocShared``. This procedure is dangerous! If one forgets to
## free the memory a leak occurs; if one tries to access freed ## free the memory a leak occurs; if one tries to access freed
## memory (or just freeing it twice!) a core dump may happen ## memory (or just freeing it twice!) a core dump may happen
## or other memory may be corrupted. ## 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 ## frees the memory allocated with ``createShared``, ``createSharedU`` or
## ``resizeShared``. This procedure is dangerous! If one forgets to ## ``resizeShared``. This procedure is dangerous! If one forgets to
## free the memory a leak occurs; if one tries to access freed ## free the memory a leak occurs; if one tries to access freed
@ -1898,7 +1904,7 @@ const nimrodStackTrace = compileOption("stacktrace")
# of the code # of the code
var 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. ## with this hook you can influence exception handling on a global level.
## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## If not nil, every 'raise' statement ends up calling this hook. Ordinary
## application code should never set this hook! You better know what you ## 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 ## exception is caught and does not propagate further through the call
## stack. ## 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 ## with this hook you can influence exception handling on a
## thread local level. ## thread local level.
## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## 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 ## do when setting this. If ``localRaiseHook`` returns false, the exception
## is caught and does not propagate further through the call stack. ## 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 ## set this variable to provide a procedure that should be called
## in case of an `out of memory`:idx: event. The standard handler ## in case of an `out of memory`:idx: event. The standard handler
## writes an error message and terminates the program. `outOfMemHook` can ## writes an error message and terminates the program. `outOfMemHook` can
@ -1965,7 +1971,7 @@ elif hostOS != "standalone":
inc(i) inc(i)
{.pop.} {.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 ## special built-in that takes a variable number of arguments. Each argument
## is converted to a string via ``$``, so it works for user-defined ## is converted to a string via ``$``, so it works for user-defined
## types that have an overloaded ``$`` operator. ## types that have an overloaded ``$`` operator.
@ -2119,14 +2125,15 @@ when not defined(JS): #and not defined(NimrodVM):
## `useStdoutAsStdmsg` compile-time switch. ## `useStdoutAsStdmsg` compile-time switch.
proc open*(f: var TFile, filename: string, 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`. ## Opens a file named `filename` with given `mode`.
## ##
## Default mode is readonly. Returns true iff the file could be opened. ## Default mode is readonly. Returns true iff the file could be opened.
## This throws no exception if the file could not be opened. ## This throws no exception if the file could not be opened.
proc open*(f: var TFile, filehandle: TFileHandle, 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`. ## Creates a ``TFile`` from a `filehandle` with given `mode`.
## ##
## Default mode is readonly. Returns true iff the file could be opened. ## 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) sysFatal(EIO, "cannot open: ", filename)
proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {. proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {.
tags: [].} tags: [], gcsafe.}
## reopens the file `f` with given `filename` and `mode`. This ## reopens the file `f` with given `filename` and `mode`. This
## is often used to redirect the `stdin`, `stdout` or `stderr` ## is often used to redirect the `stdin`, `stdout` or `stderr`
## file variables. ## file variables.
@ -2151,7 +2158,7 @@ when not defined(JS): #and not defined(NimrodVM):
proc close*(f: TFile) {.importc: "fclose", header: "<stdio.h>", tags: [].} proc close*(f: TFile) {.importc: "fclose", header: "<stdio.h>", tags: [].}
## Closes the file. ## Closes the file.
proc endOfFile*(f: TFile): bool {.tags: [].} proc endOfFile*(f: TFile): bool {.tags: [], gcsafe.}
## Returns true iff `f` is at the end. ## Returns true iff `f` is at the end.
proc readChar*(f: TFile): char {. proc readChar*(f: TFile): char {.
@ -2161,39 +2168,40 @@ when not defined(JS): #and not defined(NimrodVM):
importc: "fflush", header: "<stdio.h>", tags: [FWriteIO].} importc: "fflush", header: "<stdio.h>", tags: [FWriteIO].}
## Flushes `f`'s buffer. ## 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`. ## Reads all data from the stream `file`.
## ##
## Raises an IO exception in case of an error. It is an error if the ## 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. ## 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` ## Opens a file named `filename` for reading. Then calls `readAll`
## and closes the file afterwards. Returns the string. ## and closes the file afterwards. Returns the string.
## Raises an IO exception in case of an error. ## 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 ## Opens a file named `filename` for writing. Then writes the
## `content` completely to the file and closes the file afterwards. ## `content` completely to the file and closes the file afterwards.
## Raises an IO exception in case of an error. ## Raises an IO exception in case of an error.
proc write*(f: TFile, r: float32) {.tags: [FWriteIO].} proc write*(f: TFile, r: float32) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, i: int) {.tags: [FWriteIO].} proc write*(f: TFile, i: int) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO].} proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO].} proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, s: string) {.tags: [FWriteIO].} proc write*(f: TFile, s: string) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, b: bool) {.tags: [FWriteIO].} proc write*(f: TFile, b: bool) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, c: char) {.tags: [FWriteIO].} proc write*(f: TFile, c: char) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, c: cstring) {.tags: [FWriteIO].} proc write*(f: TFile, c: cstring) {.tags: [FWriteIO], gcsafe.}
proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO].} proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO], gcsafe.}
## Writes a value to the file `f`. May throw an IO exception. ## 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. ## 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 ## A line of text may be delimited by ``CR``, ``LF`` or
## ``CRLF``. The newline character(s) are not part of the returned string. ## ``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 ## reads a line of text from the file `f` into `line`. `line` must not be
## ``nil``! May throw an IO exception. ## ``nil``! May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## 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`` ## Returns ``false`` if the end of the file has been reached, ``true``
## otherwise. If ``false`` is returned `line` contains no new data. ## 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". ## writes the values `x` to `f` and then writes "\n".
## May throw an IO exception. ## 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`. ## retrieves the file size (in bytes) of `f`.
proc readBytes*(f: TFile, a: var openArray[int8], start, len: int): int {. 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 ## 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 ## 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. ## `len` (if not as many bytes are remaining), but not greater.
proc readChars*(f: TFile, a: var openArray[char], start, len: int): int {. 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 ## 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 ## 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. ## `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 ## 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 ## 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. ## `len` (if not as many bytes are remaining), but not greater.
proc writeBytes*(f: TFile, a: openArray[int8], start, len: int): int {. 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 ## 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 ## the number of actual written bytes, which may be less than `len` in case
## of an error. ## of an error.
proc writeChars*(f: TFile, a: openArray[char], start, len: int): int {. 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 ## 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 ## the number of actual written bytes, which may be less than `len` in case
## of an error. ## of an error.
proc writeBuffer*(f: TFile, buffer: pointer, len: int): int {. 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 ## 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 ## file `f`. Returns the number of actual written bytes, which may be less
## than `len` in case of an error. ## 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 ## sets the position of the file pointer that is used for read/write
## operations. The file's first byte has the index zero. ## 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 ## 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. ## 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) dealloc(a)
when not defined(NimrodVM): 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. ## 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. ## atomic decrement of `memLoc`. Returns the value after the operation.
include "system/atomics" include "system/atomics"

View file

@ -51,7 +51,7 @@ proc split(t: var PAvlNode) =
t.link[0] = temp t.link[0] = temp
inc t.level 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: if t == bottom:
t = allocAvlNode(a, key, upperBound) t = allocAvlNode(a, key, upperBound)
else: else:
@ -64,7 +64,7 @@ proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) =
skew(t) skew(t)
split(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 if t == bottom: return
a.last = t a.last = t
if x <% t.key: if x <% t.key:

View file

@ -9,7 +9,7 @@
# Headers for procs that the code generator depends on ("compilerprocs") # 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 type
TLibHandle = pointer # private type TLibHandle = pointer # private type
@ -21,5 +21,5 @@ proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr {.compilerproc.}
proc nimLoadLibraryError(path: string) {.compilerproc, noinline.} proc nimLoadLibraryError(path: string) {.compilerproc, noinline.}
proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline.} proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe.}

View file

@ -11,7 +11,7 @@
# use the heap (and nor exceptions) do not include the GC or memory allocator. # use the heap (and nor exceptions) do not include the GC or memory allocator.
var var
errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO].}) errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO], gcsafe.})
## Function that will be called ## Function that will be called
## instead of stdmsg.write when printing stacktrace. ## instead of stdmsg.write when printing stacktrace.
## Unstable API. ## Unstable API.
@ -32,10 +32,10 @@ proc showErrorMessage(data: cstring) =
else: else:
writeToStdErr(data) writeToStdErr(data)
proc chckIndx(i, a, b: int): int {.inline, compilerproc.} proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.}
proc chckRange(i, a, b: int): int {.inline, compilerproc.} proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.}
proc chckRangeF(x, a, b: float): float {.inline, compilerproc.} proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.}
proc chckNil(p: pointer) {.noinline, compilerproc.} proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.}
var var
framePtr {.rtlThreadVar.}: PFrame framePtr {.rtlThreadVar.}: PFrame
@ -322,5 +322,5 @@ when not defined(noSignalHandler):
proc setControlCHook(hook: proc () {.noconv.}) = proc setControlCHook(hook: proc () {.noconv.}) =
# ugly cast, but should work on all architectures: # 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)) c_signal(SIGINT, cast[TSignalHandler](hook))

View file

@ -51,7 +51,7 @@ type
waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack, waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack,
waCollectWhite, 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 # A ref type can have a finalizer that is called before the object's
# storage is freed. # storage is freed.
@ -152,11 +152,11 @@ template gcTrace(cell, state: expr): stmt {.immediate.} =
when traceGC: traceCell(cell, state) when traceGC: traceCell(cell, state)
# forward declarations: # forward declarations:
proc collectCT(gch: var TGcHeap) proc collectCT(gch: var TGcHeap) {.gcsafe.}
proc isOnStack*(p: pointer): bool {.noinline.} proc isOnStack*(p: pointer): bool {.noinline, gcsafe.}
proc forAllChildren(cell: PCell, op: TWalkOp) proc forAllChildren(cell: PCell, op: TWalkOp) {.gcsafe.}
proc doOperation(p: pointer, op: TWalkOp) proc doOperation(p: pointer, op: TWalkOp) {.gcsafe.}
proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) {.gcsafe.}
# we need the prototype here for debugging purposes # we need the prototype here for debugging purposes
when hasThreadSupport and hasSharedHeap: when hasThreadSupport and hasSharedHeap:
@ -294,7 +294,7 @@ proc initGC() =
when useMarkForDebug or useBackupGc: when useMarkForDebug or useBackupGc:
type type
TGlobalMarkerProc = proc () {.nimcall.} TGlobalMarkerProc = proc () {.nimcall, gcsafe.}
var var
globalMarkersLen: int globalMarkersLen: int
globalMarkers: array[0.. 7_000, TGlobalMarkerProc] globalMarkers: array[0.. 7_000, TGlobalMarkerProc]
@ -311,7 +311,7 @@ proc cellsetReset(s: var TCellSet) =
deinit(s) deinit(s)
init(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) var d = cast[TAddress](dest)
case n.kind case n.kind
of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) 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.} = proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
doOperation(d, TWalkOp(op)) doOperation(d, TWalkOp(op))
proc collectZCT(gch: var TGcHeap): bool proc collectZCT(gch: var TGcHeap): bool {.gcsafe.}
when useMarkForDebug or useBackupGc: when useMarkForDebug or useBackupGc:
proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl.} proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl,
gcsafe.}
proc collectRoots(gch: var TGcHeap) = proc collectRoots(gch: var TGcHeap) =
for s in elements(gch.cycleRoots): for s in elements(gch.cycleRoots):

View file

@ -85,7 +85,7 @@ type
base: ptr TNimType base: ptr TNimType
node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum
finalizer: pointer # the finalizer for the type 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 PNimType = ptr TNimType
# node.len may be the ``first`` element of a set # node.len may be the ``first`` element of a set

View file

@ -52,7 +52,7 @@ when defined(Windows):
proc closeHandle(hObject: THandle) {.stdcall, noSideEffect, proc closeHandle(hObject: THandle) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "CloseHandle".} dynlib: "kernel32", importc: "CloseHandle".}
proc waitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {. proc waitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".} stdcall, dynlib: "kernel32", importc: "WaitForSingleObject", noSideEffect.}
proc signalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect, proc signalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "SetEvent".} dynlib: "kernel32", importc: "SetEvent".}
@ -89,16 +89,16 @@ else:
proc releaseSys(L: var TSysLock) {.noSideEffect, proc releaseSys(L: var TSysLock) {.noSideEffect,
importc: "pthread_mutex_unlock", header: "<pthread.h>".} 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>".} importc: "pthread_mutex_destroy", header: "<pthread.h>".}
proc initSysCond(cond: var TSysCond, cond_attr: pointer = nil) {. 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) {. 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) {. 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>".} importc: "pthread_cond_destroy", header: "<pthread.h>".}

172
lib/system/sysspawn.nim Normal file
View file

@ -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()

View file

@ -522,7 +522,7 @@ proc inet_addr*(cp: cstring): int32 {.
stdcall, importc: "inet_addr", dynlib: ws2dll.} stdcall, importc: "inet_addr", dynlib: ws2dll.}
proc WSAFDIsSet(s: TSocketHandle, FDSet: var TFdSet): bool {. 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 = proc FD_ISSET*(Socket: TSocketHandle, FDSet: var TFdSet): cint =
result = if WSAFDIsSet(Socket, FDSet): 1'i32 else: 0'i32 result = if WSAFDIsSet(Socket, FDSet): 1'i32 else: 0'i32