Michael, Here is a Go solution to the OP's problem. It uses Go memory and Go pointers passed to C in Go memory point to pinned Go memory.
https://go.dev/play/p/KLdenIesz1K Peter On Monday, January 29, 2024 at 12:21:18 PM UTC-5 Michael Knyszek wrote: > Thanks for the reproducer. I think I've puzzled out what's going wrong and > it's pretty subtle. > > TL;DR: You can work around this by either calling `calloc` or just > allocating `inRows` as Go memory and pinning that as well. The latter will > be safer and faster overall. It's not totally clear to me at this moment > whether just using `calloc` instead should be sufficient. Read on for more > details. > > Writing pointers into C memory from Go may invoke a write barrier. The > current write barrier is a hybrid barrier: it writes down the pointers that > are both being written, and those that are being deleted in the write. > > In this particular case, we're writing a Go pointer into C memory *from > Go*. If that C memory isn't zeroed *and* it points to some dead Go > memory, we have a problem. That appears to be what's happening here. If I > use calloc instead of malloc to create inRows, the problem goes away. Also, > if I create inRows in Go and pin it, that also works. > > I think this is actually documented in the cgo pointer passing rules ( > https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers), but we all missed it: > > Note: the current implementation has a bug. While Go code is permitted to > write nil or a C pointer (but not a Go pointer) to C memory, the current > implementation may sometimes cause a runtime error if the contents of the C > memory appear to be a Go pointer. Therefore, avoid passing uninitialized C > memory to Go code if the Go code is going to store pointer values in it. > Zero out the memory in C before passing it to Go. > > > AFAICT, the rest of the page does not say one way or the other whether Go > code writing a Go pointer into C memory is allowed (note that, > counterintuitively, *C.float is a Go pointer, because it may point to Go > memory). > > One question that I have to wonder about is whether it's OK to write a Go > pointer into C memory at all and if we should just restrict that like we > did before. The text above already implies that it is not OK, in which case > the code in this thread is invalid and inRows basically must be allocated > on the Go side. However, I think this may also just be an oversight from > when I was updating the documentation for pinning; I think my original > intent was to say that it's OK for Go code to write pinned Go pointers to C > memory. > > Anyway, this needs more thought, since allowing Go code to write Go > pointers to C memory (pinned or not) may end up restricting GC > implementations in a way we don't want to. I filed > https://github.com/golang/go/issues/65349 to track this. > > On Mon, Jan 29, 2024 at 11:17 AM peterGo <go.pe...@gmail.com> wrote: > >> Michael, >> >> The OP appears to have lost interest in debugging. Here's my minimal, >> reproducible example that produces the same fatal errror: >> >> https://go.dev/play/p/flEmSh1euqR (run locally) >> >> If the runtime.GC() statement is uncommented then the fatal error does >> not occur. >> >> The use of this profligate, inefficient algorithm is for debugging >> purposes only. The use of runtime.Pinner is not required. A single >> implicitly pinned C wrapper function argument pointing to contiguous >> underlying slice data arrays would suffice. >> >> Peter >> >> >> On Friday, January 26, 2024 at 12:05:38 PM UTC-5 Michael Knyszek wrote: >> >>> Ignoring more efficient ways to pass memory to C for a moment, >>> superficially I do think your code using Pinner should work. Do you have a >>> full reproducer? It's hard to tell from just looking at your code if your >>> code is the problem, or its just enough to trigger some other cgo issue >>> elsewhere in your codebase. There's a slim chance this is a bug in the >>> Pinner implementation, but that would be surprising to me. The Pinner >>> trivially keeps all pointers passed to it live by just holding onto their >>> references. >>> >>> Also, have you tried running your code with GODEBUG=cgocheck=1 and/or >>> building your code with GOEXPERIMENT=cgocheck2? That might help identify >>> what the issue is. >>> On Friday, January 26, 2024 at 7:05:45 AM UTC-5 Tamás Gulácsi wrote: >>> >>>> To convert a Go slice to C array is easy with unsafe.Slice. >>>> >>>> The problem is that the multi-dimensional C array is an array of >>>> pointers (*(*float64)) >>>> which cannot travel between the C/Go barrier. >>>> >>>> In your example, you flatten your slice, and recreate the pointers in >>>> that one big slice. >>>> You could go on with that example, but from the Go side: >>>> >>>> 1. have a "flat" []float64 wihich is N*M >>>> 2. have the [][]float64 as subslices of that one big flattened >>>> []float64: flat[i*M:i*(M+1)] >>>> 3. send that flat slice to the C side with unsafe.Slice >>>> 4. have the C side implement the same subslicing: create a []*float64 >>>> array and have each point to the corredt &flat[i*M]. >>>> 5. profit >>>> >>>> >>>> Denis a következőt írta (2024. január 26., péntek, 1:18:59 UTC+1): >>>> >>>>> 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? >>>>> >>>> -- >> You received this message because you are subscribed to a topic in the >> Google Groups "golang-nuts" group. >> To unsubscribe from this topic, visit >> https://groups.google.com/d/topic/golang-nuts/_7bUuHX0ca4/unsubscribe. >> To unsubscribe from this group and all its topics, send an email to >> golang-nuts...@googlegroups.com. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/582e4c81-28f2-40f5-a083-259a897bb45an%40googlegroups.com >> >> <https://groups.google.com/d/msgid/golang-nuts/582e4c81-28f2-40f5-a083-259a897bb45an%40googlegroups.com?utm_medium=email&utm_source=footer> >> . >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/8d50a5ef-ae7d-4737-818a-c69311452ee2n%40googlegroups.com.