Skip to content

Latest commit

 

History

History
867 lines (700 loc) · 42.6 KB

File metadata and controls

867 lines (700 loc) · 42.6 KB

Changelog

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.

[0.6.0] - 2026-07-12

Added

  • errno capture in assembly trampolineCallFunction now always captures C errno inside 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

Changed

  • BREAKING: CallFunction returns (syscall.Errno, error) — errno is always captured and returned. Callers that don't need errno use _, err := ffi.CallFunction(...). This replaces the opt-in CallFunctionErrno which was a pit of failure (you don't know you need errno until the function fails)
  • BREAKING: CallFunctionContext returns (syscall.Errno, error) — same change with context support
  • BREAKING: FunctionCaller.Execute interface changed — now accepts errnoFn uintptr parameter and returns (cerrno uintptr, err error)
  • Removed CallFunctionErrno / CallFunctionErrnoContext (superseded by always-capture)
  • Removed FunctionCallerErrno interface (merged into FunctionCaller)
  • Reduced code by 429 lines (eliminated Execute/ExecuteErrno duplication)

Migration Guide

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

[0.5.6] - 2026-07-05

Fixed

  • Critical: callback stack-move corruption — when a C callback re-enters Go and grows the goroutine stack (copystack), syscallArgs on the goroutine stack would move but syscallN on g0 still held the old address, writing return values to freed memory. Fix: syscallArgs is now heap-allocated via sync.Pool, immune to copystack. Reverts the unsafe //go:noescape optimization from v0.5.4. Discovered and fixed by @tie with TestCallbackGrowStack reproducer. (PR #59)
  • sret cleanup — simplified >16B struct return path in call_unix.go, removed redundant sretBuf variable
  • Added TestStructReturn24B — end-to-end test for >16B struct return via sret hidden pointer

[0.5.5] - 2026-06-15

Fixed

  • Example: avalue pointer bugexamples/simple/main.go passed 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.mod modules are now compiled as part of CI pipeline

[0.5.4] - 2026-06-15

Changed

  • Zero-allocation FFI calls — added //go:noescape to runtime_cgocall linkname declarations, eliminating 1 heap allocation (208–248 bytes) per call on all Unix platforms. syscallArgs now 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)

[0.5.3] - 2026-05-28

Fixed

  • FreeBSD ARM64 build failure — added freebsd to build tags in internal/arch/arm64/call_arm64.go, internal/syscall/syscall_arm64.go, internal/syscall/syscall_arm64.s, and ffi/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 trampolineffi/callback_arm64.s was missing freebsd in build tags while ffi/callback_arm64.go (Go side) already included it, causing NewCallback() to silently return invalid addresses on FreeBSD ARM64

Changed

  • Platform count: 7 → 8 targets (added FreeBSD ARM64)
  • CI cross-compilation check now validates all 8 platforms including freebsd/arm64

[0.5.2] - 2026-05-25

Added

  • Variadic function supportPrepareVariadicCallInterface(cif, convention, nfixedargs, returnType, argTypes) enables calling C variadic functions such as printf, 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 to PrepareCallInterface. Closes #47
  • ARM64 Darwin variadic ABI — register allocators (GP and FP) are exhausted at nfixedargs boundary on GOOS=darwin, matching Apple's clang and libffi 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

[0.5.1] - 2026-05-13

Fixed

  • 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.Pointer casts with double-indirection pattern (Go proposal #58625) in callback dispatch and return handling. CGO_ENABLED=1 go test -race now passes cleanly
  • Classification: >16B structsclassifyArgumentAMD64 now 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 with reflect.Pointer in 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)

Added

  • CGO_ENABLED=1 support (#13, PR #37 by @jiyeyuran) — goffi now builds and tests under both CGO_ENABLED=0 (fakecgo) and CGO_ENABLED=1 (real runtime/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_FromCThread verifies NewCallback works when invoked from a pthread_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 testsffi/struct_e2e_test.go compiles 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

[0.5.0] - 2026-03-29

Added

  • 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.go and dl_freebsd_nocgo.go for libc.so.7 dynamic loading. FreeBSD uses identical System V ABI as Linux. Requires -gcflags="github.com/go-webgpu/goffi/internal/fakecgo=-std" for CGO_ENABLED=0 builds
  • CI cross-compilation check — new job validates all 7 supported platforms compile correctly (linux/darwin/windows × amd64 + arm64 + freebsd/amd64)

Changed

  • Renamed ARM64 files to drop misleading "unix" suffix: call_unix.gocall_arm64.go, syscall_unix_arm64.go/ssyscall_arm64.go/s
  • Extended ffi/dl_windows.go and ffi/callback_windows.go from windows && amd64 to windows (all architectures)
  • Extended 15+ build tags to include freebsd alongside linux || darwin

[0.4.2] - 2026-03-03

Fixed

  • Unix: duplicate symbol conflict with purego — added build tag nofakecgo to disable goffi's internal fakecgo for projects that already provide _cgo_init and related runtime symbols (e.g., via purego). Build with go build -tags nofakecgo when using both libraries with CGO_ENABLED=0 (#22)

Changed

  • README: replaced static coverage badge with dynamic Codecov badge
  • README: added purego compatibility workaround to Known Limitations

Added

  • Unit tests for types package: 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

Fixed

  • Float32 argument encoding bug — used math.Float32bits instead 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

Added

  • Overflow detectionPrepareCallInterface now returns ErrTooManyArguments when 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)

