Source file src/hash/adler32/adler32_simd_amd64.go
1 // Copyright 2026 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build goexperiment.simd 6 7 package adler32 8 9 import "simd/archsimd" 10 11 // haveSIMD reports whether the CPU supports the AVX2 instructions 12 // used by updateSIMD. 13 var haveSIMD = archsimd.X86.AVX2() 14 15 const ( 16 // minSIMD is the smallest input length for which updateSIMD 17 // outperforms updateGeneric. 18 minSIMD = 64 19 20 // blockSize is the number of bytes processed per iteration of 21 // the vector loop. 22 blockSize = 32 23 24 // nmaxSIMD is nmax rounded down to a multiple of blockSize. The 25 // vector loop processes at most this many bytes between modular 26 // reductions. 27 nmaxSIMD = nmax - nmax%blockSize 28 ) 29 30 // taps[i] is the number of times the i'th byte of a block is counted 31 // in the second sum: blockSize times for the first byte down to once 32 // for the last. Cross-block contributions are accounted for by vps 33 // in updateSIMD. 34 var taps = [blockSize]int8{ 35 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 36 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 37 } 38 39 // updateSIMD computes the checksum using AVX2 vector instructions, 40 // processing blockSize bytes per iteration. It accumulates, in 41 // uint32 lanes: 42 // 43 // vs1: the plain sum of all bytes, via VPSADBW against zero, 44 // vs2: the taps-weighted sum of the bytes of each block, via 45 // VPMADDUBSW with the taps and VPMADDWD with 1, 46 // vps: the sum, over all blocks, of vs1 as it stood before that block, 47 // 48 // so that after an n-byte run starting from state (s1, s2), 49 // s1' = s1 + sum(vs1) and 50 // s2' = s2 + n*s1 + blockSize*sum(vps) + sum(vs2). 51 // 52 // The VPMADDUBSW pair sums are at most 255*(32+31) < 2^15-1, so they 53 // cannot saturate. Modular reduction is deferred to the end of each 54 // run. A run is at most nmaxSIMD <= nmax bytes, so by nmax's 55 // defining property the total contribution to s2', and hence every 56 // uint32 lane, stays below 2^32. 57 func updateSIMD(d digest, p []byte) digest { 58 s1, s2 := uint32(d&0xffff), uint32(d>>16) 59 60 w := archsimd.LoadInt8x32Array(&taps) 61 ones := archsimd.BroadcastInt16x16(1) 62 var zero archsimd.Uint8x32 63 64 for len(p) >= blockSize { 65 n := nmaxSIMD 66 if n > len(p) { 67 n = len(p) - len(p)%blockSize 68 } 69 q := p[:n] 70 var vs1, vs2, vps archsimd.Uint32x8 71 for len(q) >= blockSize { 72 b := archsimd.LoadUint8x32(q) 73 vps = vps.Add(vs1) 74 vs1 = vs1.Add(b.SumOf8AbsDiff(zero).AsUint32x8()) 75 vs2 = vs2.Add(b.DotProductPairsSaturated(w).DotProductPairs(ones).AsUint32x8()) 76 q = q[blockSize:] 77 } 78 vs2 = vs2.Add(vps.ShiftAllLeft(5)) // 32 = blockSize bytes per block 79 r1 := vs1.GetLo().Add(vs1.GetHi()) 80 r2 := vs2.GetLo().Add(vs2.GetHi()) 81 s2 += uint32(n)*s1 + r2.GetElem(0) + r2.GetElem(1) + r2.GetElem(2) + r2.GetElem(3) 82 s1 += r1.GetElem(0) + r1.GetElem(1) + r1.GetElem(2) + r1.GetElem(3) 83 s1 %= mod 84 s2 %= mod 85 p = p[n:] 86 } 87 if len(p) > 0 { 88 return updateGeneric(digest(s2<<16|s1), p) 89 } 90 return digest(s2<<16 | s1) 91 } 92