bugfix: 'set' overloadable; further steps for multi threading support

This commit is contained in:
Araq 2011-07-08 01:29:15 +02:00
commit 99bcc233cd
34 changed files with 934 additions and 459 deletions

View file

@ -57,7 +57,7 @@ type
nkStrLit, # a string literal "" nkStrLit, # a string literal ""
nkRStrLit, # a raw string literal r"" nkRStrLit, # a raw string literal r""
nkTripleStrLit, # a triple string literal """ nkTripleStrLit, # a triple string literal """
nkMetaNode, # difficult to explan; represents itself nkMetaNode, # difficult to explain; represents itself
# (used for macros) # (used for macros)
nkNilLit, # the nil literal nkNilLit, # the nil literal
# end of atoms # end of atoms
@ -217,7 +217,7 @@ type
sfDiscriminant, # field is a discriminant in a record/object sfDiscriminant, # field is a discriminant in a record/object
sfDeprecated, # symbol is deprecated sfDeprecated, # symbol is deprecated
sfInClosure, # variable is accessed by a closure sfInClosure, # variable is accessed by a closure
sfTypeCheck, # wether macro parameters should be type checked sfThread, # proc will run as a thread
sfCompileTime, # proc can be evaluated at compile time sfCompileTime, # proc can be evaluated at compile time
sfThreadVar, # variable is a thread variable sfThreadVar, # variable is a thread variable
sfMerge, # proc can be merged with itself sfMerge, # proc can be merged with itself
@ -274,7 +274,8 @@ type
tfFinal, # is the object final? tfFinal, # is the object final?
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``
TTypeFlags* = set[TTypeFlag] TTypeFlags* = set[TTypeFlag]
@ -310,7 +311,7 @@ type
TMagic* = enum # symbols that require compiler magic: TMagic* = enum # symbols that require compiler magic:
mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs,
mEcho, mCreateThread, mShallowCopy, mEcho, mShallowCopy,
mUnaryLt, mSucc, mUnaryLt, mSucc,
mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray, mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray,
mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref, mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref,

View file

@ -1452,7 +1452,6 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet, of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet,
mInSet: mInSet:
genSetOp(p, e, d, op) genSetOp(p, e, d, op)
of mCreateThread: genCall(p, e, d)
of mNewString, mNewStringOfCap, mCopyStr, mCopyStrLast, mExit: of mNewString, mNewStringOfCap, mCopyStr, mCopyStrLast, mExit:
var opr = e.sons[0].sym var opr = e.sons[0].sym
if lfNoDecl notin opr.loc.flags: if lfNoDecl notin opr.loc.flags:

View file

@ -10,10 +10,13 @@
## Thread var support for crappy architectures that lack native support for ## Thread var support for crappy architectures that lack native support for
## thread local storage. ## thread local storage.
proc emulatedThreadVars(): bool {.inline.} =
result = optThreads in gGlobalOptions
# NOW: Use the work-around everywhere, because it should be faster anyway.
#platform.OS[targetOS].props.contains(ospLacksThreadVars)
proc AccessThreadLocalVar(p: BProc, s: PSym) = proc AccessThreadLocalVar(p: BProc, s: PSym) =
if optThreads in gGlobalOptions: if emulatedThreadVars() and not p.ThreadVarAccessed:
if platform.OS[targetOS].props.contains(ospLacksThreadVars):
if not p.ThreadVarAccessed:
p.ThreadVarAccessed = true p.ThreadVarAccessed = true
p.module.usesThreadVars = true p.module.usesThreadVars = true
appf(p.s[cpsLocals], "NimThreadVars* NimTV;$n") appf(p.s[cpsLocals], "NimThreadVars* NimTV;$n")
@ -24,10 +27,6 @@ var
nimtvDeps: seq[PType] = @[] nimtvDeps: seq[PType] = @[]
nimtvDeclared = initIntSet() nimtvDeclared = initIntSet()
proc emulatedThreadVars(): bool {.inline.} =
result = optThreads in gGlobalOptions and
platform.OS[targetOS].props.contains(ospLacksThreadVars)
proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) = proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
if emulatedThreadVars(): if emulatedThreadVars():
# we gather all thread locals var into a struct; we need to allocate # we gather all thread locals var into a struct; we need to allocate

View file

@ -94,7 +94,7 @@ type
warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel,
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
warnXisPassedToProcVar, warnDerefDeprecated, warnAnalysisLoophole, warnXisPassedToProcVar, warnDerefDeprecated, warnAnalysisLoophole,
warnDifferentHeaps, warnDifferentHeaps, warnWriteToForeignHeap,
warnUser, warnUser,
hintSuccess, hintSuccessX, hintSuccess, hintSuccessX,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
@ -328,7 +328,8 @@ const
warnXisPassedToProcVar: "\'$1\' is passed to a procvar; deprecated [XisPassedToProcVar]", warnXisPassedToProcVar: "\'$1\' is passed to a procvar; deprecated [XisPassedToProcVar]",
warnDerefDeprecated: "p^ is deprecated; use p[] instead [DerefDeprecated]", warnDerefDeprecated: "p^ is deprecated; use p[] instead [DerefDeprecated]",
warnAnalysisLoophole: "thread analysis incomplete due to unkown call '$1' [AnalysisLoophole]", warnAnalysisLoophole: "thread analysis incomplete due to unkown call '$1' [AnalysisLoophole]",
warnDifferentHeaps: "possible inconsistency of thread local heaps", warnDifferentHeaps: "possible inconsistency of thread local heaps [DifferentHeaps]",
warnWriteToForeignHeap: "write to foreign heap [WriteToForeignHeap]",
warnUser: "$1 [User]", warnUser: "$1 [User]",
hintSuccess: "operation successful [Success]", hintSuccess: "operation successful [Success]",
hintSuccessX: "operation successful ($1 lines compiled; $2 sec total) [SuccessX]", hintSuccessX: "operation successful ($1 lines compiled; $2 sec total) [SuccessX]",
@ -346,12 +347,12 @@ const
hintUser: "$1 [User]"] hintUser: "$1 [User]"]
const const
WarningsToStr*: array[0..17, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr*: array[0..18, string] = ["CannotOpenFile", "OctalEscape",
"XIsNeverRead", "XmightNotBeenInit", "CannotWriteMO2", "CannotReadMO2", "XIsNeverRead", "XmightNotBeenInit", "CannotWriteMO2", "CannotReadMO2",
"Deprecated", "SmallLshouldNotBeUsed", "UnknownMagic", "Deprecated", "SmallLshouldNotBeUsed", "UnknownMagic",
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
"CommentXIgnored", "XisPassedToProcVar", "DerefDeprecated", "CommentXIgnored", "XisPassedToProcVar", "DerefDeprecated",
"AnalysisLoophole", "DifferentHeaps", "User"] "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "User"]
HintsToStr*: array[0..13, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr*: array[0..13, string] = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",

View file

@ -22,11 +22,11 @@ const
procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader, wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader,
wCompilerProc, wPure, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wCompilerProc, wPure, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
wBorrow, wExtern, wImportCompilerProc} wBorrow, wExtern, wImportCompilerProc, wThread}
converterPragmas* = procPragmas converterPragmas* = procPragmas
methodPragmas* = procPragmas methodPragmas* = procPragmas
macroPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, macroPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
wMagic, wNosideEffect, wCompilerProc, wDeprecated, wTypeCheck, wExtern} wMagic, wNosideEffect, wCompilerProc, wDeprecated, wExtern}
iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideEffect, wSideEffect, iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideEffect, wSideEffect,
wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern} wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks,
@ -37,7 +37,7 @@ const
wInfChecks, wNanChecks, wPragma, wEmit, wUnroll, wLinearScanEnd} wInfChecks, wNanChecks, wPragma, wEmit, wUnroll, wLinearScanEnd}
lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader, wPure, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader, wPure,
wDeprecated, wExtern} wDeprecated, wExtern, wThread}
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}
fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern} fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern}
@ -45,7 +45,8 @@ const
wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern} wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern}
constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl,
wExtern} wExtern}
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideEffect} procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideEffect,
wThread}
proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
proc pragmaAsm*(c: PContext, n: PNode): char proc pragmaAsm*(c: PContext, n: PNode): char
@ -125,7 +126,6 @@ proc processMagic(c: PContext, n: PNode, s: PSym) =
s.magic = m s.magic = m
break break
if s.magic == mNone: Message(n.info, warnUnknownMagic, v) if s.magic == mNone: Message(n.info, warnUnknownMagic, v)
if s.magic != mCreateThread:
# magics don't need an implementation, so we # magics don't need an implementation, so we
# treat them as imported, instead of modifing a lot of working code: # treat them as imported, instead of modifing a lot of working code:
incl(s.flags, sfImportc) incl(s.flags, sfImportc)
@ -500,9 +500,11 @@ proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
noVal(it) noVal(it)
if sym.typ == nil: invalidPragma(it) if sym.typ == nil: invalidPragma(it)
incl(sym.typ.flags, tfShallow) incl(sym.typ.flags, tfShallow)
of wTypeCheck: of wThread:
noVal(it) noVal(it)
incl(sym.flags, sfTypeCheck) incl(sym.flags, sfThread)
incl(sym.flags, sfProcVar)
if sym.typ != nil: incl(sym.typ.flags, tfThread)
of wHint: Message(it.info, hintUser, expectStrLit(c, it)) of wHint: Message(it.info, hintUser, expectStrLit(c, it))
of wWarning: Message(it.info, warnUser, expectStrLit(c, it)) of wWarning: Message(it.info, warnUser, expectStrLit(c, it))
of wError: LocalError(it.info, errUser, expectStrLit(c, it)) of wError: LocalError(it.info, errUser, expectStrLit(c, it))

View file

@ -203,8 +203,9 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
result = ast.emptyNode result = ast.emptyNode
proc checkThreads(c: PContext) = proc checkThreads(c: PContext) =
if not needsGlobalAnalysis(): return
for i in 0 .. c.threadEntries.len-1: for i in 0 .. c.threadEntries.len-1:
semthreads.AnalyseThread(c.threadEntries[i]) semthreads.AnalyseThreadProc(c.threadEntries[i])
proc myClose(context: PPassContext, n: PNode): PNode = proc myClose(context: PPassContext, n: PNode): PNode =
var c = PContext(context) var c = PContext(context)

View file

@ -41,7 +41,7 @@ type
generics*: PNode # a list of the things to compile; list of generics*: PNode # a list of the things to compile; list of
# nkExprEqExpr nodes which contain the # nkExprEqExpr nodes which contain the
# generic symbol and the instantiated symbol # generic symbol and the instantiated symbol
threadEntries*: PNode # list of thread entries to check threadEntries*: TSymSeq # list of thread entries to check
lastGenericIdx*: int # used for the generics stack lastGenericIdx*: int # used for the generics stack
tab*: TSymTab # each module has its own symbol table tab*: TSymTab # each module has its own symbol table
AmbiguousSymbols*: TIntSet # ids of all ambiguous symbols (cannot AmbiguousSymbols*: TIntSet # ids of all ambiguous symbols (cannot
@ -125,7 +125,7 @@ proc newContext(module: PSym, nimfile: string): PContext =
append(result.optionStack, newOptionEntry()) append(result.optionStack, newOptionEntry())
result.module = module result.module = module
result.generics = newNode(nkStmtList) result.generics = newNode(nkStmtList)
result.threadEntries = newNode(nkStmtList) result.threadEntries = @[]
result.converters = @[] result.converters = @[]
result.filename = nimfile result.filename = nimfile
result.includedFiles = initIntSet() result.includedFiles = initIntSet()

View file

