ffi

package
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2026 License: MIT Imports: 10 Imported by: 4

Documentation

Overview

Package ffi provides callback support for Foreign Function Interface (Unix version). This file implements Go function registration as C callbacks using pre-compiled assembly trampolines for optimal performance.

Package ffi provides a Foreign Function Interface for calling C functions from Go without CGO. It enables direct calls to C libraries with full type safety and platform abstraction.

Overview

This package allows you to:

  • Load dynamic libraries (LoadLibrary)
  • Get function pointers (GetSymbol)
  • Prepare function call interfaces (PrepareCallInterface)
  • Execute C function calls (CallFunction)

Basic Usage

// Load a library
handle, err := ffi.LoadLibrary("libm.so.6")
if err != nil {
    log.Fatal(err)
}

// Get a function pointer
sqrtPtr, err := ffi.GetSymbol(handle, "sqrt")
if err != nil {
    log.Fatal(err)
}

// Prepare call interface
var cif types.CallInterface
err = ffi.PrepareCallInterface(
    &cif,
    types.DefaultCall,
    types.DoubleTypeDescriptor,
    []*types.TypeDescriptor{types.DoubleTypeDescriptor},
)

// Call the function
var result float64
arg := 16.0
err = ffi.CallFunction(
    &cif,
    sqrtPtr,
    unsafe.Pointer(&result),
    []unsafe.Pointer{unsafe.Pointer(&arg)},
)
// result is now 4.0

Supported Platforms

  • Linux AMD64 (System V ABI)
  • Windows AMD64 (Win64 ABI)
  • macOS AMD64 (planned)
  • ARM64 (planned)

Performance

This implementation uses hand-optimized assembly for each platform's calling convention. Overhead is approximately 50-60ns per call, which is negligible for most use cases (e.g., WebGPU rendering).

Safety

While this package uses unsafe.Pointer internally, the public API validates all inputs and provides type-safe wrappers. Users should ensure:

  • Argument types match the C function signature exactly
  • Pointers remain valid during the call (use runtime.KeepAlive if needed)
  • Return value buffer is large enough for the result

Thread Safety

This package follows Go's standard library conventions for concurrent access:

  • PrepareCallInterface and CallFunction are safe to call concurrently with different CallInterface instances
  • DO NOT use the same CallInterface from multiple goroutines simultaneously without external synchronization
  • Library handles (from LoadLibrary) are safe to use concurrently for read operations (GetSymbol)
  • DO NOT call FreeLibrary while other goroutines are using GetSymbol on the same handle
  • Similar to io.Reader: methods are not inherently thread-safe; synchronization is caller's responsibility

Race detector is not supported for zero-CGO libraries (race detector requires CGO_ENABLED=1, which conflicts with our fakecgo implementation using build tag !cgo). This is a fundamental limitation of the zero-CGO approach. However, this library contains no data races in its internal implementation - all shared state (Registry, TypeDescriptors) is initialized once at startup and accessed read-only thereafter.

Zero Dependencies

This package has zero external dependencies (except for internal/fakecgo on Linux). All FFI logic is implemented in pure Go and assembly.

Index

Constants

View Source
const (
	// RTLD_NOW resolves all symbols when loading the library (recommended).
	RTLD_NOW = dl.RTLD_NOW

	// RTLD_GLOBAL makes symbols available for subsequently loaded libraries.
	RTLD_GLOBAL = dl.RTLD_GLOBAL
)

RTLD constants from <dlfcn.h> for dynamic library loading.

Variables

View Source
var (
	// ErrInvalidCallInterface is deprecated. Use InvalidCallInterfaceError instead.
	//
	// Deprecated: This sentinel error is kept for backwards compatibility only.
	// New code should use errors.As() with *InvalidCallInterfaceError to get
	// detailed error information.
	ErrInvalidCallInterface = &InvalidCallInterfaceError{
		Field:  "unknown",
		Reason: "invalid call interface",
		Index:  -1,
	}

	// ErrFunctionCallFailed is deprecated. Use specific typed errors instead.
	//
	// Deprecated: This generic error doesn't provide useful debugging information.
	// New code should handle specific error types returned by CallFunction.
	ErrFunctionCallFailed = fmt.Errorf("function call failed")
)

