summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim5
-rw-r--r--compiler/condsyms.nim1
-rw-r--r--compiler/guards.nim3
-rw-r--r--compiler/modulegraphs.nim4
-rw-r--r--compiler/pragmas.nim37
-rw-r--r--compiler/semcall.nim8
-rw-r--r--compiler/semmagic.nim25
-rw-r--r--compiler/sempass2.nim110
-rw-r--r--compiler/semstmts.nim2
-rw-r--r--compiler/sigmatch.nim2
-rw-r--r--compiler/trees.nim6
-rw-r--r--compiler/wordrecg.nim2
-rw-r--r--doc/drnim.rst203
-rw-r--r--drnim/drnim.nim675
-rw-r--r--drnim/nim.cfg18
-rw-r--r--drnim/tests/config.nims7
-rw-r--r--drnim/tests/tbasic_array_index.nim43
-rw-r--r--drnim/tests/tensures.nim31
-rw-r--r--drnim/tests/tsetlen_invalidates.nim22
-rw-r--r--koch.nim28
-rw-r--r--lib/std/logic.nim10
-rw-r--r--lib/system/strmantle.nim8
-rw-r--r--testament/categories.nim24
-rw-r--r--testament/testament.nim.cfg2
-rw-r--r--tools/kochdocs.nim1
25 files changed, 1242 insertions, 35 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 8b000ecb9..4d45f0e55 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -335,8 +335,8 @@ const
   nkEffectList* = nkArgList
   # hacks ahead: an nkEffectList is a node with 4 children:
   exceptionEffects* = 0 # exceptions at position 0
-  usesEffects* = 1      # read effects at position 1
-  writeEffects* = 2     # write effects at position 2
+  requiresEffects* = 1      # 'requires' annotation
+  ensuresEffects* = 2     # 'ensures' annotation
   tagEffects* = 3       # user defined tag ('gc', 'time' etc.)
   pragmasEffects* = 4    # not an effect, but a slot for pragmas in proc type
   effectListLen* = 5    # list of effects list
@@ -633,6 +633,7 @@ type
     mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr,
     mStrToStr, mEnumToStr,
     mAnd, mOr,
+    mImplies, mIff, mExists, mForall, mOld,
     mEqStr, mLeStr, mLtStr,
     mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet,
     mConStrStr, mSlice,
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index b6997e6fc..4fc882ab7 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -114,4 +114,5 @@ proc initDefines*(symbols: StringTableRef) =
 
   defineSymbol("nimHasSinkInference")
   defineSymbol("nimNewIntegerOps")
+  defineSymbol("nimHasInvariant")
   defineSymbol("nimHasStacktraceMsgs")
diff --git a/compiler/guards.nim b/compiler/guards.nim
index aaf7ecf0e..88441eb96 100644
--- a/compiler/guards.nim
+++ b/compiler/guards.nim
@@ -249,6 +249,9 @@ proc pred(n: PNode): PNode =
   else:
     result = n
 
+proc buildLe*(o: Operators; a, b: PNode): PNode =
+  result = o.opLe.buildCall(a, b)
+
 proc canon*(n: PNode; o: Operators): PNode =
   if n.safeLen >= 1:
     result = shallowCopy(n)
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index dff3f3b58..db4c28af7 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -72,6 +72,10 @@ type
     onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
     onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
     globalDestructors*: seq[PNode]
+    proofEngine*: proc (graph: ModuleGraph; assumptions: seq[PNode]; toProve: PNode): (bool, string) {.nimcall.}
+    requirementsCheck*: proc (graph: ModuleGraph; assumptions: seq[PNode];
+                             call, requirement: PNode): (bool, string) {.nimcall.}
+    compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
 
   TPassContext* = object of RootObj # the pass's context
   PPassContext* = ref TPassContext
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 52df47ef0..6056afb6c 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -28,7 +28,8 @@ const
     wBorrow, wImportCompilerProc, wThread,
     wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
     wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe,
-    wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy}
+    wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
+    wRequires, wEnsures}
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas+{wBase}-{wImportCpp}
   templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
@@ -39,7 +40,7 @@ const
   iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
     wMagic, wBorrow,
     wDiscardable, wGensym, wInject, wRaises,
-    wTags, wLocks, wGcSafe}
+    wTags, wLocks, wGcSafe, wRequires, wEnsures}
   exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
   stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
     wBoundChecks, wOverflowChecks, wNilChecks, wStaticBoundchecks,
@@ -52,11 +53,12 @@ const
     wDeprecated,
     wFloatChecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll,
     wLinearScanEnd, wPatterns, wTrMacros, wEffects, wNoForward, wReorder, wComputedGoto,
-    wInjectStmt, wExperimental, wThis, wUsed}
+    wInjectStmt, wExperimental, wThis, wUsed, wInvariant, wAssume}
   lambdaPragmas* = declPragmas + {FirstCallConv..LastCallConv,
     wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
     wThread, wAsmNoStackFrame,
-    wRaises, wLocks, wTags, wGcSafe, wCodegenDecl} - {wExportNims, wError, wUsed}  # why exclude these?
+    wRaises, wLocks, wTags, wRequires, wEnsures,
+    wGcSafe, wCodegenDecl} - {wExportNims, wError, wUsed}  # why exclude these?
   typePragmas* = declPragmas + {wMagic, wAcyclic,
     wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
     wIncompleteStruct, wByCopy, wByRef,
@@ -73,7 +75,8 @@ const
     wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore}
   letPragmas* = varPragmas
   procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
-                      wThread, wRaises, wLocks, wTags, wGcSafe}
+                      wThread, wRaises, wLocks, wTags, wGcSafe,
+                      wRequires, wEnsures}
   forVarPragmas* = {wInject, wGensym}
   allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
   enumFieldPragmas* = {wDeprecated}
@@ -105,6 +108,26 @@ proc invalidPragma*(c: PContext; n: PNode) =
 proc illegalCustomPragma*(c: PContext, n: PNode, s: PSym) =
   localError(c.config, n.info, "cannot attach a custom pragma to '" & s.name.s & "'")
 
+proc pragmaProposition(c: PContext, n: PNode) =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, "proposition expected")
+  else:
+    n[1] = c.semExpr(c, n[1])
+
+proc pragmaEnsures(c: PContext, n: PNode) =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, "proposition expected")
+  else:
+    openScope(c)
+    let o = getCurrOwner(c)
+    if o.kind in routineKinds and o.typ != nil and o.typ.sons[0] != nil:
+      var s = newSym(skResult, getIdent(c.cache, "result"), o, n.info)
+      s.typ = o.typ.sons[0]
+      incl(s.flags, sfUsed)
+      addDecl(c, s)
+    n[1] = c.semExpr(c, n[1])
+    closeScope(c)
+
 proc pragmaAsm*(c: PContext, n: PNode): char =
   result = '\0'
   if n != nil:
@@ -1146,6 +1169,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
         if sym == nil: invalidPragma(c, it)
         else: sym.flags.incl sfUsed
       of wLiftLocals: discard
+      of wRequires, wInvariant, wAssume:
+        pragmaProposition(c, it)
+      of wEnsures:
+        pragmaEnsures(c, it)
       else: invalidPragma(c, it)
     elif comesFromPush and whichKeyword(ident) != wInvalid:
       discard "ignore the .push pragma; it doesn't apply"
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index d8834dc90..321745c82 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -128,7 +128,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
     else:
       break
 
-proc effectProblem(f, a: PType; result: var string) =
+proc effectProblem(f, a: PType; result: var string; c: PContext) =
   if f.kind == tyProc and a.kind == tyProc:
     if tfThread in f.flags and tfThread notin a.flags:
       result.add "\n  This expression is not GC-safe. Annotate the " &
@@ -152,7 +152,9 @@ proc effectProblem(f, a: PType; result: var string) =
       of efLockLevelsDiffer:
         result.add "\n  The `.locks` requirements differ. Annotate the " &
             "proc with {.locks: 0.} to get extended error information."
-
+      when defined(drnim):
+        if not c.graph.compatibleProps(c.graph, f, a):
+          result.add "\n  The `.requires` or `.ensures` properties are incompatible."
 
 proc renderNotLValue(n: PNode): string =
   result = $n
