Skip to content

release: v0.6.7 - #107

Merged
kolkov merged 1 commit into
mainfrom
release/v0.6.7
Oct 18, 2026
Merged

kolkov merged 1 commit into
mainfrom
release/v0.6.7

Conversation

@kolkov

@kolkov kolkov commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Changed

  • License headers follow the code's origin. goffi is MIT; only code derived from ebitengine/purego keeps purego's Apache-2.0 header and copyright line. Five original files written by the maintainer (cmd/variadic-test/main.go, examples/struct/main.go, examples/struct/structlib.c, ffi/struct_return_e2e_amd64_test.go, ffi/variadic_e2e_test.go) had an Apache-2.0 header copied from elsewhere and now carry the MIT header. Original files contributed under an Apache-2.0 header keep it until their authors agree to the change. A new CI check (scripts/check-spdx.sh) rejects an Apache-2.0 header without purego's copyright line on any other file.

Added

  • CI: the test suite runs with the race detector. The race detector needs cgo, so every CGO_ENABLED=1 test job on Linux, macOS and Windows amd64 (and the FreeBSD VM job) also runs go test -race; windows/arm64 has no race detector. -race turns on checkptr, which stops a test when an unsafe.Pointer conversion reaches past a Go allocation — it catches the struct argument over-read fixed below.
  • CI: the test suite runs on FreeBSD. GitHub has no FreeBSD runners, so FreeBSD amd64 was only cross-compiled. A new CI job runs go test with CGO_ENABLED=0 and CGO_ENABLED=1 on FreeBSD 14 and 15 in a VM, on Go 1.25 and the latest stable Go, with the base system C compiler for the C-backed end-to-end tests; it is required for merging. FreeBSD arm64 is still cross-compiled only.

Fixed

  • A types.CallInterface written as a struct literal returned some structs wrong. Without PrepareCallInterface, Flags stays 0. Arguments do not depend on it, but struct results do: on System V amd64 a 9–16 byte struct with a floating-point eightbyte ({double, double} such as CGPoint, {int64, double}, {double, int64}) was read from RAX:RDX instead of XMM0/XMM1, and on arm64 an HFA (e.g. {double, double}) was read from X0:X1 instead of D0–D3, and a struct larger than 16 bytes was called without the indirect result register X8. No error was returned. CallFunction now classifies the struct result of such a CallInterface on each call, on a copy, so a CallInterface shared between goroutines is never written; a CallInterface from PrepareCallInterface takes the same path as before. A nil ReturnType is now an *InvalidCallInterfaceError instead of a nil-pointer panic, and so is a struct result whose Size or Alignment is not set (only PrepareCallInterface computes them). The types.CallInterface documentation now describes both ways of filling it.
  • Windows amd64: structs of any size other than 1, 2, 4 and 8 bytes were returned and passed wrongly. Win64 returns such a struct through a hidden pointer to caller memory passed as the first argument; goffi did not pass it, so the callee treated whatever RCX held — the first real argument, if any — as the result address (in CI, a function returning a 16-byte struct crashed with an access violation in the callee), and goffi then stored RAX into the result buffer. The pointer is now passed and the callee writes the result into rvalue. Struct arguments of those sizes are passed as a pointer to a copy, as Win64 requires, instead of a pointer to the caller's value, which the callee was free to modify. The struct-return and {float, float} argument tests no longer skip on Windows.
  • amd64 and arm64: a struct result smaller than 8 bytes overwrote the memory after it. The result register was stored as a whole 8-byte word into rvalue, which is the caller's struct; for a 3-byte struct five bytes past it were overwritten. Exactly the struct's size is written now.
  • System V amd64 (Linux, macOS, FreeBSD): a struct argument of 3, 5, 6 or 7 bytes was read past its end (as a 4- or 8-byte word); under -race checkptr stopped the program (converted pointer straddles multiple allocations). Struct arguments and the bytes of their last eightbyte are now read with their exact size.
  • System V amd64: a struct of one eightbyte of only float/double members ({float}, {double}, {float, float}) was read from RAX; the ABI returns it in XMM0. It is now read from XMM0. Win64 is unchanged: it returns such structs in RAX.
  • arm64: a struct of a single float or double was read from X0; as a one-member HFA it is returned in S0/D0. It is now read from there.
  • A nil rvalue for a struct larger than 16 bytes crashed in the callee on System V amd64 and arm64: the result is returned through a hidden pointer, which was then nil. As with libffi, goffi now supplies a temporary buffer for every result returned through a hidden pointer (on Win64 also for the sizes above) and discards it.
  • Linux, macOS, FreeBSD: the error of a failed LoadLibrary or GetSymbol could belong to another call, or be garbage. dlerror() reports the calling thread's last dynamic-linking error, and its string is valid only until the next dlerror() on that thread. goffi called dlopen/dlsym and dlerror in two separate runtime calls, between which the goroutine could move to another OS thread and another goroutine could use the first one. With 32 goroutines failing at once, errors said unknown error, named another goroutine's library or symbol, or contained bytes of a freed string. The failed call, dlerror() and the copy of its message now run on one locked OS thread. A new test fails loads and lookups from many goroutines and checks that each error names its own library or symbol.
  • Tests on FreeBSD. The first runs on FreeBSD found two gaps; neither affected programs that use goffi. The internal/arch/amd64 tests could not start (ld-elf.so.1: /lib/libc.so.7: Undefined symbol "environ"): their test binary used the C library without internal/fakecgo, which supplies environ on FreeBSD, as the ffi package always does. Seven tests that call the system C library were skipped because they did not know its name on FreeBSD; the C and math library names now live in one place for all tests.

CHANGELOG [0.6.7] and ROADMAP for the release. README and ARCHITECTURE
describe struct returns per ABI (Win64 hidden pointer for sizes other
than 1, 2, 4 and 8 bytes, System V float structs in XMM0, arm64
single-member HFA in S0/D0) and drop the note that Windows rejects
struct arguments with float members: Win64 passes struct members only
in integer slots or by reference, and the float-pair argument test
passes on windows-latest. README lists the race detector and FreeBSD
VM jobs in CI.
@kolkov kolkov added this to the v0.6.7 milestone Oct 18, 2026
@kolkov kolkov added priority: high Important for next release type: docs Documentation only labels Oct 18, 2026
@codecov

codecov Bot commented Oct 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kolkov
kolkov merged commit 817389f into main Oct 18, 2026
97 checks passed
@kolkov
kolkov deleted the release/v0.6.7 branch October 18, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: high Important for next release type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant