# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.6.2] - 2026-07-22 ### Fixed - **Windows AMD64 scalar float returns** — recover `float32` and `float64` values from the XMM0 bits exposed as the second result of `syscall.SyscallN`. Go's `asmstdcall` already copies XMM0 into the second return slot — goffi was receiving but discarding it. Resolves long-standing known issue TASK-019. ([PR #65](https://github.com/go-webgpu/goffi/pull/65) by [@besmpl](https://github.com/besmpl)) - Removed "Windows float returns not captured" from README Known Limitations ## [0.6.1] - 2026-07-21 ### Added - **Android ARM64 Bionic support (guarded preview)** — Android arm64/API 29+ platform with Bionic loader, `__errno` routing, four-argument `_cgo_init` startup, TLS slot validation, and dual CGO mode support. Callbacks explicitly rejected pending physical-device thread proof. Cross-build, ABI, and ELF gates pass on NDK r29 with Go 1.25.12 and Go 1.26.5. ([PR #62](https://github.com/go-webgpu/goffi/pull/62) by [@besmpl](https://github.com/besmpl)) - **Android CI matrix** — NDK r29 cross-build and ELF dependency audits for Go 1.25.12 + Go 1.26.5 in both CGO modes - **`docs/ANDROID.md`** — runtime ABI contract, NDK probe, callback limitation, and build instructions - **@besmpl added as CODEOWNER** for Android paths (`*android*` across `ffi/`, `internal/dl/`, `internal/fakecgo/`, `internal/syscall/`) ### Changed - **fakecgo naming cleanup** — all `purego_` dynamic import symbols renamed to `goffi_` across `internal/fakecgo/` (18 files). Dual copyright attribution: Ebitengine Authors + Andrey Kolkov and GoGPU Contributors. ([PR #63](https://github.com/go-webgpu/goffi/pull/63)) - **LICENSE** updated to `Andrey Kolkov and GoGPU Contributors` (matching gogpu ecosystem pattern) - **NOTICE** file added documenting Apache-2.0 heritage of fakecgo from ebitengine/purego - Platform count: 8 desktop targets + Android ARM64 preview (9 total) ## [0.6.0] - 2026-07-12 ### Added - **errno capture in assembly trampoline** — `CallFunction` 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](https://github.com/go-webgpu/goffi/issues/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 ```go // 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](https://github.com/tie) with `TestCallbackGrowStack` reproducer. ([PR #59](https://github.com/go-webgpu/goffi/pull/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 bug** — `examples/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/52) - **FreeBSD ARM64 callback trampoline** — `ffi/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 support** — `PrepareVariadicCallInterface(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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/golang/go/issues/58625)) in callback dispatch and return handling. `CGO_ENABLED=1 go test -race` now passes cleanly - **Classification: >16B structs** — `classifyArgumentAMD64` 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](https://github.com/go-webgpu/goffi/pull/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](https://github.com/go-webgpu/goffi/issues/13), PR [#37](https://github.com/go-webgpu/goffi/pull/37) by [@jiyeyuran](https://github.com/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](https://github.com/go-webgpu/goffi/issues/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 tests** — `ffi/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](https://github.com/go-webgpu/goffi/issues/41), PR [#42](https://github.com/go-webgpu/goffi/pull/42) by [@pekim](https://github.com/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](https://github.com/go-webgpu/goffi/issues/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.go` → `call_arm64.go`, `syscall_unix_arm64.go/s` → `syscall_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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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 detection** — `PrepareCallInterface` 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 captured** — `syscall.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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/go-webgpu/goffi/issues/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: - `callbackasmAddr` → `trampolineEntryAddr` - `callbackasmABI0` → `trampolineBaseAddr` ### Known Limitations - **Callback dispatcher bypasses crosscall2** ([#16](https://github.com/go-webgpu/goffi/issues/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 - See [ROADMAP.md](ROADMAP.md) for upcoming features ## [0.3.8] - 2026-01-24 ### Fixed - **CGO_ENABLED=1 build error handling** ([gogpu/wgpu#43](https://github.com/gogpu/wgpu/issues/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](https://github.com/gogpu/gogpu/issues/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](https://github.com/go-webgpu/goffi/issues/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](https://github.com/golang/go/issues/12516)) - Affects both CGO and goffi equally - Fix planned for Go 1.26 ([#58542](https://github.com/golang/go/issues/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 ```go // 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](https://github.com/golang/go/issues/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](https://github.com/golang/go/issues/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.s` → `call_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](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](docs/PERFORMANCE.md) - 650+ lines comprehensive performance guide - **NEW**: [ROADMAP.md](ROADMAP.md) - Development roadmap to v1.0.0 - **NEW**: [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines - **NEW**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Community standards - **NEW**: [SECURITY.md](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: ```bash # 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:** ```go err := ffi.PrepareCallInterface( &cif, types.WindowsCallingConvention, 2, // ❌ argCount parameter removed types.SInt32TypeDescriptor, []*types.TypeDescriptor{ types.PointerTypeDescriptor, types.SInt32TypeDescriptor, }, ) ``` **New code:** ```go 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:** ```go var convention types.CallingConvention if runtime.GOOS == "windows" { convention = types.WindowsCallingConvention } else { convention = types.UnixCallingConvention } ``` **New:** ```go convention := types.DefaultCall // Automatically resolves to platform convention ``` #### 2. Add Resource Cleanup with FreeLibrary **Old:** ```go handle, err := ffi.LoadLibrary("mylib.dll") if err != nil { return err } // ❌ Library never freed - resource leak! ``` **New:** ```go handle, err := ffi.LoadLibrary("mylib.dll") if err != nil { return err } defer ffi.FreeLibrary(handle) // ✅ Proper cleanup ``` #### 3. Use Context for Cancellation **Old:** ```go err := ffi.CallFunction(&cif, funcPtr, &result, args) ``` **New (with timeout):** ```go 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:** ```go if err != nil { log.Fatal(err) // Generic error handling } ``` **New:** ```go 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](docs/PERFORMANCE.md) for complete analysis ### Roadmap See [ROADMAP.md](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 ```go 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 ```bash 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) --- [Unreleased]: https://github.com/go-webgpu/goffi/compare/v0.4.1...HEAD [0.4.1]: https://github.com/go-webgpu/goffi/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/go-webgpu/goffi/compare/v0.3.9...v0.4.0 [0.3.9]: https://github.com/go-webgpu/goffi/compare/v0.3.8...v0.3.9 [0.3.8]: https://github.com/go-webgpu/goffi/compare/v0.3.7...v0.3.8 [0.3.7]: https://github.com/go-webgpu/goffi/compare/v0.3.6...v0.3.7 [0.3.6]: https://github.com/go-webgpu/goffi/compare/v0.3.5...v0.3.6 [0.3.5]: https://github.com/go-webgpu/goffi/compare/v0.3.4...v0.3.5 [0.3.4]: https://github.com/go-webgpu/goffi/compare/v0.3.3...v0.3.4 [0.3.3]: https://github.com/go-webgpu/goffi/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/go-webgpu/goffi/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/go-webgpu/goffi/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/go-webgpu/goffi/compare/v0.2.1...v0.3.0 [0.2.1]: https://github.com/go-webgpu/goffi/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/go-webgpu/goffi/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/go-webgpu/goffi/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/go-webgpu/goffi/releases/tag/v0.1.0