Index: compiler-rt/lib/builtins/CMakeLists.txt =================================================================== --- compiler-rt/lib/builtins/CMakeLists.txt +++ compiler-rt/lib/builtins/CMakeLists.txt @@ -41,6 +41,7 @@ divti3.c divtf3.c divxc3.c + emutls.c enable_execute_stack.c eprintf.c extendsfdf2.c Index: compiler-rt/lib/builtins/README.txt =================================================================== --- compiler-rt/lib/builtins/README.txt +++ compiler-rt/lib/builtins/README.txt @@ -220,9 +220,11 @@ // for use with some implementations of assert() in void __eprintf(const char* format, const char* assertion_expression, const char* line, const char* file); - +// for systems with emulated thread local storage +void* __emutls_get_address(struct __emutls_control*); + // Power PC specific functions // There is no C interface to the saveFP/restFP functions. They are helper Index: compiler-rt/lib/builtins/emutls.c =================================================================== --- compiler-rt/lib/builtins/emutls.c +++ compiler-rt/lib/builtins/emutls.c @@ -0,0 +1,182 @@ +/* ===---------- emutls.c - Implements __emutls_get_address ---------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + */ +#include +#include +#include +#include + +#include "int_util.h" + +/* Default is not to use posix_memalign, so systems like Android + * can use thread local data without heavier POSIX memory allocators. + */ +#ifndef EMUTLS_USE_POSIX_MEMALIGN +#define EMUTLS_USE_POSIX_MEMALIGN 0 +#endif + +/* For every TLS variable xyz, + * there is one __emutls_control variable named __emutls_v.xyz. + * If xyz has non-zero initial value, __emutls_v.xyz's "value" + * will point to __emutls_t.xyz, which has the initial value. + */ +typedef struct __emutls_control { + size_t size; /* size of the object in bytes */ + size_t align; /* alignment of the object in bytes */ + union { + uintptr_t index; /* data[index-1] is the object address */ + void* address; /* object address, when in single thread env */ + } object; + void* value; /* null or non-zero initial value for the object */ +} __emutls_control; + +static inline void* emutls_memalign_alloc(size_t align, size_t size) { + void *base; +#if EMUTLS_USE_POSIX_MEMALIGN + if (posix_memalign(&base, align, size) != 0) + abort(); +#else + /* Allocate (align - 1 + sizeof(void*) + size) bytes. */ + char* object; + if ((object = malloc(align - 1 + sizeof(void*) + size)) == NULL) + abort(); + base = (void*)(((uintptr_t)(object + sizeof(void*) + align - 1)) + & ~(uintptr_t)(align - 1)); + + ((void**)base)[-1] = object; +#endif + return base; +} + +static inline void emutls_memalign_free(void* base) { +#if EMUTLS_USE_POSIX_MEMALIGN + free(base); +#else + /* The mallocated address is in ((void**)base)[-1] */ + free(((void**)base)[-1]); +#endif +} + +/* Emulated TLS objects are always allocated at run-time. */ +static inline void* emutls_allocate_object(__emutls_control* control) { + /* Use standard C types, check with gcc's emutls.o. */ + typedef unsigned int gcc_word __attribute__((mode(word))); + typedef unsigned int gcc_pointer __attribute__((mode(pointer))); + COMPILE_TIME_ASSERT(sizeof(size_t) == sizeof(gcc_word)); + COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(gcc_pointer)); + COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(void*)); + + size_t size = control->size; + size_t align = control->align; + if (align < sizeof(void*)) + align = sizeof(void*); + /* Make sure that align is power of 2. */ + if ((((align - 1) | align) + 1) != (align << 1)) + abort(); + + void* base = emutls_memalign_alloc(align, size); + if (control->value) + memcpy(base, control->value, size); + else + memset(base, 0, size); + return base; +} + +static pthread_mutex_t emutls_mutex = PTHREAD_MUTEX_INITIALIZER; + +static size_t emutls_num_object = 0; /* number of allocated TLS objects */ + +typedef struct emutls_address_array { + uintptr_t size; /* number of elements in the 'data' array */ + void* data[]; +} emutls_address_array; + +static pthread_key_t emutls_pthread_key; + +static void emutls_key_destructor(void* ptr) { + emutls_address_array* array = (emutls_address_array*)ptr; + uintptr_t i; + for (i = 0; i < array->size; ++i) { + if (array->data[i]) { + emutls_memalign_free(array->data[i]); + } + } + free(ptr); +} + +static void emutls_init(void) { + if (pthread_key_create(&emutls_pthread_key, emutls_key_destructor) != 0) + abort(); +} + +/* Returns control->object.index; set index if not allocated yet. */ +static inline uintptr_t emutls_get_index(__emutls_control* control) { + uintptr_t index = __atomic_load_n(&control->object.index, __ATOMIC_ACQUIRE); + if (!index) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, emutls_init); + pthread_mutex_lock(&emutls_mutex); + index = control->object.index; + if (!index) { + index = ++emutls_num_object; + __atomic_store_n(&control->object.index, index, __ATOMIC_RELEASE); + } + pthread_mutex_unlock(&emutls_mutex); + } + return index; +} + +/* Updates newly allocated thread local emutls_address_array. */ +static inline void emutls_check_array_set_size(emutls_address_array* array, + uintptr_t size) { + if (array == NULL) + abort(); + array->size = size; + pthread_setspecific(emutls_pthread_key, (void*)array); +} + +/* Returns the new 'data' array size, number of elements, + * which must be no smaller than the given index. + */ +static inline uintptr_t emutls_new_data_array_size(uintptr_t index) { + /* Need to allocate emutls_address_array with one extra slot + * to store the data array size. + * Round up the emutls_address_array size to multiple of 16. + */ + return ((index + 1 + 15) & ~((uintptr_t)15)) - 1; +} + +/* Returns the thread local emutls_address_array. + * Extends its size if necessary to hold address at index. + */ +static inline emutls_address_array* emutls_get_address_array(uintptr_t index) { + emutls_address_array* array = pthread_getspecific(emutls_pthread_key); + if (array == NULL) { + uintptr_t new_size = emutls_new_data_array_size(index); + array = calloc(new_size + 1, sizeof(void*)); + emutls_check_array_set_size(array, new_size); + } else if (index > array->size) { + uintptr_t orig_size = array->size; + uintptr_t new_size = emutls_new_data_array_size(index); + array = realloc(array, (new_size + 1) * sizeof(void*)); + if (array) + memset(array->data + orig_size, 0, + (new_size - orig_size) * sizeof(void*)); + emutls_check_array_set_size(array, new_size); + } + return array; +} + +void* __emutls_get_address(__emutls_control* control) { + uintptr_t index = emutls_get_index(control); + emutls_address_array* array = emutls_get_address_array(index); + if (array->data[index - 1] == NULL) + array->data[index - 1] = emutls_allocate_object(control); + return array->data[index - 1]; +} Index: compiler-rt/lib/builtins/int_util.h =================================================================== --- compiler-rt/lib/builtins/int_util.h +++ compiler-rt/lib/builtins/int_util.h @@ -26,4 +26,9 @@ void compilerrt_abort_impl(const char *file, int line, const char *function) __attribute__((noreturn)); +#define COMPILE_TIME_ASSERT(expr) COMPILE_TIME_ASSERT1(expr, __COUNTER__) +#define COMPILE_TIME_ASSERT1(expr, cnt) COMPILE_TIME_ASSERT2(expr, cnt) +#define COMPILE_TIME_ASSERT2(expr, cnt) \ + typedef char ct_assert_##cnt[(expr) ? 1 : -1] __attribute__((unused)) + #endif /* INT_UTIL_H */ Index: compiler-rt/test/builtins/Unit/emutls_test.c =================================================================== --- compiler-rt/test/builtins/Unit/emutls_test.c +++ compiler-rt/test/builtins/Unit/emutls_test.c @@ -0,0 +1,142 @@ +//===-- emutls_test.c - Test __emutls_get_address ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file tests __emutls_get_address for the compiler_rt library. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include + +typedef unsigned int gcc_word __attribute__((mode(word))); +typedef unsigned int gcc_pointer __attribute__((mode(pointer))); + +struct gcc_emutls_object { // for libgcc + gcc_word size; + gcc_word align; + union { + gcc_pointer offset; + void *ptr; + } loc; + void *templ; +}; + +typedef struct __emutls_control { // for clang/llvm + size_t size; + size_t align; + union { + uintptr_t index; + void* address; + } object; + void* value; +} __emutls_control; + +void* __emutls_get_address(__emutls_control*); + +#define COMPILE_TIME_ASSERT(pred) { \ + char dummy[(pred) ? 0 : -1] __attribute__((unused)); \ +} + +#define ASSERT_TRUE(v) { \ + if (!(v)) { \ + printf("%s:%d: error in emutls expect true: (%s)\n", \ + __FILE__, __LINE__, #v); \ + return 1; \ + } \ +} + +#define ASSERT_EQ(v1, v2) { \ + if ((v1) != (v2)) { \ + printf("%s:%d: error in emutls test: (%s) != (%s)\n", \ + __FILE__, __LINE__, #v1, #v2); \ + return 1; \ + } \ +} + +#define ASSERT_STREQ(v1, v2) { \ + if (strcmp((v1), (v2))) { \ + printf("%s:%d: error in emutls test: " \ + "string %s (\"%s\") != %s (\"%s\")\n", \ + __FILE__, __LINE__, #v1, (v1), #v2, (v2)); \ + return 1; \ + } \ +} + +int test__type_size () +{ + COMPILE_TIME_ASSERT(sizeof(size_t) == sizeof(gcc_word)); + COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(gcc_pointer)); + COMPILE_TIME_ASSERT(sizeof(uintptr_t) == sizeof(void*)); + COMPILE_TIME_ASSERT( + sizeof(__emutls_control) == sizeof(struct gcc_emutls_object)); + return 0; +} + +int test__init_align() +{ + const char* tls_value1 = "123456789"; + const char* tls_value2 = "abcdefghi"; + const int num_saved_values = 10; + __emutls_control tls_var[num_saved_values]; + void* saved_gap[num_saved_values]; + void* saved_p[num_saved_values]; + size_t prev_index = 0; + int n = 0; + + ASSERT_TRUE(strlen(tls_value2) <= strlen(tls_value1)); + + __emutls_control c = {strlen(tls_value1) + 1, 1, {0}, (void*)tls_value1}; + for (n = 0; n < num_saved_values; n++) { + memcpy(&tls_var[n], &c, sizeof(c)); + tls_var[n].align = (1 << n); + } + + for (n = 0; n < num_saved_values; n++) { + saved_gap[n] = malloc(1); + void* p = __emutls_get_address(&tls_var[n]); + saved_p[n] = p; + ASSERT_TRUE(p != NULL); + ASSERT_TRUE(tls_var[n].object.index != 0); + // check if p is a new object. + if (n > 0) { + // In single-thread environment, object.address == p. + // In multi-threads environment, object.index is increased. + ASSERT_TRUE(prev_index + 1 == tls_var[n].object.index || + p == tls_var[n].object.address); + } + prev_index = tls_var[n].object.index; + // check if p is aligned + uintptr_t align = (1 << n); + ASSERT_EQ(((uintptr_t)p & ~(align - 1)), (uintptr_t)p); + // check if *p is initialized + ASSERT_STREQ(tls_value1, (char*)p); + memcpy(p, (void*)tls_value2, strlen(tls_value2) + 1); + } + + for (n = 0; n < num_saved_values; n++) { + free(saved_gap[n]); + } + for (n = 0; n < num_saved_values; n++) { + void* p = __emutls_get_address(&tls_var[n]); + ASSERT_EQ(p, saved_p[n]); + // check if *p has the new value + ASSERT_STREQ(tls_value2, (char*)p); + } +} + +int main() +{ + if (test__type_size()) + return 1; + if (test__init_align()) + return 1; + return 0; +}