@@ -238,7 +240,7 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
           var got = nArg.typ
           candidates.add typeToString(got)
           doAssert wanted != nil
-          if got != nil: effectProblem(wanted, got, candidates)
+          if got != nil: effectProblem(wanted, got, candidates, c)
       of kUnknown: discard "do not break 'nim check'"
       candidates.add "\n"
       if err.firstMismatch.arg == 1 and nArg.kind == nkTupleConstr and
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index af80b3ca3..8c07d7d18 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -428,6 +428,27 @@ proc turnFinalizerIntoDestructor(c: PContext; orig: PSym; info: TLineInfo): PSym
   result.typ = newProcType(result.info, result)
   result.typ.addParam newParam
 
+proc semQuantifier(c: PContext; n: PNode): PNode =
+  checkMinSonsLen(n, 2, c.config)
+  openScope(c)
+  for i in 0..n.len-2:
+    let v = newSymS(skForVar, n[i], c)
+    styleCheckDef(c.config, v)
+    onDef(n.info, v)
+    n[i] = newSymNode(v)
+    addDecl(c, v)
+  n[^1] = forceBool(c, semExprWithType(c, n[^1]))
+  closeScope(c)
+
+proc semOld(c: PContext; n: PNode): PNode =
+  if n[1].kind == nkHiddenDeref:
+    n[1] = n[1][0]
+  if n[1].kind != nkSym or n[1].sym.kind != skParam:
+    localError(c.config, n[1].info, "'old' takes a parameter name")
+  elif n[1].sym.owner != getCurrOwner(c):
+    localError(c.config, n[1].info, n[1].sym.name.s & " does not belong to " & getCurrOwner(c).name.s)
+  result = n
+
 proc magicsAfterOverloadResolution(c: PContext, n: PNode,
                                    flags: TExprFlags): PNode =
   ## This is the preferred code point to implement magics.
@@ -505,4 +526,8 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
       result[0] = newSymNode(t.destructor)
   of mUnown:
     result = semUnown(c, n)
+  of mExists, mForall:
+    result = semQuantifier(c, n)
+  of mOld:
+    result = semOld(c, n)
   else: result = n
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index c8ad14c41..4658f5454 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -565,6 +565,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType; caller: PNode)
       elif tfNoSideEffect notin op.flags:
         markSideEffect(tracked, a)
   if paramType != nil and paramType.kind == tyVar:
+    invalidateFacts(tracked.guards, n)
     if n.kind == nkSym and isLocalVar(tracked, n.sym):
       makeVolatile(tracked, n.sym)
   if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags:
@@ -675,28 +676,75 @@ proc cstringCheck(tracked: PEffects; n: PNode) =
       a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
     message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
 
+proc prove(c: PEffects; prop: PNode): bool =
+  if c.graph.proofEngine != nil:
+    let (success, m) = c.graph.proofEngine(c.graph, c.guards.s,
+      canon(prop, c.guards.o))
+    if not success:
+      message(c.config, prop.info, warnStaticIndexCheck, "cannot prove: " & $prop & m)
+    result = success
+
+proc patchResult(c: PEffects; n: PNode) =
+  if n.kind == nkSym and n.sym.kind == skResult:
+    let fn = c.owner
+    if fn != nil and fn.kind in routineKinds and fn.ast != nil and resultPos < fn.ast.len:
+      n.sym = fn.ast[resultPos].sym
+    else:
+      localError(c.config, n.info, "routine has no return type, but .requires contains 'result'")
+  else:
+    for i in 0..<safeLen(n):
+      patchResult(c, n[i])
+
+when defined(drnim):
+  proc requiresCheck(c: PEffects; call: PNode; op: PType) =
+    assert op.n[0].kind == nkEffectList
+    if requiresEffects < op.n[0].len:
+      let requires = op.n[0][requiresEffects]
+      if requires != nil and requires.kind != nkEmpty:
+        # we need to map the call arguments to the formal parameters used inside
+        # 'requires':
+        let (success, m) = c.graph.requirementsCheck(c.graph, c.guards.s, call, canon(requires, c.guards.o))
+        if not success:
+          message(c.config, call.info, warnStaticIndexCheck, "cannot prove: " & $requires & m)
+
+else:
+  template requiresCheck(c, n, op) = discard
+
 proc checkLe(c: PEffects; a, b: PNode) =
-  case proveLe(c.guards, a, b)
-  of impUnknown:
-    #for g in c.guards.s:
-    #  if g != nil: echo "I Know ", g
-    message(c.config, a.info, warnStaticIndexCheck,
-      "cannot prove: " & $a & " <= " & $b)
-  of impYes:
-    discard
-  of impNo:
-    message(c.config, a.info, warnStaticIndexCheck,
-      "can prove: " & $a & " > " & $b)
+  if c.graph.proofEngine != nil:
+    var cmpOp = mLeI
+    if a.typ != nil:
+      case a.typ.skipTypes(abstractInst).kind
+      of tyFloat..tyFloat128: cmpOp = mLeF64
+      of tyChar, tyUInt..tyUInt64: cmpOp = mLeU
+      else: discard
+
+    let cmp = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), a, b)
+    cmp.info = a.info
+    discard prove(c, cmp)
+  else:
+    case proveLe(c.guards, a, b)
+    of impUnknown:
+      #for g in c.guards.s:
+      #  if g != nil: echo "I Know ", g
+      message(c.config, a.info, warnStaticIndexCheck,
+        "cannot prove: " & $a & " <= " & $b)
+    of impYes:
+      discard
+    of impNo:
+      message(c.config, a.info, warnStaticIndexCheck,
+        "can prove: " & $a & " > " & $b)
 
 proc checkBounds(c: PEffects; arr, idx: PNode) =
   checkLe(c, lowBound(c.config, arr), idx)
   checkLe(c, idx, highBound(c.config, arr, c.guards.o))
 
 proc checkRange(c: PEffects; value: PNode; typ: PType) =
-  if typ.skipTypes(abstractInst - {tyRange}).kind == tyRange:
-    let lowBound = nkIntLit.newIntNode(firstOrd(c.config, typ))
+  let t = typ.skipTypes(abstractInst - {tyRange})
+  if t.kind == tyRange:
+    let lowBound = copyTree(t.n[0])
     lowBound.info = value.info
-    let highBound = nkIntLit.newIntNode(lastOrd(c.config, typ))
+    let highBound = copyTree(t.n[1])
     highBound.info = value.info
     checkLe(c, lowBound, value)
     checkLe(c, value, highBound)
@@ -783,6 +831,7 @@ proc track(tracked: PEffects, n: PNode) =
         mergeEffects(tracked, effectList[exceptionEffects], n)
         mergeTags(tracked, effectList[tagEffects], n)
         gcsafeAndSideeffectCheck()
+      requiresCheck(tracked, n, op)
     if a.kind != nkSym or a.sym.magic != mNBindSym:
       for i in 1..<n.len: trackOperand(tracked, n[i], paramType(op, i), a)
     if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
@@ -976,6 +1025,13 @@ proc track(tracked: PEffects, n: PNode) =
         enforcedGcSafety = true
       elif pragma == wNoSideEffect:
         enforceNoSideEffects = true
+      when defined(drnim):
+        if pragma == wAssume:
+          addFact(tracked.guards, pragmaList[i][1])
+        elif pragma == wInvariant:
+          if prove(tracked, pragmaList[i][1]):
+            addFact(tracked.guards, pragmaList[i][1])
+
     if enforcedGcSafety: tracked.inEnforcedGcSafe = true
     if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
     track(tracked, n.lastSon)
@@ -1065,6 +1121,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
   if sfThread in disp.flags and notGcSafe(branch.typ):
     localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
                                 branch.name.s)
