Go 1.19.5 repairs architecture-dependent atomic comparisons
Go 1.19.5 fixes atomic comparisons on several architectures and restores typed certificate errors on macOS. Both changes preserve behavior that application code should be able to take for granted.
A compare-and-swap operation can fail even when the stored value equals its expected argument if the compiler gives those two values different register representations. That is one of the defects repaired in Go 1.19.5, released on January 10. The release summary lists fixes to the compiler, linker and several standard library packages. The atomic fix explains why an apparently routine maintenance release can matter to code that has no visible connection to a new language feature.
I value Go because ordinary source code remains readable without reconstructing a private set of programming conventions. That expectation also depends on the implementation. A call to an atomic operation should retain its documented meaning when the compiler replaces it with machine instructions, including on architectures that the application author does not use for development.
Equal values with different register contents
The compiler backport concerns 32-bit atomic compare-and-swap on loong64, mips64 and riscv64. Those implementations use a load-linked and store-conditional instruction pair. The load-linked instruction sign-extends its result, which matters when the highest bit of the 32-bit value is set.
Consider an expected value of 0x80000000. As an unsigned 32-bit integer, it is a perfectly ordinary value. After the load-linked instruction places the stored value in a wider register, the register contains 0xffffffff80000000. An expected argument represented as 0x0000000080000000 has the same low 32 bits. A comparison of the wider registers nevertheless reports a difference.
That comparison is part of the implementation's decision about whether the expected value matches memory. Comparing inconsistent representations can therefore reject an operation that should be eligible to succeed. The repair sign-extends the expected argument before the comparison. Both operands then use the same representation, so the instruction compares the values that the Go operation is meant to compare.
The commit describes an especially revealing distinction between compiler paths. The problem appeared when the compiler used an intrinsic implementation. By contrast, the non-intrinsic path loaded the expected argument from the stack with a sign-extending instruction, so it did not have the same mismatch. A source-level operation could consequently encounter different behavior depending on how the compiler implemented the call.
This is an implementation error below the abstraction that application code uses. Changing a variable's name or rewriting a retry loop does not explain the defect. The relevant facts are the width of the operation, the upper bit of the value, and the instructions selected for the target architecture. A repair belongs in the compiler because that is where the inconsistent representations originate.
Portability includes compiler assumptions
Another backport in the release fixes an ARM64 conditional-select rule. The commit says that ARM64 maintains booleans in the low byte of registers and leaves the upper parts unspecified. The old rule used all 32 bits of a register containing a boolean. After the repair, the rule examines the low bit.
These changes concern different operations, but they share a concrete failure mode. Machine registers can contain bits that have no meaning for the source-level value. A compiler transformation must respect the representation promised by its own implementation. Treating unused bits as meaningful can change a program even when every operation in the source is well typed.
My preference for a small language does not imply that its compiler is simple. It means that compiler complexity should preserve a stable set of source-level obligations. These fixes are useful precisely because application authors should not need architecture-specific boolean conventions to write an ordinary conditional expression.
The release also addresses a different portability problem in the linker. The loong64 relocation backport recognizes R_LARCH_32_PCREL. Its description explains that newer binutils can emit this relocation in object files, including for some entries in .eh_frame. Previously, linking those objects could fail. The backport handles the relocation without adding an API to debug/elf, which keeps the maintenance change narrower than the original development change.
Certificate failures regain useful types
The macOS certificate verification change repairs a separate part of the application contract. It takes the error code returned by SecTrustEvaluateWithError and uses it to construct typed errors returned by certificate verification.
An error's type matters when callers distinguish classes of failure. A message that a person can read does not provide the same contract as a type that a program can inspect. The change preserves information supplied by the operating system when Go reports the failure to its caller. That makes the boundary between platform verification and Go error handling more useful without asking callers to interpret platform-specific text.
I like Go's explicit treatment of errors because it leaves operational decisions visible in the calling code. Typed verification errors support that style. The application can distinguish the failure it understands while retaining an error path for everything else, instead of treating every certificate failure as an undifferentiated string.
The atomic and certificate changes affect different layers, but each repairs behavior behind an existing interface. For applications built with Go 1.19, the value of this release lies in those corrected implementations. The source can stay the same while the generated comparisons and platform error translation become more faithful to the operations it already expresses.