| /* | |
| * 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)); | |
| } | |
| } |