From the Nim Manual:
We call a proc p GC safe when it doesn't access any global variable that contains GC'ed memory (string, seq, ref or a closure) either directly or indirectly through a call to a GC unsafe proc.
In other words, a GC safe proc never accesses the heap either directly or indirectly.
I understand the need to assure GC safe annotations when using the older memory management strategies where the heap is thread-local. But I do not understand the need for it in the case of ORC/ARC, where there is a single shared (i.e. global) heap.
I fully understand the need to ensure SAFE access to such variables, say by guarding them appropriately or by employing move semantics. What I do not understand is the need to ensure NO access to those variables.
My understanding, which may be incorrect.
In ARC/ORC, reference counting is not atomic across threads even though there is a single shared heap, and therefore access across threads can still be problematic.
If you're not working with ref object, then lock/s can help you, but to coordinate across threads without copying will involve use of addr/ptr/[]. See, for example, Lockers and the Mutable Parameters section below it.
For ref object, you would need to look into the experimental atomicRef, which is only available in nim-lang/Nim devel branch, I think.
Thanks, I was wondering if non-atomic reference counts might have something to do with this.
when two procs accessing a GC'ed global exit concurrently, it may cause a data race (Both trying to decrease the reference count at the same time)
From my reading of the withLock template, putting the global inside withLock blocks should remove the data race:
template withLock*(a: Lock, body: untyped) =
## Acquires the given lock, executes the statements in body and
## releases the lock after the statements finish executing.
acquire(a)
{.locks: [a].}:
try:
body
finally:
release(a)
The variable would go out of scope at the end of the try block, and the reference count should be decremented there. The lock is not released until the finally block - after the decrement has occurred.
Assuming, of course, that the compiler (nim or target) doesn't mess things up with some kind of optimization or re-ordering under the hood.