Deprecated: Legacy sentinel errors kept for backwards compatibility. Use typed errors above with errors.As() for better error handling.

Functions

func CallFunction

func CallFunction(
	cif *types.CallInterface,
	fn unsafe.Pointer,
	rvalue unsafe.Pointer,
	avalue []unsafe.Pointer,
) error

CallFunction executes a C function call without context support.

This is equivalent to CallFunctionContext(context.Background(), cif, fn, rvalue, avalue). For operations that need cancellation or timeout control, use CallFunctionContext instead.

Parameters:

  • cif: Prepared call interface (from PrepareCallInterface)
  • fn: Function pointer obtained from GetSymbol (must not be nil)
  • rvalue: Pointer to buffer for return value (can be nil for void functions)
  • avalue: Slice of pointers to argument values (length must match argCount from PrepareCallInterface)

Returns:

  • nil on success
  • ErrInvalidCallInterface if cif or fn is nil
  • ErrFunctionCallFailed if the call execution fails

Example:

// Calling strlen(const char *str)
var result uintptr
str := "Hello"
err := ffi.CallFunction(
    &cif,
    strlenPtr,
    unsafe.Pointer(&result),
    []unsafe.Pointer{unsafe.Pointer(&str)},
)

For context-aware calls with timeout support, see CallFunctionContext.

func CallFunctionContext

func CallFunctionContext(
	ctx context.Context,
	cif *types.CallInterface,
	fn unsafe.Pointer,
	rvalue unsafe.Pointer,
	avalue []unsafe.Pointer,
) error

CallFunctionContext executes a C function call with context support.

This function performs the actual FFI call to the C function, handling all platform-specific calling convention details automatically. It checks the context before executing to prevent starting expensive operations when the context is already cancelled or has exceeded its deadline.

Parameters:

  • ctx: Context for cancellation and timeout control (use context.Background() if not needed)
  • cif: Prepared call interface (from PrepareCallInterface)
  • fn: Function pointer obtained from GetSymbol (must not be nil)
  • rvalue: Pointer to buffer for return value (can be nil for void functions)
  • avalue: Slice of pointers to argument values (length must match argCount from PrepareCallInterface)

Returns:

  • nil on success
  • ctx.Err() if context is cancelled or deadline exceeded before call starts
  • ErrInvalidCallInterface if cif or fn is nil
  • ErrFunctionCallFailed if the call execution fails

Example:

// Call with timeout
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

var result float64
arg := 16.0
err := ffi.CallFunctionContext(
    ctx,
    &cif,
    sqrtPtr,
    unsafe.Pointer(&result),
    []unsafe.Pointer{unsafe.Pointer(&arg)},
)
if err == context.DeadlineExceeded {
    log.Println("Call timed out")
}

Note:

  • Context cancellation check occurs BEFORE the call to prevent starting expensive operations when the context is already cancelled.
  • Once the C function starts executing, it CANNOT be interrupted mid-flight.
  • For cancellable operations, the C library itself must support cancellation.

Safety:

  • All argument pointers must remain valid during the call
  • Return value buffer must be large enough for the result type
  • Use runtime.KeepAlive() if needed to prevent premature GC of arguments

func FreeLibrary

func FreeLibrary(handle unsafe.Pointer) error

FreeLibrary unloads a previously loaded library using dlclose.

This function decrements the reference count of the loaded library. When the reference count reaches zero, the library is unloaded from memory.

Parameters:

  • handle: Library handle from LoadLibrary (can be nil)

Returns:

  • nil on success
  • Error if the library could not be unloaded

Example:

handle, err := ffi.LoadLibrary("libm.so.6")
if err != nil {
    log.Fatal(err)
}
defer ffi.FreeLibrary(handle)

