summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--drnim/drnim.nim164
-rw-r--r--drnim/tests/tphi.nim23
2 files changed, 172 insertions, 15 deletions
diff --git a/drnim/drnim.nim b/drnim/drnim.nim
index 7c79add26..6c08bed42 100644
--- a/drnim/drnim.nim
+++ b/drnim/drnim.nim
@@ -65,11 +65,13 @@ type
     graph: ModuleGraph
     facts: seq[(PNode, VersionScope)]
     varVersions: seq[int] # this maps variable IDs to their current version.
+    varSyms: seq[PSym] # mirrors 'varVersions'
     o: Operators
     hasUnstructedCf: int
     currOptions: TOptions
     owner: PSym
     mangler: seq[PSym]
+    opImplies: PSym
 
   DrCon = object
     graph: ModuleGraph
@@ -126,9 +128,18 @@ proc isLoc(m: PNode; assumeUniqueness: bool): bool =
     else:
       discard
 
-proc varVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
+proc currentVarVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
+  # we need to take into account both en- and disabled var bindings here,
+  # hence the 'abs' call:
   result = 0
   for i in countdown(int(begin)-1, 0):
+    if abs(c.varVersions[i]) == s.id: inc result
+
+proc previousVarVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
+  # we need to ignore currently disabled var bindings here,
+  # hence no 'abs' call here.
+  result = -1
+  for i in countdown(int(begin)-1, 0):
     if c.varVersions[i] == s.id: inc result
 
 proc disamb(c: DrnimContext; s: PSym): int =
@@ -156,15 +167,30 @@ proc stableName(result: var string; c: DrnimContext; n: PNode; version: VersionS
       if d != 0:
         result.add "`scope="
         result.addInt d
-      let v = c.varVersion(n.sym, version) - ord(isOld)
-      assert v >= 0
+      let v = if isOld: c.previousVarVersion(n.sym, version)
+              else: c.currentVarVersion(n.sym, version)
       if v > 0:
         result.add '`'
         result.addInt v
     else:
       result.add "`magic="
       result.addInt ord(n.sym.magic)
-
+  of nkBindStmt:
+    # we use 'bind x 3' to use the 3rd version of variable 'x'. This
+    # is easier than using 'old' which is position relative.
+    assert n.len == 2
+    assert n[0].kind == nkSym
+    assert n[1].kind == nkIntLit
+    let s = n[0].sym
+    let v = int(n[1].intVal)
+    result.add s.name.s
+    let d = disamb(c, s)
+    if d != 0:
+      result.add "`scope="
+      result.addInt d
+    if v > 0:
+      result.add '`'
+      result.addInt v
   of nkCharLit..nkUInt64Lit:
     result.addInt n.intVal
   of nkFloatLit..nkFloat64Lit:
@@ -700,7 +726,8 @@ proc compatibleProps(graph: ModuleGraph; formal, actual: PType): bool {.nimcall.
       c.graph = graph
       c.canonParameterNames = true
       try:
-        c.up = DrnimContext(z3: setupZ3(), o: initOperators(graph), graph: graph, owner: nil)
+        c.up = DrnimContext(z3: setupZ3(), o: initOperators(graph), graph: graph, owner: nil,
+          opImplies: createMagic(graph, "->", mImplies))
         template zero: untyped = VersionScope(0)
         if not frequires.isEmpty:
           result = not arequires.isEmpty and proofEngineAux(c, @[(frequires, zero)], (arequires, zero))[0]
@@ -721,14 +748,22 @@ template config(c: typed): untyped = c.graph.config
 proc addFact(c: DrnimContext; n: PNode) =
   let v = c.currentScope
   if n[0].kind == nkSym and n[0].sym.magic in {mOr, mAnd}:
-    c.facts.add((n[1], v))
+    c.addFact(n[1])
   c.facts.add((n, v))
 
+proc neg(c: DrnimContext; n: PNode): PNode =
+  result = newNodeI(nkCall, n.info, 2)
+  result[0] = newSymNode(c.o.opNot)
+  result[1] = n
+
 proc addFactNeg(c: DrnimContext; n: PNode) =
-  var neg = newNodeI(nkCall, n.info, 2)
-  neg[0] = newSymNode(c.o.opNot)
-  neg[1] = n
-  addFact(c, neg)
+  addFact(c, neg(c, n))
+
+proc combineFacts(c: DrnimContext; a, b: PNode): PNode =
+  if a == nil:
+    result = b
+  else:
+    result = buildCall(c.o.opAnd, a, b)
 
 proc prove(c: DrnimContext; prop: PNode): bool =
   let (success, m) = proofEngine(c, c.facts, (prop, c.currentScope))
@@ -745,6 +780,8 @@ proc traversePragmaStmt(c: DrnimContext, n: PNode) =
       elif pragma == wInvariant or pragma == wAssert:
         if prove(c, it[1]):
           addFact(c, it[1])
+        else:
+          echoFacts(c)
 
 proc requiresCheck(c: DrnimContext, call: PNode; op: PType) =
   assert op.n[0].kind == nkEffectList
@@ -761,6 +798,7 @@ proc freshVersion(c: DrnimContext; arg: PNode) =
   let v = getRoot(arg)
   if v != nil:
     c.varVersions.add v.id
+    c.varSyms.add v
 
 proc translateEnsuresFromCall(c: DrnimContext, e, call: PNode): PNode =
   if e.kind in nkCallKinds and e[0].kind == nkSym and e[0].sym.magic == mOld:
@@ -837,23 +875,118 @@ proc traverseCase(c: DrnimContext; n: PNode) =
   # XXX make this as smart as 'if elif'
   setLen(c.facts, oldFacts)
 
+proc disableVarVersions(c: DrnimContext; until: int) =
+  for i in until..<c.varVersions.len:
+    c.varVersions[i] = - abs(c.varVersions[i])
+
+proc varOfVersion(c: DrnimContext; x: PSym; scope: int): PNode =
+  let version = currentVarVersion(c, x, VersionScope(scope))
+  result = newTree(nkBindStmt, newSymNode(x), newIntNode(nkIntLit, version))
+
 proc traverseIf(c: DrnimContext; n: PNode) =
-  traverse(c, n[0][0])
-  let oldFacts = c.facts.len
-  addFact(c, n[0][0])
+  #[ Consider this example::
+
+    var x = y   # x'0
+    if a:
+      inc x     # x'1 == x'0 + 1
+    elif b:
+      inc x, 2  # x'2 == x'0 + 2
+
+  afterwards we know this is fact::
+
+    x'3 = Phi(x'0, x'1, x'2)
+
+  So a Phi node from SSA representation is an 'or' formula like::
+
+    x'3 == x'1 or x'3 == x'2 or x'3 == x'0
 
-  traverse(c, n[0][1])
+  However, this loses some information. The formula that doesn't
+  lose information is::
 
-  for i in 1..<n.len:
+    (a -> (x'3 == x'1)) and
+    ((not a and b) -> (x'3 == x'2)) and
+    ((not a and not b) -> (x'3 == x'0))
+
+  (Where ``->`` is the logical implication.)
+
+  In addition to the Phi information we also know the 'facts'
+  computed by the branches, for example::
+
+    if a:
+      factA
+    elif b:
+      factB
+    else:
+      factC
+
+    (a -> factA) and
+    ((not a and b) -> factB) and
+    ((not a and not b) -> factC)
+
+  We can combine these two aspects by producing the following facts
+  after each branch::
+
+    var x = y   # x'0
+    if a:
+      inc x     # x'1 == x'0 + 1
+      # also:     x'1 == x'final
+    elif b:
+      inc x, 2  # x'2 == x'0 + 2
+      # also:     x'2 == x'final
+    else:
+      # also:     x'0 == x'final
+
+  ]#
+  let oldFacts = c.facts.len
+  let oldVars = c.varVersions.len
+  var newFacts: seq[PNode]
+  var branches = newSeq[(PNode, int)](n.len) # (cond, newVars) pairs
+  template condVersion(): untyped = VersionScope(oldVars)
+
+  for i in 0..<n.len:
     let branch = n[i]
     setLen(c.facts, oldFacts)
