summary refs log tree commit diff stats
path: root/compiler/sempass2.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/sempass2.nim')
-rw-r--r--compiler/sempass2.nim236
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)