1 /* $Id: avx_sig.c,v 1.12 2021/12/11 22:47:09 kostik Exp $ */
2 /*
3 * Naive test to check that context switches and signal delivery do
4 * not corrupt AVX registers file (%xmm). Run until some
5 * inconsistency detected, then aborts.
6 *
7 * FreeBSD:
8 * ${CC} -Wall -Wextra -O -g -o avx_sig avx_sig.c -lpthread
9 * Linux
10 * ${CC} -D_GNU_SOURCE -Wall -Wextra -O -g -o avx_sig avx_sig.c -lbsd -lpthread
11 */
12
13 #include <sys/param.h>
14 #include <sys/time.h>
15 #include <sys/resource.h>
16 #include <sys/syscall.h>
17 #include <errno.h>
18 #include <pthread.h>
19 #ifdef __FreeBSD__
20 #include <pthread_np.h>
21 #endif
22 #ifdef __linux__
23 #ifdef __GLIBC__
24 #include <gnu/libc-version.h>
25 #endif
26 #if !defined(__GLIBC__) || (__GLIBC__ * 100 + __GLIBC_MINOR__) < 236
27 #include <bsd/stdlib.h>
28 #endif
29 #endif
30 #include <signal.h>
31 #include <stdatomic.h>
32 #include <stdbool.h>
33 #include <stdint.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 /* SIGALRM interval in seconds. */
40 #ifndef TIMO
41 #define TIMO 5
42 #endif
43
44 #ifndef __unused
45 #define __unused __attribute__((__unused__))
46 #endif
47
48 struct xregs_bank {
49 const char *b_name;
50 const char *r_name;
51 uint32_t regs;
52 uint32_t bytes;
53 void (*x2c)(uint8_t *);
54 void (*c2x)(uint8_t *);
55 };
56
57 int xregs_banks_max(void);
58
59 #if defined(__amd64__)
60 void cpu_to_xmm(uint8_t *);
61 void xmm_to_cpu(uint8_t *);
62 void cpu_to_avx(uint8_t *);
63 void avx_to_cpu(uint8_t *);
64
65 static const struct xregs_bank xregs_banks[] = {
66 {
67 .b_name = "SSE",
68 .r_name = "xmm",
69 .regs = 16,
70 .bytes = 16,
71 .x2c = xmm_to_cpu,
72 .c2x = cpu_to_xmm,
73 },
74 {
75 .b_name = "AVX",
76 .r_name = "ymm",
77 .regs = 16,
78 .bytes = 32,
79 .x2c = avx_to_cpu,
80 .c2x = cpu_to_avx,
81 },
82 };
83 #elif defined(__aarch64__)
84 void cpu_to_vfp(uint8_t *);
85 void vfp_to_cpu(uint8_t *);
86
87 static const struct xregs_bank xregs_banks[] = {
88 {
89 .b_name = "VFP",
90 .r_name = "q",
91 .regs = 32,
92 .bytes = 16,
93 .x2c = vfp_to_cpu,
94 .c2x = cpu_to_vfp,
95 },
96 };
97 #endif
98
99 static atomic_uint sigs;
100 static int max_bank_idx;
101
102
103 static void
sigusr1_handler(int sig __unused,siginfo_t * si __unused,void * m __unused)104 sigusr1_handler(int sig __unused, siginfo_t *si __unused, void *m __unused)
105 {
106 atomic_fetch_add_explicit(&sigs, 1, memory_order_relaxed);
107 }
108
109 static void
sigalrm_handler(int sig __unused)110 sigalrm_handler(int sig __unused)
111 {
112 struct rusage r;
113
114 if (getrusage(RUSAGE_SELF, &r) == 0) {
115 printf("%lu vctx %lu nvctx %lu nsigs %u SIGUSR1\n",
116 r.ru_nvcsw, r.ru_nivcsw, r.ru_nsignals, sigs);
117 }
118 alarm(TIMO);
119 }
120
121
122 static void
fill_xregs(uint8_t * xregs,int bank)123 fill_xregs(uint8_t *xregs, int bank)
124 {
125 arc4random_buf(xregs, xregs_banks[bank].regs * xregs_banks[bank].bytes);
126 }
127
128 static void
dump_xregs(const uint8_t * r,int bank)129 dump_xregs(const uint8_t *r, int bank)
130 {
131 unsigned k;
132
133 for (k = 0; k < xregs_banks[bank].bytes; k++) {
134 if (k != 0)
135 printf(" ");
136 printf("%02x", r[k]);
137 }
138 printf("\n");
139 }
140
141 static pthread_mutex_t show_lock;
142
143 static void
show_diff(const uint8_t * xregs1,const uint8_t * xregs2,int bank)144 show_diff(const uint8_t *xregs1, const uint8_t *xregs2, int bank)
145 {
146 const uint8_t *r1, *r2;
147 unsigned i, j;
148
149 #if defined(__FreeBSD__)
150 printf("thr %d\n", pthread_getthreadid_np());
151 #elif defined(__linux__)
152 printf("thr %ld\n", syscall(SYS_gettid));
153 #endif
154 for (i = 0; i < xregs_banks[bank].regs; i++) {
155 r1 = xregs1 + i * xregs_banks[bank].bytes;
156 r2 = xregs2 + i * xregs_banks[bank].bytes;
157 for (j = 0; j < xregs_banks[bank].bytes; j++) {
158 if (r1[j] != r2[j]) {
159 printf("%%%s%u\n", xregs_banks[bank].r_name, i);
160 dump_xregs(r1, bank);
161 dump_xregs(r2, bank);
162 break;
163 }
164 }
165 }
166 }
167
168 static void
my_pause(void)169 my_pause(void)
170 {
171 usleep(0);
172 }
173
174 static void *
worker_thread(void * arg)175 worker_thread(void *arg)
176 {
177 int bank = (uintptr_t)arg;
178 int sz = xregs_banks[bank].regs * xregs_banks[bank].bytes;
179 uint8_t xregs[sz], xregs_cpu[sz], zero_xregs[sz];
180
181 memset(zero_xregs, 0, sz);
182
183 fill_xregs(xregs, bank);
184 for (;;) {
185 xregs_banks[bank].x2c(xregs);
186 my_pause();
187 xregs_banks[bank].c2x(xregs_cpu);
188 if (memcmp(xregs, xregs_cpu, sz) != 0) {
189 pthread_mutex_lock(&show_lock);
190 show_diff(xregs, xregs_cpu, bank);
191 abort();
192 pthread_mutex_unlock(&show_lock);
193 }
194
195 xregs_banks[bank].x2c(zero_xregs);
196 my_pause();
197 xregs_banks[bank].c2x(xregs_cpu);
198 if (memcmp(zero_xregs, xregs_cpu, sz) != 0) {
199 pthread_mutex_lock(&show_lock);
200 show_diff(zero_xregs, xregs_cpu, bank);
201 abort();
202 pthread_mutex_unlock(&show_lock);
203 }
204 }
205 return (NULL);
206 }
207
208 int
main(void)209 main(void)
210 {
211 struct sigaction sa;
212 int error, i, ncpu, bank;
213
214 max_bank_idx = xregs_banks_max();
215
216 bzero(&sa, sizeof(sa));
217 sa.sa_handler = sigalrm_handler;
218 if (sigaction(SIGALRM, &sa, NULL) == -1) {
219 fprintf(stderr, "sigaction SIGALRM %s\n", strerror(errno));
220 exit(1);
221 }
222
223 bzero(&sa, sizeof(sa));
224 sa.sa_sigaction = sigusr1_handler;
225 sa.sa_flags = SA_SIGINFO;
226 if (sigaction(SIGUSR1, &sa, NULL) == -1) {
227 fprintf(stderr, "sigaction SIGUSR1 %s\n", strerror(errno));
228 exit(1);
229 }
230
231 error = pthread_mutex_init(&show_lock, NULL);
232 if (error != 0) {
233 fprintf(stderr, "pthread_mutex_init %s\n", strerror(error));
234 exit(1);
235 }
236
237 ncpu = sysconf(_SC_NPROCESSORS_ONLN);
238 if (max_bank_idx == 0)
239 ncpu *= 2;
240 bank = 0;
241 pthread_t wt[ncpu];
242 nextbank:
243 printf("Starting %d threads for registers bank %s sized [%d][%d]\n", ncpu,
244 xregs_banks[bank].b_name, xregs_banks[bank].regs, xregs_banks[bank].bytes);
245 for (i = 0; i < ncpu; i++) {
246 error = pthread_create(&wt[i], NULL, worker_thread,
247 (void *)(uintptr_t)bank);
248 if (error != 0) {
249 fprintf(stderr, "pthread_create %s\n", strerror(error));
250 }
251 }
252 if (++bank <= max_bank_idx)
253 goto nextbank;
254
255 alarm(TIMO);
256 for (;;) {
257 for (i = 0; i < ncpu; i++) {
258 my_pause();
259 pthread_kill(wt[i], SIGUSR1);
260 }
261 }
262 }
263