Finer analysis for array access (#16787)

* Refine the analysis for array access
* Cleanup
* Add comments
This commit is contained in:
Clyybber
2021-01-24 21:01:41 +01:00
committed by GitHub
parent 8241e55023
commit 301e5838ec
3 changed files with 106 additions and 29 deletions

View File

@@ -574,32 +574,84 @@ proc skipConvDfa*(n: PNode): PNode =
result = result[1]
else: break
proc aliases*(obj, field: PNode): bool =
var n = field
var obj = obj
while true:
case obj.kind
of {nkObjDownConv, nkObjUpConv, nkAddr, nkHiddenAddr, nkDerefExpr, nkHiddenDeref}:
obj = obj[0]
of PathKinds1:
obj = obj[1]
else: break
while true:
if sameTrees(obj, n): return true
case n.kind
of PathKinds0:
n = n[0]
of PathKinds1:
n = n[1]
else: break
type AliasKind* = enum
yes, no, maybe
proc aliases*(obj, field: PNode): AliasKind =
# obj -> field:
# x -> x: true
# x -> x.f: true
# x.f -> x: false
# x.f -> x.f: true
# x.f -> x.v: false
# x -> x[0]: true
# x[0] -> x: false
# x[0] -> x[0]: true
# x[0] -> x[1]: false
# x -> x[i]: true
# x[i] -> x: false
# x[i] -> x[i]: maybe; Further analysis could make this return true when i is a runtime-constant
# x[i] -> x[j]: maybe; also returns maybe if only one of i or j is a compiletime-constant
template collectImportantNodes(result, n) =
var result: seq[PNode]
var n = n
while true:
case n.kind
of PathKinds0 - {nkDotExpr, nkCheckedFieldExpr, nkBracketExpr}:
n = n[0]
of PathKinds1:
n = n[1]
of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr:
result.add n
n = n[0]
of nkSym:
result.add n; break
else: return no
collectImportantNodes(objImportantNodes, obj)
collectImportantNodes(fieldImportantNodes, field)
# If field is less nested than obj, then it cannot be part of/aliased by obj
if fieldImportantNodes.len < objImportantNodes.len: return no
result = yes
for i in 1..objImportantNodes.len:
# We compare the nodes leading to the location of obj and field
# with each other.
# We continue until they diverge, in which case we return no, or
# until we reach the location of obj, in which case we do not need
# to look further, since field must be part of/aliased by obj now.
# If we encounter an element access using an index which is a runtime value,
# we simply return maybe instead of yes; should further nodes not diverge.
let currFieldPath = fieldImportantNodes[^i]
let currObjPath = objImportantNodes[^i]
if currFieldPath.kind != currObjPath.kind:
return no
case currFieldPath.kind
of nkSym:
if currFieldPath.sym != currObjPath.sym: return no
of nkDotExpr, nkCheckedFieldExpr:
if currFieldPath[1].sym != currObjPath[1].sym: return no
of nkBracketExpr:
if currFieldPath[1].kind in nkLiterals and currObjPath[1].kind in nkLiterals:
if currFieldPath[1].intVal != currObjPath[1].intVal:
return no
else:
result = maybe
else: assert false # unreachable
type InstrTargetKind* = enum
None, Full, Partial
proc instrTargets*(insloc, loc: PNode): InstrTargetKind =
if sameTrees(insloc, loc) or insloc.aliases(loc):
case insloc.aliases(loc)
of yes:
Full # x -> x; x -> x.f
elif loc.aliases(insloc):
of maybe:
Partial # We treat this like a partial write/read
elif loc.aliases(insloc) != no:
Partial # x.f -> x
else: None
@@ -607,16 +659,10 @@ proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
var n = orig
while true:
case n.kind
of PathKinds0 - {nkBracketExpr, nkHiddenDeref, nkDerefExpr}:
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
n = n[0]
of PathKinds1:
n = n[1]
of nkBracketExpr:
# in a[i] the 'i' must be known
if n.len > 1 and n[1].kind in {nkCharLit..nkUInt64Lit}:
n = n[0]
else:
return false
of nkHiddenDeref, nkDerefExpr:
# We "own" sinkparam[].loc but not ourVar[].location as it is a nasty
# pointer indirection.

View File

@@ -969,7 +969,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
proc sameLocation*(a, b: PNode): bool =
proc sameConstant(a, b: PNode): bool =
a.kind in nkLiterals and exprStructuralEquivalent(a, b)
a.kind in nkLiterals and a.intVal == b.intVal
const nkEndPoint = {nkSym, nkDotExpr, nkCheckedFieldExpr, nkBracketExpr}
if a.kind in nkEndPoint and b.kind in nkEndPoint:
@@ -1006,7 +1006,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod
# unpacking of tuple: take over the elements
result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
if not aliases(dest, ri):
if aliases(dest, ri) == no:
# Rule 3: `=sink`(x, z); wasMoved(z)
var snk = c.genSink(dest, ri, isDecl)
result = newTree(nkStmtList, snk, c.genWasMoved(ri))

View File

@@ -92,10 +92,20 @@ copy
destroy
destroy
destroy
sink
sink
destroy
copy
(f: 1)
destroy
destroy
part-to-whole assigment:
sink
(children: @[])
destroy
sink
(children: @[])
destroy
copy
destroy
'''
@@ -675,6 +685,16 @@ proc caseNotAConstant =
caseNotAConstant()
proc potentialSelfAssign(i: var int) =
var a: array[2, OO]
a[i] = OO(f: 1)
a[1] = OO(f: 2)
a[i+1] = a[i] # This must not =sink, but =copy
inc i
echo a[i-1] # (f: 1)
potentialSelfAssign (var xi = 0; xi)
#--------------------------------------------------------------------
echo "part-to-whole assigment:"
@@ -700,6 +720,17 @@ proc partToWholeSeq =
partToWholeSeq()
proc partToWholeSeqRTIndex =
var i = 0
var t = Tree(children: @[Tree()])
t = t.children[i] # See comment in partToWholeSeq
var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()])
tc = tc.children[i] # See comment in partToWholeSeq
echo tc
partToWholeSeqRTIndex()
type List = object
next: ref List