summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2020-09-06 22:01:39 +0200
committerGitHub <noreply@github.com>2020-09-06 22:01:39 +0200
commit2f6d04fd5d302646d40abc60b611653ec58463d0 (patch)
tree85a81c8958e7d166e7d1dd9cb70e6615cabc6bd0 /compiler
parent072c6556539706f8301731ebb695ffed848cfe71 (diff)
downloadNim-2f6d04fd5d302646d40abc60b611653ec58463d0.tar.gz
strict funcs: use control flow information for a more precise analysis (#15271)
* strict funcs: use control flow information for a more precise analysis

* cursor inference uses control flow information
Diffstat (limited to 'compiler')
-rw-r--r--compiler/varpartitions.nim99
1 files changed, 79 insertions, 20 deletions
diff --git a/compiler/varpartitions.nim b/compiler/varpartitions.nim
index 23ad8fe09..6cf8e1932 100644
--- a/compiler/varpartitions.nim
+++ b/compiler/varpartitions.nim
@@ -39,18 +39,26 @@ type
     of dependsOn: parent: int
     of isRootOf: graphIndex: int
     sym: PSym
+    aliveStart, aliveEnd: int # the range for which the variable is alive.
 
   MutationInfo* = object
     param: PSym
     mutatedHere, connectedVia: TLineInfo
     flags: set[SubgraphFlag]
+    maxMutation, minConnection: int
+    mutations: seq[int]
 
   Partitions = object
+    abstractTime: int
     s: seq[VarIndex]
     graphs: seq[MutationInfo]
     unanalysableMutation, performCursorInference: bool
     inAsgnSource, inConstructor, inNoSideEffectSection: int
 
+proc mutationAfterConnection(g: MutationInfo): bool {.inline.} =
+  #echo g.maxMutation, " ", g.minConnection, " ", g.param
+  g.maxMutation > g.minConnection
+
 proc `$`*(config: ConfigRef; g: MutationInfo): string =
   result = ""
   if g.flags == {isMutated, connectsConstParam}:
@@ -68,27 +76,31 @@ proc `$`*(config: ConfigRef; g: MutationInfo): string =
 
 proc hasSideEffect(c: var Partitions; info: var MutationInfo): bool =
   for g in mitems c.graphs:
-    if g.flags == {isMutated, connectsConstParam}:
+    if g.flags == {isMutated, connectsConstParam} and mutationAfterConnection(g):
       info = g
       return true
   return false
 
 template isConstParam(a): bool = a.kind == skParam and a.typ.kind != tyVar
 
-proc registerVariable(c: var Partitions; n: PNode) =
-  if n.kind == nkSym:
-    if isConstParam(n.sym):
-      c.s.add VarIndex(kind: isRootOf, graphIndex: c.graphs.len, sym: n.sym)
-      c.graphs.add MutationInfo(param: n.sym, mutatedHere: unknownLineInfo,
-                            connectedVia: unknownLineInfo, flags: {connectsConstParam})
-    else:
-      c.s.add VarIndex(kind: isEmptyRoot, sym: n.sym)
-
 proc variableId(c: Partitions; x: PSym): int {.inline.} =
   for i in 0 ..< c.s.len:
     if c.s[i].sym == x: return i
   return -1
 
+proc registerVariable(c: var Partitions; n: PNode) =
+  if n.kind == nkSym and variableId(c, n.sym) < 0:
+    if isConstParam(n.sym):
+      c.s.add VarIndex(kind: isRootOf, graphIndex: c.graphs.len, sym: n.sym,
+                       aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
+      c.graphs.add MutationInfo(param: n.sym, mutatedHere: unknownLineInfo,
+                            connectedVia: unknownLineInfo, flags: {connectsConstParam},
+                            maxMutation: -1, minConnection: high(int),
+                            mutations: @[])
+    else:
+      c.s.add VarIndex(kind: isEmptyRoot, sym: n.sym,
+                       aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
+
 proc root(v: var Partitions; start: int): int =
   result = start
   var depth = 0
@@ -101,7 +113,9 @@ proc root(v: var Partitions; start: int): int =
     while v.s[it].kind == dependsOn:
       let next = v.s[it].parent
       v.s[it] = VarIndex(kind: dependsOn, parent: result,
-                         sym: v.s[it].sym, flags: v.s[it].flags)
+                         sym: v.s[it].sym, flags: v.s[it].flags,
+                         aliveStart: v.s[it].aliveStart,
+                         aliveEnd: v.s[it].aliveEnd)
       it = next
 
 proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
@@ -111,16 +125,22 @@ proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
     case v.s[r].kind
     of isEmptyRoot:
       v.s[r] = VarIndex(kind: isRootOf, graphIndex: v.graphs.len,
-                        sym: v.s[r].sym, flags: v.s[r].flags)
+                        sym: v.s[r].sym, flags: v.s[r].flags,
+                        aliveStart: v.s[r].aliveStart,
+                        aliveEnd: v.s[r].aliveEnd)
       v.graphs.add MutationInfo(param: if isConstParam(s): s else: nil, mutatedHere: info,
-                            connectedVia: unknownLineInfo, flags: {isMutated})
+                            connectedVia: unknownLineInfo, flags: {isMutated},
+                            maxMutation: v.abstractTime, minConnection: high(int),
+                            mutations: @[v.abstractTime])
     of isRootOf:
       let g = addr v.graphs[v.s[r].graphIndex]
       if g.param == nil and isConstParam(s):
         g.param = s
