summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorClyybber <darkmine956@gmail.com>2020-05-15 20:24:43 +0200
committerGitHub <noreply@github.com>2020-05-15 19:24:43 +0100
commit9f78f116b2276a367f371ece550f669d64dbe594 (patch)
treea424db8e3aea955383df17da66d4ab2f79437d70
parent13cfaf5fd55796bc671737a73f35643626c98b6e (diff)
downloadNim-9f78f116b2276a367f371ece550f669d64dbe594.tar.gz
New "ping-pong" DFA (#14322)
* New ping-pong analysis

* Add testcase for #13456

* Remove debugging leftover

* Unquote "unstructured controlflow"

* Fix typo

* Fix exponential complexity in edge cases

* Add sanity testcase

* Fix
-rw-r--r--compiler/dfa.nim63
-rw-r--r--compiler/injectdestructors.nim70
-rw-r--r--tests/arc/tmovebug.nim103
3 files changed, 163 insertions, 73 deletions
diff --git a/compiler/dfa.nim b/compiler/dfa.nim
index 0db89579b..67a9e26d8 100644
--- a/compiler/dfa.nim
+++ b/compiler/dfa.nim
@@ -35,11 +35,11 @@ from patterns import sameTrees
 
 type
   InstrKind* = enum
-    goto, fork, join, def, use
+    goto, fork, def, use
   Instr* = object
     n*: PNode # contains the def/use location.
     case kind*: InstrKind
-    of goto, fork, join: dest*: int
+    of goto, fork: dest*: int
     else: discard
 
   ControlFlowGraph* = seq[Instr]
@@ -47,8 +47,6 @@ type
   TPosition = distinct int
 
   TBlock = object
-    joins: seq[Instr]
-    forks: seq[TPosition]
     case isTryBlock: bool
     of false:
       label: PSym
@@ -72,7 +70,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
   var jumpTargets = initIntSet()
   let last = if last < 0: c.len-1 else: min(last, c.len-1)
   for i in start..last:
-    if c[i].kind in {goto, fork, join}:
+    if c[i].kind in {goto, fork}:
       jumpTargets.incl(i+c[i].dest)
   var i = start
   while i <= last:
@@ -83,7 +81,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
     case c[i].kind
     of def, use:
       result.add renderTree(c[i].n)
-    of goto, fork, join:
+    of goto, fork:
       result.add "L"
       result.addInt c[i].dest+i
     result.add("\t#")
@@ -102,7 +100,6 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
 proc forkI(c: var Con; n: PNode): TPosition =
   result = TPosition(c.code.len)
   c.code.add Instr(n: n, kind: fork, dest: 0)
-  c.blocks[^1].forks.add result
 
 proc gotoI(c: var Con; n: PNode): TPosition =
   result = TPosition(c.code.len)
@@ -110,6 +107,24 @@ proc gotoI(c: var Con; n: PNode): TPosition =
 
 #[
 
+Join is no more
+===============
+Instead of generating join instructions we adapt our traversal of the CFG.
+
+When encountering a fork we split into two paths, we follow the path
+starting at "pc + 1" until it encounters the joinpoint: "pc + forkInstr.dest".
+If we encounter gotos that would jump further than the current joinpoint,
+as can happen with gotos generated by unstructured controlflow such as break, raise or return,
+we simply suspend following the current path, and follow the other path until the new joinpoint
+which is simply the instruction pointer returned to us by the now suspended path.
+If the path we are following now, also encounters a goto that exceeds the joinpoint
+we repeat the process; suspending the current path and evaluating the other one with a new joinpoint.
+If we eventually reach a common joinpoint we join the two paths.
+This new "ping-pong" approach has the obvious advantage of not requiring join instructions, as such
+cutting down on the CFG size but is also mandatory for correctly handling complicated cases
+of unstructured controlflow.
+
+
 Design of join
 ==============
 
@@ -258,10 +273,6 @@ duplicate the 'join' instructions on breaks and return exits!
 
 ]#
 
-proc joinI(c: var Con; fromFork: TPosition; n: PNode) =
-  let dist = fromFork.int - c.code.len
-  c.code.add Instr(n: n, kind: join, dest: dist)
-
 proc genLabel(c: Con): TPosition =
   result = TPosition(c.code.len)
 
@@ -289,10 +300,6 @@ proc popBlock(c: var Con; oldLen: int) =
     exits.add c.gotoI(newNode(nkEmpty))
   for e in exits:
     c.patch e
-  for j in c.blocks[oldLen].joins:
-    var patchedJ = j
-    patchedJ.dest -= c.code.len
-    c.code.add patchedJ
   c.blocks.setLen(oldLen)
 
 template withBlock(labl: PSym; body: untyped) {.dirty.} =
