1OVERVIEW 2-------- 3 4This is a harness to test the atomicity of certain operations, and to 5make sure the compiler does not introduce data races in a 6multi-threaded environment. 7 8The basic premise is that we set up testcases such that the thing we 9want test, say an atomic instruction which stores a double word is in 10a function of its own. We then run this testcase within GDB, 11controlled by a gdb script (simulate-thread.gdb). The gdb script will 12break on the function to be tested, and then single step through every 13machine instruction in the function. We set this up so GDB can make a 14couple of inferior function calls before and after each of these 15single step instructions for a couple of purposes: 16 17 1. One of the calls simulates another thread running in the 18 process which changes or access memory. 19 20 2. The other calls are used to verify that we always get the 21 expected behavior. 22 23For example, in the case of an atomic store, anyone looking at the 24memory associated with an atomic variable should never see any in 25between states. If you have an atomic long long int, and it starts 26with the value 0, and you write the value MAX_LONG_LONG, any other 27thread looking at that variable should never see anything other than 0 28or MAX_LONG_LONG. If you implement the atomic write as a sequence of 292 stores, it is possible for another thread to read the location after 30the first store, but before the second one is complete. That thread 31would then see an in-between state (one word would still be 0). 32 33We simulate this in the testcase by having GDB step through the 34program, instruction by instruction, and after each step, making an 35inferior function call which looks at the value of the atomic variable 36and verifies that it sees either 0 or MAX_LONG_LONG. If it sees any 37other value, it fails the testcase. 38 39This way, we are *sure* there is no in between state because we 40effectively acted like an OS and switched to another thread after 41every single instruction of the routine is executed and looked at the 42results each time. 43 44We use the same idea to test for data races to see if an illegal load 45has been hoisted, or that two parallel bitfield writes don't overlap 46in a data race. 47 48Below is a skeleton of how a test should look like. For more details, 49look at the tests themselves. 50 51ANATOMY OF A TEST 52----------------- 53 54/* { dg-do link } */ 55/* { dg-options "-some-flags" } */ 56/* { dg-final { simulate-thread } } */ 57 58/* NOTE: Any failure must be indicated by displaying "FAIL:". */ 59 60#include "simulate-thread.h" 61 62/* Called before each instruction, simulating another thread executing. */ 63void simulate_thread_other_threads() 64{ 65} 66 67/* Called after each instruction. Returns 1 if any inconsistency is 68 found, 0 otherwise. */ 69int simulate_thread_step_verify() 70{ 71 if (some_problem) 72 { 73 printf("FAIL: reason\n"); 74 return 1; 75 } 76 return 0; 77} 78 79/* Called at the end of the program (simulate_thread_fini == 1). Verifies 80 the state of the program and returns 1 if any inconsistency is 81 found, 0 otherwise. */ 82int simulate_thread_final_verify() 83{ 84 if (some_problem) 85 { 86 printf("FAIL: reason\n"); 87 return 1; 88 } 89 return 0; 90} 91 92/* The gdb script will break on simulate_thread_main(), so make sure 93 GCC does not inline it, thus making the break point fail. */ 94__attribute__((noinline)) 95void simulate_thread_main() 96{ 97 /* Do stuff. */ 98} 99 100int main() 101{ 102 103 /* Perform any setup code that will run outside of the testing 104 harness. Put code here that you do NOT want to be interrupted on 105 an instruction basis. E.g., setup code, and system library 106 calls. */ 107 108 /* Do un-instrumented stuff. */ 109 /* ... */ 110 111 /* Start the instrumented show. */ 112 simulate_thread_main(); 113 114 /* Must be called at the end of the test. */ 115 simulate_thread_done(); 116 117 return 0; 118} 119