+  when defined(drnim):
+    if not g.compatibleProps(g, disp.typ, branch.typ):
+      localError(g.config, branch.info, "for method '" & branch.name.s &
+        "' the `.requires` or `.ensures` properties are incompatible.")
+
   if branch.typ.lockLevel > disp.typ.lockLevel:
     when true:
       message(g.config, branch.info, warnLockLevel,
@@ -1088,14 +1149,22 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
     let tagsSpec = effectSpec(n, wTags)
     if not isNil(tagsSpec):
       effects[tagEffects] = tagsSpec
+
+    let requiresSpec = propSpec(n, wRequires)
+    if not isNil(requiresSpec):
+      effects[requiresEffects] = requiresSpec
+    let ensuresSpec = propSpec(n, wEnsures)
+    if not isNil(ensuresSpec):
+      effects[ensuresEffects] = ensuresSpec
+
     effects[pragmasEffects] = n
 
 proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
   newSeq(effects.sons, effectListLen)
   effects[exceptionEffects] = newNodeI(nkArgList, s.info)
   effects[tagEffects] = newNodeI(nkArgList, s.info)
-  effects[usesEffects] = g.emptyNode
-  effects[writeEffects] = g.emptyNode
+  effects[requiresEffects] = g.emptyNode
+  effects[ensuresEffects] = g.emptyNode
   effects[pragmasEffects] = g.emptyNode
 
   t.exc = effects[exceptionEffects]
@@ -1155,6 +1224,15 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
     # after the check, use the formal spec:
     effects[tagEffects] = tagsSpec
 
+  let requiresSpec = propSpec(p, wRequires)
+  if not isNil(requiresSpec):
+    effects[requiresEffects] = requiresSpec
+  let ensuresSpec = propSpec(p, wEnsures)
+  if not isNil(ensuresSpec):
+    patchResult(t, ensuresSpec)
+    effects[ensuresEffects] = ensuresSpec
+    discard prove(t, ensuresSpec)
+
   if sfThread in s.flags and t.gcUnsafe:
     if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions:
       #localError(s.info, "'$1' is not GC-safe" % s.name.s)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index d99a02183..b45c42fe1 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1930,7 +1930,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     if n[pragmasPos].kind != nkEmpty:
       pragma(c, s, n[pragmasPos], validPragmas)
       # To ease macro generation that produce forwarded .async procs we now
-      # allow a bit redudancy in the pragma declarations. The rule is
+      # allow a bit redundancy in the pragma declarations. The rule is
       # a prototype's pragma list must be a superset of the current pragma
       # list.
       # XXX This needs more checks eventually, for example that external
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 4f05282a3..79b3ea94c 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -646,6 +646,8 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
         return isNone
     when useEffectSystem:
       if compatibleEffects(f, a) != efCompat: return isNone
+    when defined(drnim):
+      if not c.c.graph.compatibleProps(c.c.graph, f, a): return isNone
 
   of tyNil:
     result = f.allowsNil
diff --git a/compiler/trees.nim b/compiler/trees.nim
index 9a1186a4f..455bf5301 100644
--- a/compiler/trees.nim
+++ b/compiler/trees.nim
@@ -148,6 +148,12 @@ proc effectSpec*(n: PNode, effectType: TSpecialWord): PNode =
         result.add(it[1])
       return
 
+proc propSpec*(n: PNode, effectType: TSpecialWord): PNode =
+  for i in 0..<n.len:
+    var it = n[i]
+    if it.kind == nkExprColonExpr and whichPragma(it) == effectType:
+      return it[1]
+
 proc unnestStmts(n, result: PNode) =
   if n.kind == nkStmtList:
     for x in items(n): unnestStmts(x, result)
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 28b2f9c05..2ce2b9322 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -54,6 +54,7 @@ type
     wNonReloadable, wExecuteOnReload,
     wAssertions, wPatterns, wTrMacros, wSinkInference, wWarnings,
     wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags,
+    wRequires, wEnsures, wInvariant, wAssume,
     wDeadCodeElimUnused,  # deprecated, dead code elim always happens
     wSafecode, wPackage, wNoForward, wReorder, wNoRewrite, wNoDestroy,
     wPragma,
@@ -142,6 +143,7 @@ const
 
     "assertions", "patterns", "trmacros", "sinkinference", "warnings", "hints",
     "optimization", "raises", "writes", "reads", "size", "effects", "tags",
+    "requires", "ensures", "invariant", "assume",
     "deadcodeelim",  # deprecated, dead code elim always happens
     "safecode", "package", "noforward", "reorder", "norewrite", "nodestroy",
     "pragma",
diff --git a/doc/drnim.rst b/doc/drnim.rst
new file mode 100644
index 000000000..a9ae9baeb
--- /dev/null
+++ b/doc/drnim.rst
@@ -0,0 +1,203 @@
+===================================
+   DrNim User Guide
+===================================
+
+:Author: Andreas Rumpf
+:Version: |nimversion|
+
+.. contents::
+
+
+Introduction
+============
+
+This document describes the usage of the *DrNim* tool. DrNim combines
+the Nim frontend with the `Z3 <https://github.com/Z3Prover/z3>`_ proof
+engine in order to allow verify / validate software written in Nim.
+DrNim's command line options are the same as the Nim compiler's.
+
+
+DrNim currently only checks the sections of your code that are marked
+via ``staticBoundChecks: on``:
+
+.. code-block:: nim
+
+  {.push staticBoundChecks: on.}
+  # <--- code section here ---->
+  {.pop.}
+
+DrNim currently only tries to prove array indexing or subrange checks,
+overflow errors are *not* prevented. Overflows will be checked for in
+the future.
+
+Later versions of the **Nim compiler** will **assume** that the checks inside
+the ``staticBoundChecks: on`` environment have been proven correct and so
+it will **omit** the runtime checks. If you do not want this behavior, use
+instead ``{.push staticBoundChecks: defined(nimDrNim).}``. This way the
+Nim compiler remains unaware of the performed proofs but DrNim will prove
+your code.
+
+
+Installation
+============
+
+Run ``koch drnim``, the executable will afterwards be in ``$nim/bin/drnim``.
+
+
+Motivating Example
+==================
+
+The follow example highlights what DrNim can easily do, even
+without additional annotations:
+
+.. code-block:: nim
+
+  {.push staticBoundChecks: on.}
+
+  proc sum(a: openArray[int]): int =
+    for i in 0..a.len:
+      result += a[i]
+
+  {.pop.}
+
+  echo sum([1, 2, 3])
+
+This program contains a famous "index out of bounds" bug. DrNim
+detects it and produces the following error message::
+
+  cannot prove: i <= len(a) + -1; counter example: i -> 0 a.len -> 0 [IndexCheck]
+
+In other words for ``i == 0`` and ``a.len == 0`` (for example!) there would be
+an index out of bounds error.
+
+
+Pre-, postconditions and invariants
+===================================
+
+DrNim adds 4 additional annotations (pragmas) to Nim:
+
+- `requires`:idx:
+- `ensures`:idx:
+- `invariant`:idx:
+- `assume`:idx:
+
+These pragmas are ignored by the Nim compiler so that they don't have to
+be disabled via ``when defined(nimDrNim)``.
+
+
+Invariant
+---------
+
+An ``invariant`` is a proposition that must be true after every loop
+iteration, it's tied to the loop body it's part of.
+
+
+Requires
+--------
+
+A ``requires`` annotation describes what the function expects to be true
+before it's called so that it can perform its operation. A ``requires``
+annotation is also called a `precondition`:idx:.
+
+
+Ensures
+-------
+
+An ``ensures`` annotation describes what will be true after the function
+call. An ``ensures`` annotation is also called a `postcondition`:idx:.
+
+
+Assume
+------
+
+An ``assume`` annotation describes what DrNim should **assume** to be true
+in this section of the program. It is an unsafe escape mechanism comparable
+to Nim's ``cast`` statement. Use it only when you really know better
+than DrNim. You should add a comment to a paper that proves the proposition
+you assume.
+
+
+Example: insertionSort
+======================
+
+**Note**: This example does not yet work with DrNim. ``forall`` and ``exists``
+are not implemented.
+
+.. code-block:: nim
+
+  import std / logic
+
+  proc insertionSort(a: var openArray[int]) {.
+      ensures: forall(i in 1..<a.len, a[i-1] <= a[i]).} =
+
+    for k in 1 ..< a.len:
+      {.invariant: 1 <= k and k <= a.len.}
+      {.invariant: forall(j in 1..<k, i in 0..<j, a[i] <= a[j]).}
+      var t = k
+      while t > 0 and a[t-1] > a[t]:
+        {.invariant: k < a.len.}
+        {.invariant: 0 <= t and t <= k.}
+        {.invariant: forall(j in 1..k, i in 0..<j, j == t or a[i] <= a[j]).}
+        swap a[t], a[t-1]
+        dec t
+
+Unfortunately the invariants required to prove this code correct take more
+code than the imperative instructions. However this effort can be compensated
+by the fact that the result needs very little testing. Be aware though that
+DrNim only proves that after ``insertionSort`` this condition holds::
+
+  forall(i in 1..<a.len, a[i-1] <= a[i])
+
+
+This is required, but not sufficient to describe that a ``sort`` operation
+was performed. For example, the same postcondition is true for this proc
+which doesn't sort at all:
+
+.. code-block:: nim
+
+  import std / logic
+
+  proc insertionSort(a: var openArray[int]) {.
+      ensures: forall(i in 1..<a.len, a[i-1] <= a[i]).} =
+    # does not sort, overwrites `a`'s contents!
+    for i in 0..<a.len: a[i] = i
+
+
+
+Syntax of propositions
+======================
+
+The basic syntax is ``ensures|requires|invariant: <prop>``.
+A ``prop`` is either a comparison or a compound::
+
+  prop = nim_bool_expression
+       | prop 'and' prop
+       | prop 'or' prop
+       | prop '->' prop # implication
+       | prop '<->' prop
+       | 'not' prop
+       | '(' prop ')' # you can group props via ()
+       | forallProp
+       | existsProp
+
+  forallProp = 'forall' '(' quantifierList ',' prop ')'
+  existsProp = 'exists' '(' quantifierList ',' prop ')'
+
+  quantifierList = quantifier (',' quantifier)*
+  quantifier = <new identifier> 'in' nim_iteration_expression
+
+
+``nim_iteration_expression`` here is an ordinary expression of Nim code
+that describes an iteration space, for example ``1..4`` or ``1..<a.len``.
+
+``nim_bool_expression`` here is an ordinary expression of Nim code of
+type ``bool`` like ``a == 3`` or ``23 > a.len``.
+
+The supported subset of Nim code that can be used in these expressions
+is currently underspecified but ``let`` variables, function parameters
+and ``result`` (which represents the function's final result) are amenable
+for verification. The expressions must not have any side-effects and must
+terminate.
+
+The operators ``forall``, ``exists``, ``->``, ``<->`` have to imported
+from ``std / logic``.
diff --git a/drnim/drnim.nim b/drnim/drnim.nim
new file mode 100644
index 000000000..e814496e4
--- /dev/null
+++ b/drnim/drnim.nim
@@ -0,0 +1,675 @@
+#
+#
+#            Doctor Nim
+#        (c) Copyright 2020 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+#[
+
+- Most important bug:
+
+  while i < x.len and use(s[i]): inc i # is safe
+
+- We need to map arrays to Z3 and test for something like 'forall(i, (i in 3..4) -> (a[i] > 3))'
+- forall/exists need syntactic sugar as the manual
+- We need teach DrNim what 'inc', 'dec' and 'swap' mean, for example
+  'x in n..m; inc x' implies 'x in n+1..m+1'
+
+- We need an ``old`` annotation:
+
+proc f(x: var int; y: var int) {.ensures: x == old(x)+1 and y == old(y)+1 .} =
+  inc x
+  inc y
+
+var x = 3
+var y: range[N..M]
+f(x, y)
+{.assume: y in N+1 .. M+1.}
+# --> y in N+1..M+1
+
+proc myinc(x: var int) {.ensures: x-1 == old(x).} =
+  inc x
+
+facts(x) # x < 3
+myinc x
+facts(x+1)
+
+We handle state transitions in this way:
+
+  for every f in facts:
+    replace 'x' by 'old(x)'
+  facts.add ensuresClause
+
+  # then we know: old(x) < 3; x-1 == old(x)
+  # we can conclude:  x-1 < 3 but leave this task to Z3
+
+]#
+
+import std / [
+  parseopt, strutils, os, tables, times
+]
+
+import ".." / compiler / [
+  ast, types, renderer,
+  commands, options, msgs,
+  platform,
+  idents, lineinfos, cmdlinehelper, modulegraphs, condsyms,
+  pathutils, passes, passaux, sem, modules
+]
+
+import z3 / z3_api
+
+when not defined(windows):
+  # on UNIX we use static linking because UNIX's lib*.so system is broken
+  # beyond repair and the neckbeards don't understand software development.
+  {.passL: "dist/z3/build/libz3.a".}
+
+const
+  HelpMessage = "DrNim Version $1 [$2: $3]\n" &
+      "Compiled at $4\n" &
+      "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n"
+
+const
+  Usage = """
+drnim [options] [projectfile]
+
+Options: Same options that the Nim compiler supports.
+"""
+
+proc getCommandLineDesc(conf: ConfigRef): string =
+  result = (HelpMessage % [system.NimVersion, platform.OS[conf.target.hostOS].name,
+                           CPU[conf.target.hostCPU].name, CompileDate]) &
+                           Usage
+
+proc helpOnError(conf: ConfigRef) =
+  msgWriteln(conf, getCommandLineDesc(conf), {msgStdout})
+  msgQuit(0)
+
+type
+  CannotMapToZ3Error = object of ValueError
+  Z3Exception = object of ValueError
+
+  DrCon = object
+    z3: Z3_context
+    graph: ModuleGraph
+    mapping: Table[string, Z3_ast]
+    canonParameterNames: bool
+
+proc stableName(result: var string; n: PNode) =
+  # we can map full Nim expressions like 'f(a, b, c)' to Z3 variables.
+  # We must be carefult to select a unique, stable name for these expressions
+  # based on structural equality. 'stableName' helps us with this problem.
+  case n.kind
+  of nkEmpty, nkNilLit, nkType: discard
+  of nkIdent:
+    result.add n.ident.s
+  of nkSym:
+    result.add n.sym.name.s
+    result.add '_'
+    result.addInt n.sym.id
+  of nkCharLit..nkUInt64Lit:
+    result.addInt n.intVal
+  of nkFloatLit..nkFloat64Lit:
+    result.addFloat n.floatVal
+  of nkStrLit..nkTripleStrLit:
+    result.add strutils.escape n.strVal
+  else:
+    result.add $n.kind
+    result.add '('
+    for i in 0..<n.len:
+      if i > 0: result.add ", "
+      stableName(result, n[i])
+    result.add ')'
+
+proc stableName(n: PNode): string = stableName(result, n)
+
+proc notImplemented(msg: string) {.noinline.} =
+  raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & msg)
+
+proc translateEnsures(e, x: PNode): PNode =
+  if e.kind == nkSym and e.sym.kind == skResult:
+    result = x
+  else:
+    result = shallowCopy(e)
+    for i in 0 ..< safeLen(e):
+      result[i] = translateEnsures(e[i], x)
+
+proc typeToZ3(c: DrCon; t: PType): Z3_sort =
+  template ctx: untyped = c.z3
+  case t.skipTypes(abstractInst+{tyVar}).kind
+  of tyEnum, tyInt..tyInt64:
+    result = Z3_mk_int_sort(ctx)
+  of tyBool:
+    result = Z3_mk_bool_sort(ctx)
+  of tyFloat..tyFloat128:
+    result = Z3_mk_fpa_sort_double(ctx)
+  of tyChar, tyUInt..tyUInt64:
+    result = Z3_mk_bv_sort(ctx, 64)
+    #cuint(getSize(c.graph.config, t) * 8))
+  else:
+    notImplemented(typeToString(t))
+
+template binary(op, a, b): untyped =
+  var arr = [a, b]
+  op(ctx, cuint(2), addr(arr[0]))
+
+proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast
+
+template quantorToZ3(fn) {.dirty.} =
+  template ctx: untyped = c.z3
+
+  var bound = newSeq[Z3_app](n.len-1)
+  for i in 0..n.len-2:
+    doAssert n[i].kind == nkSym
+    let v = n[i].sym
+    let name = Z3_mk_string_symbol(ctx, v.name.s)
+    let vz3 = Z3_mk_const(ctx, name, typeToZ3(c, v.typ))
+    c.mapping[stableName(n[i])] = vz3
+    bound[i] = Z3_to_app(ctx, vz3)
+
+  var dummy: seq[PNode]
+  let x = nodeToZ3(c, n[^1], dummy)
+  result = fn(ctx, 0, bound.len.cuint, addr(bound[0]), 0, nil, x)
+
+proc forallToZ3(c: var DrCon; n: PNode): Z3_ast = quantorToZ3(Z3_mk_forall_const)
+proc existsToZ3(c: var DrCon; n: PNode): Z3_ast = quantorToZ3(Z3_mk_exists_const)
+
+proc paramName(n: PNode): string =
+  case n.sym.kind
+  of skParam: result = "arg" & $n.sym.position
+  of skResult: result = "result"
+  else: result = stableName(n)
+
+proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
+  template ctx: untyped = c.z3
+  template rec(n): untyped = nodeToZ3(c, n, vars)
+  case n.kind
+  of nkSym:
+    let key = if c.canonParameterNames: paramName(n) else: stableName(n)
+    result = c.mapping.getOrDefault(key)
+    if pointer(result) == nil:
+      let name = Z3_mk_string_symbol(ctx, n.sym.name.s)
+      result = Z3_mk_const(ctx, name, typeToZ3(c, n.sym.typ))
+      c.mapping[key] = result
+      vars.add n
+  of nkCharLit..nkUInt64Lit:
+    if n.typ != nil and n.typ.skipTypes(abstractInst).kind in {tyInt..tyInt64}:
+      # optimized for the common case
+      result = Z3_mk_int64(ctx, clonglong(n.intval), Z3_mk_int_sort(ctx))
+    elif n.typ != nil and n.typ.kind == tyBool:
+      result = if n.intval != 0: Z3_mk_true(ctx) else: Z3_mk_false(ctx)
+    elif n.typ != nil and isUnsigned(n.typ):
+      result = Z3_mk_unsigned_int64(ctx, cast[uint64](n.intVal), typeToZ3(c, n.typ))
+    else:
+      let zt = if n.typ == nil: Z3_mk_int_sort(ctx) else: typeToZ3(c, n.typ)
+      result = Z3_mk_numeral(ctx, $getOrdValue(n), zt)
+  of nkFloatLit..nkFloat64Lit:
+    result = Z3_mk_fpa_numeral_double(ctx, n.floatVal, Z3_mk_fpa_sort_double(ctx))
+  of nkCallKinds:
+    assert n.len > 0
+    assert n[0].kind == nkSym
+    let operator = n[0].sym.magic
+    case operator
+    of mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
+        mEqStr, mEqSet, mEqCString:
+      result = Z3_mk_eq(ctx, rec n[1], rec n[2])
+    of mLeI, mLeEnum, mLeCh, mLeB, mLePtr, mLeStr:
+      result = Z3_mk_le(ctx, rec n[1], rec n[2])
+    of mLtI, mLtEnum, mLtCh, mLtB, mLtPtr, mLtStr:
+      result = Z3_mk_lt(ctx, rec n[1], rec n[2])
+    of mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq:
+      # len(x) needs the same logic as 'x' itself
+      if n[1].kind == nkSym:
+        let key = stableName(n)
+        let sym = n[1].sym
+        result = c.mapping.getOrDefault(key)
+        if pointer(result) == nil:
+          let name = Z3_mk_string_symbol(ctx, sym.name.s & ".len")
+          result = Z3_mk_const(ctx, name, Z3_mk_int_sort(ctx))
+          c.mapping[key] = result
+          vars.add n
+      else:
+        notImplemented(renderTree(n))
+    of mAddI, mSucc:
+      result = binary(Z3_mk_add, rec n[1], rec n[2])
+    of mSubI, mPred:
+      result = binary(Z3_mk_sub, rec n[1], rec n[2])
+    of mMulI:
+      result = binary(Z3_mk_mul, rec n[1], rec n[2])
+    of mDivI:
+      result = Z3_mk_div(ctx, rec n[1], rec n[2])
+    of mModI:
+      result = Z3_mk_mod(ctx, rec n[1], rec n[2])
+    of mMaxI:
+      # max(a, b) <=> ite(a < b, b, a)
+      result = Z3_mk_ite(ctx, Z3_mk_lt(ctx, rec n[1], rec n[2]),
+        rec n[2], rec n[1])
+    of mMinI:
+      # min(a, b) <=> ite(a < b, a, b)
+      result = Z3_mk_ite(ctx, Z3_mk_lt(ctx, rec n[1], rec n[2]),
+        rec n[1], rec n[2])
+    of mLeU:
+      result = Z3_mk_bvule(ctx, rec n[1], rec n[2])
+    of mLtU:
+      result = Z3_mk_bvult(ctx, rec n[1], rec n[2])
+    of mAnd:
+      result = binary(Z3_mk_and, rec n[1], rec n[2])
+    of mOr:
+      result = binary(Z3_mk_or, rec n[1], rec n[2])
+    of mXor:
+      result = Z3_mk_xor(ctx, rec n[1], rec n[2])
+    of mNot:
+      result = Z3_mk_not(ctx, rec n[1])
+    of mImplies:
+      result = Z3_mk_implies(ctx, rec n[1], rec n[2])
+    of mIff:
+      result = Z3_mk_iff(ctx, rec n[1], rec n[2])
+    of mForall:
+      result = forallToZ3(c, n)
+    of mExists:
+      result = existsToZ3(c, n)
+    of mLeF64:
+      result = Z3_mk_fpa_leq(ctx, rec n[1], rec n[2])
+    of mLtF64:
+      result = Z3_mk_fpa_lt(ctx, rec n[1], rec n[2])
+    of mAddF64:
+      result = Z3_mk_fpa_add(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
+    of mSubF64:
+      result = Z3_mk_fpa_sub(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
+    of mMulF64:
+      result = Z3_mk_fpa_mul(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
+    of mDivF64:
+      result = Z3_mk_fpa_div(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
+    of mShrI:
+      # XXX handle conversions from int to uint here somehow
+      result = Z3_mk_bvlshr(ctx, rec n[1], rec n[2])
+    of mAshrI:
+      result = Z3_mk_bvashr(ctx, rec n[1], rec n[2])
+    of mShlI:
+      result = Z3_mk_bvshl(ctx, rec n[1], rec n[2])
+    of mBitandI:
+      result = Z3_mk_bvand(ctx, rec n[1], rec n[2])
+    of mBitorI:
+      result = Z3_mk_bvor(ctx, rec n[1], rec n[2])
+    of mBitxorI:
+      result = Z3_mk_bvxor(ctx, rec n[1], rec n[2])
+    of mOrd, mChr:
+      result = rec n[1]
+    of mOld:
+      let key = (if c.canonParameterNames: paramName(n[1]) else: stableName(n[1])) & ".old"
+      result = c.mapping.getOrDefault(key)
+      if pointer(result) == nil:
+        let name = Z3_mk_string_symbol(ctx, $n)
+        result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
+        c.mapping[key] = result
+        # XXX change the logic in `addRangeInfo` for this
+        #vars.add n
+
+    else:
+      # sempass2 adds some 'fact' like 'x = f(a, b)' (see addAsgnFact)
+      # 'f(a, b)' can have an .ensures annotation and we need to make use
+      # of this information.
+      # we need to map 'f(a, b)' to a Z3 variable of this name
+      let op = n[0].typ
+      if op != nil and op.n != nil and op.n.len > 0 and op.n[0].kind == nkEffectList and
+          ensuresEffects < op.n[0].len:
+        let ensures = op.n[0][ensuresEffects]
+        if ensures != nil and ensures.kind != nkEmpty:
+          let key = stableName(n)
+          result = c.mapping.getOrDefault(key)
+          if pointer(result) == nil:
+            let name = Z3_mk_string_symbol(ctx, $n)
+            result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
+            c.mapping[key] = result
+            vars.add n
+
+      if pointer(result) == nil:
+        notImplemented(renderTree(n))
+  of nkStmtListExpr, nkPar:
+    var isTrivial = true
+    for i in 0..n.len-2:
+      isTrivial = isTrivial and n[i].kind in {nkEmpty, nkCommentStmt}
+    if isTrivial:
+      result = nodeToZ3(c, n[^1], vars)
+    else:
+      notImplemented(renderTree(n))
+  of nkHiddenDeref:
+    result = rec n[0]
+  else:
+    notImplemented(renderTree(n))
+
+proc addRangeInfo(c: var DrCon, n: PNode, res: var seq[Z3_ast]) =
+  var cmpOp = mLeI
+  if n.typ != nil:
+    cmpOp =
+      case n.typ.skipTypes(abstractInst).kind
+      of tyFloat..tyFloat128: mLeF64
+      of tyChar, tyUInt..tyUInt64: mLeU
+      else: mLeI
+
+  var lowBound, highBound: PNode
+  if n.kind == nkSym:
+    let v = n.sym
+    let t = v.typ.skipTypes(abstractInst - {tyRange})
+
+    case t.kind
+    of tyRange:
+      lowBound = t.n[0]
+      highBound = t.n[1]
+    of tyFloat..tyFloat128:
+      # no range information for non-range'd floats
+      return
+    of tyUInt..tyUInt64, tyChar:
+      lowBound = newIntNode(nkUInt64Lit, firstOrd(nil, v.typ))
+      lowBound.typ = v.typ
+      highBound = newIntNode(nkUInt64Lit, lastOrd(nil, v.typ))
+      highBound.typ = v.typ
+    of tyInt..tyInt64, tyEnum:
+      lowBound = newIntNode(nkInt64Lit, firstOrd(nil, v.typ))
+      highBound = newIntNode(nkInt64Lit, lastOrd(nil, v.typ))
+    else:
+      # no range information available:
+      return
+  elif n.kind in nkCallKinds and n.len == 2 and n[0].kind == nkSym and
+      n[0].sym.magic in {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq}:
+    # we know it's a 'len(x)' expression and we seek to teach
+    # Z3 that the result is >= 0 and <= high(int).
+    doAssert n.kind in nkCallKinds
+    doAssert n[0].kind == nkSym
+    doAssert n.len == 2
+
+    lowBound = newIntNode(nkInt64Lit, 0)
+    if n.typ != nil:
+      highBound = newIntNode(nkInt64Lit, lastOrd(nil, n.typ))
+    else:
+      highBound = newIntNode(nkInt64Lit, high(int64))
+  else:
+    let op = n[0].typ
+    if op != nil and op.n != nil and op.n.len > 0 and op.n[0].kind == nkEffectList and
+        ensuresEffects < op.n[0].len:
+      let ensures = op.n[0][ensuresEffects]
+      if ensures != nil and ensures.kind != nkEmpty:
+        var dummy: seq[PNode]
+        res.add nodeToZ3(c, translateEnsures(ensures, n), dummy)
+    return
+
+  let x = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), lowBound, n)
+  let y = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), n, highBound)
+
+  var dummy: seq[PNode]
+  res.add nodeToZ3(c, x, dummy)
+  res.add nodeToZ3(c, y, dummy)
+
+proc on_err(ctx: Z3_context, e: Z3_error_code) {.nimcall.} =
+  #writeStackTrace()
+  let msg = $Z3_get_error_msg(ctx, e)
+  raise newException(Z3Exception, msg)
+
+proc forall(ctx: Z3_context; vars: seq[Z3_ast]; assumption, body: Z3_ast): Z3_ast =
+  let x = Z3_mk_implies(ctx, assumption, body)
+  if vars.len > 0:
+    var bound: seq[Z3_app]
+    for v in vars: bound.add Z3_to_app(ctx, v)
+    result = Z3_mk_forall_const(ctx, 0, bound.len.cuint, addr(bound[0]), 0, nil, x)
+  else:
+    result = x
+
+proc conj(ctx: Z3_context; conds: seq[Z3_ast]): Z3_ast =
+  if conds.len > 0:
+    result = Z3_mk_and(ctx, cuint(conds.len), unsafeAddr conds[0])
+  else:
+    result = Z3_mk_true(ctx)
+
+proc proofEngineAux(c: var DrCon; assumptions: seq[PNode]; toProve: PNode): (bool, string) =
+  c.mapping = initTable[string, Z3_ast]()
+  let cfg = Z3_mk_config()
+  Z3_set_param_value(cfg, "model", "true");
+  let ctx = Z3_mk_context(cfg)
+  c.z3 = ctx
+  Z3_del_config(cfg)
+  Z3_set_error_handler(ctx, on_err)
+
+  when false:
+    Z3_set_param_value(cfg, "timeout", "1000")
+
+  try:
+
+    #[
+    For example, let's have these facts:
+
+      i < 10
+      i > 0
+
+    Question:
+
+      i + 3 < 13
+
+    What we need to produce:
+
+    forall(i, (i < 10) & (i > 0) -> (i + 3 < 13))
+
+    ]#
+
+    var collectedVars: seq[PNode]
+
+    let solver = Z3_mk_solver(ctx)
+    var lhs: seq[Z3_ast]
+    for assumption in assumptions:
+      if assumption != nil:
+        try:
+          let za = nodeToZ3(c, assumption, collectedVars)
+          #Z3_solver_assert ctx, solver, za
+          lhs.add za
+        except CannotMapToZ3Error:
+          discard "ignore a fact we cannot map to Z3"
+
+    let z3toProve = nodeToZ3(c, toProve, collectedVars)
+    for v in collectedVars:
+      addRangeInfo(c, v, lhs)
+
+    # to make Z3 produce nice counterexamples, we try to prove the
+    # negation of our conjecture and see if it's Z3_L_FALSE
+    let fa = Z3_mk_not(ctx, Z3_mk_implies(ctx, conj(ctx, lhs), z3toProve))
+
+    #Z3_mk_not(ctx, forall(ctx, collectedVars, conj(ctx, lhs), z3toProve))
+
+    #echo "toProve: ", Z3_ast_to_string(ctx, fa), " ", c.graph.config $ toProve.info
+    Z3_solver_assert ctx, solver, fa
+
+    let z3res = Z3_solver_check(ctx, solver)
+    result[0] = z3res == Z3_L_FALSE
+    result[1] = ""
+    if not result[0]:
+      let counterex = strip($Z3_model_to_string(ctx, Z3_solver_get_model(ctx, solver)))
+      if counterex.len > 0:
+        result[1].add "; counter example: " & counterex
+  except ValueError:
+    result[0] = false
+    result[1] = getCurrentExceptionMsg()
+  finally:
+    Z3_del_context(ctx)
+
+proc proofEngine(graph: ModuleGraph; assumptions: seq[PNode]; toProve: PNode): (bool, string) =
+  var c: DrCon
+  c.graph = graph
+  result = proofEngineAux(c, assumptions, toProve)
+
+proc translateReq(r, call: PNode): PNode =
+  if r.kind == nkSym and r.sym.kind == skParam:
+    if r.sym.position+1 < call.len:
+      result = call[r.sym.position+1]
+    else:
+      notImplemented("no argument given for formal parameter: " & r.sym.name.s)
+  else:
+    result = shallowCopy(r)
+    for i in 0 ..< safeLen(r):
+      result[i] = translateReq(r[i], call)
+
+proc requirementsCheck(graph: ModuleGraph; assumptions: seq[PNode];
+                      call, requirement: PNode): (bool, string) {.nimcall.} =
+  try:
+    let r = translateReq(requirement, call)
+    result = proofEngine(graph, assumptions, r)
+  except ValueError:
+    result[0] = false
+    result[1] = getCurrentExceptionMsg()
+
+proc compatibleProps(graph: ModuleGraph; formal, actual: PType): bool {.nimcall.} =
+  #[
+  Thoughts on subtyping rules for 'proc' types:
+
+    proc a(y: int) {.requires: y > 0.}  # a is 'weaker' than F
+    # 'requires' must be weaker (or equal)
+    # 'ensures'  must be stronger (or equal)
+
+    # a 'is weaker than' b iff  b -> a
+    # a 'is stronger than' b iff a -> b
+    # --> We can use Z3 to compute whether 'var x: T = q' is valid
+
+    type
+      F = proc (y: int) {.requires: y > 5.}
+
+    var
+      x: F = a # valid?
+  ]#
+  proc isEmpty(n: PNode): bool {.inline.} = n == nil or n.safeLen == 0
+
+  result = true
+  if formal.n != nil and formal.n.len > 0 and formal.n[0].kind == nkEffectList and
+      ensuresEffects < formal.n[0].len:
+
+    let frequires = formal.n[0][requiresEffects]
+    let fensures = formal.n[0][ensuresEffects]
+
+    if actual.n != nil and actual.n.len > 0 and actual.n[0].kind == nkEffectList and
+        ensuresEffects < actual.n[0].len:
+      let arequires = actual.n[0][requiresEffects]
+      let aensures = actual.n[0][ensuresEffects]
+
+      var c: DrCon
+      c.graph = graph
+      c.canonParameterNames = true
+      if not frequires.isEmpty:
+        result = not arequires.isEmpty and proofEngineAux(c, @[frequires], arequires)[0]
+
+      if result:
+        if not fensures.isEmpty:
+          result = not aensures.isEmpty and proofEngineAux(c, @[aensures], fensures)[0]
+    else:
+      # formal has requirements but 'actual' has none, so make it
+      # incompatible. XXX What if the requirement only mentions that
+      # we already know from the type system?
+      result = frequires.isEmpty and fensures.isEmpty
+
+proc mainCommand(graph: ModuleGraph) =
+  let conf = graph.config
+  conf.lastCmdTime = epochTime()
+
+  graph.proofEngine = proofEngine
+  graph.requirementsCheck = requirementsCheck
+  graph.compatibleProps = compatibleProps
+
+  graph.config.errorMax = high(int)  # do not stop after first error
+  defineSymbol(graph.config.symbols, "nimcheck")
+  defineSymbol(graph.config.symbols, "nimDrNim")
+
+  registerPass graph, verbosePass
+  registerPass graph, semPass
+  compileProject(graph)
+  if conf.errorCounter == 0:
+    let mem =
+      when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
+      else: formatSize(getTotalMem()) & " totmem"
+    let loc = $conf.linesCompiled
+    let build = if isDefined(conf, "danger"): "Dangerous Release"
+                elif isDefined(conf, "release"): "Release"
+                else: "Debug"
+    let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
+    let project = if optListFullPaths in conf.globalOptions: $conf.projectFull else: $conf.projectName
+    var output = $conf.absOutFile
+    if optListFullPaths notin conf.globalOptions: output = output.AbsoluteFile.extractFilename
+    rawMessage(conf, hintSuccessX, [
+      "loc", loc,
+      "sec", sec,
+      "mem", mem,
+      "build", build,
+      "project", project,
+      "output", output,
+      ])
+
+proc prependCurDir(f: AbsoluteFile): AbsoluteFile =
+  when defined(unix):
+    if os.isAbsolute(f.string): result = f
+    else: result = AbsoluteFile("./" & f.string)
+  else:
+    result = f
+
+proc addCmdPrefix(result: var string, kind: CmdLineKind) =
+  # consider moving this to std/parseopt
+  case kind
+  of cmdLongOption: result.add "--"
+  of cmdShortOption: result.add "-"
+  of cmdArgument, cmdEnd: discard
+
+proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
+  var p = parseopt.initOptParser(cmd)
+  var argsCount = 1
+
+  config.commandLine.setLen 0
+  config.command = "check"
+  config.cmd = cmdCheck
+
+  while true:
+    parseopt.next(p)
+    case p.kind
+    of cmdEnd: break
+    of cmdLongOption, cmdShortOption:
+      config.commandLine.add " "
+      config.commandLine.addCmdPrefix p.kind
+      config.commandLine.add p.key.quoteShell # quoteShell to be future proof
+      if p.val.len > 0:
+        config.commandLine.add ':'
+        config.commandLine.add p.val.quoteShell
+
+      if p.key == " ":
+        p.key = "-"
+        if processArgument(pass, p, argsCount, config): break
+      else:
+        processSwitch(pass, p, config)
+    of cmdArgument:
+      config.commandLine.add " "
+      config.commandLine.add p.key.quoteShell
+      if processArgument(pass, p, argsCount, config): break
+  if pass == passCmd2:
+    if {optRun, optWasNimscript} * config.globalOptions == {} and
+        config.arguments.len > 0 and config.command.normalize notin ["run", "e"]:
+      rawMessage(config, errGenerated, errArgsNeedRunOption)
+
+proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
+  let self = NimProg(
+    supportsStdinFile: true,
+    processCmdLine: processCmdLine,
+    mainCommand: mainCommand
+  )
+  self.initDefinesProg(conf, "drnim")
+  if paramCount() == 0:
+    helpOnError(conf)
+    return
+
+  self.processCmdLineAndProjectPath(conf)
+  if not self.loadConfigsAndRunMainCommand(cache, conf): return
+  if conf.hasHint(hintGCStats): echo(GC_getStatistics())
+
+when compileOption("gc", "v2") or compileOption("gc", "refc"):
+  # the new correct mark&sweep collector is too slow :-/
+  GC_disableMarkAndSweep()
+
+when not defined(selftest):
+  let conf = newConfigRef()
+  handleCmdLine(newIdentCache(), conf)
+  when declared(GC_setMaxPause):
+    echo GC_getStatistics()
+  msgQuit(int8(conf.errorCounter > 0))
diff --git a/drnim/nim.cfg b/drnim/nim.cfg
new file mode 100644
index 000000000..58c1725d9
--- /dev/null
+++ b/drnim/nim.cfg
@@ -0,0 +1,18 @@
+# Special configuration file for the Nim project
+
+hint[XDeclaredButNotUsed]:off
+
+define:booting
+define:nimcore
+define:drnim
+
+@if windows:
+  cincludes: "$lib/wrappers/libffi/common"
+@end
+
+define:useStdoutAsStdmsg
+
+--path: "../../nimz3/src"
+--path: "../dist/nimz3/src"
+
+#define:useNodeIds
diff --git a/drnim/tests/config.nims b/drnim/tests/config.nims
new file mode 100644
index 000000000..cd4ee4b08
--- /dev/null
+++ b/drnim/tests/config.nims
@@ -0,0 +1,7 @@
+switch("path", "$nim/testament/lib") # so we can `import stdtest/foo` in this dir
+
+## prevent common user config settings to interfere with testament expectations
+## Indifidual tests can override this if needed to test for these options.
+switch("colors", "off")
+switch("listFullPaths", "off")
+switch("excessiveStackTrace", "off")
diff --git a/drnim/tests/tbasic_array_index.nim b/drnim/tests/tbasic_array_index.nim
new file mode 100644
index 000000000..3f24ed3bd
--- /dev/null
+++ b/drnim/tests/tbasic_array_index.nim
@@ -0,0 +1,43 @@
+discard """
+  nimout: '''tbasic_array_index.nim(26, 17) Warning: cannot prove: 0 <= len(a) - 4; counter example: a.len -> 0 [IndexCheck]
+tbasic_array_index.nim(32, 5) Warning: cannot prove: 4.0 <= 1.0 [IndexCheck]
+tbasic_array_index.nim(38, 36) Warning: cannot prove: a <= 10'u32; counter example: a -> #x000000000000000b
+'''
+  cmd: "drnim $file"
+  action: "compile"
+"""
+
+{.push staticBoundChecks: defined(nimDrNim).}
+
+proc takeNat(n: Natural) =
+  discard
+
+proc p(a, b: openArray[int]) =
+  if a.len > 0:
+    echo a[0]
+
+  for i in 0..a.len-8:
+    #{.invariant: i < a.len.}
+    echo a[i]
+
+  for i in 0..min(a.len, b.len)-1:
+    echo a[i], " ", b[i]
+
+  takeNat(a.len - 4)
+
+proc r(x: range[0.0..1.0]) = echo x
+
+proc sum() =
+  r 1.0
+  r 4.0
+
+proc ru(x: range[1u32..10u32]) = echo x
+
+proc xu(a: uint) =
+  if a >= 4u:
+    let chunk = range[1u32..10u32](a)
+    ru chunk
+
+{.pop.}
+
+p([1, 2, 3], [4, 5])
diff --git a/drnim/tests/tensures.nim b/drnim/tests/tensures.nim
new file mode 100644
index 000000000..1283b493b
--- /dev/null
+++ b/drnim/tests/tensures.nim
@@ -0,0 +1,31 @@
+discard """
+  nimout: '''tensures.nim(11, 10) Warning: BEGIN [User]
+tensures.nim(20, 5) Warning: cannot prove:
+0 < n [IndexCheck]
+tensures.nim(30, 10) Warning: END [User]'''
+  cmd: "drnim $file"
+  action: "compile"
+"""
+
+{.push staticBoundChecks: defined(nimDrNim).}
+{.warning: "BEGIN".}
+
+proc fac(n: int) {.requires: n > 0.} =
+  discard
+
+proc g(): int {.ensures: result > 0.} =
+  result = 10
+
+fac 7 # fine
+fac -45 # wrong
+
+fac g() # fine
+
+proc main =
+  var x = g()
+  fac x
+
+main()
+
+{.warning: "END".}
+{.pop.}
diff --git a/drnim/tests/tsetlen_invalidates.nim b/drnim/tests/tsetlen_invalidates.nim
new file mode 100644
index 000000000..03620af10
--- /dev/null
+++ b/drnim/tests/tsetlen_invalidates.nim
@@ -0,0 +1,22 @@
+discard """
+  nimout: '''
+tsetlen_invalidates.nim(15, 12) Warning: cannot prove: 0 <= len(a) + -1; counter example: a.len -> 0 [IndexCheck]
+'''
+  cmd: "drnim $file"
+  action: "compile"
+"""
+
+{.push staticBoundChecks: defined(nimDrNim).}
+
+proc p() =
+  var a = newSeq[int](3)
+  if a.len > 0:
+    a.setLen 0
+    echo a[0]
+
+  if a.len > 0:
+    echo a[0]
+
+{.pop.}
+
+p()
diff --git a/koch.nim b/koch.nim
index b05b56916..511b78df6 100644
--- a/koch.nim
+++ b/koch.nim
@@ -12,6 +12,10 @@
 const
   NimbleStableCommit = "4007b2a778429a978e12307bf13a038029b4c4d9" # master
 
