better lock level handling
This commit is contained in:
parent
dcaba49c3f
commit
e9dec2feed
4 changed files with 78 additions and 40 deletions
|
|
@ -770,6 +770,7 @@ type
|
||||||
# it won't cause problems
|
# it won't cause problems
|
||||||
|
|
||||||
TTypeSeq* = seq[PType]
|
TTypeSeq* = seq[PType]
|
||||||
|
TLockLevel* = distinct int16
|
||||||
TType* {.acyclic.} = object of TIdObj # \
|
TType* {.acyclic.} = object of TIdObj # \
|
||||||
# types are identical iff they have the
|
# types are identical iff they have the
|
||||||
# same id; there may be multiple copies of a type
|
# same id; there may be multiple copies of a type
|
||||||
|
|
@ -797,7 +798,7 @@ type
|
||||||
size*: BiggestInt # the size of the type in bytes
|
size*: BiggestInt # the size of the type in bytes
|
||||||
# -1 means that the size is unkwown
|
# -1 means that the size is unkwown
|
||||||
align*: int16 # the type's alignment requirements
|
align*: int16 # the type's alignment requirements
|
||||||
lockLevel*: int16 # lock level as required for deadlock checking
|
lockLevel*: TLockLevel # lock level as required for deadlock checking
|
||||||
loc*: TLoc
|
loc*: TLoc
|
||||||
|
|
||||||
TPair*{.final.} = object
|
TPair*{.final.} = object
|
||||||
|
|
@ -1165,7 +1166,15 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
|
||||||
result.sons = @[name, pattern, genericParams, params,
|
result.sons = @[name, pattern, genericParams, params,
|
||||||
pragmas, exceptions, body]
|
pragmas, exceptions, body]
|
||||||
|
|
||||||
const UnspecifiedLockLevel* = -1
|
const
|
||||||
|
UnspecifiedLockLevel* = TLockLevel(-1'i16)
|
||||||
|
MaxLockLevel* = 1000'i16
|
||||||
|
UnknownLockLevel* = TLockLevel(1001'i16)
|
||||||
|
|
||||||
|
proc `$`*(x: TLockLevel): string =
|
||||||
|
if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"
|
||||||
|
elif x.ord == UnknownLockLevel.ord: result = "<unknown>"
|
||||||
|
else: result = $int16(x)
|
||||||
|
|
||||||
proc newType(kind: TTypeKind, owner: PSym): PType =
|
proc newType(kind: TTypeKind, owner: PSym): PType =
|
||||||
new(result)
|
new(result)
|
||||||
|
|
|
||||||
|
|
@ -525,16 +525,16 @@ proc pragmaLockStmt(c: PContext; it: PNode) =
|
||||||
for i in 0 .. <n.len:
|
for i in 0 .. <n.len:
|
||||||
n.sons[i] = c.semExpr(c, n.sons[i])
|
n.sons[i] = c.semExpr(c, n.sons[i])
|
||||||
|
|
||||||
proc pragmaLocks(c: PContext, it: PNode): int16 =
|
proc pragmaLocks(c: PContext, it: PNode): TLockLevel =
|
||||||
if it.kind != nkExprColonExpr:
|
if it.kind != nkExprColonExpr:
|
||||||
invalidPragma(it)
|
invalidPragma(it)
|
||||||
else:
|
else:
|
||||||
if it[1].kind != nkNilLit:
|
if it[1].kind != nkNilLit:
|
||||||
let x = expectIntLit(c, it)
|
let x = expectIntLit(c, it)
|
||||||
if x < 0 or x > high(int16):
|
if x < 0 or x > MaxLockLevel:
|
||||||
localError(it[1].info, "integer must be within 0..high(int16)")
|
localError(it[1].info, "integer must be within 0.." & $MaxLockLevel)
|
||||||
else:
|
else:
|
||||||
result = int16(x)
|
result = TLockLevel(x)
|
||||||
|
|
||||||
proc typeBorrow(sym: PSym, n: PNode) =
|
proc typeBorrow(sym: PSym, n: PNode) =
|
||||||
if n.kind == nkExprColonExpr:
|
if n.kind == nkExprColonExpr:
|
||||||
|
|
@ -571,10 +571,14 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
|
||||||
if n.kind == nkSym:
|
if n.kind == nkSym:
|
||||||
result = n.sym
|
result = n.sym
|
||||||
elif kind == skField:
|
elif kind == skField:
|
||||||
# we return a dummy symbol; later passes over the type will repair it. Note
|
# First check if the guard is a global variable:
|
||||||
# that generic instantiation needs to know about this too. But we're lazy
|
result = qualifiedLookUp(c, n, {})
|
||||||
# and perform the lookup on demand instead.
|
if result.isNil or result.kind notin {skLet, skVar} or
|
||||||
result = newSym(skUnknown, considerQuotedIdent(n), nil, n.info)
|
sfGlobal notin result.flags:
|
||||||
|
# We return a dummy symbol; later passes over the type will repair it.
|
||||||
|
# Generic instantiation needs to know about this too. But we're lazy
|
||||||
|
# and perform the lookup on demand instead.
|
||||||
|
result = newSym(skUnknown, considerQuotedIdent(n), nil, n.info)
|
||||||
else:
|
else:
|
||||||
result = qualifiedLookUp(c, n)
|
result = qualifiedLookUp(c, n)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,35 +75,42 @@ type
|
||||||
guards: TModel # nested guards
|
guards: TModel # nested guards
|
||||||
locked: seq[PNode] # locked locations
|
locked: seq[PNode] # locked locations
|
||||||
gcUnsafe, isRecursive, isToplevel: bool
|
gcUnsafe, isRecursive, isToplevel: bool
|
||||||
maxLockLevel, currLockLevel: int16
|
maxLockLevel, currLockLevel: TLockLevel
|
||||||
PEffects = var TEffects
|
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 =
|
proc isLocalVar(a: PEffects, s: PSym): bool =
|
||||||
s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner
|
s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner
|
||||||
|
|
||||||
proc getLockLevel(t: PType): int16 =
|
proc getLockLevel(t: PType): TLockLevel =
|
||||||
var t = t
|
var t = t
|
||||||
# tyGenericInst(TLock {tyGenericBody}, tyStatic, tyObject):
|
# tyGenericInst(TLock {tyGenericBody}, tyStatic, tyObject):
|
||||||
if t.kind == tyGenericInst and t.len == 3: t = t.sons[1]
|
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}:
|
if t.kind == tyStatic and t.n != nil and t.n.kind in {nkCharLit..nkInt64Lit}:
|
||||||
result = t.n.intVal.int16
|
result = t.n.intVal.TLockLevel
|
||||||
|
|
||||||
proc lockLocations(a: PEffects; pragma: PNode) =
|
proc lockLocations(a: PEffects; pragma: PNode) =
|
||||||
if pragma.kind != nkExprColonExpr:
|
if pragma.kind != nkExprColonExpr:
|
||||||
internalError(pragma.info, "no colon")
|
localError(pragma.info, errGenerated, "locks pragma without argument")
|
||||||
return
|
return
|
||||||
var firstLL = -1'i16
|
var firstLL = TLockLevel(-1'i16)
|
||||||
for x in pragma[1]:
|
for x in pragma[1]:
|
||||||
let thisLL = getLockLevel(x.typ)
|
let thisLL = getLockLevel(x.typ)
|
||||||
if thisLL != 0:
|
if thisLL != 0.TLockLevel:
|
||||||
if firstLL < 0: firstLL = thisLL
|
if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel:
|
||||||
|
localError(x.info, "invalid lock level: " & $thisLL)
|
||||||
|
elif firstLL < 0.TLockLevel: firstLL = thisLL
|
||||||
elif firstLL != thisLL:
|
elif firstLL != thisLL:
|
||||||
localError(x.info, errGenerated,
|
localError(x.info, errGenerated,
|
||||||
"multi-lock requires the same static lock level for every operand")
|
"multi-lock requires the same static lock level for every operand")
|
||||||
a.maxLockLevel = max(a.maxLockLevel, firstLL)
|
a.maxLockLevel = max(a.maxLockLevel, firstLL)
|
||||||
a.locked.add x
|
a.locked.add x
|
||||||
if firstLL >= 0 and firstLL != a.currLockLevel:
|
if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel:
|
||||||
if a.currLockLevel > 0 and a.currLockLevel < firstLL:
|
if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL:
|
||||||
localError(pragma.info, errGenerated,
|
localError(pragma.info, errGenerated,
|
||||||
"invalid nested locking")
|
"invalid nested locking")
|
||||||
a.currLockLevel = firstLL
|
a.currLockLevel = firstLL
|
||||||
|
|
@ -396,14 +403,23 @@ proc importedFromC(n: PNode): bool =
|
||||||
# when imported from C, we assume GC-safety.
|
# when imported from C, we assume GC-safety.
|
||||||
result = n.kind == nkSym and sfImportc in n.sym.flags
|
result = n.kind == nkSym and sfImportc in n.sym.flags
|
||||||
|
|
||||||
proc mergeLockLevels(tracked: PEffects, n: PNode, t: PType) =
|
proc getLockLevel(s: PSym): TLockLevel =
|
||||||
if t.lockLevel > tracked.currLockLevel:
|
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 in lock section:
|
||||||
if tracked.currLockLevel > 0:
|
if tracked.currLockLevel > 0.TLockLevel:
|
||||||
localError n.info, errGenerated,
|
localError n.info, errGenerated,
|
||||||
"expected lock level <= " & $tracked.currLockLevel &
|
"expected lock level < " & $tracked.currLockLevel &
|
||||||
" but got lock level " & $t.lockLevel
|
" but got lock level " & $lockLevel
|
||||||
tracked.maxLockLevel = max(tracked.maxLockLevel, t.lockLevel)
|
tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel)
|
||||||
|
|
||||||
proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
||||||
let pragma = s.ast.sons[pragmasPos]
|
let pragma = s.ast.sons[pragmasPos]
|
||||||
|
|
@ -416,7 +432,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
||||||
if notGcSafe(s.typ) and sfImportc notin s.flags:
|
if notGcSafe(s.typ) and sfImportc notin s.flags:
|
||||||
if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n))
|
if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n))
|
||||||
tracked.gcUnsafe = true
|
tracked.gcUnsafe = true
|
||||||
mergeLockLevels(tracked, n, s.typ)
|
mergeLockLevels(tracked, n, s.getLockLevel)
|
||||||
|
|
||||||
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
let n = n.skipConv
|
let n = n.skipConv
|
||||||
|
|
@ -436,6 +452,13 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
||||||
of impYes: discard
|
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) =
|
proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
let op = skipConvAndClosure(n).typ
|
let op = skipConvAndClosure(n).typ
|
||||||
if op != nil and op.kind == tyProc and n.kind != nkNilLit:
|
if op != nil and op.kind == tyProc and n.kind != nkNilLit:
|
||||||
|
|
@ -451,8 +474,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
propagateEffects(tracked, n, n.sym)
|
propagateEffects(tracked, n, n.sym)
|
||||||
else:
|
else:
|
||||||
# we have no explicit effects so assume the worst:
|
# we have no explicit effects so assume the worst:
|
||||||
addEffect(tracked, createRaise(n))
|
assumeTheWorst(tracked, n, op)
|
||||||
addTag(tracked, createTag(n))
|
|
||||||
# assume GcUnsafe unless in its type; 'forward' does not matter:
|
# assume GcUnsafe unless in its type; 'forward' does not matter:
|
||||||
if notGcSafe(op):
|
if notGcSafe(op):
|
||||||
if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n))
|
if warnGcUnsafe in gNotes: message(n.info, warnGcUnsafe, renderTree(n))
|
||||||
|
|
@ -588,8 +610,12 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
# are indistinguishable from normal procs (both have tyProc type) and
|
# are indistinguishable from normal procs (both have tyProc type) and
|
||||||
# we can detect them only by checking for attached nkEffectList.
|
# we can detect them only by checking for attached nkEffectList.
|
||||||
if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList:
|
if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList:
|
||||||
if a.kind == nkSym and a.sym == tracked.owner:
|
if a.kind == nkSym:
|
||||||
tracked.isRecursive = true
|
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]
|
var effectList = op.n.sons[0]
|
||||||
if a.kind == nkSym and a.sym.kind == skMethod:
|
if a.kind == nkSym and a.sym.kind == skMethod:
|
||||||
propagateEffects(tracked, n, a.sym)
|
propagateEffects(tracked, n, a.sym)
|
||||||
|
|
@ -597,8 +623,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
if isForwardedProc(a):
|
if isForwardedProc(a):
|
||||||
propagateEffects(tracked, n, a.sym)
|
propagateEffects(tracked, n, a.sym)
|
||||||
elif isIndirectCall(a, tracked.owner):
|
elif isIndirectCall(a, tracked.owner):
|
||||||
addEffect(tracked, createRaise(n))
|
assumeTheWorst(tracked, n, op)
|
||||||
addTag(tracked, createTag(n))
|
|
||||||
else:
|
else:
|
||||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||||
mergeTags(tracked, effectList.sons[tagEffects], n)
|
mergeTags(tracked, effectList.sons[tagEffects], n)
|
||||||
|
|
@ -607,7 +632,6 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
if not (a.kind == nkSym and a.sym == tracked.owner):
|
if not (a.kind == nkSym and a.sym == tracked.owner):
|
||||||
message(n.info, warnGcUnsafe, renderTree(n))
|
message(n.info, warnGcUnsafe, renderTree(n))
|
||||||
tracked.gcUnsafe = true
|
tracked.gcUnsafe = true
|
||||||
mergeLockLevels(tracked, n, op)
|
|
||||||
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
|
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
|
||||||
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
||||||
# may not look like an assignment, but it is:
|
# may not look like an assignment, but it is:
|
||||||
|
|
|
||||||
|
|
@ -530,15 +530,16 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
|
||||||
if i < sonsLen(t) - 1: add(result, ", ")
|
if i < sonsLen(t) - 1: add(result, ", ")
|
||||||
add(result, ')')
|
add(result, ')')
|
||||||
if t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
|
if t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
|
||||||
var prag: string
|
var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv]
|
||||||
if t.callConv != ccDefault: prag = CallingConvToStr[t.callConv]
|
if tfNoSideEffect in t.flags:
|
||||||
else: prag = ""
|
|
||||||
if tfNoSideEffect in t.flags:
|
|
||||||
addSep(prag)
|
addSep(prag)
|
||||||
add(prag, "noSideEffect")
|
add(prag, "noSideEffect")
|
||||||
if tfThread in t.flags:
|
if tfThread in t.flags:
|
||||||
addSep(prag)
|
addSep(prag)
|
||||||
add(prag, "gcsafe")
|
add(prag, "gcsafe")
|
||||||
|
if t.lockLevel.ord != UnspecifiedLockLevel.ord:
|
||||||
|
addSep(prag)
|
||||||
|
add(prag, "locks: " & $t.lockLevel)
|
||||||
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])
|
||||||
|
|
@ -579,8 +580,7 @@ proc firstOrd(t: PType): BiggestInt =
|
||||||
else:
|
else:
|
||||||
assert(t.n.sons[0].kind == nkSym)
|
assert(t.n.sons[0].kind == nkSym)
|
||||||
result = t.n.sons[0].sym.position
|
result = t.n.sons[0].sym.position
|
||||||
of tyGenericInst, tyDistinct, tyConst, tyMutable,
|
of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc, tyFieldAccessor:
|
||||||
tyTypeDesc, tyFieldAccessor:
|
|
||||||
result = firstOrd(lastSon(t))
|
result = firstOrd(lastSon(t))
|
||||||
of tyOrdinal:
|
of tyOrdinal:
|
||||||
if t.len > 0: result = firstOrd(lastSon(t))
|
if t.len > 0: result = firstOrd(lastSon(t))
|
||||||
|
|
@ -1360,7 +1360,8 @@ proc compatibleEffects*(formal, actual: PType): bool =
|
||||||
if real.len == 0: return false
|
if real.len == 0: return false
|
||||||
result = compatibleEffectsAux(st, real.sons[tagEffects])
|
result = compatibleEffectsAux(st, real.sons[tagEffects])
|
||||||
if not result: return
|
if not result: return
|
||||||
result = true
|
result = formal.lockLevel.ord < 0 or
|
||||||
|
actual.lockLevel.ord <= formal.lockLevel.ord
|
||||||
|
|
||||||
proc isCompileTimeOnly*(t: PType): bool {.inline.} =
|
proc isCompileTimeOnly*(t: PType): bool {.inline.} =
|
||||||
result = t.kind in {tyTypeDesc, tyStatic}
|
result = t.kind in {tyTypeDesc, tyStatic}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue