All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- errno capture in assembly trampoline —
CallFunctionnow always captures Cerrnoinside the assembly trampoline immediately after the C function returns, before the Go runtime can migrate the goroutine to a different OS thread. This is the only thread-safe window for errno capture. goffi is the first pure-Go FFI with correct errno capture on Linux. (#60) - Platform-specific errno resolution:
__errno_location(Linux glibc/musl),__error(macOS/FreeBSD) via//go:cgo_import_dynamic
- BREAKING:
CallFunctionreturns(syscall.Errno, error)— errno is always captured and returned. Callers that don't need errno use_, err := ffi.CallFunction(...). This replaces the opt-inCallFunctionErrnowhich was a pit of failure (you don't know you need errno until the function fails) - BREAKING:
CallFunctionContextreturns(syscall.Errno, error)— same change with context support - BREAKING:
FunctionCaller.Executeinterface changed — now acceptserrnoFn uintptrparameter and returns(cerrno uintptr, err error) - Removed
CallFunctionErrno/CallFunctionErrnoContext(superseded by always-capture) - Removed
FunctionCallerErrnointerface (merged intoFunctionCaller) - Reduced code by 429 lines (eliminated Execute/ExecuteErrno duplication)
// Before (v0.5.x):
err := ffi.CallFunction(cif, fn, &result, args)
// After (v0.6.0):
errno, err := ffi.CallFunction(cif, fn, &result, args)
// Or if errno not needed:
_, err := ffi.CallFunction(cif, fn, &result, args)- Critical: callback stack-move corruption — when a C callback re-enters Go and grows the goroutine stack (
copystack),syscallArgson the goroutine stack would move butsyscallNon g0 still held the old address, writing return values to freed memory. Fix:syscallArgsis now heap-allocated viasync.Pool, immune tocopystack. Reverts the unsafe//go:noescapeoptimization from v0.5.4. Discovered and fixed by @tie withTestCallbackGrowStackreproducer. (PR #59) - sret cleanup — simplified >16B struct return path in
call_unix.go, removed redundantsretBufvariable - Added
TestStructReturn24B— end-to-end test for >16B struct return via sret hidden pointer
- Example: avalue pointer bug —
examples/simple/main.gopassed string pointer directly as avalue instead of pointer-to-pointer on Windows/Linux path. Darwin path was correct. avalue elements must be pointers TO argument values (unsafe.Pointer(&cstr)), not values directly. Reported by @lkmavi on Windows 11 ARM64 - CI: examples not verified — added examples build check to cross-compile CI job. All
examples/*/go.modmodules are now compiled as part of CI pipeline
- Zero-allocation FFI calls — added
//go:noescapetoruntime_cgocalllinkname declarations, eliminating 1 heap allocation (208–248 bytes) per call on all Unix platforms.syscallArgsnow stays on the goroutine stack. Expected 10–25% performance improvement on the hot path (#54) - ABI-safe struct layout — added
structs.HostLayout(Go 1.23+) to all 8 assembly-interface structures (syscallArgs,callbackArgs,dlopenArgs,dlsymArgs,dlerrorArgs,G,ThreadStart). Guarantees C ABI-compatible memory layout regardless of future Go compiler optimizations (#55)
- FreeBSD ARM64 build failure — added
freebsdto build tags ininternal/arch/arm64/call_arm64.go,internal/syscall/syscall_arm64.go,internal/syscall/syscall_arm64.s, andffi/callback_arm64.s. FreeBSD ARM64 uses identical AAPCS64 calling convention as Linux ARM64 — no code changes needed, only build tag additions. Requires same-gcflags="github.com/go-webgpu/goffi/internal/fakecgo=-std"workaround as FreeBSD AMD64. Closes #52 - FreeBSD ARM64 callback trampoline —
ffi/callback_arm64.swas missingfreebsdin build tags whileffi/callback_arm64.go(Go side) already included it, causingNewCallback()to silently return invalid addresses on FreeBSD ARM64
- Platform count: 7 → 8 targets (added FreeBSD ARM64)
- CI cross-compilation check now validates all 8 platforms including
freebsd/arm64
- Variadic function support —
PrepareVariadicCallInterface(cif, convention, nfixedargs, returnType, argTypes)enables calling C variadic functions such asprintf,sprintf, and custom variadic APIs. On Apple ARM64, variadic arguments are forced onto the stack at the fixed/variadic boundary per Apple's AAPCS64 extension (differs from standard AAPCS64 used on Linux ARM64). On all other platforms the call is functionally identical toPrepareCallInterface. Closes #47 - ARM64 Darwin variadic ABI — register allocators (GP and FP) are exhausted at
nfixedargsboundary onGOOS=darwin, matching Apple'sclangandlibffi ffi_prep_cif_var()behaviour. This fixes incorrect variadic calls on Apple Silicon (M1/M2/M3/M4) where variadic arguments were incorrectly placed in X1-X7 instead of the stack cmd/variadic-test— standalone verification program that @unxed and others can run on Apple Silicon to confirm variadic function calls produce correct results:go run github.com/go-webgpu/goffi/cmd/variadic-test- E2E variadic tests (
ffi/variadic_e2e_test.go) — two gcc-compiled test functions (sum_variadic,variadic_two_fixed) exercised on Linux, macOS, FreeBSD, and Windows (amd64 + arm64) where gcc is available
- AMD64: struct-by-value argument passing — structs passed as arguments were sent as raw pointers instead of their bytes. Now correctly handles ≤8B (single eightbyte, INTEGER/SSE classification), 9-16B (two eightbytes classified independently), and >16B (MEMORY class, copied directly to stack bypassing registers). Follows System V AMD64 ABI §3.2.3. (#33)
- AMD64 Windows: struct argument passing — structs of exactly 1, 2, 4, or 8 bytes are now passed by value per Win64 ABI, not by pointer
- Race detector compatibility — replaced direct
uintptr→unsafe.Pointercasts with double-indirection pattern (Go proposal #58625) in callback dispatch and return handling.CGO_ENABLED=1 go test -racenow passes cleanly - Classification: >16B structs —
classifyArgumentAMD64now correctly returns zero register usage for MEMORY class structs (previously claimed GP registers) - Classification: mixed eightbyte — per-eightbyte SSE/INTEGER classification now walks all members with INTEGER-wins merge rule per System V ABI
- Deprecated
reflect.Ptr— replaced withreflect.Pointerin callback validation (PR #38, flagged by golangci-lint v2.12.1) - AMD64: 9-16B struct return via XMM registers — structs like
{float64, float64}(NSPoint, NSSize, CGPoint, CGSize) now correctly return via XMM0:XMM1 per System V ABI. Previously misclassified as sret (hidden pointer), producing corrupted values on macOS Intel. Four return modes now supported: RAX:RDX, RAX:XMM0, XMM0:RAX, XMM0:XMM1 (TASK-045)
CGO_ENABLED=1support (#13, PR #37 by @jiyeyuran) — goffi now builds and tests under bothCGO_ENABLED=0(fakecgo) andCGO_ENABLED=1(realruntime/cgo). Enables race detector, coexistence with CGO libraries (gocv, database drivers, etc.), and resolves #22 duplicate symbol conflict as alternative workaround- C-thread callback test —
TestCallback_FromCThreadverifiesNewCallbackworks when invoked from apthread_create-spawned C thread under both CGO modes - Unit tests for struct argument classification and value packing (25 test cases)
- End-to-end struct argument tests —
ffi/struct_e2e_test.gocompiles a C test library (testdata/structtest.c) via gcc at test time and validates 5 struct passing scenarios: ≤8B integer pair (issue #33 repro), ≤8B float pair (SSE), 16B two-eightbyte, >16B MEMORY class, and struct+scalar mixed arguments. Runs on Linux/macOS/FreeBSD/Windows where gcc is available; skipped gracefully elsewhere - Callback struct argument support (#41, PR #42 by @pekim) — callbacks (C→Go) now accept struct arguments on AMD64 Unix. All three size classes: ≤8B (INTEGER/SSE), 9-16B (two-eightbyte), >16B (MEMORY class from stack). Includes reflect-based ABI classification, nested struct support in
isStructAllFloats, e2e tests with gcc-compiled C callback wrappers - End-to-end callback struct tests — 5 callback scenarios with C functions that construct structs and pass them by value to Go callbacks
- Windows ARM64 (Snapdragon X) support — extended AAPCS64 ARM64 implementation to Windows via build tag changes. Uses
runtime.cgocall(free on Windows without fakecgo). Tested on Samsung Galaxy Book 4 Edge with Snapdragon X Elite (#31) - FreeBSD amd64 support — added
internal/dl/dl_freebsd.goanddl_freebsd_nocgo.goforlibc.so.7dynamic loading. FreeBSD uses identical System V ABI as Linux. Requires-gcflags="github.com/go-webgpu/goffi/internal/fakecgo=-std"forCGO_ENABLED=0builds - CI cross-compilation check — new job validates all 7 supported platforms compile correctly (linux/darwin/windows × amd64 + arm64 + freebsd/amd64)
- Renamed ARM64 files to drop misleading "unix" suffix:
call_unix.go→call_arm64.go,syscall_unix_arm64.go/s→syscall_arm64.go/s - Extended
ffi/dl_windows.goandffi/callback_windows.gofromwindows && amd64towindows(all architectures) - Extended 15+ build tags to include
freebsdalongsidelinux || darwin
- Unix: duplicate symbol conflict with purego — added build tag
nofakecgoto disable goffi's internal fakecgo for projects that already provide_cgo_initand related runtime symbols (e.g., via purego). Build withgo build -tags nofakecgowhen using both libraries withCGO_ENABLED=0(#22)
- README: replaced static coverage badge with dynamic Codecov badge
- README: added purego compatibility workaround to Known Limitations
- Unit tests for
typespackage:RuntimeEnvironment,DefaultConvention, type descriptors, constants - Unit tests for
internal/arch/amd64:align,classifyReturnAMD64,classifyArgumentAMD64,handleReturn(17 sub-cases) - Overall test coverage increased from 75% to 89% (
-coverpkg=./...)
0.4.1 - 2026-03-02
- Float32 argument encoding bug — used
math.Float32bitsinstead of widening to float64, which corrupted the lower 32 bits in XMM registers (#19) - AMD64 Unix: stack spill for arguments 7+ — arguments beyond the 6 GP registers (RDI..R9) are now correctly pushed onto the stack before CALL. Previously they were silently dropped (#19)
- ARM64 Unix: stack spill for arguments 9+ — arguments beyond the 8 GP registers (X0-X7) are now correctly pushed onto the stack before BL
- AMD64 struct return 9-16 bytes — return values in the RAX+RDX register pair are now correctly assembled into the output buffer
- AMD64 sret hidden pointer — structs larger than 16 bytes now inject a caller-allocated buffer pointer as the first argument (RDI), matching System V AMD64 ABI
- ARM64 HFA stack spill — when a Homogeneous Floating-point Aggregate doesn't fit in the remaining FP registers, the entire HFA is correctly spilled to the stack per AAPCS64
- runtime.KeepAlive added after FFI calls — prevents GC from collecting argument pointers during C function execution
- Overflow detection —
PrepareCallInterfacenow returnsErrTooManyArgumentswhen argument count exceeds platform capacity (15 args max: 6 GP + 9 stack on AMD64, 8 GP + 7 stack on ARM64) - Regression tests:
TestWindowsStackArguments(CreateFileA, 7 args),TestWindowsStackArgumentsFileIO(CreateFileA+WriteFile+ReadFile, data integrity),TestWindowsStackArguments10Args(CreateProcessA, 10 args with exit code verification),TestFloat32ArgEncoding(modff),TestOverflowDetection(20 args),TestUnixStackSpill7Args(snprintf, 7 args)
- Dead
callUnix64assembly experiment fromcall_unix.s
- Windows: float return values from XMM0 not captured —
syscall.SyscallNonly returns RAX. Workaround: reinterpret integer return bits if function returns float via RAX
0.4.0 - 2026-02-27
- crosscall2 integration for C-thread callback support (#16)
- Callback dispatchers now route through
crosscall2 → runtime·load_g → runtime·cgocallback - Supports callbacks from both Go-managed threads and C-library-created threads
- Fixes crashes when C libraries invoke callbacks from internal threads (G = nil)
- Likely fixes gogpu#89 (blank Metal window on ARM64 macOS)
callbackWrap_callclosure variable for ABIInternal function pointer reference from assembly- Uses
go_asm.hauto-generated constants forcallbackArgsstruct offsets - ARM64: paired load/store instructions (FSTPD/STP) for efficient register saving
- AMD64:
PUSH_REGS_HOST_TO_ABI0/POP_REGS_HOST_TO_ABI0for proper ABI transition
- Callback dispatchers now route through
- fakecgo trampoline register bugs (synced with purego v0.10.0)
- ARM64: R26 (callee-saved) → R9 (volatile), R2 (argument) → R9 (volatile)
- ARM64:
threadentrynow saves/restores callee-saved registers (R19-R28, F8-F15) - AMD64: DX (3rd arg register) → R11 (volatile), CX (4th arg register) → R11 (volatile)
- AMD64: BX (callee-saved) → R11 in
setgandcall5 - AMD64:
notify/bindmtrampolines use JMP (tail call) instead of CALL+RET - AMD64:
threadentryusesPUSH_REGS_HOST_TO_ABI0for proper ABI transition
- crosscall2 bypass warnings from assembly and Go source comments (no longer applicable)
crosscall2is the Go runtime's entry point for C→Go transitions- It calls
runtime·load_gto set up the goroutine pointer, thenruntime·cgocallback - This chain is essential for callbacks arriving on C-created threads where G = nil
- The
callbackWrap_callclosure pattern is the canonical workaround for referencing ABIInternal function pointers from assembly outside the runtime package - Go 1.26 confirmed: crosscall2 API unchanged, cgo ~30% faster
0.3.9 - 2026-02-18
-
ARM64 callback trampoline: BL overwrites LR (Critical, #15)
BL(Branch-with-Link) was destroying C caller's return address in LR register- Rewrote all 2000 trampoline entries to
MOVD $index, R12+B(Branch without Link) - Matches Go runtime (
zcallback_windows_arm64.s) and purego (zcallback_arm64.s) patterns - New dispatcher saves/restores R27 (callee-saved, used by Go assembler) and R30 (LR)
- 176-byte stack frame, 16-byte aligned per AAPCS64
entrySizecomment updated:MOVD (4 bytes) + B (4 bytes) = 8 bytes
-
Callback assembly symbol collision with purego (#15)
- Global symbols
callbackasm/callbackasm1conflicted with purego when linked together - Renamed to package-scoped middot symbols (
·callbackTrampoline/·callbackDispatcher) - At link time these become
github.com/go-webgpu/goffi/ffi.callbackTrampoline— no collision - Go variable/function names updated for consistency:
callbackasmAddr→trampolineEntryAddrcallbackasmABI0→trampolineBaseAddr
- Global symbols
- Callback dispatcher bypasses crosscall2 (#16)
- Callbacks work correctly on Go-managed threads (primary WebGPU use case)
- Callbacks on C-library-created threads will crash (G = nil)
- Planned fix: crosscall2 integration in v0.4.0
- See ROADMAP.md for upcoming features
0.3.8 - 2026-01-24
- CGO_ENABLED=1 build error handling (gogpu/wgpu#43)
- Users on Linux/macOS with gcc/clang installed got confusing linker errors
- Root cause: Assembly files compiled under CGO=1 but referenced undefined symbols
- Solution: Enterprise-grade error handling with compile-time + runtime guards
-
Compile-time CGO detection with descriptive error identifier
undefined: GOFFI_REQUIRES_CGO_ENABLED_0- immediately clear what's wrong- Godoc comment in
cgo_unsupported.gowith full instructions - Runtime panic fallback with detailed fix instructions
-
Requirements section in README.md
- Clear documentation that
CGO_ENABLED=0is required - Three options for setting CGO_ENABLED
- Explanation of why this is needed (cgo_import_dynamic mechanism)
- Clear documentation that
internal/dl/dl_stubs_unix.s- Added!cgobuild constraintinternal/dl/dl_wrappers_unix.s- Added!cgobuild constraintinternal/dl/dl_stubs_arm64.s- Added!cgobuild constraintinternal/dl/dl_wrappers_arm64.s- Added!cgobuild constraintffi/dl_unix.go- Added!cgobuild constraintffi/dl_darwin.go- Added!cgobuild constraint
- goffi uses
cgo_import_dynamicfor dynamic library loading without CGO - This mechanism only works when
CGO_ENABLED=0 - On Linux/macOS with C compiler installed, Go defaults to
CGO_ENABLED=1 - New error handling provides clear guidance instead of cryptic linker errors
0.3.7 - 2026-01-03
-
ARM64 Darwin comprehensive support (PR #9 by @ppoage)
- Tested on Apple Silicon M3 Pro (64 ns/op benchmark)
- Nested struct handling via
placeStructRegisters() - Mixed int/float struct support via
countStructRegUsage() ensureStructLayout()for auto-computing size/alignment- Assembly shim (
abi_capture_test.s) for ABI verification - Comprehensive darwin ObjC tests (747 lines)
- Struct argument tests (537 lines)
-
r2 (X1) return for 9-16 byte struct returns
Call8Floatnow returns both X0 and X1- Fixes struct returns between 9-16 bytes on ARM64
-
uint64 bit patterns for float registers
- Cleaner handling of mixed float32/float64 arguments
fpr [8]uint64instead offpr [8]float64
- BenchmarkGoffiStringOutput segfault on darwin
- Pointer argument now correctly passed as
unsafe.Pointer(&strPtr) - Follows documented API: args contains pointers to argument storage
- Pointer argument now correctly passed as
-
internal/syscall/syscall_unix_arm64.goCall8Floatsignature:(r1, r2 uintptr, fret [4]uint64)- Float registers now use raw bit patterns (uint64)
-
internal/arch/arm64/classification.goisHomogeneousFloatAggregatenow walks nested structs- Returns element kind for proper HFA detection
-
internal/arch/arm64/implementation.gohandleReturnaccepts both retLo and retHihandleHFAReturnuses bit patterns for correct float32/float64
- @ppoage - ARM64 Darwin fixes, ObjC tests, assembly shim
0.3.6 - 2025-12-29
-
ARM64 HFA (Homogeneous Floating-point Aggregate) returns (Critical)
- NSRect (4 × float64) returned zeros on Apple Silicon (gogpu#24)
- Root cause: Assembly only saved D0-D1, HFA needs D0-D3
- Solution: Save all 4 float registers (D0-D3) for HFA returns
- Affects Objective-C runtime calls on macOS ARM64 (Apple Silicon M1/M2/M3/M4)
-
ARM64 large struct return via X8 (sret) (Critical)
- Non-HFA structs >16 bytes returned via implicit pointer in X8
- Root cause: X8 register never loaded before function call
- Solution: Load rvalue pointer into X8 for sret calls
ReturnHFA2,ReturnHFA3,ReturnHFA4return flag constantshandleHFAReturnfunction for processing HFA struct returnsclassifyReturnARM64now detects HFA structs (1-4 floats/doubles)- Unit tests for ARM64 HFA classification
-
internal/syscall/syscall_unix_arm64.go- Added fr1-fr4 fields for D0-D3 float returns
- Added r8 field for X8 sret pointer
- Updated
Call8Floatsignature: accepts r8, returns [4]float64
-
internal/syscall/syscall_unix_arm64.s- Load X8 from r8 field (offset 184) before function call
- Save D0-D3 to fr1-fr4 (offsets 152-176) after call
- Fixed offsets: was incorrectly saving to input arg offsets
-
internal/arch/arm64/implementation.gohandleReturnnow accepts fret [4]float64 for HFA- Fixed sret handling: do nothing (callee writes directly via X8)
-
internal/arch/arm64/call_unix.go- Pass rvalue as r8 for ReturnViaPointer calls
- Pass full fret [4]float64 to handleReturn
- AAPCS64 (ARM64 ABI): HFA structs with 1-4 same-type floats return in D0-D3
- AAPCS64: Large non-HFA structs (>16 bytes) return via hidden pointer in X8
- NSRect = CGRect = 4 × float64 = 32 bytes = HFA (returns in D0-D3)
- Fixes blank window issue on macOS ARM64 (GPU window size was 0×0)
0.3.5 - 2025-12-27
- Windows stack arguments not implemented (Critical)
- Functions with >4 arguments caused
panic: stack arguments not implemented - Win64 ABI: first 4 args in registers (RCX/RDX/R8/R9), args 5+ on stack
- Solution: Use
syscall.SyscallNwith variadic args for unlimited argument support - Affected Vulkan functions:
vkCreateGraphicsPipelines(6 args),vkCmdBindVertexBuffers(5 args), etc. - Reported via go-webgpu/wgpu project
- Functions with >4 arguments caused
- Simplified Windows FFI - removed intermediate syscall wrapper
- Removed:
internal/syscall/syscall_windows_amd64.go(no longer needed) call_windows.gonow callssyscall.SyscallNdirectly withargs...- Cleaner code, fewer indirections
- Removed:
syscall.SyscallN(fn, args...)supports up to 15+ arguments- Handles both register (1-4) and stack (5+) arguments automatically
- Same approach used by purego for Windows FFI
0.3.4 - 2025-12-27
- Windows stack overflow on Vulkan API calls (Critical)
callWin64assembly usedNOSPLIT, $32which prevented stack growth- When calling C functions needing significant stack (Vulkan drivers), caused
STACK_OVERFLOW(Exception 0xc00000fd) - Solution: Replace direct assembly with
syscall.SyscallN(Go runtime's asmstdcall) - This matches purego's proven approach for Windows FFI
- Reported via go-webgpu/wgpu project
- Windows FFI architecture - Enterprise-grade refactoring
- Removed:
internal/arch/amd64/call_windows.s(direct assembly) - Added:
internal/syscall/syscall_windows_amd64.go(SyscallN wrapper) - Uses Go runtime's built-in stack management via
syscall.SyscallN - Proper shadow space and stack alignment handled by Go runtime
- Removed:
- Win64 ABI: First 4 args in RCX/RDX/R8/R9 (or XMM0-3 for floats)
syscall.SyscallNinternally usescgocall(asmstdcallAddr, ...)- Go runtime allocates proper stack and handles preemption/GC
- Float return values not captured (known limitation, matches purego)
0.3.3 - 2025-12-24
- PointerType argument passing bug (#4)
- PointerType was passing address instead of value
- Now correctly dereferences:
*(*uintptr)(avalue[idx])instead ofuintptr(avalue[idx]) - Fixed in all three Execute implementations:
internal/arch/arm64/call_unix.gointernal/arch/amd64/call_unix.gointernal/arch/amd64/call_windows.go
- Added missing SInt8/UInt8/SInt16/UInt16 type handling in AMD64 Unix
- Fixed float32 handling in Windows (was treating as float64)
- Reported by go-webgpu project via GitHub Issue #4
- Regression tests for argument passing
TestPointerArgumentPassing- strlen-based test for PointerType (Issue #4)TestIntegerArgumentTypes- abs-based test for integer types- Both tests use documented API pattern:
[]unsafe.Pointer{unsafe.Pointer(&arg)}
- API contract (ffi.go line 43):
avaluecontains pointers TO argument values - For PointerType: pass
unsafe.Pointer(&ptr), notptrdirectly - Tests now correctly use documented pattern, preventing future regressions
0.3.2 - 2025-12-23
- ARM64 HFA (Homogeneous Floating-point Aggregate) classification bug
- HFA structs (e.g., NSRect with 4 doubles) were incorrectly passed by reference
- Now correctly passed in floating-point registers D0-D7 per AAPCS64 ABI
- Fix: Check HFA status before struct size in
classifyArgumentARM64() - Reported via go-webgpu/webgpu macOS ARM64 testing
- AAPCS64 requires HFA detection before size-based classification
- Example:
NSRect(4 × float64 = 32 bytes) is HFA → uses D0-D3, not reference - Affects Objective-C runtime calls on Apple Silicon (M1/M2/M3/M4)
0.3.1 - 2025-11-28
- ARM64 build constraints for dynamic library loading functions
dl_unix.go: Added ARM64 support (linux && (amd64 || arm64))dl_darwin.go: Added ARM64 support (darwin && (amd64 || arm64))stubs/caller.go: Exclude ARM64 from stubs (!amd64 && !arm64)- Fixes
undefined: ffi.LoadLibraryon ARM64 platforms - Reported by go-webgpu project
0.3.0 - 2025-11-28
- ARM64 architecture support (AAPCS64 ABI for Linux and macOS)
internal/arch/arm64/- Complete ARM64 implementationinternal/syscall/syscall_unix_arm64.s- ARM64 assembly for FFI callsffi/callback_arm64.go+ffi/callback_arm64.s- 2000-entry callback trampolines- X0-X7 integer registers, D0-D7 floating-point registers
- Homogeneous Floating-point Aggregate (HFA) detection
- Cross-compile verified:
GOOS=linux/darwin GOARCH=arm64
- Pre-release script improvements for ARM64 and cross-platform builds
- ✅ Linux AMD64 (System V ABI)
- ✅ Windows AMD64 (Win64 ABI)
- ✅ macOS AMD64 (System V ABI)
- 🟡 Linux ARM64 (AAPCS64 ABI) - cross-compile verified, hardware testing pending
- 🟡 macOS ARM64 (AAPCS64 ABI) - cross-compile verified, hardware testing pending
ARM64 support is feature-complete but awaiting real hardware testing. Cross-compilation verified on all platforms. Use with caution on production ARM64 systems until v0.3.1.
0.2.1 - 2025-11-27
- Windows callback ABI: Use
syscall.NewCallbackfor proper Win64 ABI compliance- Windows callbacks now use the official Go syscall mechanism
- Resolves ABI mismatch issues with native Windows calling convention
- SEH limitation documented: C++ exceptions crash the program on Windows
0.2.0 - 2025-11-27
- Callback support for C-to-Go function calls (
NewCallbackAPI)NewCallback(fn any) uintptr- Register Go function as C callback- Pre-compiled trampoline table with 2000 entries
- Thread-safe callback registry with mutex protection
- Reflection-based argument and return value marshaling
- System V AMD64 ABI compatibility (Linux, macOS)
- Win64 ABI compatibility (Windows)
- Files:
ffi/callback.go- Core callback implementationffi/callback_amd64.s- Assembly trampolines (2000 entries)ffi/callback_test.go- Comprehensive test suite
- Supported argument types:
- Integers: int, int8, int16, int32, int64
- Unsigned: uint, uint8, uint16, uint32, uint64, uintptr
- Floats: float32, float64
- Pointers: *T, unsafe.Pointer
- Boolean: bool
- Return types: All above types + void (no return)
- Tests: 20 comprehensive tests covering all scenarios
- Roadmap updated: Callbacks moved from v0.5.0 to v0.2.0
- Builder pattern API moved to v0.3.0
// Create callback for wgpuInstanceRequestAdapter
cb := ffi.NewCallback(func(status int, adapter uintptr, msg uintptr, ud uintptr) {
result := (*adapterResult)(unsafe.Pointer(ud))
result.status = status
result.adapter = adapter
close(result.done)
})
// Pass to C function
ffi.CallFunction(&cif, wgpuRequestAdapter, nil,
[]unsafe.Pointer{&instance, &opts, &cb, &userdata})Callback-specific:
- Maximum 2000 callbacks per process (memory never released)
- Complex types (string, slice, map, chan, interface) not supported as arguments
- Callbacks must have at most one return value
Windows SEH (Go runtime limitation):
- C++ exceptions crash the program on Windows (Go #12516)
- Affects Rust libraries with
panic=unwind(default), including wgpu-native - This is NOT goffi-specific - CGO has the same issue
- Workaround: Build native libraries with
panic=abortor use Linux/macOS - Fix planned for Go 1.26 (#58542)
0.1.1 - 2025-11-18
- macOS AMD64 support via System V ABI shared implementation
- Shared
call_unix.sassembly for Linux and macOS (identical System V ABI) - Platform-specific dynamic library constants (RTLD_GLOBAL: 0x8 on macOS vs 0x100 on Linux)
- Complete
dl_darwin.goimplementation with LoadLibrary/GetSymbol/FreeLibrary internal/dl/Unix implementation shared between platformsfakecgosupport extended to macOS
- Shared
- Thread safety documentation in
ffi/ffi.go- Documented concurrent access patterns
- Clarified race detector limitation for zero-CGO libraries
- CI/CD improvements for cross-platform testing
- Added
macos-13runner (Intel AMD64) to test matrix - Fixed coverage calculation (test specific packages instead of all files)
- Explicit
CGO_ENABLED=0environment variable in all jobs - Coverage restored from 28-56% (diluted) to 87.1% (accurate)
- Added
- Architecture refactoring for better code organization
- Renamed
call_linux.s→call_unix.swith(linux || darwin)build tags - Renamed
syscall_linux_amd64.*→syscall_unix_amd64.*for shared Unix code - Split platform-specific constants into
dl_linux.goanddl_darwin.go - Shared implementation in
dl_unix.gofor both Unix platforms
- Renamed
- Linter exclusions for assembly-called functions and FFI unsafe.Pointer usage
- Build constraints compatibility with fakecgo
!cgotag across all platforms - CI coverage calculation methodology (test only main packages:
./ffi ./types)
- ✅ Linux AMD64 (System V ABI) - FULLY SUPPORTED
- ✅ Windows AMD64 (Win64 ABI) - FULLY SUPPORTED
- ✅ macOS AMD64 (System V ABI) - NEWLY ADDED
- 🟡 ARM64 (AAPCS64 ABI) - In development for v0.3.0
- All 3 platforms tested in CI/CD (ubuntu-latest, windows-latest, macos-13)
- Quality gate: 70% minimum coverage threshold (current: 87.1%)
- Benchmark validation: FFI overhead < 200ns threshold
0.1.0 - 2025-11-17
- Professional typed error system following Go 2025 best practices
InvalidCallInterfaceError: Detailed CIF preparation errors with field, reason, and indexLibraryError: Dynamic library operation errors with operation type and underlying causeCallingConventionError: Unsupported calling convention errors with platform infoTypeValidationError: Type descriptor validation errors with kind and indexUnsupportedPlatformError: Platform not supported errors with OS/Arch details
- Context support for cancellation and timeouts (
CallFunctionContext) DefaultConvention()helper function for automatic platform detectiontypes.DefaultCallconstant for simplified cross-platform codeFreeLibrary()implementation on all platforms (Windows + Linux)- Comprehensive error handling with
errors.As()anderrors.Is()support - Comprehensive benchmarks (CRITICAL milestone!)
BenchmarkGoffiOverhead: 88.09 ns/op (empty function)BenchmarkGoffiIntArgs: 113.9 ns/op (integer arguments)BenchmarkGoffiStringOutput: 97.81 ns/op (string processing)BenchmarkDirectGo: 0.21 ns/op (baseline)- See docs/PERFORMANCE.md for complete analysis
- BREAKING: Removed redundant
argCountparameter fromPrepareCallInterface- Old:
PrepareCallInterface(cif, convention, argCount, returnType, argTypes) - New:
PrepareCallInterface(cif, convention, returnType, argTypes) - Migration: Simply remove the
argCountparameter - it's now calculated automatically
- Old:
- Improved error messages with specific context (field names, indices, reasons)
- Enhanced documentation with godoc examples for all public APIs
- Resource leaks: Added
FreeLibrary()to properly clean up loaded libraries - Linter warnings: Added
//nolintannotations for assembly-called functions
- Added comprehensive package documentation with usage examples
- Documented all exported functions with parameters, returns, and examples
- Added safety guidelines for
unsafe.Pointerusage - Created API audit documentation
- NEW: docs/PERFORMANCE.md - 650+ lines comprehensive performance guide
- NEW: ROADMAP.md - Development roadmap to v1.0.0
- NEW: CONTRIBUTING.md - Contribution guidelines
- NEW: CODE_OF_CONDUCT.md - Community standards
- NEW: SECURITY.md - Security policy and best practices
- Achieved 89.1% test coverage (exceeding 80% target)
- Added 23 comprehensive test functions with 50+ test scenarios
- Tested all error types with
errors.As()anderrors.Is() - Platform-specific tests for Windows and Linux
- Context cancellation and timeout tests
- NEW: Benchmark suite with 8 benchmarks (overhead, types, performance)
- NEW:
.github/CODEOWNERS- Code ownership (@kolkov) - NEW:
.github/workflows/ci.yml- Comprehensive CI/CD (lint, format, test, benchmarks, coverage) - NEW:
.codecov.yml- Codecov configuration (70% target) - NEW:
scripts/pre-release-check.sh- Pre-release validation script
- FFI Overhead: 88-114 ns/op (BETTER than estimated 230ns!)
- Verdict: ✅ Excellent for WebGPU (< 5% overhead for GPU operations)
- Comparison: Competitive with CGO (~200-250ns) and purego (~150-200ns)
Version 0.1.1 is fully backward compatible with 0.1.0. All existing code will continue to work without modifications.
macOS AMD64 Support - If you were previously targeting only Linux/Windows, you can now add macOS to your build targets:
# Build for macOS
GOOS=darwin GOARCH=amd64 go build ./...
# Your existing code works unchanged
handle, _ := ffi.LoadLibrary("libc.dylib") # macOS system libraryThread Safety Documentation - Review new concurrency guidelines in package documentation.
Old code:
err := ffi.PrepareCallInterface(
&cif,
types.WindowsCallingConvention,
2, // ❌ argCount parameter removed
types.SInt32TypeDescriptor,
[]*types.TypeDescriptor{
types.PointerTypeDescriptor,
types.SInt32TypeDescriptor,
},
)New code:
err := ffi.PrepareCallInterface(
&cif,
types.DefaultCall, // ✅ Use DefaultCall for cross-platform
types.SInt32TypeDescriptor,
[]*types.TypeDescriptor{
types.PointerTypeDescriptor,
types.SInt32TypeDescriptor,
},
)Old:
var convention types.CallingConvention
if runtime.GOOS == "windows" {
convention = types.WindowsCallingConvention
} else {
convention = types.UnixCallingConvention
}New:
convention := types.DefaultCall // Automatically resolves to platform conventionOld:
handle, err := ffi.LoadLibrary("mylib.dll")
if err != nil {
return err
}
// ❌ Library never freed - resource leak!New:
handle, err := ffi.LoadLibrary("mylib.dll")
if err != nil {
return err
}
defer ffi.FreeLibrary(handle) // ✅ Proper cleanupOld:
err := ffi.CallFunction(&cif, funcPtr, &result, args)New (with timeout):
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
err := ffi.CallFunctionContext(ctx, &cif, funcPtr, &result, args)
if err == context.DeadlineExceeded {
// Handle timeout
}Old:
if err != nil {
log.Fatal(err) // Generic error handling
}New:
var libErr *ffi.LibraryError
if errors.As(err, &libErr) {
fmt.Printf("Failed to %s %q: %v\n", libErr.Operation, libErr.Name, libErr.Err)
}
var icErr *ffi.InvalidCallInterfaceError
if errors.As(err, &icErr) {
if icErr.Index >= 0 {
fmt.Printf("Argument %d failed: %s\n", icErr.Index, icErr.Reason)
}
}Zero-Dependency FFI - Pure Go implementation without CGO, enabling:
- WebGPU access through wgpu-native
- Cross-platform graphics programming
- High-performance GPU operations
Professional Error Handling - Go 2025 best practices:
- Structured errors with context (field, index, reason)
- Type-safe error checking with
errors.As() - Better debugging with detailed error messages
Simplified API - Reduced boilerplate:
- Automatic argument counting
- Platform auto-detection with
DefaultCall - Resource cleanup with
FreeLibrary() - Context support for timeouts/cancellation
High Quality - Production-ready:
- 89.1% test coverage
- 0 linter issues
- Comprehensive documentation
- Professional error messages
- ~50-60ns overhead per call
- 0 allocations for most operations
- Hand-optimized assembly for each platform
- Suitable for real-time graphics (60 FPS+)
Critical (affects functionality):
- Variadic functions NOT supported (
printf,sprintf, etc.)- Win64 requires float→GP register duplication
- System V requires
ALregister = SSE count - Workaround: Use non-variadic wrappers
- Planned: v0.5.0
Important (platform-specific):
- Struct packing follows System V ABI only
- Windows
#pragma packdirectives NOT honored - MSVC alignment may differ from GCC/Clang
- Workaround: Manually specify
Size/Alignment - Planned: v0.2.0 (platform-specific rules)
- Windows
Architectural:
- Composite types (structs) require manual initialization via
PrepareCallInterface - Cannot interrupt C functions mid-execution (use
CallFunctionContextfor timeouts) - Limited to amd64 architecture (ARM64 in development for v0.3.0)
- No bitfields in structs (manual bit manipulation required)
- Race detector not supported - Race detection requires CGO_ENABLED=1, which conflicts with our fakecgo (!cgo build tag). This is a fundamental limitation of zero-CGO libraries. Manual testing possible with real C runtime.
Performance (BENCHMARKED in v0.1.0):
- Measured 88-114 ns/op FFI overhead (better than estimated 230ns!)
- < 5% overhead for WebGPU operations (GPU calls are 1-100µs)
- Acceptable for: WebGPU, system calls, I/O operations, GPU computing
- NOT recommended for: Tight loops (>100K calls/sec), hot-path math libraries
- See docs/PERFORMANCE.md for complete analysis
See ROADMAP.md for detailed roadmap to v1.0.
v0.3.0 (ARM64 Support) - Q1 2025
- ARM64 support (Linux + macOS AAPCS64 ABI)
- AAPCS64 calling convention implementation
- 2000-entry callback trampolines for ARM64
- Cross-compile verified, real hardware testing pending
v0.5.0 (Usability + Variadic) - Q2 2025
- Builder pattern for CallInterface
lib.Call("func").Arg(...).ReturnInt()
- Platform-specific struct alignment (Windows
#pragma pack) - Variadic function support (printf, sprintf, etc.)
- AL register handling (System V)
- Float→GP duplication (Win64)
- Windows ARM64 (experimental)
v0.8.0 (Advanced Features) - Q4 2025
- Codegen tool (
goffi-gen) - JSON intermediate formatgoffi-gen --input=api.json --output=bindings.go
- Struct builder API
- Performance optimizations (JIT stub generation?)
- Thread-local storage (TLS) handling
v1.0.0 (Stable Release) - Q1 2026
- API stability guarantee (SemVer 2.0)
- Security audit
- Reference implementations (WebGPU, Vulkan, SQLite)
- Comprehensive documentation (book-style guide)
- Performance benchmarks published
- Support policy (LTS for v1.x)