@@ -353,7 +360,6 @@ when true:
           c.gen(n[1])
     else:
       withBlock(nil):
-        let oldForksLen = c.blocks[^1].forks.len
         var endings: array[3, TPosition]
         for i in 0..2:
           c.gen(n[0])
@@ -362,8 +368,6 @@ when true:
         for i in countdown(endings.high, 0):
           let endPos = endings[i]
           c.patch(endPos)
-          c.joinI(c.blocks[^1].forks.pop(), n)
-        doAssert(c.blocks[^1].forks.len == oldForksLen)
 
 else:
 
@@ -374,7 +378,6 @@ else:
     #   body
     #   jmp lab1
     # lab2:
-    let oldForksLen = c.blocks[^1].forks.len
     let lab1 = c.genLabel
     withBlock(nil):
       if isTrue(n[0]):
@@ -386,15 +389,11 @@ else:
         c.gen(n[1])
         c.jmpBack(n, lab1)
         c.patch(lab2)
-    setLen(c.blocks[^1].forks, oldForksLen)
 
 template forkT(n, body) =
-  let oldLen = c.blocks[^1].forks.len
   let lab1 = c.forkI(n)
   body
   c.patch(lab1)
-  c.joinI(lab1, n)
-  setLen(c.blocks[^1].forks, oldLen)
 
 proc genIf(c: var Con, n: PNode) =
   #[
@@ -433,7 +432,6 @@ proc genIf(c: var Con, n: PNode) =
     join F1
 
   ]#
-  let oldLen = c.blocks[^1].forks.len
   var endings: seq[TPosition] = @[]
   for i in 0..<n.len:
     var it = n[i]
@@ -446,8 +444,6 @@ proc genIf(c: var Con, n: PNode) =
   for i in countdown(endings.high, 0):
     let endPos = endings[i]
     c.patch(endPos)
-    c.joinI(c.blocks[^1].forks.pop(), n)
-  doAssert(c.blocks[^1].forks.len == oldLen)
 
 proc genAndOr(c: var Con; n: PNode) =
   #   asgn dest, a
@@ -474,7 +470,6 @@ proc genCase(c: var Con; n: PNode) =
     abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
 
   var endings: seq[TPosition] = @[]
-  let oldLen = c.blocks[^1].forks.len
   c.gen(n[0])
   for i in 1..<n.len:
     let it = n[i]
@@ -491,8 +486,6 @@ proc genCase(c: var Con; n: PNode) =
   for i in countdown(endings.high, 0):
     let endPos = endings[i]
     c.patch(endPos)
-    c.joinI(c.blocks[^1].forks.pop(), n)
-  doAssert(c.blocks[^1].forks.len == oldLen)
 
 proc genBlock(c: var Con; n: PNode) =
   withBlock(n[0].sym):
@@ -511,10 +504,6 @@ proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) =
 
     c.blocks[i].breakFixups.add (lab1, trailingFinales)
 
-  for b in countdown(c.blocks.high, i):
-    for f in countdown(c.blocks[b].forks.high, 0):
-      c.blocks[i].joins.add Instr(n: n, kind: join, dest: c.blocks[b].forks[f].int)
-
 proc genBreak(c: var Con; n: PNode) =
   if n[0].kind == nkSym:
     #echo cast[int](n[0].sym)
@@ -530,7 +519,6 @@ proc genBreak(c: var Con; n: PNode) =
         return
 
 proc genTry(c: var Con; n: PNode) =
-  let oldForksLen = c.blocks[^1].forks.len
   var endings: seq[TPosition] = @[]
 
   let oldLen = c.blocks.len
@@ -543,10 +531,6 @@ proc genTry(c: var Con; n: PNode) =
 
   for f in c.blocks[oldLen].raiseFixups:
     c.patch(f)
-  for j in c.blocks[oldLen].joins:
-    var patchedJ = j
-    patchedJ.dest -= c.code.len
-    c.code.add patchedJ
 
   c.blocks.setLen oldLen
 
@@ -561,7 +545,6 @@ proc genTry(c: var Con; n: PNode) =
   for i in countdown(endings.high, 0):
     let endPos = endings[i]
     c.patch(endPos)
-    c.joinI(c.blocks[^1].forks.pop(), n)
 
   # join the 'elsePos' forkI instruction:
   #c.joinI(c.blocks[^1].forks.pop(), n)
@@ -569,7 +552,6 @@ proc genTry(c: var Con; n: PNode) =
   let fin = lastSon(n)
   if fin.kind == nkFinally:
     c.gen(fin[0])
