Search code examples
cgocgo

Passing 2d arrays from Go to C using runtime.Pinner


I am trying to pass 2d array from Go to some C function void foo(in **float, out *double). Since I want to have wrapper for this C function, I'd like that Go function has definition like func FooWrapper([][]float32) []float64. The easiest but not efficient implementation is allocating all memory through C that listed below:

func FooWrapper(values [][]float32) []float64 {
    totalObj := len(values)
    totalParams := len(values[0])
    results := make([]float64, totalObj)

    ptrArrLength := uintptr(totalObj) * cPointerSize
    paramArrLength := uintptr(totalParams) * cFloatSize

    ptr := C.malloc(C.size_t(ptrArrLength + paramArrLength*uintptr(totalObj)))
    defer C.free(ptr)

    ptrSlice := unsafe.Slice((**C.float)(ptr), totalObj)
    for i, obj := range values {
        paramArrPtr := (*C.float)(unsafe.Add(ptr, ptrArrLength+uintptr(i)*paramArrLength))
        ptrSlice[i] = paramArrPtr

        paramSlice := unsafe.Slice(paramArrPtr, totalParams)
        for j, param := range obj {
            paramSlice[j] = (C.float)(param)
        }
    }

    C.foo((**C.float)(ptr), (*C.double)(&results[0]))

    return results
}

Is that safe implementation? Can I pass pointer of result data? As far as I know, this pointer will be pinned because it passed to C function.

But I want to allocate less memory just reusing Go memory, I've learned about runtime.Pinner that make pointer pinned until runtime.Pinner.Unpin() invocation. I tried to write another implementation using pinner:

func FooWrapper(values [][]float32) []float64 {
    length := len(values)
    results := make([]float64, length)

    pinner := runtime.Pinner{}
    defer pinner.Unpin()

    arr := (**C.float)(C.malloc(C.size_t(uintptr(length) * cPointerSize)))
    defer C.free(unsafe.Pointer(arr))
    slice := unsafe.Slice(arr, length)
    for i, v := range values {
        pinner.Pin(&v[0])
        slice[i] = (*C.float)(&v[0])
    }

    C.foo(arr, (*C.double)(&results[0]))

    return results
}

But, unfortunately, this code doesn't work

runtime: pointer 0xc016ecbfc0 to unused region of span span.base()=0xc016eca000 span.limit=0xc016ecbfa0 span.state=1
fatal error: found bad pointer in Go heap (incorrect use of unsafe or cgo?)

Do I use runtime.Pinner wrong (as far as I know, I can pin slice data)? Or there is another error in this code. Are there some implementations for passing 3d (4d and so on) array to C function except for allocatiing and copying all data to C memory?


Solution

  • I've found the answer to my question. Use of malloc is dangerous because of uninitialized memory which GC can recognize as 'bad' when I try to write Go pointers to C memory, so if I change malloc to calloc there will be no problem. Of course this is not the best solution, it's better to allocate memory in Go because it's faster and safer.

    All discussion here.