New concurrency model: next steps
This commit is contained in:
parent
8e08ff559f
commit
3b69a8d27a
23 changed files with 460 additions and 200 deletions
|
|
@ -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,9 +1330,11 @@ proc propagateToOwner*(owner, elem: PType) =
|
||||||
if elem.isMetaType:
|
if elem.isMetaType:
|
||||||
owner.flags.incl tfHasMeta
|
owner.flags.incl tfHasMeta
|
||||||
|
|
||||||
if elem.kind in {tyString, tyRef, tySequence} or
|
if owner.kind != tyProc:
|
||||||
elem.kind == tyProc and elem.callConv == ccClosure:
|
if elem.kind in {tyString, tyRef, tySequence} or
|
||||||
owner.flags.incl tfHasGCedMem
|
elem.kind == tyProc and elem.callConv == ccClosure or
|
||||||
|
tfHasGCedMem in elem.flags:
|
||||||
|
owner.flags.incl tfHasGCedMem
|
||||||
|
|
||||||
proc rawAddSon*(father, son: PType) =
|
proc rawAddSon*(father, son: PType) =
|
||||||
if isNil(father.sons): father.sons = @[]
|
if isNil(father.sons): father.sons = @[]
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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}:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
104
doc/manual.txt
104
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.
|
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
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>".}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
114
lib/system.nim
114
lib/system.nim
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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.}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
172
lib/system/sysspawn.nim
Normal 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()
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue