History

simd: stop the AVX2 rolling checksum reading past its buffer

The loop is software-pipelined: each iteration folds in the 64 bytes it preloaded last time and preloads the next 64. Nothing stopped the final iteration doing that preload, so it always read the 64 bytes after the region it was asked to checksum. Not an edge case. The assembly processes len&~63 and leaves the remainder to the caller, so the remainder is by construction under 64 bytes and the preload passed buf+len on every call, by 64 minus the remainder. It normally landed in slack inside the map_ptr() window and nothing noticed. Where the buffer ended near an unmapped page it was a SIGSEGV in the middle of a transfer -- reported on macOS x86-64 by Roland Kletzing, whose `partial` run died with "connection unexpectedly closed" because the generator had crashed. A guard page reproduces it on Linux too, so it was latent there, not absent. Run the pipelined loop one block short and finish the last block in .last, which does the same arithmetic without the preload. No per-iteration cost, and checksums are bit-identical -- simdtest compares every implementation against the C reference. The earlier fix for that report, "lib: use .balign in md5 x86-64 asm", was to the md5 assembly. It addressed the linker alignment warning that appeared alongside, not this.

Andrew Tridgell committed 3 months ago

Commit 2979d8e1 parent bb6329b1 file · +31 −1Browse files

Unified · Split

simd-checksum-avx2.S+31−1
@@ -46,6 +46,14 @@ get_checksum1_avx2_asm: · 45 unmodified lines
46 vpxor xmm1, xmm1, xmm1 # reset both partial sums accumulators.46 vpxor xmm1, xmm1, xmm1 # reset both partial sums accumulators.
47 vpxor xmm4, xmm4, xmm447 vpxor xmm4, xmm4, xmm4
48 mov eax, [r8]48 mov eax, [r8]
49 # The loop below is software-pipelined: it preloads the 64 bytes AFTER the
50 # ones it is folding in. Run it one block short and finish that block in
51 # .last, which does not preload -- otherwise the final iteration reads 64
52 # bytes past the end of what the caller gave us. The remainder (len & 63)
53 # is always under 64, so that read always crossed buf+len; it only faulted
54 # when the buffer happened to end at a page boundary.
55 sub esi, 1
56 jz .last
49 .p2align 4 # should fit into the LSD allocation queue.57 .p2align 4 # should fit into the LSD allocation queue.
50.loop:58.loop:
51 vpmaddubsw ymm0, ymm15, ymm2 # s1 partial sums59 vpmaddubsw ymm0, ymm15, ymm2 # s1 partial sums
@@ -70,11 +78,33 @@ get_checksum1_avx2_asm: · 18 unmodified lines
70 vpaddd ymm6, ymm10, ymm6 # 32*CHAR_OFFSET78 vpaddd ymm6, ymm10, ymm6 # 32*CHAR_OFFSET
71 vpaddd ymm1, ymm13, ymm1 # 528*CHAR_OFFSET79 vpaddd ymm1, ymm13, ymm1 # 528*CHAR_OFFSET
72#endif80#endif
73 vmovdqa ymm2, ymm8 # move the next 64 bytes 81 vmovdqa ymm2, ymm8 # move the next 64 bytes
74 vmovdqa ymm3, ymm9 # into the right registers82 vmovdqa ymm3, ymm9 # into the right registers
75 sub esi, 183 sub esi, 1
76 jnz .loop84 jnz .loop
77 85
86 # The final 64 bytes, already in ymm2/ymm3. Same arithmetic as the loop
87 # body with the preload, the prefetch and the loop control dropped.
88.last:
89 vpmaddubsw ymm0, ymm15, ymm2 # s1 partial sums
90 vpmaddubsw ymm5, ymm15, ymm3
91 vpaddd ymm4, ymm4, ymm6
92 vpaddw ymm5, ymm5, ymm0
93 vpsrld ymm0, ymm5, 16
94 vpaddw ymm5, ymm0, ymm5
95 vpaddd ymm6, ymm5, ymm6
96 vpmaddubsw ymm2, ymm7, ymm2 # s2 partial sums
97 vpmaddubsw ymm3, ymm12, ymm3
98 vpaddw ymm3, ymm2, ymm3
99 vpsrldq ymm2, ymm3, 2
100 vpaddd ymm3, ymm2, ymm3
101 vpaddd ymm1, ymm1, ymm3
102
103#if CHAR_OFFSET != 0
104 vpaddd ymm6, ymm10, ymm6 # 32*CHAR_OFFSET
105 vpaddd ymm1, ymm13, ymm1 # 528*CHAR_OFFSET
106#endif
107
78 # now we reduce the partial sums.108 # now we reduce the partial sums.
79 vpslld ymm3, ymm4, 6109 vpslld ymm3, ymm4, 6
80 vpsrldq ymm2, ymm6, 4110 vpsrldq ymm2, ymm6, 4