Safety:

  • Do not use function pointers obtained from this library after FreeLibrary
  • Always pair LoadLibrary with FreeLibrary to prevent resource leaks
  • Safe to call with nil handle (returns nil without error)

func GetSymbol

func GetSymbol(handle unsafe.Pointer, name string) (unsafe.Pointer, error)

GetSymbol retrieves a function pointer from a loaded library using dlsym.

This function looks up a symbol (function or variable) in the loaded library and returns its address for use with CallFunction.

Parameters:

  • handle: Library handle from LoadLibrary
  • name: Name of the symbol to retrieve (e.g., "sqrt", "glClear")

Returns:

  • Function pointer (use with CallFunction)
  • Error if symbol not found or lookup fails

Example:

sqrtPtr, err := ffi.GetSymbol(handle, "sqrt")
if err != nil {
    log.Fatal(err)
}

Note: The returned pointer is only valid while the library remains loaded.

func LoadLibrary

func LoadLibrary(name string) (unsafe.Pointer, error)

LoadLibrary loads a shared library using dlopen.

This function loads the specified shared library and returns a handle for use with GetSymbol. The library is loaded with RTLD_NOW|RTLD_GLOBAL flags.

Parameters:

  • name: Path to the shared library (e.g., "libm.so.6", "/usr/lib/libGL.so.1")

Returns:

  • Handle to the loaded library (use with GetSymbol and FreeLibrary)
  • Error if loading fails

Example:

handle, err := ffi.LoadLibrary("libm.so.6")
if err != nil {
    log.Fatal(err)
}
defer ffi.FreeLibrary(handle)

Note: Always pair LoadLibrary with FreeLibrary to prevent resource leaks.

func NewCallback added in v0.2.0

func NewCallback(fn any) uintptr

NewCallback registers a Go function as a C callback and returns a function pointer. The returned uintptr can be passed to C code as a callback function pointer.

Requirements:

  • fn must be a function (not nil)
  • fn can have multiple arguments of basic types (int, float, pointer, etc.)
  • fn can return at most one value of basic type
  • Complex types (string, slice, map, chan, interface) are not supported
  • Maximum 2000 callbacks can be registered (program lifetime limit)

Memory Management:

  • Callbacks are never freed (stored in global registry)
  • This prevents GC from collecting callback data while C code uses it
  • For applications with dynamic callback creation, consider callback pools

Usage Example:

func myCallback(x int, y float64) int {
    return x + int(y)
}

callbackPtr := ffi.NewCallback(myCallback)
// Pass callbackPtr to C code as function pointer

Using unsafe.Pointer is necessary here as we're creating a function pointer that C code can call. The pointer is obtained from the assembly trampoline table and is guaranteed to be valid for the program lifetime.

func PrepareCallInterface

func PrepareCallInterface(
	cif *types.CallInterface,
	convention types.CallingConvention,
	returnType *types.TypeDescriptor,
	argTypes []*types.TypeDescriptor,
) error

PrepareCallInterface prepares a function call interface for calling a C function.

This function initializes the CallInterface structure with the necessary metadata for making FFI calls. It must be called before CallFunction.

Parameters:

  • cif: Pointer to CallInterface structure to initialize (must not be nil)
  • convention: Calling convention (types.DefaultCall, types.CDecl, types.StdCall, etc.)
  • returnType: Type descriptor for return value (use types.VoidTypeDescriptor for void)
  • argTypes: Slice of type descriptors for each argument (nil or empty slice for no arguments)

Returns:

  • nil on success
  • ErrInvalidCallInterface if parameters are invalid
  • Other errors if type validation or platform preparation fails

Example:

var cif types.CallInterface
err := ffi.PrepareCallInterface(
    &cif,
    types.DefaultCall,
    types.Int32TypeDescriptor,
    []*types.TypeDescriptor{
        types.PointerTypeDescriptor,
        types.Int32TypeDescriptor,
    },
)

For functions with no arguments, pass nil or an empty slice for argTypes:

