| // This program is a thorough test of the LOADVn/STOREVn shadow memory |
| // operations. |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include "memcheck/memcheck.h" |
| |
| // All the sizes here are in *bytes*, not bits. |
| |
| typedef unsigned char U1; |
| typedef unsigned short U2; |
| typedef unsigned int U4; |
| typedef unsigned long long U8; |
| |
| typedef float F4; |
| typedef double F8; |
| |
| #define SZB_OF_a 64 |
| |
| // a[] is the array in which we do our loads and stores. |
| // b[] is another one in which we do some copying. |
| U8 a [SZB_OF_a / 8]; // Type is U8 to ensure it's 8-aligned |
| U8 b [SZB_OF_a / 8]; // same size as a[] |
| |
| // XXX: should check the error cases for SET/GET_VBITS also |
| |
| // For the byte 'x', build a value of 'size' bytes from that byte, eg: |
| // size 1 --> x |
| // size 2 --> xx |
| // size 4 --> xxxx |
| // size 8 --> xxxxxxxx |
| // where the 0 bits are seen by Memcheck as defined, and the 1 bits are |
| // seen as undefined (ie. the value of each bit matches its V bit, ie. the |
| // resulting value is the same as its metavalue). |
| // |
| U8 build(int size, U1 byte) |
| { |
| int i; |
| U8 mask = 0; |
| U8 shres; |
| U8 res = 0xffffffffffffffffULL, res2; |
| VALGRIND_MAKE_MEM_UNDEFINED(&res, 8); |
| assert(1 == size || 2 == size || 4 == size || 8 == size); |
| |
| for (i = 0; i < size; i++) { |
| mask <<= 8; |
| mask |= (U8)byte; |
| } |
| |
| res &= mask; |
| |
| // res is now considered partially defined, but we know exactly what its |
| // value is (it happens to be the same as its metavalue). |
| |
| (void)VALGRIND_GET_VBITS(&res, &shres, 8); |
| res2 = res; |
| (void)VALGRIND_MAKE_MEM_DEFINED(&res2, 8); // avoid the 'undefined' warning |
| assert(res2 == shres); |
| return res; |
| } |
| |
| // Check that all the bytes in a[x..y-1] have their V byte equal |
| // to either 'expected_byte' or 'expected_byte_alt'. |
| // 'str' and 'offset' are only used for printing an error message if |
| // something goes wrong. |
| void check_all(U4 x, U4 y, U1 expected_byte, U1 expected_byte_alt, |
| char* str, int offset) |
| { |
| U1 sh[SZB_OF_a]; // Used for getting a[]'s V bits |
| int i; |
| |
| (void)VALGRIND_GET_VBITS(a, sh, sizeof(a)); |
| for (i = x; i < y; i++) { |
| if ( expected_byte != sh[i] && expected_byte_alt != sh[i] ) { |
| fprintf(stderr, "\n\nFAILURE: %s, offset %d, byte %d -- " |
| "is 0x%x, should be 0x%x or 0x%x\n\n", |
| str, offset, i, sh[i], expected_byte, |
| expected_byte_alt); |
| exit(1); |
| } |
| } |
| } |
| |
| int main(void) |
| { |
| int h, i, j; |
| U1 *undefA, expected_byte, expected_byte_alt; |
| |
| if (0 == RUNNING_ON_VALGRIND) { |
| fprintf(stderr, "error: this program only works when run under Valgrind\n"); |
| exit(1); |
| } |
| |
| // Check a[] has the expected alignment, and that it's not too high in |
| // the address space (which would trigger the slow cases in |
| // LOADVn/STOREVn) on 64-bit platforms). |
| assert( 0 == (long)a % 8); |
| if (sizeof(void*) == 8) { |
| assert( ((U1*)(&a[0])) < ((U1*)(32ULL * 1024*1024*1024)/*32G*/) ); |
| } |
| |
| // Check basic types have the expected sizes. |
| assert(1 == sizeof(U1)); |
| assert(2 == sizeof(U2)); |
| assert(4 == sizeof(U4)); |
| assert(8 == sizeof(U8)); |
| |
| // Create an array of values that has all the possible V bit metavalues. |
| // Because 0 represents a defined bit, and because undefA[] is initially |
| // zeroed, we have the nice property that: |
| // |
| // i == undefA[i] == V_bits_of(undefA[i]) |
| // |
| // which is useful for testing below. |
| undefA = calloc(1, 256); // one for each possible undefinedness value |
| VALGRIND_MAKE_MEM_UNDEFINED(undefA, 256); |
| for (i = 0; i < 256; i++) { |
| undefA[i] &= i; |
| } |
| |
| // This code does a whole lot of reads and writes of a particular size |
| // (NNN = 1, 2, 4 or 8), with varying alignments, of values with |
| // different not/partially/fully defined metavalues, and checks that the |
| // V bits are set in a[] as expected using GET_VBITS. |
| // |
| // 'Ty' is the type of the thing we are copying. It can be an integer |
| // type or an FP type. 'ITy' is the same-sized integer type (and thus |
| // will be the same as 'Ty' if 'ITy' is an integer type). 'ITy' is used |
| // when doing shifting/masking and stuff like that. |
| |
| #define DO(NNN, Ty, ITy, isF4) \ |
| fprintf(stderr, "-- NNN: %d %s %s ------------------------\n", NNN, #Ty, #ITy); \ |
| /* For all of the alignments from (0..NNN-1), eg. if NNN==4, we do */ \ |
| /* alignments of 0, 1, 2, 3. */ \ |
| for (h = 0; h < NNN; h++) { \ |
| \ |
| size_t n = sizeof(a); \ |
| size_t nN = n / sizeof(Ty); \ |
| Ty* aN = (Ty*)a; \ |
| Ty* bN = (Ty*)b; \ |
| Ty* aNb = (Ty*)(((U1*)aN) + h); /* set offset from a[] */ \ |
| Ty* bNb = (Ty*)(((U1*)bN) + h); /* set offset from b[] */ \ |
| \ |
| fprintf(stderr, "h = %d (checking %d..%d) ", h, h, (int)(n-NNN+h)); \ |
| \ |
| /* For each of the 256 possible V byte values... */ \ |
| for (j = 0; j < 256; j++) { \ |
| /* build the value for i (one of: i, ii, iiii, iiiiiiii) */ \ |
| U8 tmp = build(NNN, j); \ |
| ITy undefN_ITy = (ITy)tmp; \ |
| Ty* undefN_Ty; \ |
| { /* This just checks that no overflow occurred when squeezing */ \ |
| /* the output of build() into a variable of type 'Ty'. */ \ |
| U8 tmpDef = tmp; \ |
| ITy undefN_ITyDef = undefN_ITy; \ |
| VALGRIND_MAKE_MEM_DEFINED(&tmpDef, 8 ); \ |
| VALGRIND_MAKE_MEM_DEFINED(&undefN_ITyDef, NNN); \ |
| assert(tmpDef == (U8)undefN_ITyDef); \ |
| } \ |
| \ |
| /* We have to use an array for undefN_Ty -- because if we try to |
| * convert an integer type from build into an FP type with a |
| * straight cast -- eg "float f = (float)i" -- the value gets |
| * converted. With this pointer/array nonsense the exact bit |
| * pattern gets used as an FP value unchanged (that FP value is |
| * undoubtedly nonsense, but that's not a problem here). */ \ |
| undefN_Ty = (Ty*)&undefN_ITy; \ |
| if (0 == j % 32) fprintf(stderr, "%d...", j); /* progress meter */ \ |
| \ |
| \ |
| /* A nasty exception: most machines so far (x86/PPC32/PPC64) |
| * don't have 32-bit floats. So 32-bit floats get cast to 64-bit |
| * floats. Memcheck does a PCast in this case, which means that if |
| * any V bits for the 32-bit float are undefined (ie. 0 != j), all |
| * the V bits in the 64-bit float are undefined. So account for |
| * this when checking. AMD64 typically does FP arithmetic on |
| * SSE, effectively giving it access to 32-bit FP registers. So |
| * in short, for floats, we have to allow either 'j' or 0xFF |
| * as an acceptable result. Sigh. */ \ |
| if (isF4) { \ |
| expected_byte = j; \ |
| expected_byte_alt = 0 != j ? 0xFF : j; \ |
| } else { \ |
| expected_byte = j; \ |
| expected_byte_alt = j; \ |
| } \ |
| \ |
| /* STOREVn. Note that we use the first element of the undefN_Ty |
| * array, as explained above. */ \ |
| for (i = 0; i < nN-1; i++) { aNb[i] = undefN_Ty[0]; } \ |
| check_all(h, n-NNN+h, expected_byte, expected_byte_alt, "STOREVn", h); \ |
| \ |
| /* LOADVn -- by copying the values to one place and then back, |
| * we ensure that LOADVn gets exercised. */ \ |
| for (i = 0; i < nN-1; i++) { bNb[i] = aNb[i]; } \ |
| for (i = 0; i < nN-1; i++) { aNb[i] = bNb[i]; } \ |
| check_all(h, n-NNN+h, expected_byte, expected_byte_alt, "LOADVn", h); \ |
| } \ |
| fprintf(stderr, "\n"); \ |
| } |
| |
| // For sizes 4 and 8 we do both integer and floating-point types. The |
| // reason being that on 32-bit machines just using integer types never |
| // exercises LOADV8/STOREV8 -- for integer types these loads/stores get |
| // broken into two 32-bit loads/stores. |
| DO(1, U1, U1, /*isF4*/0); |
| DO(2, U2, U2, /*isF4*/0); |
| DO(4, U4, U4, /*isF4*/0); |
| DO(4, F4, U4, /*isF4*/1); |
| DO(8, U8, U8, /*isF4*/0); |
| DO(8, F8, U8, /*isF4*/0); |
| |
| return 0; |
| } |