Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make sink operator optional #13068

Merged
merged 8 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
- An `align` pragma can now be used for variables and object fields, similar
to the `alignas` declaration modifier in C/C++.

- `=sink` type bound operator is now optional. Compiler can now use combination
of `=destroy` and `copyMem` to move objects efficiently.


## Language changes

- Unsigned integer operators have been fixed to allow promotion of the first operand.
Expand Down
60 changes: 23 additions & 37 deletions compiler/injectdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ proc genOp(c: Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
addrExp.add(dest)
result = newTree(nkCall, newSymNode(op), addrExp)

proc genDestroy(c: Con; dest: PNode): PNode =
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
result = genOp(c, t, attachedDestructor, dest, nil)

when false:
proc preventMoveRef(dest, ri: PNode): bool =
let lhs = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
Expand All @@ -248,18 +252,17 @@ proc canBeMoved(c: Con; t: PType): bool {.inline.} =

proc genSink(c: var Con; dest, ri: PNode): PNode =
if isFirstWrite(dest, c): # optimize sink call into a bitwise memcopy
result = newTree(nkFastAsgn, dest)
result = newTree(nkFastAsgn, dest, ri)
else:
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
let k = if t.attachedOps[attachedSink] != nil: attachedSink
else: attachedAsgn
if t.attachedOps[k] != nil:
result = genOp(c, t, k, dest, ri)
if t.attachedOps[attachedSink] != nil:
result = genOp(c, t, attachedSink, dest, ri)
result.add ri
else:
# in rare cases only =destroy exists but no sink or assignment
# (see Pony object in tmove_objconstr.nim)
# we generate a fast assignment in this case:
result = newTree(nkFastAsgn, dest)
# the default is to use combination of `=destroy(dest)` and
# and copyMem(dest, source). This is efficient.
let snk = newTree(nkFastAsgn, dest, ri)
result = newTree(nkStmtList, genDestroy(c, dest), snk)

proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
Expand All @@ -273,10 +276,6 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode =
checkForErrorPragma(c, t, ri, "=")
result = genCopyNoCheck(c, dest, ri)

proc genDestroy(c: Con; dest: PNode): PNode =
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
result = genOp(c, t, attachedDestructor, dest, nil)

proc addTopVar(c: var Con; v: PNode) =
c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)

Expand All @@ -298,8 +297,6 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =

proc destructiveMoveVar(n: PNode; c: var Con): PNode =
# generate: (let tmp = v; reset(v); tmp)
# XXX: Strictly speaking we can only move if there is a ``=sink`` defined
# or if no ``=sink`` is defined and also no assignment.
if not hasDestructor(n.typ):
result = copyTree(n)
else:
Expand Down Expand Up @@ -440,9 +437,7 @@ proc ensureDestruction(arg: PNode; c: var Con): PNode =
# This was already done in the sink parameter handling logic.
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
let tmp = getTemp(c, arg.typ, arg.info)
var sinkExpr = genSink(c, tmp, arg)
sinkExpr.add arg
result.add sinkExpr
result.add genSink(c, tmp, arg)
result.add tmp
c.destroys.add genDestroy(c, tmp)
else:
Expand Down Expand Up @@ -598,8 +593,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
if ri.kind == nkEmpty and c.inLoop > 0:
ri = genDefaultCall(v.typ, c, v.info)
if ri.kind != nkEmpty:
let r = moveOrCopy(v, ri, c)
result.add r
result.add moveOrCopy(v, ri, c)
else: # keep the var but transform 'ri':
var v = copyNode(n)
var itCopy = copyNode(it)
Expand Down Expand Up @@ -667,8 +661,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
if isUnpackedTuple(dest):
result = newTree(nkFastAsgn, dest, p(ri, c, consumed))
else:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed)
result = genSink(c, dest, p(ri, c, consumed))
of nkBracketExpr:
if isUnpackedTuple(ri[0]):
# unpacking of tuple: take over elements
Expand All @@ -677,7 +670,6 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
not aliases(dest, ri):
# Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
Expand All @@ -686,24 +678,21 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
# array constructor
if ri.len > 0 and isDangerousSeq(ri.typ):
result = genCopy(c, dest, ri)
result.add p(ri, c, consumed)
else:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed)
result = genSink(c, dest, p(ri, c, consumed))
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
result = genSink(c, dest, ri)
result.add p(ri, c, consumed)
result = genSink(c, dest, p(ri, c, consumed))
of nkSym:
if isSinkParam(ri.sym):
# Rule 3: `=sink`(x, z); wasMoved(z)
sinkParamIsLastReadCheck(c, ri)
var snk = genSink(c, dest, ri)
snk.add ri
let snk = genSink(c, dest, ri)
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
elif ri.sym.kind != skParam and ri.sym.owner == c.owner and
isLastRead(ri, c) and canBeMoved(c, dest.typ):
# Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri)
snk.add ri
let snk = genSink(c, dest, ri)
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
Expand All @@ -716,25 +705,22 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
copyRi[1] = result[^1]
result[^1] = copyRi
else:
result = genSink(c, dest, ri)
result.add p(ri, c, sinkArg)
result = genSink(c, dest, p(ri, c, sinkArg))
of nkObjDownConv, nkObjUpConv:
when false:
result = moveOrCopy(dest, ri[0], c)
let copyRi = copyTree(ri)
copyRi[0] = result[^1]
result[^1] = copyRi
else:
result = genSink(c, dest, ri)
result.add p(ri, c, sinkArg)
result = genSink(c, dest, p(ri, c, sinkArg))
of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt:
handleNested(ri): moveOrCopy(dest, node, c)
else:
if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and
canBeMoved(c, dest.typ):
# Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri)
snk.add ri
let snk = genSink(c, dest, ri)
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else:
result = genCopy(c, dest, ri)
Expand Down
30 changes: 19 additions & 11 deletions compiler/liftdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -775,18 +775,26 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
# register this operation already:
typ.attachedOps[kind] = result

