From d82dca6ca43fa63ffc32cb6559212d609001dcd2 Mon Sep 17 00:00:00 2001 From: Andy Date: Sat, 9 May 2026 16:27:31 +0300 Subject: [PATCH] docs: update CHANGELOG, README, ARCHITECTURE for callback struct args (PR #42) --- CHANGELOG.md | 5 ++++- README.md | 3 ++- docs/ARCHITECTURE.md | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26545b6..d7e8396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **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) ### 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 where gcc is available; skipped gracefully elsewhere +- **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 diff --git a/README.md b/README.md index c45e7ae..33c87b2 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ ffi.CallFunction(cif, sym, unsafe.Pointer(&result), args) | **Zero CGO** | Pure Go | No C compiler needed. `go get` and build. | | **Fast** | 88–114 ns/op | Pre-computed CIF, zero per-call allocations | | **Cross-platform** | 7 targets | Windows, Linux, macOS, FreeBSD × AMD64 + ARM64 | -| **Callbacks** | C→Go safe | `crosscall2` integration, works from any C thread | +| **Callbacks** | C→Go safe | `crosscall2` integration, struct args, works from any C thread | | **Type-safe** | Runtime validation | 5 typed error types with `errors.As()` support | | **Struct pass/return** | Full ABI | Args: INTEGER/SSE classification. Returns: ≤8B (RAX), 9–16B (RAX+RDX), >16B (sret) | | **Context** | Timeouts | `CallFunctionContext(ctx, ...)` cancellation | @@ -224,6 +224,7 @@ if err != nil { | API style | libffi-like (prepare once, call many) | reflect-based (RegisterFunc) | Native | | Per-call allocations | Zero (CIF reusable) | reflect + sync.Pool per call | Zero | | Struct pass/return | Full (RAX+RDX, sret) | Partial (no Windows structs) | Full | +| Callback struct args | ≤8B, 9-16B, >16B (AMD64) | Not supported (panic) | Full | | Callback float returns | XMM0 in asm | Not supported (panic) | Full | | ARM64 HFA detection | Recursive (nested structs) | Partial (bug in nested path) | Full | | Typed errors | 5 types + errors.As() | Generic | N/A | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 01b96aa..3eed6fa 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -169,7 +169,21 @@ Struct argument passing is verified by `ffi/struct_e2e_test.go`, which compiles 4. **24B triple** (`{int64, int64, int64}`) — MEMORY class, copied to stack 5. **Struct + scalar** — mixed register allocation -Tests run on Linux, macOS, and FreeBSD where gcc is available; skipped gracefully on Windows. +Tests run on Linux, macOS, FreeBSD, and Windows where gcc is available; skipped gracefully otherwise. + +--- + +## Callback Struct Arguments (C→Go) + +When C code calls a goffi callback with struct arguments, the callback dispatch (`callbackWrap` in `callback.go`) reconstructs struct values from CPU registers and stack using `reflect.Type`: + +- **≤ 8 bytes**: single eightbyte in GP or XMM register. `isStructAllFloats()` determines classification (recursive, supports nested structs). +- **9-16 bytes**: two eightbytes, each classified independently via `classifyEightbyte()` using `reflect.StructField.Offset`. +- **\> 16 bytes**: MEMORY class — C caller copies bytes onto stack. Callback reads consecutive stack slots directly. No assembly changes needed. + +Classification uses `reflect.Type` (not `types.TypeDescriptor`) since callback signatures are Go functions registered via `NewCallback()`. + +**Limitations**: callback struct args supported on AMD64 Unix only. ARM64 and Windows callbacks do not yet support struct arguments. ---