Removed

  • Dead callUnix64 assembly experiment from call_unix.s

Known Limitations

  • Windows: float return values from XMM0 not capturedsyscall.SyscallN only returns RAX. Workaround: reinterpret integer return bits if function returns float via RAX

0.4.0 - 2026-02-27

Added

  • 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_call closure variable for ABIInternal function pointer reference from assembly
    • Uses go_asm.h auto-generated constants for callbackArgs struct offsets
    • ARM64: paired load/store instructions (FSTPD/STP) for efficient register saving
    • AMD64: PUSH_REGS_HOST_TO_ABI0 / POP_REGS_HOST_TO_ABI0 for proper ABI transition

Fixed

  • fakecgo trampoline register bugs (synced with purego v0.10.0)
    • ARM64: R26 (callee-saved) → R9 (volatile), R2 (argument) → R9 (volatile)
    • ARM64: threadentry now 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 setg and call5
    • AMD64: notify/bindm trampolines use JMP (tail call) instead of CALL+RET
    • AMD64: threadentry uses PUSH_REGS_HOST_TO_ABI0 for proper ABI transition

Removed

  • crosscall2 bypass warnings from assembly and Go source comments (no longer applicable)

Technical Details

  • crosscall2 is the Go runtime's entry point for C→Go transitions
  • It calls runtime·load_g to set up the goroutine pointer, then runtime·cgocallback
  • This chain is essential for callbacks arriving on C-created threads where G = nil
  • The callbackWrap_call closure 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

Fixed

  • 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
    • entrySize comment updated: MOVD (4 bytes) + B (4 bytes) = 8 bytes
  • Callback assembly symbol collision with purego (#15)

    • Global symbols callbackasm/callbackasm1 conflicted 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:
      • callbackasmAddrtrampolineEntryAddr
      • callbackasmABI0trampolineBaseAddr

Known Limitations

  • 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

Planned

0.3.8 - 2026-01-24

Fixed

  • 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

Added

  • Compile-time CGO detection with descriptive error identifier

    • undefined: GOFFI_REQUIRES_CGO_ENABLED_0 - immediately clear what's wrong
    • Godoc comment in cgo_unsupported.go with full instructions
    • Runtime panic fallback with detailed fix instructions
  • Requirements section in README.md

    • Clear documentation that CGO_ENABLED=0 is required
    • Three options for setting CGO_ENABLED
    • Explanation of why this is needed (cgo_import_dynamic mechanism)

Changed

  • internal/dl/dl_stubs_unix.s - Added !cgo build constraint
  • internal/dl/dl_wrappers_unix.s - Added !cgo build constraint
  • internal/dl/dl_stubs_arm64.s - Added !cgo build constraint
  • internal/dl/dl_wrappers_arm64.s - Added !cgo build constraint
  • ffi/dl_unix.go - Added !cgo build constraint
  • ffi/dl_darwin.go - Added !cgo build constraint

Technical Details

  • goffi uses cgo_import_dynamic for 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

Added

  • 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

    • Call8Float now 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]uint64 instead of fpr [8]float64

Fixed

  • BenchmarkGoffiStringOutput segfault on darwin
    • Pointer argument now correctly passed as unsafe.Pointer(&strPtr)
    • Follows documented API: args contains pointers to argument storage

Changed

  • internal/syscall/syscall_unix_arm64.go

    • Call8Float signature: (r1, r2 uintptr, fret [4]uint64)
    • Float registers now use raw bit patterns (uint64)
  • internal/arch/arm64/classification.go

    • isHomogeneousFloatAggregate now walks nested structs
    • Returns element kind for proper HFA detection
  • internal/arch/arm64/implementation.go

    • handleReturn accepts both retLo and retHi
    • handleHFAReturn uses bit patterns for correct float32/float64

