blob: 412e4fe7afd54673524d1645eee5f31b588bffbf [file] [log] [blame]
/*
* Copyright (c) 2013 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* This test ensures that the PNaCl backends can deal with compiler
* intrinsics as would be written by regular users, including PNaCl's
* ABI stabilization and target lowering.
*
* See other synchronization_* tests in this directory.
*
* This is a syntactical check as we do not run this multithreaded. Some
* real testing is done here: tests/threads/thread_test.c
*
* PNaCl's atomic support is based on C11/C++11's, and also supports
* GCC's legacy __sync_* intrinsics which internally map to the same IR
* as C11/C++11's atomics. PNaCl only supports 8, 16, 32 and 64-bit
* atomics, and should be able to handle any type that can map to these
* (e.g. 32- and 64-bit atomic floating point load/store).
*
* GCC Legacy __sync Built-in Functions for Atomic Memory Access.
* See:
* http://gcc.gnu.org/onlinedocs/gcc-4.8.1/gcc/_005f_005fsync-Builtins.html
*
* Note: nand is ignored because it isn't in C11/C++11.
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B) CONCAT_(A, B)
#define STR_(A) #A
#define STR(A) STR_(A)
#define CHECK_EQ(LHS, RHS, MSG) do { \
printf("\t" MSG ":\t" STR(LHS) "=%" PRIu64 \
" and " STR(RHS) "=%" PRIu64 "\n", \
(uint64_t)(LHS), (uint64_t)(RHS)); \
if ((LHS) != (RHS)) { \
fprintf(stderr, "ERROR: " MSG ": `" \
STR(LHS) " != " STR(RHS) "` " \
"\n"); \
exit(1); \
} \
} while (0)
/*
* Test that using some synchronization primitives without using the
* return value works. Do this by modifying volatile globals.
*/
volatile uint8_t g_uint8_t = 0;
volatile uint16_t g_uint16_t = 0;
volatile uint32_t g_uint32_t = 0;
volatile uint64_t g_uint64_t = 0;
#define SINK_ADDR(TYPE) (&CONCAT(g_, TYPE))
/*
* Test that base+displacement as synchronization address also works,
* with small and big displacements (which should affect unrolling).
*/
/* Small displacement. */
enum { gsmall_ArrSize = 8 };
volatile struct GS8 { void *ptr; uint8_t arr[gsmall_ArrSize]; }
gsmall_uint8_t = { 0, { 0 } };
volatile struct GS16 { void *ptr; uint16_t arr[gsmall_ArrSize]; }
gsmall_uint16_t = { 0, { 0 } };
volatile struct GS32 { void *ptr; uint32_t arr[gsmall_ArrSize]; }
gsmall_uint32_t = { 0, { 0 } };
volatile struct GS64 { void *ptr; uint64_t arr[gsmall_ArrSize]; }
gsmall_uint64_t = { 0, { 0 } };
#define SMALL_SINK_ADDR(TYPE) ( \
CONCAT(gsmall_, TYPE).ptr = (void*) CONCAT(gsmall_, TYPE).arr, \
(TYPE*) CONCAT(gsmall_, TYPE).ptr)
/* Big displacement. */
enum { gbig_ArrSize = 128 };
volatile struct GB8 { void *ptr; uint8_t arr[gbig_ArrSize]; }
gbig_uint8_t = { 0, { 0 } };
volatile struct GB16 { void *ptr; uint16_t arr[gbig_ArrSize]; }
gbig_uint16_t = { 0, { 0 } };
volatile struct GB32 { void *ptr; uint32_t arr[gbig_ArrSize]; }
gbig_uint32_t = { 0, { 0 } };
volatile struct GB64 { void *ptr; uint64_t arr[gbig_ArrSize]; }
gbig_uint64_t = { 0, { 0 } };
#define BIG_SINK_ADDR(TYPE) ( \
CONCAT(gbig_, TYPE).ptr = (void*) CONCAT(gbig_, TYPE).arr, \
(TYPE*) CONCAT(gbig_, TYPE).ptr)
/*
* fetch_and_*
*
* These built-in functions perform the operation suggested by the name,
* and returns the value that had previously been in memory:
* { tmp = *ptr; *ptr op= value; return tmp; }
*
* *_and_fetch
*
* These built-in functions perform the operation suggested by the name,
* and return the new value:
* { *ptr op= value; return *ptr; }
*/
void test_fetch(void) {
# define FETCH_TEST(OPERATION_NAME, OPERATION, TYPE) do { \
TYPE res, loc, value, res_, loc_, value_; \
printf("Testing " STR(OPERATION_NAME) " with " STR(TYPE) "\n"); \
/* Test __sync_fetch_and_* */ \
loc = loc_ = 5; \
value = value_ = 41; \
res = CONCAT(__sync_fetch_and_, OPERATION_NAME)(&loc, value); \
res_ = loc_; loc_ = loc_ OPERATION value_; \
CHECK_EQ(res, res_, "__sync_fetch_and_" STR(OPERATION_NAME)); \
/* Test __sync_*_and_fetch */ \
loc = loc_ = 5; \
value = value_ = 41; \
res = CONCAT(CONCAT(__sync_, OPERATION_NAME), _and_fetch)( \
&loc, value); \
loc_ = loc_ OPERATION value_; res_ = loc_; \
CHECK_EQ(res, res_, "__sync_" STR(OPERATION_NAME) "_and_fetch"); \
/* Test the above two variants, without reading the result back. */ \
{ /* In just one variable. */ \
volatile TYPE *sink = SINK_ADDR(TYPE); \
(void) CONCAT(__sync_fetch_and_, OPERATION_NAME)(sink, value); \
(void) CONCAT(CONCAT(__sync_, OPERATION_NAME), _and_fetch)( \
sink, value); \
} \
{ /* In a small array. */ \
volatile TYPE *small_sink = SMALL_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gsmall_ArrSize; ++i) { \
(void) CONCAT(__sync_fetch_and_, OPERATION_NAME)( \
&small_sink[i], value); \
(void) CONCAT(CONCAT(__sync_, OPERATION_NAME), _and_fetch)( \
&small_sink[i], value); \
} \
} \
{ /* In a big array. */ \
volatile TYPE *big_sink = BIG_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gbig_ArrSize; ++i) { \
(void) CONCAT(__sync_fetch_and_, OPERATION_NAME)( \
&big_sink[i], value); \
(void) CONCAT(CONCAT(__sync_, OPERATION_NAME), _and_fetch)( \
&big_sink[i], value); \
} \
} \
} while (0)
# define FETCH_TEST_ALL_TYPES(OPERATION_NAME, OPERATION) do { \
FETCH_TEST(OPERATION_NAME, OPERATION, uint8_t); \
FETCH_TEST(OPERATION_NAME, OPERATION, uint16_t); \
FETCH_TEST(OPERATION_NAME, OPERATION, uint32_t); \
FETCH_TEST(OPERATION_NAME, OPERATION, uint64_t); \
} while (0)
FETCH_TEST_ALL_TYPES(add, +);
FETCH_TEST_ALL_TYPES(sub, -);
FETCH_TEST_ALL_TYPES(or, |);
FETCH_TEST_ALL_TYPES(and, &);
FETCH_TEST_ALL_TYPES(xor, ^);
}
/*
* *_compare_and_swap
*
* These built-in functions perform an atomic compare and swap. That is,
* if the current value of *ptr is oldval, then write newval into
* *ptr. The “bool” version returns true if the comparison is successful
* and newval is written. The "val" version returns the contents of *ptr
* before the operation.
*
* Note: LLVM also has __sync_swap, which is ignored because it isn't
* C11/C++11 and isn't in GCC.
*/
void test_cas(void) {
# define CAS_TEST(TYPE) do { \
TYPE res, loc, oldval, newval; \
printf("Testing CAS with " STR(TYPE) "\n"); \
/* Test __sync_bool_compare_and_swap */ \
loc = 5; \
oldval = 29; \
newval = 41; \
res = __sync_bool_compare_and_swap(&loc, oldval, newval); \
CHECK_EQ(res, 0, "__sync_bool_compare_and_swap"); \
CHECK_EQ(loc, 5, "__sync_bool_compare_and_swap"); \
loc = oldval; \
res = __sync_bool_compare_and_swap(&loc, oldval, newval); \
CHECK_EQ(res, 1, "__sync_bool_compare_and_swap"); \
CHECK_EQ(loc, 41, "__sync_bool_compare_and_swap"); \
/* Test __sync_val_compare_and_swap */ \
loc = 5; \
oldval = 29; \
newval = 41; \
res = __sync_val_compare_and_swap(&loc, oldval, newval); \
CHECK_EQ(res, 5, "__sync_val_compare_and_swap"); \
CHECK_EQ(loc, 5, "__sync_val_compare_and_swap"); \
loc = oldval; \
res = __sync_val_compare_and_swap(&loc, oldval, newval); \
CHECK_EQ(res, oldval, "__sync_val_compare_and_swap"); \
CHECK_EQ(loc, 41, "__sync_val_compare_and_swap"); \
/* Test the above two variants, without reading the result back. */ \
{ /* In just one variable. */ \
volatile TYPE *sink = SINK_ADDR(TYPE); \
(void) __sync_bool_compare_and_swap(sink, oldval, newval); \
(void) __sync_val_compare_and_swap(sink, oldval, newval); \
} \
{ /* In a small array. */ \
volatile TYPE *small_sink = SMALL_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gsmall_ArrSize; ++i) { \
(void) __sync_bool_compare_and_swap( \
&small_sink[i], oldval, newval); \
(void) __sync_val_compare_and_swap( \
&small_sink[i], oldval, newval); \
} \
} \
{ /* In a big array. */ \
volatile TYPE *big_sink = BIG_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gbig_ArrSize; ++i) { \
(void) __sync_bool_compare_and_swap( \
&big_sink[i], oldval, newval); \
(void) __sync_val_compare_and_swap( \
&big_sink[i], oldval, newval); \
} \
} \
} while (0)
CAS_TEST(uint8_t);
CAS_TEST(uint16_t);
CAS_TEST(uint32_t);
CAS_TEST(uint64_t);
}
/*
* synchronize
*
* This built-in function issues a full memory barrier. Simply test that
* it compiles fine.
*/
void test_synchronize(void) {
__sync_synchronize();
}
/*
* lock_test_and_set
*
* This built-in function, as described by Intel, is not a traditional
* test-and-set operation, but rather an atomic exchange operation. It
* writes value into *ptr, and returns the previous contents of *ptr.
* Many targets have only minimal support for such locks, and do not
* support a full exchange operation. In this case, a target may support
* reduced functionality here by which the only valid value to store is
* the immediate constant 1. The exact value actually stored in *ptr is
* implementation defined.
*
* This built-in function is not a full barrier, but rather an acquire
* barrier. This means that references after the operation cannot move
* to (or be speculated to) before the operation, but previous memory
* stores may not be globally visible yet, and previous memory loads may
* not yet be satisfied.
*
* lock_release
*
* This built-in function releases the lock acquired by
* __sync_lock_test_and_set. Normally this means writing the constant 0
* to *ptr.
*
* This built-in function is not a full barrier, but rather a release
* barrier. This means that all previous memory stores are globally
* visible, and all previous memory loads have been satisfied, but
* following memory reads are not prevented from being speculated to
* before the barrier.
*/
void test_tas(void) {
# define TAS_TEST(TYPE) do { \
TYPE res, loc, value; \
printf("Testing TAS with " STR(TYPE) "\n"); \
/* Test __sync_lock_test_and_set. */ \
loc = 5; \
value = 41; \
res = __sync_lock_test_and_set(&loc, value); \
CHECK_EQ(res, 5, "__sync_lock_test_and_set"); \
CHECK_EQ(loc, 41, "__sync_lock_test_and_set"); \
/* Test __sync_lock_release. */ \
__sync_lock_release(&loc); \
CHECK_EQ(loc, 0, "__sync_lock_release"); \
{ /* In just one variable. */ \
volatile TYPE *sink = SINK_ADDR(TYPE); \
(void) __sync_lock_test_and_set(sink, value); \
__sync_lock_release(sink); \
} \
/* Test the above two variants, without reading the result back. */ \
{ /* In a small array. */ \
volatile TYPE *small_sink = SMALL_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gsmall_ArrSize; ++i) { \
(void) __sync_lock_test_and_set(&small_sink[i], value); \
__sync_lock_release(&small_sink[i]); \
} \
} \
{ /* In a big array. */ \
volatile TYPE *big_sink = BIG_SINK_ADDR(TYPE); \
size_t i; \
for (i = 0; i != gbig_ArrSize; ++i) { \
(void) __sync_lock_test_and_set(&big_sink[i], value); \
__sync_lock_release(&big_sink[i]); \
} \
} \
} while (0)
TAS_TEST(uint8_t);
TAS_TEST(uint16_t);
TAS_TEST(uint32_t);
TAS_TEST(uint64_t);
}
int main(void) {
test_fetch();
test_cas();
test_synchronize();
test_tas();
return 0;
}