* fix #16185 * fix test * fix comment * fix comment * better approach * Add more tests and move sameLocation to injectdestructors * Better and more strict sameLocation * Small cleanup and preliminary spec clarification * Fix * Fix doc * Expand test Co-authored-by: Andrey R (cooldome) <ariabushenko@gmail.com>
This commit is contained in:
parent
4fc7fcb775
commit
ccb11a63fb
4 changed files with 254 additions and 63 deletions
|
|
@ -968,7 +968,37 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode =
|
||||||
else:
|
else:
|
||||||
internalError(c.graph.config, n.info, "cannot inject destructors to node kind: " & $n.kind)
|
internalError(c.graph.config, n.info, "cannot inject destructors to node kind: " & $n.kind)
|
||||||
|
|
||||||
|
proc sameLocation*(a, b: PNode): bool =
|
||||||
|
proc sameConstant(a, b: PNode): bool =
|
||||||
|
a.kind in nkLiterals and exprStructuralEquivalent(a, b)
|
||||||
|
|
||||||
|
const nkEndPoint = {nkSym, nkDotExpr, nkCheckedFieldExpr, nkBracketExpr}
|
||||||
|
if a.kind in nkEndPoint and b.kind in nkEndPoint:
|
||||||
|
if a.kind == b.kind:
|
||||||
|
case a.kind
|
||||||
|
of nkSym: a.sym == b.sym
|
||||||
|
of nkDotExpr, nkCheckedFieldExpr: sameLocation(a[0], b[0]) and sameLocation(a[1], b[1])
|
||||||
|
of nkBracketExpr: sameLocation(a[0], b[0]) and sameConstant(a[1], b[1])
|
||||||
|
else: false
|
||||||
|
else: false
|
||||||
|
else:
|
||||||
|
case a.kind
|
||||||
|
of nkSym, nkDotExpr, nkCheckedFieldExpr, nkBracketExpr:
|
||||||
|
# Reached an endpoint, flip to recurse the other side.
|
||||||
|
sameLocation(b, a)
|
||||||
|
of nkAddr, nkHiddenAddr, nkDerefExpr, nkHiddenDeref:
|
||||||
|
# We don't need to check addr/deref levels or differentiate between the two,
|
||||||
|
# since pointers don't have hooks :) (e.g: var p: ptr pointer; p[] = addr p)
|
||||||
|
sameLocation(a[0], b)
|
||||||
|
of nkObjDownConv, nkObjUpConv: sameLocation(a[0], b)
|
||||||
|
of nkHiddenStdConv, nkHiddenSubConv: sameLocation(a[1], b)
|
||||||
|
else: false
|
||||||
|
|
||||||
proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNode =
|
proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNode =
|
||||||
|
if sameLocation(dest, ri):
|
||||||
|
# rule (self-assignment-removal):
|
||||||
|
result = newNodeI(nkEmpty, dest.info)
|
||||||
|
else:
|
||||||
case ri.kind
|
case ri.kind
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
|
result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
|
||||||
|
|
@ -996,10 +1026,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod
|
||||||
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
|
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
|
||||||
result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
|
result = c.genSink(dest, p(ri, c, s, consumed), isDecl)
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if dest.kind == nkSym and dest.sym == ri.sym:
|
if isSinkParam(ri.sym) and isLastRead(ri, c):
|
||||||
# rule (self-assignment-removal):
|
|
||||||
result = newNodeI(nkEmpty, dest.info)
|
|
||||||
elif isSinkParam(ri.sym) and isLastRead(ri, c):
|
|
||||||
# Rule 3: `=sink`(x, z); wasMoved(z)
|
# Rule 3: `=sink`(x, z); wasMoved(z)
|
||||||
let snk = c.genSink(dest, ri, isDecl)
|
let snk = c.genSink(dest, ri, isDecl)
|
||||||
result = newTree(nkStmtList, snk, c.genWasMoved(ri))
|
result = newTree(nkStmtList, snk, c.genWasMoved(ri))
|
||||||
|
|
|
||||||
|
|
@ -376,8 +376,14 @@ it's subtle.
|
||||||
|
|
||||||
The simple case of ``x = x`` cannot be turned
|
The simple case of ``x = x`` cannot be turned
|
||||||
into ``=sink(x, x); wasMoved(x)`` because that would lose ``x``'s value.
|
into ``=sink(x, x); wasMoved(x)`` because that would lose ``x``'s value.
|
||||||
The solution is that simple self-assignments are simply transformed into
|
The solution is that simple self-assignments that consist of
|
||||||
an empty statement that does nothing.
|
|
||||||
|
- Symbols: ``x = x``
|
||||||
|
- Field access: ``x.f = x.f``
|
||||||
|
- Array, sequence or string access with indices known at compile-time: ``x[0] = x[0]``
|
||||||
|
|
||||||
|
are transformed into an empty statement that does nothing.
|
||||||
|
The compiler is free to optimize further cases.
|
||||||
|
|
||||||
The complex case looks like a variant of ``x = f(x)``, we consider
|
The complex case looks like a variant of ``x = f(x)``, we consider
|
||||||
``x = select(rand() < 0.5, x, y)`` here:
|
``x = select(rand() < 0.5, x, y)`` here:
|
||||||
|
|
|
||||||
|
|
@ -168,3 +168,50 @@ proc testRefs() =
|
||||||
testRefs()
|
testRefs()
|
||||||
|
|
||||||
doAssert(dDestroyed == 2)
|
doAssert(dDestroyed == 2)
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Issue #16185, complex self-assingment elimination
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type
|
||||||
|
CpuStorage*[T] = ref CpuStorageObj[T]
|
||||||
|
CpuStorageObj[T] = object
|
||||||
|
size*: int
|
||||||
|
raw_buffer*: ptr UncheckedArray[T]
|
||||||
|
Tensor[T] = object
|
||||||
|
buf*: CpuStorage[T]
|
||||||
|
TestObject = object
|
||||||
|
x: Tensor[float]
|
||||||
|
|
||||||
|
proc `=destroy`[T](s: var CpuStorageObj[T]) =
|
||||||
|
if s.raw_buffer != nil:
|
||||||
|
s.raw_buffer.deallocShared()
|
||||||
|
s.size = 0
|
||||||
|
s.raw_buffer = nil
|
||||||
|
|
||||||
|
proc `=`[T](a: var CpuStorageObj[T]; b: CpuStorageObj[T]) {.error.}
|
||||||
|
|
||||||
|
proc allocCpuStorage[T](s: var CpuStorage[T], size: int) =
|
||||||
|
new(s)
|
||||||
|
s.raw_buffer = cast[ptr UncheckedArray[T]](allocShared0(sizeof(T) * size))
|
||||||
|
s.size = size
|
||||||
|
|
||||||
|
proc newTensor[T](size: int): Tensor[T] =
|
||||||
|
allocCpuStorage(result.buf, size)
|
||||||
|
|
||||||
|
proc `[]`[T](t: Tensor[T], idx: int): T = t.buf.raw_buffer[idx]
|
||||||
|
proc `[]=`[T](t: Tensor[T], idx: int, val: T) = t.buf.raw_buffer[idx] = val
|
||||||
|
|
||||||
|
proc toTensor[T](s: seq[T]): Tensor[T] =
|
||||||
|
result = newTensor[T](s.len)
|
||||||
|
for i, x in s:
|
||||||
|
result[i] = x
|
||||||
|
|
||||||
|
proc main2() =
|
||||||
|
var t: TestObject
|
||||||
|
t.x = toTensor(@[1.0, 2, 3, 4])
|
||||||
|
t.x = t.x
|
||||||
|
doAssert(t.x.buf != nil) # self-assignment above should be eliminated
|
||||||
|
|
||||||
|
main2()
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,25 @@ bye
|
||||||
()
|
()
|
||||||
()
|
()
|
||||||
()
|
()
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
copy (self-assign)
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
1
|
||||||
|
destroy
|
||||||
|
destroy
|
||||||
|
copy
|
||||||
|
@[(f: 2), (f: 2), (f: 3)]
|
||||||
|
destroy
|
||||||
|
destroy
|
||||||
|
destroy
|
||||||
'''
|
'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -557,3 +576,95 @@ echo $w2[]
|
||||||
|
|
||||||
let w3 = newWrapper2(-1)
|
let w3 = newWrapper2(-1)
|
||||||
echo $w3[]
|
echo $w3[]
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------
|
||||||
|
#self-assignments
|
||||||
|
|
||||||
|
# Self-assignments that are not statically determinable will get
|
||||||
|
# turned into `=copy` calls as caseBracketExprCopy demonstrates.
|
||||||
|
# (`=copy` handles self-assignments at runtime)
|
||||||
|
|
||||||
|
type
|
||||||
|
OO = object
|
||||||
|
f: int
|
||||||
|
W = object
|
||||||
|
o: OO
|
||||||
|
|
||||||
|
proc `=destroy`(x: var OO) =
|
||||||
|
if x.f != 0:
|
||||||
|
echo "destroy"
|
||||||
|
x.f = 0
|
||||||
|
|
||||||
|
proc `=sink`(x: var OO, y: OO) =
|
||||||
|
`=destroy`(x)
|
||||||
|
echo "sink"
|
||||||
|
x.f = y.f
|
||||||
|
|
||||||
|
proc `=copy`(x: var OO, y: OO) =
|
||||||
|
if x.f != y.f:
|
||||||
|
`=destroy`(x)
|
||||||
|
echo "copy"
|
||||||
|
x.f = y.f
|
||||||
|
else:
|
||||||
|
echo "copy (self-assign)"
|
||||||
|
|
||||||
|
proc caseSym =
|
||||||
|
var o = OO(f: 1)
|
||||||
|
o = o # NOOP
|
||||||
|
echo o.f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseSym()
|
||||||
|
|
||||||
|
proc caseDotExpr =
|
||||||
|
var w = W(o: OO(f: 1))
|
||||||
|
w.o = w.o # NOOP
|
||||||
|
echo w.o.f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseDotExpr()
|
||||||
|
|
||||||
|
proc caseBracketExpr =
|
||||||
|
var w = [0: OO(f: 1)]
|
||||||
|
w[0] = w[0] # NOOP
|
||||||
|
echo w[0].f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseBracketExpr()
|
||||||
|
|
||||||
|
proc caseBracketExprCopy =
|
||||||
|
var w = [0: OO(f: 1)]
|
||||||
|
let i = 0
|
||||||
|
w[i] = w[0] # "copy (self-assign)"
|
||||||
|
echo w[0].f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseBracketExprCopy()
|
||||||
|
|
||||||
|
proc caseDotExprAddr =
|
||||||
|
var w = W(o: OO(f: 1))
|
||||||
|
w.o = addr(w.o)[] # NOOP
|
||||||
|
echo w.o.f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseDotExprAddr()
|
||||||
|
|
||||||
|
proc caseBracketExprAddr =
|
||||||
|
var w = [0: OO(f: 1)]
|
||||||
|
addr(w[0])[] = addr(addr(w[0])[])[] # NOOP
|
||||||
|
echo w[0].f # "1"
|
||||||
|
# "destroy"
|
||||||
|
|
||||||
|
caseBracketExprAddr()
|
||||||
|
|
||||||
|
proc caseNotAConstant =
|
||||||
|
var i = 0
|
||||||
|
proc rand: int =
|
||||||
|
result = i
|
||||||
|
inc i
|
||||||
|
var s = @[OO(f: 1), OO(f: 2), OO(f: 3)]
|
||||||
|
s[rand()] = s[rand()] # "destroy" "copy"
|
||||||
|
echo s # @[(f: 2), (f: 2), (f: 3)]
|
||||||
|
|
||||||
|
caseNotAConstant()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue