Repository navigation
188 lines (181 loc) · 8.33 KB
/
Copy pathci.yml
File metadata and controls
188 lines (181 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
# Split-CI coverage policy (mirroring the go-asmgen / go-fft orgs).
#
# A single `go test -coverpkg` run cannot reach 100% statement coverage over a
# SIMD-dispatched package: each arch compiles exactly one sumSIMD wrapper — the
# amd64 SSE2 wrapper (reduce_amd64.go -> sum_amd64.s), the arm64 NEON wrapper
# (reduce_arm64.go -> sum_arm64.s), the ppc64le VSX wrapper (reduce_ppc64le.go
# -> sum_ppc64le.s), the loong64 LASX wrapper (reduce_loong64.go ->
# sum_loong64.s), the riscv64 RVV wrapper (reduce_riscv64.go -> sum_riscv64.s),
# the s390x vector wrapper (reduce_s390x.go -> sum_s390x.s), or the
# scalar-alias wrapper (reduce_generic.go, on the 32-bit targets). No
# single build sees
# more than one, so no single run covers them all.
#
# The honest split:
# * The pure-Go scalar CORE plus the multicore kernels (parallel.go) are held
# to 100% statement coverage by the `test` gate. It runs on the amd64 hosts
# (ubuntu/windows) AND the arm64 host (macos); on each, exactly one sumSIMD
# wrapper compiles and IS exercised by TestSumSIMD, so the gate legitimately
# reaches 100% on every OS — nothing reachable is excluded.
# * The generated Plan 9 assembly carries no Go statements, so it cannot be
# statement-covered. It is validated by the per-arch execution jobs
# (arch-native / arch-qemu): each regenerates the committed .s with
# go-asmgen and fails if it is stale, vets it (asmdecl cross-checks every
# name+offset(FP)), builds it (cmd/asm encodes the mnemonics), and RUNS the
# suite — TestSumSIMD asserts the SIMD sum matches the scalar oracle to a
# tight relative tolerance (the lane-parallel grouping is a valid reordering,
# exactly the trade-off numpy's pairwise summation makes; bit-identity is
# not claimed for a reduction).
#
# So every line is either statement-covered by the gate or executed by a
# per-arch job; the gate is never lowered and there is no coverage-gaming knob.
# amd64/arm64/ppc64le/loong64 ship SIMD kernels (ppc64le's and loong64's were
# run on real POWER8/POWER9 and a Loongson 3C5000L; under qemu here loong64
# takes its scalar fallback unless the emulated CPU reports LASX);
# riscv64 ships RVV kernels (used when the CPU reports V) and s390x
# vector-facility kernels (used when AT_HWCAP reports it).
jobs:
test:
name: test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: 1.27.2
- run: go version
- run: go vet ./...
- run: go build ./...
- name: Test + 100% coverage gate
shell: bash
run: |
COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -n 40
cov=$(go tool cover -func=cover.out | tail -1 | awk '{print $3}' | tr -d '%')
echo "coverage: ${cov}%"
awk -v t="$cov" 'BEGIN { exit !(t+0 >= 100.0) }' || {
echo "::error::coverage ${cov}% < 100%"; exit 1; }
# Native per-arch execution. amd64/arm64 have GitHub-hosted runners, so the
# generated assembly runs natively. Each regenerates the committed .s via
# go-asmgen and fails if it is stale, then vets/builds/runs the suite —
# TestSumSIMD asserts SIMD sum == scalar oracle (to tolerance).
arch-native:
name: arch ${{ matrix.label }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, label: amd64, regen: amd64 }
- { os: ubuntu-24.04-arm, label: arm64, regen: arm64 }
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: 1.27.2
- run: go env GOARCH
- name: Regenerate committed assembly and check it is current
run: |
( cd internal/kernels/asmgen/${{ matrix.regen }} && GOFLAGS=-mod=mod go run gen.go )
mv internal/kernels/asmgen/${{ matrix.regen }}/sum_${{ matrix.regen }}.s \
internal/kernels/sum_${{ matrix.regen }}.s
git diff --exit-code -- internal/kernels/sum_${{ matrix.regen }}.s || {
echo "::error::generated ${{ matrix.regen }} assembly is stale; run go generate and commit"; exit 1; }
- name: Vet (asmdecl) + build + run (SIMD == scalar)
run: |
go vet ./...
go build ./...
go test ./...
# qemu-user execution for the four targets without a hosted runner. They keep
# the validated scalar reduction path (no vector-double SIMD kernel shipped),
# so for them this job is the endianness/word-size guard plus the
# TestSumSIMD scalar-alias check; the amd64 SSE2 kernel is additionally
# cross-validated here under qemu-x86_64.
arch-qemu:
name: arch ${{ matrix.goarch }} (qemu)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { goarch: riscv64, qemu: qemu-riscv64-static }
- { goarch: loong64, qemu: qemu-loongarch64-static }
- { goarch: ppc64le, qemu: qemu-ppc64le-static }
- { goarch: s390x, qemu: qemu-s390x-static }
# 32-bit: int is 32 bits, so shape/size overflow paths differ.
- { goarch: arm, qemu: qemu-arm-static }
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: 1.27.2
- run: sudo apt-get update && sudo apt-get install -y qemu-user-static
- name: Regenerate committed assembly and check it is current
if: matrix.goarch == 'ppc64le' || matrix.goarch == 'loong64' || matrix.goarch == 'riscv64' || matrix.goarch == 's390x'
run: |
( cd internal/kernels/asmgen/${{ matrix.goarch }} && go run . )
mv internal/kernels/asmgen/${{ matrix.goarch }}/sum_${{ matrix.goarch }}.s internal/kernels/sum_${{ matrix.goarch }}.s
git diff --exit-code -- internal/kernels/sum_${{ matrix.goarch }}.s || {
echo "::error::generated ${{ matrix.goarch }} assembly is stale; run go generate and commit"; exit 1; }
- name: Vet + test under qemu (${{ matrix.goarch }})
run: |
GOOS=linux GOARCH=${{ matrix.goarch }} go vet ./...
GOARCH=${{ matrix.goarch }} go test -exec=${{ matrix.qemu }} ./...
# 386 runs natively on the amd64 runner (the kernel executes 32-bit x86
# binaries), not under qemu-i386, whose emulation loses x87/SSE state across
# preemption. It is the second 32-bit word-size lane, beside arm.
arch-386:
name: arch 386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: 1.27.2
- name: Vet + test (GOARCH=386)
run: |
GOARCH=386 go vet ./...
GOARCH=386 go test ./...
# Every GOOS/GOARCH pair Go supports must at least compile the library's
# tests: a test that does not compile on a target is a test
# that never runs there, and nothing else would notice (a 1<<62 literal
# once broke every 32-bit target this way). Execution is the lanes above.
cross-compile:
name: compile tests on every target
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: 1.27.2
- name: go test -c (go vet on android/ios) for each go tool dist list pair
run: |
fail=0
for t in $(go tool dist list); do
os=${t%/*}; arch=${t#*/}
# android/ios refuse to build without a cgo linker, even for vet;
# with cgo nominally on and CC=false, vet type-checks the package
# and its tests (there is no import "C" to compile).
case $os in android|ios) mode=vet ;; *) mode=test ;; esac
for p in $(go list ./...); do
if [ $mode = test ]; then
out=$(CGO_ENABLED=0 GOOS=$os GOARCH=$arch go test -c -o /dev/null "$p" 2>&1) || { echo "::error::$t $p: $out"; fail=1; }
else
out=$(CGO_ENABLED=1 CC=false GOOS=$os GOARCH=$arch go vet "$p" 2>&1) || { echo "::error::$t $p: $out"; fail=1; }
fi
done
done
exit $fail