renamed '=' to '=copy' [backport:1.2] (#15585)

* Assign hook name changed to `=copy`
* Adapt destructors.rst
* [nobackport] Duplicate tests for =copy hook
* Fix tests
* added a changelog entry

Co-authored-by: Clyybber <darkmine956@gmail.com>
This commit is contained in:
Andreas Rumpf 2020-10-15 12:52:30 +02:00 • committed by GitHub
commit da4aa2e1fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1001 additions and 29 deletions

View file

@ -215,6 +215,10 @@
- The `=destroy` hook no longer has to reset its target, as the compiler now automatically inserts - The `=destroy` hook no longer has to reset its target, as the compiler now automatically inserts
`wasMoved` calls where needed. `wasMoved` calls where needed.
- The `=` hook is now called `=copy` for clarity. The old name `=` is still available so there
is no need to update your code. This change was backported to 1.2 too so you can use the
more readability `=copy` without loss of compatibility.
- In the newruntime it is now allowed to assign to the discriminator field - In the newruntime it is now allowed to assign to the discriminator field
without restrictions as long as case object doesn't have custom destructor. without restrictions as long as case object doesn't have custom destructor.
The discriminator value doesn't have to be a constant either. If you have a The discriminator value doesn't have to be a constant either. If you have a

View file

@ -1331,7 +1331,7 @@ const
MaxLockLevel* = 1000'i16 MaxLockLevel* = 1000'i16
UnknownLockLevel* = TLockLevel(1001'i16) UnknownLockLevel* = TLockLevel(1001'i16)
AttachedOpToStr*: array[TTypeAttachedOp, string] = [ AttachedOpToStr*: array[TTypeAttachedOp, string] = [
"=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"] "=destroy", "=copy", "=sink", "=trace", "=dispose", "=deepcopy"]
proc `$`*(x: TLockLevel): string = proc `$`*(x: TLockLevel): string =
if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>" if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"

View file

@ -235,7 +235,7 @@ template isUnpackedTuple(n: PNode): bool =
proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) = proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">" var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
if opname == "=" and ri != nil: if (opname == "=" or opname == "=copy") and ri != nil:
m.add "; requires a copy because it's not the last read of '" m.add "; requires a copy because it's not the last read of '"
m.add renderTree(ri) m.add renderTree(ri)
m.add '\'' m.add '\''
@ -319,7 +319,7 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode =
if tfHasOwned in t.flags and ri.kind != nkNilLit: if tfHasOwned in t.flags and ri.kind != nkNilLit:
# try to improve the error message here: # try to improve the error message here:
if c.otherRead == nil: discard isLastRead(ri, c) if c.otherRead == nil: discard isLastRead(ri, c)
c.checkForErrorPragma(t, ri, "=") c.checkForErrorPragma(t, ri, "=copy")
result = c.genCopyNoCheck(dest, ri) result = c.genCopyNoCheck(dest, ri)
proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode = proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =

View file

@ -809,7 +809,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and
tracked.owner.kind != skMacro: tracked.owner.kind != skMacro:
let opKind = find(AttachedOpToStr, a.sym.name.s.normalize) var opKind = find(AttachedOpToStr, a.sym.name.s.normalize)
if a.sym.name.s.normalize == "=": opKind = attachedAsgn.int
if opKind != -1: if opKind != -1:
# rebind type bounds operations after createTypeBoundOps call # rebind type bounds operations after createTypeBoundOps call
let t = n[1].typ.skipTypes({tyAlias, tyVar}) let t = n[1].typ.skipTypes({tyAlias, tyVar})

View file