err := ffi.PrepareCallInterface(&cif, types.DefaultCall, types.VoidTypeDescriptor, nil)

Types

type CallingConventionError

type CallingConventionError struct {
	Convention int    // The invalid convention value
	Platform   string // Current platform (OS/Arch)
	Reason     string // Why it's not supported
}

CallingConventionError indicates an unsupported or invalid calling convention.

This error is returned when attempting to use a calling convention that is not supported on the current platform or is invalid.

func (*CallingConventionError) Error

func (e *CallingConventionError) Error() string

func (*CallingConventionError) Is

func (e *CallingConventionError) Is(target error) bool

Is implements error equality for errors.Is().

type InvalidCallInterfaceError

type InvalidCallInterfaceError struct {
	Field  string // Which field was invalid ("cif", "returnType", "argTypes", etc.)
	Reason string // Why it was invalid (human-readable description)
	Index  int    // For array fields like argTypes (-1 if not applicable)
}

InvalidCallInterfaceError indicates CallInterface preparation failed due to invalid parameters.

This error provides detailed information about which field failed validation and why, enabling programmatic error handling and better debugging.

Example:

var icErr *InvalidCallInterfaceError
if errors.As(err, &icErr) {
    fmt.Printf("Field %s failed: %s\n", icErr.Field, icErr.Reason)
    if icErr.Index >= 0 {
        fmt.Printf("At index: %d\n", icErr.Index)
    }
}

func (*InvalidCallInterfaceError) Error

func (e *InvalidCallInterfaceError) Error() string

func (*InvalidCallInterfaceError) Is

func (e *InvalidCallInterfaceError) Is(target error) bool

Is implements error equality for errors.Is().

type LibraryError

type LibraryError struct {
	Operation string // "load", "symbol", or "free"
	Name      string // Library path or symbol name
	Err       error  // Underlying OS error (can be nil)
}

LibraryError wraps dynamic library loading and symbol resolution errors.

This error provides context about which library operation failed and includes the underlying OS-specific error for detailed diagnostics.

Example:

var libErr *LibraryError
if errors.As(err, &libErr) {
    fmt.Printf("Failed to %s library %q\n", libErr.Operation, libErr.Name)
    fmt.Printf("OS error: %v\n", libErr.Err)
}

func (*LibraryError) Error

func (e *LibraryError) Error() string

func (*LibraryError) Is

func (e *LibraryError) Is(target error) bool

Is implements error equality for errors.Is().

func (*LibraryError) Unwrap

func (e *LibraryError) Unwrap() error

Unwrap returns the underlying error for errors.Unwrap().

type TypeValidationError

type TypeValidationError struct {
	TypeName string // Name or description of the type
	Kind     int    // The TypeKind value that failed
	Reason   string // Why validation failed
	Index    int    // For composite types (-1 if not applicable)
}

TypeValidationError indicates a type descriptor failed validation.

This error provides details about which type failed and why, helping users fix type definition issues.

func (*TypeValidationError) Error

func (e *TypeValidationError) Error() string

func (*TypeValidationError) Is

func (e *TypeValidationError) Is(target error) bool

Is implements error equality for errors.Is().

type UnsupportedPlatformError

type UnsupportedPlatformError struct {
	OS   string // Operating system (e.g., "linux", "windows", "darwin")
	Arch string // Architecture (e.g., "amd64", "arm64")
}

UnsupportedPlatformError indicates the current platform is not supported by FFI.

This error is returned when attempting to use FFI on a platform that doesn't have an implementation (e.g., ARM64 before it's fully implemented).

Example:

var upErr *UnsupportedPlatformError
if errors.As(err, &upErr) {
    fmt.Printf("Platform %s/%s not supported\n", upErr.OS, upErr.Arch)
}

func (*UnsupportedPlatformError) Error

func (e *UnsupportedPlatformError) Error() string

func (*UnsupportedPlatformError) Is

func (e *UnsupportedPlatformError) Is(target error) bool

Is implements error equality for errors.Is().

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL