/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include <stdint.h> #include <string.h> #if defined(__GNUC__) && !defined(__clang__) && \ (defined(__arm__) || defined(__aarch64__)) // Gcc has a bug with -O -fdata-section for the arm target: http://b/22772147. // Until that bug is fixed, disable optimization since // it is not essential for this test. #pragma GCC optimize("-O0") #endif __thread int local_var = 100; int shared_var = 200; static void reset_vars() { local_var = 1000; shared_var = 2000; // local_var should be reset by threads } typedef void* (*MyThread)(void*); static void* inc_shared_var(void* p) { int *data = reinterpret_cast<int*>(p); shared_var++; *data = shared_var; return nullptr; } static void* inc_local_var(void* p) { int *data = reinterpret_cast<int*>(p); local_var++; *data = local_var; return nullptr; } static int run_one_thread(MyThread foo) { pthread_t t; int data; int error = pthread_create(&t, nullptr, foo, &data); if (!error) error = pthread_join(t, nullptr); return error ? error : data; } TEST(thread_local_storage, shared) { reset_vars(); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2000); // Update shared_var, local_var remains 1000. ASSERT_EQ(run_one_thread(inc_shared_var), 2001); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2001); ASSERT_EQ(run_one_thread(inc_shared_var), 2002); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2002); ASSERT_EQ(run_one_thread(inc_shared_var), 2003); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2003); } TEST(thread_local_storage, local) { reset_vars(); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2000); // When a child thread updates its own TLS variable, // this thread's local_var and shared_var are not changed. // TLS local_var is initialized to 100 in a thread. ASSERT_EQ(run_one_thread(inc_local_var), 101); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2000); ASSERT_EQ(run_one_thread(inc_local_var), 101); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2000); ASSERT_EQ(run_one_thread(inc_local_var), 101); ASSERT_EQ(local_var, 1000); ASSERT_EQ(shared_var, 2000); } // Test TLS initialization of more complicated type, array of struct. struct Point { int x, y; }; typedef Point Triangle[3]; __thread Triangle local_triangle = {{10,10}, {20,20}, {30,30}}; Triangle shared_triangle = {{1,1}, {2,2}, {3,3}}; static void reset_triangle() { static const Triangle t1 = {{3,3}, {4,4}, {5,5}}; static const Triangle t2 = {{2,2}, {3,3}, {4,4}}; memcpy(local_triangle, t1, sizeof(local_triangle)); memcpy(shared_triangle, t2, sizeof(shared_triangle)); } static void* move_shared_triangle(void* p) { int *data = reinterpret_cast<int*>(p); shared_triangle[1].y++; *data = shared_triangle[1].y; return nullptr; } static void* move_local_triangle(void* p) { int *data = reinterpret_cast<int*>(p); local_triangle[1].y++; *data = local_triangle[1].y; return nullptr; } TEST(thread_local_storage, shared_triangle) { reset_triangle(); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 3); // Update shared_triangle, local_triangle remains 1000. ASSERT_EQ(run_one_thread(move_shared_triangle), 4); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 4); ASSERT_EQ(run_one_thread(move_shared_triangle), 5); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 5); ASSERT_EQ(run_one_thread(move_shared_triangle), 6); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 6); } TEST(thread_local_storage, local_triangle) { reset_triangle(); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 3); // Update local_triangle, parent thread's // shared_triangle and local_triangle are unchanged. ASSERT_EQ(run_one_thread(move_local_triangle), 21); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 3); ASSERT_EQ(run_one_thread(move_local_triangle), 21); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 3); ASSERT_EQ(run_one_thread(move_local_triangle), 21); ASSERT_EQ(local_triangle[1].y, 4); ASSERT_EQ(shared_triangle[1].y, 3); } // Test emutls runtime data structures and __emutls_get_address function. 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; TEST(thread_local_storage, type_size) { static_assert(sizeof(size_t) == sizeof(gcc_word), "size_t != gcc_word"); static_assert(sizeof(uintptr_t) == sizeof(gcc_pointer), "uintptr_t != gcc_pointer"); static_assert(sizeof(uintptr_t) == sizeof(void*), "sizoeof(uintptr_t) != sizeof(void*)"); static_assert(sizeof(__emutls_control) == sizeof(struct gcc_emutls_object), "sizeof(__emutls_control) != sizeof(struct gcc_emutls_object)"); } extern "C" void* __emutls_get_address(__emutls_control*); TEST(thread_local_storage, init_value) { char tls_value1[] = "123456789"; char tls_value2[] = "abcdefghi"; constexpr size_t num_saved_values = 10; __emutls_control tls_var[num_saved_values]; size_t prev_index = 0; void* saved_gap[num_saved_values]; void* saved_p[num_saved_values]; ASSERT_TRUE(strlen(tls_value2) <= strlen(tls_value1)); __emutls_control c = {strlen(tls_value1) + 1, 1, {0}, tls_value1}; for (size_t n = 0; n < num_saved_values; n++) { memcpy(&tls_var[n], &c, sizeof(c)); tls_var[n].align = (1 << n); } for (size_t n = 0; n < num_saved_values; n++) { // Try to mess up malloc space so that the next malloc will not have the // required alignment, but __emutls_get_address should still return an // aligned address. saved_gap[n] = malloc(1); void* p = __emutls_get_address(&tls_var[n]); saved_p[n] = p; ASSERT_TRUE(p != nullptr); 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); ASSERT_TRUE(p != saved_p[n - 1]); } prev_index = tls_var[n].object.index; // check if p is aligned uintptr_t align = (1 << n); uintptr_t address= reinterpret_cast<uintptr_t>(p); ASSERT_EQ((address & ~(align - 1)), address); // check if *p is initialized ASSERT_STREQ(tls_value1, static_cast<char*>(p)); // change value in *p memcpy(p, tls_value2, strlen(tls_value2) + 1); } for (size_t n = 0; n < num_saved_values; n++) { free(saved_gap[n]); } for (size_t 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, static_cast<char*>(p)); } }