@ -41,8 +41,8 @@ proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
case s.kind case s.kind
of skProc, skMethod, skIterator, skConverter: of skProc, skMethod, skIterator, skConverter:
if not (sfProcVar in s.flags) and (s.typ.callConv == ccDefault) and if sfProcVar notin s.flags and s.typ.callConv == ccDefault and
(getModule(s).id != c.module.id): getModule(s).id != c.module.id:
LocalError(n.info, errXCannotBePassedToProcVar, s.name.s) LocalError(n.info, errXCannotBePassedToProcVar, s.name.s)
result = symChoice(c, n, s) result = symChoice(c, n, s)
of skConst: of skConst:
@ -103,8 +103,8 @@ proc checkConvertible(info: TLineInfo, castDest, src: PType) =
d = base(d) d = base(d)
s = base(s) s = base(s)
if d == nil: if d == nil:
GlobalError(info, errGenerated, `%`(msgKindToString(errIllegalConvFromXtoY), [ GlobalError(info, errGenerated, msgKindToString(errIllegalConvFromXtoY) % [
typeToString(src), typeToString(castDest)])) src.typeToString, castDest.typeToString])
elif d.Kind == tyObject and s.Kind == tyObject: elif d.Kind == tyObject and s.Kind == tyObject:
checkConversionBetweenObjects(info, d, s) checkConversionBetweenObjects(info, d, s)
elif (skipTypes(castDest, abstractVarRange).Kind in IntegralTypes) and elif (skipTypes(castDest, abstractVarRange).Kind in IntegralTypes) and
@ -195,13 +195,11 @@ proc semIs(c: PContext, n: PNode): PNode =
if sonsLen(n) == 3: if sonsLen(n) == 3:
n.sons[1] = semExprWithType(c, n.sons[1], {efAllowType}) n.sons[1] = semExprWithType(c, n.sons[1], {efAllowType})
n.sons[2] = semExprWithType(c, n.sons[2], {efAllowType}) n.sons[2] = semExprWithType(c, n.sons[2], {efAllowType})
var a = n.sons[1].typ var a = skipTypes(n.sons[1].typ, abstractPtrs)
var b = n.sons[2].typ var b = skipTypes(n.sons[2].typ, abstractPtrs)
# a and b can be nil in case of an error: if b.kind != tyObject or a.kind != tyObject:
if a != nil and b != nil:
if (b.kind != tyObject) or (a.kind != tyObject):
GlobalError(n.info, errIsExpectsObjectTypes) GlobalError(n.info, errIsExpectsObjectTypes)
while (b != nil) and (b.id != a.id): b = b.sons[0] while b != nil and b.id != a.id: b = b.sons[0]
if b == nil: if b == nil:
GlobalError(n.info, errXcanNeverBeOfThisSubtype, typeToString(a)) GlobalError(n.info, errXcanNeverBeOfThisSubtype, typeToString(a))
n.typ = getSysType(tyBool) n.typ = getSysType(tyBool)
@ -338,13 +336,13 @@ proc isAssignable(n: PNode): TAssignableResult =
result = arNone result = arNone
case n.kind case n.kind
of nkSym: of nkSym:
if (n.sym.kind in {skVar, skTemp}): result = arLValue if n.sym.kind in {skVar, skTemp}: result = arLValue
of nkDotExpr: of nkDotExpr:
if skipTypes(n.sons[0].typ, abstractInst).kind in {tyVar, tyPtr, tyRef}: if skipTypes(n.sons[0].typ, abstractInst).kind in {tyVar, tyPtr, tyRef}:
result = arLValue result = arLValue
else: else:
result = isAssignable(n.sons[0]) result = isAssignable(n.sons[0])
if (result == arLValue) and (sfDiscriminant in n.sons[1].sym.flags): if result == arLValue and sfDiscriminant in n.sons[1].sym.flags:
result = arDiscriminant result = arDiscriminant
of nkBracketExpr: of nkBracketExpr:
if skipTypes(n.sons[0].typ, abstractInst).kind in {tyVar, tyPtr, tyRef}: if skipTypes(n.sons[0].typ, abstractInst).kind in {tyVar, tyPtr, tyRef}:
@ -400,7 +398,7 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
mAppendSeqElem, mNewSeq, mReset, mShallowCopy} mAppendSeqElem, mNewSeq, mReset, mShallowCopy}
checkMinSonsLen(n, 1) checkMinSonsLen(n, 1)
var t = n.sons[0].typ var t = n.sons[0].typ
if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic in FakeVarParams): if n.sons[0].kind == nkSym and n.sons[0].sym.magic in FakeVarParams:
# BUGFIX: check for L-Value still needs to be done for the arguments! # BUGFIX: check for L-Value still needs to be done for the arguments!
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
if i < sonsLen(t) and t.sons[i] != nil and if i < sonsLen(t) and t.sons[i] != nil and
@ -409,8 +407,8 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
LocalError(n.sons[i].info, errVarForOutParamNeeded) LocalError(n.sons[i].info, errVarForOutParamNeeded)
return return
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
if (i < sonsLen(t)) and if i < sonsLen(t) and
(skipTypes(t.sons[i], abstractInst).kind == tyVar): skipTypes(t.sons[i], abstractInst).kind == tyVar:
n.sons[i] = analyseIfAddressTaken(c, n.sons[i]) n.sons[i] = analyseIfAddressTaken(c, n.sons[i])
proc semDirectCallAnalyseEffects(c: PContext, n: PNode, proc semDirectCallAnalyseEffects(c: PContext, n: PNode,
@ -466,7 +464,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
result = m.call result = m.call
# we assume that a procedure that calls something indirectly # we assume that a procedure that calls something indirectly
# has side-effects: # has side-effects:
if not (tfNoSideEffect in t.flags): incl(c.p.owner.flags, sfSideEffect) if tfNoSideEffect notin t.flags: incl(c.p.owner.flags, sfSideEffect)
else: else:
result = overloadedCallOpr(c, n) result = overloadedCallOpr(c, n)
# Now that nkSym does not imply an iteration over the proc/iterator space, # Now that nkSym does not imply an iteration over the proc/iterator space,
@ -845,10 +843,6 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
of mSizeOf: result = semSizeof(c, setMs(n, s)) of mSizeOf: result = semSizeof(c, setMs(n, s))
of mIs: result = semIs(c, setMs(n, s)) of mIs: result = semIs(c, setMs(n, s))
of mEcho: result = semEcho(c, setMs(n, s)) of mEcho: result = semEcho(c, setMs(n, s))
of mCreateThread:
result = semDirectOp(c, n, flags)
if semthreads.needsGlobalAnalysis():
c.threadEntries.add(result)
of mShallowCopy: of mShallowCopy:
if sonsLen(n) == 3: if sonsLen(n) == 3:
# XXX ugh this is really a hack: shallowCopy() can be overloaded only # XXX ugh this is really a hack: shallowCopy() can be overloaded only
@ -1103,8 +1097,13 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
of skTemplate: result = semTemplateExpr(c, n, s) of skTemplate: result = semTemplateExpr(c, n, s)
of skType: of skType:
if n.kind != nkCall: GlobalError(n.info, errXisNotCallable, s.name.s) if n.kind != nkCall: GlobalError(n.info, errXisNotCallable, s.name.s)
# XXX does this check make any sense? # XXX think about this more (``set`` procs)
if n.len == 2:
result = semConv(c, n, s) result = semConv(c, n, s)
elif Contains(c.AmbiguousSymbols, s.id):
LocalError(n.info, errUseQualifier, s.name.s)
elif s.magic == mNone: result = semDirectOp(c, n, flags)
else: result = semMagic(c, n, s, flags)
of skProc, skMethod, skConverter, skIterator: of skProc, skMethod, skConverter, skIterator:
if s.magic == mNone: result = semDirectOp(c, n, flags) if s.magic == mNone: result = semDirectOp(c, n, flags)
else: result = semMagic(c, n, s, flags) else: result = semMagic(c, n, s, flags)

View file

@ -549,10 +549,12 @@ proc sideEffectsCheck(c: PContext, s: PSym) =
if {sfNoSideEffect, sfSideEffect} * s.flags == if {sfNoSideEffect, sfSideEffect} * s.flags ==
{sfNoSideEffect, sfSideEffect}: {sfNoSideEffect, sfSideEffect}:
LocalError(s.info, errXhasSideEffects, s.name.s) LocalError(s.info, errXhasSideEffects, s.name.s)
elif sfThread in s.flags and semthreads.needsGlobalAnalysis():
c.threadEntries.add(s)
proc addResult(c: PContext, t: PType, info: TLineInfo) = proc addResult(c: PContext, t: PType, info: TLineInfo) =
if t != nil: if t != nil:
var s = newSym(skVar, getIdent("result"), getCurrOwner()) var s = newSym(skVar, getIdent"result", getCurrOwner())
s.info = info s.info = info
s.typ = t s.typ = t
incl(s.flags, sfResult) incl(s.flags, sfResult)
@ -566,7 +568,7 @@ proc addResultNode(c: PContext, n: PNode) =
proc semLambda(c: PContext, n: PNode): PNode = proc semLambda(c: PContext, n: PNode): PNode =
result = n result = n
checkSonsLen(n, codePos + 1) checkSonsLen(n, codePos + 1)
var s = newSym(skProc, getIdent(":anonymous"), getCurrOwner()) var s = newSym(skProc, getIdent":anonymous", getCurrOwner())
s.info = n.info s.info = n.info
s.ast = n s.ast = n
n.sons[namePos] = newSymNode(s) n.sons[namePos] = newSymNode(s)
@ -594,6 +596,7 @@ proc semLambda(c: PContext, n: PNode): PNode =
popProcCon(c) popProcCon(c)
else: else:
LocalError(n.info, errImplOfXexpected, s.name.s) LocalError(n.info, errImplOfXexpected, s.name.s)
sideEffectsCheck(c, s)
closeScope(c.tab) # close scope for parameters closeScope(c.tab) # close scope for parameters
popOwner() popOwner()
result.typ = s.typ result.typ = s.typ

View file

@ -18,7 +18,7 @@
## The only crucial operation that can violate the heap invariants is the ## The only crucial operation that can violate the heap invariants is the
## write access. The analysis needs to distinguish between 'unknown', 'mine', ## write access. The analysis needs to distinguish between 'unknown', 'mine',
## and 'theirs' memory and pointers. Assignments 'whatever <- unknown' are ## and 'theirs' memory and pointers. Assignments 'whatever <- unknown' are
## invalid, and so are 'theirs <- mine' but not 'mine <- theirs'. Since ## invalid, and so are 'theirs <- whatever' but not 'mine <- theirs'. Since
## strings and sequences are heap allocated they are affected too: ## strings and sequences are heap allocated they are affected too:
## ##
## .. code-block:: nimrod ## .. code-block:: nimrod
@ -30,8 +30,9 @@
## If the type system would distinguish between 'ref' and '!ref' and threads ## If the type system would distinguish between 'ref' and '!ref' and threads
## could not have '!ref' as input parameters the analysis could simply need to ## could not have '!ref' as input parameters the analysis could simply need to
## reject any write access to a global variable which contains GC'ed data. ## reject any write access to a global variable which contains GC'ed data.
## However, '!ref' is not implemented yet and this scheme would be too ## Thanks to the write barrier of the GC, this is exactly what needs to be
## restrictive anyway. ## done! Every write access to a global that contains GC'ed data needs to
## be prevented! Unfortunately '!ref' is not implemented yet...
## ##
## The assignment target is essential for the algorithm: only ## The assignment target is essential for the algorithm: only
## write access to heap locations and global variables are critical and need ## write access to heap locations and global variables are critical and need
@ -42,7 +43,8 @@
## ##
## var x = globalVar # 'x' points to 'theirs' ## var x = globalVar # 'x' points to 'theirs'
## while true: ## while true:
## globalVar = x # OK: 'theirs <- theirs' ## globalVar = x # NOT OK: 'theirs <- theirs' invalid due to
## # write barrier!
## x = "new string" # ugh: 'x is toUnknown'! ## x = "new string" # ugh: 'x is toUnknown'!
## ##
## --> Solution: toUnknown is never allowed anywhere! ## --> Solution: toUnknown is never allowed anywhere!
@ -106,10 +108,12 @@ proc analyseSym(c: PProcCtx, n: PNode): TThreadOwner =
if result != toUndefined: return if result != toUndefined: return
case v.kind case v.kind
of skVar: of skVar:
if sfGlobal in v.flags:
result = if sfThreadVar in v.flags: toMine else: toTheirs
else:
result = toNil result = toNil
if sfGlobal in v.flags:
if sfThreadVar in v.flags:
result = toMine
elif containsTyRef(v.typ):
result = toTheirs
of skTemp, skForVar: result = toNil of skTemp, skForVar: result = toNil
of skConst: result = toMine of skConst: result = toMine
of skParam: of skParam:
@ -136,7 +140,8 @@ proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) =
of toNil: of toNil:
c.mapping[v.id] = owner # fine, toNil can be overwritten c.mapping[v.id] = owner # fine, toNil can be overwritten
of toVoid, toUndefined: InternalError(n.info, "writeAccess") of toVoid, toUndefined: InternalError(n.info, "writeAccess")
of toTheirs, toMine: of toTheirs: Message(n.info, warnWriteToForeignHeap)
of toMine:
if lastOwner != owner and owner != toNil: if lastOwner != owner and owner != toNil:
Message(n.info, warnDifferentHeaps) Message(n.info, warnDifferentHeaps)
else: else:
@ -145,7 +150,8 @@ proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) =
case lastOwner case lastOwner
of toNil: nil # fine, toNil can be overwritten of toNil: nil # fine, toNil can be overwritten
of toVoid, toUndefined: InternalError(n.info, "writeAccess") of toVoid, toUndefined: InternalError(n.info, "writeAccess")
of toTheirs, toMine: of toTheirs: Message(n.info, warnWriteToForeignHeap)
of toMine:
if lastOwner != owner and owner != toNil: if lastOwner != owner and owner != toNil:
Message(n.info, warnDifferentHeaps) Message(n.info, warnDifferentHeaps)
@ -171,7 +177,8 @@ proc analyseCall(c: PProcCtx, n: PNode): TThreadOwner =
newCtx.mapping[formal.id] = call.args[i-1] newCtx.mapping[formal.id] = call.args[i-1]
pushInfoContext(n.info) pushInfoContext(n.info)
result = analyse(newCtx, prc.ast.sons[codePos]) result = analyse(newCtx, prc.ast.sons[codePos])
if prc.ast.sons[codePos].kind == nkEmpty and sfNoSideEffect notin prc.flags: if prc.ast.sons[codePos].kind == nkEmpty and
{sfNoSideEffect, sfThread} * prc.flags == {}:
Message(n.info, warnAnalysisLoophole, renderTree(n)) Message(n.info, warnAnalysisLoophole, renderTree(n))
if prc.typ.sons[0] != nil: if prc.typ.sons[0] != nil:
if prc.ast.len > resultPos: if prc.ast.len > resultPos:
@ -228,7 +235,7 @@ proc analyseArgs(c: PProcCtx, n: PNode, start = 1) =
proc analyseOp(c: PProcCtx, n: PNode): TThreadOwner = proc analyseOp(c: PProcCtx, n: PNode): TThreadOwner =
if n[0].kind != nkSym or n[0].sym.kind != skProc: if n[0].kind != nkSym or n[0].sym.kind != skProc:
if tfNoSideEffect notin n[0].typ.flags: if {tfNoSideEffect, tfThread} * n[0].typ.flags == {}:
Message(n.info, warnAnalysisLoophole, renderTree(n)) Message(n.info, warnAnalysisLoophole, renderTree(n))
result = toNil result = toNil
else: else:
@ -335,22 +342,26 @@ proc analyse(c: PProcCtx, n: PNode): TThreadOwner =
result = toVoid result = toVoid
else: InternalError(n.info, "analysis not implemented for: " & $n.kind) else: InternalError(n.info, "analysis not implemented for: " & $n.kind)
proc analyseThreadCreationCall(n: PNode) = proc analyseThreadProc*(prc: PSym) =
var c = newProcCtx(prc)
var formals = skipTypes(prc.typ, abstractInst).n
for i in 1 .. formals.len-1:
var formal = formals.sons[i].sym
c.mapping[formal.id] = toTheirs # thread receives foreign data!
discard analyse(c, prc.ast.sons[codePos])
when false:
proc analyseThreadCreationCall(n: PNode) =
# thread proc is second param of ``createThread``: # thread proc is second param of ``createThread``:
if n[2].kind != nkSym or n[2].sym.kind != skProc: if n[2].kind != nkSym or n[2].sym.kind != skProc:
Message(n.info, warnAnalysisLoophole, renderTree(n)) Message(n.info, warnAnalysisLoophole, renderTree(n))
return return
var prc = n[2].sym analyseProc(n[2].sym)
var c = newProcCtx(prc)
var formal = skipTypes(prc.typ, abstractInst).n.sons[1].sym proc AnalyseThread*(threadCreation: PNode) =
c.mapping[formal.id] = toTheirs # thread receives foreign data! analyseThreadCreationCall(threadCreation)
discard analyse(c, prc.ast.sons[codePos])
proc needsGlobalAnalysis*: bool = proc needsGlobalAnalysis*: bool =
result = gGlobalOptions * {optThreads, optThreadAnalysis} == result = gGlobalOptions * {optThreads, optThreadAnalysis} ==
{optThreads, optThreadAnalysis} {optThreads, optThreadAnalysis}
proc AnalyseThread*(threadCreation: PNode) =
if needsGlobalAnalysis():
analyseThreadCreationCall(threadCreation)

View file

@ -220,6 +220,9 @@ proc procTypeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
result = isNone result = isNone
if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags: if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
result = isNone result = isNone
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
# noSideEffect implies ``tfThread``!
result = isNone
else: nil else: nil
proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation = proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =

View file

@ -331,6 +331,13 @@ proc containsGarbageCollectedRef(typ: PType): bool =
# that are garbage-collected) # that are garbage-collected)
result = searchTypeFor(typ, isGBCRef) result = searchTypeFor(typ, isGBCRef)
proc isTyRef(t: PType): bool =
result = t.kind == tyRef
proc containsTyRef*(typ: PType): bool =
# returns true if typ contains a 'ref'
result = searchTypeFor(typ, isTyRef)
proc isHiddenPointer(t: PType): bool = proc isHiddenPointer(t: PType): bool =
result = t.kind in {tyString, tySequence} result = t.kind in {tyString, tySequence}
@ -484,6 +491,9 @@ proc TypeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
if tfNoSideEffect in t.flags: if tfNoSideEffect in t.flags:
addSep(prag) addSep(prag)
add(prag, "noSideEffect") add(prag, "noSideEffect")
if tfThread in t.flags:
addSep(prag)
add(prag, "thread")
if len(prag) != 0: add(result, "{." & prag & ".}") if len(prag) != 0: add(result, "{." & prag & ".}")
else: else:
result = typeToStr[t.kind] result = typeToStr[t.kind]

View file

@ -33,7 +33,7 @@ type
wWithout, wXor, wYield, wWithout, wXor, wYield,
wColon, wColonColon, wEquals, wDot, wDotDot, wHat, wStar, wMinus, wColon, wColonColon, wEquals, wDot, wDotDot, wHat, wStar, wMinus,
wMagic, wTypeCheck, wFinal, wProfiler, wObjChecks, wMagic, wThread, wFinal, wProfiler, wObjChecks,
wImportCompilerProc, wImportCompilerProc,
wImportc, wExportc, wExtern, wImportc, wExportc, wExtern,
wAlign, wNodecl, wPure, wVolatile, wRegister, wSideeffect, wHeader, wAlign, wNodecl, wPure, wVolatile, wRegister, wSideeffect, wHeader,
@ -80,7 +80,7 @@ const
"yield", "yield",
":", "::", "=", ".", "..", "^", "*", "-", ":", "::", "=", ".", "..", "^", "*", "-",
"magic", "typecheck", "final", "profiler", "objchecks", "magic", "thread", "final", "profiler", "objchecks",
"importcompilerproc", "importc", "exportc", "extern", "importcompilerproc", "importc", "exportc", "extern",
"align", "nodecl", "pure", "volatile", "register", "sideeffect", "align", "nodecl", "pure", "volatile", "register", "sideeffect",
"header", "nosideeffect", "noreturn", "merge", "lib", "dynlib", "header", "nosideeffect", "noreturn", "merge", "lib", "dynlib",

View file

@ -52,6 +52,8 @@ Collections and algorithms
* `lists <lists.html>`_ * `lists <lists.html>`_
Nimrod linked list support. Contains singly and doubly linked lists and Nimrod linked list support. Contains singly and doubly linked lists and
circular lists ("rings"). circular lists ("rings").
* `queues <queues.html>`_
Implementation of a queue. The underlying implementation uses a ``seq``.
* `intsets <intsets.html>`_ * `intsets <intsets.html>`_
Efficient implementation of a set of ints as a sparse bit set. Efficient implementation of a set of ints as a sparse bit set.

View file

@ -45,9 +45,9 @@ manually. An alternative is to create a symbolic link in ``/usr/bin``::
[sudo] ln -s $your_install_dir/bin/nimrod /usr/bin/nimrod [sudo] ln -s $your_install_dir/bin/nimrod /usr/bin/nimrod
From version 0.7.10 onwards ``install.sh`` and ``deinstall.sh`` scripts are There are also ``install.sh`` and ``deinstall.sh`` scripts for distributing
provided for distributing the files over the UNIX hierarchy. However, the files over the UNIX hierarchy. However, updating your Nimrod installation
updating your Nimrod installation is more cumbersome then. is more cumbersome then.
Installation on the Macintosh Installation on the Macintosh

View file

@ -0,0 +1,89 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Implementation of a queue. The underlying implementation uses a ``seq``.
import math
type
TQueue* {.pure, final.}[T] = object ## a queue
data: seq[T]
rd, wr, count, mask: int
proc initQueue*[T](initialSize=4): TQueue[T] =
## creates a new queue. `initialSize` needs to be a power of 2.
assert IsPowerOfTwo(initialSize)
result.mask = initialSize-1
newSeq(result.data, initialSize)
proc len*[T](q: TQueue[T]): int =
## returns the number of elements of `q`.
result = q.count
iterator items*[T](q: TQueue[T]): T =
## yields every element of `q`.
var i = q.rd
var c = q.count
while c > 0:
dec c
yield q.data[i]
i = (i + 1) and q.mask
proc add*[T](q: var TQueue[T], item: T) =
## adds an `item` to the end of the queue `q`.
var cap = q.mask+1
if q.count >= cap:
var n: seq[T]
newSeq(n, cap*2)
var i = 0
for x in items(q):
shallowCopy(n[i], x)
inc i
shallowCopy(q.data, n)
q.mask = cap*2 - 1
q.wr = q.count
q.rd = 0
inc q.count
q.data[q.wr] = item
q.wr = (q.wr + 1) and q.mask
proc enqueue*[T](q: var TQueue[T], item: T) =
## alias for the ``add`` operation.
add(q, item)
proc dequeue*[T](q: var TQueue[T]): T =
## removes and returns the first element of the queue `q`.
assert q.count > 0
dec q.count
result = q.data[q.rd]
q.rd = (q.rd + 1) and q.mask
proc `$`*[T](q: TQueue[T]): string =
## turns a queue into its string representation.
result = "["
for x in items(q):
if result.len > 1: result.add(", ")
result.add($x)
result.add("]")
when isMainModule:
var q = initQueue[int]()
q.add(123)
q.add(9)
q.add(4)
var first = q.dequeue
q.add(56)
q.add(6)
var second = q.dequeue
q.add(789)
assert first == 123
assert second == 9
assert($q == "[4, 56, 6, 789]")

View file

