blob: dac807dd68705a5ed3fef45e5945a6b4891d4fe0 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <float.h>
#include <limits.h>
#include <sched.h>
#include <stdint.h>
#include <threads.h>
#include <limits>
#include <vector>
#include <zxtest/zxtest.h>
namespace {
constexpr char kMainThreadError[] = "MainThread: Unexpected initialized value";
constexpr char kBackgroundThreadError[] = "BackgroundThread: Unexpected initialized value";
struct Bits {
uint64_t bits0 : 9;
uint64_t bits1 : 9;
uint64_t bits2 : 9;
uint64_t bits3 : 9;
uint64_t bits4 : 9;
uint64_t bits5 : 9;
uint64_t bits6 : 9;
double f64;
uint64_t bits7 : 9;
uint64_t bits8 : 9;
uint64_t bits9 : 9;
uint64_t bits10 : 9;
uint64_t bits11 : 9;
uint64_t bits12 : 9;
uint64_t bits13 : 9;
};
thread_local bool u1 = true;
thread_local uint8_t u8 = std::numeric_limits<uint8_t>::max();
thread_local uint16_t u16 = std::numeric_limits<uint16_t>::max();
thread_local uint32_t u32 = std::numeric_limits<uint32_t>::max();
thread_local uint64_t u64 = std::numeric_limits<uint64_t>::max();
thread_local uintptr_t uptr = std::numeric_limits<uintptr_t>::max();
thread_local int8_t i8 = std::numeric_limits<int8_t>::max();
thread_local int16_t i16 = std::numeric_limits<int16_t>::max();
thread_local int32_t i32 = std::numeric_limits<int32_t>::max();
thread_local int64_t i64 = std::numeric_limits<int64_t>::max();
thread_local intptr_t iptr = std::numeric_limits<intptr_t>::max();
thread_local float f32 = std::numeric_limits<float>::max();
thread_local double f64 = std::numeric_limits<double>::max();
thread_local void* ptr = &ptr;
thread_local Bits bits = {
0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, std::numeric_limits<double>::max(),
0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu, 0x1ffu,
};
struct BasicInitializerInfo {
bool u1;
uint8_t u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;
uintptr_t uptr;
int8_t i8;
int16_t i16;
int32_t i32;
int64_t i64;
intptr_t iptr;
float f32;
double f64;
void* ptr_addr;
void* ptr_val;
Bits bits;
};
int GetBasicInitializers(void* arg) {
auto info = reinterpret_cast<BasicInitializerInfo*>(arg);
info->u1 = u1;
info->u8 = u8;
info->u16 = u16;
info->u32 = u32;
info->u64 = u64;
info->uptr = uptr;
info->i8 = i8;
info->i16 = i16;
info->i32 = i32;
info->i64 = i64;
info->iptr = iptr;
info->f32 = f32;
info->f64 = f64;
info->ptr_addr = &ptr;
info->ptr_val = ptr;
info->bits.bits0 = bits.bits0;
info->bits.bits1 = bits.bits1;
info->bits.bits2 = bits.bits2;
info->bits.bits3 = bits.bits3;
info->bits.bits4 = bits.bits4;
info->bits.bits5 = bits.bits5;
info->bits.bits6 = bits.bits7;
info->bits.bits7 = bits.bits7;
info->bits.bits8 = bits.bits8;
info->bits.bits9 = bits.bits9;
info->bits.bits10 = bits.bits10;
info->bits.bits11 = bits.bits11;
info->bits.bits12 = bits.bits12;
info->bits.bits13 = bits.bits13;
return 0;
}
void VerifyBasicInitializers(const BasicInitializerInfo* info, const char* error_message) {
ASSERT_EQ(info->u1, true, "%s", error_message);
ASSERT_EQ(info->u8, std::numeric_limits<uint8_t>::max(), "%s", error_message);
ASSERT_EQ(info->u16, std::numeric_limits<uint16_t>::max(), "%s", error_message);
ASSERT_EQ(info->u32, std::numeric_limits<uint32_t>::max(), "%s", error_message);
ASSERT_EQ(info->u64, std::numeric_limits<uint64_t>::max(), "%s", error_message);
ASSERT_EQ(info->uptr, std::numeric_limits<uintptr_t>::max(), "%s", error_message);
ASSERT_EQ(info->i8, std::numeric_limits<int8_t>::max(), "%s", error_message);
ASSERT_EQ(info->i16, std::numeric_limits<int16_t>::max(), "%s", error_message);
ASSERT_EQ(info->i32, std::numeric_limits<int32_t>::max(), "%s", error_message);
ASSERT_EQ(info->i64, std::numeric_limits<int64_t>::max(), "%s", error_message);
ASSERT_EQ(info->iptr, std::numeric_limits<intptr_t>::max(), "%s", error_message);
ASSERT_EQ(info->f32, std::numeric_limits<float>::max(), "%s", error_message);
ASSERT_EQ(info->f64, std::numeric_limits<double>::max(), "%s", error_message);
ASSERT_EQ(info->ptr_addr, info->ptr_val, "%s", error_message);
ASSERT_EQ(info->bits.bits0, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits1, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits2, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits3, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits4, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits5, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits6, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits7, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits8, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits9, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits10, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits11, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits12, 0x1ffu, "%s", error_message);
ASSERT_EQ(info->bits.bits13, 0x1ffu, "%s", error_message);
}
constexpr char kThreadNameBasicInitializer[] = "GetInitializers";
TEST(ExecutableTlsTest, BasicInitalizersInThread) {
thrd_t thread;
auto info = std::make_unique<BasicInitializerInfo>();
int ret = thrd_create_with_name(&thread, &GetBasicInitializers, info.get(),
kThreadNameBasicInitializer);
ASSERT_EQ(ret, thrd_success, "unable to create GetInitializers thread");
ret = thrd_join(thread, nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join GetInitializers thread");
VerifyBasicInitializers(info.get(), kBackgroundThreadError);
}
TEST(ExecutableTlsTest, BasicInitalizersInMain) {
auto info = std::make_unique<BasicInitializerInfo>();
ASSERT_EQ(GetBasicInitializers(info.get()), 0);
VerifyBasicInitializers(info.get(), kMainThreadError);
}
constexpr int kArraySize = 1024;
#define BYTES_4 0xffu, 0xffu, 0xffu, 0xffu,
#define BYTES_16 BYTES_4 BYTES_4 BYTES_4 BYTES_4
#define BYTES_64 BYTES_16 BYTES_16 BYTES_16 BYTES_16
#define BYTES_256 BYTES_64 BYTES_64 BYTES_64 BYTES_64
#define BYTES_1024 BYTES_256 BYTES_256 BYTES_256 BYTES_256
thread_local uint8_t array[kArraySize] = {BYTES_1024};
struct ArrayInfo {
uint8_t array[kArraySize];
};
int GetArray(void* arg) {
auto info = reinterpret_cast<ArrayInfo*>(arg);
memcpy(info->array, array, kArraySize);
return 0;
}
constexpr char kThreadNameArrayInitializer[] = "ArrayInitializers";
TEST(ExecutableTlsTest, ArrayInitializerInThread) {
thrd_t thread;
auto info = std::make_unique<ArrayInfo>();
int ret = thrd_create_with_name(&thread, &GetArray, info.get(), kThreadNameArrayInitializer);
ASSERT_EQ(ret, thrd_success, "unable to create GetArray thread");
ret = thrd_join(thread, nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join GetArray thread");
for (const auto byte : info->array) {
ASSERT_EQ(byte, std::numeric_limits<uint8_t>::max(), "%s", kBackgroundThreadError);
}
}
TEST(ExecutableTlsTest, ArrayInitializerInMain) {
auto info = std::make_unique<ArrayInfo>();
ASSERT_EQ(GetArray(info.get()), 0);
for (const auto byte : info->array) {
ASSERT_EQ(byte, std::numeric_limits<uint8_t>::max(), "%s", kMainThreadError);
}
}
constexpr int kBitArraySize = 1 << 20;
thread_local uint8_t big_array[kBitArraySize];
struct BigArrayInfo {
uint8_t big_array[kBitArraySize];
};
int GetBigArray(void* arg) {
auto info = reinterpret_cast<BigArrayInfo*>(arg);
memcpy(info->big_array, big_array, kBitArraySize);
return 0;
}
void VerifyBigArray(BigArrayInfo const* const info, const char* error_message) {
uint8_t sum = 0u;
for (const auto byte : info->big_array) {
sum |= byte;
}
ASSERT_EQ(sum, 0u, "%s", error_message);
}
constexpr char kThreadNameBigArray[] = "GetBigArray";
TEST(ExecutableTlsTest, BigArrayInitializerInThread) {
thrd_t thread;
auto info = std::make_unique<BigArrayInfo>();
int ret = thrd_create_with_name(&thread, &GetBigArray, info.get(), kThreadNameBigArray);
ASSERT_EQ(ret, thrd_success, "unable to create GetBigArray thread");
ret = thrd_join(thread, nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join GetBigArray thread");
VerifyBigArray(info.get(), kBackgroundThreadError);
}
TEST(ExecutableTlsTest, BigArrayInitializerInMain) {
auto info = std::make_unique<BigArrayInfo>();
ASSERT_EQ(GetBigArray(info.get()), 0);
VerifyBigArray(info.get(), kMainThreadError);
}
struct Ctor {
Ctor() : x(std::numeric_limits<uint64_t>::max()) {}
uint64_t x;
};
thread_local Ctor ctor;
struct CtorInfo {
Ctor ctor;
};
int GetCtor(void* arg) {
auto info = reinterpret_cast<CtorInfo*>(arg);
info->ctor = ctor;
return 0;
}
constexpr char kThreadNameStructureInitializer[] = "StructureInitializer";
TEST(ExecutableTlsTest, StructureInitializerInThread) {
thrd_t thread;
auto info = std::make_unique<CtorInfo>();
int ret = thrd_create_with_name(&thread, &GetCtor, info.get(), kThreadNameStructureInitializer);
ASSERT_EQ(ret, thrd_success, "unable to create GetCtor thread");
ret = thrd_join(thread, nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join GetCtor thread");
ASSERT_EQ(info->ctor.x, std::numeric_limits<uint64_t>::max(), "%s", kBackgroundThreadError);
}
TEST(ExecutableTlsTest, StructureInitalizierInMain) {
auto info = std::make_unique<CtorInfo>();
ASSERT_EQ(GetCtor(info.get()), 0);
ASSERT_EQ(info->ctor.x, std::numeric_limits<uint64_t>::max(), "%s", kMainThreadError);
}
__attribute__((aligned(0x1000))) thread_local int aligned_var = 123;
struct AlignmentInfo {
int* aligned_var_addr;
int aligned_var_value;
};
int GetAlignment(void* arg) {
auto info = reinterpret_cast<AlignmentInfo*>(arg);
// TODO(https://fxbug.dev/42106507): Make this work on ARM64.
info->aligned_var_value = aligned_var;
info->aligned_var_addr = &aligned_var;
return 0;
}
void VerifyAlignment(AlignmentInfo const* const info, const char* error_message) {
// TODO(https://fxbug.dev/42106507): Make this work on ARM64.
EXPECT_EQ(reinterpret_cast<uintptr_t>(info->aligned_var_addr) % 0x1000, 0, "%s", error_message);
EXPECT_EQ(info->aligned_var_value, 123, "%s", error_message);
}
constexpr char kThreadNameGetAlignment[] = "GetInitializers";
TEST(ExecutableTlsTest, AlignmentInitializerInThread) {
thrd_t thread;
auto info = std::make_unique<AlignmentInfo>();
int ret = thrd_create_with_name(&thread, &GetAlignment, info.get(), kThreadNameGetAlignment);
ASSERT_EQ(ret, thrd_success, "unable to create GetAlignment thread");
ret = thrd_join(thread, nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join GetAlignment thread");
VerifyAlignment(info.get(), kBackgroundThreadError);
}
TEST(ExecutableTlsTest, AlignmentInitializierInMain) {
auto info = std::make_unique<AlignmentInfo>();
ASSERT_EQ(GetAlignment(info.get()), 0);
VerifyAlignment(info.get(), kMainThreadError);
}
thread_local uint8_t spam_array[kArraySize] = {BYTES_1024};
struct ArraySpamInfo {
uint8_t index;
bool failure;
uint8_t failure_offset;
uint8_t actual_value;
uint8_t expected_value;
};
int TestArraySpam(void* arg) {
auto info = reinterpret_cast<ArraySpamInfo*>(arg);
for (uint8_t iteration = 0; iteration < 100; ++iteration) {
uint8_t starting_value = static_cast<uint8_t>(info->index + iteration);
uint8_t value = starting_value;
for (auto& byte : spam_array) {
byte = value;
++value;
}
sched_yield();
value = starting_value;
uint8_t failure_offset = 0;
for (const auto byte : spam_array) {
if (byte != value) {
info->failure = true;
info->actual_value = byte;
info->expected_value = value;
info->failure_offset = failure_offset;
return 0;
}
++value;
++failure_offset;
}
}
info->failure = false;
return 0;
}
constexpr char kThreadNameArraySpam[] = "TestArraySpam";
TEST(ExecutableTlsTest, ArrayInitializerSpamThread) {
constexpr int kThreadCount = 64;
std::vector<thrd_t> threads(kThreadCount);
std::vector<ArraySpamInfo> info(kThreadCount);
for (uint8_t index = 0; index < kThreadCount; ++index) {
info[index].index = index;
int ret =
thrd_create_with_name(&threads[index], &TestArraySpam, &info[index], kThreadNameArraySpam);
ASSERT_EQ(ret, thrd_success, "unable to create TestArraySpamInfo thread");
}
for (uint8_t index = 0; index < kThreadCount; ++index) {
int ret = thrd_join(threads[index], nullptr);
ASSERT_EQ(ret, thrd_success, "unable to join TestArraySpamInfo thread");
ASSERT_FALSE(info[index].failure,
"%s: thread=%d ExpectedValue=%d ActualValue=%d FailureOffset=%d",
kBackgroundThreadError, index, info[index].expected_value,
info[index].actual_value, info[index].failure_offset);
}
}
TEST(ExecutableTlsTest, ArrayInitializierSpamMain) {
auto info = std::make_unique<ArraySpamInfo>();
ASSERT_EQ(TestArraySpam(info.get()), 0);
ASSERT_FALSE(info->failure, "%s: ExpectedValue=%d ActualValue=%d FailureOffset=%d",
kMainThreadError, info->expected_value, info->actual_value, info->failure_offset);
}
} // namespace