Skip to content

go-webgpu/goffi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 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-12-23 | goffi v0.3.2