@ -9,6 +9,25 @@
## This module contains procs for serialization and deseralization of ## This module contains procs for serialization and deseralization of
## arbitrary Nimrod data structures. The serialization format uses JSON. ## arbitrary Nimrod data structures. The serialization format uses JSON.
##
## **Restriction**: For objects their type is **not** serialized. This means
## essentially that it does not work if the object has some other runtime
## type than its compiletime type:
##
## .. code-block:: nimrod
##
## type
## TA = object
## TB = object of TA
## f: int
##
## var
## a: ref TA
## b: ref TB
##
## new(b)
## a = b
## echo($$a[]) # produces "{}", not "{f: 0}"
import streams, typeinfo, json, intsets, tables import streams, typeinfo, json, intsets, tables
@ -286,3 +305,15 @@ when isMainModule:
echo($$test7) echo($$test7)
testit(test7) testit(test7)
type
TA = object
TB = object of TA
f: int
var
a: ref TA
b: ref TB
new(b)
a = b
echo($$a[]) # produces "{}", not "{f: 0}"

View file

@ -77,11 +77,14 @@ proc startProcess*(command: string,
## If ``env == nil`` the environment is inherited of ## If ``env == nil`` the environment is inherited of
## the parent process. `options` are additional flags that may be passed ## the parent process. `options` are additional flags that may be passed
## to `startProcess`. See the documentation of ``TProcessOption`` for the ## to `startProcess`. See the documentation of ``TProcessOption`` for the
## meaning of these flags. ## meaning of these flags. You need to `close` the process when done.
## ##
## Return value: The newly created process object. Nil is never returned, ## Return value: The newly created process object. Nil is never returned,
## but ``EOS`` is raised in case of an error. ## but ``EOS`` is raised in case of an error.
proc close*(p: PProcess) {.rtl, extern: "nosp$1".}
## When the process has finished executing, cleanup related handles
proc suspend*(p: PProcess) {.rtl, extern: "nosp$1".} proc suspend*(p: PProcess) {.rtl, extern: "nosp$1".}
## Suspends the process `p`. ## Suspends the process `p`.
@ -179,6 +182,7 @@ proc execProcesses*(cmds: openArray[string],
err.add("\n") err.add("\n")
echo(err) echo(err)
result = max(waitForExit(q[r]), result) result = max(waitForExit(q[r]), result)
if q[r] != nil: close(q[r])
q[r] = startProcessAux(cmds[i], options=options) q[r] = startProcessAux(cmds[i], options=options)
r = (r + 1) mod n r = (r + 1) mod n
else: else:
@ -189,15 +193,18 @@ proc execProcesses*(cmds: openArray[string],
if not running(q[r]): if not running(q[r]):
#echo(outputStream(q[r]).readLine()) #echo(outputStream(q[r]).readLine())
result = max(waitForExit(q[r]), result) result = max(waitForExit(q[r]), result)
if q[r] != nil: close(q[r])
q[r] = startProcessAux(cmds[i], options=options) q[r] = startProcessAux(cmds[i], options=options)
inc(i) inc(i)
if i > high(cmds): break if i > high(cmds): break
for i in 0..m-1: for i in 0..m-1:
if q[i] != nil: close(q[i])
result = max(waitForExit(q[i]), result) result = max(waitForExit(q[i]), result)
else: else:
for i in 0..high(cmds): for i in 0..high(cmds):
var p = startProcessAux(cmds[i], options=options) var p = startProcessAux(cmds[i], options=options)
result = max(waitForExit(p), result) result = max(waitForExit(p), result)
close(p)
proc select*(readfds: var seq[PProcess], timeout = 500): int proc select*(readfds: var seq[PProcess], timeout = 500): int
## `select` with a sensible Nimrod interface. `timeout` is in miliseconds. ## `select` with a sensible Nimrod interface. `timeout` is in miliseconds.
@ -215,6 +222,8 @@ when not defined(useNimRtl):
while running(p) or not outp.atEnd(outp): while running(p) or not outp.atEnd(outp):
result.add(outp.readLine()) result.add(outp.readLine())
result.add("\n") result.add("\n")
outp.close(outp)
close(p)
when false: when false:
proc deallocCStringArray(a: cstringArray) = proc deallocCStringArray(a: cstringArray) =
@ -356,6 +365,12 @@ when defined(Windows) and not defined(useNimRtl):
result.FProcessHandle = procInfo.hProcess result.FProcessHandle = procInfo.hProcess
result.id = procInfo.dwProcessID result.id = procInfo.dwProcessID
proc close(p: PProcess) =
discard CloseHandle(p.inputHandle)
discard CloseHandle(p.outputHandle)
discard CloseHandle(p.errorHandle)
discard CloseHandle(p.FProcessHandle)
proc suspend(p: PProcess) = proc suspend(p: PProcess) =
discard SuspendThread(p.FProcessHandle) discard SuspendThread(p.FProcessHandle)
@ -523,6 +538,11 @@ elif not defined(useNimRtl):
discard close(p_stdin[readIdx]) discard close(p_stdin[readIdx])
discard close(p_stdout[writeIdx]) discard close(p_stdout[writeIdx])
proc close(p: PProcess) =
discard close(p.inputHandle)
discard close(p.outputHandle)
discard close(p.errorHandle)
proc suspend(p: PProcess) = proc suspend(p: PProcess) =
discard kill(p.id, SIGSTOP) discard kill(p.id, SIGSTOP)

View file

@ -785,6 +785,10 @@ when hasThreadSupport and not hasSharedHeap:
else: else:
{.pragma: rtlThreadVar.} {.pragma: rtlThreadVar.}
template sysAssert(cond: expr) =
# change this to activate system asserts
nil
include "system/inclrtl" include "system/inclrtl"
when not defined(ecmascript) and not defined(nimrodVm): when not defined(ecmascript) and not defined(nimrodVm):
@ -1251,7 +1255,7 @@ proc each*[T](data: var openArray[T], op: proc (x: var T)) =
for i in 0..data.len-1: op(data[i]) for i in 0..data.len-1: op(data[i])
iterator fields*[T: tuple](x: T): expr {.magic: "Fields", noSideEffect.} iterator fields*[T: tuple](x: T): expr {.magic: "Fields", noSideEffect.}
## iterates over every field of `x`. Warning: This is really transforms ## iterates over every field of `x`. Warning: This really transforms
## the 'for' and unrolls the loop. The current implementation also has a bug ## the 'for' and unrolls the loop. The current implementation also has a bug
## that affects symbol binding in the loop body. ## that affects symbol binding in the loop body.
iterator fields*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {. iterator fields*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {.
@ -1261,13 +1265,13 @@ iterator fields*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {.
## The current implementation also has a bug that affects symbol binding ## The current implementation also has a bug that affects symbol binding
## in the loop body. ## in the loop body.
iterator fieldPairs*[T: tuple](x: T): expr {.magic: "FieldPairs", noSideEffect.} iterator fieldPairs*[T: tuple](x: T): expr {.magic: "FieldPairs", noSideEffect.}
## iterates over every field of `x`. Warning: This is really transforms ## iterates over every field of `x`. Warning: This really transforms
## the 'for' and unrolls the loop. The current implementation also has a bug ## the 'for' and unrolls the loop. The current implementation also has a bug
## that affects symbol binding in the loop body. ## that affects symbol binding in the loop body.
iterator fieldPairs*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {. iterator fieldPairs*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {.
magic: "FieldPairs", noSideEffect.} magic: "FieldPairs", noSideEffect.}
## iterates over every field of `x` and `y`. ## iterates over every field of `x` and `y`.
## Warning: This is really transforms the 'for' and unrolls the loop. ## Warning: This really transforms the 'for' and unrolls the loop.
## The current implementation also has a bug that affects symbol binding ## The current implementation also has a bug that affects symbol binding
## in the loop body. ## in the loop body.
@ -1703,10 +1707,10 @@ when not defined(EcmaScript) and not defined(NimrodVM):
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
proc atomicInc*(memLoc: var int, x: int): int {.inline.} proc atomicInc*(memLoc: var int, x: int = 1): int {.inline.}
## 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): int {.inline.} proc atomicDec*(memLoc: var int, x: int = 1): int {.inline.}
## 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"
@ -1719,6 +1723,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
context: C_JmpBuf context: C_JmpBuf
when hasThreadSupport: when hasThreadSupport:
include "system/syslocks"
include "system/threads" include "system/threads"
else: else:
initStackBottom() initStackBottom()
@ -1739,14 +1744,14 @@ when not defined(EcmaScript) and not defined(NimrodVM):
proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl.} proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl.}
proc getDiscriminant(aa: Pointer, n: ptr TNimNode): int = proc getDiscriminant(aa: Pointer, n: ptr TNimNode): int =
assert(n.kind == nkCase) sysAssert(n.kind == nkCase)
var d: int var d: int
var a = cast[TAddress](aa) var a = cast[TAddress](aa)
case n.typ.size case n.typ.size
of 1: d = ze(cast[ptr int8](a +% n.offset)[]) of 1: d = ze(cast[ptr int8](a +% n.offset)[])
of 2: d = ze(cast[ptr int16](a +% n.offset)[]) of 2: d = ze(cast[ptr int16](a +% n.offset)[])
of 4: d = int(cast[ptr int32](a +% n.offset)[]) of 4: d = int(cast[ptr int32](a +% n.offset)[])
else: assert(false) else: sysAssert(false)
return d return d
proc selectBranch(aa: Pointer, n: ptr TNimNode): ptr TNimNode = proc selectBranch(aa: Pointer, n: ptr TNimNode): ptr TNimNode =
@ -1764,6 +1769,8 @@ when not defined(EcmaScript) and not defined(NimrodVM):
{.pop.} {.pop.}
include "system/sysio" include "system/sysio"
when hasThreadSupport:
include "system/inboxes"
iterator lines*(filename: string): string = iterator lines*(filename: string): string =
## Iterate over any line in the file named `filename`. ## Iterate over any line in the file named `filename`.

View file

@ -128,12 +128,12 @@ template bigChunkOverhead(): expr = sizeof(TBigChunk)-sizeof(TAlignType)
proc roundup(x, v: int): int {.inline.} = proc roundup(x, v: int): int {.inline.} =
result = (x + (v-1)) and not (v-1) result = (x + (v-1)) and not (v-1)
assert(result >= x) sysAssert(result >= x)
#return ((-x) and (v-1)) +% x #return ((-x) and (v-1)) +% x
assert(roundup(14, PageSize) == PageSize) sysAssert(roundup(14, PageSize) == PageSize)
assert(roundup(15, 8) == 16) sysAssert(roundup(15, 8) == 16)
assert(roundup(65, 8) == 72) sysAssert(roundup(65, 8) == 72)
# ------------- chunk table --------------------------------------------------- # ------------- chunk table ---------------------------------------------------
# We use a PtrSet of chunk starts and a table[Page, chunksize] for chunk # We use a PtrSet of chunk starts and a table[Page, chunksize] for chunk
@ -149,7 +149,7 @@ type
acc: int # accumulator acc: int # accumulator
next: PLLChunk # next low-level chunk; only needed for dealloc next: PLLChunk # next low-level chunk; only needed for dealloc
TAllocator {.final, pure.} = object TMemRegion {.final, pure.} = object
llmem: PLLChunk llmem: PLLChunk
currMem, maxMem, freeMem: int # memory sizes (allocated from OS) currMem, maxMem, freeMem: int # memory sizes (allocated from OS)
lastSize: int # needed for the case that OS gives us pages linearly lastSize: int # needed for the case that OS gives us pages linearly
@ -157,27 +157,27 @@ type
freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access
chunkStarts: TIntSet chunkStarts: TIntSet
proc incCurrMem(a: var TAllocator, bytes: int) {.inline.} = proc incCurrMem(a: var TMemRegion, bytes: int) {.inline.} =
inc(a.currMem, bytes) inc(a.currMem, bytes)
proc decCurrMem(a: var TAllocator, bytes: int) {.inline.} = proc decCurrMem(a: var TMemRegion, bytes: int) {.inline.} =
a.maxMem = max(a.maxMem, a.currMem) a.maxMem = max(a.maxMem, a.currMem)
dec(a.currMem, bytes) dec(a.currMem, bytes)
proc getMaxMem(a: var TAllocator): int = proc getMaxMem(a: var TMemRegion): int =
# Since we update maxPagesCount only when freeing pages, # Since we update maxPagesCount only when freeing pages,
# maxPagesCount may not be up to date. Thus we use the # maxPagesCount may not be up to date. Thus we use the
# maximum of these both values here: # maximum of these both values here:
return max(a.currMem, a.maxMem) return max(a.currMem, a.maxMem)
proc llAlloc(a: var TAllocator, size: int): pointer = proc llAlloc(a: var TMemRegion, size: int): pointer =
# *low-level* alloc for the memory managers data structures. Deallocation # *low-level* alloc for the memory managers data structures. Deallocation
# is done at he end of the allocator's life time. # is done at he end of the allocator's life time.
if a.llmem == nil or size > a.llmem.size: if a.llmem == nil or size > a.llmem.size:
# the requested size is ``roundup(size+sizeof(TLLChunk), PageSize)``, but # the requested size is ``roundup(size+sizeof(TLLChunk), PageSize)``, but
# since we know ``size`` is a (small) constant, we know the requested size # since we know ``size`` is a (small) constant, we know the requested size
# is one page: # is one page:
assert roundup(size+sizeof(TLLChunk), PageSize) == PageSize sysAssert roundup(size+sizeof(TLLChunk), PageSize) == PageSize
var old = a.llmem # can be nil and is correct with nil var old = a.llmem # can be nil and is correct with nil
a.llmem = cast[PLLChunk](osAllocPages(PageSize)) a.llmem = cast[PLLChunk](osAllocPages(PageSize))
incCurrMem(a, PageSize) incCurrMem(a, PageSize)
@ -189,7 +189,7 @@ proc llAlloc(a: var TAllocator, size: int): pointer =
inc(a.llmem.acc, size) inc(a.llmem.acc, size)
zeroMem(result, size) zeroMem(result, size)
proc llDeallocAll(a: var TAllocator) = proc llDeallocAll(a: var TMemRegion) =
var it = a.llmem var it = a.llmem
while it != nil: while it != nil:
# we know each block in the list has the size of 1 page: # we know each block in the list has the size of 1 page:
@ -204,7 +204,7 @@ proc IntSetGet(t: TIntSet, key: int): PTrunk =
it = it.next it = it.next
result = nil result = nil
proc IntSetPut(a: var TAllocator, t: var TIntSet, key: int): PTrunk = proc IntSetPut(a: var TMemRegion, t: var TIntSet, key: int): PTrunk =
result = IntSetGet(t, key) result = IntSetGet(t, key)
if result == nil: if result == nil:
result = cast[PTrunk](llAlloc(a, sizeof(result[]))) result = cast[PTrunk](llAlloc(a, sizeof(result[])))
@ -220,7 +220,7 @@ proc Contains(s: TIntSet, key: int): bool =
else: else:
result = false result = false
proc Incl(a: var TAllocator, s: var TIntSet, key: int) = proc Incl(a: var TMemRegion, s: var TIntSet, key: int) =
var t = IntSetPut(a, s, key shr TrunkShift) var t = IntSetPut(a, s, key shr TrunkShift)
var u = key and TrunkMask var u = key and TrunkMask
t.bits[u shr IntShift] = t.bits[u shr IntShift] or (1 shl (u and IntMask)) t.bits[u shr IntShift] = t.bits[u shr IntShift] or (1 shl (u and IntMask))
@ -259,13 +259,13 @@ proc pageIndex(p: pointer): int {.inline.} =
proc pageAddr(p: pointer): PChunk {.inline.} = proc pageAddr(p: pointer): PChunk {.inline.} =
result = cast[PChunk](cast[TAddress](p) and not PageMask) result = cast[PChunk](cast[TAddress](p) and not PageMask)
#assert(Contains(allocator.chunkStarts, pageIndex(result))) #sysAssert(Contains(allocator.chunkStarts, pageIndex(result)))
proc requestOsChunks(a: var TAllocator, size: int): PBigChunk = proc requestOsChunks(a: var TMemRegion, size: int): PBigChunk =
incCurrMem(a, size) incCurrMem(a, size)
inc(a.freeMem, size) inc(a.freeMem, size)
result = cast[PBigChunk](osAllocPages(size)) result = cast[PBigChunk](osAllocPages(size))
assert((cast[TAddress](result) and PageMask) == 0) sysAssert((cast[TAddress](result) and PageMask) == 0)
#zeroMem(result, size) #zeroMem(result, size)
result.next = nil result.next = nil
result.prev = nil result.prev = nil
@ -273,7 +273,7 @@ proc requestOsChunks(a: var TAllocator, size: int): PBigChunk =
result.size = size result.size = size
# update next.prevSize: # update next.prevSize:
var nxt = cast[TAddress](result) +% size var nxt = cast[TAddress](result) +% size
assert((nxt and PageMask) == 0) sysAssert((nxt and PageMask) == 0)
var next = cast[PChunk](nxt) var next = cast[PChunk](nxt)
if pageIndex(next) in a.chunkStarts: if pageIndex(next) in a.chunkStarts:
#echo("Next already allocated!") #echo("Next already allocated!")
@ -281,7 +281,7 @@ proc requestOsChunks(a: var TAllocator, size: int): PBigChunk =
# set result.prevSize: # set result.prevSize:
var lastSize = if a.lastSize != 0: a.lastSize else: PageSize var lastSize = if a.lastSize != 0: a.lastSize else: PageSize
var prv = cast[TAddress](result) -% lastSize var prv = cast[TAddress](result) -% lastSize
assert((nxt and PageMask) == 0) sysAssert((nxt and PageMask) == 0)
var prev = cast[PChunk](prv) var prev = cast[PChunk](prv)
if pageIndex(prev) in a.chunkStarts and prev.size == lastSize: if pageIndex(prev) in a.chunkStarts and prev.size == lastSize:
#echo("Prev already allocated!") #echo("Prev already allocated!")
@ -290,11 +290,11 @@ proc requestOsChunks(a: var TAllocator, size: int): PBigChunk =
result.prevSize = 0 # unknown result.prevSize = 0 # unknown
a.lastSize = size # for next request a.lastSize = size # for next request
proc freeOsChunks(a: var TAllocator, p: pointer, size: int) = proc freeOsChunks(a: var TMemRegion, p: pointer, size: int) =
# update next.prevSize: # update next.prevSize:
var c = cast[PChunk](p) var c = cast[PChunk](p)
var nxt = cast[TAddress](p) +% c.size var nxt = cast[TAddress](p) +% c.size
assert((nxt and PageMask) == 0) sysAssert((nxt and PageMask) == 0)
var next = cast[PChunk](nxt) var next = cast[PChunk](nxt)
if pageIndex(next) in a.chunkStarts: if pageIndex(next) in a.chunkStarts:
next.prevSize = 0 # XXX used next.prevSize = 0 # XXX used
@ -304,7 +304,7 @@ proc freeOsChunks(a: var TAllocator, p: pointer, size: int) =
dec(a.freeMem, size) dec(a.freeMem, size)
#c_fprintf(c_stdout, "[Alloc] back to OS: %ld\n", size) #c_fprintf(c_stdout, "[Alloc] back to OS: %ld\n", size)
proc isAccessible(a: TAllocator, p: pointer): bool {.inline.} = proc isAccessible(a: TMemRegion, p: pointer): bool {.inline.} =
result = Contains(a.chunkStarts, pageIndex(p)) result = Contains(a.chunkStarts, pageIndex(p))
proc contains[T](list, x: T): bool = proc contains[T](list, x: T): bool =
@ -313,7 +313,7 @@ proc contains[T](list, x: T): bool =
if it == x: return true if it == x: return true
it = it.next it = it.next
proc writeFreeList(a: TAllocator) = proc writeFreeList(a: TMemRegion) =
var it = a.freeChunksList var it = a.freeChunksList
c_fprintf(c_stdout, "freeChunksList: %p\n", it) c_fprintf(c_stdout, "freeChunksList: %p\n", it)
while it != nil: while it != nil:
@ -322,23 +322,23 @@ proc writeFreeList(a: TAllocator) =
it = it.next it = it.next
proc ListAdd[T](head: var T, c: T) {.inline.} = proc ListAdd[T](head: var T, c: T) {.inline.} =
assert(c notin head) sysAssert(c notin head)
assert c.prev == nil sysAssert c.prev == nil
assert c.next == nil sysAssert c.next == nil
c.next = head c.next = head
if head != nil: if head != nil:
assert head.prev == nil sysAssert head.prev == nil
head.prev = c head.prev = c
head = c head = c
proc ListRemove[T](head: var T, c: T) {.inline.} = proc ListRemove[T](head: var T, c: T) {.inline.} =
assert(c in head) sysAssert(c in head)
if c == head: if c == head:
head = c.next head = c.next
assert c.prev == nil sysAssert c.prev == nil
if head != nil: head.prev = nil if head != nil: head.prev = nil
else: else:
assert c.prev != nil sysAssert c.prev != nil
c.prev.next = c.next c.prev.next = c.next
if c.next != nil: c.next.prev = c.prev if c.next != nil: c.next.prev = c.prev
c.next = nil c.next = nil
@ -350,22 +350,22 @@ proc isSmallChunk(c: PChunk): bool {.inline.} =
proc chunkUnused(c: PChunk): bool {.inline.} = proc chunkUnused(c: PChunk): bool {.inline.} =
result = not c.used result = not c.used
proc updatePrevSize(a: var TAllocator, c: PBigChunk, proc updatePrevSize(a: var TMemRegion, c: PBigChunk,
prevSize: int) {.inline.} = prevSize: int) {.inline.} =
var ri = cast[PChunk](cast[TAddress](c) +% c.size) var ri = cast[PChunk](cast[TAddress](c) +% c.size)
assert((cast[TAddress](ri) and PageMask) == 0) sysAssert((cast[TAddress](ri) and PageMask) == 0)
if isAccessible(a, ri): if isAccessible(a, ri):
ri.prevSize = prevSize ri.prevSize = prevSize
proc freeBigChunk(a: var TAllocator, c: PBigChunk) = proc freeBigChunk(a: var TMemRegion, c: PBigChunk) =
var c = c var c = c
assert(c.size >= PageSize) sysAssert(c.size >= PageSize)
inc(a.freeMem, c.size) inc(a.freeMem, c.size)
when coalescRight: when coalescRight:
var ri = cast[PChunk](cast[TAddress](c) +% c.size) var ri = cast[PChunk](cast[TAddress](c) +% c.size)
assert((cast[TAddress](ri) and PageMask) == 0) sysAssert((cast[TAddress](ri) and PageMask) == 0)
if isAccessible(a, ri) and chunkUnused(ri): if isAccessible(a, ri) and chunkUnused(ri):
assert(not isSmallChunk(ri)) sysAssert(not isSmallChunk(ri))
if not isSmallChunk(ri): if not isSmallChunk(ri):
ListRemove(a.freeChunksList, cast[PBigChunk](ri)) ListRemove(a.freeChunksList, cast[PBigChunk](ri))
inc(c.size, ri.size) inc(c.size, ri.size)
@ -373,9 +373,9 @@ proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
when coalescLeft: when coalescLeft:
if c.prevSize != 0: if c.prevSize != 0:
var le = cast[PChunk](cast[TAddress](c) -% c.prevSize) var le = cast[PChunk](cast[TAddress](c) -% c.prevSize)
assert((cast[TAddress](le) and PageMask) == 0) sysAssert((cast[TAddress](le) and PageMask) == 0)
if isAccessible(a, le) and chunkUnused(le): if isAccessible(a, le) and chunkUnused(le):
assert(not isSmallChunk(le)) sysAssert(not isSmallChunk(le))
if not isSmallChunk(le): if not isSmallChunk(le):
ListRemove(a.freeChunksList, cast[PBigChunk](le)) ListRemove(a.freeChunksList, cast[PBigChunk](le))
inc(le.size, c.size) inc(le.size, c.size)
@ -390,9 +390,9 @@ proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
else: else:
freeOsChunks(a, c, c.size) freeOsChunks(a, c, c.size)
proc splitChunk(a: var TAllocator, c: PBigChunk, size: int) = proc splitChunk(a: var TMemRegion, c: PBigChunk, size: int) =
var rest = cast[PBigChunk](cast[TAddress](c) +% size) var rest = cast[PBigChunk](cast[TAddress](c) +% size)
assert(rest notin a.freeChunksList) sysAssert(rest notin a.freeChunksList)
rest.size = c.size - size rest.size = c.size - size
rest.used = false rest.used = false
rest.next = nil rest.next = nil
@ -403,14 +403,14 @@ proc splitChunk(a: var TAllocator, c: PBigChunk, size: int) =
incl(a, a.chunkStarts, pageIndex(rest)) incl(a, a.chunkStarts, pageIndex(rest))
ListAdd(a.freeChunksList, rest) ListAdd(a.freeChunksList, rest)
proc getBigChunk(a: var TAllocator, size: int): PBigChunk = proc getBigChunk(a: var TMemRegion, size: int): PBigChunk =
# use first fit for now: # use first fit for now:
assert((size and PageMask) == 0) sysAssert((size and PageMask) == 0)
assert(size > 0) sysAssert(size > 0)
result = a.freeChunksList result = a.freeChunksList
block search: block search:
while result != nil: while result != nil:
assert chunkUnused(result) sysAssert chunkUnused(result)
if result.size == size: if result.size == size:
ListRemove(a.freeChunksList, result) ListRemove(a.freeChunksList, result)
break search break search
@ -419,7 +419,7 @@ proc getBigChunk(a: var TAllocator, size: int): PBigChunk =
splitChunk(a, result, size) splitChunk(a, result, size)
break search break search
result = result.next result = result.next
assert result != a.freeChunksList sysAssert result != a.freeChunksList
if size < InitialMemoryRequest: if size < InitialMemoryRequest:
result = requestOsChunks(a, InitialMemoryRequest) result = requestOsChunks(a, InitialMemoryRequest)
splitChunk(a, result, size) splitChunk(a, result, size)
@ -430,10 +430,10 @@ proc getBigChunk(a: var TAllocator, size: int): PBigChunk =
incl(a, a.chunkStarts, pageIndex(result)) incl(a, a.chunkStarts, pageIndex(result))
dec(a.freeMem, size) dec(a.freeMem, size)
proc getSmallChunk(a: var TAllocator): PSmallChunk = proc getSmallChunk(a: var TMemRegion): PSmallChunk =
var res = getBigChunk(a, PageSize) var res = getBigChunk(a, PageSize)
assert res.prev == nil sysAssert res.prev == nil
assert res.next == nil sysAssert res.next == nil
result = cast[PSmallChunk](res) result = cast[PSmallChunk](res)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -442,9 +442,13 @@ proc getCellSize(p: pointer): int {.inline.} =
var c = pageAddr(p) var c = pageAddr(p)
result = c.size result = c.size
proc rawAlloc(a: var TAllocator, requestedSize: int): pointer = proc memSize(a: TMemRegion, p: pointer): int {.inline.} =
assert(roundup(65, 8) == 72) var c = pageAddr(p)
assert requestedSize >= sizeof(TFreeCell) result = c.size
proc rawAlloc(a: var TMemRegion, requestedSize: int): pointer =
sysAssert(roundup(65, 8) == 72)
sysAssert requestedSize >= sizeof(TFreeCell)
var size = roundup(requestedSize, MemAlign) var size = roundup(requestedSize, MemAlign)
#c_fprintf(c_stdout, "alloc; size: %ld; %ld\n", requestedSize, size) #c_fprintf(c_stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
if size <= SmallChunkSize-smallChunkOverhead(): if size <= SmallChunkSize-smallChunkOverhead():
@ -454,7 +458,7 @@ proc rawAlloc(a: var TAllocator, requestedSize: int): pointer =
if c == nil: if c == nil:
c = getSmallChunk(a) c = getSmallChunk(a)
c.freeList = nil c.freeList = nil
assert c.size == PageSize sysAssert c.size == PageSize
c.size = size c.size = size
c.acc = size c.acc = size
c.free = SmallChunkSize - smallChunkOverhead() - size c.free = SmallChunkSize - smallChunkOverhead() - size
@ -462,36 +466,40 @@ proc rawAlloc(a: var TAllocator, requestedSize: int): pointer =
c.prev = nil c.prev = nil
ListAdd(a.freeSmallChunks[s], c) ListAdd(a.freeSmallChunks[s], c)
result = addr(c.data) result = addr(c.data)
assert((cast[TAddress](result) and (MemAlign-1)) == 0) sysAssert((cast[TAddress](result) and (MemAlign-1)) == 0)
else: else:
assert c.next != c sysAssert c.next != c
#if c.size != size: #if c.size != size:
# c_fprintf(c_stdout, "csize: %lld; size %lld\n", c.size, size) # c_fprintf(c_stdout, "csize: %lld; size %lld\n", c.size, size)
assert c.size == size sysAssert c.size == size
if c.freeList == nil: if c.freeList == nil:
assert(c.acc + smallChunkOverhead() + size <= SmallChunkSize) sysAssert(c.acc + smallChunkOverhead() + size <= SmallChunkSize)
result = cast[pointer](cast[TAddress](addr(c.data)) +% c.acc) result = cast[pointer](cast[TAddress](addr(c.data)) +% c.acc)
inc(c.acc, size) inc(c.acc, size)
else: else:
result = c.freeList result = c.freeList
assert(c.freeList.zeroField == 0) sysAssert(c.freeList.zeroField == 0)
c.freeList = c.freeList.next c.freeList = c.freeList.next
dec(c.free, size) dec(c.free, size)
assert((cast[TAddress](result) and (MemAlign-1)) == 0) sysAssert((cast[TAddress](result) and (MemAlign-1)) == 0)
if c.free < size: if c.free < size:
ListRemove(a.freeSmallChunks[s], c) ListRemove(a.freeSmallChunks[s], c)
else: else:
size = roundup(requestedSize+bigChunkOverhead(), PageSize) size = roundup(requestedSize+bigChunkOverhead(), PageSize)
# allocate a large block # allocate a large block
var c = getBigChunk(a, size) var c = getBigChunk(a, size)
assert c.prev == nil sysAssert c.prev == nil
assert c.next == nil sysAssert c.next == nil
assert c.size == size sysAssert c.size == size
result = addr(c.data) result = addr(c.data)
assert((cast[TAddress](result) and (MemAlign-1)) == 0) sysAssert((cast[TAddress](result) and (MemAlign-1)) == 0)
assert(isAccessible(a, result)) sysAssert(isAccessible(a, result))
proc rawDealloc(a: var TAllocator, p: pointer) = proc rawAlloc0(a: var TMemRegion, requestedSize: int): pointer =
result = rawAlloc(a, requestedSize)
zeroMem(result, requestedSize)
proc rawDealloc(a: var TMemRegion, p: pointer) =
var c = pageAddr(p) var c = pageAddr(p)
if isSmallChunk(c): if isSmallChunk(c):
# `p` is within a small chunk: # `p` is within a small chunk:
@ -499,7 +507,7 @@ proc rawDealloc(a: var TAllocator, p: pointer) =
var s = c.size var s = c.size
var f = cast[ptr TFreeCell](p) var f = cast[ptr TFreeCell](p)
#echo("setting to nil: ", $cast[TAddress](addr(f.zeroField))) #echo("setting to nil: ", $cast[TAddress](addr(f.zeroField)))
assert(f.zeroField != 0) sysAssert(f.zeroField != 0)
f.zeroField = 0 f.zeroField = 0
f.next = c.freeList f.next = c.freeList
c.freeList = f c.freeList = f
@ -509,7 +517,7 @@ proc rawDealloc(a: var TAllocator, p: pointer) =
s -% sizeof(TFreeCell)) s -% sizeof(TFreeCell))
# check if it is not in the freeSmallChunks[s] list: # check if it is not in the freeSmallChunks[s] list:
if c.free < s: if c.free < s:
assert c notin a.freeSmallChunks[s div memAlign] sysAssert c notin a.freeSmallChunks[s div memAlign]
# add it to the freeSmallChunks[s] array: # add it to the freeSmallChunks[s] array:
ListAdd(a.freeSmallChunks[s div memAlign], c) ListAdd(a.freeSmallChunks[s div memAlign], c)
inc(c.free, s) inc(c.free, s)
@ -525,7 +533,7 @@ proc rawDealloc(a: var TAllocator, p: pointer) =
# free big chunk # free big chunk
freeBigChunk(a, cast[PBigChunk](c)) freeBigChunk(a, cast[PBigChunk](c))
proc isAllocatedPtr(a: TAllocator, p: pointer): bool = proc isAllocatedPtr(a: TMemRegion, p: pointer): bool =
if isAccessible(a, p): if isAccessible(a, p):
var c = pageAddr(p) var c = pageAddr(p)
if not chunkUnused(c): if not chunkUnused(c):
@ -539,9 +547,8 @@ proc isAllocatedPtr(a: TAllocator, p: pointer): bool =
var c = cast[PBigChunk](c) var c = cast[PBigChunk](c)
result = p == addr(c.data) and cast[ptr TFreeCell](p).zeroField >% 1 result = p == addr(c.data) and cast[ptr TFreeCell](p).zeroField >% 1
proc deallocOsPages(a: var TAllocator) = proc deallocOsPages(a: var TMemRegion) =
# we free every 'ordinarily' allocated page by iterating over the page # we free every 'ordinarily' allocated page by iterating over the page bits:
# bits:
for p in elements(a.chunkStarts): for p in elements(a.chunkStarts):
var page = cast[PChunk](p shl pageShift) var page = cast[PChunk](p shl pageShift)
var size = if page.size < PageSize: PageSize else: page.size var size = if page.size < PageSize: PageSize else: page.size
@ -549,30 +556,31 @@ proc deallocOsPages(a: var TAllocator) =
# And then we free the pages that are in use for the page bits: # And then we free the pages that are in use for the page bits:
llDeallocAll(a) llDeallocAll(a)
var proc getFreeMem(a: TMemRegion): int {.inline.} = result = a.freeMem
allocator {.rtlThreadVar.}: TAllocator proc getTotalMem(a: TMemRegion): int {.inline.} = result = a.currMem
proc getOccupiedMem(a: TMemRegion): int {.inline.} =
result = a.currMem - a.freeMem
proc deallocOsPages = deallocOsPages(allocator) # ---------------------- thread memory region -------------------------------
# ---------------------- interface to programs ------------------------------- template InstantiateForRegion(allocator: expr) =
proc deallocOsPages = deallocOsPages(allocator)
when not defined(useNimRtl): proc unlockedAlloc(size: int): pointer =
proc unlockedAlloc(size: int): pointer {.inline.} =
result = rawAlloc(allocator, size+sizeof(TFreeCell)) result = rawAlloc(allocator, size+sizeof(TFreeCell))
cast[ptr TFreeCell](result).zeroField = 1 # mark it as used cast[ptr TFreeCell](result).zeroField = 1 # mark it as used
assert(not isAllocatedPtr(allocator, result)) sysAssert(not isAllocatedPtr(allocator, result))
result = cast[pointer](cast[TAddress](result) +% sizeof(TFreeCell)) result = cast[pointer](cast[TAddress](result) +% sizeof(TFreeCell))
proc unlockedAlloc0(size: int): pointer {.inline.} = proc unlockedAlloc0(size: int): pointer =
result = unlockedAlloc(size) result = unlockedAlloc(size)
zeroMem(result, size) zeroMem(result, size)
proc unlockedDealloc(p: pointer) {.inline.} = proc unlockedDealloc(p: pointer) =
var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell)) var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell))
assert(cast[ptr TFreeCell](x).zeroField == 1) sysAssert(cast[ptr TFreeCell](x).zeroField == 1)
rawDealloc(allocator, x) rawDealloc(allocator, x)
assert(not isAllocatedPtr(allocator, x)) sysAssert(not isAllocatedPtr(allocator, x))
proc alloc(size: int): pointer = proc alloc(size: int): pointer =
when hasThreadSupport and hasSharedHeap: AcquireSys(HeapLock) when hasThreadSupport and hasSharedHeap: AcquireSys(HeapLock)
@ -601,6 +609,7 @@ when not defined(useNimRtl):
elif p != nil: elif p != nil:
dealloc(p) dealloc(p)
when false:
proc countFreeMem(): int = proc countFreeMem(): int =
# only used for assertions # only used for assertions
var it = allocator.freeChunksList var it = allocator.freeChunksList
@ -610,28 +619,8 @@ when not defined(useNimRtl):
proc getFreeMem(): int = proc getFreeMem(): int =
result = allocator.freeMem result = allocator.freeMem
#assert(result == countFreeMem()) #sysAssert(result == countFreeMem())
proc getTotalMem(): int = return allocator.currMem proc getTotalMem(): int = return allocator.currMem
proc getOccupiedMem(): int = return getTotalMem() - getFreeMem() proc getOccupiedMem(): int = return getTotalMem() - getFreeMem()
when isMainModule:
const iterations = 4000_000
incl(allocator.chunkStarts, 11)
assert 11 in allocator.chunkStarts
excl(allocator.chunkStarts, 11)
assert 11 notin allocator.chunkStarts
var p: array [1..iterations, pointer]
for i in 7..7:
var x = i * 8
for j in 1.. iterations:
p[j] = alloc(allocator, x)
for j in 1..iterations:
assert isAllocatedPtr(allocator, p[j])
echo($i, " used memory: ", $(allocator.currMem))
for j in countdown(iterations, 1):
#echo("j: ", $j)
dealloc(allocator, p[j])
assert(not isAllocatedPtr(allocator, p[j]))
echo($i, " after freeing: ", $(allocator.currMem))