Contributors

  • @ppoage - ARM64 Darwin fixes, ObjC tests, assembly shim

0.3.6 - 2025-12-29

Fixed

  • 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

Added

  • ReturnHFA2, ReturnHFA3, ReturnHFA4 return flag constants
  • handleHFAReturn function for processing HFA struct returns
  • classifyReturnARM64 now detects HFA structs (1-4 floats/doubles)
  • Unit tests for ARM64 HFA classification

Changed

  • internal/syscall/syscall_unix_arm64.go

    • Added fr1-fr4 fields for D0-D3 float returns
    • Added r8 field for X8 sret pointer
    • Updated Call8Float signature: 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.go

    • handleReturn now 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

Technical Details

  • 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

Fixed

  • 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.SyscallN with variadic args for unlimited argument support
    • Affected Vulkan functions: vkCreateGraphicsPipelines (6 args), vkCmdBindVertexBuffers (5 args), etc.
    • Reported via go-webgpu/wgpu project

Changed

  • Simplified Windows FFI - removed intermediate syscall wrapper
    • Removed: internal/syscall/syscall_windows_amd64.go (no longer needed)
    • call_windows.go now calls syscall.SyscallN directly with args...
    • Cleaner code, fewer indirections

Technical Details

  • 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

Fixed

  • Windows stack overflow on Vulkan API calls (Critical)
    • callWin64 assembly used NOSPLIT, $32 which 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

Changed

  • 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

Technical Details

  • Win64 ABI: First 4 args in RCX/RDX/R8/R9 (or XMM0-3 for floats)
  • syscall.SyscallN internally uses cgocall(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

Fixed

  • PointerType argument passing bug (#4)
    • PointerType was passing address instead of value
    • Now correctly dereferences: *(*uintptr)(avalue[idx]) instead of uintptr(avalue[idx])
    • Fixed in all three Execute implementations:
      • internal/arch/arm64/call_unix.go
      • internal/arch/amd64/call_unix.go
      • internal/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

Added

  • 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)}

Technical Details

  • API contract (ffi.go line 43): avalue contains pointers TO argument values
  • For PointerType: pass unsafe.Pointer(&ptr), not ptr directly
  • Tests now correctly use documented pattern, preventing future regressions

0.3.2 - 2025-12-23

Fixed

  • 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

Technical Details

  • 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

Fixed

  • 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.LoadLibrary on ARM64 platforms
    • Reported by go-webgpu project

0.3.0 - 2025-11-28

Added

  • ARM64 architecture support (AAPCS64 ABI for Linux and macOS)
    • internal/arch/arm64/ - Complete ARM64 implementation
    • internal/syscall/syscall_unix_arm64.s - ARM64 assembly for FFI calls
    • ffi/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

Platform Support

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

Note

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

Fixed

  • Windows callback ABI: Use syscall.NewCallback for proper Win64 ABI compliance
    • Windows callbacks now use the official Go syscall mechanism
    • Resolves ABI mismatch issues with native Windows calling convention

