blob: cdb229f2dc13cd20cb4b7e4af313452fb4c949c9 [file] [log] [blame] [view]
# Proposal: Rules for passing pointers between Go and C
Author: Ian Lance Taylor
Last updated: October, 2015
Discussion at https://golang.org/issue/12416.
## Abstract
List specific rules for when it is safe to pass pointers between Go
and C using cgo.
## Background
Go programmers need to know the rules for how to use cgo safely to
share memory between Go and C.
When using cgo, there is memory allocated by Go and memory allocated
by C.
For this discussion, we define a *Go pointer* to be a pointer to Go
memory, and a *C pointer* to be a pointer to C memory.
The rules that need to be defined are when and how Go code can use C
pointers and C code can use Go pointers.
Note that for this discussion a Go pointer may be any pointer type,
including a pointer to a type defined in C.
Note that some Go values contain Go pointers implicitly, such as
strings, slices, maps, channels, and function values.
It is a generally accepted (but not actually documented) rule that Go
code can use C pointers, and they will work as well or as poorly as C
code holding C pointers.
So the only question is this: when can C code use Go pointers?
The de-facto rule for 1.4 is: you can pass any Go pointer to C.
C code may use it freely.
If C code stores the Go pointer in C memory then there must be a live
copy of the pointer in Go as well.
You can allocate Go memory in C code by calling the Go function
`_cgo_allocate`.
The de-facto rule for 1.5 adds restrictions.
You can still pass any Go pointer to C.
However, C code may not store a Go pointer in Go memory (C code can
still store a Go pointer in C memory, with the same restrictions as
in 1.4).
The `_cgo_allocate` function has been removed.
We do not want to document the 1.5 de-facto restrictions as the
permanent rules because they are somewhat confusing, they limit future
garbage collection choices, and in particular they prohibit any future
development of a moving garbage collector.
## Proposal
I propose that we permit Go code to pass Go pointers to C code, while
preserving the following invariant:
* The Go garbage collector must be aware of the location of all Go
pointers, except for a known set of pointers that are temporarily
*visible to C code*.
The pointers visible to C code exist in an area that the garbage
collector can not see, and the garbage collector may not modify or
release them.
It is impossible to break this invariant in Go code that does not
import "unsafe" and does not call C.
I propose the following rules for passing pointers between Go and C,
while preserving this invariant:
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.
2. C code may not keep a copy of a Go pointer after the call returns.
* A Go pointer passed as an argument to C code is only visible to C
code for the duration of the function call.
3. A Go function called by C code may not return a Go pointer.
* A Go function called by C code may take C pointers as arguments,
and it may store non-pointer or C pointer data through those
pointers, but it may not store a Go pointer in memory pointed to
by a C pointer.
* A Go function called by C code may take a Go pointer as an
argument, but it must preserve the property that the Go memory to
which it points does not contain any Go pointers.
* C code calling a Go function can not cause any additional Go
pointers to become visible to C code.
4. Go code may not store a Go pointer in C memory.
* C code may store a Go pointer in C memory subject to rule 2: it
must stop storing the pointer before it returns to Go.
The purpose of these four rules is to preserve the above invariant and
to limit the number of Go pointers visible to C code at any one time.
### Examples
Go code can pass the address of an element of a byte slice to C, and C
code can use pointer arithmetic to access all the data in the slice,
and change it (the C code is of course responsible for doing its own
bounds checking).
Go code can pass a Go string to C. With the current Go compilers it
will look like a two element struct.
Go code can pass the address of a struct to C, and C code can use the
data or change it.
Go code can pass the address of a struct that has pointer fields, but
those pointers must be nil or must be C pointers.
Go code can pass a non-nested Go func value into C, and the C code may
call a Go function passing the func value as an argument, but it must
not save the func value in C memory between calls, and it must not
call the func value directly.
A Go function called by C code may not return a string.
### Consequences
This proposal restricts the Go garbage collector: any Go pointer
passed to C code must be pinned for the duration of the C call.
By definition, since that memory block may not contain any Go
pointers, this will only pin a single block of memory.
Because C code can call back into Go code, and that Go code may need
to copy the stack, we can never pass a Go stack pointer into C code.
Any pointer passed into C code must be treated by the compiler as
escaping, even though the above rules mean that we know it will not
escape.
This is an additional cost to the already high cost of calling C code.
Although these rules are written in terms of cgo, they also apply to
SWIG, which uses cgo internally.
Similar rules may apply to the syscall package. Individual functions
in the syscall package will have to declare what Go pointers are
permitted. This particularly applies to Windows.
That completes the rules for sharing memory and the implementation
restrictions on Go code.
### Support
We turn now to helping programmers use these rules correctly.
There is little we can do on the C side.
Programmers will have to learn that C code may not store Go pointers
in Go memory, and may not keep copies of Go pointers after the
function returns.
We can help programmers on the Go side, by implementing restrictions
within the cgo program.
Let us assume that the C code and any unsafe Go code behaves perfectly.
We want to have a way to test that the Go code never breaks the
invariant.
We propose an expensive dynamic check that may be enabled upon
request, similar to the race detector.
The dynamic checker will be turned on via a new option to go build:
`-checkcgo`.
The dynamic checker will have the following effects:
* We will turn on the write barrier at all times.
Whenever a pointer is written to memory, we will check whether the
pointer is a Go pointer.
If it is, we will check whether we are writing it to Go
memory (including the heap, the stack, global variables).
If we are not, we will report an error.
* We will change cgo to add code to check any pointer value passed to
a C function.
If the value points to memory containing a Go pointer, we will
report an error.
* We will change cgo to add the same check to any pointer value passed
to an exported Go function, except that the check will be done on
function return rather than function entry.
* We will change cgo to check that any pointer returned by an exported
Go function is not a Go pointer.
These rules taken together preserve the invariant.
It will be impossible to write a Go pointer to non-Go memory.
When passing a Go pointer to C, only that Go pointer will be made
visible to C.
The cgo check ensures that no other pointers are exposed.
Although the Go pointer may contain pointer to C memory, the write barrier
ensures that that C memory can not contain any Go pointers.
When C code calls a Go function, no additional Go pointers will become
visible to C.
We propose that we enable the above changes, other than the write
barrier, at all times.
These checks are reasonably cheap.
These checks should detect all violations of the invariant on the Go side.
It is still possible to violate the invariant on the C side.
There is little we can do about this (in the long run we could imagine
writing a Go specific memory sanitizer to catch errors.)
A particular unsafe area is C code that wants to hold on to Go func
and pointer values for future callbacks from C to Go.
This works today but is not permitted by the invariant.
It is hard to detect.
One safe approach is: Go code that wants to preserve funcs/pointers
stores them into a map indexed by an int.
Go code calls the C code, passing the int, which the C code may store
freely.
When the C code wants to call into Go, it passes the int to a Go
function that looks in the map and makes the call.
An explicit call is required to release the value from the map if it
is no longer needed, but that was already true before.
## Rationale
The garbage collector has more flexibility when it has complete
control over all Go pointers.
We want to preserve that flexibility as much as possible.
One simple rule would be to always prohibit passing Go pointers to C.
Unfortunately that breaks existing packages, like github.com/gonum/blas,
that pass slices of floats from Go to C for efficiency.
It also breaks the standard library, which passes the address of a
`C.struct_addrinfo` to `C.getaddrinfo`.
It would be possible to require all such code to change to allocate
their memory in C rather than Go, but it would make cgo considerably
harder to use.
This proposal is an attempt at the next simplest rule.
We permit passing Go pointers to C, but we limit their number, and
require that the garbage collector be aware of exactly which pointers
have been passed.
If a later garbage collector implements moving pointers, cgo will
introduce temporary pins for the duration of the C call.
Rules are necessary, but it's always useful to enforce the rules.
We can not enforce the rules in C code, but we can attempt to do so in
Go code.
If we adopt these rules, we can not change them later, except to
loosen them.
We can, however, change the enforcement mechanism, if we think of
better approaches.
## Compatibility
This rules are intended to extend the Go 1 compatibility guidelines to
the cgo interface.
## Implementation
The implementation of the rules requires adding documentation to the
cgo command.
The implementation of the enforcement mechanism requires changes to
the cgo tool and the go tool.
The goal is to get agreement on this proposal and to complete the work
before the 1.6 freeze date.
## Open issues
Can and should we provide library support for certain operations, like
passing a token for a Go value through C to Go functions called from
C?
Should there be a way for C code to allocate Go memory, where of
course the Go memory may not contain any Go pointers?