@ -1732,7 +1732,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
"signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
incl(s.flags, sfUsed) incl(s.flags, sfUsed)
incl(s.flags, sfOverriden) incl(s.flags, sfOverriden)
of "=", "=sink": of "=", "=copy", "=sink":
if s.magic == mAsgn: return if s.magic == mAsgn: return
incl(s.flags, sfUsed) incl(s.flags, sfUsed)
incl(s.flags, sfOverriden) incl(s.flags, sfOverriden)
@ -1754,7 +1754,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
# attach these ops to the canonical tySequence # attach these ops to the canonical tySequence
obj = canonType(c, obj) obj = canonType(c, obj)
#echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj) #echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj)
let k = if name == "=": attachedAsgn else: attachedSink let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
if obj.attachedOps[k] == s: if obj.attachedOps[k] == s:
discard "forward declared op" discard "forward declared op"
elif obj.attachedOps[k].isNil and tfCheckedForDestructor notin obj.flags: elif obj.attachedOps[k].isNil and tfCheckedForDestructor notin obj.flags:

View file

@ -40,7 +40,7 @@ written as:
for i in 0..<x.len: `=destroy`(x[i]) for i in 0..<x.len: `=destroy`(x[i])
dealloc(x.data) dealloc(x.data)
proc `=`*[T](a: var myseq[T]; b: myseq[T]) = proc `=copy`*[T](a: var myseq[T]; b: myseq[T]) =
# do nothing for self-assignments: # do nothing for self-assignments:
if a.data == b.data: return if a.data == b.data: return
`=destroy`(a) `=destroy`(a)
@ -134,7 +134,7 @@ not free the resources afterwards by setting the object to its default value
default value is written as ``wasMoved(x)``. When not provided the compiler default value is written as ``wasMoved(x)``. When not provided the compiler
is using a combination of `=destroy` and `copyMem` instead. This is efficient 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 hence users rarely need to implement their own `=sink` operator, it is enough to
provide `=destroy` and `=`, compiler will take care about the rest. provide `=destroy` and `=copy`, 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:
@ -157,10 +157,10 @@ The general pattern in ``=sink`` looks like:
How self-assignments are handled is explained later in this document. How self-assignments are handled is explained later in this document.
`=` (copy) hook `=copy` hook
--------------- ---------------
The ordinary assignment in Nim conceptually copies the values. The ``=`` hook The ordinary assignment in Nim conceptually copies the values. The ``=copy`` hook
is called for assignments that couldn't be transformed into ``=sink`` is called for assignments that couldn't be transformed into ``=sink``
operations. operations.
@ -168,14 +168,14 @@ The prototype of this hook for a type ``T`` needs to be:
.. code-block:: nim .. code-block:: nim
proc `=`(dest: var T; source: T) proc `=copy`(dest: var T; source: T)
The general pattern in ``=`` looks like: The general pattern in ``=copy`` looks like:
.. code-block:: nim .. code-block:: nim
proc `=`(dest: var T; source: T) = proc `=copy`(dest: var T; source: T) =
# protect against self-assignments: # protect against self-assignments:
if dest.field != source.field: if dest.field != source.field:
`=destroy`(dest) `=destroy`(dest)
@ -183,7 +183,7 @@ The general pattern in ``=`` looks like:
dest.field = duplicateResource(source.field) dest.field = duplicateResource(source.field)
The ``=`` proc can be marked with the ``{.error.}`` pragma. Then any assignment The ``=copy`` proc can be marked with the ``{.error.}`` pragma. Then any assignment
that otherwise would lead to a copy is prevented at compile-time. that otherwise would lead to a copy is prevented at compile-time.
@ -201,7 +201,7 @@ Swap
==== ====
The need to check for self-assignments and also the need to destroy previous The need to check for self-assignments and also the need to destroy previous
objects inside ``=`` and ``=sink`` is a strong indicator to treat objects inside ``=copy`` and ``=sink`` is a strong indicator to treat
``system.swap`` as a builtin primitive of its own that simply swaps every ``system.swap`` as a builtin primitive of its own that simply swaps every
field in the involved objects via ``copyMem`` or a comparable mechanism. field in the involved objects via ``copyMem`` or a comparable mechanism.
In other words, ``swap(a, b)`` is **not** implemented In other words, ``swap(a, b)`` is **not** implemented
@ -326,7 +326,7 @@ destroyed at the scope exit.
x = y x = y
------------------ (copy) ------------------ (copy)
`=`(x, y) `=copy`(x, y)
f_sink(g()) f_sink(g())
@ -336,7 +336,7 @@ destroyed at the scope exit.
f_sink(notLastReadOf y) f_sink(notLastReadOf y)
-------------------------- (copy-to-sink) -------------------------- (copy-to-sink)
(let tmp; `=`(tmp, y); (let tmp; `=copy`(tmp, y);
f_sink(tmp)) f_sink(tmp))

