diff options
Diffstat (limited to 'tests/arc')
100 files changed, 9233 insertions, 0 deletions
diff --git a/tests/arc/amodule.nim b/tests/arc/amodule.nim new file mode 100644 index 000000000..2b4204a66 --- /dev/null +++ b/tests/arc/amodule.nim @@ -0,0 +1,21 @@ +# bug #14219 +var vectors = @["a", "b", "c", "d", "e"] + +iterator testVectors(): string = + for vector in vectors: + yield vector + +var r = "" +for item in testVectors(): + r.add item +echo r + +# bug #12990 +iterator test(): int {.closure.} = + yield 0 + +let x = test +while true: + let val = x() + if finished(x): break + echo val diff --git a/tests/arc/bmodule.nim b/tests/arc/bmodule.nim new file mode 100644 index 000000000..70c2cc645 --- /dev/null +++ b/tests/arc/bmodule.nim @@ -0,0 +1,4 @@ +import cmodule + +for i in @[1, 2, 3].cycle(): + echo i diff --git a/tests/arc/cmodule.nim b/tests/arc/cmodule.nim new file mode 100644 index 000000000..6c6e6c0fa --- /dev/null +++ b/tests/arc/cmodule.nim @@ -0,0 +1,4 @@ +iterator cycle*[T](s: openArray[T]): T = + let s = @s + for x in s: + yield x diff --git a/tests/arc/dmodule.nim b/tests/arc/dmodule.nim new file mode 100644 index 000000000..455ec7084 --- /dev/null +++ b/tests/arc/dmodule.nim @@ -0,0 +1,23 @@ +type + MinKind* = enum + minDictionary + minBool + MinValue* = object + case kind*: MinKind + of minDictionary: + symbols: seq[MinOperator] + else: discard + MinOperator = object + +# remove this inline pragma to make it compile +proc `$`*(a: MinValue): string {.inline.} = + case a.kind + of minDictionary: + result = "hello" + for i in a.symbols: + result = "hello" + else: discard + +proc parseMinValue*(): MinValue = + # or this echo + echo result diff --git a/tests/arc/nim.cfg b/tests/arc/nim.cfg new file mode 100644 index 000000000..7c148b797 --- /dev/null +++ b/tests/arc/nim.cfg @@ -0,0 +1 @@ +--sinkInference:on diff --git a/tests/arc/t14383.nim b/tests/arc/t14383.nim new file mode 100644 index 000000000..96b505166 --- /dev/null +++ b/tests/arc/t14383.nim @@ -0,0 +1,217 @@ +discard """ + cmd: "nim c --gc:arc $file" + output: ''' +hello +hello +@["a", "b"] +--------------------- +plain: +destroying: ('first', 42) +destroying: ('second', 20) +destroying: ('third', 12) + +Option[T]: +destroying: ('first', 42) +destroying: ('second', 20) +destroying: ('third', 12) + +seq[T]: +destroying: ('first', 42) +destroying: ('second', 20) +destroying: ('third', 12) + +1 1 +''' +""" + +import dmodule + +var val = parseMinValue() +if val.kind == minDictionary: + echo val + +#------------------------------------------------------------------------------ +# Issue #15238 +#------------------------------------------------------------------------------ + +proc sinkArg(x: sink seq[string]) = + discard + +proc varArg(lst: var seq[string]) = + sinkArg(lst) + +var x = @["a", "b"] +varArg(x) +echo x + + +#------------------------------------------------------------------------------ +# Issue #15286 +#------------------------------------------------------------------------------ + +import std/os +discard getFileInfo(".") + + +#------------------------------------------------------------------------------ +# Issue #15707 +#------------------------------------------------------------------------------ + +type + JVMObject = ref object +proc freeJVMObject(o: JVMObject) = + discard +proc fromJObject(T: typedesc[JVMObject]): T = + result.new(cast[proc(r: T) {.nimcall.}](freeJVMObject)) + +discard JVMObject.fromJObject() + + +#------------------------------------------------------------------------------ +# Issue #15910 +#------------------------------------------------------------------------------ + +import options + +type + Thing = object + name: string + age: int + +proc `=destroy`(thing: var Thing) = + if thing.name != "": + echo "destroying: ('", thing.name, "', ", thing.age, ")" + `=destroy`(thing.name) + `=destroy`(thing.age) + +proc plain() = + var t = Thing(name: "first", age: 42) + t = Thing(name: "second", age: 20) + t = Thing() + let u = Thing(name: "third", age: 12) + +proc optionT() = + var t = Thing(name: "first", age: 42).some + t = Thing(name: "second", age: 20).some + t = none(Thing) + let u = Thing(name: "third", age: 12).some + +proc seqT() = + var t = @[Thing(name: "first", age: 42)] + t = @[Thing(name: "second", age: 20)] + t = @[] + let u = @[Thing(name: "third", age: 12)] + +echo "---------------------" +echo "plain:" +plain() +echo() + +echo "Option[T]:" +optionT() +echo() + +echo "seq[T]:" +seqT() +echo() + + +#------------------------------------------------------------------------------ +# Issue #16120, const seq into sink +#------------------------------------------------------------------------------ + +proc main = + let avals = @[@[1.0'f32, 4.0, 7.0, 10.0]] + let rankdef = avals + echo avals.len, " ", rankdef.len + +main() + + +#------------------------------------------------------------------------------ +# Issue #16722, ref on distinct type, wrong destructors called +#------------------------------------------------------------------------------ + +type + Obj = object of RootObj + ObjFinal = object + ObjRef = ref Obj + ObjFinalRef = ref ObjFinal + D = distinct Obj + DFinal = distinct ObjFinal + DRef = ref D + DFinalRef = ref DFinal + +proc `=destroy`(o: var Obj) = + doAssert false, "no Obj is constructed in this sample" + +proc `=destroy`(o: var ObjFinal) = + doAssert false, "no ObjFinal is constructed in this sample" + +var dDestroyed: int +proc `=destroy`(d: var D) = + dDestroyed.inc + +proc `=destroy`(d: var DFinal) = + dDestroyed.inc + +func newD(): DRef = + DRef ObjRef() + +func newDFinal(): DFinalRef = + DFinalRef ObjFinalRef() + +proc testRefs() = + discard newD() + discard newDFinal() + +testRefs() + +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() diff --git a/tests/arc/t14472.nim b/tests/arc/t14472.nim new file mode 100644 index 000000000..4ef661161 --- /dev/null +++ b/tests/arc/t14472.nim @@ -0,0 +1,43 @@ +discard """ + valgrind: true + cmd: "nim cpp --gc:arc -d:useMalloc --deepcopy:on $file" +""" + +type + ImportMaterial* = object + # Adding a field here makes the problem go away. + + Mesh* = object + vertices: seq[float32] + material: ImportMaterial + + ImportedScene* = object + meshes*: seq[Mesh] + +proc bork() : ImportedScene = + var mats: seq[ImportMaterial] + + setLen(mats, 1) + add(result.meshes, Mesh(material: mats[0])) + +var s = bork() + + +#------------------------------------------------------------------------ +# issue #15543 + +import tables + +type + cdbl {.importc: "double".} = object + + MyObject = ref object of RootObj + y: Table[string, cdbl] + + +proc test = + var x = new(MyObject) + +test() + + diff --git a/tests/arc/t14864.nim b/tests/arc/t14864.nim new file mode 100644 index 000000000..f59b14d2c --- /dev/null +++ b/tests/arc/t14864.nim @@ -0,0 +1,5 @@ +discard """ + cmd: "nim c --gc:arc $file" +""" + +import bmodule diff --git a/tests/arc/t15909.nim b/tests/arc/t15909.nim new file mode 100644 index 000000000..f25c89daf --- /dev/null +++ b/tests/arc/t15909.nim @@ -0,0 +1,16 @@ +discard """ + action: run + cmd: "nim c --gc:arc $file" +""" + +proc f1() {.noreturn.} = raise newException(CatchableError, "") + +proc f2(y: int): int = + if y != 0: + y + else: + f1() + +doAssert f2(5) == 5 +doAssertRaises(CatchableError): + discard f2(0) diff --git a/tests/arc/t16033.nim b/tests/arc/t16033.nim new file mode 100644 index 000000000..59ed22e4d --- /dev/null +++ b/tests/arc/t16033.nim @@ -0,0 +1,10 @@ +discard """ + targets: "c js" + matrix: "--gc:arc" +""" + +# bug #16033 +when defined js: + doAssert not compileOption("gc", "arc") +else: + doAssert compileOption("gc", "arc") diff --git a/tests/arc/t16458.nim b/tests/arc/t16458.nim new file mode 100644 index 000000000..6ae114287 --- /dev/null +++ b/tests/arc/t16458.nim @@ -0,0 +1,6 @@ +discard """ + matrix: "--gc:orc --d:useNimRtl" + action: "compile" +""" + +echo 134 \ No newline at end of file diff --git a/tests/arc/t16558.nim b/tests/arc/t16558.nim new file mode 100644 index 000000000..0dbe02b33 --- /dev/null +++ b/tests/arc/t16558.nim @@ -0,0 +1,9 @@ +discard """ + matrix: "--gc:arc" + errormsg: "expression cannot be cast to 'int'" +""" + +block: # bug #16558 + var value = "hi there" + var keepInt: int + keepInt = cast[int](value) diff --git a/tests/arc/t17025.nim b/tests/arc/t17025.nim new file mode 100644 index 000000000..a64c59ac1 --- /dev/null +++ b/tests/arc/t17025.nim @@ -0,0 +1,56 @@ +discard """ + cmd: "nim c --gc:arc $file" + output: ''' +{"Package": {"name": "hello"}, "Author": {"name": "name", "qq": "123456789", "email": "email"}} +hello +name +123456789 +email +hello +name2 +987654321 +liame +''' +""" + +import parsecfg, streams, tables + +const cfg = """[Package] +name=hello +[Author] +name=name +qq=123456789 +email="email"""" + +proc main() = + let stream = newStringStream(cfg) + let dict = loadConfig(stream) + var pname = dict.getSectionValue("Package","name") + var name = dict.getSectionValue("Author","name") + var qq = dict.getSectionValue("Author","qq") + var email = dict.getSectionValue("Author","email") + echo dict[] + echo pname & "\n" & name & "\n" & qq & "\n" & email + stream.close() + +main() + +proc getDict(): OrderedTableRef[string, OrderedTableRef[string, string]] = + result = newOrderedTable[string, OrderedTableRef[string, string]]() + result["Package"] = newOrderedTable[string, string]() + result["Package"]["name"] = "hello" + result["Author"] = newOrderedTable[string, string]() + result["Author"]["name"] = "name2" + result["Author"]["qq"] = "987654321" + result["Author"]["email"] = "liame" + +proc main2() = + let dict = getDict() + var pname = dict.getSectionValue("Package","name") + var name = dict.getSectionValue("Author","name") + var qq = dict.getSectionValue("Author","qq") + var email = dict.getSectionValue("Author","email") + echo pname & "\n" & name & "\n" & qq & "\n" & email + +main2() + diff --git a/tests/arc/t17173.nim b/tests/arc/t17173.nim new file mode 100644 index 000000000..0acd886a2 --- /dev/null +++ b/tests/arc/t17173.nim @@ -0,0 +1,10 @@ +discard """ + matrix: "--gc:refc; --gc:arc" +""" + +import std/strbasics + + +var a = " vhellov " +strip(a) +doAssert a == "vhellov" diff --git a/tests/arc/t17812.nim b/tests/arc/t17812.nim new file mode 100644 index 000000000..dd8ac89b0 --- /dev/null +++ b/tests/arc/t17812.nim @@ -0,0 +1,41 @@ +discard """ + targets: "c js" + matrix: "--gc:refc; --gc:arc" +""" + +import std/times + +block: # bug #17812 + block: + type + Task = object + cb: proc () + + proc hello() = discard + + + let t = Task(cb: hello) + + doAssert t.repr.len > 0 + + + block: + type MyObj = object + field: DateTime + + + proc `$`(o: MyObj): string = o.repr + + doAssert ($MyObj()).len > 0 + +# bug #22175 + +type Xxx = object + value: string + +proc complete(xxx: ref Xxx, v: sink string) = + xxx.value = move(v) + +let yyy = (ref Xxx)() + +yyy.complete("test") diff --git a/tests/arc/t18645.nim b/tests/arc/t18645.nim new file mode 100644 index 000000000..c5fddd4bb --- /dev/null +++ b/tests/arc/t18645.nim @@ -0,0 +1,18 @@ +discard """ + matrix: "--gc:arc; --gc:refc" + output: ''' +1 +2 +3 +''' +""" + +proc bitTypeIdUnion() = + var bitId {.global.} = block: + 0 + inc bitId + echo bitId + +bitTypeIdUnion() +bitTypeIdUnion() +bitTypeIdUnion() diff --git a/tests/arc/t18971.nim b/tests/arc/t18971.nim new file mode 100644 index 000000000..9b587d956 --- /dev/null +++ b/tests/arc/t18971.nim @@ -0,0 +1,10 @@ +discard """ + cmd: "nim c --gc:arc $file" +""" + +type MyObj = ref object + +var o = MyObj() +proc x: var MyObj = o + +var o2 = x() diff --git a/tests/arc/t18977.nim b/tests/arc/t18977.nim new file mode 100644 index 000000000..c775551a4 --- /dev/null +++ b/tests/arc/t18977.nim @@ -0,0 +1,26 @@ +discard """ + matrix: "--mm:arc" +""" + +type + E = enum + a, b, c, d + X = object + v: int + O = object + case kind: E + of a: + a: int + of {b, c}: + b: float + else: + d: X + +proc `=destroy`(x: var X) = + echo "x destroyed" + +var o = O(kind: d, d: X(v: 12345)) +doAssert o.d.v == 12345 + +doAssertRaises(FieldDefect): + o.kind = a diff --git a/tests/arc/t19231.nim b/tests/arc/t19231.nim new file mode 100644 index 000000000..40fcf277c --- /dev/null +++ b/tests/arc/t19231.nim @@ -0,0 +1,18 @@ +discard """ + matrix: "--mm:orc" + targets: "c cpp" +""" + +type + Game* = ref object + +proc free*(game: Game) = + var mixNumOpened:cint = 0 + for i in 0..<mixNumOpened: + mixNumOpened -= 1 + +proc newGame*(): Game = + new result, free + +var + game*: Game diff --git a/tests/arc/t19364.nim b/tests/arc/t19364.nim new file mode 100644 index 000000000..f520f3291 --- /dev/null +++ b/tests/arc/t19364.nim @@ -0,0 +1,30 @@ +discard """ + cmd: '''nim c --gc:arc --expandArc:fooLeaks $file''' + nimout: ''' +--expandArc: fooLeaks + +var + tmpTuple_cursor + a_cursor + b_cursor + c_cursor +tmpTuple_cursor = refTuple +a_cursor = tmpTuple_cursor[0] +b_cursor = tmpTuple_cursor[1] +c_cursor = tmpTuple_cursor[2] +-- end of expandArc ------------------------ +''' +""" + +func fooLeaks(refTuple: tuple[a, + b, + c: seq[float]]): float = + let (a, b, c) = refTuple + +let refset = (a: newSeq[float](25_000_000), + b: newSeq[float](25_000_000), + c: newSeq[float](25_000_000)) + +var res = newSeq[float](1_000_000) +for i in 0 .. res.high: + res[i] = fooLeaks(refset) diff --git a/tests/arc/t19401.nim b/tests/arc/t19401.nim new file mode 100644 index 000000000..56702a4a2 --- /dev/null +++ b/tests/arc/t19401.nim @@ -0,0 +1,32 @@ +discard """ + output: ''' +delete foo +delete foo +delete foo +''' + matrix: "--mm:arc" +""" + +type Foo = ref object + data: int +proc delete(self: Foo) +proc newFoo: Foo = + let x = 12 + discard x + new(result, delete) + result.data = x +proc delete(self: Foo) = + doAssert self.data == 12 + echo("delete foo") + +if isMainModule: + proc test() = + let x1 = newFoo() + let x2 = newFoo() + discard x1 + discard x2 + var x3: Foo + new(x3, delete) + x3.data = 12 + discard x3 + test() diff --git a/tests/arc/t19402.nim b/tests/arc/t19402.nim new file mode 100644 index 000000000..5ee6fc798 --- /dev/null +++ b/tests/arc/t19402.nim @@ -0,0 +1,32 @@ +discard """ + output: ''' +delete foo +delete foo +delete foo +''' + matrix: "--mm:arc" +""" + +type Foo = ref object of RootObj + data: int +proc delete(self: Foo) +proc newFoo: Foo = + let x = 12 + discard x + new(result, delete) + result.data = x +proc delete(self: Foo) = + doAssert self.data == 12 + echo("delete foo") + +if isMainModule: + proc test() = + let x1 = newFoo() + let x2 = newFoo() + discard x1 + discard x2 + var x3: Foo + new(x3, delete) + x3.data = 12 + discard x3 + test() \ No newline at end of file diff --git a/tests/arc/t19435.nim b/tests/arc/t19435.nim new file mode 100644 index 000000000..519216bad --- /dev/null +++ b/tests/arc/t19435.nim @@ -0,0 +1,29 @@ +discard """ + matrix: "--gc:arc" +""" + +# bug #19435 +{.experimental: "views".} + +type + Bar = object + placeholder: int + Foo = object + placeholder: int + c: seq[Bar] # remove this line to make things right + +func children*(s: var seq[Foo]): openArray[Foo] = + s.toOpenArray(0, s.len-1) + +proc test = + var foos = @[Foo(), Foo()] + + assert foos.children.len == 2 + var flag = true + for a in foos.children: + flag = false + + if flag: + doAssert false + +test() \ No newline at end of file diff --git a/tests/arc/t19457.nim b/tests/arc/t19457.nim new file mode 100644 index 000000000..78447ce82 --- /dev/null +++ b/tests/arc/t19457.nim @@ -0,0 +1,16 @@ +discard """ + matrix: "--gc:refc; --gc:arc" +""" + +# bug #19457 +proc gcd(x, y: seq[int]): seq[int] = + var + a = x + b = y + while b[0] > 0: + let c = @[a[0] mod b[0]] + a = b + b = c + return a + +doAssert gcd(@[1], @[2]) == @[1] \ No newline at end of file diff --git a/tests/arc/t19862.nim b/tests/arc/t19862.nim new file mode 100644 index 000000000..6d3f57692 --- /dev/null +++ b/tests/arc/t19862.nim @@ -0,0 +1,15 @@ +discard """ + matrix: "--gc:refc; --gc:arc" +""" + +import std/widestrs + +# bug #19862 +type NewString = object + +proc len(s: NewString): int = 10 + +converter toNewString(x: WideCStringObj): NewString = discard + +let w = newWideCString("test") +doAssert len(w) == 4 diff --git a/tests/arc/t20456.nim b/tests/arc/t20456.nim new file mode 100644 index 000000000..ace79255a --- /dev/null +++ b/tests/arc/t20456.nim @@ -0,0 +1,7 @@ +discard """ + cmd: "nim check $file" + action: "compile" +""" + +when not defined(gcOrc): + {.error: "orc".} diff --git a/tests/arc/t20588.nim b/tests/arc/t20588.nim new file mode 100644 index 000000000..008bd1dcd --- /dev/null +++ b/tests/arc/t20588.nim @@ -0,0 +1,25 @@ +discard """ + cmd: "nim check --warnings:off --hints:off $file" + errormsg: "" + nimout: ''' +t20588.nim(20, 12) Error: illegal type conversion to 'auto' +t20588.nim(21, 14) Error: illegal type conversion to 'typed' +t20588.nim(22, 16) Error: illegal type conversion to 'untyped' +t20588.nim(24, 7) Error: illegal type conversion to 'any' +''' +""" + + + + + + + + + +discard 0.0.auto +discard typed("abc") +discard untyped(4) +var a = newSeq[bool](1000) +if any(a): + echo "ok?" \ No newline at end of file diff --git a/tests/arc/t21184.nim b/tests/arc/t21184.nim new file mode 100644 index 000000000..91d0c42c7 --- /dev/null +++ b/tests/arc/t21184.nim @@ -0,0 +1,77 @@ +discard """ + matrix: "--mm:orc" +""" + +import std/[with] + +type + Node* {.acyclic.} = ref object of RootObj + name: string + data: pointer + children: seq[Node] + TextNode = ref object of Node + text: string + +proc fakeEcho(s: string) = + if s.len < 0: + echo s + +proc newNode[T: Node](parent: Node): T = + new result + result.data = alloc0(250) + parent.children.add(result) + +proc newRootNode(): Node = + new result + result.data = alloc0(250) + +method printNode(node: Node) {.base.} = + fakeEcho node.name + +method printNode(node: TextNode) = + procCall printNode(Node(node)) + fakeEcho node.text + +proc printChildren(node: Node) = + for child in node.children: + child.printNode() + printChildren(child) + +proc free(node: Node) = + for child in node.children: + free(child) + dealloc(node.data) + +template node(parent: Node, body: untyped): untyped = + var node = newNode[Node](parent) + with node: + body + +proc textNode(parent: Node, text: string) = + var node = newNode[TextNode](parent) + node.text = text + +template withRootNode(body: untyped): untyped = + var root = newRootNode() + root.name = "root" + with root: + body + root.printNode() + printChildren(root) + root.free() + +proc doTest() = + withRootNode: + node: + name = "child1" + node: + name = "child2" + node: + name = "child3" + textNode "Hello, world!" + + +# bug #21171 +if isMainModule: + for i in 0..100000: + doTest() diff --git a/tests/arc/t22218.nim b/tests/arc/t22218.nim new file mode 100644 index 000000000..7837ed1d0 --- /dev/null +++ b/tests/arc/t22218.nim @@ -0,0 +1,25 @@ +discard """ + cmd: "nim c --mm:arc $file" + errormsg: "'=copy' is not available for type <Obj>; requires a copy because it's not the last read of 'chan[]'; routine: test" +""" + +# bug #22218 +type Obj[T] = object + v: T + +proc `=copy`[T]( + dest: var Obj[T], + source: Obj[T] + ) {.error: "A channel cannot be copied".} + +from system/ansi_c import c_calloc + +proc test() = + var v: bool = true + var chan = cast[ptr Obj[int]](c_calloc(1, csize_t sizeof(Obj[int]))) + var copy = chan[] + + echo chan.v + echo v + +test() \ No newline at end of file diff --git a/tests/arc/t22237.nim b/tests/arc/t22237.nim new file mode 100644 index 000000000..c6dbf768a --- /dev/null +++ b/tests/arc/t22237.nim @@ -0,0 +1,55 @@ +discard """ + matrix: "--mm:arc; --mm:orc" +""" + +import std/macros +import std/streams + +# bug #22237 + +proc iterlines_closure2(f: File | Stream): iterator (): string = + result = iterator(): string = + for line in f.lines: + if line.len == 0: + break + yield line + +proc test() = + let f = newStringStream(""" + 1 + 2 + + 3 + 4 + + 5 + 6 + 7 + + 8 +""") + while not f.atEnd(): + let iterator_inst = iterlines_closure2(f) + for item in iterator_inst(): # Fails with "SIGSEGV: Illegal storage access. (Attempt to read from nil?)" + discard + +test() + +# bug #21160 +import sequtils + +iterator allMoves(fls: seq[int]): seq[int] = + yield fls + +proc neighbors(flrs: seq[int]): iterator: seq[int] = + return iterator(): seq[int] = + for flrs2 in allMoves(flrs): + yield flrs2 + for flrs3 in allMoves(flrs2): + yield flrs3 + +let f = @[1] +for _ in neighbors(f): + discard +for _ in neighbors(f): + discard diff --git a/tests/arc/t22478.nim b/tests/arc/t22478.nim new file mode 100644 index 000000000..5373fa161 --- /dev/null +++ b/tests/arc/t22478.nim @@ -0,0 +1,46 @@ +discard """ + matrix: "-d:nimNoLentIterators --mm:arc" + output: '''PUSH DATA: {"test.message":{"test":{"nested":"v1"}}}''' + joinable: false +""" + +# bug #22748 +import std/[json, typetraits, times] + +# publish + +proc publish*[T](payload: T) = + discard + +type MetricsPoint* = JsonNode + +proc push*(stat: string, data: JsonNode, usec: int64 = 0) = + let payload = newJObject() + + # this results in a infinite recursion unless we deepCopy() + payload[stat] = data #.deepCopy + + echo "PUSH DATA: ", payload + + publish[MetricsPoint](payload) + +var scopes {.threadvar.}: seq[JsonNode] + +type WithTimeCallback*[T] = proc(data: var JsonNode): T + +proc pushScoped*[T](metric: string, blk: WithTimeCallback[T]): T {.gcsafe.} = + scopes.add newJObject() + defer: discard scopes.pop() + + let stc = (cpuTime() * 1000_000).int64 + result = blk(scopes[^1]) + let dfc = (cpuTime() * 1000_000).int64 - stc + + push(metric, scopes[^1], dfc) + +# demo code + +discard pushScoped[int]("test.message") do (data: var JsonNode) -> int: + data["test"] = %*{ + "nested": "v1" + } \ No newline at end of file diff --git a/tests/arc/t22787.nim b/tests/arc/t22787.nim new file mode 100644 index 000000000..5840a984b --- /dev/null +++ b/tests/arc/t22787.nim @@ -0,0 +1,37 @@ +discard """ + joinable: false +""" + +import std/assertions + +proc foo = + var s:seq[string] + var res = "" + + for i in 0..3: + s.add ("test" & $i) + s.add ("test" & $i) + + var lastname:string + + for i in s: + var name = i[0..4] + + if name != lastname: + res.add "NEW:" & name & "\n" + else: + res.add name & ">" & lastname & "\n" + + lastname = name + + doAssert res == """ +NEW:test0 +test0>test0 +NEW:test1 +test1>test1 +NEW:test2 +test2>test2 +NEW:test3 +test3>test3 +""" +foo() \ No newline at end of file diff --git a/tests/arc/t23247.nim b/tests/arc/t23247.nim new file mode 100644 index 000000000..0fadc50cd --- /dev/null +++ b/tests/arc/t23247.nim @@ -0,0 +1,52 @@ +discard """ + matrix: ";-d:useMalloc" +""" + +# bug #23247 +import std/hashes + +func baseAddr[T](x: openArray[T]): ptr T = + # Return the address of the zero:th element of x or `nil` if x is empty + if x.len == 0: nil else: cast[ptr T](x) + +func makeUncheckedArray[T](p: ptr T): ptr UncheckedArray[T] = + cast[ptr UncheckedArray[T]](p) + +type + LabelKey = object + data: seq[string] + refs: ptr UncheckedArray[string] + refslen: int + + Gauge = ref object + metrics: seq[seq[seq[string]]] + +template values(key: LabelKey): openArray[string] = + if key.refslen > 0: + key.refs.toOpenArray(0, key.refslen - 1) + else: + key.data + +proc hash(key: LabelKey): Hash = + hash(key.values) + +proc view(T: type LabelKey, values: openArray[string]): T = + # TODO some day, we might get view types - until then.. + LabelKey(refs: baseAddr(values).makeUncheckedArray(), refslen: values.len()) + +template withValue2(k: untyped) = + discard hash(k) + +proc setGauge( + collector: Gauge, + labelValues: openArray[string], +) = + let v = LabelKey.view(labelValues) + withValue2(v) + collector.metrics.add @[@labelValues, @labelValues] + discard @labelValues + +var nim_gc_mem_bytes = Gauge() +let threadID = $getThreadId() +setGauge(nim_gc_mem_bytes, @[threadID]) +setGauge(nim_gc_mem_bytes, @[threadID]) \ No newline at end of file diff --git a/tests/arc/t9650.nim b/tests/arc/t9650.nim new file mode 100644 index 000000000..a8182db68 --- /dev/null +++ b/tests/arc/t9650.nim @@ -0,0 +1,87 @@ +discard """ + matrix: "--gc:arc" +""" + +import typetraits + +# bug #9650 +type + SharedPtr*[T] = object + val: ptr tuple[atomicCounter: int, value: T] + + Node*[T] = object + value: T + next: SharedPtr[Node[T]] + + ForwardList*[T] = object + first: SharedPtr[Node[T]] + +proc `=destroy`*[T](p: var SharedPtr[T]) = + if p.val != nil: + let c = atomicDec(p.val[].atomicCounter) + if c == 0: + when not supportsCopyMem(T): + `=destroy`(p.val[]) + dealloc(p.val) + p.val = nil + +proc `=`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = + if dest.val != src.val: + if dest.val != nil: + `=destroy`(dest) + if src.val != nil: + discard atomicInc(src.val[].atomicCounter) + dest.val = src.val + +proc `=sink`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = + if dest.val != nil and dest.val != src.val: + `=destroy`(dest) + dest.val = src.val + + +proc newSharedPtr*[T](val: sink T): SharedPtr[T] = + result.val = cast[type(result.val)](alloc(sizeof(result.val[]))) + reset(result.val[]) + result.val.atomicCounter = 1 + result.val.value = val + +proc isNil*[T](p: SharedPtr[T]): bool = + p.val == nil + +template `->`*[T](p: SharedPtr[T], name: untyped): untyped = + p.val.value.name + +proc createNode[T](val: T): SharedPtr[ Node[T] ]= + result = newSharedPtr(Node[T](value: val)) + +proc push_front*[T](list: var ForwardList[T], val: T) = + var newElem = createNode(val) + newElem->next = list.first + list.first = newElem + +proc pop_front*[T](list: var ForwardList[T]) = + let head = list.first + list.first = head->next + +proc toString*[T](list: ForwardList[T]): string = + result = "[" + var head = list.first + while not head.isNil: + result &= $(head->value) & ", " + head = head->next + result &= ']' + +block: + var x: ForwardList[int] + x.push_front(1) + x.push_front(2) + x.push_front(3) + + doAssert toString(x) == "[3, 2, 1, ]" + + x.pop_front() + x.pop_front() + doAssert toString(x) == "[1, ]" + + x.pop_front() + doAssert toString(x) == "[]" diff --git a/tests/arc/taliased_reassign.nim b/tests/arc/taliased_reassign.nim new file mode 100644 index 000000000..5563fae8c --- /dev/null +++ b/tests/arc/taliased_reassign.nim @@ -0,0 +1,41 @@ +discard """ + matrix: "--mm:orc" +""" + +# bug #20993 + +type + Dual[int] = object # must be generic (even if fully specified) + p: int +proc D(p: int): Dual[int] = Dual[int](p: p) +proc `+`(x: Dual[int], y: Dual[int]): Dual[int] = D(x.p + y.p) + +type + Tensor[T] = object + buf: seq[T] +proc newTensor*[T](s: int): Tensor[T] = Tensor[T](buf: newSeq[T](s)) +proc `[]`*[T](t: Tensor[T], idx: int): T = t.buf[idx] +proc `[]=`*[T](t: var Tensor[T], idx: int, val: T) = t.buf[idx] = val + +proc `+.`[T](t1, t2: Tensor[T]): Tensor[T] = + let n = t1.buf.len + result = newTensor[T](n) + for i in 0 ..< n: + result[i] = t1[i] + t2[i] + +proc toTensor*[T](a: sink seq[T]): Tensor[T] = + ## This breaks it: Using `T` instead makes it work + type U = typeof(a[0]) + var t: Tensor[U] # Tensor[T] works + t.buf = a + result = t + +proc loss() = + var B = toTensor(@[D(123)]) + let a = toTensor(@[D(-10)]) + B = B +. a + doAssert B[0].p == 113, "I want to be 113, but I am " & $B[0].p + +loss() + + diff --git a/tests/arc/tamemfiles.nim b/tests/arc/tamemfiles.nim new file mode 100644 index 000000000..97deb489f --- /dev/null +++ b/tests/arc/tamemfiles.nim @@ -0,0 +1,110 @@ +discard """ + output: ''' +loop 1a +loop 1b; cols: @[1, x] +loop 1c +loop 1d +loop 1a +loop 1b; cols: @[2, y] +loop 1c +loop 1d +''' + cmd: "nim c --gc:arc $file" +""" + +# bug #13596 + +import tables, memfiles, strutils, os + +type Splitr* = tuple[ repeat: bool, chrDlm: char, setDlm: set[char], n: int ] + +type csize = uint +proc cmemchr*(s: pointer, c: char, n: csize): pointer {. + importc: "memchr", header: "<string.h>" .} +proc `-!`*(p, q: pointer): int {.inline.} = + (cast[int](p) -% cast[int](q)).int +proc `+!`*(p: pointer, i: int): pointer {.inline.} = + cast[pointer](cast[int](p) +% i) +proc `+!`*(p: pointer, i: uint64): pointer {.inline.} = + cast[pointer](cast[uint64](p) + i) + +proc charEq(x, c: char): bool {.inline.} = x == c + +proc initSplitr*(delim: string): Splitr = + if delim == "white": #User can use any other permutation if needed + result.repeat = true + result.chrDlm = ' ' + result.setDlm = { ' ', '\t', '\n' } + result.n = result.setDlm.card + return + for c in delim: + if c in result.setDlm: + result.repeat = true + continue + result.setDlm.incl(c) + inc(result.n) + if result.n == 1: #support n==1 test to allow memchr optimization + result.chrDlm = delim[0] + +proc hash(x: MemSlice): int = 55542 + +template defSplit[T](slc: T, fs: var seq[MemSlice], n: int, repeat: bool, + sep: untyped, nextSep: untyped, isSep: untyped) {.dirty.} = + fs.setLen(if n < 1: 16 else: n) + var b = slc.data + var eob = b +! slc.size + while repeat and eob -! b > 0 and isSep((cast[cstring](b))[0], sep): + b = b +! 1 + if b == eob: fs.setLen(0); return + var e = nextSep(b, sep, (eob -! b).csize) + while e != nil: + if n < 1: #Unbounded msplit + if result == fs.len - 1: #Expand capacity + fs.setLen(if fs.len < 512: 2*fs.len else: fs.len + 512) + elif result == n - 1: #Need 1 more slot for final field + break + fs[result].data = b + fs[result].size = e -! b + result += 1 + while repeat and eob -! e > 0 and isSep((cast[cstring](e))[1], sep): + e = e +! 1 + b = e +! 1 + if eob -! b <= 0: + b = eob + break + e = nextSep(b, sep, (eob -! b).csize) + if not repeat or eob -! b > 0: + fs[result].data = b + fs[result].size = eob -! b + result += 1 + fs.setLen(result) + +proc msplit*(s: MemSlice, fs: var seq[MemSlice], sep=' ', n=0, + repeat=false): int = + defSplit(s, fs, n, repeat, sep, cmemchr, charEq) + +proc split*(s: Splitr, line: MemSlice, cols: var seq[MemSlice], + n=0) {.inline.} = + discard msplit(line, cols, s.chrDlm, n, s.repeat) + +######################################################################## +# Using lines instead of memSlices & split instead of splitr.split seems +# to mask the arc problem, as does simplifying `Table` to `seq[char]`. + +proc load(path: string, delim=" "): Table[MemSlice, seq[char]] = + let f = memfiles.open(path) + let splitr = initSplitr(delim) + var cols: seq[MemSlice] = @[ ] # re-used seq buffer + var nwSq = newSeqOfCap[char](1) # re-used seq value + nwSq.setLen 1 + for line in memSlices(f, eat='\0'): + stderr.write "loop 1a\n" + splitr.split(line, cols, 2) + stderr.write "loop 1b; cols: ", cols, "\n" + let cs = cast[cstring](cols[0].data) + stderr.write "loop 1c\n" #..reports exception here, but + nwSq[0] = cs[0] #..actually doing out of bounds here + stderr.write "loop 1d\n" + result[cols[1]] = nwSq + +discard load(getAppDir() / "testfile.txt") diff --git a/tests/arc/tamodule.nim b/tests/arc/tamodule.nim new file mode 100644 index 000000000..1412e0a7c --- /dev/null +++ b/tests/arc/tamodule.nim @@ -0,0 +1,9 @@ +discard """ + output: ''' +abcde +0 +''' + cmd: "nim c --gc:arc $file" +""" + +import amodule diff --git a/tests/arc/tarc_macro.nim b/tests/arc/tarc_macro.nim new file mode 100644 index 000000000..33ade1da4 --- /dev/null +++ b/tests/arc/tarc_macro.nim @@ -0,0 +1,57 @@ +import macros + +var destroyCalled = false +macro bar() = + let s = newTree(nnkAccQuoted, ident"=destroy") + # let s = ident"`=destroy`" # this would not work + result = quote do: + type Foo = object + # proc `=destroy`(a: var Foo) = destroyCalled = true # this would not work + proc `s`(a: var Foo) = destroyCalled = true + block: + let a = Foo() +bar() +doAssert destroyCalled + +# custom `op` +var destroyCalled2 = false +macro bar(ident) = + var x = 1.5 + result = quote("@") do: + type Foo = object + let `@ident` = 0 # custom op interpolated symbols need quoted (``) + proc `=destroy`(a: var Foo) = + doAssert @x == 1.5 + doAssert compiles(@x == 1.5) + let b1 = @[1,2] + let b2 = @@[1,2] + doAssert $b1 == "[1, 2]" + doAssert $b2 == "@[1, 2]" + destroyCalled2 = true + block: + let a = Foo() +bar(someident) +doAssert destroyCalled2 + +proc `&%`(x: int): int = 1 +proc `&%`(x, y: int): int = 2 + +macro bar2() = + var x = 3 + result = quote("&%") do: + var y = &%x # quoting operator + doAssert &%&%y == 1 # unary operator => need to escape + doAssert y &% y == 2 # binary operator => no need to escape + doAssert y == 3 +bar2() + +block: + macro foo(a: openArray[string] = []): string = + echo a # Segfault doesn't happen if this is removed + newLit "" + + proc bar(a: static[openArray[string]] = []) = + const tmp = foo(a) + + # bug #22909 + doAssert not compiles(bar()) diff --git a/tests/arc/tarc_orc.nim b/tests/arc/tarc_orc.nim new file mode 100644 index 000000000..f2c7de2fc --- /dev/null +++ b/tests/arc/tarc_orc.nim @@ -0,0 +1,186 @@ +discard """ + targets: "c cpp" + matrix: "--mm:arc; --mm:orc" +""" + +block: + type + PublicKey = array[32, uint8] + PrivateKey = array[64, uint8] + + proc ed25519_create_keypair(publicKey: ptr PublicKey; privateKey: ptr PrivateKey) = + publicKey[][0] = uint8(88) + + type + KeyPair = object + public: PublicKey + private: PrivateKey + + proc initKeyPair(): KeyPair = + ed25519_create_keypair(result.public.addr, result.private.addr) + + let keys = initKeyPair() + doAssert keys.public[0] == 88 + + +template minIndexByIt: untyped = + var other = 3 + other + +proc bug20303() = + var hlibs = @["hello", "world", "how", "are", "you"] + let res = hlibs[minIndexByIt()] + doAssert res == "are" + +bug20303() + +proc main() = # todo bug with templates + block: # bug #11267 + var a: seq[char] = block: @[] + doAssert a == @[] + # 2 + proc b: seq[string] = + discard + @[] + doAssert b() == @[] +static: main() +main() + + +type Obj = tuple + value: int + arr: seq[int] + +proc bug(): seq[Obj] = + result.add (value: 0, arr: @[]) + result[^1].value = 1 + result[^1].arr.add 1 + +# bug #19990 +let s = bug() +doAssert s[0] == (value: 1, arr: @[1]) + +block: # bug #21974 + type Test[T] = ref object + values : seq[T] + counter: int + + proc newTest[T](): Test[T] = + result = new(Test[T]) + result.values = newSeq[T](16) + result.counter = 0 + + proc push[T](self: Test[T], value: T) = + self.counter += 1 + if self.counter >= self.values.len: + self.values.setLen(self.values.len * 2) + self.values[self.counter - 1] = value + + proc pop[T](self: Test[T]): T = + result = self.values[0] + self.values[0] = self.values[self.counter - 1] # <--- This line + self.counter -= 1 + + + type X = tuple + priority: int + value : string + + var a = newTest[X]() + a.push((1, "One")) + doAssert a.pop.value == "One" + +# bug #21987 + +type + EmbeddedImage* = distinct Image + Image = object + len: int + +proc imageCopy*(image: Image): Image {.nodestroy.} + +proc `=destroy`*(x: var Image) = + discard +proc `=sink`*(dest: var Image; source: Image) = + `=destroy`(dest) + wasMoved(dest) + +proc `=dup`*(source: Image): Image {.nodestroy.} = + result = imageCopy(source) + +proc `=copy`*(dest: var Image; source: Image) = + dest = imageCopy(source) # calls =sink implicitly + +proc `=destroy`*(x: var EmbeddedImage) = discard + +proc `=dup`*(source: EmbeddedImage): EmbeddedImage {.nodestroy.} = source + +proc `=copy`*(dest: var EmbeddedImage; source: EmbeddedImage) {.nodestroy.} = + dest = source + +proc imageCopy*(image: Image): Image = + result = image + +proc main2 = + block: + var a = Image(len: 2).EmbeddedImage + var b = Image(len: 1).EmbeddedImage + b = a + doAssert Image(a).len == 2 + doAssert Image(b).len == 2 + + block: + var a = Image(len: 2) + var b = Image(len: 1) + b = a + doAssert a.len == 2 + doAssert b.len == 0 + +main2() + +block: + type + TestObj = object of RootObj + name: string + + TestSubObj = object of TestObj + objname: string + + proc `=destroy`(x: TestObj) = + `=destroy`(x.name) + + proc `=destroy`(x: TestSubObj) = + `=destroy`(x.objname) + `=destroy`(TestObj(x)) + + proc testCase() = + let t1 {.used.} = TestSubObj(objname: "tso1", name: "to1") + + proc main() = + testCase() + + main() + +block: # bug #23858 + type Object = object + a: int + b: ref int + var x = 0 + proc fn(): auto {.cdecl.} = + inc x + return Object() + discard fn() + doAssert x == 1 + +block: # bug #24147 + type + O = object of RootObj + val: string + OO = object of O + + proc `=copy`(dest: var O, src: O) = + dest.val = src.val + + let oo = OO(val: "hello world") + var ooCopy : OO + `=copy`(ooCopy, oo) diff --git a/tests/arc/tarcmisc.nim b/tests/arc/tarcmisc.nim new file mode 100644 index 000000000..b4476ef4f --- /dev/null +++ b/tests/arc/tarcmisc.nim @@ -0,0 +1,836 @@ +discard """ + output: ''' +Destructor for TestTestObj +=destroy called +123xyzabc +destroyed: false +destroyed: false +destroyed2: false +destroyed2: false +destroying variable: 2 +destroying variable: 1 +whiley ends :( +1 +(x: "0") +(x: "1") +(x: "2") +(x: "3") +(x: "4") +(x: "5") +(x: "6") +(x: "7") +(x: "8") +(x: "9") +(x: "10") +0 +new line before - @['a'] +new line after - @['a'] +finalizer +aaaaa +hello +true +copying +123 +42 +@["", "d", ""] +ok +destroying variable: 20 +destroying variable: 10 +closed +''' + cmd: "nim c --mm:arc --deepcopy:on -d:nimAllocPagesViaMalloc $file" +""" + +block: # bug #23627 + type + TestObj = object of RootObj + + Test2 = object of RootObj + foo: TestObj + + TestTestObj = object of RootObj + shit: TestObj + + proc `=destroy`(x: TestTestObj) = + echo "Destructor for TestTestObj" + let test = Test2(foo: TestObj()) + + proc testCaseT() = + let tt1 {.used.} = TestTestObj(shit: TestObj()) + + + proc main() = + testCaseT() + + main() + + +# bug #9401 + +type + MyObj = object + len: int + data: ptr UncheckedArray[float] + +proc `=destroy`*(m: MyObj) = + + echo "=destroy called" + + if m.data != nil: + deallocShared(m.data) + +type + MyObjDistinct = distinct MyObj + +proc `=copy`*(m: var MyObj, m2: MyObj) = + if m.data == m2.data: return + if m.data != nil: + `=destroy`(m) + m.len = m2.len + if m.len > 0: + m.data = cast[ptr UncheckedArray[float]](allocShared(sizeof(float) * m.len)) + copyMem(m.data, m2.data, sizeof(float) * m.len) + + +proc `=sink`*(m: var MyObj, m2: MyObj) = + if m.data != m2.data: + if m.data != nil: + `=destroy`(m) + m.len = m2.len + m.data = m2.data + +proc newMyObj(len: int): MyObj = + result.len = len + result.data = cast[ptr UncheckedArray[float]](allocShared(sizeof(float) * len)) + +proc newMyObjDistinct(len: int): MyObjDistinct = + MyObjDistinct(newMyObj(len)) + +proc fooDistinct = + doAssert newMyObjDistinct(2).MyObj.len == 2 + +fooDistinct() + + +proc takeSink(x: sink string): bool = true + +proc b(x: sink string): string = + if takeSink(x): + return x & "abc" + +proc bbb(inp: string) = + let y = inp & "xyz" + echo b(y) + +bbb("123") + + +# bug #13691 +type Variable = ref object + value: int + +proc `=destroy`(self: typeof(Variable()[])) = + echo "destroying variable: ",self.value + +proc newVariable(value: int): Variable = + result = Variable() + result.value = value + #echo "creating variable: ",result.value + +proc test(count: int) = + var v {.global.} = newVariable(10) + + var count = count - 1 + if count == 0: return + + test(count) + echo "destroyed: ", v.isNil + +test(3) + +proc test2(count: int) = + block: #XXX: Fails with block currently + var v {.global.} = newVariable(20) + + var count = count - 1 + if count == 0: return + + test2(count) + echo "destroyed2: ", v.isNil + +test2(3) + +proc whiley = + var a = newVariable(1) + while true: + var b = newVariable(2) + if true: raise newException(CatchableError, "test") + +try: + whiley() +except CatchableError: + echo "whiley ends :(" + +#------------------------------------------------------------------------------ +# issue #13810 + +import streams + +type + A = ref AObj + AObj = object of RootObj + io: Stream + B = ref object of A + x: int + +proc `=destroy`(x: AObj) = + close(x.io) + echo "closed" + +var x = B(io: newStringStream("thestream")) + + +#------------------------------------------------------------------------------ +# issue #14003 + +proc cryptCTR*(nonce: var openArray[char]) = + nonce[1] = 'A' + +proc main() = + var nonce1 = "0123456701234567" + cryptCTR(nonce1) + doAssert(nonce1 == "0A23456701234567") + var nonce2 = "01234567" + cryptCTR(nonce2.toOpenArray(0, nonce2.len-1)) + doAssert(nonce2 == "0A234567") + +main() + +# bug #14079 +import std/algorithm + +let + n = @["c", "b"] + q = @[("c", "2"), ("b", "1")] + +doAssert n.sortedByIt(it) == @["b", "c"], "fine" +doAssert q.sortedByIt(it[0]) == @[("b", "1"), ("c", "2")], "fails under arc" + + +#------------------------------------------------------------------------------ +# issue #14236 + +type + MyType = object + a: seq[int] + +proc re(x: static[string]): static MyType = + MyType() + +proc match(inp: string, rg: static MyType) = + doAssert rg.a.len == 0 + +match("ac", re"a(b|c)") + +#------------------------------------------------------------------------------ +# issue #14243 + +type + Game* = ref object + +proc free*(game: Game) = + let a = 5 + +proc newGame*(): Game = + new(result, free) + +var game*: Game + + +#------------------------------------------------------------------------------ +# issue #14333 + +type + SimpleLoop = object + + Lsg = object + loops: seq[ref SimpleLoop] + root: ref SimpleLoop + +var lsg: Lsg +lsg.loops.add lsg.root +echo lsg.loops.len + +# bug #14495 +type + Gah = ref object + x: string + +proc bug14495 = + var owners: seq[Gah] + for i in 0..10: + owners.add Gah(x: $i) + + var x: seq[Gah] + for i in 0..10: + x.add owners[i] + + for i in 0..100: + setLen(x, 0) + setLen(x, 10) + + for i in 0..x.len-1: + if x[i] != nil: + echo x[i][] + + for o in owners: + echo o[] + +bug14495() + +# bug #14396 +type + Spinny = ref object + t: ref int + text: string + +proc newSpinny*(): Spinny = + Spinny(t: new(int), text: "hello") + +proc spinnyLoop(x: ref int, spinny: sink Spinny) = + echo x[] + +proc start*(spinny: sink Spinny) = + spinnyLoop(spinny.t, spinny) + +var spinner1 = newSpinny() +spinner1.start() + +# bug #14345 + +type + SimpleLoopB = ref object + children: seq[SimpleLoopB] + parent: SimpleLoopB + +proc addChildLoop(self: SimpleLoopB, loop: SimpleLoopB) = + self.children.add loop + +proc setParent(self: SimpleLoopB, parent: SimpleLoopB) = + self.parent = parent + self.parent.addChildLoop(self) + +var l = SimpleLoopB() +l.setParent(l) + + +# bug #14968 +import times +let currentTime = now().utc + + +# bug #14994 +import sequtils +var newLine = @['a'] +let indent = newSeq[char]() + +echo "new line before - ", newline + +newline.insert(indent, 0) + +echo "new line after - ", newline + +# bug #15044 + +type + Test = ref object + +proc test: Test = + # broken + new(result, proc(x: Test) = + echo "finalizer" + ) + +proc tdirectFinalizer = + discard test() + +tdirectFinalizer() + + +# bug #14480 +proc hello(): int = + result = 42 + +var leaves {.global.} = hello() +doAssert leaves == 42 + +# bug #15052 + +proc mutstrings = + var data = "hello" + for c in data.mitems(): + c = 'a' + echo data + +mutstrings() + +# bug #15038 + +type + Machine = ref object + hello: string + +var machineTypes: seq[tuple[factory: proc(): Machine]] + +proc registerMachine(factory: proc(): Machine) = + var mCreator = proc(): Machine = + result = factory() + + machineTypes.add((factory: mCreator)) + +proc facproc(): Machine = + result = Machine(hello: "hello") + +registerMachine(facproc) + +proc createMachine = + for machine in machineTypes: + echo machine.factory().hello + +createMachine() + +# bug #15122 + +import tables + +type + BENodeKind = enum + tkBytes, + tkList, + tkDict + + BENode = object + case kind: BENodeKind + of tkBytes: strVal: string + of tkList: listVal: seq[BENode] + of tkDict: dictVal: Table[string, BENode] + +var data = { + "examples": { + "values": BENode( + kind: tkList, + listVal: @[BENode(kind: tkBytes, strVal: "test")] + ) + }.toTable() +}.toTable() + +# For ARC listVal is empty for some reason +doAssert data["examples"]["values"].listVal[0].strVal == "test" + + + + +############################################################################### +# bug #15405 +import parsexml +const test_xml_str = "<A><B>value</B></A>" +var stream = newStringStream(test_xml_str) +var xml: XmlParser +open(xml, stream, "test") +var xml2 = deepCopy(xml) + +proc text_parser(xml: var XmlParser) = + var test_passed = false + while true: + xml.next() + case xml.kind + of xmlElementStart: + if xml.elementName == "B": + xml.next() + if xml.kind == xmlCharData and xml.charData == "value": + test_passed = true + + of xmlEof: break + else: discard + xml.close() + doAssert(test_passed) + +text_parser(xml) +text_parser(xml2) + +# bug #15599 +type + PixelBuffer = ref object + +proc newPixelBuffer(): PixelBuffer = + new(result) do (buffer: PixelBuffer): + echo "ok" + +discard newPixelBuffer() + + +# bug #17199 + +proc passSeq(data: seq[string]) = + # used the system.& proc initially + let wat = data & "hello" + +proc test2 = + let name = @["hello", "world"] + passSeq(name) + doAssert name == @["hello", "world"] + +static: test2() # was buggy +test2() + +proc merge(x: sink seq[string], y: sink string): seq[string] = + newSeq(result, x.len + 1) + for i in 0..x.len-1: + result[i] = move(x[i]) + result[x.len] = move(y) + +proc passSeq2(data: seq[string]) = + # used the system.& proc initially + let wat = merge(data, "hello") + +proc test3 = + let name = @["hello", "world"] + passSeq2(name) + doAssert name == @["hello", "world"] + +static: test3() # was buggy +test3() + +# bug #17712 +proc t17712 = + var ppv = new int + discard @[ppv] + var el: ref int + el = [ppv][0] + echo el != nil + +t17712() + +# bug #18030 + +type + Foo = object + n: int + +proc `=copy`(dst: var Foo, src: Foo) = + echo "copying" + dst.n = src.n + +proc `=sink`(dst: var Foo, src: Foo) = + echo "sinking" + dst.n = src.n + +var a: Foo + +proc putValue[T](n: T) + +proc useForward = + putValue(123) + +proc putValue[T](n: T) = + var b = Foo(n:n) + a = b + echo b.n + +useForward() + + +# bug #17319 +type + BrokenObject = ref object + brokenType: seq[int] + +proc use(obj: BrokenObject) = + discard + +method testMethod(self: BrokenObject) {.base.} = + iterator testMethodIter() {.closure.} = + use(self) + + var nameIterVar = testMethodIter + nameIterVar() + +let mikasa = BrokenObject() +mikasa.testMethod() + +# bug #19205 +type + InputSectionBase* = object of RootObj + relocations*: seq[int] # traced reference. string has a similar SIGSEGV. + InputSection* = object of InputSectionBase + +proc fooz(sec: var InputSectionBase) = + if sec of InputSection: # this line SIGSEGV. + echo 42 + +var sec = create(InputSection) +sec[] = InputSection(relocations: newSeq[int]()) +fooz sec[] + +block: + type + Data = ref object + id: int + proc main = + var x = Data(id: 99) + var y = x + x[] = Data(id: 778)[] + doAssert y.id == 778 + doAssert x[].id == 778 + main() + +block: # bug #19857 + type + ValueKind = enum VNull, VFloat, VObject # need 3 elements. Cannot remove VNull or VObject + + Value = object + case kind: ValueKind + of VFloat: fnum: float + of VObject: tab: Table[int, int] # OrderedTable[T, U] also makes it fail. + # "simpler" types also work though + else: discard # VNull can be like this, but VObject must be filled + + # required. Pure proc works + FormulaNode = proc(c: OrderedTable[string, int]): Value + + proc toF(v: Value): float = + doAssert v.kind == VFloat + case v.kind + of VFloat: result = v.fnum + else: discard + + + proc foo() = + let fuck = initOrderedTable[string, int]() + proc cb(fuck: OrderedTable[string, int]): Value = + # works: + #result = Value(kind: VFloat, fnum: fuck["field_that_does_not_exist"].float) + # broken: + discard "actuall runs!" + let t = fuck["field_that_does_not_exist"] + echo "never runs, but we crash after! ", t + + doAssertRaises(KeyError): + let fn = FormulaNode(cb) + let v = fn(fuck) + #echo v + let res = v.toF() + + foo() + +import std/options + +# bug #21592 +type Event* = object + code*: string + +type App* = ref object of RootObj + id*: string + +method process*(self: App): Option[Event] {.base.} = + raise Exception.new_exception("not impl") + +# bug #21617 +type Test2 = ref object of RootObj + +method bug(t: Test2): seq[float] {.base.} = discard + +block: # bug #22664 + type + ElementKind = enum String, Number + Element = object + case kind: ElementKind + of String: + str: string + of Number: + num: float + Calc = ref object + stack: seq[Element] + + var calc = new Calc + + calc.stack.add Element(kind: Number, num: 200.0) + doAssert $calc.stack == "@[(kind: Number, num: 200.0)]" + let calc2 = calc + calc2.stack = calc.stack # This nulls out the object in the stack + doAssert $calc.stack == "@[(kind: Number, num: 200.0)]" + doAssert $calc2.stack == "@[(kind: Number, num: 200.0)]" + +block: # bug #19250 + type + Bar[T] = object + err: proc(): string + + Foo[T] = object + run: proc(): Bar[T] + + proc bar[T](err: proc(): string): Bar[T] = + assert not err.isNil + Bar[T](err: err) + + proc foo(): Foo[char] = + result.run = proc(): Bar[char] = + # works + # result = Bar[char](err: proc(): string = "x") + # not work + result = bar[char](proc(): string = "x") + + proc bug[T](fs: Foo[T]): Foo[T] = + result.run = proc(): Bar[T] = + let res = fs.run() + + # works + # var errors = @[res.err] + + # not work + var errors: seq[proc(): string] + errors.add res.err + + return bar[T] do () -> string: + for err in errors: + result.add res.err() + + doAssert bug(foo()).run().err() == "x" + +block: # bug #22259 + type + ProcWrapper = tuple + p: proc() {.closure.} + + + proc f(wrapper: ProcWrapper) = + let s = @[wrapper.p] + let a = [wrapper.p] + + proc main = + # let wrapper: ProcWrapper = ProcWrapper(p: proc {.closure.} = echo 10) + let wrapper: ProcWrapper = (p: proc {.closure.} = echo 10) + f(wrapper) + + main() + +block: + block: # bug #22923 + block: + let + a: int = 100 + b: int32 = 200'i32 + + let + x = arrayWith(a, 8) # compiles + y = arrayWith(b, 8) # internal error + z = arrayWith(14, 8) # integer literal also results in a crash + + doAssert x == [100, 100, 100, 100, 100, 100, 100, 100] + doAssert $y == "[200, 200, 200, 200, 200, 200, 200, 200]" + doAssert z == [14, 14, 14, 14, 14, 14, 14, 14] + + block: + let a: string = "nim" + doAssert arrayWith(a, 3) == ["nim", "nim", "nim"] + + let b: char = 'c' + doAssert arrayWith(b, 3) == ['c', 'c', 'c'] + + let c: uint = 300'u + doAssert $arrayWith(c, 3) == "[300, 300, 300]" + +block: # bug #23505 + type + K = object + C = object + value: ptr K + + proc init(T: type C): C = + let tmp = new K + C(value: addr tmp[]) + + discard init(C) + +block: # bug #23524 + type MyType = object + a: int + + proc `=destroy`(typ: MyType) = discard + + var t1 = MyType(a: 100) + var t2 = t1 # Should be a copy? + + proc main() = + t2 = t1 + doAssert t1.a == 100 + doAssert t2.a == 100 + + main() + +block: # bug #23907 + type + Thingy = object + value: int + + ExecProc[C] = proc(value: sink C): int {.nimcall.} + + proc `=copy`(a: var Thingy, b: Thingy) {.error.} + + var thingyDestroyCount = 0 + + proc `=destroy`(thingy: Thingy) = + assert(thingyDestroyCount <= 0) + thingyDestroyCount += 1 + + proc store(value: sink Thingy): int = + result = value.value + + let callback: ExecProc[Thingy] = store + + doAssert callback(Thingy(value: 123)) == 123 + +import std/strutils + +block: # bug #23974 + func g(e: seq[string]): lent seq[string] = result = e + proc k(f: string): seq[string] = f.split("/") + proc n() = + const r = "/d/" + let t = + if true: + k(r).g() + else: + k("/" & r).g() + echo t + + n() + +block: # bug #23973 + func g(e: seq[string]): lent seq[string] = result = e + proc k(f: string): seq[string] = f.split("/") + proc n() = + const r = "/test/empty" # or "/test/empty/1" + let a = k(r).g() + let t = + if true: + k(r).g() + else: + k("/" & r).g() # or raiseAssert "" + doAssert t == a + + n() + +block: # bug #24141 + func reverse(s: var openArray[char]) = + s[0] = 'f' + + func rev(s: var string) = + s.reverse + + proc main = + var abc = "abc" + abc.rev + doAssert abc == "fbc" + + main() diff --git a/tests/arc/tasyncawait.nim b/tests/arc/tasyncawait.nim new file mode 100644 index 000000000..91a7453cd --- /dev/null +++ b/tests/arc/tasyncawait.nim @@ -0,0 +1,68 @@ +discard """ + outputsub: "result: 5000" + cmd: "nim c --gc:orc $file" +""" + +import asyncdispatch, asyncnet, nativesockets, net, strutils +from stdtest/netutils import bindAvailablePort + +var msgCount = 0 + +const + swarmSize = 50 + messagesToSend = 100 + +var clientCount = 0 + +proc sendMessages(client: AsyncFD) {.async.} = + for i in 0 ..< messagesToSend: + await send(client, "Message " & $i & "\c\L") + +proc launchSwarm(port: Port) {.async.} = + for i in 0 ..< swarmSize: + var sock = createAsyncNativeSocket() + + await connect(sock, "localhost", port) + await sendMessages(sock) + closeSocket(sock) + +proc readMessages(client: AsyncFD) {.async.} = + # wrapping the AsyncFd into a AsyncSocket object + var sockObj = newAsyncSocket(client) + var (ipaddr, port) = sockObj.getPeerAddr() + doAssert ipaddr == "127.0.0.1" + (ipaddr, port) = sockObj.getLocalAddr() + doAssert ipaddr == "127.0.0.1" + while true: + var line = await recvLine(sockObj) + if line == "": + closeSocket(client) + clientCount.inc + break + else: + if line.startsWith("Message "): + msgCount.inc + else: + doAssert false + +proc createServer(server: AsyncFD) {.async.} = + discard server.SocketHandle.listen() + while true: + asyncCheck readMessages(await accept(server)) + +proc main = + let server = createAsyncNativeSocket() + let port = bindAvailablePort(server.SocketHandle) + asyncCheck createServer(server) + asyncCheck launchSwarm(port) + while true: + poll() + if clientCount == swarmSize: break + +let mem = getOccupiedMem() +main() + +doAssert msgCount == swarmSize * messagesToSend +echo "result: ", msgCount +GC_fullCollect() +echo "memory: ", formatSize(getOccupiedMem() - mem) diff --git a/tests/arc/tasyncleak.nim b/tests/arc/tasyncleak.nim new file mode 100644 index 000000000..8e3a7b3e7 --- /dev/null +++ b/tests/arc/tasyncleak.nim @@ -0,0 +1,21 @@ +discard """ + outputsub: "(allocCount: 4050, deallocCount: 4048)" + cmd: "nim c --gc:orc -d:nimAllocStats $file" +""" + +import asyncdispatch +# bug #15076 +const + # Just to occupy some RAM + BigData = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +proc doNothing(): Future[void] {.async.} = + discard + +proc main(): Future[void] {.async.} = + for x in 0 .. 1_000: + await doNothing() + +waitFor main() +GC_fullCollect() +echo getAllocStats() diff --git a/tests/arc/tasyncleak2.nim b/tests/arc/tasyncleak2.nim new file mode 100644 index 000000000..87d7e73f9 --- /dev/null +++ b/tests/arc/tasyncleak2.nim @@ -0,0 +1,88 @@ +discard """ + output: "success" + cmd: "nim c --gc:orc $file" +""" + +# issue #15076 +import deques, strutils, asyncdispatch + +proc doNothing(): Future[void] = + #[ + var + :env + :env_1 + try: + `=destroy`(:env) + internalNew(:env) + `=sink`(:env.retFuture1, newFuture("doNothing")) + + `=destroy_1`(:env_1) + internalNew(:env_1) + `=`(:env_1.:up, :env) + `=sink_1`(:env.nameIterVar2, (doNothingIter, :env_1)) + + (doNothingNimAsyncContinue, :env)() + return `=_1`(result, :env.retFuture1) + finally: + `=destroy`(:env) + ]# + + var retFuture = newFuture[void]("doNothing") + iterator doNothingIter(): FutureBase {.closure.} = + # inspected ARC code: looks correct! + block: + var qqq = initDeque[string]() + for i in 0 .. 1000: + qqq.addLast($i) + complete(retFuture) # env.up.retFuture1 + + var nameIterVar = doNothingIter # iter_Env -> retFuture -> + + proc doNothingNimAsyncContinue() {.closure.} = + # inspected ARC code: looks correct + if not nameIterVar.finished: + var next_gensym0 = nameIterVar() + while (not next_gensym0.isNil) and next_gensym0.finished: + next_gensym0 = nameIterVar() + if nameIterVar.finished: + break + if next_gensym0 != nil: + {.gcsafe.}: + next_gensym0.addCallback cast[proc () {.closure, gcsafe.}](doNothingNimAsyncContinue) + + doNothingNimAsyncContinue() + return retFuture + +proc main(): Future[void] = + template await[T](f_gensym12: Future[T]): auto {.used.} = + var internalTmpFuture_gensym12: FutureBase = f_gensym12 + yield internalTmpFuture_gensym12 + (cast[typeof(f_gensym12)](internalTmpFuture_gensym12)).read() + + var retFuture = newFuture[void]("main") + iterator mainIter(): FutureBase {.closure.} = + block: + for x in 0 .. 1000: + await doNothing() + complete(retFuture) + + var nameIterVar_gensym11 = mainIter + proc mainNimAsyncContinue() {.closure.} = + if not nameIterVar_gensym11.finished: + var next_gensym11 = unown nameIterVar_gensym11() + while (not next_gensym11.isNil) and next_gensym11.finished: + next_gensym11 = unown nameIterVar_gensym11() + if nameIterVar_gensym11.finished: + break + if next_gensym11 != nil: + {.gcsafe.}: + next_gensym11.addCallback cast[proc () {.closure, gcsafe.}](mainNimAsyncContinue) + + mainNimAsyncContinue() + return retFuture + +for i in 0..9: + waitFor main() + GC_fullCollect() + doAssert getOccupiedMem() < 1024 +echo "success" diff --git a/tests/arc/tasyncleak3.nim b/tests/arc/tasyncleak3.nim new file mode 100644 index 000000000..21e963b7f --- /dev/null +++ b/tests/arc/tasyncleak3.nim @@ -0,0 +1,47 @@ +discard """ + cmd: "nim c --gc:orc -d:useMalloc $file" + output: "true" + valgrind: "true" +""" + +import strutils + +type + FutureBase* = ref object of RootObj ## Untyped future. + finished: bool + stackTrace: seq[StackTraceEntry] ## For debugging purposes only. + +proc newFuture*(): FutureBase = + new(result) + result.finished = false + result.stackTrace = getStackTraceEntries() + +type + PDispatcher {.acyclic.} = ref object + +var gDisp: PDispatcher +new gDisp + +proc testCompletion(): FutureBase = + var retFuture = newFuture() + + iterator testCompletionIter(): FutureBase {.closure.} = + retFuture.finished = true + when not defined(nobug): + let disp = gDisp # even worse memory consumption this way... + + var nameIterVar = testCompletionIter + proc testCompletionNimAsyncContinue() {.closure.} = + if not nameIterVar.finished: + discard nameIterVar() + testCompletionNimAsyncContinue() + return retFuture + +proc main = + for i in 0..10_000: + discard testCompletion() + +main() + +GC_fullCollect() +echo getOccupiedMem() < 1024 diff --git a/tests/arc/tasyncleak4.nim b/tests/arc/tasyncleak4.nim new file mode 100644 index 000000000..58cd7f0b7 --- /dev/null +++ b/tests/arc/tasyncleak4.nim @@ -0,0 +1,21 @@ +discard """ + cmd: "nim c --gc:orc -d:useMalloc $file" + output: '''ok''' + valgrind: "leaks" +""" + +# bug #15076 +import asyncdispatch + +var futures: seq[Future[void]] + +for i in 1..20: + futures.add sleepAsync 1 + futures.add sleepAsync 1 + + futures.all.waitFor() + futures.setLen 0 + +setGlobalDispatcher nil +GC_fullCollect() +echo "ok" diff --git a/tests/arc/tasyncorc.nim b/tests/arc/tasyncorc.nim new file mode 100644 index 000000000..63703b559 --- /dev/null +++ b/tests/arc/tasyncorc.nim @@ -0,0 +1,26 @@ +discard """ + output: '''230000''' + cmd: '''nim c --gc:orc -d:useMalloc $file''' + valgrind: "leaks" +""" + +# bug #14402 + +import asynchttpserver, asyncdispatch, httpclient, strutils + +proc cb(req: Request) {.async, gcsafe.} = + const html = " ".repeat(230000) + await req.respond(Http200, html) + +var server = newAsyncHttpServer() +asyncCheck server.serve(Port(8080), cb) + +proc test {.async.} = + var + client = newAsyncHttpClient() + resp = await client.get("http://localhost:8080") + + let x = (await resp.body).len + echo x # crash + +waitFor test() diff --git a/tests/arc/tcaseobj.nim b/tests/arc/tcaseobj.nim new file mode 100644 index 000000000..3499f5c1e --- /dev/null +++ b/tests/arc/tcaseobj.nim @@ -0,0 +1,366 @@ +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") +@[(kind: P, pChildren: @[])] +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" + {.cast(uncheckedAssign).}: + x.kind1 = true + x.y2 = @["1", "2"] + {.cast(uncheckedAssign).}: + x.kind2 = true + x.z1 = "yes" + {.cast(uncheckedAssign).}: + x.kind2 = false + x.z2 = @["1", "2"] + {.cast(uncheckedAssign).}: + x.kind2 = true + x.z1 = "yes" + x.kind2 = true # should be no effect + doAssert(x.z1 == "yes") + {.cast(uncheckedAssign).}: + x.kind2 = false + {.cast(uncheckedAssign).}: + 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] = + {.cast(uncheckedAssign).}: + 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) +{.cast(uncheckedAssign).}: + 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 + +# ------------------------------------ +# bug #20305 + +type + ContentNodeKind = enum + P, Br, Text + ContentNode = object + case kind: ContentNodeKind + of P: pChildren: seq[ContentNode] + of Br: discard + of Text: textStr: string + +proc bug20305 = + var x = ContentNode(kind: P, pChildren: @[ + ContentNode(kind: P, pChildren: @[ContentNode(kind: Text, textStr: "brrr")]) + ]) + x.pChildren.add ContentNode(kind: Br) + x.pChildren.del(0) + {.cast(uncheckedAssign).}: + x.pChildren[0].kind = P + echo x.pChildren + +bug20305() + +# bug #21023 +block: + block: + type + MGErrorKind = enum + mgeUnexpected, mgeNotFound + + type Foo = object + kind: MGErrorKind + ex: Exception + + type Boo = object + a: seq[int] + + type + Result2 = object + case o: bool + of false: + e: Foo + of true: + v: Boo + + proc startSessionSync(): Result2 = + return Result2(o: true) + + proc mainSync = + let ff = startSessionSync() + doAssert ff.o == true + + mainSync() + + block: + type + MGErrorKind = enum + mgeUnexpected, mgeNotFound + + type Foo = object + kind: MGErrorKind + ex: Exception + + type Boo = object + a: seq[int] + + type + Result2 = object + case o: bool + of false: + e: Foo + of true: + v: Boo + s: int + + proc startSessionSync(): Result2 = + return Result2(o: true, s: 12) + + proc mainSync = + let ff = startSessionSync() + doAssert ff.s == 12 + + mainSync() + +import std/sequtils + +# bug #23690 +type + SomeObj* = object of RootObj + + Item* = object + case kind*: 0..1 + of 0: + a*: int + b*: SomeObj + of 1: + c*: string + + ItemExt* = object + a*: Item + b*: string + +proc do1(x: int): seq[(string, Item)] = + result = @[("zero", Item(kind: 1, c: "first"))] + +proc do2(x: int, e: ItemExt): seq[(string, ItemExt)] = + do1(x).map(proc(v: (string, Item)): auto = (v[0], ItemExt(a: v[1], b: e.b))) + +doAssert $do2(0, ItemExt(a: Item(kind: 1, c: "second"), b: "third")) == """@[("zero", (a: (kind: 1, c: "first"), b: "third"))]""" diff --git a/tests/arc/tcaseobjcopy.nim b/tests/arc/tcaseobjcopy.nim new file mode 100644 index 000000000..fb26a4973 --- /dev/null +++ b/tests/arc/tcaseobjcopy.nim @@ -0,0 +1,253 @@ +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" + {.cast(uncheckedAssign).}: + x.kind1 = true + x.y2 = @["1", "2"] + {.cast(uncheckedAssign).}: + x.kind2 = true + x.z1 = "yes" + {.cast(uncheckedAssign).}: + x.kind2 = false + x.z2 = @["1", "2"] + {.cast(uncheckedAssign).}: + x.kind2 = true + x.z1 = "yes" + x.kind2 = true # should be no effect + doAssert(x.z1 == "yes") + {.cast(uncheckedAssign).}: + 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] = + {.cast(uncheckedAssign).}: + 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) +{.cast(uncheckedAssign).}: + 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/tclosureiter.nim b/tests/arc/tclosureiter.nim new file mode 100644 index 000000000..e4a2ceac6 --- /dev/null +++ b/tests/arc/tclosureiter.nim @@ -0,0 +1,36 @@ +discard """ + cmd: '''nim c -d:nimAllocStats --gc:arc $file''' + output: '''(allocCount: 102, deallocCount: 102)''' +""" + +type + FutureBase = ref object + someData: string + +const + # Just to occupy some RAM + BigData = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +iterator mainIter(): FutureBase {.closure.} = + for x in 0 .. 100: + var internalTmpFuture = FutureBase(someData: BigData) + yield internalTmpFuture + +proc main() = + var nameIterVar = mainIter + var next = nameIterVar() + while not isNil(next): + next = nameIterVar() + if not isNil(next): + doAssert next.someData.len == 97 + # GC_unref(next) + # If you uncomment the GC_ref above, + # the program basically uses no memory after the run. + # but crashes with refc, which might indicate + # that arc/orc simply never frees the result of "next"? + if finished(nameIterVar): + break + +main() +GC_fullCollect() +echo getAllocStats() diff --git a/tests/arc/tcomputedgoto.nim b/tests/arc/tcomputedgoto.nim new file mode 100644 index 000000000..07487684a --- /dev/null +++ b/tests/arc/tcomputedgoto.nim @@ -0,0 +1,44 @@ +discard """ + cmd: '''nim c --mm:arc $file''' + output: ''' +2 +2 +destroyed +''' +""" + +type + ObjWithDestructor = object + a: int +proc `=destroy`(self: 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/tcomputedgotocopy.nim b/tests/arc/tcomputedgotocopy.nim new file mode 100644 index 000000000..07487684a --- /dev/null +++ b/tests/arc/tcomputedgotocopy.nim @@ -0,0 +1,44 @@ +discard """ + cmd: '''nim c --mm:arc $file''' + output: ''' +2 +2 +destroyed +''' +""" + +type + ObjWithDestructor = object + a: int +proc `=destroy`(self: 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/tconst_to_sink.nim b/tests/arc/tconst_to_sink.nim new file mode 100644 index 000000000..25f659341 --- /dev/null +++ b/tests/arc/tconst_to_sink.nim @@ -0,0 +1,26 @@ +discard """ + output: '''@[(s1: "333", s2: ""), (s1: "abc", s2: "def"), (s1: "3x", s2: ""), (s1: "3x", s2: ""), (s1: "3x", s2: ""), (s1: "3x", s2: ""), (s1: "lastone", s2: "")]''' + matrix: "--gc:arc" + targets: "c cpp" +""" + +# bug #13240 + +type + Thing = object + s1: string + s2: string + +var box: seq[Thing] + +const c = [Thing(s1: "333"), Thing(s1: "abc", s2: "def")] + +for i in 0..high(c): + box.add c[i] + +for i in 0..3: + box.add Thing(s1: "3x") + +box.add Thing(s1: "lastone") + +echo box diff --git a/tests/arc/tcontrolflow.nim b/tests/arc/tcontrolflow.nim new file mode 100644 index 000000000..dbc115903 --- /dev/null +++ b/tests/arc/tcontrolflow.nim @@ -0,0 +1,118 @@ +discard """ + output: '''begin A +elif +end A +destroyed +begin false +if +end false +destroyed +begin true +if +end true +7 +##index 2 not in 0 .. 1## +true +''' + cmd: "nim c --gc:arc -d:danger $file" +""" +# we use the -d:danger switch to detect uninitialized stack +# slots more reliably (there shouldn't be any, of course). + +type + Foo = object + id: int + +proc `=destroy`(x: var Foo) = + if x.id != 0: + echo "destroyed" + x.id = 0 + +proc construct(): Foo = Foo(id: 3) + +proc elifIsEasy(cond: bool) = + echo "begin A" + if cond: + echo "if" + elif construct().id == 3: + echo "elif" + else: + echo "else" + echo "end A" + +elifIsEasy(false) + + +proc orIsHard(cond: bool) = + echo "begin ", cond + if cond or construct().id == 3: + echo "if" + else: + echo "else" + echo "end ", cond + +orIsHard(false) +orIsHard(true) + +type + Control = ref object + x: int + + MouseEvent = ref object + control: Control + button: int + +proc run(data: Control) = + var evt = MouseEvent(button: 1) + evt.control = data + if evt.button == 1: + discard + else: + return + + echo data.x + +var c = Control(x: 7) + +run(c) + +proc sysFatal(exceptn: typedesc, message: string) {.inline.} = + var buf = newStringOfCap(200) + add(buf, "##") + add(buf, message) + add(buf, "##") + echo buf + +proc ifexpr(i, a, b: int) {.compilerproc, noinline.} = + sysFatal(IndexDefect, + if b < a: "index out of bounds, the container is empty" + else: "index " & $i & " not in " & $a & " .. " & $b) + +ifexpr(2, 0, 1) + +# bug #14899 +template toSeq(): untyped = + block: + var result = @[1] + result + +proc clItems(s: seq[int]) = + assert s.len == 1 + +proc escapeCheck = + clItems(toSeq()) + +escapeCheck() + +# bug #14900 + +proc seqsEqual(a, b: string): bool = + if false: + false + else: + (var result1 = a; result1) == (var result2 = b; result2) + +# can be const or var too +let expected = "hello" + +echo seqsEqual(expected, expected) diff --git a/tests/arc/tcursor_field_obj_constr.nim b/tests/arc/tcursor_field_obj_constr.nim new file mode 100644 index 000000000..b87f734bd --- /dev/null +++ b/tests/arc/tcursor_field_obj_constr.nim @@ -0,0 +1,44 @@ +discard """ + output: '''a +b +c''' + cmd: "nim c --gc:arc $file" +""" + +# bug #18469 + +type + Edge = object + neighbor {.cursor.}: Node + + NodeObj = object + neighbors: seq[Edge] + label: string + visited: bool + Node = ref NodeObj + + Graph = object + nodes: seq[Node] + +proc `=destroy`(x: var NodeObj) = + echo x.label + `=destroy`(x.neighbors) + `=destroy`(x.label) + +proc addNode(self: var Graph; label: string): Node = + self.nodes.add(Node(label: label)) + result = self.nodes[^1] + +proc addEdge(self: Graph; source, neighbor: Node) = + source.neighbors.add(Edge(neighbor: neighbor)) + +proc main = + var graph: Graph + let nodeA = graph.addNode("a") + let nodeB = graph.addNode("b") + let nodeC = graph.addNode("c") + + graph.addEdge(nodeA, neighbor = nodeB) + graph.addEdge(nodeA, neighbor = nodeC) + +main() diff --git a/tests/arc/tcursor_on_localvar.nim b/tests/arc/tcursor_on_localvar.nim new file mode 100644 index 000000000..0f53c5efa --- /dev/null +++ b/tests/arc/tcursor_on_localvar.nim @@ -0,0 +1,161 @@ +discard """ + output: '''Section: common + Param: Floats1 +Section: local + Param: Str + Param: Bool + Param: Floats2 +destroy Foo +destroy Foo +''' + cmd: '''nim c --gc:arc $file''' +""" + +# bug #15325 + +import tables +import strutils + +const defaultSection = "***" + +type + Config* = ref object + table: OrderedTableRef[string, OrderedTable[string, string]] + +# ---------------------------------------------------------------------------------------------------------------------- +proc newConfig*(): Config = + result = new(Config) + result.table = newOrderedTable[string, OrderedTable[string, string]]() + +# ---------------------------------------------------------------------------------------------------------------------- +proc add*(self: Config, param, value, section: string) {.nosinks.} = + let s = if section == "": defaultSection else: section + + if not self.table.contains(s): + self.table[s] = initOrderedTable[string, string]() + + self.table[s][param] = value + +# ---------------------------------------------------------------------------------------------------------------------- +proc sections*(self: Config): seq[string] = + for i in self.table.keys: + let s = if i == defaultSection: "" else: i + result.add(s) + +# ---------------------------------------------------------------------------------------------------------------------- +proc params*(self: Config, section: string): seq[string] = + let s = if section == "": defaultSection else: section + + if self.table.contains(s): + for i in self.table[s].keys: + result.add(i) + +# ---------------------------------------------------------------------------------------------------------------------- +proc extract*(str, start, finish: string): string = + let startPos = str.find(start) + + if startPos < 0: + return "" + + let endPos = str.find(finish, startPos) + + if endPos < 0: + return "" + + return str[startPos + start.len() ..< endPos] + +# ---------------------------------------------------------------------------------------------------------------------- +proc loadString*(self: Config, text: string): tuple[valid: bool, errorInLine: int] {.discardable.} = + self.table.clear() + + var data = "" + + data = text + + var + actualSection = "" + lineCount = 0 + + for i in splitLines(data): + lineCount += 1 + + var line = strip(i) + + if line.len() == 0: + continue + + if line[0] == '#' or line[0] == ';': + continue + + if line[0] == '[': + let section = strip(extract(line, "[", "]")) + + if section.len() != 0: + actualSection = section + else: + self.table.clear() + return (false, lineCount) + else: + let equal = find(line, '=') + + if equal <= 0: + self.table.clear() + return (false, lineCount) + else: + let + param = strip(line[0 .. equal - 1]) + value = strip(line[equal + 1 .. ^1]) + + if param.len() == 0: + self.table.clear() + return (false, lineCount) + else: + self.add(param, value, actualSection) + + return (true, 0) + +# ---------------------------------------------------------------------------------------------------------------------- +when isMainModule: + var cfg = newConfig() + + cfg.loadString("[common]\nFloats1 = 1,2,3\n[local]\nStr = \"String...\"\nBool = true\nFloats2 = 4, 5, 6\n") + + for s in cfg.sections(): + echo "Section: " & s + + for p in cfg.params(s): + echo " Param: " & p + +# bug #16437 + +type + Foo = object + FooRef = ref Foo + + Bar = ref object + f: FooRef + +proc `=destroy`(o: var Foo) = + echo "destroy Foo" + +proc testMe(x: Bar) = + var c = (if x != nil: x.f else: nil) + assert c != nil + +proc main = + var b = Bar(f: FooRef()) + testMe(b) + +main() + +proc testMe2(x: Bar) = + var c: FooRef + c = (if x != nil: x.f else: nil) + assert c != nil + +proc main2 = + var b = Bar(f: FooRef()) + testMe2(b) + +main2() + diff --git a/tests/arc/tcursorloop.nim b/tests/arc/tcursorloop.nim new file mode 100644 index 000000000..a37a6a036 --- /dev/null +++ b/tests/arc/tcursorloop.nim @@ -0,0 +1,45 @@ +discard """ + cmd: '''nim c --gc:arc --expandArc:traverse --hint:Performance:off $file''' + nimout: ''' +--expandArc: traverse + +var + it + jt_cursor +try: + `=copy`(it, root) + block :tmp: + while ( + not (it == nil)): + if true: + echo [it.s] + `=copy`(it, it.ri) + jt_cursor = root + if ( + not (jt_cursor == nil)): + echo [jt_cursor.s] + jt_cursor = jt_cursor.ri +finally: + `=destroy`(it) +-- end of expandArc ------------------------ +''' +""" + +type + Node = ref object + le, ri: Node + s: string + +proc traverse(root: Node) = + var it = root + while it != nil: + if true: + echo it.s + it = it.ri + + var jt = root + if jt != nil: + echo jt.s + jt = jt.ri + +traverse(nil) diff --git a/tests/arc/tcustomtrace.nim b/tests/arc/tcustomtrace.nim new file mode 100644 index 000000000..5e0ecfb24 --- /dev/null +++ b/tests/arc/tcustomtrace.nim @@ -0,0 +1,240 @@ +discard """ + outputsub: '''1 +2 +3 +4 +5 +6 +89 +90 +90 +0 0 1 +0 1 2 +0 2 3 +1 0 4 +1 1 5 +1 2 6 +1 3 7 +after 6 6 +MEM 0''' +joinable: false + cmd: "nim c --gc:orc -d:useMalloc $file" + valgrind: "true" +""" + +import typetraits + +type + myseq*[T] = object + len, cap: int + data: ptr UncheckedArray[T] + +# XXX make code memory safe for overflows in '*' +var + allocCount, deallocCount: int + +proc `=destroy`*[T](x: var myseq[T]) = + if x.data != nil: + when not supportsCopyMem(T): + for i in 0..<x.len: `=destroy`(x[i]) + dealloc(x.data) + inc deallocCount + x.data = nil + x.len = 0 + x.cap = 0 + +proc `=copy`*[T](a: var myseq[T]; b: myseq[T]) = + if a.data == b.data: return + if a.data != nil: + `=destroy`(a) + #dealloc(a.data) + #inc deallocCount + #a.data = nil + a.len = b.len + a.cap = b.cap + if b.data != nil: + a.data = cast[type(a.data)](alloc(a.cap * sizeof(T))) + inc allocCount + when supportsCopyMem(T): + copyMem(a.data, b.data, a.cap * sizeof(T)) + else: + for i in 0..<a.len: + a.data[i] = b.data[i] + +proc `=sink`*[T](a: var myseq[T]; b: myseq[T]) = + if a.data != nil and a.data != b.data: + dealloc(a.data) + inc deallocCount + a.len = b.len + a.cap = b.cap + a.data = b.data + +proc `=trace`*[T](x: var myseq[T]; env: pointer) = + if x.data != nil: + for i in 0..<x.len: `=trace`(x[i], env) + +proc resize[T](s: var myseq[T]) = + let oldCap = s.cap + if oldCap == 0: s.cap = 8 + else: s.cap = (s.cap * 3) shr 1 + if s.data == nil: inc allocCount + s.data = cast[typeof(s.data)](realloc0(s.data, oldCap * sizeof(T), s.cap * sizeof(T))) + +proc reserveSlot[T](x: var myseq[T]): ptr T = + if x.len >= x.cap: resize(x) + result = addr(x.data[x.len]) + inc x.len + +template add*[T](x: var myseq[T]; y: T) = + reserveSlot(x)[] = y + +proc shrink*[T](x: var myseq[T]; newLen: int) = + assert newLen <= x.len + assert newLen >= 0 + when not supportsCopyMem(T): + for i in countdown(x.len - 1, newLen - 1): + `=destroy`(x.data[i]) + x.len = newLen + +proc grow*[T](x: var myseq[T]; newLen: int; value: T) = + if newLen <= x.len: return + assert newLen >= 0 + let oldCap = x.cap + if oldCap == 0: x.cap = newLen + else: x.cap = max(newLen, (oldCap * 3) shr 1) + if x.data == nil: inc allocCount + x.data = cast[type(x.data)](realloc0(x.data, oldCap * sizeof(T), x.cap * sizeof(T))) + for i in x.len..<newLen: + x.data[i] = value + x.len = newLen + +template default[T](t: typedesc[T]): T = + var v: T + v + +proc setLen*[T](x: var myseq[T]; newLen: int) {.deprecated.} = + if newlen < x.len: shrink(x, newLen) + else: grow(x, newLen, default(T)) + +template `[]`*[T](x: myseq[T]; i: Natural): T = + assert i < x.len + x.data[i] + +template `[]=`*[T](x: myseq[T]; i: Natural; y: T) = + assert i < x.len + x.data[i] = y + +proc createSeq*[T](elems: varargs[T]): myseq[T] = + result.cap = max(elems.len, 2) + result.len = elems.len + result.data = cast[type(result.data)](alloc0(result.cap * sizeof(T))) + inc allocCount + when supportsCopyMem(T): + copyMem(result.data, addr(elems[0]), result.cap * sizeof(T)) + else: + for i in 0..<result.len: + result.data[i] = elems[i] + +proc len*[T](x: myseq[T]): int {.inline.} = x.len + +proc main = + var s = createSeq(1, 2, 3, 4, 5, 6) + s.add 89 + s.grow s.len + 2, 90 + for i in 0 ..< s.len: + echo s[i] + + var nested = createSeq(createSeq(1, 2, 3), createSeq(4, 5, 6, 7)) + for i in 0 ..< nested.len: + for j in 0 ..< nested[i].len: + echo i, " ", j, " ", nested[i][j] + +main() +echo "after ", allocCount, " ", deallocCount + +type + Node = ref object + name: char + sccId: int + kids: myseq[Node] + rc: int + +proc edge(a, b: Node) = + inc b.rc + a.kids.add b + +proc createNode(name: char): Node = + new result + result.name = name + result.kids = createSeq[Node]() + +proc use(x: Node) = discard + +proc buildComplexGraph: Node = + # see https://en.wikipedia.org/wiki/Strongly_connected_component for the + # graph: + let a = createNode('a') + let b = createNode('b') + let c = createNode('c') + let d = createNode('d') + let e = createNode('e') + + a.edge c + c.edge b + c.edge e + b.edge a + d.edge c + e.edge d + + + let f = createNode('f') + b.edge f + e.edge f + + let g = createNode('g') + let h = createNode('h') + let i = createNode('i') + + f.edge g + f.edge i + g.edge h + h.edge i + i.edge g + + let j = createNode('j') + + h.edge j + i.edge j + + let k = createNode('k') + let l = createNode('l') + + f.edge k + k.edge l + l.edge k + k.edge j + + let m = createNode('m') + let n = createNode('n') + let p = createNode('p') + let q = createNode('q') + + m.edge n + n.edge p + n.edge q + q.edge p + p.edge m + + q.edge k + + d.edge m + e.edge n + + result = a + +proc main2 = + let g = buildComplexGraph() + +main2() +GC_fullCollect() +echo "MEM ", getOccupiedMem() diff --git a/tests/arc/tdeepcopy.nim b/tests/arc/tdeepcopy.nim new file mode 100644 index 000000000..91d93aa28 --- /dev/null +++ b/tests/arc/tdeepcopy.nim @@ -0,0 +1,67 @@ +discard """ + cmd: "nim c --gc:arc --deepcopy:on $file" + output: '''13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +13 abc +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +called deepCopy for int +0''' +""" + +type + PBinaryTree = ref object of RootObj + le, ri: PBinaryTree + value: int + +proc mainB = + var x: PBinaryTree + deepCopy(x, PBinaryTree(ri: PBinaryTree(le: PBinaryTree(value: 13)))) + + var y: string + deepCopy y, "abc" + echo x.ri.le.value, " ", y + +for i in 0..10: + mainB() + + +type + Bar[T] = object + x: T + +proc `=deepCopy`[T](b: ref Bar[T]): ref Bar[T] = + result.new + result.x = b.x + when T is int: + echo "called deepCopy for int" + else: + echo "called deepCopy for something else" + +proc main = + var dummy, c: ref Bar[int] + new(dummy) + dummy.x = 44 + + deepCopy c, dummy + +for i in 0..10: + main() + +echo getOccupiedMem() diff --git a/tests/arc/tdestroy_in_loopcond.nim b/tests/arc/tdestroy_in_loopcond.nim new file mode 100644 index 000000000..62532664d --- /dev/null +++ b/tests/arc/tdestroy_in_loopcond.nim @@ -0,0 +1,74 @@ +discard """ + output: '''400 true''' + cmd: "nim c --gc:orc $file" +""" + +type HeapQueue*[T] = object + data: seq[T] + + +proc len*[T](heap: HeapQueue[T]): int {.inline.} = + heap.data.len + +proc `[]`*[T](heap: HeapQueue[T], i: Natural): T {.inline.} = + heap.data[i] + +proc push*[T](heap: var HeapQueue[T], item: T) = + heap.data.add(item) + +proc pop*[T](heap: var HeapQueue[T]): T = + result = heap.data.pop + +proc clear*[T](heap: var HeapQueue[T]) = heap.data.setLen 0 + + +type + Future = ref object of RootObj + s: string + callme: proc() + +var called = 0 + +proc consume(f: Future) = + inc called + +proc newFuture(s: string): Future = + var r: Future + r = Future(s: s, callme: proc() = + consume r) + result = r + +var q: HeapQueue[tuple[finishAt: int64, fut: Future]] + +proc sleep(f: int64): Future = + q.push (finishAt: f, fut: newFuture("async-sleep")) + +proc processTimers = + # Pop the timers in the order in which they will expire (smaller `finishAt`). + var count = q.len + let t = high(int64) + while count > 0 and t >= q[0].finishAt: + q.pop().fut.callme() + dec count + +var futures: seq[Future] + +proc main = + for i in 1..200: + futures.add sleep(56020904056300) + futures.add sleep(56020804337500) + #futures.add sleep(2.0) + + #futures.add sleep(4.0) + + processTimers() + #q.pop()[1].callme() + #q.pop()[1].callme() + + futures.setLen 0 + + q.clear() + +main() +GC_fullCollect() +echo called, " ", getOccupiedMem() < 160 diff --git a/tests/arc/tdup.nim b/tests/arc/tdup.nim new file mode 100644 index 000000000..61f262a68 --- /dev/null +++ b/tests/arc/tdup.nim @@ -0,0 +1,71 @@ +discard """ + cmd: "nim c --mm:arc --expandArc:foo --hints:off $file" + nimout: ''' +--expandArc: foo + +var + x + :tmpD + s + :tmpD_1 +x = Ref(id: 8) +inc: + :tmpD = `=dup`(x) + :tmpD +inc: + let blitTmp = x + blitTmp +var id_1 = 777 +s = RefCustom(id_2: addr(id_1)) +inc_1 : + :tmpD_1 = `=dup_1`(s) + :tmpD_1 +inc_1 : + let blitTmp_1 = s + blitTmp_1 +-- end of expandArc ------------------------ +''' +""" + +type + Ref = ref object + id: int + + RefCustom = object + id: ptr int + +proc `=dup`(x: RefCustom): RefCustom = + result.id = x.id + +proc inc(x: sink Ref) = + inc x.id + +proc inc(x: sink RefCustom) = + inc x.id[] + + +proc foo = + var x = Ref(id: 8) + inc(x) + inc(x) + var id = 777 + var s = RefCustom(id: addr id) + inc s + inc s + +foo() + +proc foo2 = + var x = Ref(id: 8) + inc(x) + doAssert x.id == 9 + inc(x) + doAssert x.id == 10 + var id = 777 + var s = RefCustom(id: addr id) + inc s + doAssert s.id[] == 778 + inc s + doAssert s.id[] == 779 + +foo2() diff --git a/tests/arc/testfile.txt b/tests/arc/testfile.txt new file mode 100644 index 000000000..5169490be --- /dev/null +++ b/tests/arc/testfile.txt @@ -0,0 +1,2 @@ +1 x +2 y diff --git a/tests/arc/texceptions.nim b/tests/arc/texceptions.nim new file mode 100644 index 000000000..c55b463fc --- /dev/null +++ b/tests/arc/texceptions.nim @@ -0,0 +1,16 @@ +discard """ + cmd: "nim cpp --gc:arc $file" +""" + +block: # issue #13071 + type MyExcept = object of CatchableError + proc gun()= + raise newException(MyExcept, "foo:") + proc fun()= + var a = "" + try: + gun() + except Exception as e: + a = e.msg & $e.name # was segfaulting here for `nim cpp --gc:arc` + doAssert a == "foo:MyExcept" + fun() diff --git a/tests/arc/texplicit_sink.nim b/tests/arc/texplicit_sink.nim new file mode 100644 index 000000000..4b021ed62 --- /dev/null +++ b/tests/arc/texplicit_sink.nim @@ -0,0 +1,29 @@ +discard """ + output: '''de''' + cmd: '''nim c --mm:arc --expandArc:main $file''' + nimout: '''--expandArc: main + +var + a + b_cursor +try: + a = f "abc" + b_cursor = "de" + `=sink`(a, b_cursor) + echo [a] +finally: + `=destroy`(a) +-- end of expandArc ------------------------''' +""" + +# bug #20572 + +proc f(s: string): string = s + +proc main = + var a = f "abc" + var b = "de" + `=sink`(a, b) + echo a + +main() diff --git a/tests/arc/tfuncobj.nim b/tests/arc/tfuncobj.nim new file mode 100644 index 000000000..50cd9425e --- /dev/null +++ b/tests/arc/tfuncobj.nim @@ -0,0 +1,38 @@ +discard """ + outputsub: '''f1 +f2 +f3''' + cmd: "nim c --gc:orc $file" + valgrind: true +""" + +type + FuncObj = object + fn: proc (env: pointer) {.cdecl.} + env: pointer + +proc `=destroy`(x: var FuncObj) = + GC_unref(cast[RootRef](x.env)) + +proc `=copy`(x: var FuncObj, y: FuncObj) {.error.} + +# bug #18433 + +proc main = + var fs: seq[FuncObj] + + proc wrap(p: proc()) = + proc closeOver() = p() + let env = rawEnv closeOver + GC_ref(cast[RootRef](env)) + fs.add(FuncObj(fn: cast[proc(env: pointer){.cdecl.}](rawProc closeOver), env: env)) + + wrap(proc() {.closure.} = echo "f1") + wrap(proc() {.closure.} = echo "f2") + wrap(proc() {.closure.} = echo "f3") + + for a in fs: + a.fn(a.env) + +main() + diff --git a/tests/arc/thamming_orc.nim b/tests/arc/thamming_orc.nim new file mode 100644 index 000000000..777efb38e --- /dev/null +++ b/tests/arc/thamming_orc.nim @@ -0,0 +1,155 @@ +discard """ + output: '''(allocCount: 1114, deallocCount: 1112) +created 491 destroyed 491''' + cmd: "nim c --gc:orc -d:nimAllocStats $file" +""" + +# bug #18421 + +# test Nim Hamming Number Lazy List algo with reference counts and not... +# compile with "-d:release -d:danger" and test with various +# memory managment GC's, allocators, threading, etc. +# it should be guaranteed to work with zero memory leaks with `--gc:orc`... + +# compile with `-d:trace20` to trace creation and destruction of first 20 values. + +from math import log2 + +# implement our own basic BigInt so the bigints library isn't necessary... +type + BigInt = object + digits: seq[uint32] +let zeroBigInt = BigInt(digits: @[ 0'u32 ]) +let oneBigInt = BigInt(digits: @[ 1'u32 ]) + +proc shladd(bi: var BigInt; n: int; a: BigInt) = + # assume that both `bi` and `a` are sized correctly with + # msuint32 for both not containing a zero + let alen = a.digits.len + let mx = max(bi.digits.len, a.digits.len) + for i in bi.digits.len ..< mx: bi.digits.add 0'u32 + var cry = 0'u64 + for i in 0 ..< alen: + cry += (bi.digits[i].uint64 shl n) + a.digits[i].uint64 + bi.digits[i] = cry.uint32; cry = cry shr 32 + for i in alen ..< mx: + cry += bi.digits[i].uint64 shl n + bi.digits[i] = cry.uint32; cry = cry shr 32 + if cry > 0'u64: + bi.digits.add cry.uint32 + +proc `$`(x: BigInt): string = + if x.digits.len == 0 or (x.digits.len == 1 and x.digits[0] == 0'u32): + return "0" + result = ""; var n = x; var msd = n.digits.high + while msd >= 0: + if n.digits[msd] == 0'u32: msd.dec; continue + var brw = 0.uint64 + for i in countdown(msd, 0): + let dvdnd = n.digits[i].uint64 + (brw shl 32) + let q = dvdnd div 10'u64; brw = dvdnd - q * 10'u64 + n.digits[i] = q.uint32 + result &= $brw + for i in 0 .. result.high shr 1: # reverse result string in place + let tmp = result[^(i + 1)] + result[^(i + 1)] = result[i] + result[i] = tmp + +type TriVal = (uint32, uint32, uint32) +type LogRep = (float64, TriVal) +type LogRepf = proc(x: LogRep): LogRep +const one: LogRep = (0.0'f64, (0'u32, 0'u32, 0'u32)) +proc `<`(me: LogRep, othr: LogRep): bool = me[0] < othr[0] + +proc convertTriVal2BigInt(tpl: TriVal): BigInt = + result = oneBigInt + let (x2, x3, x5) = tpl + for _ in 1 .. x2: result.shladd 1, zeroBigInt + for _ in 1 .. x3: result.shladd 1, result + for _ in 1 .. x5: result.shladd 2, result + +const lb2 = 1.0'f64 +const lb3 = 3.0'f64.log2 +const lb5 = 5.0'f64.log2 + +proc mul2(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb2, (x2 + 1, x3, x5)) + +proc mul3(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb3, (x2, x3 + 1, x5)) + +proc mul5(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb5, (x2, x3, x5 + 1)) + +type + LazyListObj = object + hd: LogRep + tlf: proc(): LazyList {.closure.} + tl: LazyList + LazyList = ref LazyListObj + +var destroyed = 0 + +proc `=destroy`(ll: var LazyListObj) = + destroyed += 1 + if ll.tlf == nil and ll.tl == nil: return + + when defined(trace20): + echo "destroying: ", (destroyed, ll.hd[1].convertTriVal2BigInt) + if ll.tlf != nil: ll.tlf.`=destroy` + if ll.tl != nil: ll.tl.`=destroy` + #wasMoved(ll) + +proc rest(ll: LazyList): LazyList = # not thread-safe; needs lock on thunk + if ll.tlf != nil: ll.tl = ll.tlf(); ll.tlf = nil + ll.tl + +var created = 0 +iterator hammings(until: int): TriVal = + proc merge(x, y: LazyList): LazyList = + let xh = x.hd; let yh = y.hd; created += 1 + when defined(trace20): + echo "merge create: ", (created - 1, (if xh < yh: xh else: yh)[1].convertTriVal2BigInt) + if xh < yh: LazyList(hd: xh, tlf: proc(): auto = merge x.rest, y) + else: LazyList(hd: yh, tlf: proc(): auto = merge x, y.rest) + proc smult(mltf: LogRepf; s: LazyList): LazyList = + proc smults(ss: LazyList): LazyList = + when defined(trace20): + echo "mult create: ", (created, ss.hd.mltf[1].convertTriVal2BigInt) + created += 1; LazyList(hd: ss.hd.mltf, tlf: proc(): auto = ss.rest.smults) + s.smults + proc unnsm(s: LazyList, mltf: LogRepf): LazyList = + var r: LazyList = nil + when defined(trace20): + echo "first create: ", (created, one[1].convertTriVal2BigInt) + let frst = LazyList(hd: one, tlf: proc(): LazyList = r); created += 1 + r = if s == nil: smult(mltf, frst) else: s.merge smult(mltf, frst) + r + yield one[1] + var hmpll: LazyList = ((nil.unnsm mul5).unnsm mul3).unnsm mul2 + for _ in 2 .. until: + yield hmpll.hd[1]; hmpll = hmpll.rest # almost forever + +proc main = + var s = "" + for h in hammings(20): s &= $h.convertTrival2BigInt & " " + doAssert s == "1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 ", + "Algorithmic error finding first 20 Hamming numbers!!!" + + when not defined(trace20): + var lsth: TriVal + for h in hammings(200): lsth = h + doAssert $lsth.convertTriVal2BigInt == "16200", + "Algorithmic error finding 200th Hamming number!!!" + +let mem = getOccupiedMem() +main() +GC_FullCollect() +let mb = getOccupiedMem() - mem +doAssert mb == 0, "Found memory leak of " & $mb & " bytes!!!" + +echo getAllocStats() +echo "created ", created, " destroyed ", destroyed diff --git a/tests/arc/thamming_thinout.nim b/tests/arc/thamming_thinout.nim new file mode 100644 index 000000000..65c34ef49 --- /dev/null +++ b/tests/arc/thamming_thinout.nim @@ -0,0 +1,183 @@ +discard """ + cmd: "nim c --gc:orc -d:nimThinout -r $file" + output: '''The first 20 hammings are: 1 2 3 4 5 MEM IS 0''' +""" + +# test Nim Hamming Number Lazy List algo with reference counts and not... +# compile with "-d:release -d:danger" and test with various +# memory managment GC's, allocators, threading, etc. + +from times import epochTime +from math import log2 + +# implement our own basic BigInt so the bigints library isn't necessary... +type + BigInt = object + digits: seq[uint32] +let zeroBigInt = BigInt(digits: @[ 0'u32 ]) +let oneBigInt = BigInt(digits: @[ 1'u32 ]) + +proc shladd(bi: var BigInt; n: int; a: BigInt) = + var cry = 0'u64 + for i in 0 ..< min(bi.digits.len, a.digits.len): + cry += (bi.digits[i].uint64 shl n) + a.digits[i].uint64 + bi.digits[i] = cry.uint32 + cry = cry shr 32 + if cry > 0'u64: + bi.digits.add cry.uint32 + +proc `$`(x: BigInt): string = + if x.digits.len == 0 or (x.digits.len == 1 and x.digits[0] == 0'u32): + return "0" + var n = x + var msd = n.digits.high + result = "" + while msd >= 0: + if n.digits[msd] == 0'u32: msd.dec; continue + var brw = 0.uint64 + for i in countdown(msd, 0): + let dvdnd = n.digits[i].uint64 + (brw shl 32) + let q = dvdnd div 10'u64; brw = dvdnd - q*10'u64; n.digits[i] = q.uint32 + result &= $brw + for i in 0 .. result.high shr 1: + let tmp = result[^(i + 1)] + result[^(i + 1)] = result[i] + result[i] = tmp + +proc convertTrival2BigInt(tpl: (uint32, uint32, uint32)): BigInt = + result = oneBigInt + let (x2, x3, x5) = tpl + for _ in 1 .. x2: result.shladd 1, zeroBigInt + for _ in 1 .. x3: result.shladd 1, result + for _ in 1 .. x5: result.shladd 2, result + +type LogRep = (float64, (uint32, uint32, uint32)) +type LogRepf = proc(x: LogRep): LogRep +const one: LogRep = (0.0f64, (0u32, 0u32, 0u32)) +proc `<`(me: LogRep, othr: LogRep): bool = me[0] < othr[0] + +const lb2 = 1.0'f64 +const lb3 = 3.0'f64.log2 +const lb5 = 5.0'f64.log2 + +proc mul2(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb2, (x2 + 1, x3, x5)) + +proc mul3(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb3, (x2, x3 + 1, x5)) + +proc mul5(me: LogRep): LogRep = + let (lr, tpl) = me; let (x2, x3, x5) = tpl + (lr + lb5, (x2, x3, x5 + 1)) + +type + LazyList = ref object + hd: LogRep + tlf: proc(): LazyList {.closure.} + tl: LazyList + +proc rest(ll: LazyList): LazyList = # not thread-safe; needs lock on thunk + if ll.tlf != nil: + ll.tl = ll.tlf() + ll.tlf = nil + ll.tl + +iterator hamming(until: int): (uint32, uint32, uint32) = + proc merge(x, y: LazyList): LazyList = + let xh = x.hd + let yh = y.hd + if xh < yh: LazyList(hd: xh, tlf: proc(): auto = merge x.rest, y) + else: LazyList(hd: yh, tlf: proc(): auto = merge x, y.rest) + proc smult(mltf: LogRepf; s: LazyList): LazyList = + proc smults(ss: LazyList): LazyList = + LazyList(hd: ss.hd.mltf, tlf: proc(): auto = ss.rest.smults) + s.smults + proc unnsm(s: LazyList, mltf: LogRepf): LazyList = + var r: LazyList = nil + let frst = LazyList(hd: one, tlf: proc(): LazyList = r) + r = if s == nil: smult mltf, frst else: s.merge smult(mltf, frst) + r + var hmpll: LazyList = ((nil.unnsm mul5).unnsm mul3).unnsm mul2 + # var hmpll: LazyList = nil; for m in [mul5, mul3, mul2]: echo one.m # ; hmpll = unnsm(hmpll, m) + yield one[1] + var cnt = 1 + while hmpll != nil: + yield hmpll.hd[1] + hmpll = hmpll.rest # almost forever + cnt.inc + if cnt > until: break + #when declared(thinout): + thinout(hmpll) + +proc main = + stdout.write "The first 20 hammings are: " + for h in hamming(4): + write stdout, h.convertTrival2BigInt, " " + + for h in hamming(200): + discard h.convertTrival2BigInt + +let mem = getOccupiedMem() +main() +echo "MEM IS ", getOccupiedMem() - mem + +#[ +result = (smults, :envP.:up)(rest(:envP.ss2)) + +proc anon = + var + :tmpD_284230 + :tmpD_284233 + :tmpD_284236 + try: + `=sink_283407`(result_283502, + `=sink_283927`(:tmpD_284236, (smults_283495, + wasMoved_284234(:tmpD_284233) + `=_284014`(:tmpD_284233, :envP_283898.:up_283899) + :tmpD_284233)) + :tmpD_284236( + `=sink_283407`(:tmpD_284230, rest_283366(:envP_283898.ss2_-283497)) + :tmpD_284230)) + finally: + `=destroy_283914`(:tmpD_284236) + `=destroy_283388`(:tmpD_284230) + +proc smuls(ss: LazyList_283350; :envP_283891): LazyList_283350 = + var :env_283913 + try: + `=destroy_283951`(:env_283913) + internalNew_43643(:env_283913) + `=_283401`(:env_283913.ss2_-283497, ss_283497) + :env_283913.:up_283899 = :envP_283891 + `=sink_283407`(result_283498, LazyList_283350(hd_283353: :envP_283891.mltf1_-283492( + :env_283913.ss2_-283497.hd_283353), tlf_283356: (:anonymous_283499, + let blitTmp_284227 = :env_283913 + wasMoved_284228(:env_283913) + blitTmp_284227))) + finally: + `=destroy_283951`(:env_283913) + +proc smul = + var + :env_283969 + :tmpD_284220 + try: + `=destroy_284008`(:env_283969) + internalNew_43643(:env_283969) + `=_283976`(:env_283969.mltf1_-283492, mltf_283492) + proc smults(ss: LazyList_283350; :envP_283891): LazyList_283350 = + result_283498 = LazyList_283350(hd_283353: mltf_283492(ss_283497.hd_283353), tlf_283356: proc ( + :envP_283898): auto_43100 = result_283502 = smults_283495(rest_283366(ss_283497))) + + `=sink_283407`(result_283494, + `=sink_283927`(:tmpD_284220, (smults_283495, + let blitTmp_284218 = :env_283969 + wasMoved_284219(:env_283969) + blitTmp_284218)) + :tmpD_284220(s_283493)) + finally: + `=destroy_283914`(:tmpD_284220) + `=destroy_284008`(:env_283969) +]# diff --git a/tests/arc/thard_alignment.nim b/tests/arc/thard_alignment.nim new file mode 100644 index 000000000..30cfddb05 --- /dev/null +++ b/tests/arc/thard_alignment.nim @@ -0,0 +1,148 @@ +discard """ +disabled: "arm64" +cmd: "nim c --mm:arc -u:nimPreviewNonVarDestructor $file" +output: "y" +""" + +# TODO: fixme: investigate why it failed with non-var destructors + +{.passC: "-march=native".} + +proc isAlignedCheck(p: pointer, alignment: int) = + doAssert (cast[uint](p) and uint(alignment - 1)) == 0 + +proc isAlignedCheck[T](p: ref T, alignment: int) = + isAlignedCheck(cast[pointer](p), alignment) + +type + m256d {.importc: "__m256d", header: "immintrin.h".} = object + +proc set1(x: float): m256d {.importc: "_mm256_set1_pd", header: "immintrin.h".} +func `+`(a,b: m256d): m256d {.importc: "_mm256_add_pd", header: "immintrin.h".} +proc `$`(a: m256d): string = + result = $(cast[ptr float](a.addr)[]) + + +var res: seq[seq[m256d]] + +for _ in 1..1000: + var x = newSeq[m256d](1) + x[0] = set1(1.0) # test if operation causes segfault + isAlignedCheck(x[0].addr, alignof(m256d)) + res.add x + +var res2: seq[m256d] +for i in 1..10000: + res2.setLen(res2.len + 1) # check if realloc works + isAlignedCheck(res2[0].addr, alignof(m256d)) + +proc lambdaGen(a, b: float, z: ref m256d) : auto = + var x1 = new(m256d) + var x2 = new(m256d) + isAlignedCheck(x1, alignof(m256d)) + isAlignedCheck(x2, alignof(m256d)) + x1[] = set1(2.0 + a) + x2[] = set1(-23.0 - b) + let capturingLambda = proc(x: ref m256d): ref m256d = + var cc = new(m256d) + var bb = new(m256d) + isAlignedCheck(x1, alignof(m256d)) + isAlignedCheck(x2, alignof(m256d)) + isAlignedCheck(cc, alignof(m256d)) + isAlignedCheck(bb, alignof(m256d)) + isAlignedCheck(z, alignof(m256d)) + + cc[] = x1[] + x1[] + z[] + bb[] = x2[] + set1(12.5) + z[] + + result = new(m256d) + isAlignedCheck(result, alignof(m256d)) + result[] = cc[] + bb[] + x[] + + return capturingLambda + +var xx = new(m256d) +xx[] = set1(10) +isAlignedCheck(xx, alignOf(m256d)) + +let f1 = lambdaGen(2.0 , 2.221, xx) +let f2 = lambdaGen(-1.226 , 3.5, xx) +isAlignedCheck(f1(xx), alignOf(m256d)) +isAlignedCheck(f2(xx), alignOf(m256d)) + + +#----------------------------------------------------------------------------- + +type + MyAligned = object of RootObj + a{.align: 128.}: float + + +var f: MyAligned +isAlignedCheck(f.addr, MyAligned.alignOf) + +var fref = new(MyAligned) +isAlignedCheck(fref, MyAligned.alignOf) + +var fs: seq[MyAligned] +var fr: seq[RootRef] + +for i in 0..1000: + fs.add MyAligned() + isAlignedCheck(fs[^1].addr, MyAligned.alignOf) + fs[^1].a = i.float + + fr.add new(MyAligned) + isAlignedCheck(fr[^1], MyAligned.alignOf) + ((ref MyAligned)fr[^1])[].a = i.float + +for i in 0..1000: + doAssert(fs[i].a == i.float) + doAssert(((ref MyAligned)fr[i]).a == i.float) + + +proc lambdaTest2(a: MyAligned, z: ref MyAligned): auto = + var x1: MyAligned + x1.a = a.a + z.a + var x2: MyAligned + x2.a = a.a - z.a + let capturingLambda = proc(x: MyAligned): MyAligned = + var cc: MyAligned + var bb: MyAligned + isAlignedCheck(x1.addr, MyAligned.alignOf) + isAlignedCheck(x2.addr, MyAligned.alignOf) + isAlignedCheck(cc.addr, MyAligned.alignOf) + isAlignedCheck(bb.addr, MyAligned.alignOf) + isAlignedCheck(z, MyAligned.alignOf) + + cc.a = x1.a + x1.a + z.a + bb.a = x2.a - z.a + + isAlignedCheck(result.addr, MyAligned.alignOf) + result.a = cc.a + bb.a + x2.a + + return capturingLambda + + +let q1 = lambdaTest2(MyAligned(a: 1.0), (ref MyAligned)(a: 2.0)) +let q2 = lambdaTest2(MyAligned( a: -1.0), (ref MyAligned)(a: -2.0)) + +isAlignedCheck(rawEnv(q1), MyAligned.alignOf) +isAlignedCheck(rawEnv(q2), MyAligned.alignOf) +discard q1(MyAligned(a: 1.0)) +discard q2(MyAligned(a: -1.0)) + + +#----------------------------------------------------------------------------- + +block: + var s: seq[seq[MyAligned]] + for len in 0..128: + s.add newSeq[MyAligned](len) + for i in 0..<len: + s[^1][i] = MyAligned(a: 1.0) + + if len > 0: + isAlignedCheck(s[^1][0].addr, MyAligned.alignOf) + +echo "y" diff --git a/tests/arc/thavlak_orc_stress.nim b/tests/arc/thavlak_orc_stress.nim new file mode 100644 index 000000000..862e1a097 --- /dev/null +++ b/tests/arc/thavlak_orc_stress.nim @@ -0,0 +1,414 @@ +discard """ + cmd: "nim c --gc:orc -d:useMalloc -d:nimStressOrc $file" + valgrind: "leaks" + output: "done" +""" + +import tables +import sets +import net + +type + BasicBlock = object + inEdges: seq[ref BasicBlock] + outEdges: seq[ref BasicBlock] + name: int + +proc newBasicBlock(name: int): ref BasicBlock = + new(result) + result.inEdges = newSeq[ref BasicBlock]() + result.outEdges = newSeq[ref BasicBlock]() + result.name = name + +proc hash(x: ref BasicBlock): int {.inline.} = + result = x.name + +type + BasicBlockEdge = object + fr: ref BasicBlock + to: ref BasicBlock + + Cfg = object + basicBlockMap: Table[int, ref BasicBlock] + edgeList: seq[BasicBlockEdge] + startNode: ref BasicBlock + +proc newCfg(): Cfg = + result.basicBlockMap = initTable[int, ref BasicBlock]() + result.edgeList = newSeq[BasicBlockEdge]() + +proc createNode(self: var Cfg, name: int): ref BasicBlock = + #var node: ref BasicBlock + result = self.basicBlockMap.getOrDefault(name) + if result == nil: + result = newBasicBlock(name) + self.basicBlockMap.add name, result + + if self.startNode == nil: + self.startNode = result + +proc addEdge(self: var Cfg, edge: BasicBlockEdge) = + self.edgeList.add(edge) + +proc getNumNodes(self: Cfg): int = + self.basicBlockMap.len + +proc newBasicBlockEdge(cfg: var Cfg, fromName: int, toName: int) = + var newEdge = BasicBlockEdge() + newEdge.fr = cfg.createNode(fromName) + newEdge.to = cfg.createNode(toName) + newEdge.fr.outEdges.add(newEdge.to) + newEdge.to.inEdges.add(newEdge.fr) + cfg.addEdge(newEdge) + +type + SimpleLoop = object + basicBlocks: seq[ref BasicBlock] # TODO: set here + children: seq[ref SimpleLoop] # TODO: set here + parent: ref SimpleLoop + header: ref BasicBlock + isRoot: bool + isReducible: bool + counter: int + nestingLevel: int + depthLevel: int + +proc newSimpleLoop(): ref SimpleLoop = + new(result) + result.basicBlocks = newSeq[ref BasicBlock]() + result.children = newSeq[ref SimpleLoop]() + result.parent = nil + result.header = nil + result.isRoot = false + result.isReducible = true + result.counter = 0 + result.nestingLevel = 0 + result.depthLevel = 0 + +proc addNode(self: ref SimpleLoop, bb: ref BasicBlock) = + self.basicBlocks.add bb + +proc addChildLoop(self: ref SimpleLoop, loop: ref SimpleLoop) = + self.children.add loop + +proc setParent(self: ref SimpleLoop, parent: ref SimpleLoop) = + self.parent = parent + self.parent.addChildLoop(self) + +proc setHeader(self: ref SimpleLoop, bb: ref BasicBlock) = + self.basicBlocks.add(bb) + self.header = bb + +proc setNestingLevel(self: ref SimpleLoop, level: int) = + self.nestingLevel = level + if level == 0: self.isRoot = true + +var loop_counter: int = 0 + +type + Lsg = object + loops: seq[ref SimpleLoop] + root: ref SimpleLoop + +proc createNewLoop(self: var Lsg): ref SimpleLoop = + var s = newSimpleLoop() + loop_counter += 1 + s.counter = loop_counter + s + +proc addLoop(self: var Lsg, l: ref SimpleLoop) = + self.loops.add l + +proc newLsg(): Lsg = + result.loops = newSeq[ref SimpleLoop]() + result.root = result.createNewLoop() + result.root.setNestingLevel(0) + result.addLoop(result.root) + +proc getNumLoops(self: Lsg): int = + self.loops.len + +type + UnionFindNode = object + parent: ref UnionFindNode + bb: ref BasicBlock + l: ref SimpleLoop + dfsNumber: int + +proc newUnionFindNode(): ref UnionFindNode = + new(result) + result.parent = nil + result.bb = nil + result.l = nil + result.dfsNumber = 0 + +proc initNode(self: ref UnionFindNode, bb: ref BasicBlock, dfsNumber: int) = + self.parent = self + self.bb = bb + self.dfsNumber = dfsNumber + +proc findSet(self: ref UnionFindNode): ref UnionFindNode = + var nodeList = newSeq[ref UnionFindNode]() + var node = self + + while node != node.parent: + var parent = node.parent + if parent != parent.parent: nodeList.add node + node = parent + + for iter in nodeList: iter.parent = node.parent + node + +proc union(self: ref UnionFindNode, unionFindNode: ref UnionFindNode) = + self.parent = unionFindNode + + +const + BB_NONHEADER = 1 # a regular BB + BB_REDUCIBLE = 2 # reducible loop + BB_SELF = 3 # single BB loop + BB_IRREDUCIBLE = 4 # irreducible loop + BB_DEAD = 5 # a dead BB + + # # Marker for uninitialized nodes. + UNVISITED = -1 + + # # Safeguard against pathologic algorithm behavior. + MAXNONBACKPREDS = (32 * 1024) + +type + HavlakLoopFinder = object + cfg: Cfg + lsg: Lsg + +proc newHavlakLoopFinder(cfg: sink Cfg, lsg: sink Lsg): HavlakLoopFinder = + result.cfg = cfg + result.lsg = lsg + +proc isAncestor(w: int, v: int, last: seq[int]): bool = + w <= v and v <= last[w] + +proc dfs(currentNode: ref BasicBlock, nodes: var seq[ref UnionFindNode], number: var Table[ref BasicBlock, int], last: var seq[int], current: int): int = + nodes[current].initNode(currentNode, current) + number[currentNode] = current + + var lastid = current + for target in currentNode.outEdges: + if number[target] == UNVISITED: + lastid = dfs(target, nodes, number, last, lastid + 1) + + last[number[currentNode]] = lastid + return lastid + +proc findLoops(self: var HavlakLoopFinder): int = + var startNode = self.cfg.startNode + if startNode == nil: return 0 + var size = self.cfg.getNumNodes + + var nonBackPreds = newSeq[HashSet[int]]() + var backPreds = newSeq[seq[int]]() + var number = initTable[ref BasicBlock, int]() + var header = newSeq[int](size) + var types = newSeq[int](size) + var last = newSeq[int](size) + var nodes = newSeq[ref UnionFindNode]() + + for i in 1..size: + nonBackPreds.add initHashSet[int](1) + backPreds.add newSeq[int]() + nodes.add newUnionFindNode() + + # Step a: + # - initialize all nodes as unvisited. + # - depth-first traversal and numbering. + # - unreached BB's are marked as dead. + # + for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED + discard dfs(startNode, nodes, number, last, 0) + + # Step b: + # - iterate over all nodes. + # + # A backedge comes from a descendant in the DFS tree, and non-backedges + # from non-descendants (following Tarjan). + # + # - check incoming edges 'v' and add them to either + # - the list of backedges (backPreds) or + # - the list of non-backedges (nonBackPreds) + # + for w in 0 ..< size: + header[w] = 0 + types[w] = BB_NONHEADER + + var nodeW = nodes[w].bb + if nodeW != nil: + for nodeV in nodeW.inEdges: + var v = number[nodeV] + if v != UNVISITED: + if isAncestor(w, v, last): + backPreds[w].add v + else: + nonBackPreds[w].incl v + else: + types[w] = BB_DEAD + + # Start node is root of all other loops. + header[0] = 0 + + # Step c: + # + # The outer loop, unchanged from Tarjan. It does nothing except + # for those nodes which are the destinations of backedges. + # For a header node w, we chase backward from the sources of the + # backedges adding nodes to the set P, representing the body of + # the loop headed by w. + # + # By running through the nodes in reverse of the DFST preorder, + # we ensure that inner loop headers will be processed before the + # headers for surrounding loops. + + for w in countdown(size - 1, 0): + # this is 'P' in Havlak's paper + var nodePool = newSeq[ref UnionFindNode]() + + var nodeW = nodes[w].bb + if nodeW != nil: # dead BB + # Step d: + for v in backPreds[w]: + if v != w: + nodePool.add nodes[v].findSet + else: + types[w] = BB_SELF + + # Copy nodePool to workList. + # + var workList = newSeq[ref UnionFindNode]() + for x in nodePool: workList.add x + + if nodePool.len != 0: types[w] = BB_REDUCIBLE + + # work the list... + # + while workList.len > 0: + var x = workList[0] + workList.del(0) + + # Step e: + # + # Step e represents the main difference from Tarjan's method. + # Chasing upwards from the sources of a node w's backedges. If + # there is a node y' that is not a descendant of w, w is marked + # the header of an irreducible loop, there is another entry + # into this loop that avoids w. + # + + # The algorithm has degenerated. Break and + # return in this case. + # + var nonBackSize = nonBackPreds[x.dfsNumber].len + if nonBackSize > MAXNONBACKPREDS: return 0 + + for iter in nonBackPreds[x.dfsNumber]: + var y = nodes[iter] + var ydash = y.findSet + + if not isAncestor(w, ydash.dfsNumber, last): + types[w] = BB_IRREDUCIBLE + nonBackPreds[w].incl ydash.dfsNumber + else: + if ydash.dfsNumber != w and not nodePool.contains(ydash): + workList.add ydash + nodePool.add ydash + + # Collapse/Unionize nodes in a SCC to a single node + # For every SCC found, create a loop descriptor and link it in. + # + if (nodePool.len > 0) or (types[w] == BB_SELF): + var l = self.lsg.createNewLoop + + l.setHeader(nodeW) + l.isReducible = types[w] != BB_IRREDUCIBLE + + # At this point, one can set attributes to the loop, such as: + # + # the bottom node: + # iter = backPreds(w).begin(); + # loop bottom is: nodes(iter).node; + # + # the number of backedges: + # backPreds(w).size() + # + # whether this loop is reducible: + # types(w) != BB_IRREDUCIBLE + # + nodes[w].l = l + + for node in nodePool: + # Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.union(nodes[w]) + + # Nested loops are not added, but linked together. + var node_l = node.l + if node_l != nil: + node_l.setParent(l) + else: + l.addNode(node.bb) + + self.lsg.addLoop(l) + + result = self.lsg.getNumLoops + + +type + LoopTesterApp = object + cfg: Cfg + lsg: Lsg + +proc newLoopTesterApp(): LoopTesterApp = + result.cfg = newCfg() + result.lsg = newLsg() + +proc buildDiamond(self: var LoopTesterApp, start: int): int = + var bb0 = start + newBasicBlockEdge(self.cfg, bb0, bb0 + 1) + newBasicBlockEdge(self.cfg, bb0, bb0 + 2) + newBasicBlockEdge(self.cfg, bb0 + 1, bb0 + 3) + newBasicBlockEdge(self.cfg, bb0 + 2, bb0 + 3) + result = bb0 + 3 + +proc buildConnect(self: var LoopTesterApp, start1: int, end1: int) = + newBasicBlockEdge(self.cfg, start1, end1) + +proc buildStraight(self: var LoopTesterApp, start: int, n: int): int = + for i in 0..n-1: + self.buildConnect(start + i, start + i + 1) + result = start + n + +proc buildBaseLoop(self: var LoopTesterApp, from1: int): int = + var header = self.buildStraight(from1, 1) + var diamond1 = self.buildDiamond(header) + var d11 = self.buildStraight(diamond1, 1) + var diamond2 = self.buildDiamond(d11) + var footer = self.buildStraight(diamond2, 1) + + self.buildConnect(diamond2, d11) + self.buildConnect(diamond1, header) + self.buildConnect(footer, from1) + result = self.buildStraight(footer, 1) + +proc run(self: var LoopTesterApp) = + discard self.cfg.createNode(0) + discard self.buildBaseLoop(0) + discard self.cfg.createNode(1) + self.buildConnect(0, 2) + + for i in 1..8: + # yes, it's 8 and it's correct. + var h = newHavlakLoopFinder(self.cfg, newLsg()) + discard h.findLoops() + + echo "done" + +var l = newLoopTesterApp() +l.run() diff --git a/tests/arc/theavy_recursion.nim b/tests/arc/theavy_recursion.nim new file mode 100644 index 000000000..9813331f4 --- /dev/null +++ b/tests/arc/theavy_recursion.nim @@ -0,0 +1,43 @@ +discard """ + output: "yay" + cmd: "nim c --gc:arc $file" +""" + +# bug #15122 + +import tables + +type + BENodeKind* = enum + tkEof, + tkBytes, + tkList, + tkDict + + BENode* = object + case kind: BENodeKind + of tkBytes: strVal: string + of tkList: listVal: seq[BENode] + of tkDict: dictVal*: Table[string, BENode] + else: + discard + +proc unused(s: string): BENode = + # bad: + result = BENode(kind: tkBytes, strVal: "abc") + +proc main = + var data = { + "examples": { + "values": BENode( + kind: tkList, + listVal: @[BENode(kind: tkBytes, strVal: "test")] + ) + }.toTable() + }.toTable() + + # For ARC listVal is empty for some reason + doAssert data["examples"]["values"].listVal[0].strVal == "test" + +main() +echo "yay" diff --git a/tests/arc/timportedobj.nim b/tests/arc/timportedobj.nim new file mode 100644 index 000000000..9a88a9468 --- /dev/null +++ b/tests/arc/timportedobj.nim @@ -0,0 +1,14 @@ +discard """ + cmd: "nim c --gc:arc $file" + action: "compile" +""" + +# bug #13269 + +import posix +proc foo*() = + var last = newSeq[Stat]() + var next = last + for i in 0..3: + last = next +foo() diff --git a/tests/arc/titeration_doesnt_copy.nim b/tests/arc/titeration_doesnt_copy.nim new file mode 100644 index 000000000..e510a6eff --- /dev/null +++ b/tests/arc/titeration_doesnt_copy.nim @@ -0,0 +1,67 @@ +discard """ + output: "true" +""" + +type + Idx = object + i: int + Node = object + n: int + next: seq[Idx] + FooBar = object + s: seq[Node] + +proc `=copy`(dest: var Idx; source: Idx) {.error.} +proc `=copy`(dest: var Node; source: Node) {.error.} +proc `=copy`(dest: var FooBar; source: FooBar) {.error.} + +proc doSomething(ss: var seq[int], s: FooBar) = + for i in 0 .. s.s.len-1: + for elm in items s.s[i].next: + ss.add s.s[elm.i].n + +when isMainModule: + const foo = FooBar(s: @[Node(n: 1, next: @[Idx(i: 0)])]) + var ss: seq[int] + doSomething(ss, foo) + echo ss == @[1] + +from sequtils import mapIt +from strutils import join + +proc toBinSeq*(b: uint8): seq[uint8] = + ## Return binary sequence from each bits of uint8. + runnableExamples: + from sequtils import repeat + doAssert 0'u8.toBinSeq == 0'u8.repeat(8) + doAssert 0b1010_1010.toBinSeq == @[1'u8, 0, 1, 0, 1, 0, 1, 0] + result = @[] + var c = b + for i in 1..8: + result.add (uint8(c and 0b1000_0000) shr 7) + c = c shl 1 + +proc toBinString*(data: openArray[uint8], col: int): string = + ## Return binary string from each bits of uint8. + runnableExamples: + doAssert @[0b0000_1111'u8, 0b1010_1010].toBinString(8) == "0000111110101010" + doAssert @[0b1000_0000'u8, 0b0000_0000].toBinString(1) == "10" + result = "" + for b in items data.mapIt(it.toBinSeq.mapIt(it.`$`[0].char)): + for i, c in b: + if i < col: + result.add c + +doAssert @[0b0000_1111'u8, 0b1010_1010].toBinString(8) == "0000111110101010" +doAssert @[0b1000_0000'u8, 0b0000_0000].toBinString(1) == "10" + +block: # bug #23982 + iterator `..`(a, b: ptr int16): ptr int16 = discard + var a: seq[int16] #; let p = a[0].addr + var b: seq[ptr int16] + + try: + for x in a[0].addr .. b[1]: # `p .. b[1]` works + discard + except IndexDefect: + discard diff --git a/tests/arc/tkeys_lent.nim b/tests/arc/tkeys_lent.nim new file mode 100644 index 000000000..2c92350b1 --- /dev/null +++ b/tests/arc/tkeys_lent.nim @@ -0,0 +1,17 @@ +discard """ + output: '''{"string": 2}''' + cmd: "nim c --gc:orc $file" +""" + +import tables + +proc use(x: int) = echo x + +proc main = + var tab = {"string": 1}.toTable + for keyAAA in tab.keys(): + template alias(): untyped = tab[keyAAA] + alias() = 2 + echo tab + +main() diff --git a/tests/arc/tmalloc.nim b/tests/arc/tmalloc.nim new file mode 100644 index 000000000..1d72646c8 --- /dev/null +++ b/tests/arc/tmalloc.nim @@ -0,0 +1,16 @@ +discard """ + matrix: "--mm:arc -d:useMalloc; --mm:arc" +""" + +block: # bug #22058 + template foo(): auto = + {.noSideEffect.}: + newSeq[byte](1) + + type V = object + v: seq[byte] + + proc bar(): V = + V(v: foo()) + + doAssert bar().v == @[byte(0)] diff --git a/tests/arc/tmarshal.nim b/tests/arc/tmarshal.nim new file mode 100644 index 000000000..f7ec6e2c5 --- /dev/null +++ b/tests/arc/tmarshal.nim @@ -0,0 +1,140 @@ +discard """ + cmd: "nim c --gc:orc $file" + output: ''' +{"age": 12, "bio": "Я Cletus", "blob": [65, 66, 67, 128], "name": "Cletus"} +true +true +alpha 100 +omega 200 +0''' +""" + +import marshal + +template testit(x) = discard $$to[typeof(x)]($$x) + +var x: array[0..4, array[0..4, string]] = [ + ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], + ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], + ["test", "1", "2", "3", "4"]] + +proc blockA = + testit(x) + var test2: tuple[name: string, s: int] = ("tuple test", 56) + testit(test2) + +blockA() + +type + TE = enum + blah, blah2 + + TestObj = object + test, asd: int + case test2: TE + of blah: + help: string + else: + discard + + PNode = ref TNode + TNode = object + next, prev: PNode + data: string + +proc buildList(): PNode = + new(result) + new(result.next) + new(result.prev) + result.data = "middle" + result.next.data = "next" + result.prev.data = "prev" + result.next.next = result.prev + result.next.prev = result + result.prev.next = result + result.prev.prev = result.next + +proc blockB = + var test3: TestObj + test3.test = 42 + test3.test2 = blah + testit(test3) + + var test4: ref tuple[a, b: string] + new(test4) + test4.a = "ref string test: A" + test4.b = "ref string test: B" + testit(test4) + + var test5 = @[(0,1),(2,3),(4,5)] + testit(test5) + + var test7 = buildList() + testit(test7) + + var test6: set[char] = {'A'..'Z', '_'} + testit(test6) + +blockB() + +# bug #1352 + +type + Entity = object of RootObj + name: string + + Person = object of Entity + age: int + bio: string + blob: string + +proc blockC = + var instance1 = Person(name: "Cletus", age: 12, + bio: "Я Cletus", + blob: "ABC\x80") + echo($$instance1) + echo(to[Person]($$instance1).bio == instance1.bio) # true + echo(to[Person]($$instance1).blob == instance1.blob) # true + +blockC() + +# bug 5757 + +type + Something = object + x: string + y: int + +proc blockD = + var data1 = """{"x": "alpha", "y": 100}""" + var data2 = """{"x": "omega", "y": 200}""" + + var r = to[Something](data1) + + echo r.x, " ", r.y + + r = to[Something](data2) + + echo r.x, " ", r.y + +blockD() + +type + Foo = object + a1: string + a2: string + a3: seq[string] + a4: seq[int] + a5: seq[int] + a6: seq[int] + +proc blockE = + var foo = Foo(a2: "", a4: @[], a6: @[1]) + foo.a6.setLen 0 + doAssert $$foo == """{"a1": "", "a2": "", "a3": [], "a4": [], "a5": [], "a6": []}""" + testit(foo) + +blockE() + +GC_fullCollect() +echo getOccupiedMem() diff --git a/tests/arc/tmove_regression.nim b/tests/arc/tmove_regression.nim new file mode 100644 index 000000000..7d9a867c3 --- /dev/null +++ b/tests/arc/tmove_regression.nim @@ -0,0 +1,22 @@ +discard """ + output: '''/1/2 +/1 +/ +''' +"""" + +# bug #22001 + +import std / [os, strutils] + +proc finOp2(path, name: string): (string, File) = # Find & open FIRST `name` + var current = path + while true: + if current.isRootDir: break # <- current=="" => current.isRootDir + current = current.parentDir + let dir = current + echo dir.replace('\\', '/') # Commenting out try/except below hides bug + try: result[0] = dir/name; result[1] = open(result[0]); return + except CatchableError: discard + +discard finOp2("/1/2/3", "4") # All same if this->inside a proc diff --git a/tests/arc/tmovebug.nim b/tests/arc/tmovebug.nim new file mode 100644 index 000000000..8ad7c955c --- /dev/null +++ b/tests/arc/tmovebug.nim @@ -0,0 +1,841 @@ +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 +() +() +() +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 +sink +sink +destroy +copy +(f: 1) +destroy +destroy +part-to-whole assigment: +sink +(children: @[]) +destroy +sink +(children: @[]) +destroy +copy +destroy +(f: 1) +destroy +''' +""" + +# 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 `=`(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 `=`(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: + return "hi" + else: + "else" + +echo getScope() + +proc getScope3(): string = + try: + "try" + except: + return "except" + +echo getScope3() + +proc getScope2(): string = + case true + of true: + return "bye" + else: + "else" + +echo getScope2() + + +#-------------------------------------------------------------------- +#bug #15609 + +type + Wrapper = object + discard + +proc newWrapper(): ref Wrapper = + new(result) + result + + +proc newWrapper2(a: int): ref Wrapper = + new(result) + if a > 0: + result + else: + new(Wrapper) + + +let w1 = newWrapper() +echo $w1[] + +let w2 = newWrapper2(1) +echo $w2[] + +let w3 = newWrapper2(-1) +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() + +proc potentialSelfAssign(i: var int) = + var a: array[2, OO] + a[i] = OO(f: 1) # turned into a memcopy + a[1] = OO(f: 2) + a[i+1] = a[i] # This must not =sink, but =copy + inc i + echo a[i-1] # (f: 1) + +potentialSelfAssign (var xi = 0; xi) + + +#-------------------------------------------------------------------- +echo "part-to-whole assigment:" + +type + Tree = object + children: seq[Tree] + + TreeDefaultHooks = object + children: seq[TreeDefaultHooks] + +proc `=destroy`(x: var Tree) = echo "destroy" +proc `=sink`(x: var Tree, y: Tree) = echo "sink" +proc `=copy`(x: var Tree, y: Tree) = echo "copy" + +proc partToWholeSeq = + var t = Tree(children: @[Tree()]) + t = t.children[0] # This should be sunk, but with the special transform (tmp = t.children[0]; wasMoved(0); `=sink`(t, tmp)) + + var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()]) + tc = tc.children[0] # Ditto; if this were sunk with the normal transform (`=sink`(t, t.children[0]); wasMoved(t.children[0])) + echo tc # then it would crash because t.children[0] does not exist after the call to `=sink` + +partToWholeSeq() + +proc partToWholeSeqRTIndex = + var i = 0 + var t = Tree(children: @[Tree()]) + t = t.children[i] # See comment in partToWholeSeq + + var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()]) + tc = tc.children[i] # See comment in partToWholeSeq + echo tc + +partToWholeSeqRTIndex() + +type List = object + next: ref List + +proc `=destroy`(x: var List) = echo "destroy" +proc `=sink`(x: var List, y: List) = echo "sink" +proc `=copy`(x: var List, y: List) = echo "copy" + +proc partToWholeUnownedRef = + var t = List(next: new List) + t = t.next[] # Copy because t.next is not an owned ref, and thus t.next[] cannot be moved + +partToWholeUnownedRef() + + +#-------------------------------------------------------------------- +# test that nodes that get copied during the transformation +# (like dot exprs) don't loose their firstWrite/lastRead property + +type + OOO = object + initialized: bool + + C = object + o: OOO + +proc `=destroy`(o: var OOO) = + doAssert o.initialized, "OOO was destroyed before initialization!" + +proc initO(): OOO = + OOO(initialized: true) + +proc initC(): C = + C(o: initO()) + +proc pair(): tuple[a: C, b: C] = + result = (a: initC(), b: initC())# <- when firstWrite tries to find this node to start its analysis it fails, because injectdestructors uses copyTree/shallowCopy + +discard pair() + + +# bug #17450 +proc noConsume(x: OO) {.nosinks.} = echo x + +proc main3 = + var i = 1 + noConsume: + block: + OO(f: i) + +main3() + +# misc +proc smoltest(x: bool): bool = + while true: + if true: return x + +discard smoltest(true) + +# bug #18002 +type + TTypeAttachedOp = enum + attachedAsgn + attachedSink + attachedTrace + + PNode = ref object + discard + +proc genAddrOf(n: PNode) = + assert n != nil, "moved?!" + +proc atomicClosureOp = + let x = PNode() + + genAddrOf: + block: + x + + case attachedTrace + of attachedSink: discard + of attachedAsgn: discard + of attachedTrace: genAddrOf(x) + +atomicClosureOp() + + +template assertEq(a, b: untyped): untyped = + block: + let + aval = a + bval = b + + if aval != bval: + quit "bug!" + +proc convoluted = + let _ = (; + var val1: string; + if true: val1 = "22" + true + ) + + assertEq val1, "22" + assertEq val1, "22" + +convoluted() diff --git a/tests/arc/tmovebugcopy.nim b/tests/arc/tmovebugcopy.nim new file mode 100644 index 000000000..ec4315777 --- /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/tnewseq_legacy.nim b/tests/arc/tnewseq_legacy.nim new file mode 100644 index 000000000..4730d2c2b --- /dev/null +++ b/tests/arc/tnewseq_legacy.nim @@ -0,0 +1,13 @@ +discard """ + output: "(allocCount: 201, deallocCount: 201)" + cmd: "nim c --gc:orc -d:nimAllocStats $file" +""" + +proc main(prefix: string) = + var c: seq[string] + for i in 0..<100: + newSeq(c, 100) + c[i] = prefix & $i + +main("abc") +echo getAllocStats() diff --git a/tests/arc/top_no_cursor2.nim b/tests/arc/top_no_cursor2.nim new file mode 100644 index 000000000..5dfde57aa --- /dev/null +++ b/tests/arc/top_no_cursor2.nim @@ -0,0 +1,53 @@ +discard """ + output: '''true +true +true +true +true''' + cmd: "nim c --gc:arc $file" +""" +# bug #15361 + +type + ErrorNodeKind = enum Branch, Leaf + Error = ref object + case kind: ErrorNodeKind + of Branch: + left: Error + right: Error + of Leaf: + leafError: string + input: string + +proc ret(input: string, lefterr, righterr: Error): Error = + result = Error(kind: Branch, left: lefterr, right: righterr, input: input) + +proc parser() = + var rerrors: Error + let lerrors = Error( + kind: Leaf, + leafError: "first error", + input: "123 ;" + ) + # If you remove "block" - everything works + block: + let rresult = Error( + kind: Leaf, + leafError: "second error", + input: ";" + ) + # this assignment is needed too + rerrors = rresult + + # Returns Error(kind: Branch, left: lerrors, right: rerrors, input: "some val") + # needs to be a proc call for some reason, can't inline the result + var data = ret(input = "some val", lefterr = lerrors, righterr = rerrors) + + echo data.left.leafError == "first error" + echo data.left.input == "123 ;" + # stacktrace shows this line + echo data.right.leafError == "second error" + echo data.right.input == ";" + echo data.input == "some val" + +parser() diff --git a/tests/arc/topenarray.nim b/tests/arc/topenarray.nim new file mode 100644 index 000000000..ba91666ba --- /dev/null +++ b/tests/arc/topenarray.nim @@ -0,0 +1,86 @@ +discard """ + input: "hi" + output: ''' +hi +Nim +''' + matrix: "--gc:arc -d:useMalloc; --gc:arc" +""" +{.experimental: "views".} + +block: # bug 18627 + proc setPosition(params: openArray[string]) = + for i in params.toOpenArray(0, params.len - 1): + echo i + + proc uciLoop() = + let params = @[readLine(stdin)] + setPosition(params) + + uciLoop() + + proc uciLoop2() = + let params = @["Nim"] + for i in params.toOpenArray(0, params.len - 1): + echo i + uciLoop2() + +when defined(nimPreviewSlimSystem): + import std/assertions + +block: # bug #20954 + block: + doAssertRaises(IndexDefect): + var v: array[10, int] + + echo len(toOpenArray(v, 20, 30)) + + block: + doAssertRaises(IndexDefect): + var v: seq[int] + + echo len(toOpenArray(v, 20, 30)) + +# bug #20422 + +proc f(a: var string) = + var v = a.toOpenArray(1, 3) + v[0] = 'a' + +var a = "Hello" +f(a) +doAssert a == "Hallo" + +# bug #22132 +block: + func foo[T](arr: openArray[T], idx: int = arr.low): string = + doAssert idx == 0 + return $arr + + let bug = ["0", "c", "a"] + + let str = foo(bug) + + const expected = """["0", "c", "a"]""" + doAssert str == expected + + const noBugConst = ["0", "c", "a"] + doAssert foo(noBugConst) == expected + let noBugSeq = @["0", "c", "a"] + doAssert foo(noBugSeq) == expected + +block: # bug #20865 + var p: pointer + var x: array[0, int] + # echo toOpenArray(x, 0, 1)[0] # Raises IndexDefect + doAssertRaises(IndexDefect): + echo toOpenArray(cast[ptr array[0, int]](p)[], 0, 1)[0] # Does not raise IndexDefect + +block: # bug #20987 + var v: array[1, byte] + + var p = cast[ptr array[0, byte]](addr v) + + doAssertRaises(IndexDefect): + echo toOpenArray(p[], 1, 2) + diff --git a/tests/arc/topt_cursor.nim b/tests/arc/topt_cursor.nim new file mode 100644 index 000000000..794132921 --- /dev/null +++ b/tests/arc/topt_cursor.nim @@ -0,0 +1,57 @@ +discard """ + output: '''("string here", 80)''' + cmd: '''nim c --gc:arc --expandArc:main --expandArc:sio --hint:Performance:off $file''' + nimout: '''--expandArc: main + +var + x_cursor + :tmpD +try: + x_cursor = ("hi", 5) + if cond: + x_cursor = ("different", 54) else: + x_cursor = ("string here", 80) + echo [ + :tmpD = `$$`(x_cursor) + :tmpD] +finally: + `=destroy`(:tmpD) +-- end of expandArc ------------------------ +--expandArc: sio + +block :tmp: + var x_cursor + var f = open("debug.txt", fmRead, 8000) + try: + var res + try: + res = newStringOfCap(80) + block :tmp_1: + while readLine(f, res): + x_cursor = res + echo [x_cursor] + finally: + `=destroy`(res) + finally: + close(f) +-- end of expandArc ------------------------''' +""" + +proc main(cond: bool) = + var x = ("hi", 5) # goal: computed as cursor + + x = if cond: + ("different", 54) + else: + ("string here", 80) + + echo x + +main(false) + +proc sio = + for x in lines("debug.txt"): + echo x + +if false: + sio() diff --git a/tests/arc/topt_cursor2.nim b/tests/arc/topt_cursor2.nim new file mode 100644 index 000000000..4b566ed24 --- /dev/null +++ b/tests/arc/topt_cursor2.nim @@ -0,0 +1,76 @@ +discard """ + cmd: '''nim c --gc:arc $file''' + output: '''emptyemptyempty +inner destroy +''' +""" + +# bug #15039 + +import lists + +type + Token = ref object of RootObj + children: DoublyLinkedList[Token] + + Paragraph = ref object of Token + +method `$`(token: Token): string {.base.} = + result = "empty" + +method `$`(token: Paragraph): string = + if token.children.head == nil: + result = "" + else: + for c in token.children: + result.add $c + +proc parseLeafBlockInlines(token: Token) = + token.children.append(Token()) + token.children.append(Token()) # <-- this one AAA + + var emNode = newDoublyLinkedNode(Token()) + var i = 0 + + var it = token.children.head + while it != nil: + var nxt = it.next # this is not a cursor, it takes over ownership. + var childNode = it + if i == 0: + childNode.next = emNode # frees the object allocated in line 29 marked with AAA + elif i == 1: + emNode.next = childNode # + it = nxt # incref on freed data, 'nxt' is freed + inc i + +proc parse() = + var token = Token() + token.children.append Paragraph() + parseLeafBlockInlines(token.children.head.value) + for children in token.children: + echo children + +parse() + + +#------------------------------------------------------------------------------ +# issue #15629 + +type inner = object +type outer = ref inner + +proc `=destroy`(b: var inner) = + echo "inner destroy" + +proc newOuter(): outer = + new(result) + +type holder = object + contents: outer + +proc main() = + var t: holder + t.contents = newOuter() + +main() + diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim new file mode 100644 index 000000000..0a4984a69 --- /dev/null +++ b/tests/arc/topt_no_cursor.nim @@ -0,0 +1,376 @@ +discard """ + nimoutFull: true + cmd: '''nim c -r --warnings:off --hints:off --mm:arc --expandArc:newTarget --expandArc:delete --expandArc:p1 --expandArc:tt --hint:Performance:off --assertions:off --expandArc:extractConfig --expandArc:mergeShadowScope --expandArc:check $file''' + nimout: ''' +--expandArc: newTarget + +var + splat + :tmp + :tmp_1 +splat = splitDrive do: + let blitTmp = path + blitTmp +:tmp = splat.drive +`=wasMoved`(splat.drive) +:tmp_1 = splat.path_1 +`=wasMoved`(splat.path_1) +result = ( + let blitTmp_1 = :tmp + blitTmp_1, + let blitTmp_2 = :tmp_1 + blitTmp_2) +`=destroy`(splat) +-- end of expandArc ------------------------ +--expandArc: delete + +var + sibling + saved +`=copy`(sibling, target.parent.left) +`=copy`(saved, sibling.right) +`=copy`(sibling.right, saved.left) +`=sink`(sibling.parent, saved) +`=destroy`(sibling) +-- end of expandArc ------------------------ +--expandArc: p1 + +var + lresult + lvalue + lnext + tmpTupleAsgn +lresult = @[123] +tmpTupleAsgn = ( + let blitTmp = lresult + blitTmp, ";") +lvalue = tmpTupleAsgn[0] +lnext = tmpTupleAsgn[1] +`=sink`(result.value, move lvalue) +`=destroy`(lnext) +`=destroy_1`(lvalue) +-- end of expandArc ------------------------ +--expandArc: tt + +var + it_cursor + a + :tmpD + :tmpD_1 + :tmpD_2 +try: + it_cursor = x + a = ( + :tmpD = `=dup`(it_cursor.key) + :tmpD, + :tmpD_1 = `=dup`(it_cursor.val) + :tmpD_1) + echo [ + :tmpD_2 = `$$`(a) + :tmpD_2] +finally: + `=destroy`(:tmpD_2) + `=destroy_1`(a) +-- end of expandArc ------------------------ +--expandArc: extractConfig + +var lan_ip +try: + lan_ip = "" + block :tmp: + var line + var i = 0 + let L = len(txt) + block :tmp_1: + while i < L: + var splitted + try: + line = txt[i] + splitted = split(line, " ", -1) + if splitted[0] == "opt": + `=copy`(lan_ip, splitted[1]) + echo [lan_ip] + echo [splitted[1]] + {.push, overflowChecks: false.} + inc(i, 1) + {.pop.} + finally: + `=destroy`(splitted) +finally: + `=destroy_1`(lan_ip) +-- end of expandArc ------------------------ +--expandArc: mergeShadowScope + +var shadowScope +`=copy`(shadowScope, c.currentScope) +rawCloseScope(c) +block :tmp: + var sym + var i = 0 + let L = len(shadowScope.symbols) + block :tmp_1: + while i < L: + var :tmpD + sym = shadowScope.symbols[i] + addInterfaceDecl(c): + :tmpD = `=dup`(sym) + :tmpD + {.push, overflowChecks: false.} + inc(i, 1) + {.pop.} +`=destroy`(shadowScope) +-- end of expandArc ------------------------ +--expandArc: check + +var par +this.isValid = fileExists(this.value) +if dirExists(this.value): + var :tmpD + par = (dir: + :tmpD = `=dup`(this.value) + :tmpD, front: "") else: + var + :tmpD_1 + :tmpD_2 + :tmpD_3 + par = (dir_1: parentDir(this.value), front_1: + :tmpD_1 = `=dup`( + :tmpD_3 = splitDrive do: + :tmpD_2 = `=dup`(this.value) + :tmpD_2 + :tmpD_3.path) + :tmpD_1) + `=destroy`(:tmpD_3) +if dirExists(par.dir): + `=sink`(this.matchDirs, getSubDirs(par.dir, par.front)) +else: + `=sink`(this.matchDirs, []) +`=destroy`(par) +-- end of expandArc ------------------------ +--expandArc: check + +check(this) +-- end of expandArc ------------------------ +(package: "", ext: "meo") +doing shady stuff... +3 +6 +(@[1], @[2]) +192.168.0.1 +192.168.0.1 +192.168.0.1 +192.168.0.1 +''' +""" + +import os, std/private/ntpath + +type Target = tuple[package, ext: string] + +proc newTarget*(path: string): Target = + let splat = path.splitDrive + result = (package: splat.drive, ext: splat.path) + +echo newTarget("meo") + +type + Node = ref object + left, right, parent: Node + value: int + +proc delete(target: var Node) = + var sibling = target.parent.left # b3 + var saved = sibling.right # b3.right -> r4 + + sibling.right = saved.left # b3.right -> r4.left = nil + sibling.parent = saved # b3.parent -> r5 = r4 + + #[after this proc: + b 5 + / \ + b 3 b 6 + ]# + + +#[before: + r 5 + / \ + b 3 b 6 - to delete + / \ +empty r 4 +]# +proc main = + var five = Node(value: 5) + + var six = Node(value: 6) + six.parent = five + five.right = six + + var three = Node(value: 3) + three.parent = five + five.left = three + + var four = Node(value: 4) + four.parent = three + three.right = four + + echo "doing shady stuff..." + delete(six) + # need both of these echos + echo five.left.value + echo five.right.value + +main() + +type + Maybe = object + value: seq[int] + +proc p1(): Maybe = + let lresult = @[123] + var lvalue: seq[int] + var lnext: string + (lvalue, lnext) = (lresult, ";") + + result.value = move lvalue + +proc tissue15130 = + doAssert p1().value == @[123] + +tissue15130() + +type + KeyValue = tuple[key, val: seq[int]] + +proc tt(x: KeyValue) = + var it = x + let a = (it.key, it.val) + echo a + +proc encodedQuery = + var query: seq[KeyValue] + query.add (key: @[1], val: @[2]) + + for elem in query: + elem.tt() + +encodedQuery() + +# bug #15147 + +proc s(input: string): (string, string) = + result = (";", "") + +proc charmatch(input: string): (string, string) = + result = ("123", input[0 .. input.high]) + +proc plus(input: string) = + var + lvalue, rvalue: string # cursors + lnext: string # must be cursor!!! + rnext: string # cursor + let lresult = charmatch(input) + (lvalue, lnext) = lresult + + let rresult = s(lnext) + (rvalue, rnext) = rresult + +plus("123;") + +func substrEq(s: string, pos: int, substr: string): bool = + var i = 0 + var length = substr.len + while i < length and pos+i < s.len and s[pos+i] == substr[i]: + inc i + return i == length + +template stringHasSep(s: string, index: int, sep: string): bool = + s.substrEq(index, sep) + +template splitCommon(s, sep, maxsplit, sepLen) = + var last = 0 + var splits = maxsplit + + while last <= len(s): + var first = last + while last < len(s) and not stringHasSep(s, last, sep): + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) + +iterator split(s: string, sep: string, maxsplit = -1): string = + splitCommon(s, sep, maxsplit, sep.len) + +template accResult(iter: untyped) = + result = @[] + for x in iter: add(result, x) + +func split*(s: string, sep: string, maxsplit = -1): seq[string] = + accResult(split(s, sep, maxsplit)) + + +let txt = @["opt 192.168.0.1", "static_lease 192.168.0.1"] + +# bug #17033 + +proc extractConfig() = + var lan_ip = "" + + for line in txt: + let splitted = line.split(" ") + if splitted[0] == "opt": + lan_ip = splitted[1] # "borrow" is conditional and inside a loop. + # Not good enough... + # we need a flag that live-ranges are disjoint + echo lan_ip + echo splitted[1] # Without this line everything works + +extractConfig() + + +type + Symbol = ref object + name: string + + Scope = ref object + parent: Scope + symbols: seq[Symbol] + + PContext = ref object + currentScope: Scope + +proc rawCloseScope(c: PContext) = + c.currentScope = c.currentScope.parent + +proc addInterfaceDecl(c: PContext; s: Symbol) = + c.currentScope.symbols.add s + +proc mergeShadowScope*(c: PContext) = + let shadowScope = c.currentScope + c.rawCloseScope + for sym in shadowScope.symbols: + c.addInterfaceDecl(sym) + +mergeShadowScope(PContext(currentScope: Scope(parent: Scope()))) + +type + Foo = ref object + isValid*: bool + value*: string + matchDirs*: seq[string] + +proc getSubDirs(parent, front: string): seq[string] = @[] + +method check(this: Foo) {.base.} = + this.isValid = fileExists(this.value) + let par = if dirExists(this.value): (dir: this.value, front: "") + else: (dir: parentDir(this.value), front: splitDrive(this.value).path) + if dirExists(par.dir): + this.matchDirs = getSubDirs(par.dir, par.front) + else: + this.matchDirs = @[] + +check(Foo()) diff --git a/tests/arc/topt_refcursors.nim b/tests/arc/topt_refcursors.nim new file mode 100644 index 000000000..8c638a4a1 --- /dev/null +++ b/tests/arc/topt_refcursors.nim @@ -0,0 +1,54 @@ +discard """ + output: '''''' + cmd: '''nim c --gc:arc --expandArc:traverse --hint:Performance:off $file''' + nimout: ''' +--expandArc: traverse + +var + it_cursor + jt +try: + it_cursor = root + block :tmp: + while ( + not (it_cursor == nil)): + echo [it_cursor.s] + it_cursor = it_cursor.ri + `=copy`(jt, root) + block :tmp_1: + while ( + not (jt == nil)): + var ri_1 + try: + `=copy`(ri_1, jt.ri) + echo [jt.s] + `=sink`(jt, ri_1) + `=wasMoved`(ri_1) + finally: + `=destroy`(ri_1) +finally: + `=destroy`(jt) +-- end of expandArc ------------------------ +''' +""" + +type + Node = ref object + le, ri: Node + s: string + +proc traverse(root: Node) = + var it = root + while it != nil: + echo it.s + it = it.ri + + var jt = root + while jt != nil: + let ri = jt.ri + echo jt.s + jt = ri + +traverse(nil) + +# XXX: This optimization is not sound diff --git a/tests/arc/topt_wasmoved_destroy_pairs.nim b/tests/arc/topt_wasmoved_destroy_pairs.nim new file mode 100644 index 000000000..c96340a30 --- /dev/null +++ b/tests/arc/topt_wasmoved_destroy_pairs.nim @@ -0,0 +1,94 @@ +discard """ + output: '''''' + cmd: '''nim c --gc:arc --expandArc:main --expandArc:tfor --hint:Performance:off $file''' + nimout: ''' +--expandArc: main + +var + a + b + x +x = f() +if cond: + add(a): + let blitTmp = x + blitTmp +else: + add(b): + let blitTmp_1 = x + blitTmp_1 +`=destroy`(b) +`=destroy`(a) +-- end of expandArc ------------------------ +--expandArc: tfor + +var + a + b + x +try: + x = f() + block :tmp: + var i_cursor + mixin inc + var i_1 = 0 + block :tmp_1: + while i_1 < 4: + var :tmpD + i_cursor = i_1 + if i_cursor == 2: + return + add(a): + :tmpD = `=dup`(x) + :tmpD + inc i_1, 1 + if cond: + add(a): + let blitTmp = x + `=wasMoved`(x) + blitTmp + else: + add(b): + let blitTmp_1 = x + `=wasMoved`(x) + blitTmp_1 +finally: + `=destroy`(x) + `=destroy_1`(b) + `=destroy_1`(a) +-- end of expandArc ------------------------ +''' +""" + +proc f(): seq[int] = + @[1, 2, 3] + +proc main(cond: bool) = + var a, b: seq[seq[int]] + var x = f() + if cond: + a.add x + else: + b.add x + +# all paths move 'x' so no wasMoved(x); destroy(x) pair should be left in the +# AST. + +main(false) + + +proc tfor(cond: bool) = + var a, b: seq[seq[int]] + + var x = f() + + for i in 0 ..< 4: + if i == 2: return + a.add x + + if cond: + a.add x + else: + b.add x + +tfor(false) diff --git a/tests/arc/torc_basic_test.nim b/tests/arc/torc_basic_test.nim new file mode 100644 index 000000000..73039fad7 --- /dev/null +++ b/tests/arc/torc_basic_test.nim @@ -0,0 +1,138 @@ +discard """ + output: "MEM 0" + cmd: "nim c --gc:orc $file" +""" + +type + Node = ref object + name: char + sccId: int + #a: array[3, Node] + a0, a1, a2: Node + rc: int + +proc edge(a, b: Node) = + inc b.rc + if a.a0 == nil: a.a0 = b + elif a.a1 == nil: a.a1 = b + else: a.a2 = b + when false: + var i = 0 + while a.a[i] != nil: inc i + a.a[i] = b + +proc createNode(name: char): Node = + new result + result.name = name + +#[ + + +--------------------------------+ + v | ++---------+ +------+ | +| | | | | +| A +----->+ | +------+------+ ++--+------+ | | | | + | | | | C ------------> G <--| + | | R | | | ++--v------+ | | +-------------+ +| | | | ^ +| B <------+ | | +| | | +--------+ ++---------+ | | + +------+ + +]# +proc use(x: Node) = discard + +proc main = + let a = createNode('A') + let b = createNode('B') + let r = createNode('R') + let c = createNode('C') + + a.edge b + a.edge r + + r.edge b + r.edge c + + let g = createNode('G') + g.edge g + g.edge g + + c.edge g + c.edge a + + use g + use b + +proc buildComplexGraph: Node = + # see https://en.wikipedia.org/wiki/Strongly_connected_component for the + # graph: + let a = createNode('a') + let b = createNode('b') + let c = createNode('c') + let d = createNode('d') + let e = createNode('e') + + a.edge c + c.edge b + c.edge e + b.edge a + d.edge c + e.edge d + + + let f = createNode('f') + b.edge f + e.edge f + + let g = createNode('g') + let h = createNode('h') + let i = createNode('i') + + f.edge g + f.edge i + g.edge h + h.edge i + i.edge g + + let j = createNode('j') + + h.edge j + i.edge j + + let k = createNode('k') + let l = createNode('l') + + f.edge k + k.edge l + l.edge k + k.edge j + + let m = createNode('m') + let n = createNode('n') + let p = createNode('p') + let q = createNode('q') + + m.edge n + n.edge p + n.edge q + q.edge p + p.edge m + + q.edge k + + d.edge m + e.edge n + + result = a + +proc main2 = + let g = buildComplexGraph() + +main() +main2() +GC_fullCollect() +echo "MEM ", getOccupiedMem() diff --git a/tests/arc/torc_selfcycles.nim b/tests/arc/torc_selfcycles.nim new file mode 100644 index 000000000..ac4fa52ce --- /dev/null +++ b/tests/arc/torc_selfcycles.nim @@ -0,0 +1,33 @@ +discard """ + output: '''ok''' + cmd: '''nim c --gc:orc -d:useMalloc -d:nimStressOrc $file''' + valgrind: "leaks" +""" + +# bug #15753 + +type + NodeKind = enum + nkDancing, + nkColumn + + DancingNode = ref object + right: DancingNode + column: DancingNode + kind: NodeKind + +proc newColumnNode(): DancingNode = + result = DancingNode(kind: nkColumn) + result.right = result + result.column = result + +proc createDLXList(): DancingNode = + result = newColumnNode() + + for i in 0 .. 15: + let n = newColumnNode() + n.right = result.right + result = n + echo "ok" + +var dlxlist = createDLXList() diff --git a/tests/arc/torcbench.nim b/tests/arc/torcbench.nim new file mode 100644 index 000000000..4c9e65fee --- /dev/null +++ b/tests/arc/torcbench.nim @@ -0,0 +1,38 @@ +discard """ + output: '''true peak memory: true''' + cmd: "nim c --gc:orc -d:release $file" +""" + +import lists, strutils, times + +type + Base = ref object of RootObj + + Node = ref object of Base + parent: DoublyLinkedList[string] + le, ri: Node + self: Node # in order to create a cycle + +proc buildTree(parent: DoublyLinkedList[string]; depth: int): Node = + if depth == 0: + result = nil + elif depth == 1: + result = Node(parent: parent, le: nil, ri: nil, self: nil) + when not defined(gcArc): + result.self = result + else: + result = Node(parent: parent, le: buildTree(parent, depth - 1), ri: buildTree(parent, depth - 2), self: nil) + result.self = result + +proc main() = + for i in countup(1, 100): + var leakList = initDoublyLinkedList[string]() + for j in countup(1, 5000): + leakList.append(newString(200)) + #GC_fullCollect() + for i in 0..400: + discard buildTree(leakList, 8) + +main() +GC_fullCollect() +echo getOccupiedMem() < 10 * 1024 * 1024, " peak memory: ", getMaxMem() < 10 * 1024 * 1024 diff --git a/tests/arc/torcmisc.nim b/tests/arc/torcmisc.nim new file mode 100644 index 000000000..e41ad7c77 --- /dev/null +++ b/tests/arc/torcmisc.nim @@ -0,0 +1,66 @@ +discard """ + output: '''success''' + cmd: "nim c --gc:orc -d:release $file" +""" + +# bug #17170 + +when true: + import asyncdispatch + + type + Flags = ref object + returnedEof, reading: bool + + proc dummy(): Future[string] {.async.} = + result = "foobar" + + proc hello(s: Flags) {.async.} = + let buf = + try: + await dummy() + except CatchableError as exc: + # When an exception happens here, the Bufferstream is effectively + # broken and no more reads will be valid - for now, return EOF if it's + # called again, though this is not completely true - EOF represents an + # "orderly" shutdown and that's not what happened here.. + s.returnedEof = true + raise exc + finally: + s.reading = false + + waitFor hello(Flags()) + echo "success" + +# bug #18240 +import tables + +type + TopicHandler* = proc(topic: string, + data: seq[byte]): Future[void] {.gcsafe, raises: [Defect].} + + PeerID* = object + data*: seq[byte] + + PeerInfo* = ref object of RootObj + peerId*: PeerID + + Connection* = ref object of RootObj + peerInfo*: PeerInfo + + PubSubPeer* = ref object of RootObj + codec*: string + + PubSub* = ref object of RootObj + topics*: Table[string, seq[TopicHandler]] + peers*: Table[PeerID, PubSubPeer] + +proc getOrCreatePeer*(myParam: PubSub, peerId: PeerID, protos: seq[string]): PubSubPeer = + myParam.peers.withValue(peerId, peer): + return peer[] + +method handleConn*(myParam: PubSub, + conn: Connection, + proto: string) {.base, async.} = + myParam.peers.withValue(conn.peerInfo.peerId, peer): + let peerB = peer[] diff --git a/tests/arc/tref_cast_error.nim b/tests/arc/tref_cast_error.nim new file mode 100644 index 000000000..20139c1be --- /dev/null +++ b/tests/arc/tref_cast_error.nim @@ -0,0 +1,15 @@ +discard """ + cmd: "nim c --gc:arc $file" + errormsg: "expression cannot be cast to 'ref RootObj'" + joinable: false +""" + +type Variant* = object + refval: ref RootObj + +proc newVariant*[T](val: T): Variant = + let pt = T.new() + pt[] = val + result = Variant(refval: cast[ref RootObj](pt)) + +var v = newVariant(@[1, 2, 3]) diff --git a/tests/arc/trepr.nim b/tests/arc/trepr.nim new file mode 100644 index 000000000..abf28330a --- /dev/null +++ b/tests/arc/trepr.nim @@ -0,0 +1,97 @@ +discard """ + cmd: "nim c --gc:arc $file" + nimout: '''(a: true, n: doAssert) +Table[system.string, trepr.MyType](data: @[], counter: 0) +nil +''' + output: ''' +nil +2 +Obj(member: ref @["hello"]) +ref (member: ref @["hello"]) +ObjUa(v: 0, a: [...]) +''' +""" + +# xxx consider merging with `tests/stdlib/trepr.nim` to increase overall test coverage + +import tables + +type + NimSym = distinct NimNode + MyType = tuple + a: bool + n: NimSym + +proc myproc(t: MyType) = + echo repr(t) + +proc myproc2(t: MyType) = + var x = Table[string, t]() + echo repr(x) + +proc myproc3(t: MyType) = + var x: TableRef[string, t] + echo repr(x) + + +macro dumpSym(a: typed) = + myproc((a: true, n: NimSym(a))) + myproc2((a: true, n: NimSym(a))) + myproc3((a: true, n: NimSym(a))) + +dumpSym(doAssert) + +# bug 13731 + +import os +var a: File +echo repr a + +# bug 13872 + +echo repr(2'u16) + +# bug 14270 + +type + Obj = ref object + member: ref seq[string] + +var c = Obj(member: new seq[string]) +c.member[] = @["hello"] +echo c.repr + +var c2 = new tuple[member: ref seq[string]] +c2.member = new seq[string] +c2.member[] = @["hello"] +echo c2.repr + +proc p2 = + echo "hey" + +discard repr p2 + + +##################################################################### +# bug #15043 + +import macros + +macro extract(): untyped = + result = newStmtList() + var x: seq[tuple[node: NimNode]] + + proc test(n: NimNode) {.closure.} = + x.add (node: n) + + test(parseExpr("discard")) + +extract() + +type + ObjUa = ref object + v: int + a: UncheckedArray[char] + +echo ObjUa().repr diff --git a/tests/arc/trtree.nim b/tests/arc/trtree.nim new file mode 100644 index 000000000..683403a62 --- /dev/null +++ b/tests/arc/trtree.nim @@ -0,0 +1,640 @@ +discard """ + output: '''1 [2, 3, 4, 7] +[0, 0]''' + targets: "c" + joinable: false +disabled: 32bit + cmd: "nim c --gc:arc $file" +""" + +# bug #13110: This test failed with --gc:arc. + +# this test wasn't written for 32 bit +# don't join because the code is too messy. + +# Nim RTree and R*Tree implementation +# S. Salewski, 06-JAN-2018 + +# http://www-db.deis.unibo.it/courses/SI-LS/papers/Gut84.pdf +# http://dbs.mathematik.uni-marburg.de/publications/myPapers/1990/BKSS90.pdf + +# RT: range type like float, int +# D: Dimension +# M: Max entries in one node +# LT: leaf type + +type + Dim* = static[int] + Ext[RT] = tuple[a, b: RT] # extend (range) + Box*[D: Dim; RT] = array[D, Ext[RT]] # Rectangle for 2D + BoxCenter*[D: Dim; RT] = array[D, RT] + L*[D: Dim; RT, LT] = tuple[b: Box[D, RT]; l: LT] # called Index Entry or index record in the Guttman paper + H[M, D: Dim; RT, LT] = ref object of RootRef + parent: H[M, D, RT, LT] + numEntries: int + level: int + N[M, D: Dim; RT, LT] = tuple[b: Box[D, RT]; n: H[M, D, RT, LT]] + LA[M, D: Dim; RT, LT] = array[M, L[D, RT, LT]] + NA[M, D: Dim; RT, LT] = array[M, N[M, D, RT, LT]] + Leaf[M, D: Dim; RT, LT] = ref object of H[M, D, RT, LT] + a: LA[M, D, RT, LT] + Node[M, D: Dim; RT, LT] = ref object of H[M, D, RT, LT] + a: NA[M, D, RT, LT] + + RTree*[M, D: Dim; RT, LT] = ref object of RootRef + root: H[M, D, RT, LT] + bigM: int + m: int + + RStarTree*[M, D: Dim; RT, LT] = ref object of RTree[M, D, RT, LT] + firstOverflow: array[32, bool] + p: int + +proc newLeaf[M, D: Dim; RT, LT](): Leaf[M, D, RT, LT] = + new result + +proc newNode[M, D: Dim; RT, LT](): Node[M, D, RT, LT] = + new result + +proc newRTree*[M, D: Dim; RT, LT](minFill: range[30 .. 50] = 40): RTree[M, D, RT, LT] = + assert(M > 1 and M < 101) + new result + result.bigM = M + result.m = M * minFill div 100 + result.root = newLeaf[M, D, RT, LT]() + +proc newRStarTree*[M, D: Dim; RT, LT](minFill: range[30 .. 50] = 40): RStarTree[M, D, RT, LT] = + assert(M > 1 and M < 101) + new result + result.bigM = M + result.m = M * minFill div 100 + result.p = M * 30 div 100 + result.root = newLeaf[M, D, RT, LT]() + +proc center(r: Box): auto =#BoxCenter[r.len, typeof(r[0].a)] = + var res: BoxCenter[r.len, typeof(r[0].a)] + for i in 0 .. r.high: + when r[0].a is SomeInteger: + res[i] = (r[i].a + r[i].b) div 2 + elif r[0].a is SomeFloat: + res[i] = (r[i].a + r[i].b) / 2 + else: assert false + return res + +proc distance(c1, c2: BoxCenter): auto = + var res: typeof(c1[0]) + for i in 0 .. c1.high: + res += (c1[i] - c2[i]) * (c1[i] - c2[i]) + return res + +proc overlap(r1, r2: Box): auto = + result = typeof(r1[0].a)(1) + for i in 0 .. r1.high: + result *= (min(r1[i].b, r2[i].b) - max(r1[i].a, r2[i].a)) + if result <= 0: return 0 + +proc union(r1, r2: Box): Box = + for i in 0 .. r1.high: + result[i].a = min(r1[i].a, r2[i].a) + result[i].b = max(r1[i].b, r2[i].b) + +proc intersect(r1, r2: Box): bool = + for i in 0 .. r1.high: + if r1[i].b < r2[i].a or r1[i].a > r2[i].b: + return false + return true + +proc area(r: Box): auto = #typeof(r[0].a) = + result = typeof(r[0].a)(1) + for i in 0 .. r.high: + result *= r[i].b - r[i].a + +proc margin(r: Box): auto = #typeof(r[0].a) = + result = typeof(r[0].a)(0) + for i in 0 .. r.high: + result += r[i].b - r[i].a + +# how much enlargement does r1 need to include r2 +proc enlargement(r1, r2: Box): auto = + area(union(r1, r2)) - area(r1) + +proc search*[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]): seq[LT] = + proc s[M, D: Dim; RT, LT](n: H[M, D, RT, LT]; b: Box[D, RT]; res: var seq[LT]) = + if n of Node[M, D, RT, LT]: + let h = Node[M, D, RT, LT](n) + for i in 0 ..< n.numEntries: + if intersect(h.a[i].b, b): + s(h.a[i].n, b, res) + elif n of Leaf[M, D, RT, LT]: + let h = Leaf[M, D, RT, LT](n) + for i in 0 ..< n.numEntries: + if intersect(h.a[i].b, b): + res.add(h.a[i].l) + else: assert false + result = newSeq[LT]() + s(t.root, b, result) + +# Insertion +# a R*TREE proc +proc chooseSubtree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]; level: int): H[M, D, RT, LT] = + assert level >= 0 + var it = t.root + while it.level > level: + let nn = Node[M, D, RT, LT](it) + var i0 = 0 # selected index + var minLoss = typeof(b[0].a).high + if it.level == 1: # childreen are leaves -- determine the minimum overlap costs + for i in 0 ..< it.numEntries: + let nx = union(nn.a[i].b, b) + var loss = 0 + for j in 0 ..< it.numEntries: + if i == j: continue + loss += (overlap(nx, nn.a[j].b) - overlap(nn.a[i].b, nn.a[j].b)) # overlap (i, j) == (j, i), so maybe cache that? + var rep = loss < minLoss + if loss == minLoss: + let l2 = enlargement(nn.a[i].b, b) - enlargement(nn.a[i0].b, b) + rep = l2 < 0 + if l2 == 0: + let l3 = area(nn.a[i].b) - area(nn.a[i0].b) + rep = l3 < 0 + if l3 == 0: + rep = nn.a[i].n.numEntries < nn.a[i0].n.numEntries + if rep: + i0 = i + minLoss = loss + else: + for i in 0 ..< it.numEntries: + let loss = enlargement(nn.a[i].b, b) + var rep = loss < minLoss + if loss == minLoss: + let l3 = area(nn.a[i].b) - area(nn.a[i0].b) + rep = l3 < 0 + if l3 == 0: + rep = nn.a[i].n.numEntries < nn.a[i0].n.numEntries + if rep: + i0 = i + minLoss = loss + it = nn.a[i0].n + return it + +proc pickSeeds[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; n: Node[M, D, RT, LT] | Leaf[M, D, RT, LT]; bx: Box[D, RT]): (int, int) = + var i0, j0: int + var bi, bj: typeof(bx) + var largestWaste = typeof(bx[0].a).low + for i in -1 .. n.a.high: + for j in 0 .. n.a.high: + if unlikely(i == j): continue + if unlikely(i < 0): + bi = bx + else: + bi = n.a[i].b + bj = n.a[j].b + let b = union(bi, bj) + let h = area(b) - area(bi) - area(bj) + if h > largestWaste: + largestWaste = h + i0 = i + j0 = j + return (i0, j0) + +proc pickNext[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; n0, n1, n2: Node[M, D, RT, LT] | Leaf[M, D, RT, LT]; b1, b2: Box[D, RT]): int = + let a1 = area(b1) + let a2 = area(b2) + var d = typeof(a1).low + for i in 0 ..< n0.numEntries: + let d1 = area(union(b1, n0.a[i].b)) - a1 + let d2 = area(union(b2, n0.a[i].b)) - a2 + if (d1 - d2) * (d1 - d2) > d: + result = i + d = (d1 - d2) * (d1 - d2) + +from algorithm import SortOrder, sort +proc sortPlus[T](a: var openArray[T], ax: var T, cmp: proc (x, y: T): int {.closure.}, order = algorithm.SortOrder.Ascending) = + var j = 0 + let sign = if order == algorithm.SortOrder.Ascending: 1 else: -1 + for i in 1 .. a.high: + if cmp(a[i], a[j]) * sign < 0: + j = i + if cmp(a[j], ax) * sign < 0: + swap(ax, a[j]) + a.sort(cmp, order) + +# R*TREE procs +proc rstarSplit[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; n: var Node[M, D, RT, LT] | var Leaf[M, D, RT, LT]; lx: L[D, RT, LT] | N[M, D, RT, LT]): typeof(n) = + type NL = typeof(lx) + var nBest: typeof(n) + new nBest + var lx = lx + when n is Node[M, D, RT, LT]: + lx.n.parent = n + var lxbest: typeof(lx) + var m0 = lx.b[0].a.typeof.high + for d2 in 0 ..< 2 * D: + let d = d2 div 2 + if d2 mod 2 == 0: + sortPlus(n.a, lx, proc (x, y: NL): int = cmp(x.b[d].a, y.b[d].a)) + else: + sortPlus(n.a, lx, proc (x, y: NL): int = cmp(x.b[d].b, y.b[d].b)) + for i in t.m - 1 .. n.a.high - t.m + 1: + var b = lx.b + for j in 0 ..< i: # we can precalculate union() for range 0 .. t.m - 1, but that seems to give no real benefit.Maybe for very large M? + #echo "x",j + b = union(n.a[j].b, b) + var m = margin(b) + b = n.a[^1].b + for j in i ..< n.a.high: # again, precalculation of tail would be possible + #echo "y",j + b = union(n.a[j].b, b) + m += margin(b) + if m < m0: + nbest[] = n[] + lxbest = lx + m0 = m + var i0 = -1 + var o0 = lx.b[0].a.typeof.high + for i in t.m - 1 .. n.a.typeof.high - t.m + 1: + var b1 = lxbest.b + for j in 0 ..< i: + b1 = union(nbest.a[j].b, b1) + var b2 = nbest.a[^1].b + for j in i ..< n.a.high: + b2 = union(nbest.a[j].b, b2) + let o = overlap(b1, b2) + if o < o0: + i0 = i + o0 = o + n.a[0] = lxbest + for i in 0 ..< i0: + n.a[i + 1] = nbest.a[i] + new result + result.level = n.level + result.parent = n.parent + for i in i0 .. n.a.high: + result.a[i - i0] = nbest.a[i] + n.numEntries = i0 + 1 + result.numEntries = M - i0 + when n is Node[M, D, RT, LT]: + for i in 0 ..< result.numEntries: + result.a[i].n.parent = result + +proc quadraticSplit[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; n: var Node[M, D, RT, LT] | var Leaf[M, D, RT, LT]; lx: L[D, RT, LT] | N[M, D, RT, LT]): typeof(n) = + var n1, n2: typeof(n) + var s1, s2: int + new n1 + new n2 + n1.parent = n.parent + n2.parent = n.parent + n1.level = n.level + n2.level = n.level + var lx = lx + when n is Node[M, D, RT, LT]: + lx.n.parent = n + (s1, s2) = pickSeeds(t, n, lx.b) + assert s1 >= -1 and s2 >= 0 + if unlikely(s1 < 0): + n1.a[0] = lx + else: + n1.a[0] = n.a[s1] + dec(n.numEntries) + if s2 == n.numEntries: # important fix + s2 = s1 + n.a[s1] = n.a[n.numEntries] + inc(n1.numEntries) + var b1 = n1.a[0].b + n2.a[0] = n.a[s2] + dec(n.numEntries) + n.a[s2] = n.a[n.numEntries] + inc(n2.numEntries) + var b2 = n2.a[0].b + if s1 >= 0: + n.a[n.numEntries] = lx + inc(n.numEntries) + while n.numEntries > 0 and n1.numEntries < (t.bigM + 1 - t.m) and n2.numEntries < (t.bigM + 1 - t.m): + let next = pickNext(t, n, n1, n2, b1, b2) + let d1 = area(union(b1, n.a[next].b)) - area(b1) + let d2 = area(union(b2, n.a[next].b)) - area(b2) + if (d1 < d2) or (d1 == d2 and ((area(b1) < area(b2)) or (area(b1) == area(b2) and n1.numEntries < n2.numEntries))): + n1.a[n1.numEntries] = n.a[next] + b1 = union(b1, n.a[next].b) + inc(n1.numEntries) + else: + n2.a[n2.numEntries] = n.a[next] + b2 = union(b2, n.a[next].b) + inc(n2.numEntries) + dec(n.numEntries) + n.a[next] = n.a[n.numEntries] + if n.numEntries == 0: + discard + elif n1.numEntries == (t.bigM + 1 - t.m): + while n.numEntries > 0: + dec(n.numEntries) + n2.a[n2.numEntries] = n.a[n.numEntries] + inc(n2.numEntries) + elif n2.numEntries == (t.bigM + 1 - t.m): + while n.numEntries > 0: + dec(n.numEntries) + n1.a[n1.numEntries] = n.a[n.numEntries] + inc(n1.numEntries) + when n is Node[M, D, RT, LT]: + for i in 0 ..< n2.numEntries: + n2.a[i].n.parent = n2 + n[] = n1[] + return n2 + +proc overflowTreatment[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; n: var Node[M, D, RT, LT] | var Leaf[M, D, RT, LT]; lx: L[D, RT, LT] | N[M, D, RT, LT]): typeof(n) + +proc adjustTree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; l, ll: H[M, D, RT, LT]; hb: Box[D, RT]) = + var n = l + var nn = ll + assert n != nil + while true: + if n == t.root: + if nn == nil: + break + t.root = newNode[M, D, RT, LT]() + t.root.level = n.level + 1 + Node[M, D, RT, LT](t.root).a[0].n = n + n.parent = t.root + nn.parent = t.root + t.root.numEntries = 1 + let p = Node[M, D, RT, LT](n.parent) + var i = 0 + while p.a[i].n != n: + inc(i) + var b: typeof(p.a[0].b) + if n of Leaf[M, D, RT, LT]: + when false:#if likely(nn.isNil): # no performance gain + b = union(p.a[i].b, Leaf[M, D, RT, LT](n).a[n.numEntries - 1].b) + else: + b = Leaf[M, D, RT, LT](n).a[0].b + for j in 1 ..< n.numEntries: + b = trtree.union(b, Leaf[M, D, RT, LT](n).a[j].b) + elif n of Node[M, D, RT, LT]: + b = Node[M, D, RT, LT](n).a[0].b + for j in 1 ..< n.numEntries: + b = union(b, Node[M, D, RT, LT](n).a[j].b) + else: + assert false + #if nn.isNil and p.a[i].b == b: break # no performance gain + p.a[i].b = b + n = H[M, D, RT, LT](p) + if unlikely(nn != nil): + if nn of Leaf[M, D, RT, LT]: + b = Leaf[M, D, RT, LT](nn).a[0].b + for j in 1 ..< nn.numEntries: + b = union(b, Leaf[M, D, RT, LT](nn).a[j].b) + elif nn of Node[M, D, RT, LT]: + b = Node[M, D, RT, LT](nn).a[0].b + for j in 1 ..< nn.numEntries: + b = union(b, Node[M, D, RT, LT](nn).a[j].b) + else: + assert false + if p.numEntries < p.a.len: + p.a[p.numEntries].b = b + p.a[p.numEntries].n = nn + inc(p.numEntries) + assert n != nil + nn = nil + else: + let h: N[M, D, RT, LT] = (b, nn) + nn = quadraticSplit(t, p, h) + assert n == H[M, D, RT, LT](p) + assert n != nil + assert t.root != nil + +proc insert*[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; leaf: N[M, D, RT, LT] | L[D, RT, LT]; level: int = 0) = + when leaf is N[M, D, RT, LT]: + assert level > 0 + type NodeLeaf = Node[M, D, RT, LT] + else: + assert level == 0 + type NodeLeaf = Leaf[M, D, RT, LT] + for d in leaf.b: + assert d.a <= d.b + let l = NodeLeaf(chooseSubtree(t, leaf.b, level)) + if l.numEntries < l.a.len: + l.a[l.numEntries] = leaf + inc(l.numEntries) + when leaf is N[M, D, RT, LT]: + leaf.n.parent = l + adjustTree(t, l, nil, leaf.b) + else: + let l2 = quadraticSplit(t, l, leaf) + assert l2.level == l.level + adjustTree(t, l, l2, leaf.b) + +# R*Tree insert procs +proc rsinsert[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; leaf: N[M, D, RT, LT] | L[D, RT, LT]; level: int) + +proc reInsert[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; n: var Node[M, D, RT, LT] | var Leaf[M, D, RT, LT]; lx: L[D, RT, LT] | N[M, D, RT, LT]) = + type NL = typeof(lx) + var lx = lx + var buf: typeof(n.a) + let p = Node[M, D, RT, LT](n.parent) + var i = 0 + while p.a[i].n != n: + inc(i) + let c = center(p.a[i].b) + sortPlus(n.a, lx, proc (x, y: NL): int = cmp(distance(center(x.b), c), distance(center(y.b), c))) + n.numEntries = M - t.p + swap(n.a[n.numEntries], lx) + inc n.numEntries + var b = n.a[0].b + for i in 1 ..< n.numEntries: + b = union(b, n.a[i].b) + p.a[i].b = b + for i in M - t.p + 1 .. n.a.high: + buf[i] = n.a[i] + rsinsert(t, lx, n.level) + for i in M - t.p + 1 .. n.a.high: + rsinsert(t, buf[i], n.level) + +proc overflowTreatment[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; n: var Node[M, D, RT, LT] | var Leaf[M, D, RT, LT]; lx: L[D, RT, LT] | N[M, D, RT, LT]): typeof(n) = + if n.level != t.root.level and t.firstOverflow[n.level]: + t.firstOverflow[n.level] = false + reInsert(t, n, lx) + return nil + else: + let l2 = rstarSplit(t, n, lx) + assert l2.level == n.level + return l2 + +proc rsinsert[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; leaf: N[M, D, RT, LT] | L[D, RT, LT]; level: int) = + when leaf is N[M, D, RT, LT]: + assert level > 0 + type NodeLeaf = Node[M, D, RT, LT] + else: + assert level == 0 + type NodeLeaf = Leaf[M, D, RT, LT] + let l = NodeLeaf(chooseSubtree(t, leaf.b, level)) + if l.numEntries < l.a.len: + l.a[l.numEntries] = leaf + inc(l.numEntries) + when leaf is N[M, D, RT, LT]: + leaf.n.parent = l + adjustTree(t, l, nil, leaf.b) + else: + when leaf is N[M, D, RT, LT]: # TODO do we need this? + leaf.n.parent = l + let l2 = overflowTreatment(t, l, leaf) + if l2 != nil: + assert l2.level == l.level + adjustTree(t, l, l2, leaf.b) + +proc insert*[M, D: Dim; RT, LT](t: RStarTree[M, D, RT, LT]; leaf: L[D, RT, LT]) = + for d in leaf.b: + assert d.a <= d.b + for i in mitems(t.firstOverflow): + i = true + rsinsert(t, leaf, 0) + +# delete +proc findLeaf[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; leaf: L[D, RT, LT]): Leaf[M, D, RT, LT] = + proc fl[M, D: Dim; RT, LT](h: H[M, D, RT, LT]; leaf: L[D, RT, LT]): Leaf[M, D, RT, LT] = + var n = h + if n of Node[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + if intersect(Node[M, D, RT, LT](n).a[i].b, leaf.b): + let l = fl(Node[M, D, RT, LT](n).a[i].n, leaf) + if l != nil: + return l + elif n of Leaf[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + if Leaf[M, D, RT, LT](n).a[i] == leaf: + return Leaf[M, D, RT, LT](n) + else: + assert false + return nil + fl(t.root, leaf) + +proc condenseTree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; leaf: Leaf[M, D, RT, LT]) = + var n: H[M, D, RT, LT] = leaf + var q = newSeq[H[M, D, RT, LT]]() + var b: typeof(leaf.a[0].b) + while n != t.root: + let p = Node[M, D, RT, LT](n.parent) + var i = 0 + while p.a[i].n != n: + inc(i) + if n.numEntries < t.m: + dec(p.numEntries) + p.a[i] = p.a[p.numEntries] + q.add(n) + else: + if n of Leaf[M, D, RT, LT]: + b = Leaf[M, D, RT, LT](n).a[0].b + for j in 1 ..< n.numEntries: + b = union(b, Leaf[M, D, RT, LT](n).a[j].b) + elif n of Node[M, D, RT, LT]: + b = Node[M, D, RT, LT](n).a[0].b + for j in 1 ..< n.numEntries: + b = union(b, Node[M, D, RT, LT](n).a[j].b) + else: + assert false + p.a[i].b = b + n = n.parent + if t of RStarTree[M, D, RT, LT]: + for n in q: + if n of Leaf[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + for i in mitems(RStarTree[M, D, RT, LT](t).firstOverflow): + i = true + rsinsert(RStarTree[M, D, RT, LT](t), Leaf[M, D, RT, LT](n).a[i], 0) + elif n of Node[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + for i in mitems(RStarTree[M, D, RT, LT](t).firstOverflow): + i = true + rsinsert(RStarTree[M, D, RT, LT](t), Node[M, D, RT, LT](n).a[i], n.level) + else: + assert false + else: + for n in q: + if n of Leaf[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + insert(t, Leaf[M, D, RT, LT](n).a[i]) + elif n of Node[M, D, RT, LT]: + for i in 0 ..< n.numEntries: + insert(t, Node[M, D, RT, LT](n).a[i], n.level) + else: + assert false + +proc delete*[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; leaf: L[D, RT, LT]): bool {.discardable.} = + let l = findLeaf(t, leaf) + if l.isNil: + return false + else: + var i = 0 + while l.a[i] != leaf: + inc(i) + dec(l.numEntries) + l.a[i] = l.a[l.numEntries] + condenseTree(t, l) + if t.root.numEntries == 1: + if t.root of Node[M, D, RT, LT]: + t.root = Node[M, D, RT, LT](t.root).a[0].n + t.root.parent = nil + return true + + +var t = [4, 1, 3, 2] +var xt = 7 +sortPlus(t, xt, system.cmp, SortOrder.Ascending) +echo xt, " ", t + +type + RSE = L[2, int, int] + RSeq = seq[RSE] + +proc rseq_search(rs: RSeq; rse: RSE): seq[int] = + result = newSeq[int]() + for i in rs: + if intersect(i.b, rse.b): + result.add(i.l) + +proc rseq_delete(rs: var RSeq; rse: RSE): bool = + for i in 0 .. rs.high: + if rs[i] == rse: + #rs.delete(i) + rs[i] = rs[rs.high] + rs.setLen(rs.len - 1) + return true + +import random, algorithm + +proc test(n: int) = + var b: Box[2, int] + echo center(b) + var x1, x2, y1, y2: int + var t = newRStarTree[8, 2, int, int]() + #var t = newRTree[8, 2, int, int]() + var rs = newSeq[RSE]() + for i in 0 .. 5: + for i in 0 .. n - 1: + x1 = rand(1000) + y1 = rand(1000) + x2 = x1 + rand(25) + y2 = y1 + rand(25) + b = [(x1, x2), (y1, y2)] + let el: L[2, int, int] = (b, i + 7) + t.insert(el) + rs.add(el) + + for i in 0 .. (n div 4): + let j = rand(rs.high) + var el = rs[j] + assert t.delete(el) + assert rs.rseq_delete(el) + + for i in 0 .. n - 1: + x1 = rand(1000) + y1 = rand(1000) + x2 = x1 + rand(100) + y2 = y1 + rand(100) + b = [(x1, x2), (y1, y2)] + let el: L[2, int, int] = (b, i) + let r = search(t, b) + let r2 = rseq_search(rs, el) + assert r.len == r2.len + assert r.sorted(system.cmp) == r2.sorted(system.cmp) + +test(500) diff --git a/tests/arc/tshared_ptr_crash.nim b/tests/arc/tshared_ptr_crash.nim new file mode 100644 index 000000000..1794834db --- /dev/null +++ b/tests/arc/tshared_ptr_crash.nim @@ -0,0 +1,67 @@ +discard """ + cmd: "nim c --threads:on --gc:arc $file" + action: compile +""" + +# bug #17893 + +type + SharedPtr*[T] = object + val: ptr tuple[value: T, atomicCounter: int] + +proc `=destroy`*[T](p: var SharedPtr[T]) = + mixin `=destroy` + if p.val != nil: + if atomicLoadN(addr p.val[].atomicCounter, AtomicConsume) == 0: + `=destroy`(p.val[]) + deallocShared(p.val) + else: + discard atomicDec(p.val[].atomicCounter) + +proc `=copy`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = + if src.val != nil: + discard atomicInc(src.val[].atomicCounter) + if dest.val != nil: + `=destroy`(dest) + dest.val = src.val + +proc newSharedPtr*[T](val: sink T): SharedPtr[T] {.nodestroy.} = + result.val = cast[typeof(result.val)](allocShared(sizeof(result.val[]))) + result.val.atomicCounter = 0 + result.val.value = val + +proc isNil*[T](p: SharedPtr[T]): bool {.inline.} = + p.val == nil + +proc `[]`*[T](p: SharedPtr[T]): var T {.inline.} = + when compileOption("boundChecks"): + doAssert(p.val != nil, "deferencing nil shared pointer") + result = p.val.value + +type + Sender*[T] = object + queue: SharedPtr[seq[T]] + +proc newSender*[T](queue: sink SharedPtr[seq[T]]): Sender[T] = + result = Sender[T](queue: queue) + +proc send*[T](self: Sender[T]; t: sink T) = + self.queue[].add t + +proc newChannel*(): Sender[int] = + let queue = newSharedPtr(newSeq[int]()) + result = newSender(queue) + + +var + p: Thread[Sender[int]] + +proc threadFn(tx: Sender[int]) = + send tx, 0 + +proc multiThreadedChannel = + let tx = newChannel() + createThread(p, threadFn, tx) + joinThread(p) + +multiThreadedChannel() diff --git a/tests/arc/tstrformat.nim b/tests/arc/tstrformat.nim new file mode 100644 index 000000000..641f323da --- /dev/null +++ b/tests/arc/tstrformat.nim @@ -0,0 +1,22 @@ +discard """ + output: ''' +verstuff +''' + cmd: "nim c --gc:arc $file" +""" + +# bug #13622 + +import strformat + +template necho*(args: string) {.dirty.} = + if getCurrentException() != nil: + echo args + else: + stdout.writeLine(args) + +proc main(cond: bool; arg: string) = + if cond: + necho &"ver{arg}\n" + +main(true, "stuff") diff --git a/tests/arc/tstringliteral.nim b/tests/arc/tstringliteral.nim new file mode 100644 index 000000000..c5fac22d8 --- /dev/null +++ b/tests/arc/tstringliteral.nim @@ -0,0 +1,17 @@ +discard """ + matrix: "--mm:arc; --mm:orc" +""" + +block: # issue #24080 + var a = (s: "a") + var b = "a" + a.s.setLen 0 + b = a.s + doAssert b == "" + +block: # issue #24080, longer string + var a = (s: "abc") + var b = "abc" + a.s.setLen 2 + b = a.s + doAssert b == "ab" diff --git a/tests/arc/tthread.nim b/tests/arc/tthread.nim new file mode 100644 index 000000000..8a55a666e --- /dev/null +++ b/tests/arc/tthread.nim @@ -0,0 +1,63 @@ +discard """ + cmd: "nim cpp --gc:arc --threads:on $file" + output: '''ok1 +ok2 +destroyed +destroyed +destroyed +''' +""" +import threadpool, os + +type + MyObj = object + p: int + MyObjRef = ref MyObj + +proc `=destroy`(x: var MyObj) = + if x.p != 0: + echo "destroyed" + +proc thread1(): string = + os.sleep(1000) + return "ok1" + +proc thread2(): ref string = + os.sleep(1000) + new(result) + result[] = "ok2" + +proc thread3(): ref MyObj = + os.sleep(1000) + new(result) + result[].p = 2 + +var fv1 = spawn thread1() +var fv2 = spawn thread2() +var fv3 = spawn thread3() +sync() +echo ^fv1 +echo (^fv2)[] + + +proc thread4(x: MyObjRef): MyObjRef {.nosinks.} = + os.sleep(1000) + result = x + +proc thread5(x: sink MyObjRef): MyObjRef = + os.sleep(1000) + result = x + +proc ref_forwarding_test = + var x = new(MyObj) + x[].p = 2 + var y = spawn thread4(x) + +proc ref_sink_forwarding_test = + var x = new(MyObj) + x[].p = 2 + var y = spawn thread5(x) + +ref_forwarding_test() +ref_sink_forwarding_test() +sync() diff --git a/tests/arc/tunref_cycle.nim b/tests/arc/tunref_cycle.nim new file mode 100644 index 000000000..82551b7f7 --- /dev/null +++ b/tests/arc/tunref_cycle.nim @@ -0,0 +1,26 @@ +discard """ + outputsub: '''inside closure +hello world''' + cmd: "nim c --gc:orc -d:useMalloc $file" + valgrind: true +""" + +# bug #18579 + +var fp: proc (env: pointer) {.cdecl.} +var env: pointer + +proc store(f: proc (){.closure.}) = + proc closeOver() = + echo "inside closure" + f() + (fp,env) = (cast[proc(env: pointer){.cdecl.}](rawProc closeOver), rawEnv closeOver) + GC_ref(cast[RootRef](env)) + +proc run() = + fp(env) + GC_unref(cast[RootRef](env)) + +store(proc() = echo "hello world") +run() +GC_fullCollect() diff --git a/tests/arc/tweave.nim b/tests/arc/tweave.nim new file mode 100644 index 000000000..1c60ac403 --- /dev/null +++ b/tests/arc/tweave.nim @@ -0,0 +1,157 @@ +discard """ + outputsub: '''Success''' + cmd: '''nim c --gc:arc --threads:on $file''' + disabled: "bsd" +""" + +# bug #13936 + +import std/atomics + +when defined(nimPreviewSlimSystem): + import std/[assertions, typedthreads] + +const MemBlockSize = 256 + +type + ChannelSPSCSingle* = object + full{.align: 128.}: Atomic[bool] + itemSize*: uint8 + buffer*{.align: 8.}: UncheckedArray[byte] + +proc `=`( + 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/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/arc/twrong_sinkinference.nim b/tests/arc/twrong_sinkinference.nim new file mode 100644 index 000000000..ecf09d28a --- /dev/null +++ b/tests/arc/twrong_sinkinference.nim @@ -0,0 +1,18 @@ +discard """ + cmd: "nim c --gc:arc $file" + errormsg: "type mismatch: got <proc (a: string, b: sink string){.noSideEffect, gcsafe.}>" + line: 18 +""" + +type + Foo = proc (a, b: string) + +proc take(x: Foo) = + x("a", "b") + +proc willSink(a, b: string) = # {.nosinks.} = + var arr: array[3, string] + var x = a + arr[0] = b + +take willSink |