44
55#include "textflag.h"
66
7- // AVX2 population-count routines using the Mula/Lemire VPSHUFB nibble-lookup
8- // kernel. Each 256-bit vector holds 4 uint64; the popcount of each byte is
9- // looked up via VPSHUFB over a 16-entry nibble table (replicated in both
10- // 128-bit lanes), the two nibble counts are added byte-wise, then VPSADBW sums
11- // each group of 8 bytes into a quadword that is accumulated. A scalar POPCNTQ
12- // tail handles the remaining (len % 4) words, so the routines are correct for
13- // any length. Loads/stores are unaligned (VMOVDQU) because the bitmap slices
14- // are only 8-byte aligned. VZEROUPPER precedes every return to avoid the
15- // AVX<->SSE transition penalty.
7+ // AVX2 population-count routines for amd64. They count the set bits across a
8+ // []uint64 (the backing storage of a bitmap container), optionally combining
9+ // each pair of words with a boolean op first: And, Or, Xor, and Mask (s &^ m).
10+ //
11+ // Algorithm (Mula/Lemire VPSHUFB nibble lookup)
12+ // ---------------------------------------------
13+ // AVX2 has no single "popcount a whole vector" instruction, so each byte's
14+ // popcount is taken from a 16-entry lookup table indexed by a 4-bit nibble:
15+ // a byte is split into its low and high nibble, each nibble is looked up (one
16+ // VPSHUFB performs all 32 lookups in a 256-bit register at once), the two
17+ // results are added to give a per-byte popcount, and VPSADBW then sums each
18+ // group of 8 byte-counts into a 64-bit lane total that is accumulated. After
19+ // the loop the four lane totals are summed (HSUM) into a scalar register.
20+ // Each iteration handles 256 bits (4 uint64); a scalar POPCNTQ tail handles
21+ // the trailing len%4 words, so any slice length is counted correctly.
22+ //
23+ // Go assembler conventions used below
24+ // -----------------------------------
25+ // - Operands are written source(s) first, destination LAST. So
26+ // "VPAND Ymask, Ydata, Ylo" means Ylo = Ydata AND Ymask.
27+ // - Yn are the 256-bit AVX registers; Xn aliases the low 128 bits of Yn.
28+ // - Arguments/results are read from the frame pointer (FP). A Go slice is a
29+ // 3-word header {ptr,len,cap}: s_base+0(FP), s_len+8(FP); a second slice
30+ // argument starts at +24(FP). The uint64 result slot follows the args
31+ // (e.g. ret+24(FP) for one slice arg, ret+48(FP) for two).
32+ // - Every routine is a leaf (makes no calls): NOSPLIT with a $0 local frame.
33+ // - Loads/stores use VMOVDQU (unaligned): container slices are only 8-byte
34+ // aligned, not 32. VZEROUPPER precedes every RET to avoid the AVX<->SSE
35+ // transition penalty in any non-VEX SSE code that runs afterwards.
1636
17- // 16-entry nibble popcount table, replicated across both 128-bit lanes.
37+ // lutmask is a 64-byte read-only blob holding two constants used by every
38+ // routine:
39+ // bytes 0..31 - the nibble popcount table, i.e. table[i] = number of set
40+ // bits in the 4-bit value i. VPSHUFB indexes within each
41+ // 128-bit lane independently, so the 16-entry table is stored
42+ // twice (once per lane). Read low-byte-first, the first qword
43+ // 0x0302020102010100 is the bytes {0,1,1,2,1,2,2,3} for
44+ // nibbles 0..7, and 0x0403030203020201 is {1,2,2,3,2,3,3,4}
45+ // for nibbles 8..15.
46+ // bytes 32..63 - 0x0F in every byte: a mask that isolates the low nibble of
47+ // each byte.
48+ // RODATA|NOPTR marks it read-only and pointer-free (so the GC ignores it).
1849DATA lutmask<>+0 (SB)/8 , $0x0302020102010100
1950DATA lutmask<>+8 (SB)/8 , $0x0403030203020201
2051DATA lutmask<>+16 (SB)/8 , $0x0302020102010100
2152DATA lutmask<>+24 (SB)/8 , $0x0403030203020201
22- // low-nibble mask, 0x0F in every byte.
2353DATA lutmask<>+32 (SB)/8 , $0x0f0f0f0f0f0f0f0f
2454DATA lutmask<>+40 (SB)/8 , $0x0f0f0f0f0f0f0f0f
2555DATA lutmask<>+48 (SB)/8 , $0x0f0f0f0f0f0f0f0f
2656DATA lutmask<>+56 (SB)/8 , $0x0f0f0f0f0f0f0f0f
2757GLOBL lutmask<>(SB), RODATA|NOPTR, $64
2858
59+ // Register aliases. Ylut/Ymask/Yzero are constants set up once per call (see
60+ // SETUP); Yacc is the running accumulator of lane totals; Ydata/Yb hold the
61+ // current input vector(s); Ylo/Yhi/Yc1/Yc2 are scratch used by COUNTBLOCK.
2962#define Ylut Y0
3063#define Ymask Y1
3164#define Yzero Y2
@@ -37,8 +70,18 @@ GLOBL lutmask<>(SB), RODATA|NOPTR, $64
3770#define Yc1 Y8
3871#define Yc2 Y9
3972
40- // COUNTBLOCK adds the per-byte popcount of Ydata into the quadword accumulator
41- // Yacc, using the lookup table Ylut, the nibble mask Ymask and Yzero.
73+ // COUNTBLOCK folds the popcount of the 32 bytes currently in Ydata into the
74+ // accumulator Yacc. Line by line:
75+ // VPAND Ymask,Ydata,Ylo : Ylo = low nibble of every byte
76+ // VPSRLW $4,Ydata,Yhi : shift each 16-bit lane right by 4...
77+ // VPAND Ymask,Yhi,Yhi : ...then mask, leaving the high nibble of each byte
78+ // VPSHUFB Ylo,Ylut,Yc1 : Yc1[b] = popcount(low nibble of byte b)
79+ // VPSHUFB Yhi,Ylut,Yc2 : Yc2[b] = popcount(high nibble of byte b)
80+ // VPADDB Yc2,Yc1,Yc1 : Yc1[b] = popcount(byte b) (0..8 each)
81+ // VPSADBW Yzero,Yc1,Yc1 : sum each group of 8 bytes -> 4 lane totals (0..512)
82+ // VPADDQ Yc1,Yacc,Yacc : add the 4 lane totals into the accumulator
83+ // Per-byte counts max at 8 and lane totals at 512, so accumulating across the
84+ // whole loop never overflows the 64-bit lanes.
4285#define COUNTBLOCK \
4386 VPAND Ymask, Ydata, Ylo \
4487 VPSRLW $4 , Ydata, Yhi \
@@ -49,14 +92,18 @@ GLOBL lutmask<>(SB), RODATA|NOPTR, $64
4992 VPSADBW Yzero, Yc1, Yc1 \
5093 VPADDQ Yc1, Yacc, Yacc
5194
52- // SETUP loads the constant table/mask and zeroes the accumulator registers.
95+ // SETUP loads the lookup table and nibble mask and zeroes Yzero (the VPSADBW
96+ // addend) and Yacc (the accumulator). Run once at the top of each routine.
5397#define SETUP \
5498 VMOVDQU lutmask<>+0 (SB), Ylut \
5599 VMOVDQU lutmask<>+32 (SB), Ymask \
56100 VPXOR Yzero, Yzero, Yzero \
57101 VPXOR Yacc, Yacc, Yacc
58102
59- // HSUM horizontally sums the quadwords of Yacc into AX.
103+ // HSUM reduces Yacc's four 64-bit lane totals to a single sum in AX. X3 is the
104+ // low 128 bits of Yacc (Y3); VEXTRACTI128 pulls the high 128 bits into X5, the
105+ // two halves are added (giving two qwords in X3), and those two qwords are then
106+ // added into AX.
60107#define HSUM \
61108 VEXTRACTI128 $1 , Yacc, X5 \
62109 VPADDQ X5, X3, X3 \
@@ -66,43 +113,49 @@ GLOBL lutmask<>(SB), RODATA|NOPTR, $64
66113 ADDQ DX, AX
67114
68115// func _popcntSliceAVX2(s []uint64) uint64
116+ // Returns the total number of set bits in s. This is the canonical routine;
117+ // the And/Or/Xor/Mask variants below share its structure and differ only by
118+ // the boolean op applied before counting.
69119TEXT ·_popcntSliceAVX2(SB), NOSPLIT, $0 -32
70- MOVQ s_base+0 (FP), SI
71- MOVQ s_len+8 (FP), CX
72- XORQ AX, AX
73- SETUP
120+ MOVQ s_base+0 (FP), SI // SI = &s[0]
121+ MOVQ s_len+8 (FP), CX // CX = len(s), in 64-bit words
122+ XORQ AX, AX // AX = running result
123+ SETUP // load table/mask; zero Yzero and Yacc
74124 MOVQ CX, R8
75- SHRQ $2 , R8
125+ SHRQ $2 , R8 // R8 = len/4 = number of full 256-bit blocks
76126 TESTQ R8, R8
77- JZ slicetail
127+ JZ slicetail // fewer than 4 words: skip the vector loop
78128sliceloop:
79- VMOVDQU (SI), Ydata
80- COUNTBLOCK
81- ADDQ $32 , SI
129+ VMOVDQU (SI), Ydata // load 4 words (32 bytes)
130+ COUNTBLOCK // Yacc += popcount(those 32 bytes)
131+ ADDQ $32 , SI // advance to the next block
82132 DECQ R8
83133 JNZ sliceloop
84- HSUM
134+ HSUM // AX += sum of Yacc's lane totals
85135slicetail:
86- ANDQ $3 , CX
136+ ANDQ $3 , CX // CX = len % 4 = leftover words (0..3)
87137 TESTQ CX, CX
88138 JZ slicedone
89139slicetailloop:
90140 MOVQ (SI), DX
91- POPCNTQ DX, DX
141+ POPCNTQ DX, DX // scalar popcount of one word
92142 ADDQ DX, AX
93143 ADDQ $8 , SI
94144 DECQ CX
95145 JNZ slicetailloop
96146slicedone:
97- VZEROUPPER
98- MOVQ AX, ret +24 (FP)
147+ VZEROUPPER // clear upper YMM state before returning
148+ MOVQ AX, ret +24 (FP) // return AX
99149 RET
100150
101151// func _popcntAndSliceAVX2(s, m []uint64) uint64
152+ // Returns the sum of popcount(s[i] & m[i]). Mirrors _popcntSliceAVX2 but loads
153+ // a vector from each of s and m and ANDs them before counting. s and m are
154+ // assumed to have equal length.
102155TEXT ·_popcntAndSliceAVX2(SB), NOSPLIT, $0 -56
103- MOVQ s_base+0 (FP), SI
104- MOVQ m_base+24 (FP), DI
105- MOVQ s_len+8 (FP), CX
156+ MOVQ s_base+0 (FP), SI // SI = &s[0]
157+ MOVQ m_base+24 (FP), DI // DI = &m[0]
158+ MOVQ s_len+8 (FP), CX // CX = len
106159 XORQ AX, AX
107160 SETUP
108161 MOVQ CX, R8
@@ -112,7 +165,7 @@ TEXT ·_popcntAndSliceAVX2(SB), NOSPLIT, $0-56
112165andloop:
113166 VMOVDQU (SI), Ydata
114167 VMOVDQU (DI), Yb
115- VPAND Yb, Ydata, Ydata
168+ VPAND Yb, Ydata, Ydata // Ydata = s & m
116169 COUNTBLOCK
117170 ADDQ $32 , SI
118171 ADDQ $32 , DI
@@ -125,7 +178,7 @@ andtail:
125178 JZ anddone
126179andtailloop:
127180 MOVQ (SI), DX
128- ANDQ (DI), DX
181+ ANDQ (DI), DX // s & m, one word
129182 POPCNTQ DX, DX
130183 ADDQ DX, AX
131184 ADDQ $8 , SI
@@ -134,10 +187,12 @@ andtailloop:
134187 JNZ andtailloop
135188anddone:
136189 VZEROUPPER
137- MOVQ AX, ret +48 (FP)
190+ MOVQ AX, ret +48 (FP) // +48: result follows two 24-byte slice headers
138191 RET
139192
140193// func _popcntOrSliceAVX2(s, m []uint64) uint64
194+ // Returns the sum of popcount(s[i] | m[i]); see _popcntAndSliceAVX2 for the
195+ // shared structure.
141196TEXT ·_popcntOrSliceAVX2(SB), NOSPLIT, $0 -56
142197 MOVQ s_base+0 (FP), SI
143198 MOVQ m_base+24 (FP), DI
@@ -151,7 +206,7 @@ TEXT ·_popcntOrSliceAVX2(SB), NOSPLIT, $0-56
151206orloop:
152207 VMOVDQU (SI), Ydata
153208 VMOVDQU (DI), Yb
154- VPOR Yb, Ydata, Ydata
209+ VPOR Yb, Ydata, Ydata // Ydata = s | m
155210 COUNTBLOCK
156211 ADDQ $32 , SI
157212 ADDQ $32 , DI
@@ -164,7 +219,7 @@ ortail:
164219 JZ ordone
165220ortailloop:
166221 MOVQ (SI), DX
167- ORQ (DI), DX
222+ ORQ (DI), DX // s | m, one word
168223 POPCNTQ DX, DX
169224 ADDQ DX, AX
170225 ADDQ $8 , SI
@@ -177,6 +232,8 @@ ordone:
177232 RET
178233
179234// func _popcntXorSliceAVX2(s, m []uint64) uint64
235+ // Returns the sum of popcount(s[i] ^ m[i]); see _popcntAndSliceAVX2 for the
236+ // shared structure.
180237TEXT ·_popcntXorSliceAVX2(SB), NOSPLIT, $0 -56
181238 MOVQ s_base+0 (FP), SI
182239 MOVQ m_base+24 (FP), DI
@@ -190,7 +247,7 @@ TEXT ·_popcntXorSliceAVX2(SB), NOSPLIT, $0-56
190247xorloop:
191248 VMOVDQU (SI), Ydata
192249 VMOVDQU (DI), Yb
193- VPXOR Yb, Ydata, Ydata
250+ VPXOR Yb, Ydata, Ydata // Ydata = s ^ m
194251 COUNTBLOCK
195252 ADDQ $32 , SI
196253 ADDQ $32 , DI
@@ -203,7 +260,7 @@ xortail:
203260 JZ xordone
204261xortailloop:
205262 MOVQ (SI), DX
206- XORQ (DI), DX
263+ XORQ (DI), DX // s ^ m, one word
207264 POPCNTQ DX, DX
208265 ADDQ DX, AX
209266 ADDQ $8 , SI
@@ -216,7 +273,9 @@ xordone:
216273 RET
217274
218275// func _popcntMaskSliceAVX2(s, m []uint64) uint64
219- // Computes sum of popcount(s[i] &^ m[i]) == popcount(s & ~m).
276+ // Returns the sum of popcount(s[i] &^ m[i]) == popcount(s & ~m). Same structure
277+ // as _popcntAndSliceAVX2; the combine is VPANDN, which computes (NOT first) AND
278+ // second, i.e. VPANDN Ydata, Yb, Ydata -> Ydata = (NOT Yb) AND Ydata = s &^ m.
220279TEXT ·_popcntMaskSliceAVX2(SB), NOSPLIT, $0 -56
221280 MOVQ s_base+0 (FP), SI
222281 MOVQ m_base+24 (FP), DI
@@ -230,7 +289,7 @@ TEXT ·_popcntMaskSliceAVX2(SB), NOSPLIT, $0-56
230289maskloop:
231290 VMOVDQU (SI), Ydata
232291 VMOVDQU (DI), Yb
233- VPANDN Ydata, Yb, Ydata
292+ VPANDN Ydata, Yb, Ydata // Ydata = s &^ m (= (NOT m) AND s)
234293 COUNTBLOCK
235294 ADDQ $32 , SI
236295 ADDQ $32 , DI
@@ -243,9 +302,9 @@ masktail:
243302 JZ maskdone
244303masktailloop:
245304 MOVQ (DI), R10
246- NOTQ R10
305+ NOTQ R10 // ~m
247306 MOVQ (SI), DX
248- ANDQ R10, DX
307+ ANDQ R10, DX // s &^ m = s & ~m, one word
249308 POPCNTQ DX, DX
250309 ADDQ DX, AX
251310 ADDQ $8 , SI
@@ -258,22 +317,28 @@ maskdone:
258317 RET
259318
260319// func _hasAVX2() bool
261- // Reports whether the CPU supports AVX2 and the OS has enabled YMM state.
320+ // Reports whether the CPU supports AVX2 and the OS has enabled the wide (YMM)
321+ // register state. All three checks must pass; otherwise the Go wrappers fall
322+ // back to the scalar implementation. Note CPUID clobbers AX/BX/CX/DX.
262323TEXT ·_hasAVX2(SB), NOSPLIT, $0 -1
263- // CPUID leaf 1: require OSXSAVE (ECX bit 27) and AVX (ECX bit 28).
324+ // CPUID leaf 1: require OSXSAVE (ECX bit 27) and AVX (ECX bit 28). Both must
325+ // be set, so mask and compare against the combined bit pattern.
264326 MOVL $1 , AX
265327 XORL CX, CX
266328 CPUID
267329 ANDL $0x18000000 , CX
268330 CMPL CX, $0x18000000
269331 JNE noavx2
270- // XGETBV(0): require XCR0 bits 1 (SSE) and 2 (AVX/YMM) set.
332+ // XGETBV(0): the OS must have enabled saving of SSE and AVX/YMM state, i.e.
333+ // XCR0 bits 1 and 2. Without this the YMM registers would be corrupted
334+ // across a context switch even though the CPU supports the instructions.
271335 XORL CX, CX
272336 XGETBV
273337 ANDL $0x6 , AX
274338 CMPL AX, $0x6
275339 JNE noavx2
276- // CPUID leaf 7, sub-leaf 0: require AVX2 (EBX bit 5).
340+ // CPUID leaf 7, sub-leaf 0: require AVX2 itself (EBX bit 5). The sub-leaf is
341+ // selected via ECX, which must be 0.
277342 MOVL $7 , AX
278343 XORL CX, CX
279344 CPUID
0 commit comments