drnim: tiny progress (#13882)

* drnim: tiny progress
* refactoring complete
* drnim: prove .ensures annotations
* Moved code around to avoid code duplication
* drnim: first implementation of the 'old' property
* drnim: be precise about the assignment statement
* first implementation of --assumeUnique
* progress on forall/exists handling
This commit is contained in:
Andreas Rumpf 2020-04-15 20:03:25 +02:00 • committed by GitHub
commit 3a2697dd73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 755 additions and 259 deletions

View file

@ -218,7 +218,7 @@ type
nkEnumFieldDef, # `ident = expr` in an enumeration nkEnumFieldDef, # `ident = expr` in an enumeration
nkArgList, # argument list nkArgList, # argument list
nkPattern, # a special pattern; used for matching nkPattern, # a special pattern; used for matching
nkHiddenTryStmt, # token used for interpretation nkHiddenTryStmt, # a hidden try statement
nkClosure, # (prc, env)-pair (internally used for code gen) nkClosure, # (prc, env)-pair (internally used for code gen)
nkGotoState, # used for the state machine (for iterators) nkGotoState, # used for the state machine (for iterators)
nkState, # give a label to a code section (for iterators) nkState, # give a label to a code section (for iterators)

View file

@ -11,10 +11,24 @@
import import
options, idents, nimconf, extccomp, commands, msgs, options, idents, nimconf, extccomp, commands, msgs,
lineinfos, modulegraphs, condsyms, os, pathutils lineinfos, modulegraphs, condsyms, os, pathutils, parseopt
from strutils import normalize from strutils import normalize
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
type type
NimProg* = ref object NimProg* = ref object
suggestMode*: bool suggestMode*: bool

View file

@ -84,8 +84,8 @@ proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true)
type type
Operators* = object Operators* = object
opNot, opContains, opLe, opLt, opAnd, opOr, opIsNil, opEq: PSym opNot*, opContains*, opLe*, opLt*, opAnd*, opOr*, opIsNil*, opEq*: PSym
opAdd, opSub, opMul, opDiv, opLen: PSym opAdd*, opSub*, opMul*, opDiv*, opLen*: PSym
proc initOperators*(g: ModuleGraph): Operators = proc initOperators*(g: ModuleGraph): Operators =
result.opLe = createMagic(g, "<=", mLeI) result.opLe = createMagic(g, "<=", mLeI)
@ -156,12 +156,12 @@ proc neg(n: PNode; o: Operators): PNode =
result[0] = newSymNode(o.opNot) result[0] = newSymNode(o.opNot)
result[1] = n result[1] = n
proc buildCall(op: PSym; a: PNode): PNode = proc buildCall*(op: PSym; a: PNode): PNode =
result = newNodeI(nkCall, a.info, 2) result = newNodeI(nkCall, a.info, 2)
result[0] = newSymNode(op) result[0] = newSymNode(op)
result[1] = a result[1] = a
proc buildCall(op: PSym; a, b: PNode): PNode = proc buildCall*(op: PSym; a, b: PNode): PNode =
result = newNodeI(nkInfix, a.info, 3) result = newNodeI(nkInfix, a.info, 3)
result[0] = newSymNode(op) result[0] = newSymNode(op)
result[1] = a result[1] = a
@ -464,7 +464,7 @@ proc hasSubTree(n, x: PNode): bool =
for i in 0..n.safeLen-1: for i in 0..n.safeLen-1:
if hasSubTree(n[i], x): return true if hasSubTree(n[i], x): return true
proc invalidateFacts*(m: var TModel, n: PNode) = proc invalidateFacts*(s: var seq[PNode], n: PNode) =
# We are able to guard local vars (as opposed to 'let' variables)! # We are able to guard local vars (as opposed to 'let' variables)!
# 'while p != nil: f(p); p = p.next' # 'while p != nil: f(p); p = p.next'
# This is actually quite easy to do: # This is actually quite easy to do:
@ -482,8 +482,11 @@ proc invalidateFacts*(m: var TModel, n: PNode) =
# The same mechanism could be used for more complex data stored on the heap; # The same mechanism could be used for more complex data stored on the heap;
# procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we # procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we
# could CSE these expressions then and help C's optimizer. # could CSE these expressions then and help C's optimizer.
for i in 0..high(m.s): for i in 0..high(s):
if m.s[i] != nil and m.s[i].hasSubTree(n): m.s[i] = nil if s[i] != nil and s[i].hasSubTree(n): s[i] = nil
proc invalidateFacts*(m: var TModel, n: PNode) =
invalidateFacts(m.s, n)
proc valuesUnequal(a, b: PNode): bool = proc valuesUnequal(a, b: PNode): bool =
if a.isValue and b.isValue: if a.isValue and b.isValue:

View file

@ -72,9 +72,7 @@ type
onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
globalDestructors*: seq[PNode] globalDestructors*: seq[PNode]
proofEngine*: proc (graph: ModuleGraph; assumptions: seq[PNode]; toProve: PNode): (bool, string) {.nimcall.} strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.}
requirementsCheck*: proc (graph: ModuleGraph; assumptions: seq[PNode];
call, requirement: PNode): (bool, string) {.nimcall.}
compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.} compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
TPassContext* = object of RootObj # the pass's context TPassContext* = object of RootObj # the pass's context

View file

@ -33,20 +33,6 @@ when defined(profiler) or defined(memProfiler):
{.hint: "Profiling support is turned on!".} {.hint: "Profiling support is turned on!".}
import nimprof import nimprof
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) = proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
var p = parseopt.initOptParser(cmd) var p = parseopt.initOptParser(cmd)
var argsCount = 0 var argsCount = 0

View file