-  doAssert(c.blocks[^1].forks.len == oldForksLen)
 
 template genNoReturn(c: var Con; n: PNode) =
   # leave the graph
@@ -751,7 +733,6 @@ proc genCall(c: var Con; n: PNode) =
         genBreakOrRaiseAux(c, i, n)
         break
     c.patch(endGoto)
-    c.joinI(c.blocks[^1].forks.pop(), n)
   dec c.inCall
 
 proc genMagic(c: var Con; n: PNode; m: TMagic) =
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index 44409c760..5785df6d6 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -65,9 +65,9 @@ template dbg(body) =
 proc p(n: PNode; c: var Con; mode: ProcessMode): PNode
 proc moveOrCopy(dest, ri: PNode; c: var Con): PNode
 
-proc isLastRead(location: PNode; c: var Con; pc, comesFrom: int): int =
+proc isLastRead(location: PNode; c: var Con; pc, until: int): int =
   var pc = pc
-  while pc < c.g.len:
+  while pc < c.g.len and pc < until:
     case c.g[pc].kind
     of def:
       if defInstrTargets(c.g[pc], location):
@@ -83,15 +83,17 @@ proc isLastRead(location: PNode; c: var Con; pc, comesFrom: int): int =
       pc = pc + c.g[pc].dest
     of fork:
       # every branch must lead to the last read of the location:
-      let variantA = isLastRead(location, c, pc+1, pc)
-      if variantA < 0: return -1
-      var variantB = isLastRead(location, c, pc + c.g[pc].dest, pc)
-      if variantB < 0: return -1
+      var variantA = pc + 1
+      var variantB = pc + c.g[pc].dest
+      while variantA != variantB:
+        if min(variantA, variantB) < 0: return -1
+        if max(variantA, variantB) >= c.g.len or min(variantA, variantB) >= until:
+          break
+        if variantA < variantB:
+          variantA = isLastRead(location, c, variantA, min(variantB, until))
+        else:
+          variantB = isLastRead(location, c, variantB, min(variantA, until))
       pc = min(variantA, variantB)
-    of InstrKind.join:
-      let dest = pc + c.g[pc].dest
-      if dest == comesFrom: return pc + 1
-      inc pc
   return pc
 
 proc isLastRead(n: PNode; c: var Con): bool =
@@ -114,12 +116,12 @@ proc isLastRead(n: PNode; c: var Con): bool =
   # ensure that we don't find another 'use X' instruction.
   if instr+1 >= c.g.len: return true
 
-  result = isLastRead(n, c, instr+1, -1) >= 0
+  result = isLastRead(n, c, instr+1, int.high) >= 0
   dbg: echo "ugh ", c.otherRead.isNil, " ", result
 
-proc isFirstWrite(location: PNode; c: var Con; pc, comesFrom: int; instr: int): int =
+proc isFirstWrite(location: PNode; c: var Con; pc, until: int): int =
   var pc = pc
-  while pc < instr:
+  while pc < until:
     case c.g[pc].kind
     of def:
       if defInstrTargets(c.g[pc], location):
@@ -134,15 +136,17 @@ proc isFirstWrite(location: PNode; c: var Con; pc, comesFrom: int; instr: int):
       pc = pc + c.g[pc].dest
     of fork:
       # every branch must not contain a def/use of our location:
-      let variantA = isFirstWrite(location, c, pc+1, pc, instr)
-      if variantA < 0: return -1
-      var variantB = isFirstWrite(location, c, pc + c.g[pc].dest, pc, instr + c.g[pc].dest)
-      if variantB < 0: return -1
+      var variantA = pc + 1
+      var variantB = pc + c.g[pc].dest
+      while variantA != variantB:
+        if min(variantA, variantB) < 0: return -1
+        if max(variantA, variantB) > until:
+          break
+        if variantA < variantB:
+          variantA = isFirstWrite(location, c, variantA, min(variantB, until))
+        else:
+          variantB = isFirstWrite(location, c, variantB, min(variantA, until))
       pc = min(variantA, variantB)
-    of InstrKind.join:
-      let dest = pc + c.g[pc].dest
-      if dest == comesFrom: return pc + 1
-      inc pc
   return pc
 
 proc isFirstWrite(n: PNode; c: var Con): bool =
@@ -161,10 +165,10 @@ proc isFirstWrite(n: PNode; c: var Con): bool =
   # ensure that we don't find another 'def/use X' instruction.
   if instr == 0: return true
 
-  result = isFirstWrite(n, c, 0, -1, instr) >= 0
+  result = isFirstWrite(n, c, 0, instr) >= 0
 
 proc initialized(code: ControlFlowGraph; pc: int,
-                 init, uninit: var IntSet; comesFrom: int): int =
+                 init, uninit: var IntSet; until: int): int =
   ## Computes the set of definitely initialized variables across all code paths
   ## as an IntSet of IDs.
   var pc = pc
