fix: thread-safety in count_sliced, ship measured wins

Review (5-lane) found one MAJOR: count_sliced ignored pthread_create's
return value - on EAGAIN it joined an indeterminate pthread_t and summed
an uninitialized slice, UB plus a silently wrong count. Threads are now
initialized to 0, a failed create counts its slice inline, and nt is
hard-capped at a named MAX_THREADS (the fixed jobs[8]/th[8] arrays
smash the stack past 8 threads - reproduced by QA's thread sweep).

Shipped from the measured optimization hunt:
- -w-only mode skips the newline compare/popcount entirely (the
  (x-9)<5 range already covers '\n' in the whitespace mask) - a
  need_lines gate threads through every kernel, the scalar reference,
  the avx512 mirror, and the sliced workers.
- Thread retune: 12 threads past 32 MiB, 16 past 256 MiB (was 8) -
  up to 18% on 110 MB, ~3% on 1.1 GB in QA's interleaved sweep.
- Benchmark suites pin LC_ALL=C so GNU wc -w can't silently switch to
  multibyte decoding and inflate the win.
- popcount16 is guarded to x86 builds (zero-warnings on other arches);
  checkwc's stray argument dropped.

Verified: selftest (kernels + sliced, now with need_lines coverage),
120-trial fuzz vs GNU (file + stdin, both locales), full test-all suite
three consecutive times, format-check and clang-tidy clean. The 1B
solo monster now lands at 3.7-6s (up to 268 Mlines/s) depending on how
warm the page cache is feeling.
This commit is contained in:
2026-08-29 17:03:31 -04:00
parent 6aa461f053
commit ae5068d4e7
7 changed files with 120 additions and 67 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ writes a shame report and exits non-zero. These are the facts:
| lines (1M lines) | **Win: 1ms vs 2ms.** GNU's AVX-512 assist can't beat a mapped file. | | lines (1M lines) | **Win: 1ms vs 2ms.** GNU's AVX-512 assist can't beat a mapped file. |
| lines (10M lines) | **Win: 8-9ms vs 22-24ms (~2.5x).** GNU's lead never survives contact with the buffer. | | lines (10M lines) | **Win: 8-9ms vs 22-24ms (~2.5x).** GNU's lead never survives contact with the buffer. |
| lines (100M lines) | **Win: ~70ms vs ~140ms.** The monster race. GNU gets lapped. | | lines (100M lines) | **Win: ~70ms vs ~140ms.** The monster race. GNU gets lapped. |
| lines (1B lines) | **Solo, ~6-8s.** 11 GB in one pass; the only bottleneck left is the disk. | | lines (1B lines) | **Solo, ~4-6s.** 11 GB in one pass; the only bottleneck left is the disk. |
| stdin words (1M lines) | **Win: 12x.** GNU still reads stdin like it's 1985. | | stdin words (1M lines) | **Win: 12x.** GNU still reads stdin like it's 1985. |
| stdin lines (10M lines) | **Win: ~2.5x.** We map stdin redirects; GNU maps nothing. | | stdin lines (10M lines) | **Win: ~2.5x.** We map stdin redirects; GNU maps nothing. |
+1 -1
View File
@@ -14,7 +14,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
source "$REPO_DIR/benchmarks/std.sh" source "$REPO_DIR/benchmarks/std.sh"
checkfastwc checkfastwc
checkwc coreutils checkwc
BENCH_NAME="coreutils" BENCH_NAME="coreutils"
printf 'benchmarking %s wc vs fastwc: lines, file input (%s interleaved runs each, minimum kept)\n' \ printf 'benchmarking %s wc vs fastwc: lines, file input (%s interleaved runs each, minimum kept)\n' \
+1 -1
View File
@@ -12,7 +12,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
source "$REPO_DIR/benchmarks/std.sh" source "$REPO_DIR/benchmarks/std.sh"
checkfastwc checkfastwc
checkwc coreutils checkwc
BENCH_NAME="coreutils" BENCH_NAME="coreutils"
printf 'benchmarking %s wc vs fastwc: words, file input (%s interleaved runs each, minimum kept)\n' \ printf 'benchmarking %s wc vs fastwc: words, file input (%s interleaved runs each, minimum kept)\n' \
+6
View File
@@ -27,6 +27,12 @@
set -u set -u
# Pin the C locale: GNU wc -w silently switches to multibyte decoding under a
# UTF-8 locale, which would slow the oracle down and mask the documented
# byte-semantics divergence. Both sides count bytes here.
export LC_ALL=C
export LC_CTYPE=C
FASTWC="$REPO_DIR/bin/release/fastwc" FASTWC="$REPO_DIR/bin/release/fastwc"
DATA_DIR="$SCRIPT_DIR/.data" DATA_DIR="$SCRIPT_DIR/.data"
GENFILE="$REPO_DIR/benchmarks/tools/genfile" # optional C helper, built by test-all.sh GENFILE="$REPO_DIR/benchmarks/tools/genfile" # optional C helper, built by test-all.sh
+1 -1
View File
@@ -14,7 +14,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
source "$REPO_DIR/benchmarks/std.sh" source "$REPO_DIR/benchmarks/std.sh"
checkfastwc checkfastwc
checkwc coreutils checkwc
BENCH_NAME="coreutils" BENCH_NAME="coreutils"
printf 'benchmarking %s wc vs fastwc: stdin (%s interleaved runs each, minimum kept)\n' \ printf 'benchmarking %s wc vs fastwc: stdin (%s interleaved runs each, minimum kept)\n' \
+8 -7
View File
@@ -17,12 +17,13 @@ case, so every number below survived contact with the contract.
| lines, 1M | 2-3ms | **1ms** | | lines, 1M | 2-3ms | **1ms** |
| lines, 10M | 21-24ms | **8-9ms** | | lines, 10M | 21-24ms | **8-9ms** |
| lines, 100M (monster) | ~140ms | **~70ms** | | lines, 100M (monster) | ~140ms | **~70ms** |
| lines, 1B (solo) | — | **~6s** | | lines, 1B (solo) | — | **~4-6s** |
| bytes, 1GB sparse | reads all of it | `st_size`, no read | | bytes, 1GB sparse | reads all of it | `st_size`, no read |
That is a ~2.5x win over GNU on 10M lines, a 2x win on 1M lines, and That is a ~2.5x win over GNU on 10M lines, a 2x win on 1M lines, and
a 2x win on the 100M monster. At 1B lines — 11 GB of data — the solo a 2x win on the 100M monster. At 1B lines — 11 GB of data — the solo
run lands around 6-8 seconds (125-170 Mlines/s), and the bottleneck is run lands around 4-6 seconds (200-270 Mlines/s, warm cache), and the
bottleneck is
honest to admit: an 11 GB file does not fit in the 15 GB of RAM this honest to admit: an 11 GB file does not fit in the 15 GB of RAM this
machine has, so the last monster is racing the disk. The 100M case, machine has, so the last monster is racing the disk. The 100M case,
which fits, runs at ~17 GB/s, and that number is the counting. which fits, runs at ~17 GB/s, and that number is the counting.
@@ -42,11 +43,11 @@ which fits, runs at ~17 GB/s, and that number is the counting.
the data comes through stdin, but how we read it is our business. the data comes through stdin, but how we read it is our business.
The stdin suite is why this shows up in the scoreboard too. The stdin suite is why this shows up in the scoreboard too.
3. **Parallel across cores.** Files over 8 MiB are split into 64-byte 3. **Parallel across cores.** Files over 8 MiB are split into 64-byte
aligned slices counted by up to 8 threads. The kernels are pure, so aligned slices counted by up to 16 threads (12 past 32 MiB, 16 past
the split needs no locks; word boundaries between slices are seeded 256 MiB). The kernels are pure, so the split needs no locks; word
from the byte before the slice, which makes the split exact. Below boundaries between slices are seeded from the byte before the slice,
8 MiB the thread spawn would cost more than the counting, so we which makes the split exact. Below 8 MiB the thread spawn would cost
don't bother. more than the counting, so we don't bother.
4. **No work that isn't asked for.** `-c` on a regular file is 4. **No work that isn't asked for.** `-c` on a regular file is
`st_size` from `fstat` — GNU figured that one out too, so we copied `st_size` from `fstat` — GNU figured that one out too, so we copied
the good idea. `-l` without `-w` skips the whitespace mask entirely. the good idea. `-l` without `-w` skips the whitespace mask entirely.
+102 -56
View File
@@ -146,6 +146,7 @@ static long long count_words(const unsigned char *s, size_t n, int *prev_ws)
return w; return w;
} }
#if defined(__x86_64__) || defined(__i386__)
/* SSE2 predates POPCNT; count 16-bit masks with the classic bit trick. */ /* SSE2 predates POPCNT; count 16-bit masks with the classic bit trick. */
static unsigned popcount16(unsigned x) static unsigned popcount16(unsigned x)
{ {
@@ -154,6 +155,7 @@ static unsigned popcount16(unsigned x)
x = (x + (x >> 4)) & 0x0f0f; x = (x + (x >> 4)) & 0x0f0f;
return (x + (x >> 8)) & 0xff; return (x + (x >> 8)) & 0xff;
} }
#endif /* x86 */
typedef struct typedef struct
{ {
@@ -162,7 +164,7 @@ typedef struct
} lw_t; } lw_t;
typedef lw_t (*count_lw_fn)(const unsigned char *s, size_t n, int *prev_ws, typedef lw_t (*count_lw_fn)(const unsigned char *s, size_t n, int *prev_ws,
int need_words); int need_lines, int need_words);
/* /*
* Word separators match GNU wc (the benchmark oracle): the six C-locale * Word separators match GNU wc (the benchmark oracle): the six C-locale
@@ -172,7 +174,8 @@ typedef lw_t (*count_lw_fn)(const unsigned char *s, size_t n, int *prev_ws,
#if defined(__x86_64__) || defined(__i386__) #if defined(__x86_64__) || defined(__i386__)
__attribute__((target("avx512f,avx512bw"))) static lw_t __attribute__((target("avx512f,avx512bw"))) static lw_t
count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words) count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
int need_words)
{ {
const __m512i nl = _mm512_set1_epi8('\n'); const __m512i nl = _mm512_set1_epi8('\n');
const __m512i sp = _mm512_set1_epi8(' '); const __m512i sp = _mm512_set1_epi8(' ');
@@ -186,13 +189,18 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i + 64 <= n; i += 64) for (; i + 64 <= n; i += 64)
{ {
__m512i v = _mm512_loadu_si512((const void *)(s + i)); __m512i v = _mm512_loadu_si512((const void *)(s + i));
uint64_t nl_mask = (uint64_t)_mm512_cmpeq_epi8_mask(v, nl); uint64_t nl_mask = 0;
lines += (long long)_mm_popcnt_u64(nl_mask); if (need_lines)
{
nl_mask = (uint64_t)_mm512_cmpeq_epi8_mask(v, nl);
lines += (long long)_mm_popcnt_u64(nl_mask);
}
if (need_words) if (need_words)
{ {
/* min(d, 4) == d <=> (x - 9) < 5 unsigned */ /* min(d, 4) == d <=> (x - 9) < 5 unsigned; the range
* already covers '\n', so no newline compare is needed */
__m512i d = _mm512_sub_epi8(v, lo); __m512i d = _mm512_sub_epi8(v, lo);
uint64_t ws = uint64_t ws =
nl_mask | (uint64_t)_mm512_cmpeq_epi8_mask(v, sp) | nl_mask | (uint64_t)_mm512_cmpeq_epi8_mask(v, sp) |
@@ -206,7 +214,8 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i < n; i++) for (; i < n; i++)
{ {
int ws = ws_tab[s[i]]; int ws = ws_tab[s[i]];
lines += s[i] == '\n'; if (need_lines)
lines += s[i] == '\n';
if (need_words) if (need_words)
{ {
if (prev && !ws) if (prev && !ws)
@@ -221,7 +230,8 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
} }
__attribute__((target("avx2"))) static lw_t __attribute__((target("avx2"))) static lw_t
count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words) count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
int need_words)
{ {
const __m256i nl = _mm256_set1_epi8('\n'); const __m256i nl = _mm256_set1_epi8('\n');
const __m256i sp = _mm256_set1_epi8(' '); const __m256i sp = _mm256_set1_epi8(' ');
@@ -235,14 +245,18 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i + 32 <= n; i += 32) for (; i + 32 <= n; i += 32)
{ {
__m256i v = _mm256_loadu_si256((const void *)(s + i)); __m256i v = _mm256_loadu_si256((const void *)(s + i));
uint32_t nl_mask = uint32_t nl_mask = 0;
(uint32_t)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, nl));
lines += (long long)_mm_popcnt_u32(nl_mask); if (need_lines)
{
nl_mask = (uint32_t)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, nl));
lines += (long long)_mm_popcnt_u32(nl_mask);
}
if (need_words) if (need_words)
{ {
/* min(d, 4) == d <=> (x - 9) < 5 unsigned */ /* min(d, 4) == d <=> (x - 9) < 5 unsigned; the range
* already covers '\n', so no newline compare is needed */
__m256i d = _mm256_sub_epi8(v, lo); __m256i d = _mm256_sub_epi8(v, lo);
uint32_t ws = uint32_t ws =
nl_mask | nl_mask |
@@ -258,7 +272,8 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i < n; i++) for (; i < n; i++)
{ {
int ws = ws_tab[s[i]]; int ws = ws_tab[s[i]];
lines += s[i] == '\n'; if (need_lines)
lines += s[i] == '\n';
if (need_words) if (need_words)
{ {
if (prev && !ws) if (prev && !ws)
@@ -273,7 +288,8 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
} }
__attribute__((target("sse2"))) static lw_t __attribute__((target("sse2"))) static lw_t
count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words) count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
int need_words)
{ {
const __m128i nl = _mm_set1_epi8('\n'); const __m128i nl = _mm_set1_epi8('\n');
const __m128i sp = _mm_set1_epi8(' '); const __m128i sp = _mm_set1_epi8(' ');
@@ -287,9 +303,13 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i + 16 <= n; i += 16) for (; i + 16 <= n; i += 16)
{ {
__m128i v = _mm_loadu_si128((const void *)(s + i)); __m128i v = _mm_loadu_si128((const void *)(s + i));
uint32_t nl_mask = (uint32_t)_mm_movemask_epi8(_mm_cmpeq_epi8(v, nl)); uint32_t nl_mask = 0;
lines += (long long)popcount16(nl_mask); if (need_lines)
{
nl_mask = (uint32_t)_mm_movemask_epi8(_mm_cmpeq_epi8(v, nl));
lines += (long long)popcount16(nl_mask);
}
if (need_words) if (need_words)
{ {
@@ -307,7 +327,8 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
for (; i < n; i++) for (; i < n; i++)
{ {
int ws = ws_tab[s[i]]; int ws = ws_tab[s[i]];
lines += s[i] == '\n'; if (need_lines)
lines += s[i] == '\n';
if (need_words) if (need_words)
{ {
if (prev && !ws) if (prev && !ws)
@@ -325,11 +346,11 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
/* Reference path: the two scalar SWAR counters, kept as the fallback. */ /* Reference path: the two scalar SWAR counters, kept as the fallback. */
static lw_t count_lw_scalar(const unsigned char *s, size_t n, int *prev_ws, static lw_t count_lw_scalar(const unsigned char *s, size_t n, int *prev_ws,
int need_words) int need_lines, int need_words)
{ {
lw_t r; lw_t r;
r.lines = count_newlines(s, n); r.lines = need_lines ? count_newlines(s, n) : 0;
r.words = need_words ? count_words(s, n, prev_ws) : 0; r.words = need_words ? count_words(s, n, prev_ws) : 0;
return r; return r;
} }
@@ -513,7 +534,8 @@ static void count_stream(FILE *fp, counts_t *c)
if (flags & (F_LINES | F_WORDS)) if (flags & (F_LINES | F_WORDS))
{ {
lw_t r = lw_t r =
count_lw(buf, nread, &prev_ws, (flags & F_WORDS) != 0); count_lw(buf, nread, &prev_ws, (flags & F_LINES) != 0,
(flags & F_WORDS) != 0);
if (flags & F_LINES) if (flags & F_LINES)
c->lines += r.lines; c->lines += r.lines;
if (flags & F_WORDS) if (flags & F_WORDS)
@@ -560,7 +582,8 @@ static void count_stream(FILE *fp, counts_t *c)
c->bytes += (long long)nread; c->bytes += (long long)nread;
if (flags & (F_LINES | F_WORDS)) if (flags & (F_LINES | F_WORDS))
{ {
lw_t r = count_lw(buf, nread, &prev_ws, (flags & F_WORDS) != 0); lw_t r = count_lw(buf, nread, &prev_ws, (flags & F_LINES) != 0,
(flags & F_WORDS) != 0);
if (flags & F_LINES) if (flags & F_LINES)
c->lines += r.lines; c->lines += r.lines;
if (flags & F_WORDS) if (flags & F_WORDS)
@@ -577,6 +600,7 @@ typedef struct
const unsigned char *s; const unsigned char *s;
size_t n; size_t n;
int prev_ws; int prev_ws;
int need_lines;
int need_words; int need_words;
lw_t r; lw_t r;
} mjob_t; } mjob_t;
@@ -585,7 +609,7 @@ static void *map_worker(void *arg)
{ {
mjob_t *j = arg; mjob_t *j = arg;
j->r = count_lw(j->s, j->n, &j->prev_ws, j->need_words); j->r = count_lw(j->s, j->n, &j->prev_ws, j->need_lines, j->need_words);
return NULL; return NULL;
} }
@@ -596,14 +620,21 @@ static void *map_worker(void *arg)
* which makes the split exact. The kernels are pure, so no locks. * which makes the split exact. The kernels are pure, so no locks.
*/ */
static void count_sliced(const unsigned char *p, size_t n, int nt, static void count_sliced(const unsigned char *p, size_t n, int nt,
int need_words, long long *lines, long long *words) int need_lines, int need_words, long long *lines,
long long *words)
{ {
mjob_t jobs[8]; enum
pthread_t th[8]; {
MAX_THREADS = 16
};
mjob_t jobs[MAX_THREADS];
pthread_t th[MAX_THREADS];
long long tl = 0, tw = 0; long long tl = 0, tw = 0;
size_t per = (n + (size_t)nt - 1) / (size_t)nt; size_t per = (n + (size_t)nt - 1) / (size_t)nt;
int i; int i;
if (nt > MAX_THREADS)
nt = MAX_THREADS;
per = (per + 63) & ~(size_t)63; per = (per + 63) & ~(size_t)63;
if (per == 0) if (per == 0)
per = 64; per = 64;
@@ -615,11 +646,18 @@ static void count_sliced(const unsigned char *p, size_t n, int nt,
jobs[i].s = p + start; jobs[i].s = p + start;
jobs[i].n = (start + per <= n) ? per : (start < n ? n - start : 0); jobs[i].n = (start + per <= n) ? per : (start < n ? n - start : 0);
jobs[i].prev_ws = (i == 0 || jobs[i].n == 0) ? 1 : ws_tab[p[start - 1]]; jobs[i].prev_ws = (i == 0 || jobs[i].n == 0) ? 1 : ws_tab[p[start - 1]];
jobs[i].need_lines = need_lines;
jobs[i].need_words = need_words; jobs[i].need_words = need_words;
th[i] = 0;
if (jobs[i].n > 0) if (jobs[i].n > 0)
pthread_create(&th[i], NULL, map_worker, &jobs[i]); {
else if (pthread_create(&th[i], NULL, map_worker, &jobs[i]) != 0)
th[i] = 0; { /* out of threads: count this slice inline rather than lose it */
map_worker(&jobs[i]);
tl += jobs[i].r.lines;
tw += jobs[i].r.words;
}
}
} }
for (i = 0; i < nt; i++) for (i = 0; i < nt; i++)
@@ -640,8 +678,10 @@ static int pick_threads(size_t n)
long ncpu = sysconf(_SC_NPROCESSORS_ONLN); long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
int nt; int nt;
if (n >= (size_t)32 << 20) if (n >= (size_t)256 << 20)
nt = 8; nt = 16;
else if (n >= (size_t)32 << 20)
nt = 12;
else if (n >= (size_t)8 << 20) else if (n >= (size_t)8 << 20)
nt = 4; nt = 4;
else else
@@ -653,6 +693,7 @@ static int pick_threads(size_t n)
static void count_mapped(const unsigned char *p, size_t n, counts_t *c) static void count_mapped(const unsigned char *p, size_t n, counts_t *c)
{ {
int need_lines = (flags & F_LINES) != 0;
int need_words = (flags & F_WORDS) != 0; int need_words = (flags & F_WORDS) != 0;
int nt = pick_threads(n); int nt = pick_threads(n);
long long lines = 0, words = 0; long long lines = 0, words = 0;
@@ -662,14 +703,14 @@ static void count_mapped(const unsigned char *p, size_t n, counts_t *c)
if (nt <= 1) if (nt <= 1)
{ {
int prev_ws = 1; int prev_ws = 1;
lw_t r = count_lw(p, n, &prev_ws, need_words); lw_t r = count_lw(p, n, &prev_ws, need_lines, need_words);
lines = r.lines; lines = r.lines;
words = r.words; words = r.words;
} }
else else
{ {
count_sliced(p, n, nt, need_words, &lines, &words); count_sliced(p, n, nt, need_lines, need_words, &lines, &words);
} }
if (flags & F_LINES) if (flags & F_LINES)
@@ -915,7 +956,7 @@ static long long ref_words(const unsigned char *s, size_t n, int *prev_ws)
* the algorithm; this proves the width. * the algorithm; this proves the width.
*/ */
static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n, static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
int *prev_ws, int need_words) int *prev_ws, int need_lines, int need_words)
{ {
long long lines = 0, words = 0; long long lines = 0, words = 0;
size_t i = 0; size_t i = 0;
@@ -931,7 +972,8 @@ static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
if (ws_tab[s[i + j]]) if (ws_tab[s[i + j]])
ws |= (uint64_t)1 << j; ws |= (uint64_t)1 << j;
} }
lines += (long long)__builtin_popcountll(nl_mask); if (need_lines)
lines += (long long)__builtin_popcountll(nl_mask);
if (need_words) if (need_words)
{ {
words += (long long)__builtin_popcountll(~ws & ((ws << 1) | prev)); words += (long long)__builtin_popcountll(~ws & ((ws << 1) | prev));
@@ -942,7 +984,8 @@ static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
for (; i < n; i++) for (; i < n; i++)
{ {
int ws = ws_tab[s[i]]; int ws = ws_tab[s[i]];
lines += s[i] == '\n'; if (need_lines)
lines += s[i] == '\n';
if (need_words) if (need_words)
{ {
if (prev && !ws) if (prev && !ws)
@@ -968,29 +1011,32 @@ static int check_kernel(const char *name, count_lw_fn fn)
buf[k] = (unsigned char)rng32(); buf[k] = (unsigned char)rng32();
for (int pw = 0; pw <= 1; pw++) for (int pw = 0; pw <= 1; pw++)
{ {
for (int nw = 0; nw <= 1; nw++) for (int nl = 0; nl <= 1; nl++)
{ {
int a = pw, b = pw; for (int nw = 0; nw <= 1; nw++)
lw_t got = fn(buf, n, &a, nw);
long long want_l = ref_lines(buf, n);
long long want_w = nw ? ref_words(buf, n, &b) : 0;
if (got.lines != want_l || got.words != want_w ||
a != (nw ? b : pw))
{ {
printf("%s: n=%zu pw=%d nw=%d lines %lld/%lld " int a = pw, b = pw;
"words %lld/%lld state %d/%d\n", lw_t got = fn(buf, n, &a, nl, nw);
name, n, pw, nw, got.lines, want_l, got.words, long long want_l = nl ? ref_lines(buf, n) : 0;
want_w, a, b); long long want_w = nw ? ref_words(buf, n, &b) : 0;
if (n <= 64)
if (got.lines != want_l || got.words != want_w ||
a != (nw ? b : pw))
{ {
for (k = 0; k < n; k++) printf("%s: n=%zu pw=%d nl=%d nw=%d lines %lld/%lld "
printf("%02x", buf[k]); "words %lld/%lld state %d/%d\n",
printf("\n"); name, n, pw, nl, nw, got.lines, want_l,
got.words, want_w, a, b);
if (n <= 64)
{
for (k = 0; k < n; k++)
printf("%02x", buf[k]);
printf("\n");
}
fails++;
if (fails > 5)
return fails;
} }
fails++;
if (fails > 5)
return fails;
} }
} }
} }
@@ -1004,7 +1050,7 @@ static int check_kernel(const char *name, count_lw_fn fn)
for (int pw = 0; pw <= 1; pw++) for (int pw = 0; pw <= 1; pw++)
{ {
int a = pw, b = pw; int a = pw, b = pw;
lw_t got = fn(buf, n, &a, 1); lw_t got = fn(buf, n, &a, 1, 1);
if (got.lines != ref_lines(buf, n) || if (got.lines != ref_lines(buf, n) ||
got.words != ref_words(buf, n, &b) || a != b) got.words != ref_words(buf, n, &b) || a != b)
@@ -1045,7 +1091,7 @@ static int check_sliced(void)
int pw = 1; int pw = 1;
long long want_w = count_words(buf, n, &pw); long long want_w = count_words(buf, n, &pw);
count_sliced(buf, n, nt, 1, &tl, &tw); count_sliced(buf, n, nt, 1, 1, &tl, &tw);
if (tl != want_l || tw != want_w) if (tl != want_l || tw != want_w)
{ {
printf("sliced: n=%zu nt=%d lines %lld/%lld " printf("sliced: n=%zu nt=%d lines %lld/%lld "
@@ -1070,7 +1116,7 @@ static int check_sliced(void)
int pw = 1; int pw = 1;
long long want_w = count_words(buf, n, &pw); long long want_w = count_words(buf, n, &pw);
count_sliced(buf, n, nt, 1, &tl, &tw); count_sliced(buf, n, nt, 1, 1, &tl, &tw);
if (tl != want_l || tw != want_w) if (tl != want_l || tw != want_w)
{ {
printf("sliced-ws: n=%zu nt=%d lines %lld/%lld " printf("sliced-ws: n=%zu nt=%d lines %lld/%lld "