Sixth target: s390x¶
s390x (IBM Z) is the sixth and final 64-bit Go SIMD target — and the first
big-endian one. It reuses the shared ABI0 model (abi) unchanged and adds
only a move table; the big-endian byte order is handled by cmd/asm and the Go
runtime, so the layout model needs no change.
The move table¶
| Type | arm64 | s390x |
|---|---|---|
int64 / uint64 / pointer |
MOVD |
MOVD |
int8/int16/int32 (signed) |
MOVB/MOVH/MOVW |
MOVB/MOVH/MOVW |
uint8/uint16/uint32 |
MOVBU/MOVHU/MOVWU |
MOVBZ/MOVHZ/MOVWZ |
float32 |
FMOVS |
FMOVS |
float64 |
FMOVD |
FMOVD |
s390x's 8-byte integer move is MOVD, floats use FMOVS/FMOVD, and unsigned
sub-word loads use the Z zero-extend suffix (MOVBZ/MOVHZ/MOVWZ). The
move surface is identical to ppc64le; what differs is the byte order.
Big-endian
s390x is big-endian. ABI0 offsets are still byte-for-byte identical to the
little-endian targets — FP-relative slot placement is endianness-agnostic —
but the runtime test deliberately exercises the big-endian path, catching any
accidental little-endian assumption in the emitted assembly.
Example¶
sig := s390x.Layout(
[]string{"a", "b"}, []s390x.Type{s390x.Int64, s390x.Int64},
[]string{"ret"}, []s390x.Type{s390x.Int64},
)
b := s390x.NewFunc("add", sig, 0)
b.LoadArg("a", "R1").
LoadArg("b", "R2").
Raw("ADD R2, R1, R3").
StoreRet("R3", "ret").
Ret()
emits:
TEXT ·add(SB), NOSPLIT, $0-24
MOVD a+0(FP), R1
MOVD b+8(FP), R2
ADD R2, R1, R3
MOVD R3, ret+16(FP)
RET
SIMD¶
The packed-add example uses the z/Architecture vector facility (VL / VAF /
VST) through Raw over pointer arguments — see SIMD. It runs on the
big-endian target, so lane order is checked there too.
Validation¶
There is no common s390x developer host, so correctness is established in layers —
asmdecl (cross-arch), cmd/asm cross-build, and runtime under qemu-user, in a
dedicated asm-s390x CI job on debian:trixie (the only big-endian runtime job):