scoped memory management (#14790)

* fixes the regressions
* closes #13936
* scope based memory management implemented
* enabled tcontrolflow.nim test case
* final cleanups
This commit is contained in:
Andreas Rumpf 2020-07-04 07:37:24 +02:00 • committed by GitHub
commit 1854d29781
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 611 additions and 477 deletions

View file

@ -173,3 +173,39 @@ proc bug14495 =
echo o[]
bug14495()
when false:
# 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)

View file

@ -11,15 +11,13 @@ begin true
if
end true
7
##index 2 not in 0 .. 1##
'''
cmd: "nim c --gc:arc -d:danger $file"
disabled: "true"
"""
# we use the -d:danger switch to detect uninitialized stack
# slots more reliably (there shouldn't be any, of course).
# XXX Enable once scope based destruction works!
type
Foo = object
id: int
@ -68,12 +66,25 @@ proc run(data: Control) =
evt.control = data
if evt.button == 1:
discard
else:
else:
return
echo data.x
var c = Control(x: 7)
run(c)
proc sysFatal(exceptn: typedesc, message: string) {.inline, noreturn.} =
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)

View file

@ -247,7 +247,7 @@ iterator combinations[T](s: openarray[T], k: int): seq[T] =
break
type
UndefEx = object of Exception
UndefEx = object of ValueError
proc main2 =
var delayedSyms = @[1, 2, 3]
@ -318,10 +318,10 @@ proc `=sink`(dest: var O2, src: O2) =
var testSeq: O2
proc Update(): void =
proc update() =
# testSeq.add(0) # uncommenting this line fixes the leak
testSeq = O2(s: @[])
testSeq.s.add(0)
for i in 1..3:
Update()
update()

View file

@ -14,7 +14,7 @@ type
p: int
MyObjRef = ref MyObj
proc `=destroy`(x: var MyObj) =
proc `=destroy`(x: var MyObj) =
if x.p != 0:
echo "destroyed"
@ -48,16 +48,16 @@ proc thread5(x: sink MyObjRef): MyObjRef =
os.sleep(1000)
result = x
proc ref_forwarding_test =
proc ref_forwarding_test =
var x = new(MyObj)
x[].p = 2
var y = spawn thread4(x)
proc ref_sink_forwarding_test =
proc ref_sink_forwarding_test =
var x = new(MyObj)
x[].p = 2
var y = spawn thread5(x)
ref_forwarding_test()
ref_sink_forwarding_test()
ref_sink_forwarding_test()
sync()

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

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

View file

@ -1,8 +1,9 @@
discard """
output: '''
hasPendingOperations: false
triggerCount: 100
triggerCount: 100
'''
disabled: "windows"
"""
import asyncDispatch

View file

@ -3,6 +3,8 @@ discard """
output: '''copied
copied
2
destroyed
destroyed
copied
copied
2
@ -37,9 +39,9 @@ proc test(a: range[0..1], arg: ObjWithDestructor) =
inc iteration
case a
of 0:
assert false
of 1:
of 0:
assert false
of 1:
echo b
if iteration == 2:
break

View file

@ -1,27 +1,27 @@
discard """
output: '''----
output: '''----1
myobj constructed
myobj destroyed
----
----2
mygeneric1 constructed
mygeneric1 destroyed
----
----3
mygeneric2 constructed
mygeneric2 destroyed
myobj destroyed
----
----4
mygeneric3 constructed
mygeneric1 destroyed
----
----5
mydistinctObj constructed
myobj destroyed
mygeneric2 destroyed
------------------
----
----
myobj destroyed
------------------8
mygeneric1 destroyed
---
----6
myobj destroyed
----7
---9
myobj destroyed
myobj destroyed
'''
@ -114,19 +114,19 @@ proc mydistinctObj =
echo "mydistinctObj constructed"
echo "----"
echo "----1"
myobj()
echo "----"
echo "----2"
mygeneric1()
echo "----"
echo "----3"
mygeneric2[int](10)
echo "----"
echo "----4"
mygeneric3()
echo "----"
echo "----5"
mydistinctObj()
proc caseobj =
@ -134,16 +134,16 @@ proc caseobj =
var o1 = TCaseObj(kind: A, x: TMyGeneric1[int](x: 10))
block:
echo "----"
echo "----6"
var o2 = TCaseObj(kind: B, y: open())
block:
echo "----"
echo "----7"
var o3 = TCaseObj(kind: D, innerKind: B, r: "test",
p: TMyGeneric3[int, float, string](x: 10, y: 1.0, z: "test"))
echo "------------------"
echo "------------------8"
caseobj()
proc caseobj_test_sink: TCaseObj =
@ -153,7 +153,7 @@ proc caseobj_test_sink: TCaseObj =
result = TCaseObj(kind: B, y: open())
echo "---"
echo "---9"
discard caseobj_test_sink()
# issue #14315