-      if g.mutatedHere == unknownLineInfo:
+      if v.abstractTime > g.maxMutation:
         g.mutatedHere = info
+        g.maxMutation = v.abstractTime
       g.flags.incl isMutated
+      g.mutations.add v.abstractTime
     else:
       assert false, "cannot happen"
   else:
@@ -150,24 +170,39 @@ proc connect(v: var Partitions; a, b: PSym; info: TLineInfo) =
     # for now we always make 'rb' the slave and 'ra' the master:
     var rbFlags: set[SubgraphFlag] = {}
     var mutatedHere = unknownLineInfo
+    var mut = 0
+    var con = v.abstractTime
+    var gb: ptr MutationInfo = nil
     if v.s[rb].kind == isRootOf:
-      var gb = addr v.graphs[v.s[rb].graphIndex]
+      gb = addr v.graphs[v.s[rb].graphIndex]
       if param == nil: param = gb.param
       mutatedHere = gb.mutatedHere
       rbFlags = gb.flags
+      mut = gb.maxMutation
+      con = min(con, gb.minConnection)
 
-    v.s[rb] = VarIndex(kind: dependsOn, parent: ra, sym: v.s[rb].sym, flags: v.s[rb].flags)
+    v.s[rb] = VarIndex(kind: dependsOn, parent: ra, sym: v.s[rb].sym, flags: v.s[rb].flags,
+                       aliveStart: v.s[rb].aliveStart,
+                       aliveEnd: v.s[rb].aliveEnd)
     case v.s[ra].kind
     of isEmptyRoot:
-      v.s[ra] = VarIndex(kind: isRootOf, graphIndex: v.graphs.len, sym: v.s[ra].sym, flags: v.s[ra].flags)
+      v.s[ra] = VarIndex(kind: isRootOf, graphIndex: v.graphs.len, sym: v.s[ra].sym,
+                         flags: v.s[ra].flags,
+                         aliveStart: v.s[ra].aliveStart,
+                         aliveEnd: v.s[ra].aliveEnd)
       v.graphs.add MutationInfo(param: param, mutatedHere: mutatedHere,
-                            connectedVia: info, flags: paramFlags + rbFlags)
+                            connectedVia: info, flags: paramFlags + rbFlags,
+                            maxMutation: mut, minConnection: con,
+                            mutations: if gb != nil: gb.mutations else: @[])
     of isRootOf:
       var g = addr v.graphs[v.s[ra].graphIndex]
       if g.param == nil: g.param = param
       if g.mutatedHere == unknownLineInfo: g.mutatedHere = mutatedHere
+      g.minConnection = min(g.minConnection, con)
       g.connectedVia = info
       g.flags.incl paramFlags + rbFlags
+      if gb != nil:
+        g.mutations.add gb.mutations
     else:
       assert false, "cannot happen"
 
@@ -364,6 +399,7 @@ proc deps(c: var Partitions; dest, src: PNode) =
       rhsIsSink(c, src)
 
 proc traverse(c: var Partitions; n: PNode) =
+  inc c.abstractTime
   case n.kind
   of nkLetSection, nkVarSection:
     for child in n:
@@ -384,10 +420,18 @@ proc traverse(c: var Partitions; n: PNode) =
     traverse(c, n[1])
     dec c.inAsgnSource
     deps(c, n[0], n[1])
-  of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
+  of nkSym:
+    dec c.abstractTime
+    if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
+      let id = variableId(c, n.sym)
+      if id >= 0:
+        c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
+
+  of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
       nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
       nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
       nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr:
+    dec c.abstractTime
     discard "do not follow the construct"
   of nkCallKinds:
     for child in n: traverse(c, child)
@@ -450,6 +494,14 @@ proc traverse(c: var Partitions; n: PNode) =
     inc c.inNoSideEffectSection, enforceNoSideEffects
     traverse(c, n.lastSon)
     dec c.inNoSideEffectSection, enforceNoSideEffects
+  of nkWhileStmt, nkForStmt, nkParForStmt:
+    for child in n: traverse(c, child)
+    # analyse loops twice so that 'abstractTime' suffices to detect cases
+    # like:
+    #   while cond:
+    #     mutate(graph)
+    #     connect(graph, cursorVar)
+    for child in n: traverse(c, child)
   else:
     for child in n: traverse(c, child)
 
@@ -465,6 +517,13 @@ proc mutatesNonVarParameters*(s: PSym; n: PNode; info: var MutationInfo): bool =
   traverse(par, n)
   result = hasSideEffect(par, info)
 
+proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
+  if isMutated in g.flags:
+    for m in g.mutations:
+      if m in v.aliveStart..v.aliveEnd:
+        return true
+  return false
+
 proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
   var par = Partitions(performCursorInference: true)
   if s.kind notin {skMacro, skModule}:
@@ -481,7 +540,7 @@ proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
         v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
         v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned:
       let rid = root(par, i)
-      if par.s[rid].kind == isRootOf and isMutated in par.graphs[par.s[rid].graphIndex].flags:
+      if par.s[rid].kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].graphIndex], par.s[i]):
         discard "cannot cursor into a graph that is mutated"
       else:
         v.sym.flags.incl sfCursor