goffi - Zero-CGO FFI for Go

Pure Go Foreign Function Interface (FFI) for calling C libraries without CGO. Primary use case: WebGPU bindings for GPU computing in pure Go.
// Call C functions directly from Go - no CGO required!
handle, _ := ffi.LoadLibrary("wgpu_native.dll")
wgpuCreateInstance := ffi.GetSymbol(handle, "wgpuCreateInstance")
ffi.CallFunction(&cif, wgpuCreateInstance, &result, args)
β¨ Features
- π« Zero CGO - Pure Go, no C compiler needed
- β‘ Fast - ~100ns FFI overhead (benchmarks)
- π Cross-platform - Windows + Linux + macOS (AMD64 + ARM64)
- π Callbacks - C-to-Go function calls for async APIs (v0.2.0)
- π Type-safe - Runtime type validation with detailed errors
- π¦ Production-ready - 87% test coverage, comprehensive error handling
- π― WebGPU-optimized - Designed for wgpu-native bindings
π Quick Start
Installation
go get github.com/go-webgpu/goffi
Basic Example
package main
import (
"fmt"
"runtime"
"unsafe"
"github.com/go-webgpu/goffi/ffi"
"github.com/go-webgpu/goffi/types"
)
func main() {
// Load standard library
var libName, funcName string
switch runtime.GOOS {
case "linux":
libName, funcName = "libc.so.6", "strlen"
case "windows":
libName, funcName = "msvcrt.dll", "strlen"
default:
panic("Unsupported OS")
}
handle, err := ffi.LoadLibrary(libName)
if err != nil {
panic(err)
}
defer ffi.FreeLibrary(handle)
strlen, err := ffi.GetSymbol(handle, funcName)
if err != nil {
panic(err)
}
// Prepare call interface (reuse for multiple calls!)
cif := &types.CallInterface{}
err = ffi.PrepareCallInterface(
cif,
types.DefaultCall, // Auto-detects platform
types.UInt64TypeDescriptor, // size_t return
[]*types.TypeDescriptor{types.PointerTypeDescriptor}, // const char* arg
)
if err != nil {
panic(err)
}
// Call strlen("Hello, goffi!")
testStr := "Hello, goffi!\x00"
strPtr := unsafe.Pointer(unsafe.StringData(testStr))
var length uint64
err = ffi.CallFunction(cif, strlen, unsafe.Pointer(&length), []unsafe.Pointer{strPtr})
if err != nil {
panic(err)
}
fmt.Printf("strlen(%q) = %d\n", testStr[:len(testStr)-1], length)
// Output: strlen("Hello, goffi!") = 13
}
FFI Overhead: ~88-114 ns/op (Windows AMD64, Intel i7-1255U)
| Benchmark |
Time |
vs Direct Go |
| Empty function |
88.09 ns |
~400x slower |
| Integer arg |
113.9 ns |
~500x slower |
| String processing |
97.81 ns |
~450x slower |
Verdict: β
Excellent for WebGPU (GPU calls are 1-100Β΅s, FFI is 0.1Β΅s = 0.1-10% overhead)
See docs/PERFORMANCE.md for comprehensive analysis, optimization strategies, and when NOT to use goffi.
β οΈ Known Limitations
Critical
Windows: C++ exceptions crash the program (Go issue #12516)
- Libraries using C++ exceptions (including Rust with
panic=unwind) will crash
- This is a Go runtime limitation, not goffi-specific - affects CGO too
- Workaround: Build native libraries with
panic=abort or use Linux/macOS
- Fix planned: Go 1.26 (#58542)
Variadic functions NOT supported (printf, sprintf, etc.)
- Workaround: Use non-variadic wrappers (
puts instead of printf)
- Planned: v0.5.0 (Q2 2025)
Struct packing follows System V ABI only
- Windows
#pragma pack directives NOT honored
- Workaround: Manually specify
Size/Alignment in TypeDescriptor
- Planned: v0.5.0 (platform-specific rules)
Architectural
- Composite types (structs) require manual initialization
- Cannot interrupt C functions mid-execution (use
CallFunctionContext for timeouts)
- ARM64 - Tested on Apple Silicon (M3 Pro), Linux ARM64 cross-compile verified
- No bitfields in structs
See CHANGELOG.md for full details.
π Documentation
π οΈ Advanced Usage
Typed Error Handling
import "errors"
handle, err := ffi.LoadLibrary("nonexistent.dll")
if err != nil {
var libErr *ffi.LibraryError
if errors.As(err, &libErr) {
fmt.Printf("Failed to %s %q: %v\n", libErr.Operation, libErr.Name, libErr.Err)
// Output: Failed to load "nonexistent.dll": The specified module could not be found
}
}
goffi provides 5 typed error types for precise error handling:
InvalidCallInterfaceError - CIF preparation failures
LibraryError - Library loading/symbol lookup
CallingConventionError - Unsupported calling conventions
TypeValidationError - Type descriptor validation
UnsupportedPlatformError - Platform not supported
Context Support (Timeouts/Cancellation)
import (
"context"
"time"
)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
err := ffi.CallFunctionContext(ctx, cif, funcPtr, &result, args)
if err == context.DeadlineExceeded {
fmt.Println("Function call timed out!")
}
// Auto-detect platform (recommended)
convention := types.DefaultCall
// Or explicit:
switch runtime.GOOS {
case "windows":
convention = types.WindowsCallingConvention // Win64 ABI
case "linux", "freebsd":
convention = types.UnixCallingConvention // System V AMD64
}
ffi.PrepareCallInterface(cif, convention, returnType, argTypes)
ποΈ Architecture
goffi uses a 4-layer architecture for safe GoβC transitions:
Go Code (User Application)
β ffi.CallFunction()
runtime.cgocall (Go Runtime)
β System stack switch + GC coordination
Assembly Wrapper (Platform-specific)
β Register loading (RDI/RCX + XMM0-7)
JMP Stub (Function pointer indirection)
β Indirect jump
C Function (External Library)
Key technologies:
runtime.cgocall for GC-safe stack switching
- Hand-written assembly for System V AMD64 (Linux) and Win64 (Windows) ABIs
- Runtime type validation (no codegen/reflection)
See docs/dev/TECHNICAL_ARCHITECTURE.md for deep dive (internal docs).
πΊοΈ Roadmap
v0.2.0 - Callback Support β
RELEASED!
- Callback API (
NewCallback) for C-to-Go function calls
- 2000-entry trampoline table for async operations
- WebGPU async APIs now fully supported
v0.3.x - ARM64 Support β
RELEASED!
- ARM64 support (Linux + macOS AAPCS64 ABI)
- AAPCS64 calling convention with X0-X7, D0-D7 registers
- HFA (Homogeneous Floating-point Aggregate) returns
- Nested struct and mixed int/float struct support
- 2000-entry callback trampolines for ARM64
- Tested on Apple Silicon M3 Pro
v0.5.0 - Usability + Variadic (Q2 2025)
- Builder pattern API:
lib.Call("func").Arg(...).ReturnInt()
- Variadic function support (printf, sprintf, etc.)
- Platform-specific struct alignment (Windows
#pragma pack)
- Windows ARM64 (experimental)
v1.0.0 - Stable Release (Q1 2026)
- API stability guarantee (SemVer 2.0)
- Security audit
- Reference implementations (WebGPU, Vulkan, SQLite bindings)
- Performance benchmarks vs CGO/purego published
See CHANGELOG.md for detailed roadmap.
π§ͺ Testing
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Current coverage: 89.1%
# Run benchmarks
go test -bench=. -benchmem ./ffi
# Platform-specific tests
go test -v ./ffi # Auto-detects Windows/Linux
| Platform |
Architecture |
Status |
Notes |
| Windows |
amd64 |
β
v0.1.0 |
Win64 ABI, full support |
| Linux |
amd64 |
β
v0.1.0 |
System V ABI, full support |
| macOS |
amd64 |
β
v0.1.1 |
System V ABI, full support |
| FreeBSD |
amd64 |
β
v0.1.0 |
System V ABI (untested) |
| Linux |
arm64 |
β
v0.3.0 |
AAPCS64 ABI, cross-compile verified |
| macOS |
arm64 |
β
v0.3.7 |
AAPCS64 ABI, tested on M3 Pro |
π€ Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
Quick checklist:
- Fork the repository
- Create feature branch (
git checkout -b feat/amazing-feature)
- Write tests (maintain 80%+ coverage)
- Run linters (
golangci-lint run)
- Commit with conventional commits (
feat:, fix:, docs:)
- Open pull request
π License
MIT License - see LICENSE for details.
π Acknowledgments
- purego - Inspiration for CGO-free FFI approach
- libffi - Reference for FFI architecture patterns
- Go runtime -
runtime.cgocall for safe stack switching
Made with β€οΈ for GPU computing in pure Go
Last updated: 2026-01-03 | goffi v0.3.7