purego v0.11.1 - SyscallN Error Slot and 386 Callback Returns


purego v0.11.1 was published on 21 September 2026. The release fixes two correctness bugs in the path Go programs use to call native libraries without cgo. On platforms that do not capture errno, SyscallN was returning the third input argument as the error code, and that third return is now 0.

The full release notes and downloads are on the GitHub release page.

SyscallN calls a native function and returns two result words plus a third value. The documentation calls that third value the libc error code. Issue #505, cited in the release notes, showed that on trampolines which do not write errno back, the third value was still the caller’s third argument.

Darwin stores a real libc errno. The Linux cgo fallback writes errno into the same slot. Every other trampoline left the slot untouched, so SyscallN returned argument three: a flag word, a length, a mode, or a pointer.

A non zero third argument made a successful call look failed. A zero third argument made a failed call look like error 0. The first result stayed honest. A failing open can return the all ones word in the first result while the third return stays 0, because the flags argument was 0 and nothing stored ENOENT. Logging only the third return hid the failure. Branching on any non zero third return aborted good calls whenever that argument was set.

v0.11.1 clears the slot after the call on trampolines that do not capture errno. The third return is 0. Darwin still returns the libc errno it stored. The Linux cgo fallback still returns the errno it wrote. The assembly trampolines on Linux amd64, Linux arm64, FreeBSD, and NetBSD are in the group that returns 0. A missing file on Linux amd64 still comes back with third return 0. Decide failure from the primary return, or from the status convention of the library loaded with Dlopen and Dlsym.

A batch worker that binds a codec, a compression library, or a client library through purego should recheck that branch. Treating a non zero third return as fatal was coupled to an input argument. After this tag that branch stays quiet on the trampolines that do not capture errno. Treating 0 as libc reporting success is still wrong there. 0 means the trampoline did not capture an error. Two calls that differed only in flags or length no longer log two different error codes for the same outcome.

The other fix is limited to GOARCH=386. NewCallback hands C a pointer to a Go function. On 386 the cdecl ABI returns a 64 bit integer in EDX:EAX. The high 32 bits belong in EDX. Issue #524 showed the callback trampoline loaded only EAX. Every int64 and uint64 result lost its upper half. A callback that returned 0x100000002 came back as 2, plus whatever stale bits were already in EDX.

Values that fit in 32 bits still survived the round trip, so a test with a short length or a small counter passed. File offsets, nanosecond timestamps, and row counts past 4294967295 did not. The truncation is the same on every run, including a build with CGO_ENABLED=0.

The same trampoline overlapped the copied C arguments with the result words in callbackArgs. A long argument list could overwrite the result before the return. v0.11.1 reserves the full result struct, copies arguments past that area, and returns both words in EDX:EAX. Regression tests cover signed results, unsigned results, and a callback that takes stack arguments.

amd64 and arm64 already return a 64 bit integer in one register. This tag leaves those ports alone. A GOARCH=386 cross compile in CI is where the truncated return shows up. If the process never uses NewCallback with an int64 or uint64 result, 386 behavior is unchanged.

No symbol was renamed. No config key was added. The notes list no migration steps.

On platforms that do not capture errno, the third return of SyscallN is 0 on every call. Code that failed a call because that value was non zero will accept calls it used to reject. Read the primary result first. Darwin and the Linux cgo fallback still place a captured error in the third return. A helper that assumes every GOOS fills errno will misread Linux amd64.

On 386, remove compensation that rebuilt the high half of a callback result, or that masked the result to 32 bits to match the old trampoline. The high half is now the value the Go function returned.

The tag is a final release. Set the module requirement to v0.11.1 and rebuild.