@ -53,7 +53,7 @@ const
wDeprecated, wDeprecated,
wFloatChecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll, wFloatChecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll,
wLinearScanEnd, wPatterns, wTrMacros, wEffects, wNoForward, wReorder, wComputedGoto, wLinearScanEnd, wPatterns, wTrMacros, wEffects, wNoForward, wReorder, wComputedGoto,
wInjectStmt, wExperimental, wThis, wUsed, wInvariant, wAssume} wInjectStmt, wExperimental, wThis, wUsed, wInvariant, wAssume, wAssert}
lambdaPragmas* = declPragmas + {FirstCallConv..LastCallConv, lambdaPragmas* = declPragmas + {FirstCallConv..LastCallConv,
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader, wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
wThread, wAsmNoStackFrame, wThread, wAsmNoStackFrame,

View file

@ -429,15 +429,29 @@ proc turnFinalizerIntoDestructor(c: PContext; orig: PSym; info: TLineInfo): PSym
result.typ.addParam newParam result.typ.addParam newParam
proc semQuantifier(c: PContext; n: PNode): PNode = proc semQuantifier(c: PContext; n: PNode): PNode =
checkMinSonsLen(n, 2, c.config) checkSonsLen(n, 2, c.config)
openScope(c) openScope(c)
for i in 0..n.len-2: result = newNodeIT(n.kind, n.info, n.typ)
let v = newSymS(skForVar, n[i], c) result.add n[0]
styleCheckDef(c.config, v) let args = n[1]
onDef(n.info, v) assert args.kind == nkArgList
n[i] = newSymNode(v) for i in 0..args.len-2:
addDecl(c, v) let it = args[i]
n[^1] = forceBool(c, semExprWithType(c, n[^1])) var valid = false
if it.kind == nkInfix:
let op = considerQuotedIdent(c, it[0])
if op.id == ord(wIn):
let v = newSymS(skForVar, it[1], c)
styleCheckDef(c.config, v)
onDef(it[1].info, v)
let domain = semExprWithType(c, it[2], {efWantIterator})
v.typ = domain.typ
valid = true
addDecl(c, v)
result.add newTree(nkInfix, it[0], newSymNode(v), domain)
if not valid:
localError(c.config, n.info, "<quantifier> 'in' <range> expected")
result.add forceBool(c, semExprWithType(c, args[^1]))
closeScope(c) closeScope(c)
proc semOld(c: PContext; n: PNode): PNode = proc semOld(c: PContext; n: PNode): PNode =

View file

@ -25,7 +25,7 @@ import
ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs, ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs,
renderer, types, modulegraphs, options, spawn, lineinfos renderer, types, modulegraphs, options, spawn, lineinfos
from trees import getMagic from trees import getMagic, isTrue, getRoot
from strutils import `%` from strutils import `%`
discard """ discard """

View file

@ -433,7 +433,8 @@ proc isForwardedProc(n: PNode): bool =
proc trackPragmaStmt(tracked: PEffects, n: PNode) = proc trackPragmaStmt(tracked: PEffects, n: PNode) =
for i in 0..<n.len: for i in 0..<n.len:
var it = n[i] var it = n[i]
if whichPragma(it) == wEffects: let pragma = whichPragma(it)
if pragma == wEffects:
# list the computed effects up to here: # list the computed effects up to here:
listEffects(tracked) listEffects(tracked)
@ -664,10 +665,6 @@ proc trackBlock(tracked: PEffects, n: PNode) =
else: else:
track(tracked, n) track(tracked, n)
proc isTrue*(n: PNode): bool =
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
n.kind == nkIntLit and n.intVal != 0
proc paramType(op: PType, i: int): PType = proc paramType(op: PType, i: int): PType =
if op != nil and i < op.len: result = op[i] if op != nil and i < op.len: result = op[i]
@ -676,14 +673,6 @@ proc cstringCheck(tracked: PEffects; n: PNode) =
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}): a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
message(tracked.config, n.info, warnUnsafeCode, renderTree(n)) 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) = proc patchResult(c: PEffects; n: PNode) =
if n.kind == nkSym and n.sym.kind == skResult: if n.kind == nkSym and n.sym.kind == skResult:
let fn = c.owner let fn = c.owner
@ -695,45 +684,18 @@ proc patchResult(c: PEffects; n: PNode) =
for i in 0..<safeLen(n): for i in 0..<safeLen(n):
patchResult(c, n[i]) 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) = proc checkLe(c: PEffects; a, b: PNode) =
if c.graph.proofEngine != nil: case proveLe(c.guards, a, b)
var cmpOp = mLeI of impUnknown:
if a.typ != nil: #for g in c.guards.s:
case a.typ.skipTypes(abstractInst).kind # if g != nil: echo "I Know ", g
of tyFloat..tyFloat128: cmpOp = mLeF64 message(c.config, a.info, warnStaticIndexCheck,
of tyChar, tyUInt..tyUInt64: cmpOp = mLeU "cannot prove: " & $a & " <= " & $b)
else: discard of impYes:
discard
let cmp = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), a, b) of impNo:
cmp.info = a.info message(c.config, a.info, warnStaticIndexCheck,
discard prove(c, cmp) "can prove: " & $a & " > " & $b)
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) = proc checkBounds(c: PEffects; arr, idx: PNode) =
checkLe(c, lowBound(c.config, arr), idx) checkLe(c, lowBound(c.config, arr), idx)
@ -831,7 +793,6 @@ proc track(tracked: PEffects, n: PNode) =
mergeEffects(tracked, effectList[exceptionEffects], n) mergeEffects(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n) mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck() gcsafeAndSideeffectCheck()
requiresCheck(tracked, n, op)
if a.kind != nkSym or a.sym.magic != mNBindSym: if a.kind != nkSym or a.sym.magic != mNBindSym:
for i in 1..<n.len: trackOperand(tracked, n[i], paramType(op, i), a) 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}: if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
@ -929,7 +890,6 @@ proc track(tracked: PEffects, n: PNode) =
of nkWhen, nkIfStmt, nkIfExpr: trackIf(tracked, n) of nkWhen, nkIfStmt, nkIfExpr: trackIf(tracked, n)
of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n[1]) of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n[1])
of nkWhileStmt: of nkWhileStmt:
track(tracked, n[0])
# 'while true' loop? # 'while true' loop?
if isTrue(n[0]): if isTrue(n[0]):
trackBlock(tracked, n[1]) trackBlock(tracked, n[1])
@ -938,6 +898,7 @@ proc track(tracked: PEffects, n: PNode) =
let oldState = tracked.init.len let oldState = tracked.init.len
let oldFacts = tracked.guards.s.len let oldFacts = tracked.guards.s.len
addFact(tracked.guards, n[0]) addFact(tracked.guards, n[0])
track(tracked, n[0])
track(tracked, n[1]) track(tracked, n[1])
setLen(tracked.init, oldState) setLen(tracked.init, oldState)
setLen(tracked.guards.s, oldFacts) setLen(tracked.guards.s, oldFacts)
@ -1027,12 +988,6 @@ proc track(tracked: PEffects, n: PNode) =
enforcedGcSafety = true enforcedGcSafety = true
elif pragma == wNoSideEffect: elif pragma == wNoSideEffect:
enforceNoSideEffects = true enforceNoSideEffects = true
when defined(drnim):
if pragma == wAssume:
addFact(tracked.guards, pragmaList[i][1])
elif pragma == wInvariant or pragma == wAssert:
if prove(tracked, pragmaList[i][1]):
addFact(tracked.guards, pragmaList[i][1])
if enforcedGcSafety: tracked.inEnforcedGcSafe = true if enforcedGcSafety: tracked.inEnforcedGcSafe = true
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
@ -1181,7 +1136,10 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PC
t.init = @[] t.init = @[]
t.guards.s = @[] t.guards.s = @[]
t.guards.o = initOperators(g) t.guards.o = initOperators(g)
t.currOptions = g.config.options + s.options when defined(drnim):
t.currOptions = g.config.options + s.options - {optStaticBoundsCheck}
else:
t.currOptions = g.config.options + s.options
t.guards.beSmart = optStaticBoundsCheck in t.currOptions t.guards.beSmart = optStaticBoundsCheck in t.currOptions
t.locked = @[] t.locked = @[]
t.graph = g t.graph = g
@ -1237,7 +1195,6 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
if not isNil(ensuresSpec): if not isNil(ensuresSpec):
patchResult(t, ensuresSpec) patchResult(t, ensuresSpec)
effects[ensuresEffects] = ensuresSpec effects[ensuresEffects] = ensuresSpec
discard prove(t, ensuresSpec)
if sfThread in s.flags and t.gcUnsafe: if sfThread in s.flags and t.gcUnsafe:
if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions: if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions:
@ -1262,6 +1219,8 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
message(g.config, s.info, warnLockLevel, message(g.config, s.info, warnLockLevel,
"declared lock level is $1, but real lock level is $2" % "declared lock level is $1, but real lock level is $2" %
[$s.typ.lockLevel, $t.maxLockLevel]) [$s.typ.lockLevel, $t.maxLockLevel])
when defined(drnim):
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, s, body)
when defined(useDfa): when defined(useDfa):
if s.name.s == "testp": if s.name.s == "testp":
dataflowAnalysis(s, body) dataflowAnalysis(s, body)
@ -1277,3 +1236,5 @@ proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) =
initEffects(g, effects, module, t, c) initEffects(g, effects, module, t, c)
t.isTopLevel = isTopLevel t.isTopLevel = isTopLevel
track(t, n) track(t, n)
when defined(drnim):
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)

View file