+when not defined(windows):
+  const
+    Z3StableCommit = "65de3f748a6812eecd7db7c478d5fc54424d368b" # the version of Z3 that DrNim uses
+
 when defined(gcc) and defined(windows):
   when defined(x86):
     {.link: "icons/koch.res".}
@@ -473,6 +477,29 @@ proc xtemp(cmd: string) =
   finally:
     copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe)
 
+proc buildDrNim(args: string) =
+  if not dirExists("dist/nimz3"):
+    exec("git clone https://github.com/zevv/nimz3.git dist/nimz3")
+  when defined(windows):
+    if not dirExists("dist/dlls"):
+      exec("git clone -q https://github.com/nim-lang/dlls.git dist/dlls")
+    copyExe("dist/dlls/libz3.dll", "bin/libz3.dll")
+    execFold("build drnim", "nim c -o:$1 $2 drnim/drnim" % ["bin/drnim".exe, args])
+  else:
+    if not dirExists("dist/z3"):
+      exec("git clone -q https://github.com/Z3Prover/z3.git dist/z3")
+      withDir("dist/z3"):
+        exec("git fetch")
+        exec("git checkout " & Z3StableCommit)
+        createDir("build")
+        withDir("build"):
+          exec("""cmake -DZ3_BUILD_LIBZ3_SHARED=FALSE -G "Unix Makefiles" ../""")
+          exec("make -j4")
+    execFold("build drnim", "nim cpp --dynlibOverride=libz3 -o:$1 $2 drnim/drnim" % ["bin/drnim".exe, args])
+  # always run the tests for now:
+  exec("testament/testament".exe & " --nim:" & "drnim".exe & " pat drnim/tests")
+
+
 proc hostInfo(): string =
   "hostOS: $1, hostCPU: $2, int: $3, float: $4, cpuEndian: $5, cwd: $6" %
     [hostOS, hostCPU, $int.sizeof, $float.sizeof, $cpuEndian, getCurrentDir()]