246
tests/arc/tcaseobjcopy.nim Normal file
View file

@ -0,0 +1,246 @@
discard """
valgrind: true
cmd: "nim c --gc:arc -d:useMalloc $file"
output: '''myobj destroyed
myobj destroyed
myobj destroyed
A
B
begin
end
prevented
(ok: true, value: "ok")
myobj destroyed
'''
"""
# bug #13102
type
D = ref object
R = object
case o: bool
of false:
discard
of true:
field: D
iterator things(): R =
when true:
var
unit = D()
while true:
yield R(o: true, field: unit)
else:
while true:
var
unit = D()
yield R(o: true, field: unit)
proc main =
var i = 0
for item in things():
discard item.field
inc i
if i == 2: break
main()
# bug #13149
type
TMyObj = object
p: pointer
len: int
proc `=destroy`(o: var TMyObj) =
if o.p != nil:
dealloc o.p
o.p = nil
echo "myobj destroyed"
proc `=copy`(dst: var TMyObj, src: TMyObj) =
`=destroy`(dst)
dst.p = alloc(src.len)
dst.len = src.len
proc `=sink`(dst: var TMyObj, src: TMyObj) =
`=destroy`(dst)
dst.p = src.p
dst.len = src.len
type
TObjKind = enum Z, A, B
TCaseObj = object
case kind: TObjKind
of Z: discard
of A:
x1: int # this int plays important role
x2: TMyObj
of B:
y: TMyObj
proc testSinks: TCaseObj =
result = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
result = TCaseObj(kind: B, y: TMyObj(len: 3, p: alloc(3)))
proc use(x: TCaseObj) = discard
proc testCopies(i: int) =
var a: array[2, TCaseObj]
a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
a[i+1] = a[i] # copy, cannot move
use(a[i])
let x1 = testSinks()
testCopies(0)
# bug #12957
type
PegKind* = enum
pkCharChoice,
pkSequence
Peg* = object ## type that represents a PEG
case kind: PegKind
of pkCharChoice: charChoice: ref set[char]
else: discard
sons: seq[Peg]
proc charSet*(s: set[char]): Peg =
## constructs a PEG from a character set `s`
result = Peg(kind: pkCharChoice)
new(result.charChoice)
result.charChoice[] = s
proc len(a: Peg): int {.inline.} = return a.sons.len
proc myadd(d: var Peg, s: Peg) {.inline.} = add(d.sons, s)
proc sequence*(a: openArray[Peg]): Peg =
result = Peg(kind: pkSequence, sons: @[])
when false:
#works too:
result.myadd(a[0])
result.myadd(a[1])
for x in items(a):
# works:
#result.sons.add(x)
# fails:
result.myadd x
if result.len == 1:
result = result.sons[0] # this must not move!
when true:
# bug #12957
proc p =
echo "A"
let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'}),
charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'})])
echo "B"
p()
proc testSubObjAssignment =
echo "begin"
# There must be extactly one element in the array constructor!
let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'})])
echo "end"
testSubObjAssignment()
#------------------------------------------------
type
MyObject = object
x1: string
case kind1: bool
of false: y1: string
of true:
y2: seq[string]
case kind2: bool
of true: z1: string
of false:
z2: seq[string]
flag: bool
x2: string
proc test_myobject =
var x: MyObject
x.x1 = "x1"
x.x2 = "x2"
x.y1 = "ljhkjhkjh"
x.kind1 = true
x.y2 = @["1", "2"]
x.kind2 = true
x.z1 = "yes"
x.kind2 = false
x.z2 = @["1", "2"]
x.kind2 = true
x.z1 = "yes"
x.kind2 = true # should be no effect
doAssert(x.z1 == "yes")
x.kind2 = false
x.kind1 = x.kind2 # support self assignment with effect
try:
x.kind1 = x.flag # flag is not accesible
except FieldDefect:
echo "prevented"
doAssert(x.x1 == "x1")
doAssert(x.x2 == "x2")
test_myobject()
#------------------------------------------------
# bug #14244
type
RocksDBResult*[T] = object
case ok*: bool
of true:
value*: T
else:
error*: string
proc init(): RocksDBResult[string] =
result.ok = true
result.value = "ok"
echo init()
#------------------------------------------------
# bug #14312
type MyObj = object
case kind: bool
of false: x0: int # would work with a type like seq[int]; value would be reset
of true: x1: string
var a = MyObj(kind: false, x0: 1234)
a.kind = true
doAssert(a.x1 == "")
block:
# bug #15532
type Kind = enum
k0, k1
type Foo = object
y: int
case kind: Kind
of k0: x0: int
of k1: x1: int
const j0 = Foo(y: 1, kind: k0, x0: 2)
const j1 = Foo(y: 1, kind: k1, x1: 2)
doAssert j0.y == 1
doAssert j0.kind == k0
doAssert j1.kind == k1
doAssert j1.x1 == 2
doAssert j0.x0 == 2

