1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## This module implements the 'implies' relation for guards.
- import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer, idents,
- saturate, modulegraphs, options, lineinfos, int128
- when defined(nimPreviewSlimSystem):
- import std/assertions
- const
- someEq = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
- mEqStr, mEqSet, mEqCString}
- # set excluded here as the semantics are vastly different:
- someLe = {mLeI, mLeF64, mLeU, mLeEnum,
- mLeCh, mLeB, mLePtr, mLeStr}
- someLt = {mLtI, mLtF64, mLtU, mLtEnum,
- mLtCh, mLtB, mLtPtr, mLtStr}
- someLen = {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq}
- someIn = {mInSet}
- someHigh = {mHigh}
- # we don't list unsigned here because wrap around semantics suck for
- # proving anything:
- someAdd = {mAddI, mAddF64, mSucc}
- someSub = {mSubI, mSubF64, mPred}
- someMul = {mMulI, mMulF64}
- someDiv = {mDivI, mDivF64}
- someMod = {mModI}
- someMax = {mMaxI}
- someMin = {mMinI}
- someBinaryOp = someAdd+someSub+someMul+someMax+someMin
- proc isValue(n: PNode): bool = n.kind in {nkCharLit..nkNilLit}
- proc isLocation(n: PNode): bool = not n.isValue
- 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 notin {tyVar}:
- result = true
- proc isVar(n: PNode): bool =
- n.kind == nkSym and n.sym.kind in {skResult, skVar} and
- {sfAddrTaken} * n.sym.flags == {}
- proc isLetLocation(m: PNode, isApprox: bool): bool =
- # consider: 'n[].kind' --> we really need to support 1 deref op even if this
- # is technically wrong due to aliasing :-( We could introduce "soft" facts
- # for this; this would still be very useful for warnings and also nicely
- # solves the 'var' problems. For now we fix this by requiring much more
- # restrictive expressions for the 'not nil' checking.
- var n = m
- var derefs = 0
- while true:
- case n.kind
- of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
- n = n[0]
- of nkDerefExpr:
- n = n[0]
- inc derefs
- of nkHiddenDeref:
- n = n[0]
- if not isApprox: inc derefs
- 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
- result = n.isLet and derefs <= ord(isApprox)
- if not result and isApprox:
- result = isVar(n)
- proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true)
- proc swapArgs(fact: PNode, newOp: PSym): PNode =
- result = newNodeI(nkCall, fact.info, 3)
- result[0] = newSymNode(newOp)
- result[1] = fact[2]
- result[2] = fact[1]
- proc neg(n: PNode; o: Operators): PNode =
- if n == nil: return nil
- case n.getMagic
- of mNot:
- result = n[1]
- of someLt:
- # not (a < b) == a >= b == b <= a
- result = swapArgs(n, o.opLe)
- of someLe:
- result = swapArgs(n, o.opLt)
- of mInSet:
- if n[1].kind != nkCurly: return nil
- let t = n[2].typ.skipTypes(abstractInst)
- result = newNodeI(nkCall, n.info, 3)
- result[0] = n[0]
- result[2] = n[2]
- if t.kind == tyEnum:
- var s = newNodeIT(nkCurly, n.info, n[1].typ)
- for e in t.n:
- let eAsNode = newIntNode(nkIntLit, e.sym.position)
- if not inSet(n[1], eAsNode): s.add eAsNode
- result[1] = s
- #elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000:
- # result[1] = complement(n[1])
- else:
- # not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4
- # XXX todo
- result = nil
- of mOr:
- # not (a or b) --> not a and not b
- let
- a = n[1].neg(o)
- b = n[2].neg(o)
- if a != nil and b != nil:
- result = newNodeI(nkCall, n.info, 3)
- result[0] = newSymNode(o.opAnd)
- result[1] = a
- result[2] = b
- elif a != nil:
- result = a
- elif b != nil:
- result = b
- else:
- # leave not (a == 4) as it is
- result = newNodeI(nkCall, n.info, 2)
- result[0] = newSymNode(o.opNot)
- result[1] = n
- proc buildCall*(op: PSym; a: PNode): PNode =
- result = newNodeI(nkCall, a.info, 2)
- result[0] = newSymNode(op)
- result[1] = a
- proc buildCall*(op: PSym; a, b: PNode): PNode =
- result = newNodeI(nkInfix, a.info, 3)
- result[0] = newSymNode(op)
- result[1] = a
- result[2] = b
- proc `|+|`(a, b: PNode): PNode =
- result = copyNode(a)
- if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal |+| b.intVal
- else: result.floatVal = a.floatVal + b.floatVal
- proc `|-|`(a, b: PNode): PNode =
- result = copyNode(a)
- if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal |-| b.intVal
- else: result.floatVal = a.floatVal - b.floatVal
- proc `|*|`(a, b: PNode): PNode =
- result = copyNode(a)
- if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal |*| b.intVal
- else: result.floatVal = a.floatVal * b.floatVal
- proc `|div|`(a, b: PNode): PNode =
- result = copyNode(a)
- if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal div b.intVal
- else: result.floatVal = a.floatVal / b.floatVal
- proc negate(a, b, res: PNode; o: Operators): PNode =
- if b.kind in {nkCharLit..nkUInt64Lit} and b.intVal != low(BiggestInt):
- var b = copyNode(b)
- b.intVal = -b.intVal
- if a.kind in {nkCharLit..nkUInt64Lit}:
- b.intVal = b.intVal |+| a.intVal
- result = b
- else:
- result = buildCall(o.opAdd, a, b)
- elif b.kind in {nkFloatLit..nkFloat64Lit}:
- var b = copyNode(b)
- b.floatVal = -b.floatVal
- result = buildCall(o.opAdd, a, b)
- else:
- result = res
- proc zero(): PNode = nkIntLit.newIntNode(0)
- proc one(): PNode = nkIntLit.newIntNode(1)
- proc minusOne(): PNode = nkIntLit.newIntNode(-1)
- proc lowBound*(conf: ConfigRef; x: PNode): PNode =
- result = nkIntLit.newIntNode(firstOrd(conf, x.typ))
- result.info = x.info
- proc highBound*(conf: ConfigRef; x: PNode; o: Operators): PNode =
- let typ = x.typ.skipTypes(abstractInst)
- result = if typ.kind == tyArray:
- nkIntLit.newIntNode(lastOrd(conf, typ))
- elif typ.kind == tySequence and x.kind == nkSym and
- x.sym.kind == skConst:
- nkIntLit.newIntNode(x.sym.astdef.len-1)
- else:
- o.opAdd.buildCall(o.opLen.buildCall(x), minusOne())
- result.info = x.info
- proc reassociation(n: PNode; o: Operators): PNode =
- result = n
- # (foo+5)+5 --> foo+10; same for '*'
- case result.getMagic
- of someAdd:
- if result[2].isValue and
- result[1].getMagic in someAdd and result[1][2].isValue:
- result = o.opAdd.buildCall(result[1][1], result[1][2] |+| result[2])
- if result[2].intVal == 0:
- result = result[1]
- of someMul:
- if result[2].isValue and
- result[1].getMagic in someMul and result[1][2].isValue:
- result = o.opMul.buildCall(result[1][1], result[1][2] |*| result[2])
- if result[2].intVal == 1:
- result = result[1]
- elif result[2].intVal == 0:
- result = zero()
- else: discard
- proc pred(n: PNode): PNode =
- if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(BiggestInt):
- result = copyNode(n)
- dec result.intVal
- 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)
- for i in 0..<n.len:
- result[i] = canon(n[i], o)
- elif n.kind == nkSym and n.sym.kind == skLet and
- n.sym.astdef.getMagic in (someEq + someAdd + someMul + someMin +
- someMax + someHigh + someSub + someLen + someDiv):
- result = n.sym.astdef.copyTree
- else:
- result = n
- case result.getMagic
- of someEq, someAdd, someMul, someMin, someMax:
- # these are symmetric; put value as last:
- if result[1].isValue and not result[2].isValue:
- result = swapArgs(result, result[0].sym)
- # (4 + foo) + 2 --> (foo + 4) + 2
- of someHigh:
- # high == len+(-1)
- result = o.opAdd.buildCall(o.opLen.buildCall(result[1]), minusOne())
- of someSub:
- # x - 4 --> x + (-4)
- result = negate(result[1], result[2], result, o)
- of someLen:
- result[0] = o.opLen.newSymNode
- of someLt - {mLtF64}:
- # x < y same as x <= y-1:
- let y = n[2].canon(o)
- let p = pred(y)
- let minus = if p != y: p else: o.opAdd.buildCall(y, minusOne()).canon(o)
- result = o.opLe.buildCall(n[1].canon(o), minus)
- else: discard
- result = skipConv(result)
- result = reassociation(result, o)
- # most important rule: (x-4) <= a.len --> x <= a.len+4
- case result.getMagic
- of someLe:
- let x = result[1]
- let y = result[2]
- if x.kind in nkCallKinds and x.len == 3 and x[2].isValue and
- isLetLocation(x[1], true):
- case x.getMagic
- of someSub:
- result = buildCall(result[0].sym, x[1],
- reassociation(o.opAdd.buildCall(y, x[2]), o))
- of someAdd:
- # Rule A:
- let plus = negate(y, x[2], nil, o).reassociation(o)
- if plus != nil: result = buildCall(result[0].sym, x[1], plus)
- else: discard
- elif y.kind in nkCallKinds and y.len == 3 and y[2].isValue and
- isLetLocation(y[1], true):
- # a.len < x-3
- case y.getMagic
- of someSub:
- result = buildCall(result[0].sym, y[1],
- reassociation(o.opAdd.buildCall(x, y[2]), o))
- of someAdd:
- let plus = negate(x, y[2], nil, o).reassociation(o)
- # ensure that Rule A will not trigger afterwards with the
- # additional 'not isLetLocation' constraint:
- if plus != nil and not isLetLocation(x, true):
- result = buildCall(result[0].sym, plus, y[1])
- else: discard
- elif x.isValue and y.getMagic in someAdd and y[2].kind == x.kind:
- # 0 <= a.len + 3
- # -3 <= a.len
- result[1] = x |-| y[2]
- result[2] = y[1]
- elif x.isValue and y.getMagic in someSub and y[2].kind == x.kind:
- # 0 <= a.len - 3
- # 3 <= a.len
- result[1] = x |+| y[2]
- result[2] = y[1]
- else: discard
- proc buildAdd*(a: PNode; b: BiggestInt; o: Operators): PNode =
- canon(if b != 0: o.opAdd.buildCall(a, nkIntLit.newIntNode(b)) else: a, o)
- proc usefulFact(n: PNode; o: Operators): PNode =
- case n.getMagic
- of someEq:
- if skipConv(n[2]).kind == nkNilLit and (
- isLetLocation(n[1], false) or isVar(n[1])):
- result = o.opIsNil.buildCall(n[1])
- else:
- if isLetLocation(n[1], true) or isLetLocation(n[2], true):
- # XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
- result = n
- elif n[1].getMagic in someLen or n[2].getMagic in someLen:
- result = n
- of someLe+someLt:
- if isLetLocation(n[1], true) or isLetLocation(n[2], true):
- # XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
- result = n
- elif n[1].getMagic in someLen or n[2].getMagic in someLen:
- # XXX Rethink this whole idea of 'usefulFact' for semparallel
- result = n
- of mIsNil:
- if isLetLocation(n[1], false) or isVar(n[1]):
- result = n
- of someIn:
- if isLetLocation(n[1], true):
- result = n
- of mAnd:
- let
- a = usefulFact(n[1], o)
- b = usefulFact(n[2], o)
- if a != nil and b != nil:
- result = newNodeI(nkCall, n.info, 3)
- result[0] = newSymNode(o.opAnd)
- result[1] = a
- result[2] = b
- elif a != nil:
- result = a
- elif b != nil:
- result = b
- of mNot:
- let a = usefulFact(n[1], o)
- if a != nil:
- result = a.neg(o)
- of mOr:
- # 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
- # with that knowledge...
- # DeMorgan helps a little though:
- # not a or not b --> not (a and b)
- # (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2))
- # not (x != 3 and y != 2)
- let
- a = usefulFact(n[1], o).neg(o)
- b = usefulFact(n[2], o).neg(o)
- if a != nil and b != nil:
- result = newNodeI(nkCall, n.info, 3)
- result[0] = newSymNode(o.opAnd)
- result[1] = a
- result[2] = b
- result = result.neg(o)
- elif n.kind == nkSym and n.sym.kind == skLet:
- # consider:
- # let a = 2 < x
- # if a:
- # ...
- # We make can easily replace 'a' by '2 < x' here:
- if n.sym.astdef != nil:
- result = usefulFact(n.sym.astdef, o)
- elif n.kind == nkStmtListExpr:
- result = usefulFact(n.lastSon, o)
- type
- TModel* = object
- s*: seq[PNode] # the "knowledge base"
- g*: ModuleGraph
- beSmart*: bool
- proc addFact*(m: var TModel, nn: PNode) =
- let n = usefulFact(nn, m.g.operators)
- if n != nil:
- if not m.beSmart:
- m.s.add n
- else:
- let c = canon(n, m.g.operators)
- if c.getMagic == mAnd:
- addFact(m, c[1])
- addFact(m, c[2])
- else:
- m.s.add c
- proc addFactNeg*(m: var TModel, n: PNode) =
- let n = n.neg(m.g.operators)
- if n != nil: addFact(m, n)
- proc sameOpr(a, b: PSym): bool =
- case a.magic
- of someEq: result = b.magic in someEq
- of someLe: result = b.magic in someLe
- of someLt: result = b.magic in someLt
- of someLen: result = b.magic in someLen
- of someAdd: result = b.magic in someAdd
- of someSub: result = b.magic in someSub
- of someMul: result = b.magic in someMul
- of someDiv: result = b.magic in someDiv
- else: result = a == b
- proc sameTree*(a, b: PNode): bool =
- result = false
- if a == b:
- result = true
- elif a != nil and b != nil and a.kind == b.kind:
- case a.kind
- of nkSym:
- result = a.sym == b.sym
- if not result and a.sym.magic != mNone:
- result = a.sym.magic == b.sym.magic or sameOpr(a.sym, b.sym)
- of nkIdent: result = a.ident.id == b.ident.id
- of nkCharLit..nkUInt64Lit: result = a.intVal == b.intVal
- of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
- of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
- of nkType: result = a.typ == b.typ
- of nkEmpty, nkNilLit: result = true
- else:
- if a.len == b.len:
- for i in 0..<a.len:
- if not sameTree(a[i], b[i]): return
- result = true
- proc hasSubTree(n, x: PNode): bool =
- if n.sameTree(x): result = true
- else:
- case n.kind
- of nkEmpty..nkNilLit:
- result = n.sameTree(x)
- of nkFormalParams:
- discard
- else:
- for i in 0..<n.len:
- if hasSubTree(n[i], x): return true
- proc invalidateFacts*(s: var seq[PNode], n: PNode) =
- # We are able to guard local vars (as opposed to 'let' variables)!
- # 'while p != nil: f(p); p = p.next'
- # This is actually quite easy to do:
- # Re-assignments (incl. pass to a 'var' param) trigger an invalidation
- # of every fact that contains 'v'.
- #
- # if x < 4:
- # if y < 5
- # x = unknown()
- # # we invalidate 'x' here but it's known that x >= 4
- # # for the else anyway
- # else:
- # echo x
- #
- # 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
- # could CSE these expressions then and help C's optimizer.
- for i in 0..high(s):
- 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 =
- if a.isValue and b.isValue:
- result = not sameValue(a, b)
- proc impliesEq(fact, eq: PNode): TImplication =
- let (loc, val) = if isLocation(eq[1]): (1, 2) else: (2, 1)
- case fact[0].sym.magic
- of someEq:
- if sameTree(fact[1], eq[loc]):
- # this is not correct; consider: a == b; a == 1 --> unknown!
- if sameTree(fact[2], eq[val]): result = impYes
- elif valuesUnequal(fact[2], eq[val]): result = impNo
- elif sameTree(fact[2], eq[loc]):
- if sameTree(fact[1], eq[val]): result = impYes
- elif valuesUnequal(fact[1], eq[val]): result = impNo
- of mInSet:
- # remember: mInSet is 'contains' so the set comes first!
- if sameTree(fact[2], eq[loc]) and isValue(eq[val]):
- if inSet(fact[1], eq[val]): result = impYes
- else: result = impNo
- of mNot, mOr, mAnd: assert(false, "impliesEq")
- else: discard
- proc leImpliesIn(x, c, aSet: PNode): TImplication =
- if c.kind in {nkCharLit..nkUInt64Lit}:
- # fact: x <= 4; question x in {56}?
- # --> true if every value <= 4 is in the set {56}
- #
- var value = newIntNode(c.kind, firstOrd(nil, x.typ))
- # don't iterate too often:
- if c.intVal - value.intVal < 1000:
- var i, pos, neg: int
- while value.intVal <= c.intVal:
- if inSet(aSet, value): inc pos
- else: inc neg
- inc i; inc value.intVal
- if pos == i: result = impYes
- elif neg == i: result = impNo
- proc geImpliesIn(x, c, aSet: PNode): TImplication =
- if c.kind in {nkCharLit..nkUInt64Lit}:
- # fact: x >= 4; question x in {56}?
- # --> true iff every value >= 4 is in the set {56}
- #
- var value = newIntNode(c.kind, c.intVal)
- let max = lastOrd(nil, x.typ)
- # don't iterate too often:
- if max - getInt(value) < toInt128(1000):
- var i, pos, neg: int
- while value.intVal <= max:
- if inSet(aSet, value): inc pos
- else: inc neg
- inc i; inc value.intVal
- if pos == i: result = impYes
- elif neg == i: result = impNo
- proc compareSets(a, b: PNode): TImplication =
- if equalSets(nil, a, b): result = impYes
- elif intersectSets(nil, a, b).len == 0: result = impNo
- proc impliesIn(fact, loc, aSet: PNode): TImplication =
- case fact[0].sym.magic
- of someEq:
- if sameTree(fact[1], loc):
- if inSet(aSet, fact[2]): result = impYes
- else: result = impNo
- elif sameTree(fact[2], loc):
- if inSet(aSet, fact[1]): result = impYes
- else: result = impNo
- of mInSet:
- if sameTree(fact[2], loc):
- result = compareSets(fact[1], aSet)
- of someLe:
- if sameTree(fact[1], loc):
- result = leImpliesIn(fact[1], fact[2], aSet)
- elif sameTree(fact[2], loc):
- result = geImpliesIn(fact[2], fact[1], aSet)
- of someLt:
- if sameTree(fact[1], loc):
- result = leImpliesIn(fact[1], fact[2].pred, aSet)
- elif sameTree(fact[2], loc):
- # 4 < x --> 3 <= x
- result = geImpliesIn(fact[2], fact[1].pred, aSet)
- of mNot, mOr, mAnd: assert(false, "impliesIn")
- else: discard
- proc valueIsNil(n: PNode): TImplication =
- if n.kind == nkNilLit: impYes
- elif n.kind in {nkStrLit..nkTripleStrLit, nkBracket, nkObjConstr}: impNo
- else: impUnknown
- proc impliesIsNil(fact, eq: PNode): TImplication =
- case fact[0].sym.magic
- of mIsNil:
- if sameTree(fact[1], eq[1]):
- result = impYes
- of someEq:
- if sameTree(fact[1], eq[1]):
- result = valueIsNil(fact[2].skipConv)
- elif sameTree(fact[2], eq[1]):
- result = valueIsNil(fact[1].skipConv)
- of mNot, mOr, mAnd: assert(false, "impliesIsNil")
- else: discard
- proc impliesGe(fact, x, c: PNode): TImplication =
- assert isLocation(x)
- case fact[0].sym.magic
- of someEq:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x = 4; question x >= 56? --> true iff 4 >= 56
- if leValue(c, fact[2]): result = impYes
- else: result = impNo
- elif sameTree(fact[2], x):
- if isValue(fact[1]) and isValue(c):
- if leValue(c, fact[1]): result = impYes
- else: result = impNo
- of someLt:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x < 4; question N <= x? --> false iff N <= 4
- if leValue(fact[2], c): result = impNo
- # fact: x < 4; question 2 <= x? --> we don't know
- elif sameTree(fact[2], x):
- # fact: 3 < x; question: N-1 < x ? --> true iff N-1 <= 3
- if isValue(fact[1]) and isValue(c):
- if leValue(c.pred, fact[1]): result = impYes
- of someLe:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x <= 4; question x >= 56? --> false iff 4 <= 56
- if leValue(fact[2], c): result = impNo
- # fact: x <= 4; question x >= 2? --> we don't know
- elif sameTree(fact[2], x):
- # fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3
- if isValue(fact[1]) and isValue(c):
- if leValue(c, fact[1]): result = impYes
- of mNot, mOr, mAnd: assert(false, "impliesGe")
- else: discard
- proc impliesLe(fact, x, c: PNode): TImplication =
- if not isLocation(x):
- if c.isValue:
- if leValue(x, x): return impYes
- else: return impNo
- return impliesGe(fact, c, x)
- case fact[0].sym.magic
- of someEq:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x = 4; question x <= 56? --> true iff 4 <= 56
- if leValue(fact[2], c): result = impYes
- else: result = impNo
- elif sameTree(fact[2], x):
- if isValue(fact[1]) and isValue(c):
- if leValue(fact[1], c): result = impYes
- else: result = impNo
- of someLt:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x < 4; question x <= N? --> true iff N-1 <= 4
- if leValue(fact[2], c.pred): result = impYes
- # fact: x < 4; question x <= 2? --> we don't know
- elif sameTree(fact[2], x):
- # fact: 3 < x; question: x <= 1 ? --> false iff 1 <= 3
- if isValue(fact[1]) and isValue(c):
- if leValue(c, fact[1]): result = impNo
- of someLe:
- if sameTree(fact[1], x):
- if isValue(fact[2]) and isValue(c):
- # fact: x <= 4; question x <= 56? --> true iff 4 <= 56
- if leValue(fact[2], c): result = impYes
- # fact: x <= 4; question x <= 2? --> we don't know
- elif sameTree(fact[2], x):
- # fact: 3 <= x; question: x <= 2 ? --> false iff 2 < 3
- if isValue(fact[1]) and isValue(c):
- if leValue(c, fact[1].pred): result = impNo
- of mNot, mOr, mAnd: assert(false, "impliesLe")
- else: discard
- proc impliesLt(fact, x, c: PNode): TImplication =
- # x < 3 same as x <= 2:
- let p = c.pred
- if p != c:
- result = impliesLe(fact, x, p)
- else:
- # 4 < x same as 3 <= x
- let q = x.pred
- if q != x:
- result = impliesLe(fact, q, c)
- proc `~`(x: TImplication): TImplication =
- case x
- of impUnknown: impUnknown
- of impNo: impYes
- of impYes: impNo
- proc factImplies(fact, prop: PNode): TImplication =
- case fact.getMagic
- of mNot:
- # Consider:
- # enum nkBinary, nkTernary, nkStr
- # fact: not (k <= nkBinary)
- # question: k in {nkStr}
- # --> 'not' for facts is entirely different than 'not' for questions!
- # it's provably wrong if every value > 4 is in the set {56}
- # That's because we compute the implication and 'a -> not b' cannot
- # be treated the same as 'not a -> b'
- # (not a) -> b compute as not (a -> b) ???
- # == not a or not b == not (a and b)
- let arg = fact[1]
- case arg.getMagic
- of mIsNil, mEqRef:
- return ~factImplies(arg, prop)
- of mAnd:
- # not (a and b) means not a or not b:
- # a or b --> both need to imply 'prop'
- let a = factImplies(arg[1], prop)
- let b = factImplies(arg[2], prop)
- if a == b: return ~a
- return impUnknown
- else:
- return impUnknown
- of mAnd:
- result = factImplies(fact[1], prop)
- if result != impUnknown: return result
- return factImplies(fact[2], prop)
- else: discard
- case prop[0].sym.magic
- of mNot: result = ~fact.factImplies(prop[1])
- of mIsNil: result = impliesIsNil(fact, prop)
- of someEq: result = impliesEq(fact, prop)
- of someLe: result = impliesLe(fact, prop[1], prop[2])
- of someLt: result = impliesLt(fact, prop[1], prop[2])
- of mInSet: result = impliesIn(fact, prop[2], prop[1])
- else: result = impUnknown
- proc doesImply*(facts: TModel, prop: PNode): TImplication =
- assert prop.kind in nkCallKinds
- for f in facts.s:
- # facts can be invalidated, in which case they are 'nil':
- if not f.isNil:
- result = f.factImplies(prop)
- if result != impUnknown: return
- proc impliesNotNil*(m: TModel, arg: PNode): TImplication =
- result = doesImply(m, m.g.operators.opIsNil.buildCall(arg).neg(m.g.operators))
- proc simpleSlice*(a, b: PNode): BiggestInt =
- # returns 'c' if a..b matches (i+c)..(i+c), -1 otherwise. (i)..(i) is matched
- # as if it is (i+0)..(i+0).
- if guards.sameTree(a, b):
- if a.getMagic in someAdd and a[2].kind in {nkCharLit..nkUInt64Lit}:
- result = a[2].intVal
- else:
- result = 0
- else:
- result = -1
- template isMul(x): untyped = x.getMagic in someMul
- template isDiv(x): untyped = x.getMagic in someDiv
- template isAdd(x): untyped = x.getMagic in someAdd
- template isSub(x): untyped = x.getMagic in someSub
- template isVal(x): untyped = x.kind in {nkCharLit..nkUInt64Lit}
- template isIntVal(x, y): untyped = x.intVal == y
- import macros
- macro `=~`(x: PNode, pat: untyped): bool =
- proc m(x, pat, conds: NimNode) =
- case pat.kind
- of nnkInfix:
- case $pat[0]
- of "*": conds.add getAst(isMul(x))
- of "/": conds.add getAst(isDiv(x))
- of "+": conds.add getAst(isAdd(x))
- of "-": conds.add getAst(isSub(x))
- else:
- error("invalid pattern")
- m(newTree(nnkBracketExpr, x, newLit(1)), pat[1], conds)
- m(newTree(nnkBracketExpr, x, newLit(2)), pat[2], conds)
- of nnkPar:
- if pat.len == 1:
- m(x, pat[0], conds)
- else:
- error("invalid pattern")
- of nnkIdent:
- let c = newTree(nnkStmtListExpr, newLetStmt(pat, x))
- conds.add c
- # XXX why is this 'isVal(pat)' and not 'isVal(x)'?
- if ($pat)[^1] == 'c': c.add(getAst(isVal(x)))
- else: c.add bindSym"true"
- of nnkIntLit:
- conds.add(getAst(isIntVal(x, pat.intVal)))
- else:
- error("invalid pattern")
- var conds = newTree(nnkBracket)
- m(x, pat, conds)
- result = nestList(ident"and", conds)
- proc isMinusOne(n: PNode): bool =
- n.kind in {nkCharLit..nkUInt64Lit} and n.intVal == -1
- proc pleViaModel(model: TModel; aa, bb: PNode): TImplication
- proc ple(m: TModel; a, b: PNode): TImplication =
- template `<=?`(a,b): untyped = ple(m,a,b) == impYes
- template `>=?`(a,b): untyped = ple(m, nkIntLit.newIntNode(b), a) == impYes
- # 0 <= 3
- if a.isValue and b.isValue:
- return if leValue(a, b): impYes else: impNo
- # use type information too: x <= 4 iff high(x) <= 4
- if b.isValue and a.typ != nil and a.typ.isOrdinalType:
- if lastOrd(nil, a.typ) <= b.intVal: return impYes
- # 3 <= x iff low(x) <= 3
- if a.isValue and b.typ != nil and b.typ.isOrdinalType:
- if a.intVal <= firstOrd(nil, b.typ): return impYes
- # x <= x
- if sameTree(a, b): return impYes
- # 0 <= x.len
- if b.getMagic in someLen and a.isValue:
- if a.intVal <= 0: return impYes
- # x <= y+c if 0 <= c and x <= y
- # x <= y+(-c) if c <= 0 and y >= x
- if b.getMagic in someAdd:
- if zero() <=? b[2] and a <=? b[1]: return impYes
- # x <= y-c if x+c <= y
- if b[2] <=? zero() and (canon(m.g.operators.opSub.buildCall(a, b[2]), m.g.operators) <=? b[1]):
- return impYes
- # x+c <= y if c <= 0 and x <= y
- if a.getMagic in someAdd and a[2] <=? zero() and a[1] <=? b: return impYes
- # x <= y*c if 1 <= c and x <= y and 0 <= y
- if b.getMagic in someMul:
- if a <=? b[1] and one() <=? b[2] and zero() <=? b[1]: return impYes
- if a.getMagic in someMul and a[2].isValue and a[1].getMagic in someDiv and
- a[1][2].isValue:
- # simplify (x div 4) * 2 <= y to x div (c div d) <= y
- if ple(m, buildCall(m.g.operators.opDiv, a[1][1], `|div|`(a[1][2], a[2])), b) == impYes:
- return impYes
- # x*3 + x == x*4. It follows that:
- # x*3 + y <= x*4 if y <= x and 3 <= 4
- if a =~ x*dc + y and b =~ x2*ec:
- if sameTree(x, x2):
- let ec1 = m.g.operators.opAdd.buildCall(ec, minusOne())
- if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? x:
- return impYes
- elif a =~ x*dc and b =~ x2*ec + y:
- #echo "BUG cam ehrer e ", a, " <=? ", b
- if sameTree(x, x2):
- let ec1 = m.g.operators.opAdd.buildCall(ec, minusOne())
- if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? zero():
- return impYes
- # x+c <= x+d if c <= d. Same for *, - etc.
- if a.getMagic in someBinaryOp and a.getMagic == b.getMagic:
- if sameTree(a[1], b[1]) and a[2] <=? b[2]: return impYes
- elif sameTree(a[2], b[2]) and a[1] <=? b[1]: return impYes
- # x div c <= y if 1 <= c and 0 <= y and x <= y:
- if a.getMagic in someDiv:
- if one() <=? a[2] and zero() <=? b and a[1] <=? b: return impYes
- # x div c <= x div d if d <= c
- if b.getMagic in someDiv:
- if sameTree(a[1], b[1]) and b[2] <=? a[2]: return impYes
- # x div z <= x - 1 if z <= x
- if a[2].isValue and b.getMagic in someAdd and b[2].isMinusOne:
- if a[2] <=? a[1] and sameTree(a[1], b[1]): return impYes
- # slightly subtle:
- # x <= max(y, z) iff x <= y or x <= z
- # note that 'x <= max(x, z)' is a special case of the above rule
- if b.getMagic in someMax:
- if a <=? b[1] or a <=? b[2]: return impYes
- # min(x, y) <= z iff x <= z or y <= z
- if a.getMagic in someMin:
- if a[1] <=? b or a[2] <=? b: return impYes
- # use the knowledge base:
- return pleViaModel(m, a, b)
- #return doesImply(m, o.opLe.buildCall(a, b))
- type TReplacements = seq[tuple[a, b: PNode]]
- proc replaceSubTree(n, x, by: PNode): PNode =
- if sameTree(n, x):
- result = by
- elif hasSubTree(n, x):
- result = shallowCopy(n)
- for i in 0..n.safeLen-1:
- result[i] = replaceSubTree(n[i], x, by)
- else:
- result = n
- proc applyReplacements(n: PNode; rep: TReplacements): PNode =
- result = n
- for x in rep: result = result.replaceSubTree(x.a, x.b)
- proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication =
- # now check for inferrable facts: a <= b and b <= c implies a <= c
- for i in 0..m.s.high:
- let fact = m.s[i]
- if fact != nil and fact.getMagic in someLe:
- # mark as used:
- m.s[i] = nil
- # i <= len-100
- # i <=? len-1
- # --> true if (len-100) <= (len-1)
- let x = fact[1]
- let y = fact[2]
- # x <= y.
- # Question: x <= b? True iff y <= b.
- if sameTree(x, a):
- if ple(m, y, b) == impYes: return impYes
- if y.getMagic in someAdd and b.getMagic in someAdd and sameTree(y[1], b[1]):
- if ple(m, b[2], y[2]) == impYes:
- return impYes
- # x <= y implies a <= b if a <= x and y <= b
- if ple(m, a, x) == impYes:
- if ple(m, y, b) == impYes:
- return impYes
- #if pleViaModelRec(m, y, b): return impYes
- # fact: 16 <= i
- # x y
- # question: i <= 15? no!
- result = impliesLe(fact, a, b)
- if result != impUnknown:
- return result
- when false:
- # given: x <= y; y==a; x <= a this means: a <= b if x <= b
- if sameTree(y, a):
- result = ple(m, b, x)
- if result != impUnknown:
- return result
- proc pleViaModel(model: TModel; aa, bb: PNode): TImplication =
- # compute replacements:
- var replacements: TReplacements = @[]
- for fact in model.s:
- if fact != nil and fact.getMagic in someEq:
- let a = fact[1]
- let b = fact[2]
- if a.kind == nkSym: replacements.add((a,b))
- else: replacements.add((b,a))
- var m: TModel
- var a = aa
- var b = bb
- if replacements.len > 0:
- m.s = @[]
- m.g = model.g
- # make the other facts consistent:
- for fact in model.s:
- if fact != nil and fact.getMagic notin someEq:
- # XXX 'canon' should not be necessary here, but it is
- m.s.add applyReplacements(fact, replacements).canon(m.g.operators)
- a = applyReplacements(aa, replacements)
- b = applyReplacements(bb, replacements)
- else:
- # we have to make a copy here, because the model will be modified:
- m = model
- result = pleViaModelRec(m, a, b)
- proc proveLe*(m: TModel; a, b: PNode): TImplication =
- let x = canon(m.g.operators.opLe.buildCall(a, b), m.g.operators)
- #echo "ROOT ", renderTree(x[1]), " <=? ", renderTree(x[2])
- result = ple(m, x[1], x[2])
- if result == impUnknown:
- # try an alternative: a <= b iff not (b < a) iff not (b+1 <= a):
- let y = canon(m.g.operators.opLe.buildCall(m.g.operators.opAdd.buildCall(b, one()), a), m.g.operators)
- result = ~ple(m, y[1], y[2])
- proc addFactLe*(m: var TModel; a, b: PNode) =
- m.s.add canon(m.g.operators.opLe.buildCall(a, b), m.g.operators)
- proc addFactLt*(m: var TModel; a, b: PNode) =
- let bb = m.g.operators.opAdd.buildCall(b, minusOne())
- addFactLe(m, a, bb)
- proc settype(n: PNode): PType =
- result = newType(tySet, ItemId(module: -1, item: -1), n.typ.owner)
- var idgen: IdGenerator
- addSonSkipIntLit(result, n.typ, idgen)
- proc buildOf(it, loc: PNode; o: Operators): PNode =
- var s = newNodeI(nkCurly, it.info, it.len-1)
- s.typ = settype(loc)
- for i in 0..<it.len-1: s[i] = it[i]
- result = newNodeI(nkCall, it.info, 3)
- result[0] = newSymNode(o.opContains)
- result[1] = s
- result[2] = loc
- proc buildElse(n: PNode; o: Operators): PNode =
- var s = newNodeIT(nkCurly, n.info, settype(n[0]))
- for i in 1..<n.len-1:
- let branch = n[i]
- assert branch.kind != nkElse
- if branch.kind == nkOfBranch:
- for j in 0..<branch.len-1:
- s.add(branch[j])
- result = newNodeI(nkCall, n.info, 3)
- result[0] = newSymNode(o.opContains)
- result[1] = s
- result[2] = n[0]
- proc addDiscriminantFact*(m: var TModel, n: PNode) =
- var fact = newNodeI(nkCall, n.info, 3)
- fact[0] = newSymNode(m.g.operators.opEq)
- fact[1] = n[0]
- fact[2] = n[1]
- m.s.add fact
- proc addAsgnFact*(m: var TModel, key, value: PNode) =
- var fact = newNodeI(nkCall, key.info, 3)
- fact[0] = newSymNode(m.g.operators.opEq)
- fact[1] = key
- fact[2] = value
- m.s.add fact
- proc sameSubexprs*(m: TModel; a, b: PNode): bool =
- # This should be used to check whether two *path expressions* refer to the
- # same memory location according to 'm'. This is tricky:
- # lock a[i].guard:
- # ...
- # access a[i].guarded
- #
- # Here a[i] is the same as a[i] iff 'i' and 'a' are not changed via '...'.
- # However, nil checking requires exactly the same mechanism! But for now
- # we simply use sameTree and live with the unsoundness of the analysis.
- var check = newNodeI(nkCall, a.info, 3)
- check[0] = newSymNode(m.g.operators.opEq)
- check[1] = a
- check[2] = b
- result = m.doesImply(check) == impYes
- proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) =
- let branch = n[i]
- if branch.kind == nkOfBranch:
- m.s.add buildOf(branch, n[0], m.g.operators)
- else:
- m.s.add n.buildElse(m.g.operators).neg(m.g.operators)
- proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
- if check[1].kind == nkCurly:
- result = copyTree(check)
- if access.kind == nkDotExpr:
- var a = copyTree(access)
- a[1] = check[2]
- result[2] = a
- # 'access.kind != nkDotExpr' can happen for object constructors
- # which we don't check yet
- else:
- # it is some 'not'
- assert check.getMagic == mNot
- result = buildProperFieldCheck(access, check[1], o).neg(o)
- proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef; produceError: bool) =
- for i in 1..<n.len:
- let check = buildProperFieldCheck(n[0], n[i], m.g.operators)
- if check != nil and m.doesImply(check) != impYes:
- if produceError:
- localError(conf, n.info, "field access outside of valid case branch: " & renderTree(n[0]))
- else:
- message(conf, n.info, warnProveField, renderTree(n[0]))
- break
|