How to call a C `writev` styled library api via cgo without allocation and copying of the whole byte slice vector?

112 views
Skip to first unread message

rmfr

unread,
Mar 13, 2021, 9:24:52 AM3/13/21
to golang-nuts
Say here is a C api like `ssize_t writev(const struct iovec *iov, int iovcnt)` which the definition of iovec is like below:

```
struct iovec {
     uint8_t   *Base;  /* Base address. */
     uint64_t Len;    /* Length. */
 };
```

For C api which like `ssize_t write(const void *buf, size_t nbyte)`, the solution would be quite straight forward:

```
bs := make([]byte, 1024*1024*512)
// without extra memory allocation and copying of the whole input byte slice :-D
rc := C.write(unsafe.Pointer(&bs[0]), C.int(len(bs)))
```

But how to call C `writev` style API without extra memory allocation or copying of the whole byte slice vector?

```
bsv := make([][]byte, 1024)
for i := range bsv{
    bsv[i] = make([]byte, 5*1024*(rand.Intn(i)+1))
}
// assuming that allocation of a iovec array is acceptable
// but allocation and copying of all bsv[x] byte slice member is unacceptable
//
iovec := make([]syscall.Iovec, len(bsv))
for i := range bsv {
        bs := bsv[i]
        if len(bs) > 0 {
            iovec[i].Base = unsafe.Pointer(&bs[0])
            iovec[i].Len = uint64(len(bs))
        }
}
//
// rc := C.writev( /* how? :-( */)
rc := C.writev(unsafe.Pointer(&iovec[0]), C.int(len(iovec))) // Does this code is right and safe?
```

Does the code above is right?

I have read cgo's docs carefully, and here is a constraint from https://golang.org/cmd/cgo/#hdr-Passing_pointers:

> Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers.

If the Go memory pointed by `unsafe.Pointer(&iovec[0])` contains pointer which points to these byte slice members of bsv, so it would be a violation of the cgo constraint above. And that means you could not call C `writev` style API without allocation and copying of the whole vector.

Please correct me if I get something wrong. Thanks a lot :-D

rmfr

unread,
Mar 13, 2021, 11:41:32 PM3/13/21
to golang-nuts
I have read this proposal carefully https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md#proposal
  1. Go code may pass a Go pointer to C provided that the Go memory to which it points does not contain any Go pointers.
  • The C code must not store any Go pointers in Go memory, even temporarily.
  • When passing a pointer to a field in a struct, the Go memory in question is the memory occupied by the field, not the entire struct.
  • When passing a pointer to an element in an array or slice, the Go memory in question is the entire array or the entire backing array of the slice.
  • Passing a Go pointer to C code means that that Go pointer is visible to C code; passing one Go pointer does not cause any other Go pointers to become visible.
  • The maximum number of Go pointers that can become visible to C code in a single function call is the number of arguments to the function.
 So does it is correct that you cannot call some C api like `writev` safely without concatenating all the byte slices together into one big byte slice?

Ian Lance Taylor

unread,
Mar 15, 2021, 8:12:16 PM3/15/21
to rmfr, golang-nuts
I think that everything you wrote is correct.

In some cases you may be able to work around this problem by creating
the byte slices in memory allocated using C.malloc.

Or, in some cases you can use https://golang.org/pkg/net/#Buffers to
get Go to call writev for you.

But I don't think there is any good solution for the general case.

Ian
Reply all
Reply to author
Forward
0 new messages