Documentation

  • SEH limitation documented: C++ exceptions crash the program on Windows
    • This is a Go runtime limitation (#12516)
    • Affects both CGO and goffi equally
    • Fix planned for Go 1.26 (#58542)

0.2.0 - 2025-11-27

Added

  • Callback support for C-to-Go function calls (NewCallback API)
    • 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 implementation
      • ffi/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

Changed

  • Roadmap updated: Callbacks moved from v0.5.0 to v0.2.0
  • Builder pattern API moved to v0.3.0

Use Case: WebGPU Async Operations

// 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})

Known Limitations

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=abort or use Linux/macOS
  • Fix planned for Go 1.26 (#58542)

0.1.1 - 2025-11-18

Added

  • macOS AMD64 support via System V ABI shared implementation
    • Shared call_unix.s assembly 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.go implementation with LoadLibrary/GetSymbol/FreeLibrary
    • internal/dl/ Unix implementation shared between platforms
    • fakecgo support extended to macOS
  • Thread safety documentation in ffi/ffi.go
    • Documented concurrent access patterns
    • Clarified race detector limitation for zero-CGO libraries

Changed

  • CI/CD improvements for cross-platform testing
    • Added macos-13 runner (Intel AMD64) to test matrix
    • Fixed coverage calculation (test specific packages instead of all files)
    • Explicit CGO_ENABLED=0 environment variable in all jobs
    • Coverage restored from 28-56% (diluted) to 87.1% (accurate)
  • Architecture refactoring for better code organization
    • Renamed call_linux.scall_unix.s with (linux || darwin) build tags
    • Renamed syscall_linux_amd64.*syscall_unix_amd64.* for shared Unix code
    • Split platform-specific constants into dl_linux.go and dl_darwin.go
    • Shared implementation in dl_unix.go for both Unix platforms

Fixed

  • Linter exclusions for assembly-called functions and FFI unsafe.Pointer usage
  • Build constraints compatibility with fakecgo !cgo tag across all platforms
  • CI coverage calculation methodology (test only main packages: ./ffi ./types)

Platform Support

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

Infrastructure

  • 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

Added

  • Professional typed error system following Go 2025 best practices
    • InvalidCallInterfaceError: Detailed CIF preparation errors with field, reason, and index
    • LibraryError: Dynamic library operation errors with operation type and underlying cause
    • CallingConventionError: Unsupported calling convention errors with platform info
    • TypeValidationError: Type descriptor validation errors with kind and index
    • UnsupportedPlatformError: Platform not supported errors with OS/Arch details
  • Context support for cancellation and timeouts (CallFunctionContext)
  • DefaultConvention() helper function for automatic platform detection
  • types.DefaultCall constant for simplified cross-platform code
  • FreeLibrary() implementation on all platforms (Windows + Linux)
  • Comprehensive error handling with errors.As() and errors.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

Changed

  • BREAKING: Removed redundant argCount parameter from PrepareCallInterface
    • Old: PrepareCallInterface(cif, convention, argCount, returnType, argTypes)
    • New: PrepareCallInterface(cif, convention, returnType, argTypes)
    • Migration: Simply remove the argCount parameter - it's now calculated automatically
  • Improved error messages with specific context (field names, indices, reasons)
  • Enhanced documentation with godoc examples for all public APIs

Fixed

  • Resource leaks: Added FreeLibrary() to properly clean up loaded libraries
  • Linter warnings: Added //nolint annotations for assembly-called functions

Documentation

  • Added comprehensive package documentation with usage examples
  • Documented all exported functions with parameters, returns, and examples
  • Added safety guidelines for unsafe.Pointer usage
  • 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

Testing

  • Achieved 89.1% test coverage (exceeding 80% target)
  • Added 23 comprehensive test functions with 50+ test scenarios
  • Tested all error types with errors.As() and errors.Is()
  • Platform-specific tests for Windows and Linux
  • Context cancellation and timeout tests
  • NEW: Benchmark suite with 8 benchmarks (overhead, types, performance)

Infrastructure

  • 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

Performance

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

Migration Guide: v0.1.0 → v0.1.1

No Breaking Changes

Version 0.1.1 is fully backward compatible with 0.1.0. All existing code will continue to work without modifications.

What's New

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 library

Thread Safety Documentation - Review new concurrency guidelines in package documentation.


Migration Guide: Earlier Versions → v0.1.0

Breaking Changes (from pre-v0.1.0)

1. PrepareCallInterface Signature Change

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,
    },
)

Recommended Updates (Non-Breaking)

1. Use DefaultCall for Cross-Platform Code

Old:

var convention types.CallingConvention
if runtime.GOOS == "windows" {
    convention = types.WindowsCallingConvention
} else {
    convention = types.UnixCallingConvention
}

New:

convention := types.DefaultCall  // Automatically resolves to platform convention

2. Add Resource Cleanup with FreeLibrary

Old:

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 cleanup

3. Use Context for Cancellation

Old:

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
}

4. Use Typed Errors for Better Error Handling

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

Release Notes: v0.1.0

What's New

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

Performance

  • ~50-60ns overhead per call
  • 0 allocations for most operations
  • Hand-optimized assembly for each platform
  • Suitable for real-time graphics (60 FPS+)

Known Limitations

Critical (affects functionality):

  • Variadic functions NOT supported (printf, sprintf, etc.)
    • Win64 requires float→GP register duplication
    • System V requires AL register = SSE count
    • Workaround: Use non-variadic wrappers
    • Planned: v0.5.0

Important (platform-specific):

  • Struct packing follows System V ABI only
    • Windows #pragma pack directives NOT honored
    • MSVC alignment may differ from GCC/Clang
    • Workaround: Manually specify Size/Alignment
    • Planned: v0.2.0 (platform-specific rules)

Architectural:

  • Composite types (structs) require manual initialization via PrepareCallInterface
  • Cannot interrupt C functions mid-execution (use CallFunctionContext for 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

Roadmap

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 format
    goffi-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)