It looks like Go 1.4 will remove support for Go packages containing C code (as described below, don’t confuse this with CGO), so enjoy it while it lasts.
This is a short post designed to illustrate how Go package authors can write package level functions in C and call them from Go code without using cgo
. The code for this article is available on GitHub, https://github.com/davecheney/ccode.
Some warnings
Before we start, there are a few warnings that need to be spelled out
- Using C code is inherently unsafe, not just because it unholsters all the C footguns, but because you can address any symbol in the runtime. With great power comes great responsibility.
- The Go 1 compatibility guarantee does not extend to C code.
- C functions cannot be inlined.
- Escape analysis cannot follow values passed into C functions.
- Code coverage does not extend to C functions.
- The C compilers (5c, 6c, 8c) are not as optimised as their companion Go compilers, you may find that the code generated is not as efficient as the same code in Go.
- You are writing plan 9 style C code, which is a rough analogue of C89.
Returning a value
The first example is a simple function called True
that always returns true
.
void ·True(bool res) {
res = true;
FLUSH(&res);
}
Even with this simple example there is a lot going on, let’s start with the function signature. The signature of True
is void ·True(bool res)
. The void
return code is required as all C to Go interworking is done via arguments passed on the stack. The Interpunkt, the middle dot, ·
, is part of the package naming system in Go. By proceeding the name of the function with ·
we are declaring this function is part of the current package. It is possible to define C functions in other packages, or to provide a package name before the interpunkt, but it gets complicated when your package is heavily namespaced and so is beyond the scope of this article.
The next part of the function signature is the return argument specified in the C declaration style. Both calling and return arguments are supplied as parameters to any C function you want to call from Go. We’ll see in a moment how to write the corresponding forward declaration.
Moving on to the body of the function, assigning true
to res
is fairly straight forward, but the final line, FLUSH(&res)
needs some explanation. Because res
is not used inside the body of the function a sufficiently aggressive compiler may optimise the assignment away. FLUSH
is used to ensure the final value of res
is written back to the stack.
The forward declaration
To make the True
function available to our Go code, we need to write a forward declaration. Without the forward declaration the function is invisible to the Go compiler. This is unrelated to the normal rules for making Go a symbol public or private via a capital letter.
A forward declaration for the True
function looks like this
// True always returns true.
func True() bool
The forward declaration says that True
takes no arguments and returns one boolean argument. As this is a normal Go function, you can attach a comment describing the function which will appear in godoc (comments on the function in C code will not appear in documentation).
That is all you need to do make True
available to Go code in your package. It should be noted that while True
is a public function, this was not required.
Passing arguments to C functions
Extending from the previous example, let’s define a function called Max
which returns the maximum of two int
s.
void ·Max(intptr a, intptr b, intptr res) {
res = a > b ? a : b;
FLUSH(&res);
}
Max
is similar to the previous function; the first two arguments are function arguments, the final is the return value. Using res
for the name of the return argument is not required, but appears to be the convention used heavily throughout the standard library.
The type of a
and b
is intptr
which is the C equivalent of Go’s platform dependant int
type.
The forward declaration of Max
is shown below. You can see the how function arguments and return values map between Go and C functions.
// Max returns the maximum of two integers.
func Max(a, b int) int
Passing addresses
In the previous two examples we have passed values to functions and returned copies of the result via the stack. In Go, all arguments are passed by value, and calling to C functions is no different. For this final example we’ll write a function that increments a value by passing a pointer to that value.
void ·Inc(intptr* addr) {
*addr+=1;
USED(addr);
}
The Inc
function takes the address of a intptr
(a *int
in Go terms), dereferences it, increments it by one, and stores the result at the address addr
. The USED
macro is similar in function to FLUSH
and is used mainly to silence the compiler.
Looking at the forward declaration, we define the function to take a pointer to the int
to be incremented.
// Inc increments the value of the integer add address p.
func Inc(p *int)
Putting it all together
To demonstate using these C defined functions in Go code I’ve written a few tests which exercise the code. The code for the tests are here, and the results of running the tests are shown below.
% go test -v
github.com/davecheney/ccode
=== RUN TestTrue
--- PASS: TestTrue (0.00 seconds)
=== RUN TestMax
--- PASS: TestMax (0.00 seconds)
=== RUN TestInc
--- PASS: TestInc (0.00 seconds)
PASS
ok github.com/davecheney/ccode 0.005s
Conclusion
In this short article I’ve shown how you can write a Go package that includes functions written in C. While a quite niche use case, it may come in handy for someone and also lays important groundwork for writing packages containing functions in raw assembler.
Pingback: How to include C code in your Go package (without using cgo) | Rocketboom