View file

@ -24,7 +24,7 @@ proc genericAssignAux(dest, src: Pointer, n: ptr TNimNode, shallow: bool) =
n.typ.size) n.typ.size)
var m = selectBranch(src, n) var m = selectBranch(src, n)
if m != nil: genericAssignAux(dest, src, m, shallow) if m != nil: genericAssignAux(dest, src, m, shallow)
of nkNone: assert(false) of nkNone: sysAssert(false)
#else: #else:
# echo "ugh memory corruption! ", n.kind # echo "ugh memory corruption! ", n.kind
# quit 1 # quit 1
@ -33,7 +33,7 @@ proc genericAssignAux(dest, src: Pointer, mt: PNimType, shallow: bool) =
var var
d = cast[TAddress](dest) d = cast[TAddress](dest)
s = cast[TAddress](src) s = cast[TAddress](src)
assert(mt != nil) sysAssert(mt != nil)
case mt.Kind case mt.Kind
of tyString: of tyString:
var x = cast[ppointer](dest) var x = cast[ppointer](dest)
@ -50,7 +50,7 @@ proc genericAssignAux(dest, src: Pointer, mt: PNimType, shallow: bool) =
# this can happen! nil sequences are allowed # this can happen! nil sequences are allowed
unsureAsgnRef(x, s2) unsureAsgnRef(x, s2)
return return
assert(dest != nil) sysAssert(dest != nil)
unsureAsgnRef(x, newObj(mt, seq.len * mt.base.size + GenericSeqSize)) unsureAsgnRef(x, newObj(mt, seq.len * mt.base.size + GenericSeqSize))
var dst = cast[taddress](cast[ppointer](dest)[]) var dst = cast[taddress](cast[ppointer](dest)[])
for i in 0..seq.len-1: for i in 0..seq.len-1:
@ -101,7 +101,7 @@ proc objectInit(dest: Pointer, typ: PNimType) {.compilerProc.}
proc objectInitAux(dest: Pointer, n: ptr TNimNode) = proc objectInitAux(dest: Pointer, n: ptr TNimNode) =
var d = cast[TAddress](dest) var d = cast[TAddress](dest)
case n.kind case n.kind
of nkNone: assert(false) of nkNone: sysAssert(false)
of nkSLot: objectInit(cast[pointer](d +% n.offset), n.typ) of nkSLot: objectInit(cast[pointer](d +% n.offset), n.typ)
of nkList: of nkList:
for i in 0..n.len-1: for i in 0..n.len-1:
@ -134,7 +134,7 @@ proc genericReset(dest: Pointer, mt: PNimType) {.compilerProc.}
proc genericResetAux(dest: Pointer, n: ptr TNimNode) = proc genericResetAux(dest: Pointer, n: ptr TNimNode) =
var d = cast[TAddress](dest) var d = cast[TAddress](dest)
case n.kind case n.kind
of nkNone: assert(false) of nkNone: sysAssert(false)
of nkSlot: genericReset(cast[pointer](d +% n.offset), n.typ) of nkSlot: genericReset(cast[pointer](d +% n.offset), n.typ)
of nkList: of nkList:
for i in 0..n.len-1: genericResetAux(dest, n.sons[i]) for i in 0..n.len-1: genericResetAux(dest, n.sons[i])
@ -145,7 +145,7 @@ proc genericResetAux(dest: Pointer, n: ptr TNimNode) =
proc genericReset(dest: Pointer, mt: PNimType) = proc genericReset(dest: Pointer, mt: PNimType) =
var d = cast[TAddress](dest) var d = cast[TAddress](dest)
assert(mt != nil) sysAssert(mt != nil)
case mt.Kind case mt.Kind
of tyString, tyRef, tySequence: of tyString, tyRef, tySequence:
unsureAsgnRef(cast[ppointer](dest), nil) unsureAsgnRef(cast[ppointer](dest), nil)