View file

@ -0,0 +1,41 @@
discard """
cmd: '''nim c --newruntime $file'''
output: '''2
2'''
"""
type
ObjWithDestructor = object
a: int
proc `=destroy`(self: var ObjWithDestructor) =
echo "destroyed"
proc `=copy`(self: var ObjWithDestructor, other: ObjWithDestructor) =
echo "copied"
proc test(a: range[0..1], arg: ObjWithDestructor) =
var iteration = 0
while true:
{.computedGoto.}
let
b = int(a) * 2
c = a
d = arg
e = arg
discard c
discard d
discard e
inc iteration
case a
of 0:
assert false
of 1:
echo b
if iteration == 2:
break
test(1, ObjWithDestructor())

526
tests/arc/tmovebugcopy.nim Normal file
View file

@ -0,0 +1,526 @@
discard """
cmd: "nim c --gc:arc $file"
output: '''5
(w: 5)
(w: -5)
c.text = hello
c.text = hello
p.text = hello
p.toks = @["hello"]
c.text = hello
c[].text = hello
pA.text = hello
pA.toks = @["hello"]
c.text = hello
c.text = hello
pD.text = hello
pD.toks = @["hello"]
c.text = hello
c.text = hello
pOD.text = hello
pOD.toks = @["hello"]
fff
fff
2
fff
fff
2
fff
fff
2
mmm
fff
fff
fff
3
mmm
sink me (sink)
assign me (not sink)
sink me (not sink)
sinked and not optimized to a bitcopy
sinked and not optimized to a bitcopy
sinked and not optimized to a bitcopy
(data: @[0, 0])
(data: @[0, 0])
(data: @[0, 0])
(data: @[0, 0])
(data: @[0, 0])
(data: @[0, 0])
(data: @[0, 0])
100
hey
hey
(a: "a", b: 2)
ho
(a: "b", b: 3)
(b: "b", a: 2)
ho
(b: "a", a: 3)
hey
break
break
hey
ho
hey
ho
ho
king
live long; long live
king
hi
try
bye
'''
"""
# move bug
type
TMyObj = object
p: pointer
len: int
var destroyCounter = 0
proc `=destroy`(o: var TMyObj) =
if o.p != nil:
dealloc o.p
o.p = nil
inc destroyCounter
proc `=copy`(dst: var TMyObj, src: TMyObj) =
`=destroy`(dst)
dst.p = alloc(src.len)
dst.len = src.len
proc `=sink`(dst: var TMyObj, src: TMyObj) =
`=destroy`(dst)
dst.p = src.p
dst.len = src.len
type
TObjKind = enum Z, A, B
TCaseObj = object
case kind: TObjKind
of Z: discard
of A:
x1: int # this int plays important role
x2: TMyObj
of B:
y: TMyObj
proc use(a: TCaseObj) = discard
proc moveBug(i: var int) =
var a: array[2, TCaseObj]
a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 1
a[i+1] = a[i] # 2
inc i
use(a[i-1])
var x = 0
moveBug(x)
proc moveBug2(): (TCaseObj, TCaseObj) =
var a: array[2, TCaseObj]
a[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
a[1] = a[0] # can move 3
result[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 4
result[1] = result[0] # 5
proc main =
discard moveBug2()
main()
echo destroyCounter
# bug #13314
type
O = object
v: int
R = ref object
w: int
proc `$`(r: R): string = $r[]
proc tbug13314 =
var t5 = R(w: 5)
var execute = proc () =
echo t5
execute()
t5.w = -5
execute()
tbug13314()
#-------------------------------------------------------------------------
# bug #13368
import strutils
proc procStat() =
for line in @["a b", "c d", "e f"]:
let cols = line.splitWhitespace(maxSplit=1)
let x = cols[0]
let (nm, rest) = (cols[0], cols[1])
procStat()
# bug #14269
import sugar, strutils
type
Cursor = object
text: string
Parsed = object
text: string
toks: seq[string]
proc tokenize(c: var Cursor): seq[string] =
dump c.text
return c.text.splitWhitespace()
proc parse(): Parsed =
var c = Cursor(text: "hello")
dump c.text
return Parsed(text: c.text, toks: c.tokenize) # note: c.tokenized uses c.text
let p = parse()
dump p.text
dump p.toks
proc tokenizeA(c: ptr Cursor): seq[string] =
dump c[].text
return c[].text.splitWhitespace()
proc parseA(): Parsed =
var c = Cursor(text: "hello")
dump c.text
return Parsed(text: c.text, toks: c.addr.tokenizeA) # note: c.tokenized uses c.text
let pA = parseA()
dump pA.text
dump pA.toks
proc tokenizeD(c: Cursor): seq[string] =
dump c.text
return c.text.splitWhitespace()
proc parseD(): Parsed =
var c = cast[ptr Cursor](alloc0(sizeof(Cursor)))
c[] = Cursor(text: "hello")
dump c.text
return Parsed(text: c.text, toks: c[].tokenizeD) # note: c.tokenized uses c.text
let pD = parseD()
dump pD.text
dump pD.toks
# Bug would only pop up with owned refs
proc tokenizeOD(c: Cursor): seq[string] =
dump c.text
return c.text.splitWhitespace()
proc parseOD(): Parsed =
var c = new Cursor
c[] = Cursor(text: "hello")
dump c.text
return Parsed(text: c.text, toks: c[].tokenizeOD) # note: c.tokenized uses c.text
let pOD = parseOD()
dump pOD.text
dump pOD.toks
when false:
# Bug would only pop up with owned refs and implicit derefs, but since they don't work together..
{.experimental: "implicitDeref".}
proc tokenizeOHD(c: Cursor): seq[string] =
dump c.text
return c.text.splitWhitespace()
proc parseOHD(): Parsed =
var c = new Cursor
c[] = Cursor(text: "hello")
dump c.text
return Parsed(text: c.text, toks: c.tokenizeOHD) # note: c.tokenized uses c.text
let pOHD = parseOHD()
dump pOHD.text
dump pOHD.toks
# bug #13456
iterator combinations[T](s: openarray[T], k: int): seq[T] =
let n = len(s)
assert k >= 0 and k <= n
var pos = newSeq[int](k)
var current = newSeq[T](k)
for i in 0..k-1:
pos[k-i-1] = i
var done = false
while not done:
for i in 0..k-1:
current[i] = s[pos[k-i-1]]
yield current
var i = 0
while i < k:
pos[i] += 1
if pos[i] < n-i:
for j in 0..i-1:
pos[j] = pos[i] + i - j
break
i += 1
if i >= k:
break
type
UndefEx = object of ValueError
proc main2 =
var delayedSyms = @[1, 2, 3]
var unp: seq[int]
block myb:
for a in 1 .. 2:
if delayedSyms.len > a:
unp = delayedSyms
for t in unp.combinations(a + 1):
try:
var h = false
for k in t:
echo "fff"
if h: continue
if true:
raise newException(UndefEx, "forward declaration")
break myb
except UndefEx:
echo t.len
echo "mmm"
main2()
type ME = object
who: string
proc `=copy`(x: var ME, y: ME) =
if y.who.len > 0: echo "assign ",y.who
proc `=sink`(x: var ME, y: ME) =
if y.who.len > 0: echo "sink ",y.who
var dump: ME
template use(x) = dump = x
template def(x) = x = dump
var c = true
proc shouldSink() =
var x = ME(who: "me (sink)")
use(x) # we analyse this
if c: def(x)
else: def(x)
use(x) # ok, with the [else] part.
shouldSink()
dump = ME()
proc shouldNotSink() =
var x = ME(who: "me (not sink)")
use(x) # we analyse this
if c: def(x)
use(x) # Not ok without the '[else]'
shouldNotSink()
# bug #14568
import os
type O2 = object
s: seq[int]
proc `=sink`(dest: var O2, src: O2) =
echo "sinked and not optimized to a bitcopy"
var testSeq: O2
proc update() =
# testSeq.add(0) # uncommenting this line fixes the leak
testSeq = O2(s: @[])
testSeq.s.add(0)
for i in 1..3:
update()
# bug #14961
type
Foo = object
data: seq[int]
proc initFoo(len: int): Foo =
result = (let s = newSeq[int](len); Foo(data: s) )
var f = initFoo(2)
echo initFoo(2)
proc initFoo2(len: int) =
echo if true:
let s = newSeq[int](len); Foo(data: s)
else:
let s = newSeq[int](len); Foo(data: s)
initFoo2(2)
proc initFoo3(len: int) =
echo (block:
let s = newSeq[int](len); Foo(data: s))
initFoo3(2)
proc initFoo4(len: int) =
echo (let s = newSeq[int](len); Foo(data: s))
initFoo4(2)
proc initFoo5(len: int) =
echo (case true
of true:
let s = newSeq[int](len); Foo(data: s)
of false:
let s = newSeq[int](len); Foo(data: s))
initFoo5(2)
proc initFoo6(len: int) =
echo (block:
try:
let s = newSeq[int](len); Foo(data: s)
finally: discard)
initFoo6(2)
proc initFoo7(len: int) =
echo (block:
try:
raise newException(CatchableError, "sup")
let s = newSeq[int](len); Foo(data: s)
except CatchableError:
let s = newSeq[int](len); Foo(data: s) )
initFoo7(2)
# bug #14902
iterator zip[T](s: openarray[T]): (T, T) =
var i = 0
while i < 10:
yield (s[i mod 2], s[i mod 2 + 1])
inc i
var lastMem = int.high
proc leak =
const len = 10
var x = @[newString(len), newString(len), newString(len)]
var c = 0
for (a, b) in zip(x):
let newMem = getOccupiedMem()
assert newMem <= lastMem
lastMem = newMem
c += a.len
echo c
leak()
proc consume(a: sink string) = echo a
proc weirdScopes =
if (let a = "hey"; a.len > 0):
echo a
while (let a = "hey"; a.len > 0):
echo a
break
var a = block: (a: "a", b: 2)
echo a
(discard; a) = (echo "ho"; (a: "b", b: 3))
echo a
var b = try: (b: "b", a: 2)
except: raise
echo b
(discard; b) = (echo "ho"; (b: "a", a: 3))
echo b
var s = "break"
consume((echo "hey"; s))
echo s
echo (block:
var a = "hey"
(echo "hey"; "ho"))
var b2 = "ho"
echo (block:
var a = "hey"
(echo "hey"; b2))
echo b2
type status = enum
alive
var king = "king"
echo (block:
var a = "a"
when true:
var b = "b"
case alive
of alive:
try:
var c = "c"
if true:
king
else:
"the abyss"
except:
echo "he ded"
"dead king")
echo "live long; long live"
echo king
weirdScopes()
# bug #14985
proc getScope(): string =
if true:
"hi"
else:
"else"
echo getScope()
proc getScope3(): string =
try:
"try"
except:
"except"
echo getScope3()
proc getScope2(): string =
case true
of true:
"bye"
else:
"else"
echo getScope2()

View file

@ -33,9 +33,9 @@ result = (
var var
sibling sibling
saved saved
`=`(sibling, target.parent.left) `=copy`(sibling, target.parent.left)
`=`(saved, sibling.right) `=copy`(saved, sibling.right)
`=`(sibling.right, saved.left) `=copy`(sibling.right, saved.left)
`=sink`(sibling.parent, saved) `=sink`(sibling.parent, saved)
`=destroy`(sibling) `=destroy`(sibling)
-- end of expandArc ------------------------ -- end of expandArc ------------------------
@ -46,7 +46,7 @@ var
lvalue lvalue
lnext lnext
_ _
`=`(lresult, [123]) `=copy`(lresult, [123])
_ = ( _ = (
let blitTmp = lresult let blitTmp = lresult
blitTmp, ";") blitTmp, ";")
@ -67,10 +67,10 @@ try:
var it_cursor = x var it_cursor = x
a = ( a = (
wasMoved(:tmpD) wasMoved(:tmpD)
`=`(:tmpD, it_cursor.key) `=copy`(:tmpD, it_cursor.key)
:tmpD, :tmpD,
wasMoved(:tmpD_1) wasMoved(:tmpD_1)
`=`(:tmpD_1, it_cursor.val) `=copy`(:tmpD_1, it_cursor.val)
:tmpD_1) :tmpD_1)
echo [ echo [
:tmpD_2 = `$`(a) :tmpD_2 = `$`(a)

View file

@ -38,7 +38,7 @@ try:
return return
add(a): add(a):
wasMoved(:tmpD) wasMoved(:tmpD)
`=`(:tmpD, x) `=copy`(:tmpD, x)
:tmpD :tmpD
inc i_1, 1 inc i_1, 1
if cond: if cond:

154
tests/arc/tweavecopy.nim Normal file
View file

@ -0,0 +1,154 @@
discard """
outputsub: '''Success'''
cmd: '''nim c --gc:arc --threads:on $file'''
disabled: "bsd"
"""
# bug #13936
import std/atomics
const MemBlockSize = 256
type
ChannelSPSCSingle* = object
full{.align: 128.}: Atomic[bool]
itemSize*: uint8
buffer*{.align: 8.}: UncheckedArray[byte]
proc `=copy`(
dest: var ChannelSPSCSingle,
source: ChannelSPSCSingle
) {.error: "A channel cannot be copied".}
proc initialize*(chan: var ChannelSPSCSingle, itemsize: SomeInteger) {.inline.} =
## If ChannelSPSCSingle is used intrusive another data structure
## be aware that it should be the last part due to ending by UncheckedArray
## Also due to 128 bytes padding, it automatically takes half
## of the default MemBlockSize
assert itemsize.int in 0 .. int high(uint8)
assert itemSize.int +
sizeof(chan.itemsize) +
sizeof(chan.full) < MemBlockSize
chan.itemSize = uint8 itemsize
chan.full.store(false, moRelaxed)
func isEmpty*(chan: var ChannelSPSCSingle): bool {.inline.} =
not chan.full.load(moAcquire)
func tryRecv*[T](chan: var ChannelSPSCSingle, dst: var T): bool {.inline.} =
## Try receiving the item buffered in the channel
## Returns true if successful (channel was not empty)
##
## ⚠ Use only in the consumer thread that reads from the channel.
assert (sizeof(T) == chan.itemsize.int) or
# Support dummy object
(sizeof(T) == 0 and chan.itemsize == 1)
let full = chan.full.load(moAcquire)
if not full:
return false
dst = cast[ptr T](chan.buffer.addr)[]
chan.full.store(false, moRelease)
return true
func trySend*[T](chan: var ChannelSPSCSingle, src: sink T): bool {.inline.} =
## Try sending an item into the channel
## Reurns true if successful (channel was empty)
##
## ⚠ Use only in the producer thread that writes from the channel.
assert (sizeof(T) == chan.itemsize.int) or
# Support dummy object
(sizeof(T) == 0 and chan.itemsize == 1)
let full = chan.full.load(moAcquire)
if full:
return false
cast[ptr T](chan.buffer.addr)[] = src
chan.full.store(true, moRelease)
return true
# Sanity checks
# ------------------------------------------------------------------------------
when isMainModule:
when not compileOption("threads"):
{.error: "This requires --threads:on compilation flag".}
template sendLoop[T](chan: var ChannelSPSCSingle,
data: sink T,
body: untyped): untyped =
while not chan.trySend(data):
body
template recvLoop[T](chan: var ChannelSPSCSingle,
data: var T,
body: untyped): untyped =
while not chan.tryRecv(data):
body
type
ThreadArgs = object
ID: WorkerKind
chan: ptr ChannelSPSCSingle
WorkerKind = enum
Sender
Receiver
template Worker(id: WorkerKind, body: untyped): untyped {.dirty.} =
if args.ID == id:
body
proc thread_func(args: ThreadArgs) =
# Worker RECEIVER:
# ---------
# <- chan
# <- chan
# <- chan
#
# Worker SENDER:
# ---------
# chan <- 42
# chan <- 53
# chan <- 64
Worker(Receiver):
var val: int
for j in 0 ..< 10:
args.chan[].recvLoop(val):
# Busy loop, in prod we might want to yield the core/thread timeslice
discard
echo " Receiver got: ", val
doAssert val == 42 + j*11
Worker(Sender):
doAssert args.chan.full.load(moRelaxed) == false
for j in 0 ..< 10:
let val = 42 + j*11
args.chan[].sendLoop(val):
# Busy loop, in prod we might want to yield the core/thread timeslice
discard
echo "Sender sent: ", val
proc main() =
echo "Testing if 2 threads can send data"
echo "-----------------------------------"
var threads: array[2, Thread[ThreadArgs]]
var chan = cast[ptr ChannelSPSCSingle](allocShared(MemBlockSize))
chan[].initialize(itemSize = sizeof(int))
createThread(threads[0], thread_func, ThreadArgs(ID: Receiver, chan: chan))
createThread(threads[1], thread_func, ThreadArgs(ID: Sender, chan: chan))
joinThread(threads[0])
joinThread(threads[1])
freeShared(chan)
echo "-----------------------------------"
echo "Success"
main()

View file

@ -1,6 +1,6 @@
discard """ discard """
cmd: "nim c --newruntime $file" cmd: "nim c --newruntime $file"
errormsg: "'=' is not available for type <owned Foo>; requires a copy because it's not the last read of 'a'; another read is done here: tconsume_twice.nim(13, 10); routine: consumeTwice" errormsg: "'=copy' is not available for type <owned Foo>; requires a copy because it's not the last read of 'a'; another read is done here: tconsume_twice.nim(13, 10); routine: consumeTwice"
line: 11 line: 11
""" """
type type

View file

@ -1,5 +1,5 @@
discard """ discard """
errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'" errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
line: 29 line: 29
""" """

View file

@ -1,5 +1,5 @@
discard """ discard """
errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'" errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
file: "tprevent_assign2.nim" file: "tprevent_assign2.nim"
line: 48 line: 48
""" """

View file

@ -1,5 +1,5 @@
discard """ discard """
errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'" errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
file: "tprevent_assign3.nim" file: "tprevent_assign3.nim"
line: 46 line: 46
""" """

View file

@ -1,6 +1,6 @@
discard """ discard """
cmd: '''nim c --newruntime $file''' cmd: '''nim c --newruntime $file'''
errormsg: "'=' is not available for type <owned Button>; requires a copy because it's not the last read of ':envAlt.b1'; another read is done here: tuse_ownedref_after_move.nim(52, 4)" errormsg: "'=copy' is not available for type <owned Button>; requires a copy because it's not the last read of ':envAlt.b1'; another read is done here: tuse_ownedref_after_move.nim(52, 4)"
line: 48 line: 48
""" """