Skip to content

go-webgpu/goffi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

goffi - Zero-CGO FFI for Go

CI Coverage Go Report Card GitHub release Go version License Go Reference

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 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

πŸš€ 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 }

πŸ“Š Performance

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 in development (v0.3.0, cross-compiles but untested on real hardware)
  • 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!") }

Cross-Platform Calling Conventions

// 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.0 - ARM64 Support (Q1 2025)

  • ARM64 support (Linux + macOS AAPCS64 ABI) - in development
  • AAPCS64 calling convention with X0-X7, D0-D7 registers
  • 2000-entry callback trampolines for ARM64

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 Support

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)

🀝 Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

Quick checklist:

  1. Fork the repository
  2. Create feature branch (git checkout -b feat/amazing-feature)
  3. Write tests (maintain 80%+ coverage)
  4. Run linters (golangci-lint run)
  5. Commit with conventional commits (feat:, fix:, docs:)
  6. 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

πŸ”— Related Projects


Made with ❀️ for GPU computing in pure Go

Last updated: 2025-11-28 | goffi v0.2.1