View file

@ -22,14 +22,14 @@ else:
inc(p, val) inc(p, val)
result = p result = p
proc atomicInc(memLoc: var int, x: int): int = proc atomicInc(memLoc: var int, x: int = 1): int =
when hasThreadSupport: when hasThreadSupport:
result = sync_add_and_fetch(memLoc, x) result = sync_add_and_fetch(memLoc, x)
else: else:
inc(memLoc, x) inc(memLoc, x)
result = memLoc result = memLoc
proc atomicDec(memLoc: var int, x: int): int = proc atomicDec(memLoc: var int, x: int = 1): int =
when hasThreadSupport: when hasThreadSupport:
when defined(sync_sub_and_fetch): when defined(sync_sub_and_fetch):
result = sync_sub_and_fetch(memLoc, x) result = sync_sub_and_fetch(memLoc, x)

View file

@ -102,9 +102,9 @@ proc CellSetGet(t: TCellSet, key: TAddress): PPageDesc =
proc CellSetRawInsert(t: TCellSet, data: PPageDescArray, desc: PPageDesc) = proc CellSetRawInsert(t: TCellSet, data: PPageDescArray, desc: PPageDesc) =
var h = cast[int](desc.key) and t.max var h = cast[int](desc.key) and t.max
while data[h] != nil: while data[h] != nil:
assert(data[h] != desc) sysAssert(data[h] != desc)
h = nextTry(h, t.max) h = nextTry(h, t.max)
assert(data[h] == nil) sysAssert(data[h] == nil)
data[h] = desc data[h] = desc
proc CellSetEnlarge(t: var TCellSet) = proc CellSetEnlarge(t: var TCellSet) =
@ -130,7 +130,7 @@ proc CellSetPut(t: var TCellSet, key: TAddress): PPageDesc =
inc(t.counter) inc(t.counter)
h = cast[int](key) and t.max h = cast[int](key) and t.max
while t.data[h] != nil: h = nextTry(h, t.max) while t.data[h] != nil: h = nextTry(h, t.max)
assert(t.data[h] == nil) sysAssert(t.data[h] == nil)
# the new page descriptor goes into result # the new page descriptor goes into result
result = cast[PPageDesc](unlockedAlloc0(sizeof(TPageDesc))) result = cast[PPageDesc](unlockedAlloc0(sizeof(TPageDesc)))
result.next = t.head result.next = t.head

View file

@ -408,7 +408,7 @@ proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.exportc.} = proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.exportc.} =
case n.kind case n.kind
of nkNone: assert(false) of nkNone: sysAssert(false)
of nkSlot: of nkSlot:
asm "`dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ);" asm "`dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ);"
of nkList: of nkList:

View file

