diff options
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | compiler/ast.nim | 2 | ||||
-rw-r--r-- | compiler/injectdestructors.nim | 4 | ||||
-rw-r--r-- | compiler/sempass2.nim | 3 | ||||
-rw-r--r-- | compiler/semstmts.nim | 4 | ||||
-rw-r--r-- | doc/destructors.rst | 22 | ||||
-rw-r--r-- | tests/arc/tcaseobjcopy.nim | 246 | ||||
-rw-r--r-- | tests/arc/tcomputedgotocopy.nim | 41 | ||||
-rw-r--r-- | tests/arc/tmovebugcopy.nim | 526 | ||||
-rw-r--r-- | tests/arc/topt_no_cursor.nim | 12 | ||||
-rw-r--r-- | tests/arc/topt_wasmoved_destroy_pairs.nim | 2 | ||||
-rw-r--r-- | tests/arc/tweavecopy.nim | 154 | ||||
-rw-r--r-- | tests/destructor/tconsume_twice.nim | 2 | ||||
-rw-r--r-- | tests/destructor/tprevent_assign.nim | 2 | ||||
-rw-r--r-- | tests/destructor/tprevent_assign2.nim | 2 | ||||
-rw-r--r-- | tests/destructor/tprevent_assign3.nim | 2 | ||||
-rw-r--r-- | tests/destructor/tuse_ownedref_after_move.nim | 2 |
17 files changed, 1001 insertions, 29 deletions
diff --git a/changelog.md b/changelog.md index 2c8f9b5e2..14bcbe608 100644 --- a/changelog.md +++ b/changelog.md @@ -215,6 +215,10 @@ - The `=destroy` hook no longer has to reset its target, as the compiler now automatically inserts `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 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 diff --git a/compiler/ast.nim b/compiler/ast.nim index 8adbfa15c..f796e64c2 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1331,7 +1331,7 @@ const MaxLockLevel* = 1000'i16 UnknownLockLevel* = TLockLevel(1001'i16) AttachedOpToStr*: array[TTypeAttachedOp, string] = [ - "=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"] + "=destroy", "=copy", "=sink", "=trace", "=dispose", "=deepcopy"] proc `$`*(x: TLockLevel): string = if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>" diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index b18bcf34e..ea90299e2 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -235,7 +235,7 @@ template isUnpackedTuple(n: PNode): bool = proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) = 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 renderTree(ri) m.add '\'' @@ -319,7 +319,7 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode = if tfHasOwned in t.flags and ri.kind != nkNilLit: # try to improve the error message here: if c.otherRead == nil: discard isLastRead(ri, c) - c.checkForErrorPragma(t, ri, "=") + c.checkForErrorPragma(t, ri, "=copy") result = c.genCopyNoCheck(dest, ri) proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode = diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 5eb464bb8..aa04f7451 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -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 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: # rebind type bounds operations after createTypeBoundOps call let t = n[1].typ.skipTypes({tyAlias, tyVar}) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 27cd9019f..b5e69c135 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1732,7 +1732,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") incl(s.flags, sfUsed) incl(s.flags, sfOverriden) - of "=", "=sink": + of "=", "=copy", "=sink": if s.magic == mAsgn: return incl(s.flags, sfUsed) incl(s.flags, sfOverriden) @@ -1754,7 +1754,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = # attach these ops to the canonical tySequence obj = canonType(c, 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: discard "forward declared op" elif obj.attachedOps[k].isNil and tfCheckedForDestructor notin obj.flags: diff --git a/doc/destructors.rst b/doc/destructors.rst index b433c38d7..b581fce3e 100644 --- a/doc/destructors.rst +++ b/doc/destructors.rst @@ -40,7 +40,7 @@ written as: for i in 0..<x.len: `=destroy`(x[i]) 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: if a.data == b.data: return `=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 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. +provide `=destroy` and `=copy`, compiler will take care about the rest. 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. -`=` (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`` operations. @@ -168,14 +168,14 @@ The prototype of this hook for a type ``T`` needs to be: .. 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 - proc `=`(dest: var T; source: T) = + proc `=copy`(dest: var T; source: T) = # protect against self-assignments: if dest.field != source.field: `=destroy`(dest) @@ -183,7 +183,7 @@ The general pattern in ``=`` looks like: 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. @@ -201,7 +201,7 @@ Swap ==== 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 field in the involved objects via ``copyMem`` or a comparable mechanism. In other words, ``swap(a, b)`` is **not** implemented @@ -326,7 +326,7 @@ destroyed at the scope exit. x = y ------------------ (copy) - `=`(x, y) + `=copy`(x, y) f_sink(g()) @@ -336,7 +336,7 @@ destroyed at the scope exit. f_sink(notLastReadOf y) -------------------------- (copy-to-sink) - (let tmp; `=`(tmp, y); + (let tmp; `=copy`(tmp, y); f_sink(tmp)) diff --git a/tests/arc/tcaseobjcopy.nim b/tests/arc/tcaseobjcopy.nim new file mode 100644 index 000000000..ed07b404e --- /dev/null +++ b/tests/arc/tcaseobjcopy.nim @@ -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 diff --git a/tests/arc/tcomputedgotocopy.nim b/tests/arc/tcomputedgotocopy.nim new file mode 100644 index 000000000..78cb6c5c0 --- /dev/null +++ b/tests/arc/tcomputedgotocopy.nim @@ -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()) diff --git a/tests/arc/tmovebugcopy.nim b/tests/arc/tmovebugcopy.nim new file mode 100644 index 000000000..7c5228147 --- /dev/null +++ b/tests/arc/tmovebugcopy.nim @@ -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() diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim index 5fd21439a..d5811e91e 100644 --- a/tests/arc/topt_no_cursor.nim +++ b/tests/arc/topt_no_cursor.nim @@ -33,9 +33,9 @@ result = ( var sibling saved -`=`(sibling, target.parent.left) -`=`(saved, sibling.right) -`=`(sibling.right, saved.left) +`=copy`(sibling, target.parent.left) +`=copy`(saved, sibling.right) +`=copy`(sibling.right, saved.left) `=sink`(sibling.parent, saved) `=destroy`(sibling) -- end of expandArc ------------------------ @@ -46,7 +46,7 @@ var lvalue lnext _ -`=`(lresult, [123]) +`=copy`(lresult, [123]) _ = ( let blitTmp = lresult blitTmp, ";") @@ -67,10 +67,10 @@ try: var it_cursor = x a = ( wasMoved(:tmpD) - `=`(:tmpD, it_cursor.key) + `=copy`(:tmpD, it_cursor.key) :tmpD, wasMoved(:tmpD_1) - `=`(:tmpD_1, it_cursor.val) + `=copy`(:tmpD_1, it_cursor.val) :tmpD_1) echo [ :tmpD_2 = `$`(a) diff --git a/tests/arc/topt_wasmoved_destroy_pairs.nim b/tests/arc/topt_wasmoved_destroy_pairs.nim index 2a6391dd2..2f971f112 100644 --- a/tests/arc/topt_wasmoved_destroy_pairs.nim +++ b/tests/arc/topt_wasmoved_destroy_pairs.nim @@ -38,7 +38,7 @@ try: return add(a): wasMoved(:tmpD) - `=`(:tmpD, x) + `=copy`(:tmpD, x) :tmpD inc i_1, 1 if cond: diff --git a/tests/arc/tweavecopy.nim b/tests/arc/tweavecopy.nim new file mode 100644 index 000000000..fc796b352 --- /dev/null +++ b/tests/arc/tweavecopy.nim @@ -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() diff --git a/tests/destructor/tconsume_twice.nim b/tests/destructor/tconsume_twice.nim index 0030267f8..b0a039e9b 100644 --- a/tests/destructor/tconsume_twice.nim +++ b/tests/destructor/tconsume_twice.nim @@ -1,6 +1,6 @@ discard """ 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 """ type diff --git a/tests/destructor/tprevent_assign.nim b/tests/destructor/tprevent_assign.nim index 108ccc371..4c484ebc1 100644 --- a/tests/destructor/tprevent_assign.nim +++ b/tests/destructor/tprevent_assign.nim @@ -1,5 +1,5 @@ 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 """ diff --git a/tests/destructor/tprevent_assign2.nim b/tests/destructor/tprevent_assign2.nim index 0e4481710..ef20672d5 100644 --- a/tests/destructor/tprevent_assign2.nim +++ b/tests/destructor/tprevent_assign2.nim @@ -1,5 +1,5 @@ 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" line: 48 """ diff --git a/tests/destructor/tprevent_assign3.nim b/tests/destructor/tprevent_assign3.nim index a8a35ea5e..0577aa5ff 100644 --- a/tests/destructor/tprevent_assign3.nim +++ b/tests/destructor/tprevent_assign3.nim @@ -1,5 +1,5 @@ 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" line: 46 """ diff --git a/tests/destructor/tuse_ownedref_after_move.nim b/tests/destructor/tuse_ownedref_after_move.nim index 46540837c..ce96b741e 100644 --- a/tests/destructor/tuse_ownedref_after_move.nim +++ b/tests/destructor/tuse_ownedref_after_move.nim @@ -1,6 +1,6 @@ discard """ 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 """ |