Hacker News new | ask | show | jobs
by crawshaw 4302 days ago
You can do your own pinning, which is what I do in gobind[1]. The basic idea is to keep a map in Go of the pointers:

    var ptrs = make(map[uintptr]unsafe.Pointer)
When passing a pointer from Go to C:

    var p unsafe.Pointer = ...
    id := uintptr(p)
    ptrs[id] = p
    C.Fn(id)
When C returns the pointer to Go to be used, run it through the ptrs map again

    //export GoFn
    func GoFn(id uintptr) {
        p := ptrs[id]
        // ... use p
    }
Don't forget a cleanup function where you delete(ptrs, id). If you want to pass the same pointer multiple times to C and keep it comparable, you'll need a second map.

All of this is requires being very careful, but the hard part is the notion of holding references to memory outside the realm of the GC. I don't believe a runtime-assisted pinning API can do any better than what you can do yourself with a map.

[1] https://godoc.org/code.google.com/p/go.mobile/cmd/gobind

3 comments

That's a fine model when you want to hand a reference to a Go value, just to be used in Go itself, but it's not actual pinning. The data can still move, which means you cannot use the data in C, for example to share a buffer without copying into another temporary buffer managed by the C allocator.
That's right, you cannot dereference the pointer from C. If you want to use allocated memory from both C and Go, you'll need to allocate it in C.
Please note that the details aren't defined yet, and Ian indicates in a comment in this HN thread that some sort of pinning may exist. I also expect that to happen, given that a strict rule would break a lot of packages and turn what is today trivial into boring and slow code.
What's the use-case for passing a pointer to C that can't be de-referenced?
You can pass it back into Go for dereferencing, at which point you can use the uintptr => unsafe.Pointer map, and it will be correct since the moving GC will have preserved the unsafe.Pointer address at the right location.

The pattern is useful. It just doesn't handle the most common case, which is passing a buffer or a simple output parameter into a C function.

I don't think this would really handle the pinning aspect if a compacting GC is implemented, would it? It does keep the pointer alive, but it technically doesn't do anything to prevent the GC from changing the pointer.
The uintptr version of the pointer is not moved by the GC, so it acts as a stable identifier.
This does not work if the GC updates the pointer concurrently.
The GC will not update a pointer once it is converted to the type uintptr. It becomes invisible to the precise collector, so the uintptr acts as an id value.
Worse, it might be collected then as the collector might think it is no longer in use.
But it's in the map, so it's in use.
This remark is what triggered my comment

> It becomes invisible to the precise collector

So does the conversion to uintptr remove it from the root lists the GC searches?

It's a map[uintptr]unsafe.Pointer. The key is invisible to the collector, but the value is an unsafe.Pointer, which is will be managed for you by the precise collector.