var tk: TTypeKind
if g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
else:
tk = tyNone # no special casing for strings and seqs
case tk
of tySequence:
fillSeqOp(a, typ, body, d, newSymNode(src))
of tyString:
fillStrOp(a, typ, body, d, newSymNode(src))

if kind == attachedSink and typ.attachedOps[attachedDestructor] != nil and
sfOverriden in typ.attachedOps[attachedDestructor].flags:
## compiler can use a combination of `=destroy` and memCopy for sink op
dest.flags.incl sfCursor
body.add newOpCall(typ.attachedOps[attachedDestructor], d[0])
body.add newAsgnStmt(d, newSymNode(src))
else:
fillBody(a, typ, body, d, newSymNode(src))
var tk: TTypeKind
if g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
else:
tk = tyNone # no special casing for strings and seqs
case tk
of tySequence:
fillSeqOp(a, typ, body, d, newSymNode(src))
of tyString:
fillStrOp(a, typ, body, d, newSymNode(src))
else:
fillBody(a, typ, body, d, newSymNode(src))

var n = newNodeI(nkProcDef, info, bodyPos+1)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
Expand Down
8 changes: 6 additions & 2 deletions doc/destructors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ written as:
a.data[i] = b.data[i]

proc `=sink`*[T](a: var myseq[T]; b: myseq[T]) =
# move assignment
# move assignment, optional.
# Compiler is using `=destroy` and `copyMem` when not provided
`=destroy`(a)
a.len = b.len
a.cap = b.cap
Expand Down Expand Up @@ -130,7 +131,10 @@ A `=sink` hook moves an object around, the resources are stolen from the source
and passed to the destination. It is ensured that source's destructor does
not free the resources afterwards by setting the object to its default value
(the value the object's state started in). Setting an object ``x`` back to its
default value is written as ``wasMoved(x)``.
default value is written as ``wasMoved(x)``. When not provided the compiler
is using a combination of `=destroy` and `copyMem` instead. This is efficient
hence users rarely need to implement their own `=sink` operator, it is enough to
provide `=destroy` and `=`, compiler will take care about the rest.

The prototype of this hook for a type ``T`` needs to be:

Expand Down
6 changes: 0 additions & 6 deletions tests/destructor/smart_ptr.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ proc `=`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) {.inline.} =
discard atomicInc(src.val[].atomicCounter)
dest.val = src.val

proc `=sink`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) {.inline.} =
if dest.val != src.val:
if dest.val != nil:
`=destroy`(dest)
dest.val = src.val

proc newSharedPtr*[T](val: sink T): SharedPtr[T] =
result.val = cast[type(result.val)](allocShared0(sizeof(result.val[])))
result.val.atomicCounter = 1
Expand Down
10 changes: 4 additions & 6 deletions tests/destructor/tdestructor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ mygeneric2 destroyed
----
----
myobj destroyed
myobj destroyed
myobj destroyed
myobj destroyed
mygeneric1 destroyed
---
myobj destroyed
myobj destroyed
myobj destroyed
'''
"""

Expand All @@ -37,8 +33,10 @@ type
p: pointer

proc `=destroy`(o: var TMyObj) =
if o.p != nil: dealloc o.p
echo "myobj destroyed"
if o.p != nil:
dealloc o.p
o.p = nil
echo "myobj destroyed"

type
TMyGeneric1[T] = object
Expand Down