diff options
Diffstat (limited to 'compiler/sempass2.nim')
-rw-r--r-- | compiler/sempass2.nim | 236 |
1 files changed, 173 insertions, 63 deletions
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index acc2425f1..94a3b49e3 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1,6 +1,6 @@ # # -# The Nimrod Compiler +# The Nim Compiler # (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this @@ -57,26 +57,114 @@ discard """ c() --> we need a stack of scopes for this analysis -""" -const trackGlobals = false ## we don't need it for now + # XXX enhance the algorithm to care about 'dirty' expressions: + lock a[i].L: + inc i # mark 'i' dirty + lock a[j].L: + access a[i], a[j] # --> reject a[i] +""" type TEffects = object exc: PNode # stack of exceptions tags: PNode # list of tags - uses: PNode # list of used global variables bottom: int owner: PSym init: seq[int] # list of initialized variables guards: TModel # nested guards locked: seq[PNode] # locked locations - gcUnsafe, isRecursive: bool + gcUnsafe, isRecursive, isToplevel: bool + maxLockLevel, currLockLevel: TLockLevel PEffects = var TEffects +proc `<`(a, b: TLockLevel): bool {.borrow.} +proc `<=`(a, b: TLockLevel): bool {.borrow.} +proc `==`(a, b: TLockLevel): bool {.borrow.} +proc max(a, b: TLockLevel): TLockLevel {.borrow.} + proc isLocalVar(a: PEffects, s: PSym): bool = s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner +proc getLockLevel(t: PType): TLockLevel = + var t = t + # tyGenericInst(TLock {tyGenericBody}, tyStatic, tyObject): + if t.kind == tyGenericInst and t.len == 3: t = t.sons[1] + if t.kind == tyStatic and t.n != nil and t.n.kind in {nkCharLit..nkInt64Lit}: + result = t.n.intVal.TLockLevel + +proc lockLocations(a: PEffects; pragma: PNode) = + if pragma.kind != nkExprColonExpr: + localError(pragma.info, errGenerated, "locks pragma without argument") + return + var firstLL = TLockLevel(-1'i16) + for x in pragma[1]: + let thisLL = getLockLevel(x.typ) + if thisLL != 0.TLockLevel: + if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel: + localError(x.info, "invalid lock level: " & $thisLL) + elif firstLL < 0.TLockLevel: firstLL = thisLL + elif firstLL != thisLL: + localError(x.info, errGenerated, + "multi-lock requires the same static lock level for every operand") + a.maxLockLevel = max(a.maxLockLevel, firstLL) + a.locked.add x + if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel: + if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL: + localError(pragma.info, errGenerated, + "invalid nested locking") + a.currLockLevel = firstLL + +proc guardGlobal(a: PEffects; n: PNode; guard: PSym) = + # check whether the corresponding lock is held: + for L in a.locked: + if L.kind == nkSym and L.sym == guard: return + # we allow accesses nevertheless in top level statements for + # easier initialization: + #if a.isTopLevel: + # message(n.info, warnUnguardedAccess, renderTree(n)) + #else: + if not a.isTopLevel: + localError(n.info, errGenerated, "unguarded access: " & renderTree(n)) + +# 'guard*' are checks which are concerned with 'guard' annotations +# (var x{.guard: y.}: int) +proc guardDotAccess(a: PEffects; n: PNode) = + let ri = n.sons[1] + if ri.kind != nkSym or ri.sym.kind != skField: return + var g = ri.sym.guard + if g.isNil or a.isTopLevel: return + # fixup guard: + if g.kind == skUnknown: + var field: PSym = nil + var ty = n.sons[0].typ.skipTypes(abstractPtrs) + if ty.kind == tyTuple: + field = lookupInRecord(ty.n, g.name) + else: + while ty != nil and ty.kind == tyObject: + field = lookupInRecord(ty.n, g.name) + if field != nil: break + ty = ty.sons[0] + if ty == nil: break + ty = ty.skipTypes(abstractPtrs) + if field == nil: + localError(n.info, errGenerated, "invalid guard field: " & g.name.s) + return + g = field + #ri.sym.guard = field + # XXX unfortunately this is not correct for generic instantiations! + if g.kind == skField: + let dot = newNodeI(nkDotExpr, n.info, 2) + dot.sons[0] = n.sons[0] + dot.sons[1] = newSymNode(g) + dot.typ = g.typ + for L in a.locked: + #if a.guards.sameSubexprs(dot, L): return + if guards.sameTree(dot, L): return + localError(n.info, errGenerated, "unguarded access: " & renderTree(n)) + else: + guardGlobal(a, n, g) + proc initVar(a: PEffects, n: PNode) = if n.kind != nkSym: return let s = n.sym @@ -93,13 +181,6 @@ proc initVarViaNew(a: PEffects, n: PNode) = # are initialized: initVar(a, n) -when trackGlobals: - proc addUse(a: PEffects, e: PNode) = - var aa = a.uses - for i in 0 .. <aa.len: - if aa[i].sym.id == e.sym.id: return - a.uses.add(e) - proc useVar(a: PEffects, n: PNode) = let s = n.sym if isLocalVar(a, s): @@ -110,9 +191,8 @@ proc useVar(a: PEffects, n: PNode) = message(n.info, warnUninit, s.name.s) # prevent superfluous warnings about the same variable: a.init.add s.id - if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar: - when trackGlobals: - a.addUse(copyNode(n)) + if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind in {skVar, skLet}: + if s.guard != nil: guardGlobal(a, n, s.guard) if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem) and tfGcSafe notin s.typ.flags: if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n)) @@ -130,20 +210,27 @@ proc addToIntersection(inter: var TIntersection, s: int) = proc throws(tracked, n: PNode) = if n.typ == nil or n.typ.kind != tyError: tracked.add n - + +proc getEbase(): PType = + result = if getCompilerProc("Exception") != nil: sysTypeFromName"Exception" + else: sysTypeFromName"E_Base" + proc excType(n: PNode): PType = # reraise is like raising E_Base: - let t = if n.kind == nkEmpty: sysTypeFromName"E_Base" else: n.typ + let t = if n.kind == nkEmpty: getEbase() else: n.typ result = skipTypes(t, skipPtrs) proc createRaise(n: PNode): PNode = result = newNode(nkType) - result.typ = sysTypeFromName"E_Base" + result.typ = getEbase() if not n.isNil: result.info = n.info proc createTag(n: PNode): PNode = result = newNode(nkType) - result.typ = sysTypeFromName"TEffect" + if getCompilerProc("RootEffect") != nil: + result.typ = sysTypeFromName"RootEffect" + else: + result.typ = sysTypeFromName"TEffect" if not n.isNil: result.info = n.info proc createAnyGlobal(n: PNode): PNode = @@ -179,17 +266,11 @@ proc mergeTags(a: PEffects, b, comesFrom: PNode) = else: for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil) -when trackGlobals: - proc mergeUses(a: PEffects, b, comesFrom: PNode) = - if b.isNil: - addUse(a, createAnyGlobal(comesFrom)) - else: - for effect in items(b): addUse(a, effect) - proc listEffects(a: PEffects) = for e in items(a.exc): message(e.info, hintUser, typeToString(e.typ)) for e in items(a.tags): message(e.info, hintUser, typeToString(e.typ)) - for e in items(a.uses): message(e.info, hintUser, e.sym.name.s) + #if a.maxLockLevel != 0: + # message(e.info, hintUser, "lockLevel: " & a.maxLockLevel) proc catches(tracked: PEffects, e: PType) = let e = skipTypes(e, skipPtrs) @@ -315,7 +396,6 @@ proc documentRaises*(n: PNode) = if n.sons[namePos].kind != nkSym: return documentEffect(n, n.sons[pragmasPos], wRaises, exceptionEffects) documentEffect(n, n.sons[pragmasPos], wTags, tagEffects) - documentEffect(n, n.sons[pragmasPos], wUses, usesEffects) template notGcSafe(t): expr = {tfGcSafe, tfNoSideEffect} * t.flags == {} @@ -323,6 +403,24 @@ proc importedFromC(n: PNode): bool = # when imported from C, we assume GC-safety. result = n.kind == nkSym and sfImportc in n.sym.flags +proc getLockLevel(s: PSym): TLockLevel = + result = s.typ.lockLevel + if result == UnspecifiedLockLevel: + if {sfImportc, sfNoSideEffect} * s.flags != {} or + tfNoSideEffect in s.typ.flags: + result = 0.TLockLevel + else: + result = UnknownLockLevel + +proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) = + if lockLevel >= tracked.currLockLevel: + # if in lock section: + if tracked.currLockLevel > 0.TLockLevel: + localError n.info, errGenerated, + "expected lock level < " & $tracked.currLockLevel & + " but got lock level " & $lockLevel + tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel) + proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let pragma = s.ast.sons[pragmasPos] let spec = effectSpec(pragma, wRaises) @@ -334,10 +432,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = if notGcSafe(s.typ) and sfImportc notin s.flags: if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n)) tracked.gcUnsafe = true - - when trackGlobals: - let usesSpec = effectSpec(pragma, wUses) - mergeUses(tracked, usesSpec, n) + mergeLockLevels(tracked, n, s.getLockLevel) proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = let n = n.skipConv @@ -357,6 +452,13 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree) of impYes: discard +proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) = + addEffect(tracked, createRaise(n)) + addTag(tracked, createTag(n)) + let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel + else: op.lockLevel + mergeLockLevels(tracked, n, lockLevel) + proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let op = skipConvAndClosure(n).typ if op != nil and op.kind == tyProc and n.kind != nkNilLit: @@ -372,9 +474,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = propagateEffects(tracked, n, n.sym) else: # we have no explicit effects so assume the worst: - addEffect(tracked, createRaise(n)) - addTag(tracked, createTag(n)) - when trackGlobals: addUse(tracked, createAnyGlobal(n)) + assumeTheWorst(tracked, n, op) # assume GcUnsafe unless in its type; 'forward' does not matter: if notGcSafe(op): if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n)) @@ -382,7 +482,6 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) - when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n) if notGcSafe(op): if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n)) tracked.gcUnsafe = true @@ -511,8 +610,12 @@ proc track(tracked: PEffects, n: PNode) = # are indistinguishable from normal procs (both have tyProc type) and # we can detect them only by checking for attached nkEffectList. if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList: - if a.kind == nkSym and a.sym == tracked.owner: - tracked.isRecursive = true + if a.kind == nkSym: + if a.sym == tracked.owner: tracked.isRecursive = true + # even for recursive calls we need to check the lock levels (!): + mergeLockLevels(tracked, n, a.sym.getLockLevel) + else: + mergeLockLevels(tracked, n, op.lockLevel) var effectList = op.n.sons[0] if a.kind == nkSym and a.sym.kind == skMethod: propagateEffects(tracked, n, a.sym) @@ -520,14 +623,10 @@ proc track(tracked: PEffects, n: PNode) = if isForwardedProc(a): propagateEffects(tracked, n, a.sym) elif isIndirectCall(a, tracked.owner): - addEffect(tracked, createRaise(n)) - addTag(tracked, createTag(n)) - when trackGlobals: addUse(tracked, createAnyGlobal(n)) - # XXX handle 'gcsafe' properly for callbacks! + assumeTheWorst(tracked, n, op) else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) - when trackGlobals: mergeUses(tracked, effectList.sons[usesEffects], n) if notGcSafe(op) and not importedFromC(a): # and it's not a recursive call: if not (a.kind == nkSym and a.sym == tracked.owner): @@ -539,6 +638,9 @@ proc track(tracked: PEffects, n: PNode) = initVarViaNew(tracked, n.sons[1]) for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) + of nkDotExpr: + guardDotAccess(tracked, n) + for i in 0 .. <len(n): track(tracked, n.sons[i]) of nkCheckedFieldExpr: track(tracked, n.sons[0]) if warnProveField in gNotes: checkFieldAccess(tracked.guards, n) @@ -552,7 +654,7 @@ proc track(tracked: PEffects, n: PNode) = addAsgnFact(tracked.guards, n.sons[0], n.sons[1]) notNilCheck(tracked, n.sons[1], n.sons[0].typ) when false: cstringCheck(tracked, n) - of nkVarSection: + of nkVarSection, nkLetSection: for child in n: let last = lastSon(child) if child.kind == nkIdentDefs and last.kind != nkEmpty: @@ -594,6 +696,16 @@ proc track(tracked: PEffects, n: PNode) = if sfDiscriminant in x.sons[0].sym.flags: addDiscriminantFact(tracked.guards, x) setLen(tracked.guards, oldFacts) + of nkPragmaBlock: + let pragmaList = n.sons[0] + let oldLocked = tracked.locked.len + let oldLockLevel = tracked.currLockLevel + for i in 0 .. <pragmaList.len: + if whichPragma(pragmaList.sons[i]) == wLocks: + lockLocations(tracked, pragmaList.sons[i]) + track(tracked, n.lastSon) + setLen(tracked.locked, oldLocked) + tracked.currLockLevel = oldLockLevel of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef: discard @@ -641,13 +753,13 @@ proc checkMethodEffects*(disp, branch: PSym) = if not isNil(tagsSpec): checkRaisesSpec(tagsSpec, actual.sons[tagEffects], "can have an unlisted effect: ", hints=off, subtypeRelation) - let usesSpec = effectSpec(p, wUses) - if not isNil(usesSpec): - checkRaisesSpec(usesSpec, actual.sons[usesEffects], - "may use an unlisted global variable: ", hints=off, symbolPredicate) if sfThread in disp.flags and notGcSafe(branch.typ): localError(branch.info, "base method is GC-safe, but '$1' is not" % branch.name.s) + if branch.typ.lockLevel > disp.typ.lockLevel: + localError(branch.info, + "base method has lock level $1, but dispatcher has $2" % + [$branch.typ.lockLevel, $disp.typ.lockLevel]) proc setEffectsForProcType*(t: PType, n: PNode) = var effects = t.n.sons[0] @@ -656,29 +768,25 @@ proc setEffectsForProcType*(t: PType, n: PNode) = let raisesSpec = effectSpec(n, wRaises) tagsSpec = effectSpec(n, wTags) - usesSpec = effectSpec(n, wUses) - if not isNil(raisesSpec) or not isNil(tagsSpec) or not isNil(usesSpec): + if not isNil(raisesSpec) or not isNil(tagsSpec): internalAssert effects.len == 0 newSeq(effects.sons, effectListLen) if not isNil(raisesSpec): effects.sons[exceptionEffects] = raisesSpec if not isNil(tagsSpec): effects.sons[tagEffects] = tagsSpec - if not isNil(usesSpec): - effects.sons[usesEffects] = usesSpec proc initEffects(effects: PNode; s: PSym; t: var TEffects) = newSeq(effects.sons, effectListLen) effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info) effects.sons[tagEffects] = newNodeI(nkArgList, s.info) - effects.sons[usesEffects] = newNodeI(nkArgList, s.info) t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] - t.uses = effects.sons[usesEffects] t.owner = s t.init = @[] t.guards = @[] + t.locked = @[] proc trackProc*(s: PSym, body: PNode) = var effects = s.typ.n.sons[0] @@ -710,17 +818,19 @@ proc trackProc*(s: PSym, body: PNode) = # after the check, use the formal spec: effects.sons[tagEffects] = tagsSpec - when trackGlobals: - let usesSpec = effectSpec(p, wUses) - if not isNil(usesSpec): - checkRaisesSpec(usesSpec, t.uses, - "uses an unlisted global variable: ", hints=on, symbolPredicate) - effects.sons[usesEffects] = usesSpec if optThreadAnalysis in gGlobalOptions: if sfThread in s.flags and t.gcUnsafe: - #localError(s.info, warnGcUnsafe2, s.name.s) - localError(s.info, "'$1' is not GC-safe" % s.name.s) + if optThreads in gGlobalOptions: + localError(s.info, "'$1' is not GC-safe" % s.name.s) + else: + localError(s.info, warnGcUnsafe2, s.name.s) if not t.gcUnsafe: s.typ.flags.incl tfGcSafe + if s.typ.lockLevel == UnspecifiedLockLevel: + s.typ.lockLevel = t.maxLockLevel + elif t.maxLockLevel > s.typ.lockLevel: + localError(s.info, + "declared lock level is $1, but real lock level is $2" % + [$s.typ.lockLevel, $t.maxLockLevel]) proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, @@ -729,5 +839,5 @@ proc trackTopLevelStmt*(module: PSym; n: PNode) = var effects = newNode(nkEffectList, n.info) var t: TEffects initEffects(effects, module, t) - + t.isToplevel = true track(t, n) |