make sink operator optional (#13068)

* make sink operator optional

* bug fix, add changelog entry

* Trigger build

* fix one regression

* fix test

* Trigger build

* fix typos
This commit is contained in:
cooldome 2020-01-17 11:44:06 +00:00 • committed by GitHub
commit f51613e262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 62 deletions

View file

@ -75,6 +75,10 @@
- An `align` pragma can now be used for variables and object fields, similar - An `align` pragma can now be used for variables and object fields, similar
to the `alignas` declaration modifier in C/C++. 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 ## Language changes
- Unsigned integer operators have been fixed to allow promotion of the first operand. - Unsigned integer operators have been fixed to allow promotion of the first operand.

View file

@ -230,6 +230,10 @@ proc genOp(c: Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
addrExp.add(dest) addrExp.add(dest)
result = newTree(nkCall, newSymNode(op), addrExp) 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: when false:
proc preventMoveRef(dest, ri: PNode): bool = proc preventMoveRef(dest, ri: PNode): bool =
let lhs = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink}) let lhs = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
@ -248,18 +252,17 @@ proc canBeMoved(c: Con; t: PType): bool {.inline.} =
proc genSink(c: var Con; dest, ri: PNode): PNode = proc genSink(c: var Con; dest, ri: PNode): PNode =
if isFirstWrite(dest, c): # optimize sink call into a bitwise memcopy if isFirstWrite(dest, c): # optimize sink call into a bitwise memcopy
result = newTree(nkFastAsgn, dest) result = newTree(nkFastAsgn, dest, ri)
else: else:
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink}) let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
let k = if t.attachedOps[attachedSink] != nil: attachedSink if t.attachedOps[attachedSink] != nil:
else: attachedAsgn result = genOp(c, t, attachedSink, dest, ri)
if t.attachedOps[k] != nil: result.add ri
result = genOp(c, t, k, dest, ri)
else: else:
# in rare cases only =destroy exists but no sink or assignment # the default is to use combination of `=destroy(dest)` and
# (see Pony object in tmove_objconstr.nim) # and copyMem(dest, source). This is efficient.
# we generate a fast assignment in this case: let snk = newTree(nkFastAsgn, dest, ri)
result = newTree(nkFastAsgn, dest) result = newTree(nkStmtList, genDestroy(c, dest), snk)
proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode = proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink}) let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
@ -273,10 +276,6 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode =
checkForErrorPragma(c, t, ri, "=") checkForErrorPragma(c, t, ri, "=")
result = genCopyNoCheck(c, dest, 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) = proc addTopVar(c: var Con; v: PNode) =
c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
@ -298,8 +297,6 @@ proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
proc destructiveMoveVar(n: PNode; c: var Con): PNode = proc destructiveMoveVar(n: PNode; c: var Con): PNode =
# generate: (let tmp = v; reset(v); tmp) # 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): if not hasDestructor(n.typ):
result = copyTree(n) result = copyTree(n)
else: else:
@ -440,9 +437,7 @@ proc ensureDestruction(arg: PNode; c: var Con): PNode =
# This was already done in the sink parameter handling logic. # This was already done in the sink parameter handling logic.
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ) result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
let tmp = getTemp(c, arg.typ, arg.info) let tmp = getTemp(c, arg.typ, arg.info)
var sinkExpr = genSink(c, tmp, arg) result.add genSink(c, tmp, arg)
sinkExpr.add arg
result.add sinkExpr
result.add tmp result.add tmp
c.destroys.add genDestroy(c, tmp) c.destroys.add genDestroy(c, tmp)
else: else:
@ -598,8 +593,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
if ri.kind == nkEmpty and c.inLoop > 0: if ri.kind == nkEmpty and c.inLoop > 0:
ri = genDefaultCall(v.typ, c, v.info) ri = genDefaultCall(v.typ, c, v.info)
if ri.kind != nkEmpty: if ri.kind != nkEmpty:
let r = moveOrCopy(v, ri, c) result.add moveOrCopy(v, ri, c)
result.add r
else: # keep the var but transform 'ri': else: # keep the var but transform 'ri':
var v = copyNode(n) var v = copyNode(n)
var itCopy = copyNode(it) var itCopy = copyNode(it)
@ -667,8 +661,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
if isUnpackedTuple(dest): if isUnpackedTuple(dest):
result = newTree(nkFastAsgn, dest, p(ri, c, consumed)) result = newTree(nkFastAsgn, dest, p(ri, c, consumed))
else: else:
result = genSink(c, dest, ri) result = genSink(c, dest, p(ri, c, consumed))
result.add p(ri, c, consumed)
of nkBracketExpr: of nkBracketExpr:
if isUnpackedTuple(ri[0]): if isUnpackedTuple(ri[0]):
# unpacking of tuple: take over elements # unpacking of tuple: take over elements
@ -677,7 +670,6 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
not aliases(dest, ri): not aliases(dest, ri):
# Rule 3: `=sink`(x, z); wasMoved(z) # Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri) var snk = genSink(c, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genWasMoved(ri, c)) result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else: else:
result = genCopy(c, dest, ri) result = genCopy(c, dest, ri)
@ -686,24 +678,21 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
# array constructor # array constructor
if ri.len > 0 and isDangerousSeq(ri.typ): if ri.len > 0 and isDangerousSeq(ri.typ):
result = genCopy(c, dest, ri) result = genCopy(c, dest, ri)
result.add p(ri, c, consumed)
else: else:
result = genSink(c, dest, ri) result = genSink(c, dest, p(ri, c, consumed))
result.add p(ri, c, consumed)
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
result = genSink(c, dest, ri) result = genSink(c, dest, p(ri, c, consumed))
result.add p(ri, c, consumed)
of nkSym: of nkSym:
if isSinkParam(ri.sym): if isSinkParam(ri.sym):
# Rule 3: `=sink`(x, z); wasMoved(z) # Rule 3: `=sink`(x, z); wasMoved(z)
sinkParamIsLastReadCheck(c, ri) sinkParamIsLastReadCheck(c, ri)
var snk = genSink(c, dest, ri) let snk = genSink(c, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genWasMoved(ri, c)) result = newTree(nkStmtList, snk, genWasMoved(ri, c))
elif ri.sym.kind != skParam and ri.sym.owner == c.owner and elif ri.sym.kind != skParam and ri.sym.owner == c.owner and
isLastRead(ri, c) and canBeMoved(c, dest.typ): isLastRead(ri, c) and canBeMoved(c, dest.typ):
# Rule 3: `=sink`(x, z); wasMoved(z) # Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri) let snk = genSink(c, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genWasMoved(ri, c)) result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else: else:
result = genCopy(c, dest, ri) result = genCopy(c, dest, ri)
@ -716,8 +705,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
copyRi[1] = result[^1] copyRi[1] = result[^1]
result[^1] = copyRi result[^1] = copyRi
else: else:
result = genSink(c, dest, ri) result = genSink(c, dest, p(ri, c, sinkArg))
result.add p(ri, c, sinkArg)
of nkObjDownConv, nkObjUpConv: of nkObjDownConv, nkObjUpConv:
when false: when false:
result = moveOrCopy(dest, ri[0], c) result = moveOrCopy(dest, ri[0], c)
@ -725,16 +713,14 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
copyRi[0] = result[^1] copyRi[0] = result[^1]
result[^1] = copyRi result[^1] = copyRi
else: else:
result = genSink(c, dest, ri) result = genSink(c, dest, p(ri, c, sinkArg))
result.add p(ri, c, sinkArg)
of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt: of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt:
handleNested(ri): moveOrCopy(dest, node, c) handleNested(ri): moveOrCopy(dest, node, c)
else: else:
if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and
canBeMoved(c, dest.typ): canBeMoved(c, dest.typ):
# Rule 3: `=sink`(x, z); wasMoved(z) # Rule 3: `=sink`(x, z); wasMoved(z)
var snk = genSink(c, dest, ri) let snk = genSink(c, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genWasMoved(ri, c)) result = newTree(nkStmtList, snk, genWasMoved(ri, c))
else: else:
result = genCopy(c, dest, ri) result = genCopy(c, dest, ri)

