diff --git a/tests/Android.mk b/tests/Android.mk index 21deb5c3b..fc47e809f 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -280,6 +280,7 @@ bionic-unit-tests_src_files := \ dlfcn_test.cpp \ libdl_test.cpp \ pthread_dlfcn_test.cpp \ + thread_local_test.cpp \ bionic-unit-tests_cflags := $(test_cflags) diff --git a/tests/thread_local_test.cpp b/tests/thread_local_test.cpp new file mode 100644 index 000000000..5dead32c4 --- /dev/null +++ b/tests/thread_local_test.cpp @@ -0,0 +1,168 @@ +/* + * 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 + +#ifdef __GNUC__ +// 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(p); + shared_var++; + *data = shared_var; + return nullptr; +} + +static void* inc_local_var(void* p) { + int *data = reinterpret_cast(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(p); + shared_triangle[1].y++; + *data = shared_triangle[1].y; + return nullptr; +} + +static void* move_local_triangle(void* p) { + int *data = reinterpret_cast(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); +}