@@ -173,20 +177,22 @@ proc initialized(code: ControlFlowGraph; pc: int,
     of goto:
       pc = pc + code[pc].dest
     of fork:
-      let target = pc + code[pc].dest
       var initA = initIntSet()
       var initB = initIntSet()
-      let pcA = initialized(code, pc+1, initA, uninit, pc)
-      discard initialized(code, target, initB, uninit, pc)
+      var variantA = pc + 1
+      var variantB = pc + code[pc].dest
+      while variantA != variantB:
+        if max(variantA, variantB) > until:
+          break
+        if variantA < variantB:
+          variantA = initialized(code, variantA, initA, uninit, min(variantB, until))
+        else:
+          variantB = initialized(code, variantB, initB, uninit, min(variantA, until))
+      pc = min(variantA, variantB)
       # we add vars if they are in both branches:
       for v in initA:
         if v in initB:
           init.incl v
-      pc = pcA+1
-    of InstrKind.join:
-      let target = pc + code[pc].dest
-      if comesFrom == target: return pc
-      inc pc
     of use:
       let v = code[pc].n.sym
       if v.kind != skParam and v.id notin init:
@@ -1040,7 +1046,7 @@ proc computeUninit(c: var Con) =
     c.uninitComputed = true
     c.uninit = initIntSet()
     var init = initIntSet()
-    discard initialized(c.g, pc = 0, init, c.uninit, comesFrom = -1)
+    discard initialized(c.g, pc = 0, init, c.uninit, int.high)
 
 proc injectDefaultCalls(n: PNode, c: var Con) =
   case n.kind
diff --git a/tests/arc/tmovebug.nim b/tests/arc/tmovebug.nim
index 6ed10c403..39f1c8bf4 100644
--- a/tests/arc/tmovebug.nim
+++ b/tests/arc/tmovebug.nim
@@ -19,6 +19,23 @@ c.text = hello
 c.text = hello
 pOD.text = hello
 pOD.toks = @["hello"]
+fff
+fff
+2
+fff
+fff
+2
+fff
+fff
+2
+mmm
+fff
+fff
+fff
+3
+mmm
+sink me (sink)
+assign me (not sink)
 '''
 """
 
@@ -199,3 +216,89 @@ when false:
   let pOHD = parseOHD()
   dump pOHD.text
   dump pOHD.toks
+
+# bug #13456
+
+iterator combinations[T](s: openarray[T], k: int): seq[T] =
+  let n = len(s)
+  assert k >= 0 and k <= n
+  var pos = newSeq[int](k)
+  var current = newSeq[T](k)
+  for i in 0..k-1:
+    pos[k-i-1] = i
+  var done = false
+  while not done:
+    for i in 0..k-1:
+      current[i] = s[pos[k-i-1]]
+    yield current
+    var i = 0
+    while i < k:
+      pos[i] += 1
+      if pos[i] < n-i:
+        for j in 0..i-1:
+          pos[j] = pos[i] + i - j
+        break
+      i += 1
+    if i >= k:
+      break
+
+type
+  UndefEx = object of Exception
+
+proc main2 =
+  var delayedSyms = @[1, 2, 3]
+  var unp: seq[int]
+  block myb:
+    for a in 1 .. 2:
+      if delayedSyms.len > a:
+        unp = delayedSyms
+        for t in unp.combinations(a + 1):
+          try:
+            var h = false
+            for k in t:
+              echo "fff"
+            if h: continue
+            if true:
+              raise newException(UndefEx, "forward declaration")
+            break myb
+          except UndefEx:
+            echo t.len
+        echo "mmm"
+
+main2()
+
+
+
+type ME = object
+  who: string
+
+proc `=`(x: var ME, y: ME) =
+  if x.who.len > 0: echo "assign ",x.who
+
+proc `=sink`(x: var ME, y: ME) =
+  if x.who.len > 0: echo "sink ",x.who
+
+var dump: ME
+template use(x) = dump = x
+template def(x) = x = dump
+
+var c = true
+
+proc shouldSink() =
+  var x = ME(who: "me (sink)")
+  use(x) # we analyse this
+  if c: def(x)
+  else: def(x)
+  use(x) # ok, with the [else] part.
+
+shouldSink()
+
+dump = ME()
+
+proc shouldNotSink() =
+  var x = ME(who: "me (not sink)")
+  use(x) # we analyse this
+  if c: def(x)
+  use(x) # Not ok without the '[else]'
+
+shouldNotSink()