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 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 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 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 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 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 169 my_pause(void) 170 { 171 usleep(0); 172 } 173 174 static void * 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 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