import ast
import std / assertions
const
PathKinds0* = {nkDotExpr, nkCheckedFieldExpr,
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv}
proc skipConvDfa*(n: PNode): PNode =
result = n
while true:
case result.kind
of nkObjDownConv, nkObjUpConv:
result = result[0]
of PathKinds1:
result = result[1]
else: break
proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool =
var n = orig
while true:
case n.kind
of PathKinds0 - {nkHiddenDeref, nkDerefExpr}:
n = n[0]
of PathKinds1:
n = n[1]
of nkHiddenDeref, nkDerefExpr:
# We "own" sinkparam[].loc but not ourVar[].location as it is a nasty
# pointer indirection.
# bug #14159, we cannot reason about sinkParam[].location as it can
# still be shared for tyRef.
n = n[0]
return n.kind == nkSym and n.sym.owner == owner and
(n.sym.typ.skipTypes(abstractInst-{tyOwned}).kind in {tyOwned})
else: break
# XXX Allow closure deref operations here if we know
# the owner controlled the closure allocation?
result = n.kind == nkSym and n.sym.owner == owner and
{sfGlobal, sfThread, sfCursor} * n.sym.flags == {} and
(n.sym.kind != skParam or isSinkParam(n.sym)) # or n.sym.typ.kind == tyVar)
# Note: There is a different move analyzer possible that checks for
# consume(param.key); param.key = newValue for all paths. Then code like
#
# let splited = split(move self.root, x)
# self.root = merge(splited.lower, splited.greater)
#
# could be written without the ``move self.root``. However, this would be
# wrong! Then the write barrier for the ``self.root`` assignment would
# free the old data and all is lost! Lesson: Don't be too smart, trust the
# lower level C++ optimizer to specialize this code.
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[]: true
# x[] -> x: 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, nkBracketExpr, nkDerefExpr, nkHiddenDeref}:
n = n[0]
of PathKinds1:
n = n[1]
of nkDotExpr, nkBracketExpr, nkDerefExpr, nkHiddenDeref:
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:
if currFieldPath[1].sym != currObjPath[1].sym: return no
of nkDerefExpr, nkHiddenDeref:
discard
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