@ -11,7 +11,7 @@
import ast, types, idents, magicsys, msgs, options, modulegraphs, import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings lowerings
from trees import getMagic from trees import getMagic, getRoot
proc callProc(a: PNode): PNode = proc callProc(a: PNode): PNode =
result = newNodeI(nkCall, a.info) result = newNodeI(nkCall, a.info)
@ -74,7 +74,7 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; owner: PSym; typ: P
varInit.add call varInit.add call
else: else:
varInit.add newFastAsgnStmt(newSymNode(result), v) varInit.add newFastAsgnStmt(newSymNode(result), v)
else: else:
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions: if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
varInit.add newFastAsgnStmt(newSymNode(result), v) varInit.add newFastAsgnStmt(newSymNode(result), v)
else: else:
@ -196,7 +196,7 @@ proc createCastExpr(argsParam: PSym; objType: PType): PNode =
result.typ = newType(tyPtr, objType.owner) result.typ = newType(tyPtr, objType.owner)
result.typ.rawAddSon(objType) result.typ.rawAddSon(objType)
proc setupArgsForConcurrency(g: ModuleGraph; n: PNode; objType: PType; proc setupArgsForConcurrency(g: ModuleGraph; n: PNode; objType: PType;
owner: PSym; scratchObj: PSym, owner: PSym; scratchObj: PSym,
castExpr, call, castExpr, call,
varSection, varInit, result: PNode) = varSection, varInit, result: PNode) =
@ -221,23 +221,6 @@ proc setupArgsForConcurrency(g: ModuleGraph; n: PNode; objType: PType;
indirectAccess(castExpr, field, n.info)) indirectAccess(castExpr, field, n.info))
call.add(newSymNode(temp)) call.add(newSymNode(temp))
proc getRoot*(n: PNode): PSym =
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
## determined as the owner; ``obj`` in the example.
case n.kind
of nkSym:
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar}:
result = n.sym
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = getRoot(n[0])
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = getRoot(n[1])
of nkCallKinds:
if getMagic(n) == mSlice: result = getRoot(n[1])
else: discard
proc setupArgsForParallelism(g: ModuleGraph; n: PNode; objType: PType; proc setupArgsForParallelism(g: ModuleGraph; n: PNode; objType: PType;
owner: PSym; scratchObj: PSym; owner: PSym; scratchObj: PSym;
castExpr, call, castExpr, call,
@ -344,7 +327,7 @@ proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: P
if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}:
localError(g.config, n.info, "'spawn' takes a GC safe call expression") localError(g.config, n.info, "'spawn' takes a GC safe call expression")
var fn = n[0] var fn = n[0]
let let
name = (if fn.kind == nkSym: fn.sym.name.s else: genPrefix) & "Wrapper" name = (if fn.kind == nkSym: fn.sym.name.s else: genPrefix) & "Wrapper"
wrapperProc = newSym(skProc, getIdent(g.cache, name), owner, fn.info, g.config.options) wrapperProc = newSym(skProc, getIdent(g.cache, name), owner, fn.info, g.config.options)

View file

@ -169,3 +169,24 @@ proc flattenStmts*(n: PNode): PNode =
proc extractRange*(k: TNodeKind, n: PNode, a, b: int): PNode = proc extractRange*(k: TNodeKind, n: PNode, a, b: int): PNode =
result = newNodeI(k, n.info, b-a+1) result = newNodeI(k, n.info, b-a+1)
for i in 0..b-a: result[i] = n[i+a] for i in 0..b-a: result[i] = n[i+a]
proc isTrue*(n: PNode): bool =
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
n.kind == nkIntLit and n.intVal != 0
proc getRoot*(n: PNode): PSym =
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
## determined as the owner; ``obj`` in the example.
case n.kind
of nkSym:
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
result = n.sym
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
result = getRoot(n[0])
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = getRoot(n[1])
of nkCallKinds:
if getMagic(n) == mSlice: result = getRoot(n[1])
else: discard

View file

@ -265,7 +265,7 @@ the first is preferred.
Best practices Best practices
============= ==============
Note: these are general guidelines, not hard rules; there are always exceptions. Note: these are general guidelines, not hard rules; there are always exceptions.
Code reviews can just point to a specific section here to save time and Code reviews can just point to a specific section here to save time and
@ -361,8 +361,10 @@ General commit rules
1. Important, critical bugfixes that have a tiny chance of breaking 1. Important, critical bugfixes that have a tiny chance of breaking
somebody's code should be backported to the latest stable release somebody's code should be backported to the latest stable release
branch (currently 1.0.x). The commit message should contain ``[backport]`` branch (currently 1.2.x) and maybe also to the 1.0 branch.
then. The commit message should contain the tag ``[backport]`` for "backport to all
stable releases" and the tag ``[backport:$VERSION]`` for backporting to the
given $VERSION.
2. If you introduce changes which affect backwards compatibility, 2. If you introduce changes which affect backwards compatibility,
make breaking changes, or have PR which is tagged as ``[feature]``, make breaking changes, or have PR which is tagged as ``[feature]``,

View file

@ -120,8 +120,7 @@ you assume.
Example: insertionSort Example: insertionSort
====================== ======================
**Note**: This example does not yet work with DrNim. ``forall`` and ``exists`` **Note**: This example does not yet work with DrNim.
are not implemented.
.. code-block:: nim .. code-block:: nim

View file

@ -9,53 +9,21 @@
#[ #[
- Most important bug: - the analysis has to take 'break', 'continue' and 'raises' into account
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))' - 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 - 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' '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 / [ import std / [
parseopt, strutils, os, tables, times parseopt, strutils, os, tables, times, intsets, hashes
] ]
import ".." / compiler / [ import ".." / compiler / [
ast, types, renderer, ast, astalgo, types, renderer,
commands, options, msgs, commands, options, msgs,
platform, platform, trees, wordrecg, guards,
idents, lineinfos, cmdlinehelper, modulegraphs, condsyms, idents, lineinfos, cmdlinehelper, modulegraphs, condsyms,
pathutils, passes, passaux, sem, modules pathutils, passes, passaux, sem, modules
] ]
@ -91,42 +59,174 @@ proc helpOnError(conf: ConfigRef) =
type type
CannotMapToZ3Error = object of ValueError CannotMapToZ3Error = object of ValueError
Z3Exception = object of ValueError Z3Exception = object of ValueError
VersionScope = distinct int
DrnimContext = ref object
z3: Z3_context
graph: ModuleGraph
facts: seq[(PNode, VersionScope)]
varVersions: seq[int] # this maps variable IDs to their current version.
o: Operators
hasUnstructedCf: int
currOptions: TOptions
owner: PSym
mangler: seq[PSym]
DrCon = object DrCon = object
z3: Z3_context
graph: ModuleGraph graph: ModuleGraph
mapping: Table[string, Z3_ast] mapping: Table[string, Z3_ast]
canonParameterNames: bool canonParameterNames: bool
assumeUniqueness: bool
up: DrnimContext
proc stableName(result: var string; n: PNode) = var
assumeUniqueness: bool
proc echoFacts(c: DrnimContext) =
echo "FACTS:"
for i in 0 ..< c.facts.len:
let f = c.facts[i]
echo f[0], " version ", int(f[1])
proc isLoc(m: PNode; assumeUniqueness: bool): bool =
# We can reason about "locations" and map them to Z3 constants.
# For code that is full of "ref" (e.g. the Nim compiler itself) that
# is too limiting
proc isLet(n: PNode): bool =
if n.kind == nkSym:
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
abstractInst).kind != tyVar:
result = true
var n = m
while true:
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv, nkHiddenDeref:
n = n[0]
of nkDerefExpr:
n = n[0]
if not assumeUniqueness: return false
of nkBracketExpr:
if isConstExpr(n[1]) or isLet(n[1]) or isConstExpr(n[1].skipConv):
n = n[0]
else: return
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
n = n[1]
else:
break
if n.kind == nkSym:
case n.sym.kind
of skLet, skTemp, skForVar, skParam:
result = true
#of skParam:
# result = skipTypes(n.sym.typ, abstractInst).kind != tyVar
of skResult, skVar:
result = {sfAddrTaken} * n.sym.flags == {}
else:
discard
proc varVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
result = 0
for i in countdown(int(begin)-1, 0):
if c.varVersions[i] == s.id: inc result
proc disamb(c: DrnimContext; s: PSym): int =
# we group by 's.name.s' to compute the stable name ID.
result = 0
for i in 0 ..< c.mangler.len:
if s == c.mangler[i]: return result
if s.name.s == c.mangler[i].name.s: inc result
c.mangler.add s
proc stableName(result: var string; c: DrnimContext; n: PNode; version: VersionScope;
isOld: bool) =
# we can map full Nim expressions like 'f(a, b, c)' to Z3 variables. # 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 # We must be careful to select a unique, stable name for these expressions
# based on structural equality. 'stableName' helps us with this problem. # based on structural equality. 'stableName' helps us with this problem.
# In the future we will also use this string for the caching mechanism.
case n.kind case n.kind
of nkEmpty, nkNilLit, nkType: discard of nkEmpty, nkNilLit, nkType: discard
of nkIdent: of nkIdent:
result.add n.ident.s result.add n.ident.s
of nkSym: of nkSym:
result.add n.sym.name.s result.add n.sym.name.s
result.add '_' if n.sym.magic == mNone:
result.addInt n.sym.id let d = disamb(c, n.sym)
if d != 0:
result.add "`scope="
result.addInt d
let v = c.varVersion(n.sym, version) - ord(isOld)
assert v >= 0
if v > 0:
result.add '`'
result.addInt v
else:
result.add "`magic="
result.addInt ord(n.sym.magic)
of nkCharLit..nkUInt64Lit: of nkCharLit..nkUInt64Lit:
result.addInt n.intVal result.addInt n.intVal
of nkFloatLit..nkFloat64Lit: of nkFloatLit..nkFloat64Lit:
result.addFloat n.floatVal result.addFloat n.floatVal
of nkStrLit..nkTripleStrLit: of nkStrLit..nkTripleStrLit:
result.add strutils.escape n.strVal result.add strutils.escape n.strVal
of nkDotExpr:
stableName(result, c, n[0], version, isOld)
result.add '.'
stableName(result, c, n[1], version, isOld)
of nkBracketExpr:
stableName(result, c, n[0], version, isOld)
result.add '['
stableName(result, c, n[1], version, isOld)
result.add ']'
of nkCallKinds:
if n.len == 2:
stableName(result, c, n[1], version, isOld)
result.add '.'
case getMagic(n)
of mLengthArray, mLengthOpenArray, mLengthSeq, mLengthStr:
result.add "len"
of mHigh:
result.add "high"
of mLow:
result.add "low"
else:
stableName(result, c, n[0], version, isOld)
elif n.kind == nkInfix and n.len == 3:
result.add '('
stableName(result, c, n[1], version, isOld)
result.add ' '
stableName(result, c, n[0], version, isOld)
result.add ' '
stableName(result, c, n[2], version, isOld)
result.add ')'
else:
stableName(result, c, n[0], version, isOld)
result.add '('
for i in 1..<n.len:
if i > 1: result.add ", "
stableName(result, c, n[i], version, isOld)
result.add ')'
else: else:
result.add $n.kind result.add $n.kind
result.add '(' result.add '('
for i in 0..<n.len: for i in 0..<n.len:
if i > 0: result.add ", " if i > 0: result.add ", "
stableName(result, n[i]) stableName(result, c, n[i], version, isOld)
result.add ')' result.add ')'
proc stableName(n: PNode): string = stableName(result, n) proc stableName(c: DrnimContext; n: PNode; version: VersionScope;
isOld = false): string =
stableName(result, c, n, version, isOld)
template allScopes(c): untyped = VersionScope(c.varVersions.len)
template currentScope(c): untyped = VersionScope(c.varVersions.len)
proc notImplemented(msg: string) {.noinline.} = proc notImplemented(msg: string) {.noinline.} =
when defined(debug):
writeStackTrace()
echo msg
raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & msg) raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & msg)
proc translateEnsures(e, x: PNode): PNode = proc translateEnsures(e, x: PNode): PNode =
@ -138,7 +238,7 @@ proc translateEnsures(e, x: PNode): PNode =
result[i] = translateEnsures(e[i], x) result[i] = translateEnsures(e[i], x)
proc typeToZ3(c: DrCon; t: PType): Z3_sort = proc typeToZ3(c: DrCon; t: PType): Z3_sort =
template ctx: untyped = c.z3 template ctx: untyped = c.up.z3
case t.skipTypes(abstractInst+{tyVar}).kind case t.skipTypes(abstractInst+{tyVar}).kind
of tyEnum, tyInt..tyInt64: of tyEnum, tyInt..tyInt64:
result = Z3_mk_int_sort(ctx) result = Z3_mk_int_sort(ctx)
@ -156,42 +256,64 @@ template binary(op, a, b): untyped =
var arr = [a, b] var arr = [a, b]
op(ctx, cuint(2), addr(arr[0])) op(ctx, cuint(2), addr(arr[0]))
proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast proc nodeToZ3(c: var DrCon; n: PNode; scope: VersionScope; vars: var seq[PNode]): Z3_ast
proc nodeToDomain(c: var DrCon; n, q: PNode; opAnd: PSym): PNode =
assert n.kind == nkInfix
let opLe = createMagic(c.graph, "<=", mLeI)
case $n[0]
of "..":
result = buildCall(opAnd, buildCall(opLe, n[1], q), buildCall(opLe, q, n[2]))
of "..<":
let opLt = createMagic(c.graph, "<", mLtI)
result = buildCall(opAnd, buildCall(opLe, n[1], q), buildCall(opLt, q, n[2]))
else:
notImplemented($n)
template quantorToZ3(fn) {.dirty.} = template quantorToZ3(fn) {.dirty.} =
template ctx: untyped = c.z3 template ctx: untyped = c.up.z3
var bound = newSeq[Z3_app](n.len-1) var bound = newSeq[Z3_app](n.len-2)
for i in 0..n.len-2: let opAnd = createMagic(c.graph, "and", mAnd)
doAssert n[i].kind == nkSym var known: PNode
let v = n[i].sym for i in 1..n.len-2:
let it = n[i]
doAssert it.kind == nkInfix
let v = it[1].sym
let name = Z3_mk_string_symbol(ctx, v.name.s) let name = Z3_mk_string_symbol(ctx, v.name.s)
let vz3 = Z3_mk_const(ctx, name, typeToZ3(c, v.typ)) let vz3 = Z3_mk_const(ctx, name, typeToZ3(c, v.typ))
c.mapping[stableName(n[i])] = vz3 c.mapping[stableName(c.up, it[1], allScopes(c.up))] = vz3
bound[i] = Z3_to_app(ctx, vz3) bound[i-1] = Z3_to_app(ctx, vz3)
let domain = nodeToDomain(c, it[2], it[1], opAnd)
if known == nil:
known = domain
else:
known = buildCall(opAnd, known, domain)
var dummy: seq[PNode] var dummy: seq[PNode]
let x = nodeToZ3(c, n[^1], dummy) assert known != nil
let x = nodeToZ3(c, buildCall(createMagic(c.graph, "->", mImplies),
known, n[^1]), scope, dummy)
result = fn(ctx, 0, bound.len.cuint, addr(bound[0]), 0, nil, x) 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 forallToZ3(c: var DrCon; n: PNode; scope: VersionScope): Z3_ast = quantorToZ3(Z3_mk_forall_const)
proc existsToZ3(c: var DrCon; n: PNode): Z3_ast = quantorToZ3(Z3_mk_exists_const) proc existsToZ3(c: var DrCon; n: PNode; scope: VersionScope): Z3_ast = quantorToZ3(Z3_mk_exists_const)
proc paramName(n: PNode): string = proc paramName(c: DrnimContext; n: PNode): string =
case n.sym.kind case n.sym.kind
of skParam: result = "arg" & $n.sym.position of skParam: result = "arg" & $n.sym.position
of skResult: result = "result" of skResult: result = "result"
else: result = stableName(n) else: result = stableName(c, n, allScopes(c))
proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast = proc nodeToZ3(c: var DrCon; n: PNode; scope: VersionScope; vars: var seq[PNode]): Z3_ast =
template ctx: untyped = c.z3 template ctx: untyped = c.up.z3
template rec(n): untyped = nodeToZ3(c, n, vars) template rec(n): untyped = nodeToZ3(c, n, scope, vars)
case n.kind case n.kind
of nkSym: of nkSym:
let key = if c.canonParameterNames: paramName(n) else: stableName(n) let key = if c.canonParameterNames: paramName(c.up, n) else: stableName(c.up, n, scope)
result = c.mapping.getOrDefault(key) result = c.mapping.getOrDefault(key)
if pointer(result) == nil: if pointer(result) == nil:
let name = Z3_mk_string_symbol(ctx, n.sym.name.s) let name = Z3_mk_string_symbol(ctx, key)
result = Z3_mk_const(ctx, name, typeToZ3(c, n.sym.typ)) result = Z3_mk_const(ctx, name, typeToZ3(c, n.sym.typ))
c.mapping[key] = result c.mapping[key] = result
vars.add n vars.add n
@ -222,17 +344,23 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
result = Z3_mk_lt(ctx, rec n[1], rec n[2]) result = Z3_mk_lt(ctx, rec n[1], rec n[2])
of mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq: of mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq:
# len(x) needs the same logic as 'x' itself # len(x) needs the same logic as 'x' itself
if n[1].kind == nkSym: if isLoc(n[1], c.assumeUniqueness):
let key = stableName(n) let key = stableName(c.up, n, scope)
let sym = n[1].sym
result = c.mapping.getOrDefault(key) result = c.mapping.getOrDefault(key)
if pointer(result) == nil: if pointer(result) == nil:
let name = Z3_mk_string_symbol(ctx, sym.name.s & ".len") let name = Z3_mk_string_symbol(ctx, key)
result = Z3_mk_const(ctx, name, Z3_mk_int_sort(ctx)) result = Z3_mk_const(ctx, name, Z3_mk_int_sort(ctx))
c.mapping[key] = result c.mapping[key] = result
vars.add n vars.add n
else: else:
notImplemented(renderTree(n)) notImplemented(renderTree(n))
of mHigh:
let addOpr = createMagic(c.graph, "+", mAddI)
let lenOpr = createMagic(c.graph, "len", mLengthOpenArray)
let asLenExpr = addOpr.buildCall(lenOpr.buildCall(n[1]), nkIntLit.newIntNode(-1))
result = rec asLenExpr
of mLow:
result = rec lowBound(c.graph.config, n[1])
of mAddI, mSucc: of mAddI, mSucc:
result = binary(Z3_mk_add, rec n[1], rec n[2]) result = binary(Z3_mk_add, rec n[1], rec n[2])
of mSubI, mPred: of mSubI, mPred:
@ -256,9 +384,12 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
of mLtU: of mLtU:
result = Z3_mk_bvult(ctx, rec n[1], rec n[2]) result = Z3_mk_bvult(ctx, rec n[1], rec n[2])
of mAnd: of mAnd:
result = binary(Z3_mk_and, rec n[1], rec n[2]) # 'a and b' <=> ite(a, b, false)
result = Z3_mk_ite(ctx, rec n[1], rec n[2], Z3_mk_false(ctx))
#result = binary(Z3_mk_and, rec n[1], rec n[2])
of mOr: of mOr:
result = binary(Z3_mk_or, rec n[1], rec n[2]) result = Z3_mk_ite(ctx, rec n[1], Z3_mk_true(ctx), rec n[2])
#result = binary(Z3_mk_or, rec n[1], rec n[2])
of mXor: of mXor:
result = Z3_mk_xor(ctx, rec n[1], rec n[2]) result = Z3_mk_xor(ctx, rec n[1], rec n[2])
of mNot: of mNot:
@ -268,9 +399,9 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
of mIff: of mIff:
result = Z3_mk_iff(ctx, rec n[1], rec n[2]) result = Z3_mk_iff(ctx, rec n[1], rec n[2])
of mForall: of mForall:
result = forallToZ3(c, n) result = forallToZ3(c, n, scope)
of mExists: of mExists:
result = existsToZ3(c, n) result = existsToZ3(c, n, scope)
of mLeF64: of mLeF64:
result = Z3_mk_fpa_leq(ctx, rec n[1], rec n[2]) result = Z3_mk_fpa_leq(ctx, rec n[1], rec n[2])
of mLtF64: of mLtF64:
@ -299,11 +430,12 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
of mOrd, mChr: of mOrd, mChr:
result = rec n[1] result = rec n[1]
of mOld: of mOld:
let key = (if c.canonParameterNames: paramName(n[1]) else: stableName(n[1])) & ".old" let key = if c.canonParameterNames: (paramName(c.up, n[1]) & ".old")
else: stableName(c.up, n[1], scope, isOld = true)
result = c.mapping.getOrDefault(key) result = c.mapping.getOrDefault(key)
if pointer(result) == nil: if pointer(result) == nil:
let name = Z3_mk_string_symbol(ctx, $n) let name = Z3_mk_string_symbol(ctx, key)
result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ)) result = Z3_mk_const(ctx, name, typeToZ3(c, n[1].typ))
c.mapping[key] = result c.mapping[key] = result
# XXX change the logic in `addRangeInfo` for this # XXX change the logic in `addRangeInfo` for this
#vars.add n #vars.add n
@ -318,10 +450,10 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
ensuresEffects < op.n[0].len: ensuresEffects < op.n[0].len:
let ensures = op.n[0][ensuresEffects] let ensures = op.n[0][ensuresEffects]
if ensures != nil and ensures.kind != nkEmpty: if ensures != nil and ensures.kind != nkEmpty:
let key = stableName(n) let key = stableName(c.up, n, scope)
result = c.mapping.getOrDefault(key) result = c.mapping.getOrDefault(key)
if pointer(result) == nil: if pointer(result) == nil:
let name = Z3_mk_string_symbol(ctx, $n) let name = Z3_mk_string_symbol(ctx, key)
result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ)) result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
c.mapping[key] = result c.mapping[key] = result
vars.add n vars.add n
@ -333,15 +465,24 @@ proc nodeToZ3(c: var DrCon; n: PNode; vars: var seq[PNode]): Z3_ast =
for i in 0..n.len-2: for i in 0..n.len-2:
isTrivial = isTrivial and n[i].kind in {nkEmpty, nkCommentStmt} isTrivial = isTrivial and n[i].kind in {nkEmpty, nkCommentStmt}
if isTrivial: if isTrivial:
result = nodeToZ3(c, n[^1], vars) result = rec n[^1]
else: else:
notImplemented(renderTree(n)) notImplemented(renderTree(n))
of nkHiddenDeref: of nkHiddenDeref:
result = rec n[0] result = rec n[0]
else: else:
notImplemented(renderTree(n)) if isLoc(n, c.assumeUniqueness):
let key = stableName(c.up, n, scope)
result = c.mapping.getOrDefault(key)
if pointer(result) == nil:
let name = Z3_mk_string_symbol(ctx, key)
result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
c.mapping[key] = result
vars.add n
else:
notImplemented(renderTree(n))
proc addRangeInfo(c: var DrCon, n: PNode, res: var seq[Z3_ast]) = proc addRangeInfo(c: var DrCon, n: PNode; scope: VersionScope, res: var seq[Z3_ast]) =
var cmpOp = mLeI var cmpOp = mLeI
if n.typ != nil: if n.typ != nil:
cmpOp = cmpOp =
@ -393,15 +534,15 @@ proc addRangeInfo(c: var DrCon, n: PNode, res: var seq[Z3_ast]) =
let ensures = op.n[0][ensuresEffects] let ensures = op.n[0][ensuresEffects]
if ensures != nil and ensures.kind != nkEmpty: if ensures != nil and ensures.kind != nkEmpty:
var dummy: seq[PNode] var dummy: seq[PNode]
res.add nodeToZ3(c, translateEnsures(ensures, n), dummy) res.add nodeToZ3(c, translateEnsures(ensures, n), scope, dummy)
return return
let x = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), lowBound, n) let x = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), lowBound, n)
let y = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), n, highBound) let y = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), n, highBound)
var dummy: seq[PNode] var dummy: seq[PNode]
res.add nodeToZ3(c, x, dummy) res.add nodeToZ3(c, x, scope, dummy)
res.add nodeToZ3(c, y, dummy) res.add nodeToZ3(c, y, scope, dummy)
proc on_err(ctx: Z3_context, e: Z3_error_code) {.nimcall.} = proc on_err(ctx: Z3_context, e: Z3_error_code) {.nimcall.} =
#writeStackTrace() #writeStackTrace()
@ -423,18 +564,18 @@ proc conj(ctx: Z3_context; conds: seq[Z3_ast]): Z3_ast =
else: else:
result = Z3_mk_true(ctx) result = Z3_mk_true(ctx)
proc proofEngineAux(c: var DrCon; assumptions: seq[PNode]; toProve: PNode): (bool, string) = proc setupZ3(): Z3_context =
c.mapping = initTable[string, Z3_ast]()
let cfg = Z3_mk_config() 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: when false:
Z3_set_param_value(cfg, "timeout", "1000") Z3_set_param_value(cfg, "timeout", "1000")
Z3_set_param_value(cfg, "model", "true")
result = Z3_mk_context(cfg)
Z3_del_config(cfg)
Z3_set_error_handler(result, on_err)
proc proofEngineAux(c: var DrCon; assumptions: seq[(PNode, VersionScope)];
toProve: (PNode, VersionScope)): (bool, string) =
c.mapping = initTable[string, Z3_ast]()
try: try:
#[ #[
@ -455,20 +596,21 @@ proc proofEngineAux(c: var DrCon; assumptions: seq[PNode]; toProve: PNode): (boo
var collectedVars: seq[PNode] var collectedVars: seq[PNode]
template ctx(): untyped = c.up.z3
let solver = Z3_mk_solver(ctx) let solver = Z3_mk_solver(ctx)
var lhs: seq[Z3_ast] var lhs: seq[Z3_ast]
for assumption in assumptions: for assumption in items(assumptions):
if assumption != nil: try:
try: let za = nodeToZ3(c, assumption[0], assumption[1], collectedVars)
let za = nodeToZ3(c, assumption, collectedVars) #Z3_solver_assert ctx, solver, za
#Z3_solver_assert ctx, solver, za lhs.add za
lhs.add za except CannotMapToZ3Error:
except CannotMapToZ3Error: discard "ignore a fact we cannot map to Z3"
discard "ignore a fact we cannot map to Z3"
let z3toProve = nodeToZ3(c, toProve, collectedVars) let z3toProve = nodeToZ3(c, toProve[0], toProve[1], collectedVars)
for v in collectedVars: for v in collectedVars:
addRangeInfo(c, v, lhs) addRangeInfo(c, v, toProve[1], lhs)
# to make Z3 produce nice counterexamples, we try to prove the # to make Z3 produce nice counterexamples, we try to prove the
# negation of our conjecture and see if it's Z3_L_FALSE # negation of our conjecture and see if it's Z3_L_FALSE
@ -476,7 +618,8 @@ proc proofEngineAux(c: var DrCon; assumptions: seq[PNode]; toProve: PNode): (boo
#Z3_mk_not(ctx, forall(ctx, collectedVars, 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 when defined(dz3):
echo "toProve: ", Z3_ast_to_string(ctx, fa), " ", c.graph.config $ toProve[0].info, " ", int(toProve[1])
Z3_solver_assert ctx, solver, fa Z3_solver_assert ctx, solver, fa
let z3res = Z3_solver_check(ctx, solver) let z3res = Z3_solver_check(ctx, solver)
@ -489,18 +632,22 @@ proc proofEngineAux(c: var DrCon; assumptions: seq[PNode]; toProve: PNode): (boo
except ValueError: except ValueError:
result[0] = false result[0] = false
result[1] = getCurrentExceptionMsg() result[1] = getCurrentExceptionMsg()
finally:
Z3_del_context(ctx)
proc proofEngine(graph: ModuleGraph; assumptions: seq[PNode]; toProve: PNode): (bool, string) = proc proofEngine(ctx: DrnimContext; assumptions: seq[(PNode, VersionScope)];
toProve: (PNode, VersionScope)): (bool, string) =
var c: DrCon var c: DrCon
c.graph = graph c.graph = ctx.graph
c.assumeUniqueness = assumeUniqueness
c.up = ctx
result = proofEngineAux(c, assumptions, toProve) result = proofEngineAux(c, assumptions, toProve)
proc skipAddr(n: PNode): PNode {.inline.} =
(if n.kind == nkHiddenAddr: n[0] else: n)
proc translateReq(r, call: PNode): PNode = proc translateReq(r, call: PNode): PNode =
if r.kind == nkSym and r.sym.kind == skParam: if r.kind == nkSym and r.sym.kind == skParam:
if r.sym.position+1 < call.len: if r.sym.position+1 < call.len:
result = call[r.sym.position+1] result = call[r.sym.position+1].skipAddr
else: else:
notImplemented("no argument given for formal parameter: " & r.sym.name.s) notImplemented("no argument given for formal parameter: " & r.sym.name.s)
else: else:
@ -508,11 +655,11 @@ proc translateReq(r, call: PNode): PNode =
for i in 0 ..< safeLen(r): for i in 0 ..< safeLen(r):
result[i] = translateReq(r[i], call) result[i] = translateReq(r[i], call)
proc requirementsCheck(graph: ModuleGraph; assumptions: seq[PNode]; proc requirementsCheck(ctx: DrnimContext; assumptions: seq[(PNode, VersionScope)];
call, requirement: PNode): (bool, string) {.nimcall.} = call, requirement: PNode): (bool, string) =
try: try:
let r = translateReq(requirement, call) let r = translateReq(requirement, call)
result = proofEngine(graph, assumptions, r) result = proofEngine(ctx, assumptions, (r, ctx.currentScope))
except ValueError: except ValueError:
result[0] = false result[0] = false
result[1] = getCurrentExceptionMsg() result[1] = getCurrentExceptionMsg()
@ -552,24 +699,347 @@ proc compatibleProps(graph: ModuleGraph; formal, actual: PType): bool {.nimcall.
var c: DrCon var c: DrCon
c.graph = graph c.graph = graph
c.canonParameterNames = true c.canonParameterNames = true
if not frequires.isEmpty: try:
result = not arequires.isEmpty and proofEngineAux(c, @[frequires], arequires)[0] c.up = DrnimContext(z3: setupZ3(), o: initOperators(graph), graph: graph, owner: nil)
template zero: untyped = VersionScope(0)
if not frequires.isEmpty:
result = not arequires.isEmpty and proofEngineAux(c, @[(frequires, zero)], (arequires, zero))[0]
if result: if result:
if not fensures.isEmpty: if not fensures.isEmpty:
result = not aensures.isEmpty and proofEngineAux(c, @[aensures], fensures)[0] result = not aensures.isEmpty and proofEngineAux(c, @[(aensures, zero)], (fensures, zero))[0]
finally:
Z3_del_context(c.up.z3)
else: else:
# formal has requirements but 'actual' has none, so make it # formal has requirements but 'actual' has none, so make it
# incompatible. XXX What if the requirement only mentions that # incompatible. XXX What if the requirement only mentions that
# we already know from the type system? # we already know from the type system?
result = frequires.isEmpty and fensures.isEmpty result = frequires.isEmpty and fensures.isEmpty
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.facts.add((n, v))
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)
proc prove(c: DrnimContext; prop: PNode): bool =
let (success, m) = proofEngine(c, c.facts, (prop, c.currentScope))
if not success:
message(c.config, prop.info, warnStaticIndexCheck, "cannot prove: " & $prop & m)
result = success
proc traversePragmaStmt(c: DrnimContext, n: PNode) =
for it in n:
if it.kind == nkExprColonExpr:
let pragma = whichPragma(it)
if pragma == wAssume:
addFact(c, it[1])
elif pragma == wInvariant or pragma == wAssert:
if prove(c, it[1]):
addFact(c, it[1])
proc requiresCheck(c: DrnimContext, 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) = requirementsCheck(c, c.facts, call, requires)
if not success:
message(c.config, call.info, warnStaticIndexCheck, "cannot prove: " & $requires & m)
proc freshVersion(c: DrnimContext; arg: PNode) =
let v = getRoot(arg)
if v != nil:
c.varVersions.add v.id
proc translateEnsuresFromCall(c: DrnimContext, e, call: PNode): PNode =
if e.kind in nkCallKinds and e[0].kind == nkSym and e[0].sym.magic == mOld:
assert e[1].kind == nkSym and e[1].sym.kind == skParam
let param = e[1].sym
let arg = call[param.position+1].skipAddr
result = buildCall(e[0].sym, arg)
elif e.kind == nkSym and e.sym.kind == skParam:
let param = e.sym
let arg = call[param.position+1].skipAddr
result = arg
else:
result = shallowCopy(e)
for i in 0 ..< safeLen(e): result[i] = translateEnsuresFromCall(c, e[i], call)
proc collectEnsuredFacts(c: DrnimContext, call: PNode; op: PType) =
assert op.n[0].kind == nkEffectList
for i in 1 ..< min(call.len, op.len):
if op[i].kind == tyVar:
freshVersion(c, call[i].skipAddr)
if ensuresEffects < op.n[0].len:
let ensures = op.n[0][ensuresEffects]
if ensures != nil and ensures.kind != nkEmpty:
addFact(c, translateEnsuresFromCall(c, ensures, call))
proc checkLe(c: DrnimContext, a, b: PNode) =
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)
proc checkBounds(c: DrnimContext; arr, idx: PNode) =
checkLe(c, lowBound(c.config, arr), idx)
checkLe(c, idx, highBound(c.config, arr, c.o))
proc checkRange(c: DrnimContext; value: PNode; typ: PType) =
let t = typ.skipTypes(abstractInst - {tyRange})
if t.kind == tyRange:
let lowBound = copyTree(t.n[0])
lowBound.info = value.info
let highBound = copyTree(t.n[1])
highBound.info = value.info
checkLe(c, lowBound, value)
checkLe(c, value, highBound)
proc addAsgnFact*(c: DrnimContext, key, value: PNode) =
var fact = newNodeI(nkCall, key.info, 3)
fact[0] = newSymNode(c.o.opEq)
fact[1] = key
fact[2] = value
c.facts.add((fact, c.currentScope))
proc traverse(c: DrnimContext; n: PNode)
proc traverseTryStmt(c: DrnimContext; n: PNode) =
traverse(c, n[0])
let oldFacts = c.facts.len
for i in 1 ..< n.len:
traverse(c, n[i].lastSon)
setLen(c.facts, oldFacts)
proc traverseCase(c: DrnimContext; n: PNode) =
traverse(c, n[0])
let oldFacts = c.facts.len
for i in 1 ..< n.len:
traverse(c, n[i].lastSon)
# XXX make this as smart as 'if elif'
setLen(c.facts, oldFacts)
proc traverseIf(c: DrnimContext; n: PNode) =
traverse(c, n[0][0])
let oldFacts = c.facts.len
addFact(c, n[0][0])
traverse(c, n[0][1])
for i in 1..<n.len:
let branch = n[i]
setLen(c.facts, oldFacts)
for j in 0..i-1:
addFactNeg(c, n[j][0])
if branch.len > 1:
addFact(c, branch[0])
for i in 0..<branch.len:
traverse(c, branch[i])
setLen(c.facts, oldFacts)
proc traverseBlock(c: DrnimContext; n: PNode) =
traverse(c, n)
proc addFactLe(c: DrnimContext; a, b: PNode) =
c.addFact c.o.opLe.buildCall(a, b)
proc addFactLt(c: DrnimContext; a, b: PNode) =
c.addFact c.o.opLt.buildCall(a, b)
proc ensuresCheck(c: DrnimContext; owner: PSym) =
if owner.typ != nil and owner.typ.kind == tyProc and owner.typ.n != nil:
let n = owner.typ.n
if n.len > 0 and n[0].kind == nkEffectList and ensuresEffects < n[0].len:
let ensures = n[0][ensuresEffects]
if ensures != nil and ensures.kind != nkEmpty:
discard prove(c, ensures)
proc traverseAsgn(c: DrnimContext; n: PNode) =
traverse(c, n[0])
traverse(c, n[1])
proc replaceByOldParams(fact, le: PNode): PNode =
if guards.sameTree(fact, le):
result = newNodeIT(nkCall, fact.info, fact.typ)
result.add newSymNode createMagic(c.graph, "old", mOld)
result.add fact
else:
result = shallowCopy(fact)
for i in 0 ..< safeLen(fact):
result[i] = replaceByOldParams(fact[i], le)
freshVersion(c, n[0])
addAsgnFact(c, n[0], replaceByOldParams(n[1], n[0]))
when defined(debug):
echoFacts(c)
proc traverse(c: DrnimContext; n: PNode) =
case n.kind
of nkEmpty..nkNilLit:
discard "nothing to do"
of nkRaiseStmt, nkBreakStmt, nkContinueStmt:
inc c.hasUnstructedCf
for i in 0..<n.safeLen:
traverse(c, n[i])
of nkReturnStmt:
for i in 0 ..< n.safeLen:
traverse(c, n[i])
ensuresCheck(c, c.owner)
of nkCallKinds:
# p's effects are ours too:
var a = n[0]
let op = a.typ
if op != nil and op.kind == tyProc and op.n[0].kind == nkEffectList:
requiresCheck(c, n, op)
collectEnsuredFacts(c, n, op)
if a.kind == nkSym:
case a.sym.magic
of mNew, mNewFinalize, mNewSeq:
# may not look like an assignment, but it is:
let arg = n[1]
freshVersion(c, arg)
traverse(c, arg)
addAsgnFact(c, arg, newNodeIT(nkObjConstr, arg.info, arg.typ))
of mArrGet, mArrPut:
#if optStaticBoundsCheck in c.currOptions: checkBounds(c, n[1], n[2])
discard
else:
discard
for i in 0..<n.safeLen:
traverse(c, n[i])
of nkDotExpr:
#guardDotAccess(c, n)
for i in 0..<n.len: traverse(c, n[i])
of nkCheckedFieldExpr:
traverse(c, n[0])
#checkFieldAccess(c.facts, n, c.config)
of nkTryStmt: traverseTryStmt(c, n)
of nkPragma: traversePragmaStmt(c, n)
of nkAsgn, nkFastAsgn: traverseAsgn(c, n)
of nkVarSection, nkLetSection:
for child in n:
let last = lastSon(child)
if last.kind != nkEmpty: traverse(c, last)
if child.kind == nkIdentDefs and last.kind != nkEmpty:
for i in 0..<child.len-2:
addAsgnFact(c, child[i], last)
elif child.kind == nkVarTuple and last.kind != nkEmpty:
for i in 0..<child.len-1:
if child[i].kind == nkEmpty or
child[i].kind == nkSym and child[i].sym.name.s == "_":
discard "anon variable"
elif last.kind in {nkPar, nkTupleConstr}:
addAsgnFact(c, child[i], last[i])
of nkConstSection:
for child in n:
let last = lastSon(child)
traverse(c, last)
of nkCaseStmt: traverseCase(c, n)
of nkWhen, nkIfStmt, nkIfExpr: traverseIf(c, n)
of nkBlockStmt, nkBlockExpr: traverseBlock(c, n[1])
of nkWhileStmt:
# 'while true' loop?
if isTrue(n[0]):
traverseBlock(c, n[1])
else:
let oldFacts = c.facts.len
addFact(c, n[0])
traverse(c, n[0])
traverse(c, n[1])
setLen(c.facts, oldFacts)
of nkForStmt, nkParForStmt:
# we are very conservative here and assume the loop is never executed:
let oldFacts = c.facts.len
let iterCall = n[n.len-2]
if optStaticBoundsCheck in c.currOptions and iterCall.kind in nkCallKinds:
let op = iterCall[0]
if op.kind == nkSym and fromSystem(op.sym):
let iterVar = n[0]
case op.sym.name.s
of "..", "countup", "countdown":
let lower = iterCall[1]
let upper = iterCall[2]
# for i in 0..n means 0 <= i and i <= n. Countdown is
# the same since only the iteration direction changes.
addFactLe(c, lower, iterVar)
addFactLe(c, iterVar, upper)
of "..<":
let lower = iterCall[1]
let upper = iterCall[2]
addFactLe(c, lower, iterVar)
addFactLt(c, iterVar, upper)
else: discard
for i in 0..<n.len-2:
let it = n[i]
traverse(c, it)
let loopBody = n[^1]
traverse(c, iterCall)
traverse(c, loopBody)
setLen(c.facts, oldFacts)
of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
discard
of nkCast:
if n.len == 2:
traverse(c, n[1])
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
if n.len == 2:
traverse(c, n[1])
if optStaticBoundsCheck in c.currOptions:
checkRange(c, n[1], n.typ)
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
if n.len == 1:
traverse(c, n[0])
if optStaticBoundsCheck in c.currOptions:
checkRange(c, n[0], n.typ)
of nkBracketExpr:
if optStaticBoundsCheck in c.currOptions and n.len == 2:
if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
checkBounds(c, n[0], n[1])
for i in 0 ..< n.len: traverse(c, n[i])
else:
for i in 0 ..< n.len: traverse(c, n[i])
proc strongSemCheck(graph: ModuleGraph; owner: PSym; n: PNode) =
var c = DrnimContext()
c.currOptions = graph.config.options + owner.options
if optStaticBoundsCheck in c.currOptions:
c.z3 = setupZ3()
c.o = initOperators(graph)
c.graph = graph
c.owner = owner
try:
traverse(c, n)
ensuresCheck(c, owner)
finally:
Z3_del_context(c.z3)
proc mainCommand(graph: ModuleGraph) = proc mainCommand(graph: ModuleGraph) =
let conf = graph.config let conf = graph.config
conf.lastCmdTime = epochTime() conf.lastCmdTime = epochTime()
graph.proofEngine = proofEngine graph.strongSemCheck = strongSemCheck
graph.requirementsCheck = requirementsCheck
graph.compatibleProps = compatibleProps graph.compatibleProps = compatibleProps
graph.config.errorMax = high(int) # do not stop after first error graph.config.errorMax = high(int) # do not stop after first error
@ -600,20 +1070,6 @@ proc mainCommand(graph: ModuleGraph) =
"output", output, "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) = proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
var p = parseopt.initOptParser(cmd) var p = parseopt.initOptParser(cmd)
var argsCount = 1 var argsCount = 1
@ -638,7 +1094,11 @@ proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
p.key = "-" p.key = "-"
if processArgument(pass, p, argsCount, config): break if processArgument(pass, p, argsCount, config): break
else: else:
processSwitch(pass, p, config) case p.key.normalize
of "assumeunique":
assumeUniqueness = true
else:
processSwitch(pass, p, config)
of cmdArgument: of cmdArgument:
config.commandLine.add " " config.commandLine.add " "
config.commandLine.add p.key.quoteShell config.commandLine.add p.key.quoteShell

View file

@ -38,6 +38,14 @@ proc xu(a: uint) =
let chunk = range[1u32..10u32](a) let chunk = range[1u32..10u32](a)
ru chunk ru chunk
proc parse(s: string) =
var i = 0
while i < s.len and s[i] != 'a':
inc i
parse("abc")
{.pop.} {.pop.}
p([1, 2, 3], [4, 5]) p([1, 2, 3], [4, 5])

View file

@ -1,12 +1,19 @@
discard """ discard """
nimout: '''tensures.nim(11, 10) Warning: BEGIN [User] nimout: '''tensures.nim(18, 10) Warning: BEGIN [User]
tensures.nim(20, 5) Warning: cannot prove: tensures.nim(27, 5) Warning: cannot prove:
0 < n [IndexCheck] 0 < n [IndexCheck]
tensures.nim(30, 10) Warning: END [User]''' tensures.nim(47, 17) Warning: cannot prove: a < 4; counter example: y -> 2
a`2 -> 4
a`1 -> 3
a -> 2 [IndexCheck]
tensures.nim(69, 17) Warning: cannot prove: a < 4; counter example: y -> 2
a`1 -> 4
a -> 2 [IndexCheck]
tensures.nim(73, 10) Warning: END [User]'''
cmd: "drnim $file" cmd: "drnim $file"
action: "compile" action: "compile"
""" """
import std/logic
{.push staticBoundChecks: defined(nimDrNim).} {.push staticBoundChecks: defined(nimDrNim).}
{.warning: "BEGIN".} {.warning: "BEGIN".}
@ -27,5 +34,41 @@ proc main =
main() main()
proc myinc(x: var int) {.ensures: x == old(x)+1.} =
inc x
{.assume: old(x)+1 == x.}
proc mainB(y: int) =
var a = y
if a < 3:
myinc a
{.assert: a < 4.}
myinc a
{.assert: a < 4.} # now this is wrong!
mainB(3)
proc a(yy, z: int) {.requires: (yy - z) > 6.} = discard
# '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 (yy, z3: int) {.requires: z3 < 5 and z3 > -5 and yy > 10.}
var
x: F = a # valid?
proc testAsgn(y: int) =
var a = y
if a < 3:
a = a + 2
{.assert: a < 4.}
testAsgn(3)
{.warning: "END".} {.warning: "END".}
{.pop.} {.pop.}

View file

@ -1,12 +1,15 @@
discard """ discard """
nimout: ''' nimout: '''tsetlen_invalidates.nim(12, 10) Warning: BEGIN [User]
tsetlen_invalidates.nim(15, 12) Warning: cannot prove: 0 <= len(a) + -1; counter example: a.len -> 0 [IndexCheck] tsetlen_invalidates.nim(18, 12) Warning: cannot prove: 0 <= len(a) + -1; counter example: a`1.len -> 0
a.len -> 1 [IndexCheck]
tsetlen_invalidates.nim(26, 10) Warning: END [User]
''' '''
cmd: "drnim $file" cmd: "drnim $file"
action: "compile" action: "compile"
""" """
{.push staticBoundChecks: defined(nimDrNim).} {.push staticBoundChecks: defined(nimDrNim).}
{.warning: "BEGIN".}
proc p() = proc p() =
var a = newSeq[int](3) var a = newSeq[int](3)
@ -20,3 +23,4 @@ proc p() =
{.pop.} {.pop.}
p() p()
{.warning: "END".}