+
+    var cond = PNode(nil)
     for j in 0..i-1:
       addFactNeg(c, n[j][0])
+      cond = combineFacts(c, cond, neg(c, n[j][0]))
     if branch.len > 1:
       addFact(c, branch[0])
+      cond = combineFacts(c, cond, branch[0])
+
     for i in 0..<branch.len:
       traverse(c, branch[i])
+
+    assert cond != nil
+    branches[i] = (cond, c.varVersions.len)
+
+    var newInfo = PNode(nil)
+    for f in oldFacts..<c.facts.len:
+      newInfo = combineFacts(c, newInfo, c.facts[f][0])
+    if newInfo != nil:
+      newFacts.add buildCall(c.opImplies, cond, newInfo)
+
+    disableVarVersions(c, oldVars)
+
   setLen(c.facts, oldFacts)
+  for f in newFacts: c.facts.add((f, condVersion))
+  # build the 'Phi' information:
+  let varsWithoutFinals = c.varVersions.len
+  var mutatedVars = initIntSet()
+  for i in oldVars ..< varsWithoutFinals:
+    let vv = c.varVersions[i]
+    if not mutatedVars.containsOrIncl(vv):
+      c.varVersions.add vv
+      c.varSyms.add c.varSyms[i]
+
+  var prevIdx = oldVars
+  for i in 0 ..< branches.len:
+    for v in prevIdx .. branches[i][1] - 1:
+      c.facts.add((buildCall(c.opImplies, branches[i][0],
+        buildCall(c.o.opEq, varOfVersion(c, c.varSyms[v], branches[i][1]), newSymNode(c.varSyms[v]))),
+        condVersion))
+    prevIdx = branches[i][1]
 
 proc traverseBlock(c: DrnimContext; n: PNode) =
   traverse(c, n)
@@ -1028,6 +1161,7 @@ proc strongSemCheck(graph: ModuleGraph; owner: PSym; n: PNode) =
     c.o = initOperators(graph)
     c.graph = graph
     c.owner = owner
+    c.opImplies = createMagic(c.graph, "->", mImplies)
     try:
       traverse(c, n)
       ensuresCheck(c, owner)
diff --git a/drnim/tests/tphi.nim b/drnim/tests/tphi.nim
new file mode 100644
index 000000000..7ff49f4dc
--- /dev/null
+++ b/drnim/tests/tphi.nim
@@ -0,0 +1,23 @@
+discard """
+  nimout: '''tphi.nim(9, 10) Warning: BEGIN [User]
+tphi.nim(22, 10) Warning: END [User]'''
+  cmd: "drnim $file"
+  action: "compile"
+"""
+import std/logic
+{.push staticBoundChecks: defined(nimDrNim).}
+{.warning: "BEGIN".}
+
+proc testAsgn(y: int) =
+  var a = y
+  if a > 0:
+    if a > 3:
+      a = a + 2
+    else:
+      a = a + 1
+    {.assert: a > 1.}
+
+testAsgn(3)
+
+{.warning: "END".}
+{.pop.}