pureffi is a transparent, drop-in replacement for the excellent ebitengine/purego library, built entirely on top of go-webgpu/goffi. It provides 1:1 API compatibility with purego while eliminating linker conflicts when both ecosystems are used within the same Go project.
Both purego (pioneered by the Ebitengine team) and goffi (engineered by the go-webgpu team) are outstanding achievements in the Go ecosystem. They proved that calling foreign C libraries is possible without a C compiler, using Go's internal runtime machinery.
To do this under CGO_ENABLED=0, both libraries include their own copy of internal/fakecgo to satisfy the Go runtime's requirements for dynamic symbol resolution.
However, because they both define identical runtime symbols (such as _cgo_init, _cgo_thread_start, etc.), including both libraries in the dependency tree of a single project results in a fatal linker error:
duplicate definition of symbol _cgo_initThis conflict made it difficult to build unified, CGO-free Go applications that wanted to leverage both Ebitengine packages (like ebiten or oto) and go-webgpu packages at the same time.
pureffi solves this conflict by implementing the exact purego public API (Dlopen, Dlsym, RegisterFunc, NewCallback, SyscallN, and the objc / cstrings subpackages) using goffi as the underlying execution engine.
Because pureffi does not include its own duplicate assembler trampolines or fakecgo runtimes, it resolves the symbol conflict entirely. Your binary will compile successfully while using the highly optimized and audited FFI pathway provided by goffi.
- 1:1 API Compatibility: Works as a drop-in replacement via Go's
replacedirective. You do not need to change a single import statement in your existing codebase. - Unified Dependency Graph: Allows using
ebiten-based projects andgo-webgpumodules together without CGO or linker errors. - Apple Silicon C-Variadic ABI Fix: Fixes a long-standing ABI mismatch on macOS/ARM64. Under the hood,
pureffiuses runtime introspection (dladdr) to detect whether the target function is a true C-variadic function (likesprintf, which requires variadic arguments to be passed on the stack) or a standard dynamic dispatcher (likeobjc_msgSend, which expects them in registers), ensuring stable execution. - Optimized Fast-Path: Leverages object pooling on dispatching hot-paths, reducing fast-path heap allocations down to a single allocation per call.
- Fixed-size array arguments support in RegisterFunc
- Structs by Value: Supports passing and returning structs by value.
- CString and GoString functions to match the Go C package
- Robust Struct and Array Passing: Inherits
goffi's mature, recursive struct-passing by value and by reference. - errno capturing
- structs.HostLayout support
Initially, pureffi started as a straightforward, lightweight wrapper over goffi—primarily to resolve the symbol linker conflict without forcing developers to juggle custom build tags.
However, once the wrapper was working, the results looked promising enough that I decided to see if I could make it even better. So I spent some time optimizing dispatcher hot-paths and addressing a few community requests and architectural discussions from the purego issue tracker.
Thanks to goffi's highly optimized engine and targeted call-path optimizations, pureffi achieves lower latencies and fewer heap allocations on certain execution paths—especially the fast-path dispatcher.
Below is a comparison of benchmarks run on AMD64 Linux:
BenchmarkFastPath-4 2005327 574.4 ns/op 264 B/op 5 allocs/op
BenchmarkSlowPath-4 1861425 639.5 ns/op 296 B/op 6 allocs/op
BenchmarkSyscallN-4 5726388 199.6 ns/op 216 B/op 2 allocs/op
BenchmarkFastPath-4 4753861 255.5 ns/op 208 B/op 1 allocs/op
BenchmarkSlowPath-4 1718974 691.6 ns/op 304 B/op 7 allocs/op
BenchmarkSyscallN-4 4487470 259.6 ns/op 216 B/op 2 allocs/op
Notably, pureffi reduces FastPath heap allocations from 5 down to just 1 allocation per operation, cutting average latency by over 50%.
To use pureffi in your project, simply add a replace directive to your go.mod file.
module your_project
go 1.25.5
replace github.com/ebitengine/purego => github.com/unxed/pureffi v0.1.6Once the replacement is configured, Go will automatically route all import "github.com/ebitengine/purego" statements to pureffi's goffi-backed implementation. No further action is required.
You can run the comprehensive test suite (which includes a "full-circle" roundtrip FFI test that calls Go callbacks back through C-ABI boundaries) using:
go test -v ./...pureffi is merely a bridge between two monumental projects. I would like to express our deepest gratitude to:
- The Ebitengine team (Hajime Hoshi and contributors): For creating
purego, proving that CGO-less FFI is possible, and establishing the groundwork for the modern pure-Go graphics ecosystem. - The go-webgpu team (kolkov and contributors): For engineering
goffiwith its highly optimized, audited ABI calling conventions, extensive structure alignment handling, and robust platform support.
This project exists solely to allow these two fantastic libraries to coexist and power the next generation of pure-Go high-performance software.
BSD 3-claue. See the LICENSE file for details.