@@ -636,6 +663,7 @@ when isMainModule:
       of "pushcsource", "pushcsources": pushCsources()
       of "valgrind": valgrind(op.cmdLineRest)
       of "c2nim": bundleC2nim(op.cmdLineRest)
+      of "drnim": buildDrNim(op.cmdLineRest)
       else: showHelp()
       break
     of cmdEnd: break
diff --git a/lib/std/logic.nim b/lib/std/logic.nim
new file mode 100644
index 000000000..3cc871a6e
--- /dev/null
+++ b/lib/std/logic.nim
@@ -0,0 +1,10 @@
+## This module provides further logic operators like 'forall' and 'exists'
+## They are only supported in ``.ensures`` etc pragmas.
+
+proc `->`*(a, b: bool): bool {.magic: "Implies".}
+proc `<->`*(a, b: bool): bool {.magic: "Iff".}
+
+proc forall*(args: varargs[untyped]): bool {.magic: "Forall".}
+proc exists*(args: varargs[untyped]): bool {.magic: "Exists".}
+
+proc old*[T](x: T): T {.magic: "Old".}
diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim
index a111ec563..c3ac5fc1b 100644
--- a/lib/system/strmantle.nim
+++ b/lib/system/strmantle.nim
@@ -115,6 +115,9 @@ const
               1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
               1e20, 1e21, 1e22]
 