View file

@ -775,18 +775,26 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
# register this operation already: # register this operation already:
typ.attachedOps[kind] = result typ.attachedOps[kind] = result
var tk: TTypeKind
if g.config.selectedGC in {gcArc, gcOrc, gcHooks}: if kind == attachedSink and typ.attachedOps[attachedDestructor] != nil and
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind 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: else:
tk = tyNone # no special casing for strings and seqs var tk: TTypeKind
case tk if g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
of tySequence: tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
fillSeqOp(a, typ, body, d, newSymNode(src)) else:
of tyString: tk = tyNone # no special casing for strings and seqs
fillStrOp(a, typ, body, d, newSymNode(src)) case tk
else: of tySequence:
fillBody(a, typ, body, d, newSymNode(src)) 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) var n = newNodeI(nkProcDef, info, bodyPos+1)
for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info) for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)

View file

@ -53,7 +53,8 @@ written as:
a.data[i] = b.data[i] a.data[i] = b.data[i]
proc `=sink`*[T](a: var myseq[T]; b: myseq[T]) = 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) `=destroy`(a)
a.len = b.len a.len = b.len
a.cap = b.cap a.cap = b.cap
@ -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 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 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 (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: The prototype of this hook for a type ``T`` needs to be:

View file

@ -20,12 +20,6 @@ proc `=`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) {.inline.} =
discard atomicInc(src.val[].atomicCounter) discard atomicInc(src.val[].atomicCounter)
dest.val = src.val 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] = proc newSharedPtr*[T](val: sink T): SharedPtr[T] =
result.val = cast[type(result.val)](allocShared0(sizeof(result.val[]))) result.val = cast[type(result.val)](allocShared0(sizeof(result.val[])))
result.val.atomicCounter = 1 result.val.atomicCounter = 1

View file

@ -20,14 +20,10 @@ mygeneric2 destroyed
---- ----
---- ----
myobj destroyed myobj destroyed
myobj destroyed
myobj destroyed
myobj destroyed
mygeneric1 destroyed mygeneric1 destroyed
--- ---
myobj destroyed myobj destroyed
myobj destroyed myobj destroyed
myobj destroyed
''' '''
""" """
@ -37,8 +33,10 @@ type
p: pointer p: pointer
proc `=destroy`(o: var TMyObj) = proc `=destroy`(o: var TMyObj) =
if o.p != nil: dealloc o.p if o.p != nil:
echo "myobj destroyed" dealloc o.p
o.p = nil
echo "myobj destroyed"
type type
TMyGeneric1[T] = object TMyGeneric1[T] = object