removed flawed thread analysis pass
This commit is contained in:
parent
39e4e3f205
commit
be6474af63
17 changed files with 23 additions and 476 deletions
|
|
@ -1311,6 +1311,10 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
|
||||||
result = t
|
result = t
|
||||||
while result.kind in kinds: result = lastSon(result)
|
while result.kind in kinds: result = lastSon(result)
|
||||||
|
|
||||||
|
proc isGCedMem*(t: PType): bool {.inline.} =
|
||||||
|
result = t.kind in {tyString, tyRef, tySequence} or
|
||||||
|
t.kind == tyProc and t.callConv == ccClosure
|
||||||
|
|
||||||
proc propagateToOwner*(owner, elem: PType) =
|
proc propagateToOwner*(owner, elem: PType) =
|
||||||
const HaveTheirOwnEmpty = {tySequence, tySet}
|
const HaveTheirOwnEmpty = {tySequence, tySet}
|
||||||
owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta})
|
owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta})
|
||||||
|
|
@ -1331,9 +1335,7 @@ proc propagateToOwner*(owner, elem: PType) =
|
||||||
owner.flags.incl tfHasMeta
|
owner.flags.incl tfHasMeta
|
||||||
|
|
||||||
if owner.kind != tyProc:
|
if owner.kind != tyProc:
|
||||||
if elem.kind in {tyString, tyRef, tySequence} or
|
if elem.isGCedMem or tfHasGCedMem in elem.flags:
|
||||||
elem.kind == tyProc and elem.callConv == ccClosure or
|
|
||||||
tfHasGCedMem in elem.flags:
|
|
||||||
owner.flags.incl tfHasGCedMem
|
owner.flags.incl tfHasGCedMem
|
||||||
|
|
||||||
proc rawAddSon*(father, son: PType) =
|
proc rawAddSon*(father, son: PType) =
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,6 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool =
|
||||||
of "forcebuild", "f": result = contains(gGlobalOptions, optForceFullMake)
|
of "forcebuild", "f": result = contains(gGlobalOptions, optForceFullMake)
|
||||||
of "warnings", "w": result = contains(gOptions, optWarns)
|
of "warnings", "w": result = contains(gOptions, optWarns)
|
||||||
of "hints": result = contains(gOptions, optHints)
|
of "hints": result = contains(gOptions, optHints)
|
||||||
of "threadanalysis": result = contains(gGlobalOptions, optThreadAnalysis)
|
|
||||||
of "stacktrace": result = contains(gOptions, optStackTrace)
|
of "stacktrace": result = contains(gOptions, optStackTrace)
|
||||||
of "linetrace": result = contains(gOptions, optLineTrace)
|
of "linetrace": result = contains(gOptions, optLineTrace)
|
||||||
of "debugger": result = contains(gOptions, optEndb)
|
of "debugger": result = contains(gOptions, optEndb)
|
||||||
|
|
@ -330,7 +329,6 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
|
||||||
of "warning": processSpecificNote(arg, wWarning, pass, info)
|
of "warning": processSpecificNote(arg, wWarning, pass, info)
|
||||||
of "hint": processSpecificNote(arg, wHint, pass, info)
|
of "hint": processSpecificNote(arg, wHint, pass, info)
|
||||||
of "hints": processOnOffSwitch({optHints}, arg, pass, info)
|
of "hints": processOnOffSwitch({optHints}, arg, pass, info)
|
||||||
of "threadanalysis": processOnOffSwitchG({optThreadAnalysis}, arg, pass, info)
|
|
||||||
of "stacktrace": processOnOffSwitch({optStackTrace}, arg, pass, info)
|
of "stacktrace": processOnOffSwitch({optStackTrace}, arg, pass, info)
|
||||||
of "linetrace": processOnOffSwitch({optLineTrace}, arg, pass, info)
|
of "linetrace": processOnOffSwitch({optLineTrace}, arg, pass, info)
|
||||||
of "debugger":
|
of "debugger":
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ type # please make sure we have under 32 options
|
||||||
optContext, # ideTools: 'context'
|
optContext, # ideTools: 'context'
|
||||||
optDef, # ideTools: 'def'
|
optDef, # ideTools: 'def'
|
||||||
optUsages, # ideTools: 'usages'
|
optUsages, # ideTools: 'usages'
|
||||||
optThreadAnalysis, # thread analysis pass
|
|
||||||
optTaintMode, # taint mode turned on
|
optTaintMode, # taint mode turned on
|
||||||
optTlsEmulation, # thread var emulation turned on
|
optTlsEmulation, # thread var emulation turned on
|
||||||
optGenIndex # generate index file for documentation;
|
optGenIndex # generate index file for documentation;
|
||||||
|
|
@ -95,7 +94,7 @@ var
|
||||||
optBoundsCheck, optOverflowCheck, optAssert, optWarns,
|
optBoundsCheck, optOverflowCheck, optAssert, optWarns,
|
||||||
optHints, optStackTrace, optLineTrace,
|
optHints, optStackTrace, optLineTrace,
|
||||||
optPatterns, optNilCheck}
|
optPatterns, optNilCheck}
|
||||||
gGlobalOptions*: TGlobalOptions = {optThreadAnalysis}
|
gGlobalOptions*: TGlobalOptions = {}
|
||||||
gExitcode*: int8
|
gExitcode*: int8
|
||||||
gCmd*: TCommands = cmdNone # the command
|
gCmd*: TCommands = cmdNone # the command
|
||||||
gSelectedGC* = gcRefc # the selected GC
|
gSelectedGC* = gcRefc # the selected GC
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
import
|
import
|
||||||
strutils, lists, options, ast, astalgo, llstream, msgs, platform, os,
|
strutils, lists, options, ast, astalgo, llstream, msgs, platform, os,
|
||||||
condsyms, idents, renderer, types, extccomp, math, magicsys, nversion,
|
condsyms, idents, renderer, types, extccomp, math, magicsys, nversion,
|
||||||
nimsets, syntaxes, times, rodread, semthreads, idgen
|
nimsets, syntaxes, times, rodread, idgen
|
||||||
|
|
||||||
type
|
type
|
||||||
TPassContext* = object of TObject # the pass's context
|
TPassContext* = object of TObject # the pass's context
|
||||||
|
|
@ -74,7 +74,8 @@ proc astNeeded*(s: PSym): bool =
|
||||||
({sfCompilerProc, sfCompileTime} * s.flags == {}) and
|
({sfCompilerProc, sfCompileTime} * s.flags == {}) and
|
||||||
(s.typ.callConv != ccInline) and
|
(s.typ.callConv != ccInline) and
|
||||||
(s.ast.sons[genericParamsPos].kind == nkEmpty):
|
(s.ast.sons[genericParamsPos].kind == nkEmpty):
|
||||||
result = semthreads.needsGlobalAnalysis()
|
result = false
|
||||||
|
# XXX this doesn't really make sense with excessive CTFE
|
||||||
else:
|
else:
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import
|
||||||
wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
|
wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
|
||||||
magicsys, parser, nversion, nimsets, semfold, importer,
|
magicsys, parser, nversion, nimsets, semfold, importer,
|
||||||
procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
|
procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
|
||||||
semthreads, intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting,
|
intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting,
|
||||||
evaltempl, patterns, parampatterns, sempass2, pretty, semmacrosanity
|
evaltempl, patterns, parampatterns, sempass2, pretty, semmacrosanity
|
||||||
|
|
||||||
# implementation
|
# implementation
|
||||||
|
|
@ -416,11 +416,6 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
|
||||||
else: result = ast.emptyNode
|
else: result = ast.emptyNode
|
||||||
#if gCmd == cmdIdeTools: findSuggest(c, n)
|
#if gCmd == cmdIdeTools: findSuggest(c, n)
|
||||||
|
|
||||||
proc checkThreads(c: PContext) =
|
|
||||||
if not needsGlobalAnalysis(): return
|
|
||||||
for i in 0 .. c.threadEntries.len-1:
|
|
||||||
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)
|
||||||
closeScope(c) # close module's scope
|
closeScope(c) # close module's scope
|
||||||
|
|
@ -431,7 +426,6 @@ proc myClose(context: PPassContext, n: PNode): PNode =
|
||||||
addCodeForGenerics(c, result)
|
addCodeForGenerics(c, result)
|
||||||
if c.module.ast != nil:
|
if c.module.ast != nil:
|
||||||
result.add(c.module.ast)
|
result.add(c.module.ast)
|
||||||
checkThreads(c)
|
|
||||||
popOwner()
|
popOwner()
|
||||||
popProcCon(c)
|
popProcCon(c)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ type
|
||||||
# can access private object fields
|
# can access private object fields
|
||||||
instCounter*: int # to prevent endless instantiations
|
instCounter*: int # to prevent endless instantiations
|
||||||
|
|
||||||
threadEntries*: TSymSeq # list of thread entries to check
|
|
||||||
ambiguousSymbols*: TIntSet # ids of all ambiguous symbols (cannot
|
ambiguousSymbols*: TIntSet # ids of all ambiguous symbols (cannot
|
||||||
# store this info in the syms themselves!)
|
# store this info in the syms themselves!)
|
||||||
inTypeClass*: int # > 0 if we are in a user-defined type class
|
inTypeClass*: int # > 0 if we are in a user-defined type class
|
||||||
|
|
@ -170,7 +169,6 @@ proc newContext(module: PSym): PContext =
|
||||||
append(result.optionStack, newOptionEntry())
|
append(result.optionStack, newOptionEntry())
|
||||||
result.module = module
|
result.module = module
|
||||||
result.friendModule = module
|
result.friendModule = module
|
||||||
result.threadEntries = @[]
|
|
||||||
result.converters = @[]
|
result.converters = @[]
|
||||||
result.patterns = @[]
|
result.patterns = @[]
|
||||||
result.includedFiles = initIntSet()
|
result.includedFiles = initIntSet()
|
||||||
|
|
|
||||||
|
|
@ -127,9 +127,6 @@ 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() and
|
|
||||||
s.ast.sons[genericParamsPos].kind == nkEmpty:
|
|
||||||
c.threadEntries.add(s)
|
|
||||||
|
|
||||||
proc instGenericContainer(c: PContext, info: TLineInfo, header: PType,
|
proc instGenericContainer(c: PContext, info: TLineInfo, header: PType,
|
||||||
allowMetaTypes = false): PType =
|
allowMetaTypes = false): PType =
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ proc useVar(a: PEffects, n: PNode) =
|
||||||
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar:
|
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar:
|
||||||
when trackGlobals:
|
when trackGlobals:
|
||||||
a.addUse(copyNode(n))
|
a.addUse(copyNode(n))
|
||||||
if tfHasGCedMem in s.typ.flags:
|
if tfHasGCedMem in s.typ.flags or s.typ.isGCedMem:
|
||||||
message(n.info, warnGcUnsafe, renderTree(n))
|
message(n.info, warnGcUnsafe, renderTree(n))
|
||||||
a.gcUnsafe = true
|
a.gcUnsafe = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,390 +0,0 @@
|
||||||
#
|
|
||||||
#
|
|
||||||
# The Nimrod Compiler
|
|
||||||
# (c) Copyright 2013 Andreas Rumpf
|
|
||||||
#
|
|
||||||
# See the file "copying.txt", included in this
|
|
||||||
# distribution, for details about the copyright.
|
|
||||||
#
|
|
||||||
|
|
||||||
## Semantic analysis that deals with threads: Possible race conditions should
|
|
||||||
## be reported some day.
|
|
||||||
##
|
|
||||||
##
|
|
||||||
## ========================
|
|
||||||
## No heap sharing analysis
|
|
||||||
## ========================
|
|
||||||
##
|
|
||||||
## The only crucial operation that can violate the heap invariants is the
|
|
||||||
## write access. The analysis needs to distinguish between 'unknown', 'mine',
|
|
||||||
## and 'theirs' memory and pointers. Assignments 'whatever <- unknown' are
|
|
||||||
## invalid, and so are 'theirs <- whatever' but not 'mine <- theirs'. Since
|
|
||||||
## strings and sequences are heap allocated they are affected too:
|
|
||||||
##
|
|
||||||
## .. code-block:: nimrod
|
|
||||||
## proc p() =
|
|
||||||
## global = "alloc this string" # ugh!
|
|
||||||
##
|
|
||||||
## Thus the analysis is concerned with any type that contains a GC'ed
|
|
||||||
## reference...
|
|
||||||
## 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
|
|
||||||
## reject any write access to a global variable which contains GC'ed data.
|
|
||||||
## Thanks to the write barrier of the GC, this is exactly what needs to be
|
|
||||||
## 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
|
|
||||||
## write access to heap locations and global variables are critical and need
|
|
||||||
## to be checked. Access via 'var' parameters is no problem to analyse since
|
|
||||||
## we need the arguments' locations in the analysis.
|
|
||||||
##
|
|
||||||
## However, this is tricky:
|
|
||||||
##
|
|
||||||
## var x = globalVar # 'x' points to 'theirs'
|
|
||||||
## while true:
|
|
||||||
## globalVar = x # NOT OK: 'theirs <- theirs' invalid due to
|
|
||||||
## # write barrier!
|
|
||||||
## x = "new string" # ugh: 'x is toUnknown'!
|
|
||||||
##
|
|
||||||
## --> Solution: toUnknown is never allowed anywhere!
|
|
||||||
##
|
|
||||||
##
|
|
||||||
## Beware that the same proc might need to be
|
|
||||||
## analysed multiple times! Oh and watch out for recursion! Recursion is handled
|
|
||||||
## by a stack of symbols that we are processing, if we come back to the same
|
|
||||||
## symbol, we have to skip this check (assume no error in the recursive case).
|
|
||||||
## However this is wrong. We need to check for the particular combination
|
|
||||||
## of (procsym, threadOwner(arg1), threadOwner(arg2), ...)!
|
|
||||||
|
|
||||||
import
|
|
||||||
ast, astalgo, strutils, hashes, options, msgs, idents, types, os,
|
|
||||||
renderer, tables, rodread
|
|
||||||
|
|
||||||
type
|
|
||||||
TThreadOwner = enum
|
|
||||||
toUndefined, # not computed yet
|
|
||||||
toVoid, # no return type
|
|
||||||
toNil, # cycle in computation or nil: can be overwritten
|
|
||||||
toTheirs, # some other heap
|
|
||||||
toMine # mine heap
|
|
||||||
|
|
||||||
TCall = object {.pure.}
|
|
||||||
callee: PSym # what if callee is an indirect call?
|
|
||||||
args: seq[TThreadOwner]
|
|
||||||
|
|
||||||
PProcCtx = ref TProcCtx
|
|
||||||
TProcCtx = object {.pure.}
|
|
||||||
nxt: PProcCtx # can be stacked
|
|
||||||
mapping: tables.TTable[int, TThreadOwner] # int = symbol ID
|
|
||||||
owner: PSym # current owner
|
|
||||||
|
|
||||||
var
|
|
||||||
computed = tables.initTable[TCall, TThreadOwner]()
|
|
||||||
|
|
||||||
proc hash(c: TCall): THash =
|
|
||||||
result = hash(c.callee.id)
|
|
||||||
for a in items(c.args): result = result !& hash(ord(a))
|
|
||||||
result = !$result
|
|
||||||
|
|
||||||
proc `==`(a, b: TCall): bool =
|
|
||||||
if a.callee != b.callee: return
|
|
||||||
if a.args.len != b.args.len: return
|
|
||||||
for i in 0..a.args.len-1:
|
|
||||||
if a.args[i] != b.args[i]: return
|
|
||||||
result = true
|
|
||||||
|
|
||||||
proc newProcCtx(owner: PSym): PProcCtx =
|
|
||||||
assert owner != nil
|
|
||||||
new(result)
|
|
||||||
result.mapping = tables.initTable[int, TThreadOwner]()
|
|
||||||
result.owner = owner
|
|
||||||
|
|
||||||
proc analyse(c: PProcCtx, n: PNode): TThreadOwner
|
|
||||||
|
|
||||||
proc analyseSym(c: PProcCtx, n: PNode): TThreadOwner =
|
|
||||||
var v = n.sym
|
|
||||||
result = c.mapping[v.id]
|
|
||||||
if result != toUndefined: return
|
|
||||||
case v.kind
|
|
||||||
of skVar, skForVar, skLet, skResult:
|
|
||||||
result = toNil
|
|
||||||
if sfGlobal in v.flags:
|
|
||||||
if sfThread in v.flags:
|
|
||||||
result = toMine
|
|
||||||
elif containsGarbageCollectedRef(v.typ):
|
|
||||||
result = toTheirs
|
|
||||||
of skTemp: result = toNil
|
|
||||||
of skConst: result = toMine
|
|
||||||
of skParam:
|
|
||||||
result = c.mapping[v.id]
|
|
||||||
if result == toUndefined:
|
|
||||||
internalError(n.info, "param not set: " & v.name.s)
|
|
||||||
else:
|
|
||||||
result = toNil
|
|
||||||
c.mapping[v.id] = result
|
|
||||||
|
|
||||||
proc lvalueSym(n: PNode): PNode =
|
|
||||||
result = n
|
|
||||||
while result.kind in {nkDotExpr, nkCheckedFieldExpr,
|
|
||||||
nkBracketExpr, nkDerefExpr, nkHiddenDeref}:
|
|
||||||
result = result.sons[0]
|
|
||||||
|
|
||||||
proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) =
|
|
||||||
if owner notin {toNil, toMine, toTheirs}:
|
|
||||||
internalError(n.info, "writeAccess: " & $owner)
|
|
||||||
var a = lvalueSym(n)
|
|
||||||
if a.kind == nkSym:
|
|
||||||
var v = a.sym
|
|
||||||
var lastOwner = analyseSym(c, a)
|
|
||||||
case lastOwner
|
|
||||||
of toNil:
|
|
||||||
# fine, toNil can be overwritten
|
|
||||||
var newOwner: TThreadOwner
|
|
||||||
if sfGlobal in v.flags:
|
|
||||||
newOwner = owner
|
|
||||||
elif containsTyRef(v.typ):
|
|
||||||
# ``var local = gNode`` --> ok, but ``local`` is theirs!
|
|
||||||
newOwner = owner
|
|
||||||
else:
|
|
||||||
# ``var local = gString`` --> string copy: ``local`` is mine!
|
|
||||||
newOwner = toMine
|
|
||||||
# XXX BUG what if the tuple contains both ``tyRef`` and ``tyString``?
|
|
||||||
c.mapping[v.id] = newOwner
|
|
||||||
of toVoid, toUndefined: internalError(n.info, "writeAccess")
|
|
||||||
of toTheirs: message(n.info, warnWriteToForeignHeap)
|
|
||||||
of toMine:
|
|
||||||
if lastOwner != owner and owner != toNil:
|
|
||||||
message(n.info, warnDifferentHeaps)
|
|
||||||
else:
|
|
||||||
# we could not backtrack to a concrete symbol, but that's fine:
|
|
||||||
var lastOwner = analyse(c, n)
|
|
||||||
case lastOwner
|
|
||||||
of toNil: discard # fine, toNil can be overwritten
|
|
||||||
of toVoid, toUndefined: internalError(n.info, "writeAccess")
|
|
||||||
of toTheirs: message(n.info, warnWriteToForeignHeap)
|
|
||||||
of toMine:
|
|
||||||
if lastOwner != owner and owner != toNil:
|
|
||||||
message(n.info, warnDifferentHeaps)
|
|
||||||
|
|
||||||
proc analyseAssign(c: PProcCtx, le, ri: PNode) =
|
|
||||||
var y = analyse(c, ri) # read access; ok
|
|
||||||
writeAccess(c, le, y)
|
|
||||||
|
|
||||||
proc analyseAssign(c: PProcCtx, n: PNode) =
|
|
||||||
analyseAssign(c, n.sons[0], n.sons[1])
|
|
||||||
|
|
||||||
proc analyseCall(c: PProcCtx, n: PNode): TThreadOwner =
|
|
||||||
var prc = n[0].sym
|
|
||||||
var newCtx = newProcCtx(prc)
|
|
||||||
var call: TCall
|
|
||||||
call.callee = prc
|
|
||||||
newSeq(call.args, n.len-1)
|
|
||||||
for i in 1..n.len-1:
|
|
||||||
call.args[i-1] = analyse(c, n[i])
|
|
||||||
if not computed.hasKey(call):
|
|
||||||
computed[call] = toUndefined # we are computing it
|
|
||||||
let prctyp = skipTypes(prc.typ, abstractInst).n
|
|
||||||
for i in 1.. prctyp.len-1:
|
|
||||||
var formal = prctyp.sons[i].sym
|
|
||||||
newCtx.mapping[formal.id] = call.args[i-1]
|
|
||||||
pushInfoContext(n.info)
|
|
||||||
result = analyse(newCtx, prc.getBody)
|
|
||||||
if prc.ast.sons[bodyPos].kind == nkEmpty and
|
|
||||||
{sfNoSideEffect, sfThread, sfImportc} * prc.flags == {}:
|
|
||||||
message(n.info, warnAnalysisLoophole, renderTree(n))
|
|
||||||
if result == toUndefined: result = toNil
|
|
||||||
if prc.typ.sons[0] != nil:
|
|
||||||
if prc.ast.len > resultPos:
|
|
||||||
result = newCtx.mapping[prc.ast.sons[resultPos].sym.id]
|
|
||||||
# if the proc body does not set 'result', nor 'return's something
|
|
||||||
# explicitely, it returns a binary zero, so 'toNil' is correct:
|
|
||||||
if result == toUndefined: result = toNil
|
|
||||||
else:
|
|
||||||
result = toNil
|
|
||||||
else:
|
|
||||||
result = toVoid
|
|
||||||
computed[call] = result
|
|
||||||
popInfoContext()
|
|
||||||
else:
|
|
||||||
result = computed[call]
|
|
||||||
if result == toUndefined:
|
|
||||||
# ugh, cycle! We are already computing it but don't know the
|
|
||||||
# outcome yet...
|
|
||||||
if prc.typ.sons[0] == nil: result = toVoid
|
|
||||||
else: result = toNil
|
|
||||||
|
|
||||||
proc analyseVarTuple(c: PProcCtx, n: PNode) =
|
|
||||||
if n.kind != nkVarTuple: internalError(n.info, "analyseVarTuple")
|
|
||||||
var L = n.len
|
|
||||||
for i in countup(0, L-3): analyseAssign(c, n.sons[i], n.sons[L-1])
|
|
||||||
|
|
||||||
proc analyseSingleVar(c: PProcCtx, a: PNode) =
|
|
||||||
if a.sons[2].kind != nkEmpty: analyseAssign(c, a.sons[0], a.sons[2])
|
|
||||||
|
|
||||||
proc analyseVarSection(c: PProcCtx, n: PNode): TThreadOwner =
|
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
|
||||||
var a = n.sons[i]
|
|
||||||
if a.kind == nkCommentStmt: continue
|
|
||||||
if a.kind == nkIdentDefs:
|
|
||||||
#assert(a.sons[0].kind == nkSym); also valid for after
|
|
||||||
# closure transformation:
|
|
||||||
analyseSingleVar(c, a)
|
|
||||||
else:
|
|
||||||
analyseVarTuple(c, a)
|
|
||||||
result = toVoid
|
|
||||||
|
|
||||||
proc analyseConstSection(c: PProcCtx, t: PNode): TThreadOwner =
|
|
||||||
for i in countup(0, sonsLen(t) - 1):
|
|
||||||
var it = t.sons[i]
|
|
||||||
if it.kind == nkCommentStmt: continue
|
|
||||||
if it.kind != nkConstDef: internalError(t.info, "analyseConstSection")
|
|
||||||
if sfFakeConst in it.sons[0].sym.flags: analyseSingleVar(c, it)
|
|
||||||
result = toVoid
|
|
||||||
|
|
||||||
template aggregateOwner(result, ana: expr) =
|
|
||||||
var a = ana # eval once
|
|
||||||
if result != a:
|
|
||||||
if result == toNil: result = a
|
|
||||||
elif a != toNil: message(n.info, warnDifferentHeaps)
|
|
||||||
|
|
||||||
proc analyseArgs(c: PProcCtx, n: PNode, start = 1) =
|
|
||||||
for i in start..n.len-1: discard analyse(c, n[i])
|
|
||||||
|
|
||||||
proc analyseOp(c: PProcCtx, n: PNode): TThreadOwner =
|
|
||||||
if n[0].kind != nkSym or n[0].sym.kind != skProc:
|
|
||||||
if {tfNoSideEffect, tfThread} * n[0].typ.flags == {}:
|
|
||||||
message(n.info, warnAnalysisLoophole, renderTree(n))
|
|
||||||
result = toNil
|
|
||||||
else:
|
|
||||||
var prc = n[0].sym
|
|
||||||
case prc.magic
|
|
||||||
of mNone:
|
|
||||||
if sfSystemModule in prc.owner.flags:
|
|
||||||
# System module proc does no harm :-)
|
|
||||||
analyseArgs(c, n)
|
|
||||||
if prc.typ.sons[0] == nil: result = toVoid
|
|
||||||
else: result = toNil
|
|
||||||
else:
|
|
||||||
result = analyseCall(c, n)
|
|
||||||
of mNew, mNewFinalize, mNewSeq, mSetLengthStr, mSetLengthSeq,
|
|
||||||
mAppendSeqElem, mReset, mAppendStrCh, mAppendStrStr:
|
|
||||||
writeAccess(c, n[1], toMine)
|
|
||||||
result = toVoid
|
|
||||||
of mSwap:
|
|
||||||
var a = analyse(c, n[2])
|
|
||||||
writeAccess(c, n[1], a)
|
|
||||||
writeAccess(c, n[2], a)
|
|
||||||
result = toVoid
|
|
||||||
of mIntToStr, mInt64ToStr, mFloatToStr, mBoolToStr, mCharToStr,
|
|
||||||
mCStrToStr, mStrToStr, mEnumToStr,
|
|
||||||
mConStrStr, mConArrArr, mConArrT,
|
|
||||||
mConTArr, mConTT, mSlice,
|
|
||||||
mRepr, mArrToSeq, mCopyStr, mCopyStrLast,
|
|
||||||
mNewString, mNewStringOfCap:
|
|
||||||
analyseArgs(c, n)
|
|
||||||
result = toMine
|
|
||||||
else:
|
|
||||||
# don't recurse, but check args:
|
|
||||||
analyseArgs(c, n)
|
|
||||||
if prc.typ.sons[0] == nil: result = toVoid
|
|
||||||
else: result = toNil
|
|
||||||
|
|
||||||
proc analyse(c: PProcCtx, n: PNode): TThreadOwner =
|
|
||||||
case n.kind
|
|
||||||
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand,
|
|
||||||
nkCallStrLit, nkHiddenCallConv:
|
|
||||||
result = analyseOp(c, n)
|
|
||||||
of nkAsgn, nkFastAsgn:
|
|
||||||
analyseAssign(c, n)
|
|
||||||
result = toVoid
|
|
||||||
of nkSym: result = analyseSym(c, n)
|
|
||||||
of nkEmpty, nkNone: result = toVoid
|
|
||||||
of nkNilLit, nkCharLit..nkFloat64Lit: result = toNil
|
|
||||||
of nkStrLit..nkTripleStrLit: result = toMine
|
|
||||||
of nkDotExpr, nkBracketExpr, nkDerefExpr, nkHiddenDeref:
|
|
||||||
# field access:
|
|
||||||
# pointer deref or array access:
|
|
||||||
result = analyse(c, n.sons[0])
|
|
||||||
of nkBind: result = analyse(c, n.sons[0])
|
|
||||||
of nkPar, nkCurly, nkBracket, nkRange:
|
|
||||||
# container construction:
|
|
||||||
result = toNil # nothing until later
|
|
||||||
for i in 0..n.len-1: aggregateOwner(result, analyse(c, n[i]))
|
|
||||||
of nkObjConstr:
|
|
||||||
if n.typ != nil and containsGarbageCollectedRef(n.typ):
|
|
||||||
result = toMine
|
|
||||||
else:
|
|
||||||
result = toNil # nothing until later
|
|
||||||
for i in 1..n.len-1: aggregateOwner(result, analyse(c, n[i]))
|
|
||||||
of nkAddr, nkHiddenAddr:
|
|
||||||
var a = lvalueSym(n)
|
|
||||||
if a.kind == nkSym:
|
|
||||||
result = analyseSym(c, a)
|
|
||||||
assert result in {toNil, toMine, toTheirs}
|
|
||||||
if result == toNil:
|
|
||||||
# assume toMine here for consistency:
|
|
||||||
c.mapping[a.sym.id] = toMine
|
|
||||||
result = toMine
|
|
||||||
else:
|
|
||||||
# should never really happen:
|
|
||||||
result = analyse(c, n.sons[0])
|
|
||||||
of nkIfExpr:
|
|
||||||
result = toNil
|
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
|
||||||
var it = n.sons[i]
|
|
||||||
if it.len == 2:
|
|
||||||
discard analyse(c, it.sons[0])
|
|
||||||
aggregateOwner(result, analyse(c, it.sons[1]))
|
|
||||||
else:
|
|
||||||
aggregateOwner(result, analyse(c, it.sons[0]))
|
|
||||||
of nkStmtListExpr, nkBlockExpr:
|
|
||||||
var n = if n.kind == nkBlockExpr: n.sons[1] else: n
|
|
||||||
var L = sonsLen(n)
|
|
||||||
for i in countup(0, L-2): discard analyse(c, n.sons[i])
|
|
||||||
if L > 0: result = analyse(c, n.sons[L-1])
|
|
||||||
else: result = toVoid
|
|
||||||
of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast:
|
|
||||||
result = analyse(c, n.sons[1])
|
|
||||||
of nkStringToCString, nkCStringToString, nkChckRangeF, nkChckRange64,
|
|
||||||
nkChckRange, nkCheckedFieldExpr, nkObjDownConv,
|
|
||||||
nkObjUpConv:
|
|
||||||
result = analyse(c, n.sons[0])
|
|
||||||
of nkRaiseStmt:
|
|
||||||
var a = analyse(c, n.sons[0])
|
|
||||||
if a != toMine: message(n.info, warnDifferentHeaps)
|
|
||||||
result = toVoid
|
|
||||||
of nkVarSection, nkLetSection: result = analyseVarSection(c, n)
|
|
||||||
of nkConstSection: result = analyseConstSection(c, n)
|
|
||||||
of nkTypeSection, nkCommentStmt: result = toVoid
|
|
||||||
of nkIfStmt, nkWhileStmt, nkTryStmt, nkCaseStmt, nkStmtList, nkBlockStmt,
|
|
||||||
nkElifBranch, nkElse, nkExceptBranch, nkOfBranch, nkFinally:
|
|
||||||
for i in 0 .. <n.len: discard analyse(c, n[i])
|
|
||||||
result = toVoid
|
|
||||||
of nkBreakStmt, nkContinueStmt: result = toVoid
|
|
||||||
of nkReturnStmt, nkDiscardStmt:
|
|
||||||
if n.sons[0].kind != nkEmpty: result = analyse(c, n.sons[0])
|
|
||||||
else: result = toVoid
|
|
||||||
of nkLambdaKinds, nkClosure:
|
|
||||||
result = toMine
|
|
||||||
of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef,
|
|
||||||
nkConverterDef, nkMacroDef, nkTemplateDef,
|
|
||||||
nkGotoState, nkState, nkBreakState, nkType, nkIdent:
|
|
||||||
result = toVoid
|
|
||||||
of nkExprColonExpr:
|
|
||||||
result = analyse(c, n.sons[1])
|
|
||||||
else: internalError(n.info, "analysis not implemented for: " & $n.kind)
|
|
||||||
|
|
||||||
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
|
|
||||||
# the input is copied and belongs to the thread:
|
|
||||||
c.mapping[formal.id] = toMine
|
|
||||||
discard analyse(c, prc.getBody)
|
|
||||||
|
|
||||||
proc needsGlobalAnalysis*: bool =
|
|
||||||
result = gGlobalOptions * {optThreads, optThreadAnalysis} ==
|
|
||||||
{optThreads, optThreadAnalysis}
|
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ proc sharedPtrCheck(info: TLineInfo, t: PType) =
|
||||||
if t.sons[0].sym.magic in {mShared, mGuarded}:
|
if t.sons[0].sym.magic in {mShared, mGuarded}:
|
||||||
incl(t.flags, tfShared)
|
incl(t.flags, tfShared)
|
||||||
if t.sons[0].sym.magic == mGuarded: incl(t.flags, tfGuarded)
|
if t.sons[0].sym.magic == mGuarded: incl(t.flags, tfGuarded)
|
||||||
if tfHasGCedMem in t.flags:
|
if tfHasGCedMem in t.flags or t.isGCedMem:
|
||||||
localError(info, errGenerated,
|
localError(info, errGenerated,
|
||||||
"shared memory may not refer to GC'ed thread local memory")
|
"shared memory may not refer to GC'ed thread local memory")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -538,7 +538,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||||
add(prag, "noSideEffect")
|
add(prag, "noSideEffect")
|
||||||
if tfThread in t.flags:
|
if tfThread in t.flags:
|
||||||
addSep(prag)
|
addSep(prag)
|
||||||
add(prag, "thread")
|
add(prag, "gcsafe")
|
||||||
if len(prag) != 0: add(result, "{." & prag & ".}")
|
if len(prag) != 0: add(result, "{." & prag & ".}")
|
||||||
of tyVarargs, tyIter:
|
of tyVarargs, tyIter:
|
||||||
result = typeToStr[t.kind] % typeToString(t.sons[0])
|
result = typeToStr[t.kind] % typeToString(t.sons[0])
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ Advanced options:
|
||||||
--lineDir:on|off generation of #line directive on|off
|
--lineDir:on|off generation of #line directive on|off
|
||||||
--embedsrc embeds the original source code as comments
|
--embedsrc embeds the original source code as comments
|
||||||
in the generated output
|
in the generated output
|
||||||
--threadanalysis:on|off turn thread analysis on|off
|
|
||||||
--tlsEmulation:on|off turn thread local storage emulation on|off
|
--tlsEmulation:on|off turn thread local storage emulation on|off
|
||||||
--taintMode:on|off turn taint mode on|off
|
--taintMode:on|off turn taint mode on|off
|
||||||
--symbolFiles:on|off turn symbol files on|off (experimental)
|
--symbolFiles:on|off turn symbol files on|off (experimental)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
discard """
|
discard """
|
||||||
line: 21
|
line: 21
|
||||||
errormsg: "invalid type: 'TTable[string, proc (string)]'"
|
errormsg: "invalid type: 'TTable[string, proc (string){.gcsafe.}]'"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tables
|
import tables
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
discard """
|
discard """
|
||||||
line: 18
|
line: 18
|
||||||
errormsg: "type mismatch: got (proc (TScgi) | proc (PAsyncSocket, PStringTable, string))"
|
errormsg: "type mismatch: got (proc (TScgi) | proc (PAsyncSocket, PStringTable, string){.gcsafe.})"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#bug #442
|
#bug #442
|
||||||
import scgi, sockets, asyncio, strtabs
|
import scgi, sockets, asyncio, strtabs
|
||||||
proc handleSCGIRequest[TScgi: TScgiState | PAsyncScgiState](s: TScgi) =
|
proc handleSCGIRequest[TScgi: TScgiState | PAsyncScgiState](s: TScgi) =
|
||||||
nil
|
discard
|
||||||
proc handleSCGIRequest(client: PAsyncSocket, headers: PStringTable,
|
proc handleSCGIRequest(client: PAsyncSocket, headers: PStringTable,
|
||||||
input: string) =
|
input: string) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
proc test(handle: proc (client: PAsyncSocket, headers: PStringTable,
|
proc test(handle: proc (client: PAsyncSocket, headers: PStringTable,
|
||||||
input: string), b: int) =
|
input: string), b: int) =
|
||||||
nil
|
discard
|
||||||
|
|
||||||
test(handleSCGIRequest)
|
test(handleSCGIRequest)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
discard """
|
discard """
|
||||||
line: 12
|
line: 12
|
||||||
errormsg: "type mismatch: got (proc (int){.closure.})"
|
errormsg: "type mismatch: got (proc (int){.closure, gcsafe.})"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc ugh[T](x: T) {.closure.} =
|
proc ugh[T](x: T) {.closure.} =
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
discard """
|
discard """
|
||||||
file: "tthreadanalysis2.nim"
|
file: "tthreadanalysis2.nim"
|
||||||
line: 42
|
line: 37
|
||||||
errormsg: "write to foreign heap"
|
errormsg: "'threadFunc' is not GC-safe"
|
||||||
cmd: "nimrod $target --hints:on --threads:on $options $file"
|
cmd: "nimrod $target --hints:on --threads:on $options $file"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ import os
|
||||||
var
|
var
|
||||||
thr: array [0..5, TThread[tuple[a, b: int]]]
|
thr: array [0..5, TThread[tuple[a, b: int]]]
|
||||||
|
|
||||||
proc doNothing() = nil
|
proc doNothing() = discard
|
||||||
|
|
||||||
type
|
type
|
||||||
PNode = ref TNode
|
PNode = ref TNode
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
discard """
|
|
||||||
file: "tthreadanalysis3.nim"
|
|
||||||
line: 35
|
|
||||||
errormsg: "write to foreign heap"
|
|
||||||
cmd: "nimrod $target --hints:on --threads:on $options $file"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
var
|
|
||||||
thr: array [0..5, TThread[tuple[a, b: int]]]
|
|
||||||
|
|
||||||
proc doNothing() = nil
|
|
||||||
|
|
||||||
type
|
|
||||||
PNode = ref TNode
|
|
||||||
TNode = object {.pure.}
|
|
||||||
le, ri: PNode
|
|
||||||
data: string
|
|
||||||
|
|
||||||
var
|
|
||||||
root: PNode
|
|
||||||
|
|
||||||
proc buildTree(depth: int): PNode =
|
|
||||||
if depth == 3: return nil
|
|
||||||
new(result)
|
|
||||||
result.le = buildTree(depth-1)
|
|
||||||
result.ri = buildTree(depth-1)
|
|
||||||
result.data = $depth
|
|
||||||
|
|
||||||
proc echoLeTree(n: PNode) =
|
|
||||||
var it = n
|
|
||||||
while it != nil:
|
|
||||||
echo it.data
|
|
||||||
it = it.le
|
|
||||||
|
|
||||||
proc threadFunc(interval: tuple[a, b: int]) {.thread.} =
|
|
||||||
doNothing()
|
|
||||||
for i in interval.a..interval.b:
|
|
||||||
var r = buildTree(i)
|
|
||||||
echoLeTree(r) # for local data
|
|
||||||
echoLeTree(root) # and the same for foreign data :-)
|
|
||||||
|
|
||||||
proc main =
|
|
||||||
root = buildTree(5)
|
|
||||||
for i in 0..high(thr):
|
|
||||||
createThread(thr[i], threadFunc, (i*100, i*100+50))
|
|
||||||
joinThreads(thr)
|
|
||||||
|
|
||||||
main()
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue