Skip to content

Commit

Permalink
Add README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
csnewman committed Dec 9, 2024
1 parent 9dbcc50 commit 94525fb
Showing 1 changed file with 131 additions and 1 deletion.
132 changes: 131 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,132 @@
# goblend
CGO-less FFI for Go
CGO-less FFI for Go.

goblend uses the `.syso` trick from [GcToolchainTricks](https://go.dev/wiki/GcToolchainTricks), to enable the
embedding of object files using only the inbuilt Go linker. When combined with a small amount of trampoline assembly,
this allows programs to use pre-compiled code from another language without requiring a cross-compiler.

The main motivation behind this library is to enable library authors to reuse native code, without forcing downstream
users to modify their build steps. Provided a user is on a supported platform, a goblend library can be used like any
other Go library, with a simple `go get $pkg`.

The main downside is that all non-Go code must be pre-compiled outside of `go build`, and for library authors, must be
commited to the repository. The friction of using native code is shifted from the end user to the library maintainers.

## Features
- CGO-less FFI calls and callbacks (`CGO_ENABLED=0`)
- Cross-compilation
- e.g. Build for Windows from a Linux machine, without configuring a cross-compiler.
- CGO compatible (`CGO_ENABLED=1`)
- `runtime.iscgo == true` on all platforms
- The runtime is built from the original CGO source (for `.S` and `.c` files) for all supported
platforms.
- Should be (virtually) indistinguishable from CGO.

## Supported platforms

- Linux: arm64
- Windows: amd64
- Darwin: arm64

## Example

See `examples/add` for a complete example.

To demonstrate the library is compatible:
```bash
$ go mod init example
$ go get github.com/csnewman/goblend/examples/add
$ go run github.com/csnewman/goblend/examples/add
2024/12/09 11:23:39 Add example
Hello world from Zig!
2024/12/09 11:23:39 123
2024/12/09 11:23:39 End of example
```

### Native code

The exported functions must have the following signature:

```zig
export fn add(ptr: *const anyopaque) callconv(.c) u32 {
// code here
}
```

and should be compiled to a native object file (archives/`.a` and libraries/`.dll` are unsupported):

```bash
zig build-obj -O ReleaseFast -target aarch64-linux-gnu --library c -femit-bin=core_linux_arm64.syso add.zig add2.zig etc
```

The native object file should have the `.syso` extension, so the Go linker detects them. On Windows and Linux you can
take advantage of partial linking to reduce the number of object files down to one per platform.

### Native imports

When building, you will likely experience errors such as:

```
github.com/csnewman/goblend/runtime(.text): unknown symbol memset in callarm64
github.com/csnewman/goblend/runtime(.text): unknown symbol memset in callarm64
github.com/csnewman/goblend/runtime(.text): relocation target memset not defined
```

This is caused by your native code importing other code, such as `memset` from `libc` in the above example. As such, you
will need to create a file per platform, such as `core_linux.go` that imports the necessary dependencies:

```Go
//go:build linux

package runtime

import "unsafe"

//go:cgo_import_dynamic memset memset "libc.so.6"

// More dependencies...
```

Note: On Windows, the situation is slightly more complex due to `__imp_{some name}` symbols being a pointer to a
function, while the `{some name}` symbol expects to be the function itself. Please see `WriteFile` in the `examples/add`
example.

### Calling from Go

First you will need to create a `trampoline` for each function you intend to call: (`core.s`)

```asm
#include "go_asm.h"
#include "funcdata.h"
#include "textflag.h"
TEXT goblend_add(SB),NOSPLIT|NOFRAME,$0-0
JMP add(SB)
RET
```

Note, no parameter information is necessary here, as this is a simple `jmp`.

Then in a Go file, such as `core.go`, you will need to get a ptr to the trampoline:

```go
package main

import "unsafe"

//go:linkname add goblend_add
var add uintptr
var addPtr = uintptr(unsafe.Pointer(&add))
```

Finally, the native function can be called:

```go
ret := goblend.Call(addPtr, 0)
```

The `0` argument is the `ptr` to pass to the native function. You can pass and return any arbitrary data this way.

### Callbacks

TODO: Create example and document.

0 comments on commit 94525fb

Please sign in to comment.