Intermittent "SIGSEGV: Illegal storage access" calling init(Weave) when using gc:arc
Environment:
Ubuntu: 18.04
Nim: 1.4.2
Nimpy: 0.1.1
Arraymancer: 0.6.2
Weave: 0.4.0

I'm new to Nim and Arraymancer (a couple weeks of hacking around), so I don't have the knowledge to fully understand the nuances of GC in this context; this may be intentional behavior, or I may be using it horribly incorrectly.

When using ARC as the GC, when running more than one thread on the code below (using your Weave library), about half the time I get a SIGSEGV fault, and the other half the time it runs correctly and produces the correct output. Debugging shows that it's during the Arraymancer init(Weave) call. Using the boehm GC, I haven't seen any fault in several thousand iterations; it always runs correctly. I also noticed that if I never dereference the pointers to the tensors, I never encounter this fault.

Below is the offending code. Its a simple filtering algorithm, checking points in a numpy array (shape=100000,3) with (dtype=np.double) to see if they fall within certain coordinates, and copying those points to another ndarray of the same shape.

```nim
### minworking.nim ###
import nimpy
import nimpy / [raw_buffers, py_types]

import arraymancer

import weave
import weave/instrumentation/loggers
import weave/contexts


type
  SafePyBuffer = ref SafePyBufferObj
  SafePyBufferObj = object
    pyBuf: RawPyBuffer
    shape: seq[int]


proc newSafePyBuffer(a: PyObject): SafePyBuffer =
  when defined(gcDestructors):
    new(result)
  var buf: RawPyBuffer
  getBuffer(a, buf, PyBUF_WRITABLE or PyBUF_ND)
  let shapeArray = cast[ptr UncheckedArray[Py_ssize_t]](buf.shape)
  result.shape = newSeq[int](buf.ndim)
  for i in 0 ..< buf.ndim:
    result.shape[i] = shapeArray[i].int # py_ssize_t is csize
  result.pyBuf = buf


proc toTensor[T](s: SafePyBuffer): Tensor[T] =
  result = fromBuffer[T](s.pyBuf.buf, s.shape)


proc checkCoords(p1: cdouble, p2: cdouble): bool {.exportpy.} =
  if (p1 >= 0.2) and 
     (p2 >= 0.4) and
     (p1 <= 0.4) and
     (p2 <= 0.6):
    return true
  else:
    return false  


type Args = tuple[a_tensor, b_tensor: ptr Tensor[cdouble], tnum: int]
const t_count: int = 8


proc mapFill(a: Args) = 
  var thread_out: int = 0
  log("...tnum: %d, myID: %d \n", a.tnum, myID())
  for coord, sv in enumerateAxis(a.a_tensor[], 0):
    if checkCoords(sv[0,0],sv[0,1]):
      a.b_tensor[][thread_out,_] = sv
    #   echo a.b_tensor[][thread_out,_]
    #   echo a.b_tensor[]
      thread_out += 1


proc filterListWeave(o1, o2: PyObject, length, width: int): int {.exportpy.} =
  log("...Start filterListWeave... \n")

  log("...Allocate safe buffers... \n")
  let a_safe_buf = newSafePyBuffer(o1)
  let b_safe_buf = newSafePyBuffer(o2)

  log("...Converting buffers to tensors... \n")

  log("...Convert a_tensor... \n")
  let a_tensor = toTensor[system.float](a_safe_buf)
  log("...Convert b_tensor... \n")
  var b_tensor = toTensor[system.float](b_safe_buf)

  log("...Chuncking tensors... \n")
  var a_seq = a_tensor.chunk(t_count, 0)
  var b_seq = b_tensor.chunk(t_count, 0)

  log("...Allocate pointer arrays... \n")
  var a_seq_ptr: array[t_count, ptr Tensor[cdouble]]
  var b_seq_ptr: array[t_count, ptr Tensor[cdouble]]

  log("...Grab pointers to chuncks... \n")
  for ind in 0..<t_count:
    a_seq_ptr[ind] = a_seq[ind].addr
    b_seq_ptr[ind] = b_seq[ind].addr

  log("...Init weave... \n")
  init(Weave)

  parallelFor i in 0..<t_count:
    captures: {a_seq_ptr, b_seq_ptr}
    mapFill((a_seq_ptr[i], b_seq_ptr[i], i).Args)

  log("...Exit weave... \n")
  exit(Weave)
  
  log("...End filterListWeave... \n")

  return 0

```
I'm compiling this with `nim c --opt:speed --threads:on --define:release --app:lib --gc:arc --out:minworking.so minworking.nim`.

And some python code to test it with:
```python3
### speed-test-min.py ##
from timeit import Timer
import numpy as np

import sys
sys.path.append('.')

# import mymodule as nn
import minworking as nn

def py_random_array(size, dim=3):
    return np.random.rand(int(size), dim)


def nim_loop(array):
    pointArray = np.ndarray(dtype=np.double, shape=array.shape)
    filtered_length = nn.filterListWeave(array, pointArray, *array.shape)
    return pointArray


def py_loop(array):
    filtered_list = []
    for i in range(len(array)):
        if ((array[i][0] >= 0.2)
            and (array[i][1] >= 0.4)
            and (array[i][0] <= 0.4)
            and (array[i][1] <= 0.6)):
            filtered_list.append(array[i])

    array_np = np.array(filtered_list)
    return array_np

# Generate a random array of size 1e5
np_unfiltered_array = py_random_array(10000)

iterations = 2

py_filtered_array = py_loop(np_unfiltered_array)
t_py = Timer(lambda: py_loop(np_unfiltered_array))
timed_python = t_py.timeit(number=iterations)
print(f'Py Loop: {timed_python} / {iterations} = {timed_python/iterations}')

nim_filtered_array = nim_loop(np_unfiltered_array)
t_nim = Timer(lambda: nim_loop(np_unfiltered_array))
timed_nim = t_nim.timeit(number=iterations)
print(f'Nim Loop: {timed_nim} / {iterations} = {timed_nim/iterations}')

print(f"py sum:  {py_filtered_array.sum()}, {len(py_filtered_array)}")
print(f"nim sum:  {nim_filtered_array.sum()}, {len(nim_filtered_array)}")

```

If this is too far outside of the scope for Arraymancer, feel free to close this issue. From what I can tell, Arraymancer + Nimpy + Weave have complementing goals, so this issue might be of interest.