summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim13
-rw-r--r--compiler/pragmas.nim20
-rw-r--r--compiler/sempass2.nim70
-rw-r--r--compiler/types.nim15
4 files changed, 78 insertions, 40 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 919a1a3fd..1fbbd56d4 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -770,6 +770,7 @@ type
                               # it won't cause problems
   
   TTypeSeq* = seq[PType]
+  TLockLevel* = distinct int16
   TType* {.acyclic.} = object of TIdObj # \
                               # types are identical iff they have the
                               # same id; there may be multiple copies of a type
@@ -797,7 +798,7 @@ type
     size*: BiggestInt         # the size of the type in bytes
                               # -1 means that the size is unkwown
     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
 
   TPair*{.final.} = object 
@@ -1165,7 +1166,15 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
   result.sons = @[name, pattern, genericParams, params,
                   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 = 
   new(result)
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 5f9ee218f..510023bb8 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -525,16 +525,16 @@ proc pragmaLockStmt(c: PContext; it: PNode) =
       for i in 0 .. <n.len:
         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:
     invalidPragma(it)
   else:
     if it[1].kind != nkNilLit:
       let x = expectIntLit(c, it)
-      if x < 0 or x > high(int16):
-        localError(it[1].info, "integer must be within 0..high(int16)")
+      if x < 0 or x > MaxLockLevel:
+        localError(it[1].info, "integer must be within 0.." & $MaxLockLevel)
       else:
-        result = int16(x)
+        result = TLockLevel(x)
 
 proc typeBorrow(sym: PSym, n: PNode) =
   if n.kind == nkExprColonExpr:
@@ -571,10 +571,14 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
   if n.kind == nkSym:
     result = n.sym
   elif kind == skField:
-    # we return a dummy symbol; later passes over the type will repair it. Note
-    # that 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)
+    # First check if the guard is a global variable:
+    result = qualifiedLookUp(c, n, {})
+    if result.isNil or result.kind notin {skLet, skVar} or
+        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:
     result = qualifiedLookUp(c, n)
 
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 981e69900..94a3b49e3 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -75,35 +75,42 @@ type
     guards: TModel # nested guards
     locked: seq[PNode] # locked locations
     gcUnsafe, isRecursive, isToplevel: bool
-    maxLockLevel, currLockLevel: int16
+    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): int16 =
+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.int16
+    result = t.n.intVal.TLockLevel
 
 proc lockLocations(a: PEffects; pragma: PNode) =
   if pragma.kind != nkExprColonExpr:
-    internalError(pragma.info, "no colon")
+    localError(pragma.info, errGenerated, "locks pragma without argument")
     return
-  var firstLL = -1'i16
+  var firstLL = TLockLevel(-1'i16)
   for x in pragma[1]:
     let thisLL = getLockLevel(x.typ)
-    if thisLL != 0:
-      if firstLL < 0: firstLL = thisLL
+    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 and firstLL != a.currLockLevel:
-    if a.currLockLevel > 0 and a.currLockLevel < firstLL:
+  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
@@ -396,14 +403,23 @@ proc importedFromC(n: PNode): bool =
   # when imported from C, we assume GC-safety.
   result = n.kind == nkSym and sfImportc in n.sym.flags
 
-proc mergeLockLevels(tracked: PEffects, n: PNode, t: PType) =
-  if t.lockLevel > tracked.currLockLevel:
+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:
+    if tracked.currLockLevel > 0.TLockLevel:
       localError n.info, errGenerated,
-        "expected lock level <= " & $tracked.currLockLevel &
-        " but got lock level " & $t.lockLevel
-    tracked.maxLockLevel = max(tracked.maxLockLevel, t.lockLevel)
+        "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]
@@ -416,7 +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
-  mergeLockLevels(tracked, n, s.typ)
+  mergeLockLevels(tracked, n, s.getLockLevel)
 
 proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
   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)
     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:
@@ -451,8 +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))
+        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))
@@ -588,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)
@@ -597,8 +623,7 @@ 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))
+          assumeTheWorst(tracked, n, op)
       else:
         mergeEffects(tracked, effectList.sons[exceptionEffects], 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):
             message(n.info, warnGcUnsafe, renderTree(n))
             tracked.gcUnsafe = true
-        mergeLockLevels(tracked, n, op)
     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}:
       # may not look like an assignment, but it is:
diff --git a/compiler/types.nim b/compiler/types.nim
index 27282e684..5ef2d627b 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -530,15 +530,16 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       if i < sonsLen(t) - 1: add(result, ", ")
     add(result, ')')
     if t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
-    var prag: string
-    if t.callConv != ccDefault: prag = CallingConvToStr[t.callConv]
-    else: prag = ""
-    if tfNoSideEffect in t.flags: 
+    var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv]
+    if tfNoSideEffect in t.flags:
       addSep(prag)
       add(prag, "noSideEffect")
     if tfThread in t.flags:
       addSep(prag)
       add(prag, "gcsafe")
+    if t.lockLevel.ord != UnspecifiedLockLevel.ord:
+      addSep(prag)
+      add(prag, "locks: " & $t.lockLevel)
     if len(prag) != 0: add(result, "{." & prag & ".}")
   of tyVarargs, tyIter:
     result = typeToStr[t.kind] % typeToString(t.sons[0])
@@ -579,8 +580,7 @@ proc firstOrd(t: PType): BiggestInt =
     else: 
       assert(t.n.sons[0].kind == nkSym)
       result = t.n.sons[0].sym.position
-  of tyGenericInst, tyDistinct, tyConst, tyMutable,
-     tyTypeDesc, tyFieldAccessor:
+  of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc, tyFieldAccessor:
     result = firstOrd(lastSon(t))
   of tyOrdinal:
     if t.len > 0: result = firstOrd(lastSon(t))
@@ -1360,7 +1360,8 @@ proc compatibleEffects*(formal, actual: PType): bool =
       if real.len == 0: return false
       result = compatibleEffectsAux(st, real.sons[tagEffects])
       if not result: return
-  result = true
+  result = formal.lockLevel.ord < 0 or
+      actual.lockLevel.ord <= formal.lockLevel.ord
 
 proc isCompileTimeOnly*(t: PType): bool {.inline.} =
   result = t.kind in {tyTypeDesc, tyStatic}