-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/cgo: do not let Go pointers end up in C #8310
Labels
Milestone
Comments
Just for reference, and not meant as implementation suggestion: Java's JNI passes pointers as "handles": A JNI-wrapper (a little stub, generated on-the fly for each call signature) pushes the arguments on the stack again in C (reverse) order. For each Java object, the stack address of the corresponding Java pointer is pushed. In C, that stack address (which is immutable for the duration of the C call) acts as the handle via which the underlying Java object is accessed. This permits GC to move objects around, but comes at the cost of an extra (hopefully inlined) function call and indirection for each Java data structure access from within C. It also requires the respective C code to be written specifically with JNI in mind. |
Another option is to pin pointers that are passed into C.f for the duration of C.f. This allows the C code to use raw pointers, but does not allow the C code to squirrel the pointer away past the immediate call. There's no way to enforce the no-squirrel rule, unfortunately. This requires a mechanism to pin objects in the heap. If/when we have a copying collector this will be a pain, but might be necessary for other reasons (runtime internals?). |
The copying proposal would be bad for code like my ui package, which wants to store a Go pointer on the C side for the purposes of giving it back to Go when an event comes in. dominikh says he also has callback functions which work similarly. I don't know how you intend to deal with this, unless it's something through unsafe.Pointer, but then you would need to use unsafe.Pointer for C-side types allocated in Go, which is also ugly. |
In the long run there just isn't any way that we can support passing a Go pointer into C. We want to make it possible to write a moving garbage collector, but that means that the garbage collector has to be able to change all pointers. The GC won't be able to see pointers stored in C code. So we can't permit that. To pass a pointer into C and get it back you will have to do something like use a map[uintptr]*X. When you want to pass the pointer down, convert to uintptr and store the pointer in the map. When you get it back, use the map to get the real Go pointer. |
Thank you for the code, Ian. But I don't see how your proposal helps with original issue raised by rsc: var x C.T C.f(&x) What will you do here? Also, there is still issue of Go objects been moved by GC. For example, in windows net package we pass Go buffers (just like in #8) to kernel. The syscall returns immediately, while kernel proceed to write to passed buffer. We keep pointer to buffer in Go, until kernel finished writing (just like your proposal suggests). But that approach will fail, if GC will move the buffer. Alex |
perhaps we can use sync.Pool to manage buffers allocated on C heaps, then they could be made pretty cheap to use. One main downside is that we can't return C buffers as is to Go land, otherwise it becomes impossible to manage their life time and might lead to memory leaks, and this necessitates at least one copying from C buffer to Go buffer. |
> We don't literally have to allocate by calling C code. We just have to allocate using an allocator that the Go GC does not examine. If this is provided by Go runtime or similar, then it should work just fine. I assume we are talking about memory that needs to live after syscall returns. You would have to find a separate solution for any code that just uses Go memory during syscall, just like: var x C.T C.f(&x) Alex |
I don't understand what is meant by a pointer "crossing the boundary" to C. If Go maintains the pointer while C is doing stuff with the underlying data, is that okay? In github.com/gonum/blas/cblas [0], we send []float64 to C by sending unsafe.Pointer(&x[0]) to C. The x variable stays alive on the Go side, but the values it points to are modified by the C code. Is the plan to still support this? [0] https://github.com/gonum/blas/blob/master/cblas/blas.go |
tracey.brendan: see comment #16. We haven't yet decided what the exact rules will be, but the general case of passing a pointer to C will not work even if the pointer is maintained on the Go side. I don't know whether the use case you describe will work or not, but it's clearly an important case to keep in mind. |
In my opinion map[token]pointer seems really inelegant; I tried to actually remove similar (map[C pointer]Go pointer with more data) from package ui's rewrite (redo/ folder). I'm willing to hear counterarguments, though. Also by moving GC do you mean only stack pointers will move or will heap pointers move too? |
This is going to break a huge number of existing cgo bindings. I understand this is necessary to support things like copying/compacting GC... but in light how much existing (and in production) Go software this is going to break I imagine whatever release this becomes reality in, many people will be stuck on the previous release quite a while as they scramble to replace offending dependencies. Since the common case is buffers ([]byte, etc) being passed to C, filled and handed back, what about introducing a cgo function to make this situation easier: buf := C.make([]byte, 2048) defer C.release(buf) C.someFunction((...)buf.Data) C.make would be like make, except the GC would be unaware of the resulting memory (different memory arena from normal GC'd memory?). C.release would be like 'free' but rather then freeing memory it just makes the GC aware of it and eligible for later collection. (These functions are meant to be Go functions so the above code only crosses the boundary once. Also notice no extra copying is necessary to go on to use buf inside of Go objects [unless the GC decides to copy a "released" slice into the normal GC space later of course]) I hope this would make it trivial to "fix" most broken cgo dependencies and at the same time stay out of the way of GC advancement. The alternative to something like this is either making two cgo calls (one to perform the action and make the allocation) and another to free the buffer later OR to create some kind of pooling object that allocates the pool on the C side and returns batches later on the Go side. This solution isn't perfect but I think its better than the alternatives. In some ways this is like pinning without the burdons of supporting arbitrary one of pinning in the primary GC arena. |
See also #12116 . |
Proposal at #12416 . |
Proposal #12416 was implemented. Closing this issue. |
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
The text was updated successfully, but these errors were encountered: