kern: cleanup KThread, optimize/normalize KThreadQueue/KWaitObject

This commit is contained in:
Michael Scire 2020-12-01 15:19:29 -08:00
parent a4c4cf22c9
commit 72671d39ab
10 changed files with 58 additions and 168 deletions

View file

@ -24,7 +24,6 @@ namespace ams::kern {
struct InfoCreateThread { struct InfoCreateThread {
u32 thread_id; u32 thread_id;
uintptr_t tls_address; uintptr_t tls_address;
uintptr_t entrypoint;
}; };
struct InfoExitProcess { struct InfoExitProcess {

View file

@ -31,7 +31,7 @@ namespace ams::kern {
using KThreadFunction = void (*)(uintptr_t); using KThreadFunction = void (*)(uintptr_t);
class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KSynchronizationObject>, public KTimerTask, public KWorkerTask { class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KSynchronizationObject>, public util::IntrusiveListBaseNode<KThread>, public KTimerTask, public KWorkerTask {
MESOSPHERE_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject); MESOSPHERE_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject);
private: private:
friend class KProcess; friend class KProcess;
@ -111,6 +111,8 @@ namespace ams::kern {
constexpr void SetPrev(KThread *t) { this->prev = t; } constexpr void SetPrev(KThread *t) { this->prev = t; }
constexpr void SetNext(KThread *t) { this->next = t; } constexpr void SetNext(KThread *t) { this->next = t; }
}; };
using WaiterList = util::IntrusiveListBaseTraits<KThread>::ListType;
private: private:
static constexpr size_t PriorityInheritanceCountMax = 10; static constexpr size_t PriorityInheritanceCountMax = 10;
union SyncObjectBuffer { union SyncObjectBuffer {
@ -141,13 +143,19 @@ namespace ams::kern {
static inline std::atomic<u64> s_next_thread_id = 0; static inline std::atomic<u64> s_next_thread_id = 0;
private: private:
alignas(16) KThreadContext thread_context{}; alignas(16) KThreadContext thread_context{};
util::IntrusiveListNode process_list_node{};
util::IntrusiveRedBlackTreeNode condvar_arbiter_tree_node{};
s32 priority{};
using ConditionVariableThreadTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&KThread::condvar_arbiter_tree_node>;
using ConditionVariableThreadTree = ConditionVariableThreadTreeTraits::TreeType<ConditionVariableComparator>;
ConditionVariableThreadTree *condvar_tree{};
uintptr_t condvar_key{};
KAffinityMask affinity_mask{}; KAffinityMask affinity_mask{};
u64 thread_id{}; u64 thread_id{};
std::atomic<s64> cpu_time{}; std::atomic<s64> cpu_time{};
KSynchronizationObject *synced_object{}; KSynchronizationObject *synced_object{};
KLightLock *waiting_lock{};
uintptr_t condvar_key{};
uintptr_t entrypoint{};
KProcessAddress address_key{}; KProcessAddress address_key{};
KProcess *parent{}; KProcess *parent{};
void *kernel_stack_top{}; void *kernel_stack_top{};
@ -159,43 +167,31 @@ namespace ams::kern {
s64 schedule_count{}; s64 schedule_count{};
s64 last_scheduled_tick{}; s64 last_scheduled_tick{};
QueueEntry per_core_priority_queue_entry[cpu::NumCores]{}; QueueEntry per_core_priority_queue_entry[cpu::NumCores]{};
QueueEntry sleeping_queue_entry{}; KLightLock *waiting_lock{};
KThreadQueue *sleeping_queue{}; KThreadQueue *sleeping_queue{};
util::IntrusiveListNode waiter_list_node{};
util::IntrusiveRedBlackTreeNode condvar_arbiter_tree_node{};
util::IntrusiveListNode process_list_node{};
using WaiterListTraits = util::IntrusiveListMemberTraitsDeferredAssert<&KThread::waiter_list_node>;
using WaiterList = WaiterListTraits::ListType;
using ConditionVariableThreadTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&KThread::condvar_arbiter_tree_node>;
using ConditionVariableThreadTree = ConditionVariableThreadTreeTraits::TreeType<ConditionVariableComparator>;
WaiterList waiter_list{}; WaiterList waiter_list{};
WaiterList pinned_waiter_list{}; WaiterList pinned_waiter_list{};
KThread *lock_owner{}; KThread *lock_owner{};
ConditionVariableThreadTree *condvar_tree{};
uintptr_t debug_params[3]{}; uintptr_t debug_params[3]{};
u32 address_key_value{}; u32 address_key_value{};
u32 suspend_request_flags{}; u32 suspend_request_flags{};
u32 suspend_allowed_flags{}; u32 suspend_allowed_flags{};
Result wait_result; Result wait_result;
Result debug_exception_result; Result debug_exception_result;
s32 priority{};
s32 current_core_id{};
s32 core_id{};
s32 base_priority{}; s32 base_priority{};
s32 ideal_core_id{}; s32 ideal_core_id{};
s32 num_kernel_waiters{}; s32 num_kernel_waiters{};
s32 current_core_id{};
s32 core_id{};
KAffinityMask original_affinity_mask{}; KAffinityMask original_affinity_mask{};
s32 original_ideal_core_id{}; s32 original_ideal_core_id{};
s32 num_core_migration_disables{}; s32 num_core_migration_disables{};
ThreadState thread_state{}; ThreadState thread_state{};
std::atomic<bool> termination_requested{}; std::atomic<bool> termination_requested{};
bool ipc_cancelled{};
bool wait_cancelled{}; bool wait_cancelled{};
bool cancellable{}; bool cancellable{};
bool registered{};
bool signaled{}; bool signaled{};
bool initialized{}; bool initialized{};
bool debug_attached{}; bool debug_attached{};
@ -393,8 +389,6 @@ namespace ams::kern {
constexpr QueueEntry &GetPriorityQueueEntry(s32 core) { return this->per_core_priority_queue_entry[core]; } constexpr QueueEntry &GetPriorityQueueEntry(s32 core) { return this->per_core_priority_queue_entry[core]; }
constexpr const QueueEntry &GetPriorityQueueEntry(s32 core) const { return this->per_core_priority_queue_entry[core]; } constexpr const QueueEntry &GetPriorityQueueEntry(s32 core) const { return this->per_core_priority_queue_entry[core]; }
constexpr QueueEntry &GetSleepingQueueEntry() { return this->sleeping_queue_entry; }
constexpr const QueueEntry &GetSleepingQueueEntry() const { return this->sleeping_queue_entry; }
constexpr void SetSleepingQueue(KThreadQueue *q) { this->sleeping_queue = q; } constexpr void SetSleepingQueue(KThreadQueue *q) { this->sleeping_queue = q; }
constexpr ConditionVariableThreadTree *GetConditionVariableTree() const { return this->condvar_tree; } constexpr ConditionVariableThreadTree *GetConditionVariableTree() const { return this->condvar_tree; }
@ -459,8 +453,6 @@ namespace ams::kern {
constexpr KProcess *GetOwnerProcess() const { return this->parent; } constexpr KProcess *GetOwnerProcess() const { return this->parent; }
constexpr bool IsUserThread() const { return this->parent != nullptr; } constexpr bool IsUserThread() const { return this->parent != nullptr; }
constexpr uintptr_t GetEntrypoint() const { return this->entrypoint; }
constexpr KProcessAddress GetThreadLocalRegionAddress() const { return this->tls_address; } constexpr KProcessAddress GetThreadLocalRegionAddress() const { return this->tls_address; }
constexpr void *GetThreadLocalRegionHeapAddress() const { return this->tls_heap_address; } constexpr void *GetThreadLocalRegionHeapAddress() const { return this->tls_heap_address; }
@ -541,10 +533,6 @@ namespace ams::kern {
virtual void OnTimer() override; virtual void OnTimer() override;
virtual void DoWorkerTask() override; virtual void DoWorkerTask() override;
public: public:
static constexpr bool IsWaiterListValid() {
return WaiterListTraits::IsValid();
}
static constexpr bool IsConditionVariableThreadTreeValid() { static constexpr bool IsConditionVariableThreadTreeValid() {
return ConditionVariableThreadTreeTraits::IsValid(); return ConditionVariableThreadTreeTraits::IsValid();
} }
@ -555,7 +543,6 @@ namespace ams::kern {
using ConditionVariableThreadTreeType = ConditionVariableThreadTree; using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
}; };
static_assert(alignof(KThread) == 0x10); static_assert(alignof(KThread) == 0x10);
static_assert(KThread::IsWaiterListValid());
static_assert(KThread::IsConditionVariableThreadTreeValid()); static_assert(KThread::IsConditionVariableThreadTreeValid());
class KScopedDisableDispatch { class KScopedDisableDispatch {

View file

@ -21,92 +21,38 @@ namespace ams::kern {
class KThreadQueue { class KThreadQueue {
private: private:
using Entry = KThread::QueueEntry; KThread::WaiterList wait_list;
private:
Entry root;
public: public:
constexpr ALWAYS_INLINE KThreadQueue() : root() { /* ... */ } constexpr ALWAYS_INLINE KThreadQueue() : wait_list() { /* ... */ }
constexpr ALWAYS_INLINE bool IsEmpty() const { return this->root.GetNext() == nullptr; } bool IsEmpty() const { return this->wait_list.empty(); }
constexpr ALWAYS_INLINE KThread *GetFront() const { return this->root.GetNext(); } KThread::WaiterList::iterator begin() { return this->wait_list.begin(); }
constexpr ALWAYS_INLINE KThread *GetNext(KThread *t) const { return t->GetSleepingQueueEntry().GetNext(); } KThread::WaiterList::iterator end() { return this->wait_list.end(); }
private:
constexpr ALWAYS_INLINE KThread *GetBack() const { return this->root.GetPrev(); }
constexpr ALWAYS_INLINE void Enqueue(KThread *add) {
/* Get the entry associated with the added thread. */
Entry &add_entry = add->GetSleepingQueueEntry();
/* Get the entry associated with the end of the queue. */
KThread *tail = this->GetBack();
Entry &tail_entry = (tail != nullptr) ? tail->GetSleepingQueueEntry() : this->root;
/* Link the entries. */
add_entry.SetPrev(tail);
add_entry.SetNext(nullptr);
tail_entry.SetNext(add);
this->root.SetPrev(add);
}
constexpr ALWAYS_INLINE void Remove(KThread *remove) {
/* Get the entry associated with the thread. */
Entry &remove_entry = remove->GetSleepingQueueEntry();
/* Get the entries associated with next and prev. */
KThread *prev = remove_entry.GetPrev();
KThread *next = remove_entry.GetNext();
Entry &prev_entry = (prev != nullptr) ? prev->GetSleepingQueueEntry() : this->root;
Entry &next_entry = (next != nullptr) ? next->GetSleepingQueueEntry() : this->root;
/* Unlink. */
prev_entry.SetNext(next);
next_entry.SetPrev(prev);
}
public:
constexpr ALWAYS_INLINE void Dequeue() {
/* Get the front of the queue. */
KThread *head = this->GetFront();
if (head == nullptr) {
return;
}
MESOSPHERE_ASSERT(head->GetState() == KThread::ThreadState_Waiting);
/* Get the entry for the next head. */
KThread *next = GetNext(head);
Entry &next_entry = (next != nullptr) ? next->GetSleepingQueueEntry() : this->root;
/* Link the entries. */
this->root.SetNext(next);
next_entry.SetPrev(nullptr);
/* Clear the head's queue. */
head->SetSleepingQueue(nullptr);
}
bool SleepThread(KThread *t) { bool SleepThread(KThread *t) {
KScopedSchedulerLock sl;
/* If the thread needs terminating, don't enqueue it. */
if (t->IsTerminationRequested()) {
return false;
}
/* Set the thread's queue and mark it as waiting. */ /* Set the thread's queue and mark it as waiting. */
t->SetSleepingQueue(this); t->SetSleepingQueue(this);
t->SetState(KThread::ThreadState_Waiting); t->SetState(KThread::ThreadState_Waiting);
/* Add the thread to the queue. */ /* Add the thread to the queue. */
this->Enqueue(t); this->wait_list.push_back(*t);
/* If the thread needs terminating, undo our work. */
if (t->IsTerminationRequested()) {
this->WakeupThread(t);
return false;
}
return true; return true;
} }
void WakeupThread(KThread *t) { void WakeupThread(KThread *t) {
MESOSPHERE_ASSERT(t->GetState() == KThread::ThreadState_Waiting); KScopedSchedulerLock sl;
/* Remove the thread from the queue. */ /* Remove the thread from the queue. */
this->Remove(t); this->wait_list.erase(this->wait_list.iterator_to(*t));
/* Mark the thread as no longer sleeping. */ /* Mark the thread as no longer sleeping. */
t->SetState(KThread::ThreadState_Runnable); t->SetState(KThread::ThreadState_Runnable);
@ -114,18 +60,24 @@ namespace ams::kern {
} }
KThread *WakeupFrontThread() { KThread *WakeupFrontThread() {
KThread *front = this->GetFront(); KScopedSchedulerLock sl;
if (front != nullptr) {
MESOSPHERE_ASSERT(front->GetState() == KThread::ThreadState_Waiting);
if (this->wait_list.empty()) {
return nullptr;
} else {
/* Remove the thread from the queue. */ /* Remove the thread from the queue. */
this->Dequeue(); auto it = this->wait_list.begin();
KThread *thread = std::addressof(*it);
this->wait_list.erase(it);
MESOSPHERE_ASSERT(thread->GetState() == KThread::ThreadState_Waiting);
/* Mark the thread as no longer sleeping. */ /* Mark the thread as no longer sleeping. */
front->SetState(KThread::ThreadState_Runnable); thread->SetState(KThread::ThreadState_Runnable);
front->SetSleepingQueue(nullptr); thread->SetSleepingQueue(nullptr);
return thread;
} }
return front;
} }
}; };

View file

@ -22,45 +22,13 @@ namespace ams::kern {
class KWaitObject : public KTimerTask { class KWaitObject : public KTimerTask {
private: private:
using Entry = KThread::QueueEntry; KThread::WaiterList wait_list;
private:
Entry root;
bool timer_used; bool timer_used;
public: public:
constexpr KWaitObject() : root(), timer_used() { /* ... */ } constexpr KWaitObject() : wait_list(), timer_used() { /* ... */ }
virtual void OnTimer() override; virtual void OnTimer() override;
Result Synchronize(s64 timeout); Result Synchronize(s64 timeout);
private:
constexpr ALWAYS_INLINE void Enqueue(KThread *add) {
/* Get the entry associated with the added thread. */
Entry &add_entry = add->GetSleepingQueueEntry();
/* Get the entry associated with the end of the queue. */
KThread *tail = this->root.GetPrev();
Entry &tail_entry = (tail != nullptr) ? tail->GetSleepingQueueEntry() : this->root;
/* Link the entries. */
add_entry.SetPrev(tail);
add_entry.SetNext(nullptr);
tail_entry.SetNext(add);
this->root.SetPrev(add);
}
constexpr ALWAYS_INLINE void Remove(KThread *remove) {
/* Get the entry associated with the thread. */
Entry &remove_entry = remove->GetSleepingQueueEntry();
/* Get the entries associated with next and prev. */
KThread *prev = remove_entry.GetPrev();
KThread *next = remove_entry.GetNext();
Entry &prev_entry = (prev != nullptr) ? prev->GetSleepingQueueEntry() : this->root;
Entry &next_entry = (next != nullptr) ? next->GetSleepingQueueEntry() : this->root;
/* Unlink. */
prev_entry.SetNext(next);
next_entry.SetPrev(prev);
}
}; };
} }

View file

@ -26,7 +26,7 @@ namespace ams::kern::arch::arm64 {
/* Send KDebug event for this thread's creation. */ /* Send KDebug event for this thread's creation. */
{ {
KScopedInterruptEnable ei; KScopedInterruptEnable ei;
KDebug::OnDebugEvent(ams::svc::DebugEvent_CreateThread, GetCurrentThread().GetId(), GetInteger(GetCurrentThread().GetThreadLocalRegionAddress()), GetCurrentThread().GetEntrypoint()); KDebug::OnDebugEvent(ams::svc::DebugEvent_CreateThread, GetCurrentThread().GetId(), GetInteger(GetCurrentThread().GetThreadLocalRegionAddress()));
} }
/* Handle any pending dpc. */ /* Handle any pending dpc. */

View file

@ -327,7 +327,7 @@ namespace ams::kern {
it->SetDebugAttached(); it->SetDebugAttached();
/* Send the event. */ /* Send the event. */
this->PushDebugEvent(ams::svc::DebugEvent_CreateThread, it->GetId(), GetInteger(it->GetThreadLocalRegionAddress()), it->GetEntrypoint()); this->PushDebugEvent(ams::svc::DebugEvent_CreateThread, it->GetId(), GetInteger(it->GetThreadLocalRegionAddress()));
} }
} }
} }
@ -682,7 +682,6 @@ namespace ams::kern {
/* Set the thread creation info. */ /* Set the thread creation info. */
info->info.create_thread.thread_id = param0; info->info.create_thread.thread_id = param0;
info->info.create_thread.tls_address = param1; info->info.create_thread.tls_address = param1;
info->info.create_thread.entrypoint = param2;
} }
break; break;
case ams::svc::DebugEvent_ExitProcess: case ams::svc::DebugEvent_ExitProcess:
@ -842,7 +841,6 @@ namespace ams::kern {
{ {
out->info.create_thread.thread_id = info->info.create_thread.thread_id; out->info.create_thread.thread_id = info->info.create_thread.thread_id;
out->info.create_thread.tls_address = info->info.create_thread.tls_address; out->info.create_thread.tls_address = info->info.create_thread.tls_address;
out->info.create_thread.entrypoint = info->info.create_thread.entrypoint;
} }
break; break;
case ams::svc::DebugEvent_ExitProcess: case ams::svc::DebugEvent_ExitProcess:

View file

@ -74,7 +74,7 @@ namespace ams::kern {
/* If we can reply, do so. */ /* If we can reply, do so. */
if (!this->current_request->IsTerminationRequested()) { if (!this->current_request->IsTerminationRequested()) {
MESOSPHERE_ASSERT(this->current_request->GetState() == KThread::ThreadState_Waiting); MESOSPHERE_ASSERT(this->current_request->GetState() == KThread::ThreadState_Waiting);
MESOSPHERE_ASSERT(this->current_request == this->request_queue.GetFront()); MESOSPHERE_ASSERT(this->request_queue.begin() != this->request_queue.end() && this->current_request == std::addressof(*this->request_queue.begin()));
std::memcpy(this->current_request->GetLightSessionData(), server_thread->GetLightSessionData(), KLightSession::DataSize); std::memcpy(this->current_request->GetLightSessionData(), server_thread->GetLightSessionData(), KLightSession::DataSize);
this->request_queue.WakeupThread(this->current_request); this->request_queue.WakeupThread(this->current_request);
} }
@ -110,8 +110,8 @@ namespace ams::kern {
R_UNLESS(!this->parent->IsServerClosed(), svc::ResultSessionClosed()); R_UNLESS(!this->parent->IsServerClosed(), svc::ResultSessionClosed());
/* If we have a request available, use it. */ /* If we have a request available, use it. */
if (this->current_request == nullptr && this->request_queue.IsEmpty()) { if (this->current_request == nullptr && !this->request_queue.IsEmpty()) {
this->current_request = this->request_queue.GetFront(); this->current_request = std::addressof(*this->request_queue.begin());
this->current_request->Open(); this->current_request->Open();
this->server_thread = server_thread; this->server_thread = server_thread;
break; break;
@ -148,7 +148,7 @@ namespace ams::kern {
/* Reply to the current request. */ /* Reply to the current request. */
if (!this->current_request->IsTerminationRequested()) { if (!this->current_request->IsTerminationRequested()) {
MESOSPHERE_ASSERT(this->current_request->GetState() == KThread::ThreadState_Waiting); MESOSPHERE_ASSERT(this->current_request->GetState() == KThread::ThreadState_Waiting);
MESOSPHERE_ASSERT(this->current_request == this->request_queue.GetFront()); MESOSPHERE_ASSERT(this->request_queue.begin() != this->request_queue.end() && this->current_request == std::addressof(*this->request_queue.begin()));
this->request_queue.WakeupThread(this->current_request); this->request_queue.WakeupThread(this->current_request);
this->current_request->SetSyncedObject(nullptr, svc::ResultSessionClosed()); this->current_request->SetSyncedObject(nullptr, svc::ResultSessionClosed());
} }

View file

@ -98,7 +98,6 @@ namespace ams::kern {
/* Set sync booleans. */ /* Set sync booleans. */
this->signaled = false; this->signaled = false;
this->ipc_cancelled = false;
this->termination_requested = false; this->termination_requested = false;
this->wait_cancelled = false; this->wait_cancelled = false;
this->cancellable = false; this->cancellable = false;
@ -119,7 +118,6 @@ namespace ams::kern {
this->waiting_lock = nullptr; this->waiting_lock = nullptr;
/* Initialize sleeping queue. */ /* Initialize sleeping queue. */
this->sleeping_queue_entry.Initialize();
this->sleeping_queue = nullptr; this->sleeping_queue = nullptr;
/* Set suspend flags. */ /* Set suspend flags. */
@ -141,7 +139,6 @@ namespace ams::kern {
/* We have no waiters, but we do have an entrypoint. */ /* We have no waiters, but we do have an entrypoint. */
this->num_kernel_waiters = 0; this->num_kernel_waiters = 0;
this->entrypoint = reinterpret_cast<uintptr_t>(func);
/* Set our current core id. */ /* Set our current core id. */
this->current_core_id = core; this->current_core_id = core;
@ -172,7 +169,7 @@ namespace ams::kern {
const bool is_64_bit = this->parent ? this->parent->Is64Bit() : IsDefault64Bit; const bool is_64_bit = this->parent ? this->parent->Is64Bit() : IsDefault64Bit;
const bool is_user = (type == ThreadType_User); const bool is_user = (type == ThreadType_User);
const bool is_main = (type == ThreadType_Main); const bool is_main = (type == ThreadType_Main);
this->thread_context.Initialize(this->entrypoint, reinterpret_cast<uintptr_t>(this->GetStackTop()), GetInteger(user_stack_top), arg, is_user, is_64_bit, is_main); this->thread_context.Initialize(reinterpret_cast<uintptr_t>(func), reinterpret_cast<uintptr_t>(this->GetStackTop()), GetInteger(user_stack_top), arg, is_user, is_64_bit, is_main);
/* Setup the stack parameters. */ /* Setup the stack parameters. */
StackParameters &sp = this->GetStackParameters(); StackParameters &sp = this->GetStackParameters();

View file

@ -21,19 +21,8 @@ namespace ams::kern {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
/* Wake up all the waiting threads. */ /* Wake up all the waiting threads. */
Entry *entry = std::addressof(this->root); for (KThread &thread : this->wait_list) {
while (true) { thread.Wakeup();
/* Get the next thread. */
KThread *thread = entry->GetNext();
if (thread == nullptr) {
break;
}
/* Wake it up. */
thread->Wakeup();
/* Advance. */
entry = std::addressof(thread->GetSleepingQueueEntry());
} }
} }
@ -73,7 +62,7 @@ namespace ams::kern {
this->OnTimer(); this->OnTimer();
} else { } else {
/* Otherwise, sleep until the timeout occurs. */ /* Otherwise, sleep until the timeout occurs. */
this->Enqueue(cur_thread); this->wait_list.push_back(GetCurrentThread());
cur_thread->SetState(KThread::ThreadState_Waiting); cur_thread->SetState(KThread::ThreadState_Waiting);
cur_thread->SetSyncedObject(nullptr, svc::ResultTimedOut()); cur_thread->SetSyncedObject(nullptr, svc::ResultTimedOut());
} }
@ -93,7 +82,7 @@ namespace ams::kern {
/* Remove the thread from our queue. */ /* Remove the thread from our queue. */
if (timeout != 0) { if (timeout != 0) {
this->Remove(cur_thread); this->wait_list.erase(this->wait_list.iterator_to(GetCurrentThread()));
} }
} }

View file

@ -31,7 +31,7 @@ namespace ams::svc {
struct DebugInfoCreateThread { struct DebugInfoCreateThread {
u64 thread_id; u64 thread_id;
u64 tls_address; u64 tls_address;
u64 entrypoint; /* Removed in 11.0.0 u64 entrypoint; */
}; };
struct DebugInfoExitProcess { struct DebugInfoExitProcess {