@ -53,17 +53,20 @@ type
TGcHeap {.final, pure.} = object # this contains the zero count and TGcHeap {.final, pure.} = object # this contains the zero count and
# non-zero count table # non-zero count table
stackBottom: pointer
cycleThreshold: int
zct: TCellSeq # the zero count table zct: TCellSeq # the zero count table
decStack: TCellSeq # cells in the stack that are to decref again decStack: TCellSeq # cells in the stack that are to decref again
cycleRoots: TCellSet cycleRoots: TCellSet
tempStack: TCellSeq # temporary stack for recursion elimination tempStack: TCellSeq # temporary stack for recursion elimination
recGcLock: int # prevent recursion via finalizers; no thread lock recGcLock: int # prevent recursion via finalizers; no thread lock
region: TMemRegion # garbage collected region
stat: TGcStat stat: TGcStat
var var
stackBottom {.rtlThreadVar.}: pointer
gch {.rtlThreadVar.}: TGcHeap gch {.rtlThreadVar.}: TGcHeap
cycleThreshold {.rtlThreadVar.}: int
InstantiateForRegion(gch.region)
proc acquire(gch: var TGcHeap) {.inline.} = proc acquire(gch: var TGcHeap) {.inline.} =
when hasThreadSupport and hasSharedHeap: when hasThreadSupport and hasSharedHeap:
@ -124,30 +127,30 @@ when traceGC:
of csAllocated: of csAllocated:
if c in states[csAllocated]: if c in states[csAllocated]:
writeCell("attempt to alloc an already allocated cell", c) writeCell("attempt to alloc an already allocated cell", c)
assert(false) sysAssert(false)
excl(states[csCycFreed], c) excl(states[csCycFreed], c)
excl(states[csZctFreed], c) excl(states[csZctFreed], c)
of csZctFreed: of csZctFreed:
if c in states[csZctFreed]: if c in states[csZctFreed]:
writeCell("attempt to free zct cell twice", c) writeCell("attempt to free zct cell twice", c)
assert(false) sysAssert(false)
if c in states[csCycFreed]: if c in states[csCycFreed]:
writeCell("attempt to free with zct, but already freed with cyc", c) writeCell("attempt to free with zct, but already freed with cyc", c)
assert(false) sysAssert(false)
if c notin states[csAllocated]: if c notin states[csAllocated]:
writeCell("attempt to free not an allocated cell", c) writeCell("attempt to free not an allocated cell", c)
assert(false) sysAssert(false)
excl(states[csAllocated], c) excl(states[csAllocated], c)
of csCycFreed: of csCycFreed:
if c notin states[csAllocated]: if c notin states[csAllocated]:
writeCell("attempt to free a not allocated cell", c) writeCell("attempt to free a not allocated cell", c)
assert(false) sysAssert(false)
if c in states[csCycFreed]: if c in states[csCycFreed]:
writeCell("attempt to free cyc cell twice", c) writeCell("attempt to free cyc cell twice", c)
assert(false) sysAssert(false)
if c in states[csZctFreed]: if c in states[csZctFreed]:
writeCell("attempt to free with cyc, but already freed with zct", c) writeCell("attempt to free with cyc, but already freed with zct", c)
assert(false) sysAssert(false)
excl(states[csAllocated], c) excl(states[csAllocated], c)
incl(states[state], c) incl(states[state], c)
@ -216,7 +219,7 @@ proc decRef(c: PCell) {.inline.} =
when stressGC: when stressGC:
if c.refcount <% rcIncrement: if c.refcount <% rcIncrement:
writeCell("broken cell", c) writeCell("broken cell", c)
assert(c.refcount >=% rcIncrement) sysAssert(c.refcount >=% rcIncrement)
#if c.refcount <% rcIncrement: quit("leck mich") #if c.refcount <% rcIncrement: quit("leck mich")
if --c.refcount: if --c.refcount:
rtlAddZCT(c) rtlAddZCT(c)
@ -233,7 +236,7 @@ proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p))
proc asgnRef(dest: ppointer, src: pointer) {.compilerProc, inline.} = proc asgnRef(dest: ppointer, src: pointer) {.compilerProc, inline.} =
# the code generator calls this proc! # the code generator calls this proc!
assert(not isOnStack(dest)) sysAssert(not isOnStack(dest))
# BUGFIX: first incRef then decRef! # BUGFIX: first incRef then decRef!
if src != nil: incRef(usrToCell(src)) if src != nil: incRef(usrToCell(src))
if dest[] != nil: decRef(usrToCell(dest[])) if dest[] != nil: decRef(usrToCell(dest[]))
@ -267,7 +270,7 @@ proc initGC() =
when not defined(useNimRtl): when not defined(useNimRtl):
when traceGC: when traceGC:
for i in low(TCellState)..high(TCellState): Init(states[i]) for i in low(TCellState)..high(TCellState): Init(states[i])
cycleThreshold = InitialCycleThreshold gch.cycleThreshold = InitialCycleThreshold
gch.stat.stackScans = 0 gch.stat.stackScans = 0
gch.stat.cycleCollections = 0 gch.stat.cycleCollections = 0
gch.stat.maxThreshold = 0 gch.stat.maxThreshold = 0
@ -289,7 +292,7 @@ proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
of nkCase: of nkCase:
var m = selectBranch(dest, n) var m = selectBranch(dest, n)
if m != nil: forAllSlotsAux(dest, m, op) if m != nil: forAllSlotsAux(dest, m, op)
of nkNone: assert(false) of nkNone: sysAssert(false)
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) = proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
var d = cast[TAddress](dest) var d = cast[TAddress](dest)
@ -306,9 +309,9 @@ proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
else: nil else: nil
proc forAllChildren(cell: PCell, op: TWalkOp) = proc forAllChildren(cell: PCell, op: TWalkOp) =
assert(cell != nil) sysAssert(cell != nil)
assert(cell.typ != nil) sysAssert(cell.typ != nil)
assert cell.typ.kind in {tyRef, tySequence, tyString} sysAssert cell.typ.kind in {tyRef, tySequence, tyString}
case cell.typ.Kind case cell.typ.Kind
of tyRef: # common case of tyRef: # common case
forAllChildrenAux(cellToUsr(cell), cell.typ.base, op) forAllChildrenAux(cellToUsr(cell), cell.typ.base, op)
@ -321,12 +324,7 @@ proc forAllChildren(cell: PCell, op: TWalkOp) =
GenericSeqSize), cell.typ.base, op) GenericSeqSize), cell.typ.base, op)
else: nil else: nil
proc checkCollection {.inline.} = proc addNewObjToZCT(res: PCell, gch: var TGcHeap) {.inline.} =
# checks if a collection should be done
if gch.recGcLock == 0:
collectCT(gch)
proc addNewObjToZCT(res: PCell) {.inline.} =
# we check the last 8 entries (cache line) for a slot that could be reused. # we check the last 8 entries (cache line) for a slot that could be reused.
# In 63% of all cases we succeed here! But we have to optimize the heck # In 63% of all cases we succeed here! But we have to optimize the heck
# out of this small linear search so that ``newObj`` is not slowed down. # out of this small linear search so that ``newObj`` is not slowed down.
@ -370,14 +368,14 @@ proc addNewObjToZCT(res: PCell) {.inline.} =
return return
add(gch.zct, res) add(gch.zct, res)
proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = proc newObj(typ: PNimType, size: int, gch: var TGcHeap): pointer =
# generates a new object and sets its reference counter to 0 # generates a new object and sets its reference counter to 0
acquire(gch) acquire(gch)
assert(typ.kind in {tyRef, tyString, tySequence}) sysAssert(typ.kind in {tyRef, tyString, tySequence})
checkCollection() collectCT(gch)
var res = cast[PCell](rawAlloc(allocator, size + sizeof(TCell))) var res = cast[PCell](rawAlloc(gch.region, size + sizeof(TCell)))
zeroMem(res, size+sizeof(TCell)) zeroMem(res, size+sizeof(TCell))
assert((cast[TAddress](res) and (MemAlign-1)) == 0) sysAssert((cast[TAddress](res) and (MemAlign-1)) == 0)
# now it is buffered in the ZCT # now it is buffered in the ZCT
res.typ = typ res.typ = typ
when debugGC and not hasThreadSupport: when debugGC and not hasThreadSupport:
@ -385,37 +383,39 @@ proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
res.filename = framePtr.prev.filename res.filename = framePtr.prev.filename
res.line = framePtr.prev.line res.line = framePtr.prev.line
res.refcount = rcZct # refcount is zero, but mark it to be in the ZCT res.refcount = rcZct # refcount is zero, but mark it to be in the ZCT
assert(isAllocatedPtr(allocator, res)) sysAssert(isAllocatedPtr(gch.region, res))
# its refcount is zero, so add it to the ZCT: # its refcount is zero, so add it to the ZCT:
addNewObjToZCT(res) addNewObjToZCT(res, gch)
when logGC: writeCell("new cell", res) when logGC: writeCell("new cell", res)
gcTrace(res, csAllocated) gcTrace(res, csAllocated)
release(gch) release(gch)
result = cellToUsr(res) result = cellToUsr(res)
proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = newObj(typ, size, gch)
proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
# `newObj` already uses locks, so no need for them here. # `newObj` already uses locks, so no need for them here.
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize)) result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
cast[PGenericSeq](result).len = len cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).space = len cast[PGenericSeq](result).space = len
proc growObj(old: pointer, newsize: int): pointer {.rtl.} = proc growObj(old: pointer, newsize: int, gch: var TGcHeap): pointer =
acquire(gch) acquire(gch)
checkCollection() collectCT(gch)
var ol = usrToCell(old) var ol = usrToCell(old)
assert(ol.typ != nil) sysAssert(ol.typ != nil)
assert(ol.typ.kind in {tyString, tySequence}) sysAssert(ol.typ.kind in {tyString, tySequence})
var res = cast[PCell](rawAlloc(allocator, newsize + sizeof(TCell))) var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(TCell)))
var elemSize = 1 var elemSize = 1
if ol.typ.kind != tyString: if ol.typ.kind != tyString: elemSize = ol.typ.base.size
elemSize = ol.typ.base.size
var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize
copyMem(res, ol, oldsize + sizeof(TCell)) copyMem(res, ol, oldsize + sizeof(TCell))
zeroMem(cast[pointer](cast[TAddress](res)+% oldsize +% sizeof(TCell)), zeroMem(cast[pointer](cast[TAddress](res)+% oldsize +% sizeof(TCell)),
newsize-oldsize) newsize-oldsize)
assert((cast[TAddress](res) and (MemAlign-1)) == 0) sysAssert((cast[TAddress](res) and (MemAlign-1)) == 0)
assert(res.refcount shr rcShift <=% 1) sysAssert(res.refcount shr rcShift <=% 1)
#if res.refcount <% rcIncrement: #if res.refcount <% rcIncrement:
# add(gch.zct, res) # add(gch.zct, res)
#else: # XXX: what to do here? #else: # XXX: what to do here?
@ -434,29 +434,32 @@ proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
writeCell("growObj new cell", res) writeCell("growObj new cell", res)
gcTrace(ol, csZctFreed) gcTrace(ol, csZctFreed)
gcTrace(res, csAllocated) gcTrace(res, csAllocated)
when reallyDealloc: rawDealloc(allocator, ol) when reallyDealloc: rawDealloc(gch.region, ol)
else: else:
assert(ol.typ != nil) sysAssert(ol.typ != nil)
zeroMem(ol, sizeof(TCell)) zeroMem(ol, sizeof(TCell))
release(gch) release(gch)
result = cellToUsr(res) result = cellToUsr(res)
proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
result = growObj(old, newsize, gch)
# ---------------- cycle collector ------------------------------------------- # ---------------- cycle collector -------------------------------------------
proc doOperation(p: pointer, op: TWalkOp) = proc doOperation(p: pointer, op: TWalkOp) =
if p == nil: return if p == nil: return
var c: PCell = usrToCell(p) var c: PCell = usrToCell(p)
assert(c != nil) sysAssert(c != nil)
case op # faster than function pointers because of easy prediction case op # faster than function pointers because of easy prediction
of waZctDecRef: of waZctDecRef:
assert(c.refcount >=% rcIncrement) sysAssert(c.refcount >=% rcIncrement)
c.refcount = c.refcount -% rcIncrement c.refcount = c.refcount -% rcIncrement
when logGC: writeCell("decref (from doOperation)", c) when logGC: writeCell("decref (from doOperation)", c)
if c.refcount <% rcIncrement: addZCT(gch.zct, c) if c.refcount <% rcIncrement: addZCT(gch.zct, c)
of waPush: of waPush:
add(gch.tempStack, c) add(gch.tempStack, c)
of waCycleDecRef: of waCycleDecRef:
assert(c.refcount >=% rcIncrement) sysAssert(c.refcount >=% rcIncrement)
c.refcount = c.refcount -% rcIncrement c.refcount = c.refcount -% rcIncrement
# we now use a much simpler and non-recursive algorithm for cycle removal # we now use a much simpler and non-recursive algorithm for cycle removal
@ -496,20 +499,20 @@ proc collectCycles(gch: var TGcHeap) =
prepareDealloc(c) prepareDealloc(c)
gcTrace(c, csCycFreed) gcTrace(c, csCycFreed)
when logGC: writeCell("cycle collector dealloc cell", c) when logGC: writeCell("cycle collector dealloc cell", c)
when reallyDealloc: rawDealloc(allocator, c) when reallyDealloc: rawDealloc(gch.region, c)
else: else:
assert(c.typ != nil) sysAssert(c.typ != nil)
zeroMem(c, sizeof(TCell)) zeroMem(c, sizeof(TCell))
Deinit(gch.cycleRoots) Deinit(gch.cycleRoots)
Init(gch.cycleRoots) Init(gch.cycleRoots)
proc gcMark(p: pointer) {.inline.} = proc gcMark(gch: var TGcHeap, p: pointer) {.inline.} =
# the addresses are not as cells on the stack, so turn them to cells: # the addresses are not as cells on the stack, so turn them to cells:
var cell = usrToCell(p) var cell = usrToCell(p)
var c = cast[TAddress](cell) var c = cast[TAddress](cell)
if c >% PageSize and (c and (MemAlign-1)) == 0: if c >% PageSize and (c and (MemAlign-1)) == 0:
# fast check: does it look like a cell? # fast check: does it look like a cell?
if isAllocatedPtr(allocator, cell): if isAllocatedPtr(gch.region, cell):
# mark the cell: # mark the cell:
cell.refcount = cell.refcount +% rcIncrement cell.refcount = cell.refcount +% rcIncrement
add(gch.decStack, cell) add(gch.decStack, cell)
@ -520,13 +523,13 @@ proc markThreadStacks(gch: var TGcHeap) =
var it = threadList var it = threadList
while it != nil: while it != nil:
# mark registers: # mark registers:
for i in 0 .. high(it.registers): gcMark(it.registers[i]) for i in 0 .. high(it.registers): gcMark(gch, it.registers[i])
var sp = cast[TAddress](it.stackBottom) var sp = cast[TAddress](it.stackBottom)
var max = cast[TAddress](it.stackTop) var max = cast[TAddress](it.stackTop)
# XXX stack direction? # XXX stack direction?
# XXX unroll this loop: # XXX unroll this loop:
while sp <=% max: while sp <=% max:
gcMark(cast[ppointer](sp)[]) gcMark(gch, cast[ppointer](sp)[])
sp = sp +% sizeof(pointer) sp = sp +% sizeof(pointer)
it = it.next it = it.next
@ -545,24 +548,24 @@ when not defined(useNimRtl):
proc setStackBottom(theStackBottom: pointer) = proc setStackBottom(theStackBottom: pointer) =
#c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom) #c_fprintf(c_stdout, "stack bottom: %p;\n", theStackBottom)
# the first init must be the one that defines the stack bottom: # the first init must be the one that defines the stack bottom:
if stackBottom == nil: stackBottom = theStackBottom if gch.stackBottom == nil: gch.stackBottom = theStackBottom
else: else:
var a = cast[TAddress](theStackBottom) # and not PageMask - PageSize*2 var a = cast[TAddress](theStackBottom) # and not PageMask - PageSize*2
var b = cast[TAddress](stackBottom) var b = cast[TAddress](gch.stackBottom)
when stackIncreases: when stackIncreases:
stackBottom = cast[pointer](min(a, b)) gch.stackBottom = cast[pointer](min(a, b))
else: else:
stackBottom = cast[pointer](max(a, b)) gch.stackBottom = cast[pointer](max(a, b))
proc stackSize(): int {.noinline.} = proc stackSize(): int {.noinline.} =
var stackTop {.volatile.}: pointer var stackTop {.volatile.}: pointer
result = abs(cast[int](addr(stackTop)) - cast[int](stackBottom)) result = abs(cast[int](addr(stackTop)) - cast[int](gch.stackBottom))
when defined(sparc): # For SPARC architecture. when defined(sparc): # For SPARC architecture.
proc isOnStack(p: pointer): bool = proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer var stackTop {.volatile.}: pointer
stackTop = addr(stackTop) stackTop = addr(stackTop)
var b = cast[TAddress](stackBottom) var b = cast[TAddress](gch.stackBottom)
var a = cast[TAddress](stackTop) var a = cast[TAddress](stackTop)
var x = cast[TAddress](p) var x = cast[TAddress](p)
result = a <=% x and x <=% b result = a <=% x and x <=% b
@ -574,13 +577,13 @@ when defined(sparc): # For SPARC architecture.
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """ asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
var var
max = stackBottom max = gch.stackBottom
sp: PPointer sp: PPointer
stackTop: array[0..1, pointer] stackTop: array[0..1, pointer]
sp = addr(stackTop[0]) sp = addr(stackTop[0])
# Addresses decrease as the stack grows. # Addresses decrease as the stack grows.
while sp <= max: while sp <= max:
gcMark(sp[]) gcMark(gch, sp[])
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer)) sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
elif defined(ELATE): elif defined(ELATE):
@ -593,7 +596,7 @@ elif stackIncreases:
proc isOnStack(p: pointer): bool = proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer var stackTop {.volatile.}: pointer
stackTop = addr(stackTop) stackTop = addr(stackTop)
var a = cast[TAddress](stackBottom) var a = cast[TAddress](gch.stackBottom)
var b = cast[TAddress](stackTop) var b = cast[TAddress](stackTop)
var x = cast[TAddress](p) var x = cast[TAddress](p)
result = a <=% x and x <=% b result = a <=% x and x <=% b
@ -606,12 +609,12 @@ elif stackIncreases:
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} = proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
var registers: C_JmpBuf var registers: C_JmpBuf
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
var max = cast[TAddress](stackBottom) var max = cast[TAddress](gch.stackBottom)
var sp = cast[TAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer) var sp = cast[TAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer)
# sp will traverse the JMP_BUF as well (jmp_buf size is added, # sp will traverse the JMP_BUF as well (jmp_buf size is added,
# otherwise sp would be below the registers structure). # otherwise sp would be below the registers structure).
while sp >=% max: while sp >=% max:
gcMark(cast[ppointer](sp)[]) gcMark(gch, cast[ppointer](sp)[])
sp = sp -% sizeof(pointer) sp = sp -% sizeof(pointer)
else: else:
@ -621,7 +624,7 @@ else:
proc isOnStack(p: pointer): bool = proc isOnStack(p: pointer): bool =
var stackTop {.volatile.}: pointer var stackTop {.volatile.}: pointer
stackTop = addr(stackTop) stackTop = addr(stackTop)
var b = cast[TAddress](stackBottom) var b = cast[TAddress](gch.stackBottom)
var a = cast[TAddress](stackTop) var a = cast[TAddress](stackTop)
var x = cast[TAddress](p) var x = cast[TAddress](p)
result = a <=% x and x <=% b result = a <=% x and x <=% b
@ -633,22 +636,22 @@ else:
type PStackSlice = ptr array [0..7, pointer] type PStackSlice = ptr array [0..7, pointer]
var registers: C_JmpBuf var registers: C_JmpBuf
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
var max = cast[TAddress](stackBottom) var max = cast[TAddress](gch.stackBottom)
var sp = cast[TAddress](addr(registers)) var sp = cast[TAddress](addr(registers))
# loop unrolled: # loop unrolled:
while sp <% max - 8*sizeof(pointer): while sp <% max - 8*sizeof(pointer):
gcMark(cast[PStackSlice](sp)[0]) gcMark(gch, cast[PStackSlice](sp)[0])
gcMark(cast[PStackSlice](sp)[1]) gcMark(gch, cast[PStackSlice](sp)[1])
gcMark(cast[PStackSlice](sp)[2]) gcMark(gch, cast[PStackSlice](sp)[2])
gcMark(cast[PStackSlice](sp)[3]) gcMark(gch, cast[PStackSlice](sp)[3])
gcMark(cast[PStackSlice](sp)[4]) gcMark(gch, cast[PStackSlice](sp)[4])
gcMark(cast[PStackSlice](sp)[5]) gcMark(gch, cast[PStackSlice](sp)[5])
gcMark(cast[PStackSlice](sp)[6]) gcMark(gch, cast[PStackSlice](sp)[6])
gcMark(cast[PStackSlice](sp)[7]) gcMark(gch, cast[PStackSlice](sp)[7])
sp = sp +% sizeof(pointer)*8 sp = sp +% sizeof(pointer)*8
# last few entries: # last few entries:
while sp <=% max: while sp <=% max:
gcMark(cast[ppointer](sp)[]) gcMark(gch, cast[ppointer](sp)[])
sp = sp +% sizeof(pointer) sp = sp +% sizeof(pointer)
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@ -664,7 +667,7 @@ proc CollectZCT(gch: var TGcHeap) =
while L[] > 0: while L[] > 0:
var c = gch.zct.d[0] var c = gch.zct.d[0]
# remove from ZCT: # remove from ZCT:
assert((c.refcount and colorMask) == rcZct) sysAssert((c.refcount and colorMask) == rcZct)
c.refcount = c.refcount and not colorMask c.refcount = c.refcount and not colorMask
gch.zct.d[0] = gch.zct.d[L[] - 1] gch.zct.d[0] = gch.zct.d[L[] - 1]
dec(L[]) dec(L[])
@ -683,41 +686,42 @@ proc CollectZCT(gch: var TGcHeap) =
# access invalid memory. This is done by prepareDealloc(): # access invalid memory. This is done by prepareDealloc():
prepareDealloc(c) prepareDealloc(c)
forAllChildren(c, waZctDecRef) forAllChildren(c, waZctDecRef)
when reallyDealloc: rawDealloc(allocator, c) when reallyDealloc: rawDealloc(gch.region, c)
else: else:
assert(c.typ != nil) sysAssert(c.typ != nil)
zeroMem(c, sizeof(TCell)) zeroMem(c, sizeof(TCell))
proc unmarkStackAndRegisters(gch: var TGcHeap) = proc unmarkStackAndRegisters(gch: var TGcHeap) =
var d = gch.decStack.d var d = gch.decStack.d
for i in 0..gch.decStack.len-1: for i in 0..gch.decStack.len-1:
assert isAllocatedPtr(allocator, d[i]) sysAssert isAllocatedPtr(allocator, d[i])
# decRef(d[i]) inlined: cannot create a cycle and must not acquire lock # decRef(d[i]) inlined: cannot create a cycle and must not acquire lock
var c = d[i] var c = d[i]
# XXX no need for an atomic dec here: # XXX no need for an atomic dec here:
if --c.refcount: if --c.refcount:
addZCT(gch.zct, c) addZCT(gch.zct, c)
assert c.typ != nil sysAssert c.typ != nil
gch.decStack.len = 0 gch.decStack.len = 0
proc collectCT(gch: var TGcHeap) = proc collectCT(gch: var TGcHeap) =
if gch.zct.len >= ZctThreshold or (cycleGC and if (gch.zct.len >= ZctThreshold or (cycleGC and
getOccupiedMem() >= cycleThreshold) or stressGC: getOccupiedMem(gch.region) >= gch.cycleThreshold) or stressGC) and
gch.recGcLock == 0:
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
assert(gch.decStack.len == 0) sysAssert(gch.decStack.len == 0)
markStackAndRegisters(gch) markStackAndRegisters(gch)
markThreadStacks(gch) markThreadStacks(gch)
gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len) gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len)
inc(gch.stat.stackScans) inc(gch.stat.stackScans)
collectZCT(gch) collectZCT(gch)
when cycleGC: when cycleGC:
if getOccupiedMem() >= cycleThreshold or stressGC: if getOccupiedMem() >= gch.cycleThreshold or stressGC:
collectCycles(gch) collectCycles(gch)
collectZCT(gch) collectZCT(gch)
inc(gch.stat.cycleCollections) inc(gch.stat.cycleCollections)
cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() *
cycleIncrease) cycleIncrease)
gch.stat.maxThreshold = max(gch.stat.maxThreshold, cycleThreshold) gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold)
unmarkStackAndRegisters(gch) unmarkStackAndRegisters(gch)
when not defined(useNimRtl): when not defined(useNimRtl):
@ -741,18 +745,18 @@ when not defined(useNimRtl):
of gcOptimizeTime: nil of gcOptimizeTime: nil
proc GC_enableMarkAndSweep() = proc GC_enableMarkAndSweep() =
cycleThreshold = InitialCycleThreshold gch.cycleThreshold = InitialCycleThreshold
proc GC_disableMarkAndSweep() = proc GC_disableMarkAndSweep() =
cycleThreshold = high(cycleThreshold)-1 gch.cycleThreshold = high(gch.cycleThreshold)-1
# set to the max value to suppress the cycle detector # set to the max value to suppress the cycle detector
proc GC_fullCollect() = proc GC_fullCollect() =
acquire(gch) acquire(gch)
var oldThreshold = cycleThreshold var oldThreshold = gch.cycleThreshold
cycleThreshold = 0 # forces cycle collection gch.cycleThreshold = 0 # forces cycle collection
collectCT(gch) collectCT(gch)
cycleThreshold = oldThreshold gch.cycleThreshold = oldThreshold
release(gch) release(gch)
proc GC_getStatistics(): string = proc GC_getStatistics(): string =

203
lib/system/inboxes.nim Normal file
View file

@ -0,0 +1,203 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Message passing for threads. The current implementation is slow and does
## not work with cyclic data structures. But hey, it's better than nothing.
type
pbytes = ptr array[0.. 0xffff, byte]
TInbox {.pure, final.} = object ## msg queue for a thread
rd, wr, count, mask: int
data: pbytes
lock: TSysLock
cond: TSysCond
elemType: PNimType
region: TMemRegion
PInbox = ptr TInbox
TLoadStoreMode = enum mStore, mLoad
proc initInbox(p: pointer) =
var inbox = cast[PInbox](p)
initSysLock(inbox.lock)
initSysCond(inbox.cond)
inbox.mask = -1
proc freeInbox(p: pointer) =
var inbox = cast[PInbox](p)
deallocOsPages(inbox.region)
deinitSys(inbox.lock)
deinitSysCond(inbox.cond)
proc storeAux(dest, src: Pointer, mt: PNimType, t: PInbox, mode: TLoadStoreMode)
proc storeAux(dest, src: Pointer, n: ptr TNimNode, t: PInbox,
mode: TLoadStoreMode) =
var
d = cast[TAddress](dest)
s = cast[TAddress](src)
case n.kind
of nkSlot: storeAux(cast[pointer](d +% n.offset),
cast[pointer](s +% n.offset), n.typ, t, mode)
of nkList:
for i in 0..n.len-1: storeAux(dest, src, n.sons[i], t, mode)
of nkCase:
copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset),
n.typ.size)
var m = selectBranch(src, n)
if m != nil: storeAux(dest, src, m, t, mode)
of nkNone: sysAssert(false)
proc storeAux(dest, src: Pointer, mt: PNimType, t: PInbox,
mode: TLoadStoreMode) =
var
d = cast[TAddress](dest)
s = cast[TAddress](src)
sysAssert(mt != nil)
case mt.Kind
of tyString:
if mode == mStore:
var x = cast[ppointer](dest)
var s2 = cast[ppointer](s)[]
if s2 == nil:
x[] = nil
else:
var ss = cast[NimString](s2)
var ns = cast[NimString](rawAlloc(t.region, ss.len+1 + GenericSeqSize))
copyMem(ns, ss, ss.len+1 + GenericSeqSize)
x[] = ns
else:
var x = cast[ppointer](dest)
var s2 = cast[ppointer](s)[]
if s2 == nil:
unsureAsgnRef(x, s2)
else:
unsureAsgnRef(x, copyString(cast[NimString](s2)))
rawDealloc(t.region, s2)
of tySequence:
var s2 = cast[ppointer](src)[]
var seq = cast[PGenericSeq](s2)
var x = cast[ppointer](dest)
if s2 == nil:
if mode == mStore:
x[] = nil
else:
unsureAsgnRef(x, nil)
else:
sysAssert(dest != nil)
if mode == mStore:
x[] = rawAlloc(t.region, seq.len *% mt.base.size +% GenericSeqSize)
else:
unsureAsgnRef(x, newObj(mt, seq.len * mt.base.size + GenericSeqSize))
var dst = cast[taddress](cast[ppointer](dest)[])
for i in 0..seq.len-1:
storeAux(
cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
cast[pointer](cast[TAddress](s2) +% i *% mt.base.size +%
GenericSeqSize),
mt.Base, t, mode)
var dstseq = cast[PGenericSeq](dst)
dstseq.len = seq.len
dstseq.space = seq.len
if mode != mStore: rawDealloc(t.region, s2)
of tyObject:
# copy type field:
var pint = cast[ptr PNimType](dest)
# XXX use dynamic type here!
pint[] = mt
storeAux(dest, src, mt.node, t, mode)
of tyTuple, tyPureObject:
storeAux(dest, src, mt.node, t, mode)
of tyArray, tyArrayConstr:
for i in 0..(mt.size div mt.base.size)-1:
storeAux(cast[pointer](d +% i*% mt.base.size),
cast[pointer](s +% i*% mt.base.size), mt.base, t, mode)
of tyRef:
var s = cast[ppointer](src)[]
var x = cast[ppointer](dest)
if s == nil:
if mode == mStore:
x[] = nil
else:
unsureAsgnRef(x, nil)
else:
if mode == mStore:
x[] = rawAlloc(t.region, mt.base.size)
else:
# XXX we should use the dynamic type here too, but that is not stored in
# the inbox at all --> use source[]'s object type? but how? we need a
# tyRef to the object!
var obj = newObj(mt.base, mt.base.size)
unsureAsgnRef(x, obj)
storeAux(x[], s, mt.base, t, mode)
if mode != mStore: rawDealloc(t.region, s)
else:
copyMem(dest, src, mt.size) # copy raw bits
proc rawSend(q: PInbox, data: pointer, typ: PNimType) =
## adds an `item` to the end of the queue `q`.
var cap = q.mask+1
if q.count >= cap:
# start with capicity for 2 entries in the queue:
if cap == 0: cap = 1
var n = cast[pbytes](rawAlloc0(q.region, cap*2*typ.size))
var z = 0
var i = q.rd
var c = q.count
while c > 0:
dec c
copyMem(addr(n[z*typ.size]), addr(q.data[i*typ.size]), typ.size)
i = (i + 1) and q.mask
inc z
if q.data != nil: rawDealloc(q.region, q.data)
q.data = n
q.mask = cap*2 - 1
q.wr = q.count
q.rd = 0
#echo "came here"
storeAux(addr(q.data[q.wr * typ.size]), data, typ, q, mStore)
inc q.count
q.wr = (q.wr + 1) and q.mask
proc rawRecv(q: PInbox, data: pointer, typ: PNimType) =
assert q.count > 0
dec q.count
storeAux(data, addr(q.data[q.rd * typ.size]), typ, q, mLoad)
q.rd = (q.rd + 1) and q.mask
template lockInbox(q: expr, action: stmt) =
acquireSys(q.lock)
action
releaseSys(q.lock)
proc send*[TMsg](receiver: var TThread[TMsg], msg: TMsg) =
## sends a message to a thread. `msg` is deeply copied.
var q = cast[PInbox](getInBoxMem(receiver))
acquireSys(q.lock)
var m: TMsg
shallowCopy(m, msg)
rawSend(q, addr(m), cast[PNimType](getTypeInfo(msg)))
releaseSys(q.lock)
SignalSysCond(q.cond)
proc recv*[TMsg](): TMsg =
## receives a message from its internal message queue. This blocks until
## a message has arrived! You may use ``peek`` to avoid the blocking.
var q = cast[PInbox](getInBoxMem())
acquireSys(q.lock)
while q.count <= 0:
WaitSysCond(q.cond, q.lock)
rawRecv(q, addr(result), cast[PNimType](getTypeInfo(result)))
releaseSys(q.lock)
proc peek*(): int =
## returns the current number of messages in the inbox.
var q = cast[PInbox](getInBoxMem())
lockInbox(q):
result = q.count

View file

@ -62,11 +62,10 @@ when defined(boehmgc):
const boehmLib = "boehmgc.dll" const boehmLib = "boehmgc.dll"
elif defined(macosx): elif defined(macosx):
const boehmLib = "libgc.dylib" const boehmLib = "libgc.dylib"
proc boehmGCinit {.importc: "GC_init", dynlib: boehmLib.}
else: else:
const boehmLib = "/usr/lib/libgc.so.1" const boehmLib = "/usr/lib/libgc.so.1"
proc boehmGCinit {.importc: "GC_init", dynlib: boehmLib.}
proc boehmGC_disable {.importc: "GC_disable", dynlib: boehmLib.} proc boehmGC_disable {.importc: "GC_disable", dynlib: boehmLib.}
proc boehmGC_enable {.importc: "GC_enable", dynlib: boehmLib.} proc boehmGC_enable {.importc: "GC_enable", dynlib: boehmLib.}
proc boehmGCincremental {. proc boehmGCincremental {.
@ -177,12 +176,20 @@ elif defined(nogc):
proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} = proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} =
dest[] = src dest[] = src
var allocator {.rtlThreadVar.}: TMemRegion
InstantiateForRegion(allocator)
include "system/cellsets" include "system/cellsets"
else: else:
include "system/alloc" include "system/alloc"
proc unlockedAlloc(size: int): pointer {.inline.}
proc unlockedAlloc0(size: int): pointer {.inline.}
proc unlockedDealloc(p: pointer) {.inline.}
include "system/cellsets" include "system/cellsets"
assert(sizeof(TCell) == sizeof(TFreeCell)) sysAssert(sizeof(TCell) == sizeof(TFreeCell))
include "system/gc" include "system/gc"
{.pop.} {.pop.}

View file

@ -158,7 +158,7 @@ when not defined(useNimRtl):
proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode, proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode,
cl: var TReprClosure) = cl: var TReprClosure) =
case n.kind case n.kind
of nkNone: assert(false) of nkNone: sysAssert(false)
of nkSlot: of nkSlot:
add result, $n.name add result, $n.name
add result, " = " add result, " = "
@ -206,7 +206,7 @@ when not defined(useNimRtl):
var t = cast[ptr PNimType](p)[] var t = cast[ptr PNimType](p)[]
reprRecord(result, p, t, cl) reprRecord(result, p, t, cl)
of tyRef, tyPtr: of tyRef, tyPtr:
assert(p != nil) sysAssert(p != nil)
if cast[ppointer](p)[] == nil: add result, "nil" if cast[ppointer](p)[] == nil: add result, "nil"
else: reprRef(result, cast[ppointer](p)[], typ, cl) else: reprRef(result, cast[ppointer](p)[], typ, cl)
of tySequence: of tySequence:

101
lib/system/syslocks.nim Normal file
View file

@ -0,0 +1,101 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Low level system locks and condition vars.
when defined(Windows):
type
THandle = int
TSysLock {.final, pure.} = object # CRITICAL_SECTION in WinApi
DebugInfo: pointer
LockCount: int32
RecursionCount: int32
OwningThread: int
LockSemaphore: int
Reserved: int32
TSysCond = THandle
proc InitSysLock(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "InitializeCriticalSection".}
## Initializes the lock `L`.
proc TryAcquireSysAux(L: var TSysLock): int32 {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "TryEnterCriticalSection".}
## Tries to acquire the lock `L`.
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
result = TryAcquireSysAux(L) != 0'i32
proc AcquireSys(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "EnterCriticalSection".}
## Acquires the lock `L`.
proc ReleaseSys(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "LeaveCriticalSection".}
## Releases the lock `L`.
proc DeinitSys(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "DeleteCriticalSection".}
proc CreateEvent(lpEventAttributes: pointer,
bManualReset, bInitialState: int32,
lpName: cstring): TSysCond {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "CreateEvent".}
proc CloseHandle(hObject: THandle) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "CloseHandle".}
proc WaitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".}
proc SignalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "SetEvent".}
proc InitSysCond(cond: var TSysCond) {.inline.} =
cond = CreateEvent(nil, 0'i32, 0'i32, nil)
proc DeinitSysCond(cond: var TSysCond) {.inline.} =
CloseHandle(cond)
proc WaitSysCond(cond: var TSysCond, lock: var TSysLock) =
releaseSys(lock)
discard WaitForSingleObject(cond, -1'i32)
acquireSys(lock)
else:
type
TSysLock {.importc: "pthread_mutex_t", pure, final,
header: "<sys/types.h>".} = object
TSysCond {.importc: "pthread_cond_t", pure, final,
header: "<sys/types.h>".} = object
proc InitSysLock(L: var TSysLock, attr: pointer = nil) {.
importc: "pthread_mutex_init", header: "<pthread.h>", noSideEffect.}
proc AcquireSys(L: var TSysLock) {.noSideEffect,
importc: "pthread_mutex_lock", header: "<pthread.h>".}
proc TryAcquireSysAux(L: var TSysLock): cint {.noSideEffect,
importc: "pthread_mutex_trylock", header: "<pthread.h>".}
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
result = TryAcquireSysAux(L) == 0'i32
proc ReleaseSys(L: var TSysLock) {.noSideEffect,
importc: "pthread_mutex_unlock", header: "<pthread.h>".}
proc DeinitSys(L: var TSysLock) {.
importc: "pthread_mutex_destroy", header: "<pthread.h>".}
proc InitSysCond(cond: var TSysCond, cond_attr: pointer = nil) {.
importc: "pthread_cond_init", header: "<pthread.h>".}
proc WaitSysCond(cond: var TSysCond, lock: var TSysLock) {.
importc: "pthread_cond_wait", header: "<pthread.h>".}
proc SignalSysCond(cond: var TSysCond) {.
importc: "pthread_cond_signal", header: "<pthread.h>".}
proc DeinitSysCond(cond: var TSysCond) {.
importc: "pthread_cond_destroy", header: "<pthread.h>".}

View file

@ -25,7 +25,7 @@
## thr: array [0..4, TThread[tuple[a,b: int]]] ## thr: array [0..4, TThread[tuple[a,b: int]]]
## L: TLock ## L: TLock
## ##
## proc threadFunc(interval: tuple[a,b: int]) {.procvar.} = ## proc threadFunc(interval: tuple[a,b: int]) {.thread.} =
## for i in interval.a..interval.b: ## for i in interval.a..interval.b:
## Acquire(L) # lock stdout ## Acquire(L) # lock stdout
## echo i ## echo i
@ -41,38 +41,13 @@ const
maxRegisters = 256 # don't think there is an arch with more registers maxRegisters = 256 # don't think there is an arch with more registers
maxLocksPerThread* = 10 ## max number of locks a thread can hold maxLocksPerThread* = 10 ## max number of locks a thread can hold
## at the same time ## at the same time
useStackMaskHack = false ## use the stack mask hack for better performance
StackGuardSize = 4096
ThreadStackMask = 1024*256*sizeof(int)-1
ThreadStackSize = ThreadStackMask+1 - StackGuardSize
when defined(Windows): when defined(windows):
type type
TSysLock {.final, pure.} = object # CRITICAL_SECTION in WinApi
DebugInfo: pointer
LockCount: int32
RecursionCount: int32
OwningThread: int
LockSemaphore: int
Reserved: int32
proc InitSysLock(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "InitializeCriticalSection".}
## Initializes the lock `L`.
proc TryAcquireSysAux(L: var TSysLock): int32 {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "TryEnterCriticalSection".}
## Tries to acquire the lock `L`.
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
result = TryAcquireSysAux(L) != 0'i32
proc AcquireSys(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "EnterCriticalSection".}
## Acquires the lock `L`.
proc ReleaseSys(L: var TSysLock) {.stdcall, noSideEffect,
dynlib: "kernel32", importc: "LeaveCriticalSection".}
## Releases the lock `L`.
type
THandle = int
TSysThread = THandle TSysThread = THandle
TWinThreadProc = proc (x: pointer): int32 {.stdcall.} TWinThreadProc = proc (x: pointer): int32 {.stdcall.}
@ -95,9 +70,6 @@ when defined(Windows):
dwMilliseconds: int32): int32 {. dwMilliseconds: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "WaitForMultipleObjects".} stdcall, dynlib: "kernel32", importc: "WaitForMultipleObjects".}
proc WaitForSingleObject(hHandle: TSysThread, dwMilliseconds: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".}
proc TerminateThread(hThread: TSysThread, dwExitCode: int32): int32 {. proc TerminateThread(hThread: TSysThread, dwExitCode: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "TerminateThread".} stdcall, dynlib: "kernel32", importc: "TerminateThread".}
@ -115,24 +87,6 @@ else:
{.passL: "-pthread".} {.passL: "-pthread".}
{.passC: "-pthread".} {.passC: "-pthread".}
type
TSysLock {.importc: "pthread_mutex_t", pure, final,
header: "<sys/types.h>".} = object
proc InitSysLock(L: var TSysLock, attr: pointer = nil) {.
importc: "pthread_mutex_init", header: "<pthread.h>", noSideEffect.}
proc AcquireSys(L: var TSysLock) {.noSideEffect,
importc: "pthread_mutex_lock", header: "<pthread.h>".}
proc TryAcquireSysAux(L: var TSysLock): cint {.noSideEffect,
importc: "pthread_mutex_trylock", header: "<pthread.h>".}
proc TryAcquireSys(L: var TSysLock): bool {.inline.} =
result = TryAcquireSysAux(L) == 0'i32
proc ReleaseSys(L: var TSysLock) {.noSideEffect,
importc: "pthread_mutex_unlock", header: "<pthread.h>".}
type type
TSysThread {.importc: "pthread_t", header: "<sys/types.h>", TSysThread {.importc: "pthread_t", header: "<sys/types.h>",
final, pure.} = object final, pure.} = object
@ -191,57 +145,71 @@ else:
proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.inline.} = proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.inline.} =
result = pthread_getspecific(s) result = pthread_getspecific(s)
const emulatedThreadVars = defined(macosx) when useStackMaskHack:
proc pthread_attr_setstack(attr: var TPthread_attr, stackaddr: pointer,
size: int): cint {.
importc: "pthread_attr_setstack", header: "<pthread.h>".}
const
emulatedThreadVars = true
when emulatedThreadVars: when emulatedThreadVars:
# the compiler generates this proc for us, so that we can get the size of # the compiler generates this proc for us, so that we can get the size of
# the thread local var block: # the thread local var block; we use this only for sanity checking though
proc NimThreadVarsSize(): int {.noconv, importc: "NimThreadVarsSize".} proc NimThreadVarsSize(): int {.noconv, importc: "NimThreadVarsSize".}
proc ThreadVarsAlloc(size: int): pointer = # we preallocate a fixed size for thread local storage, so that no heap
result = c_malloc(size) # allocations are needed. Currently less than 7K are used on a 64bit machine.
zeroMem(result, size) # We use ``float`` for proper alignment:
proc ThreadVarsDealloc(p: pointer) {.importc: "free", nodecl.}
type type
TThreadLocalStorage = array [0..1_000, float]
PGcThread = ptr TGcThread PGcThread = ptr TGcThread
TGcThread {.pure.} = object TGcThread {.pure.} = object
sys: TSysThread sys: TSysThread
next, prev: PGcThread next, prev: PGcThread
stackBottom, stackTop, threadLocalStorage: pointer stackBottom, stackTop: pointer
stackSize: int stackSize: int
locksLen: int inbox: TThreadLocalStorage
locks: array [0..MaxLocksPerThread-1, pointer] when emulatedThreadVars and not useStackMaskHack:
registers: array[0..maxRegisters-1, pointer] # register contents for GC tls: TThreadLocalStorage
else:
nil
# XXX it'd be more efficient to not use a global variable for the # XXX it'd be more efficient to not use a global variable for the
# thread storage slot, but to rely on the implementation to assign slot 0 # thread storage slot, but to rely on the implementation to assign slot 0
# for us... ;-) # for us... ;-)
var globalsSlot = ThreadVarAlloc() var globalsSlot = ThreadVarAlloc()
#const globalsSlot = TThreadVarSlot(0) #const globalsSlot = TThreadVarSlot(0)
#assert checkSlot.int == globalsSlot.int #sysAssert checkSlot.int == globalsSlot.int
proc ThisThread(): PGcThread {.compilerRtl, inl.} =
result = cast[PGcThread](ThreadVarGetValue(globalsSlot))
proc GetThreadLocalVars(): pointer {.compilerRtl, inl.} = proc GetThreadLocalVars(): pointer {.compilerRtl, inl.} =
result = cast[PGcThread](ThreadVarGetValue(globalsSlot)).threadLocalStorage result = addr(cast[PGcThread](ThreadVarGetValue(globalsSlot)).tls)
when useStackMaskHack:
proc MaskStackPointer(offset: int): pointer {.compilerRtl, inl.} =
var x {.volatile.}: pointer
x = addr(x)
result = cast[pointer]((cast[int](x) and not ThreadStackMask) +%
(0) +% offset)
# create for the main thread. Note: do not insert this data into the list # create for the main thread. Note: do not insert this data into the list
# of all threads; it's not to be stopped etc. # of all threads; it's not to be stopped etc.
when not defined(useNimRtl): when not defined(useNimRtl):
when not useStackMaskHack:
var mainThread: TGcThread var mainThread: TGcThread
ThreadVarSetValue(globalsSlot, addr(mainThread)) ThreadVarSetValue(globalsSlot, addr(mainThread))
when emulatedThreadVars:
mainThread.threadLocalStorage = ThreadVarsAlloc(NimThreadVarsSize())
initStackBottom() initStackBottom()
initGC() initGC()
var heapLock: TSysLock var heapLock: TSysLock
InitSysLock(HeapLock) InitSysLock(HeapLock)
when emulatedThreadVars:
if NimThreadVarsSize() > sizeof(TThreadLocalStorage):
echo "too large thread local storage size requested"
quit 1
var var
threadList: PGcThread threadList: PGcThread
@ -251,7 +219,7 @@ when not defined(useNimRtl):
t.prev = nil t.prev = nil
t.next = threadList t.next = threadList
if threadList != nil: if threadList != nil:
assert(threadList.prev == nil) sysAssert(threadList.prev == nil)
threadList.prev = t threadList.prev = t
threadList = t threadList = t
ReleaseSys(HeapLock) ReleaseSys(HeapLock)
@ -270,9 +238,7 @@ when not defined(useNimRtl):
# on UNIX, the GC uses ``SIGFREEZE`` to tell every thread to stop so that # on UNIX, the GC uses ``SIGFREEZE`` to tell every thread to stop so that
# the GC can examine the stacks? # the GC can examine the stacks?
proc stopTheWord() = nil
proc stopTheWord() =
nil
# We jump through some hops here to ensure that Nimrod thread procs can have # We jump through some hops here to ensure that Nimrod thread procs can have
# the Nimrod calling convention. This is needed because thread procs are # the Nimrod calling convention. This is needed because thread procs are
@ -286,26 +252,33 @@ type
fn: proc (p: TParam) fn: proc (p: TParam)
data: TParam data: TParam
proc initInbox(p: pointer)
proc freeInbox(p: pointer)
when not defined(boehmgc) and not hasSharedHeap: when not defined(boehmgc) and not hasSharedHeap:
proc deallocOsPages() proc deallocOsPages()
template ThreadProcWrapperBody(closure: expr) = template ThreadProcWrapperBody(closure: expr) =
ThreadVarSetValue(globalsSlot, closure) ThreadVarSetValue(globalsSlot, closure)
var t = cast[ptr TThread[TParam]](closure) var t = cast[ptr TThread[TParam]](closure)
when emulatedThreadVars: when useStackMaskHack:
t.threadLocalStorage = ThreadVarsAlloc(NimThreadVarsSize()) var tls: TThreadLocalStorage
when not defined(boehmgc) and not hasSharedHeap: when not defined(boehmgc) and not hasSharedHeap:
# init the GC for this thread: # init the GC for this thread:
setStackBottom(addr(t)) setStackBottom(addr(t))
initGC() initGC()
t.stackBottom = addr(t) t.stackBottom = addr(t)
registerThread(t) registerThread(t)
initInbox(addr(t.inbox))
try: try:
when false:
var a = addr(tls)
var b = MaskStackPointer(1293920-372736-303104-36864)
c_fprintf(c_stdout, "TLS: %p\nmasked: %p\ndiff: %ld\n",
a, b, cast[int](a) - cast[int](b))
t.fn(t.data) t.fn(t.data)
finally: finally:
# XXX shut-down is not executed when the thread is forced down! # XXX shut-down is not executed when the thread is forced down!
when emulatedThreadVars: freeInbox(addr(t.inbox))
ThreadVarsDealloc(t.threadLocalStorage)
unregisterThread(t) unregisterThread(t)
when defined(deallocOsPages): deallocOsPages() when defined(deallocOsPages): deallocOsPages()
@ -330,7 +303,7 @@ proc joinThreads*[TParam](t: openArray[TThread[TParam]]) =
## waits for every thread in `t` to finish. ## waits for every thread in `t` to finish.
when hostOS == "windows": when hostOS == "windows":
var a: array[0..255, TSysThread] var a: array[0..255, TSysThread]
assert a.len >= t.len sysAssert a.len >= t.len
for i in 0..t.high: a[i] = t[i].sys for i in 0..t.high: a[i] = t[i].sys
discard WaitForMultipleObjects(t.len, cast[ptr TSysThread](addr(a)), 1, -1) discard WaitForMultipleObjects(t.len, cast[ptr TSysThread](addr(a)), 1, -1)
else: else:
@ -338,7 +311,7 @@ proc joinThreads*[TParam](t: openArray[TThread[TParam]]) =
when false: when false:
# XXX a thread should really release its heap here somehow: # XXX a thread should really release its heap here somehow:
proc destroyThread*[TParam](t: var TThread[TParam]) {.inline.} = proc destroyThread*[TParam](t: var TThread[TParam]) =
## forces the thread `t` to terminate. This is potentially dangerous if ## forces the thread `t` to terminate. This is potentially dangerous if
## you don't have full control over `t` and its acquired resources. ## you don't have full control over `t` and its acquired resources.
when hostOS == "windows": when hostOS == "windows":
@ -348,28 +321,32 @@ when false:
unregisterThread(addr(t)) unregisterThread(addr(t))
proc createThread*[TParam](t: var TThread[TParam], proc createThread*[TParam](t: var TThread[TParam],
tp: proc (param: TParam), tp: proc (param: TParam) {.thread.},
param: TParam, param: TParam) =
stackSize = 1024*256*sizeof(int)) {.
magic: "CreateThread".} =
## creates a new thread `t` and starts its execution. Entry point is the ## creates a new thread `t` and starts its execution. Entry point is the
## proc `tp`. `param` is passed to `tp`. ## proc `tp`. `param` is passed to `tp`.
t.data = param t.data = param
t.fn = tp t.fn = tp
t.stackSize = stackSize t.stackSize = ThreadStackSize
when hostOS == "windows": when hostOS == "windows":
var dummyThreadId: int32 var dummyThreadId: int32
t.sys = CreateThread(nil, stackSize, threadProcWrapper[TParam], t.sys = CreateThread(nil, ThreadStackSize, threadProcWrapper[TParam],
addr(t), 0'i32, dummyThreadId) addr(t), 0'i32, dummyThreadId)
if t.sys <= 0: if t.sys <= 0:
raise newException(EResourceExhausted, "cannot create thread") raise newException(EResourceExhausted, "cannot create thread")
else: else:
var a: Tpthread_attr var a: Tpthread_attr
pthread_attr_init(a) pthread_attr_init(a)
pthread_attr_setstacksize(a, stackSize) pthread_attr_setstacksize(a, ThreadStackSize)
if pthread_create(t.sys, a, threadProcWrapper[TParam], addr(t)) != 0: if pthread_create(t.sys, a, threadProcWrapper[TParam], addr(t)) != 0:
raise newException(EResourceExhausted, "cannot create thread") raise newException(EResourceExhausted, "cannot create thread")
when useStackMaskHack:
proc runMain(tp: proc (dummy: pointer) {.thread.}) {.compilerproc.} =
var mainThread: TThread[pointer]
createThread(mainThread, tp, nil)
joinThread(mainThread)
# --------------------------- lock handling ---------------------------------- # --------------------------- lock handling ----------------------------------
type type
@ -380,18 +357,20 @@ const
when nodeadlocks: when nodeadlocks:
var var
deadlocksPrevented* = 0 ## counts the number of times a deadlocksPrevented*: int ## counts the number of times a
## deadlock has been prevented ## deadlock has been prevented
locksLen {.threadvar.}: int
locks {.threadvar.}: array [0..MaxLocksPerThread-1, pointer]
proc OrderedLocks(): bool =
for i in 0 .. locksLen-2:
if locks[i] >= locks[i+1]: return false
result = true
proc InitLock*(lock: var TLock) {.inline.} = proc InitLock*(lock: var TLock) {.inline.} =
## Initializes the lock `lock`. ## Initializes the lock `lock`.
InitSysLock(lock) InitSysLock(lock)
proc OrderedLocks(g: PGcThread): bool =
for i in 0 .. g.locksLen-2:
if g.locks[i] >= g.locks[i+1]: return false
result = true
proc TryAcquire*(lock: var TLock): bool {.inline.} = proc TryAcquire*(lock: var TLock): bool {.inline.} =
## Try to acquires the lock `lock`. Returns `true` on success. ## Try to acquires the lock `lock`. Returns `true` on success.
result = TryAcquireSys(lock) result = TryAcquireSys(lock)
@ -399,88 +378,93 @@ proc TryAcquire*(lock: var TLock): bool {.inline.} =
if not result: return if not result: return
# we have to add it to the ordered list. Oh, and we might fail if # we have to add it to the ordered list. Oh, and we might fail if
# there is no space in the array left ... # there is no space in the array left ...
var g = ThisThread() if locksLen >= len(locks):
if g.locksLen >= len(g.locks):
ReleaseSys(lock) ReleaseSys(lock)
raise newException(EResourceExhausted, "cannot acquire additional lock") raise newException(EResourceExhausted, "cannot acquire additional lock")
# find the position to add: # find the position to add:
var p = addr(lock) var p = addr(lock)
var L = g.locksLen-1 var L = locksLen-1
var i = 0 var i = 0
while i <= L: while i <= L:
assert g.locks[i] != nil sysAssert locks[i] != nil
if g.locks[i] < p: inc(i) # in correct order if locks[i] < p: inc(i) # in correct order
elif g.locks[i] == p: return # thread already holds lock elif locks[i] == p: return # thread already holds lock
else: else:
# do the crazy stuff here: # do the crazy stuff here:
while L >= i: while L >= i:
g.locks[L+1] = g.locks[L] locks[L+1] = locks[L]
dec L dec L
g.locks[i] = p locks[i] = p
inc(g.locksLen) inc(locksLen)
assert OrderedLocks(g) sysAssert OrderedLocks()
return return
# simply add to the end: # simply add to the end:
g.locks[g.locksLen] = p locks[locksLen] = p
inc(g.locksLen) inc(locksLen)
assert OrderedLocks(g) sysAssert OrderedLocks(g)
proc Acquire*(lock: var TLock) = proc Acquire*(lock: var TLock) =
## Acquires the lock `lock`. ## Acquires the lock `lock`.
when nodeadlocks: when nodeadlocks:
var g = ThisThread()
var p = addr(lock) var p = addr(lock)
var L = g.locksLen-1 var L = locksLen-1
var i = 0 var i = 0
while i <= L: while i <= L:
assert g.locks[i] != nil sysAssert locks[i] != nil
if g.locks[i] < p: inc(i) # in correct order if locks[i] < p: inc(i) # in correct order
elif g.locks[i] == p: return # thread already holds lock elif locks[i] == p: return # thread already holds lock
else: else:
# do the crazy stuff here: # do the crazy stuff here:
if g.locksLen >= len(g.locks): if locksLen >= len(locks):
raise newException(EResourceExhausted, raise newException(EResourceExhausted,
"cannot acquire additional lock") "cannot acquire additional lock")
while L >= i: while L >= i:
ReleaseSys(cast[ptr TSysLock](g.locks[L])[]) ReleaseSys(cast[ptr TSysLock](locks[L])[])
g.locks[L+1] = g.locks[L] locks[L+1] = locks[L]
dec L dec L
# acquire the current lock: # acquire the current lock:
AcquireSys(lock) AcquireSys(lock)
g.locks[i] = p locks[i] = p
inc(g.locksLen) inc(locksLen)
# acquire old locks in proper order again: # acquire old locks in proper order again:
L = g.locksLen-1 L = locksLen-1
inc i inc i
while i <= L: while i <= L:
AcquireSys(cast[ptr TSysLock](g.locks[i])[]) AcquireSys(cast[ptr TSysLock](locks[i])[])
inc(i) inc(i)
# DANGER: We can only modify this global var if we gained every lock! # DANGER: We can only modify this global var if we gained every lock!
# NO! We need an atomic increment. Crap. # NO! We need an atomic increment. Crap.
discard system.atomicInc(deadlocksPrevented, 1) discard system.atomicInc(deadlocksPrevented, 1)
assert OrderedLocks(g) sysAssert OrderedLocks(g)
return return
# simply add to the end: # simply add to the end:
if g.locksLen >= len(g.locks): if locksLen >= len(locks):
raise newException(EResourceExhausted, "cannot acquire additional lock") raise newException(EResourceExhausted, "cannot acquire additional lock")
AcquireSys(lock) AcquireSys(lock)
g.locks[g.locksLen] = p locks[locksLen] = p
inc(g.locksLen) inc(locksLen)
assert OrderedLocks(g) sysAssert OrderedLocks(g)
else: else:
AcquireSys(lock) AcquireSys(lock)
proc Release*(lock: var TLock) = proc Release*(lock: var TLock) =
## Releases the lock `lock`. ## Releases the lock `lock`.
when nodeadlocks: when nodeadlocks:
var g = ThisThread()
var p = addr(lock) var p = addr(lock)
var L = g.locksLen var L = locksLen
for i in countdown(L-1, 0): for i in countdown(L-1, 0):
if g.locks[i] == p: if locks[i] == p:
for j in i..L-2: g.locks[j] = g.locks[j+1] for j in i..L-2: locks[j] = locks[j+1]
dec g.locksLen dec locksLen
break break
ReleaseSys(lock) ReleaseSys(lock)
# ------------------------ message passing support ---------------------------
proc getInBoxMem*[TMsg](t: var TThread[TMsg]): pointer {.inline.} =
result = addr(t.inbox)
proc getInBoxMem*(): pointer {.inline.} =
result = addr(cast[PGcThread](ThreadVarGetValue(globalsSlot)).inbox)

View file

@ -14,7 +14,7 @@ var
proc doNothing() = nil proc doNothing() = nil
proc threadFunc(interval: tuple[a, b: int]) {.procvar.} = proc threadFunc(interval: tuple[a, b: int]) {.thread.} =
doNothing() doNothing()
for i in interval.a..interval.b: for i in interval.a..interval.b:
when nodeadlocks: when nodeadlocks:

View file

@ -1,7 +1,15 @@
High priority (version 0.8.12) High priority (version 0.8.12)
============================== ==============================
* test threads on windows; thread analysis needs to be even more restrictive! * test threads on windows
* implement message passing built-ins: channels/queues * test thread analysis:
var x = globalString # ok, copied; `x` is mine!
vs
var x = globalRef # read access, `x` is theirs!
* test message passing built-ins
* make threadvar efficient again on linux after testing
* document Nimrod's threads
* document Nimrod's two phase symbol lookup for generics
* bug: {:}.toTable[int, string]() * bug: {:}.toTable[int, string]()
@ -11,6 +19,7 @@ version 0.9.0
- add --deadlock_prevention:on|off switch? timeout for locks? - add --deadlock_prevention:on|off switch? timeout for locks?
- bug: tfFinal not passed to generic - bug: tfFinal not passed to generic
- bug: forward proc for generic seems broken - bug: forward proc for generic seems broken
- ``var T`` as a return type; easy to prove that location is not on the stack
- test the sort implementation again - test the sort implementation again
- warning for implicit openArray -> varargs convention - warning for implicit openArray -> varargs convention
- implement explicit varargs - implement explicit varargs
@ -74,7 +83,6 @@ Low priority
- ``when T is int`` for generic code - ``when T is int`` for generic code
- ``when validCode( proc () )`` for generic code - ``when validCode( proc () )`` for generic code
- macros: ``typecheck`` pragma; this allows transformations based on types!
- find a way for easy constructors and destructors; (destructors are much more - find a way for easy constructors and destructors; (destructors are much more
important than constructors) important than constructors)
- code generated for type information is wasteful - code generated for type information is wasteful

View file

@ -56,6 +56,7 @@ Additions
- Added ``lists`` module which contains generic linked lists. - Added ``lists`` module which contains generic linked lists.
- Added ``sets`` module which contains generic hash sets. - Added ``sets`` module which contains generic hash sets.
- Added ``tables`` module which contains generic hash tables. - Added ``tables`` module which contains generic hash tables.
- Added ``queues`` module which contains generic sequence based queues.
- Added ``intsets`` module which contains a specialized int set data type. - Added ``intsets`` module which contains a specialized int set data type.
- Added ``scgi`` module. - Added ``scgi`` module.
- Added ``smtp`` module. - Added ``smtp`` module.

View file

@ -39,7 +39,7 @@ srcdoc: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors"
srcdoc: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics" srcdoc: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics"
srcdoc: "impure/rdstdin;wrappers/zmq;wrappers/sphinx" srcdoc: "impure/rdstdin;wrappers/zmq;wrappers/sphinx"
srcdoc: "pure/collections/tables;pure/collections/sets;pure/collections/lists" srcdoc: "pure/collections/tables;pure/collections/sets;pure/collections/lists"
srcdoc: "pure/collections/intsets;pure/encodings" srcdoc: "pure/collections/intsets;pure/collections/queues;pure/encodings"
webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup"
webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc"