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)- π« Zero CGO - Pure Go, no C compiler needed
- β‘ Fast - ~100ns FFI overhead (benchmarks)
- π Cross-platform - Windows + Linux + macOS AMD64; ARM64 in development
- π 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
go get github.com/go-webgpu/goffipackage 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.
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=abortor use Linux/macOS - Fix planned: Go 1.26 (#58542)
Variadic functions NOT supported (printf, sprintf, etc.)
- Workaround: Use non-variadic wrappers (
putsinstead ofprintf) - Planned: v0.5.0 (Q2 2025)
Struct packing follows System V ABI only
- Windows
#pragma packdirectives NOT honored - Workaround: Manually specify
Size/AlignmentinTypeDescriptor - Planned: v0.5.0 (platform-specific rules)
- Composite types (structs) require manual initialization
- Cannot interrupt C functions mid-execution (use
CallFunctionContextfor timeouts) - ARM64 in development (v0.3.0, cross-compiles but untested on real hardware)
- No bitfields in structs
See CHANGELOG.md for full details.
- CHANGELOG.md - Version history, migration guides
- ROADMAP.md - Development roadmap to v1.0
- docs/PERFORMANCE.md - Comprehensive performance analysis
- CONTRIBUTING.md - Contribution guidelines
- SECURITY.md - Security policy and best practices
- CODE_OF_CONDUCT.md - Community standards
- examples/ - Working code examples
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 failuresLibraryError- Library loading/symbol lookupCallingConventionError- Unsupported calling conventionsTypeValidationError- Type descriptor validationUnsupportedPlatformError- Platform not supported
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)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.cgocallfor 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).
- Callback API (
NewCallback) for C-to-Go function calls - 2000-entry trampoline table for async operations
- WebGPU async APIs now fully supported
- ARM64 support (Linux + macOS AAPCS64 ABI) - in development
- AAPCS64 calling convention with X0-X7, D0-D7 registers
- 2000-entry callback trampolines for ARM64
- Builder pattern API:
lib.Call("func").Arg(...).ReturnInt() - Variadic function support (printf, sprintf, etc.)
- Platform-specific struct alignment (Windows
#pragma pack) - Windows ARM64 (experimental)
- 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.
# 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 (in development) |
| macOS | arm64 | π‘ v0.3.0 | AAPCS64 ABI (in development) |
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
MIT License - see LICENSE for details.
- purego - Inspiration for CGO-free FFI approach
- libffi - Reference for FFI architecture patterns
- Go runtime -
runtime.cgocallfor safe stack switching
- go-webgpu - WebGPU bindings using goffi (coming soon!)
- wgpu-native - Native WebGPU implementation
Made with β€οΈ for GPU computing in pure Go
Last updated: 2025-11-28 | goffi v0.2.1