+when defined(nimHasInvariant):
+  {.push staticBoundChecks: off.}
+
 proc nimParseBiggestFloat(s: string, number: var BiggestFloat,
                           start = 0): int {.compilerproc.} =
   # This routine attempt to parse float that can parsed quickly.
@@ -236,7 +239,7 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat,
     # if exponent is greater try to fit extra exponent above 22 by multiplying
     # integer part is there is space left.
     let slop = 15 - kdigits - fdigits
-    if  absExponent <= 22 + slop and not expNegative:
+    if absExponent <= 22 + slop and not expNegative:
       number = sign * integer.float * powtens[slop] * powtens[absExponent-slop]
       return i - start
 
@@ -274,6 +277,9 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat,
   else:
     number = c_strtod(t, nil)
 
+when defined(nimHasInvariant):
+  {.pop.} # staticBoundChecks
+
 proc nimInt64ToStr(x: int64): string {.compilerRtl.} =
   result = newStringOfCap(sizeof(x)*4)
   result.addInt x
diff --git a/testament/categories.nim b/testament/categories.nim
index c6ebadfca..601e9306e 100644
--- a/testament/categories.nim
+++ b/testament/categories.nim
@@ -728,12 +728,22 @@ proc processCategory(r: var TResults, cat: Category,
 
 proc processPattern(r: var TResults, pattern, options: string; simulate: bool) =
   var testsRun = 0
-  for name in walkPattern(pattern):
-    if simulate:
-      echo "Detected test: ", name
-    else:
-      var test = makeTest(name, options, Category"pattern")
-      testSpec r, test
-    inc testsRun
+  if dirExists(pattern):
+    for k, name in walkDir(pattern):
+      if k in {pcFile, pcLinkToFile} and name.endsWith(".nim"):
+        if simulate:
+          echo "Detected test: ", name
+        else:
+          var test = makeTest(name, options, Category"pattern")
+          testSpec r, test
+        inc testsRun
+  else:
+    for name in walkPattern(pattern):
+      if simulate:
+        echo "Detected test: ", name
+      else:
+        var test = makeTest(name, options, Category"pattern")
+        testSpec r, test
+      inc testsRun
   if testsRun == 0:
     echo "no tests were found for pattern: ", pattern
diff --git a/testament/testament.nim.cfg b/testament/testament.nim.cfg
index 69f044554..f1fcd95f3 100644
--- a/testament/testament.nim.cfg
+++ b/testament/testament.nim.cfg
@@ -1,2 +1,4 @@
 path = "$nim" # For compiler/nodejs
 -d:ssl # For azure
+# my SSL doesn't have this feature and I don't care:
+-d:nimDisableCertificateValidation
diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim
index 0dd6dbc2f..a23d17b21 100644
--- a/tools/kochdocs.nim
+++ b/tools/kochdocs.nim
@@ -110,6 +110,7 @@ doc/tut2.rst
 doc/tut3.rst
 doc/nimc.rst
 doc/hcr.rst
+doc/drnim.rst
 doc/overview.rst
 doc/filters.rst
 doc/tools.rst