//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

#include "pal/corunix.hpp"
#include "pal/context.h"
#include "pal/thread.hpp"
#include "pal/mutex.hpp"
#include "pal/handlemgr.hpp"
#include "pal/cs.hpp"

#include "procprivate.hpp"
#include "pal/process.h"
#include "pal/module.h"
#include "pal/dbgmsg.h"
#include "pal/misc.h"
#include "pal/init.h"

#include <signal.h>
#include <pthread.h>
#if HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <stddef.h>
#include <sys/stat.h>
#if HAVE_MACH_THREADS
#include <mach/mach.h>
#endif // HAVE_MACH_THREADS
#if HAVE_POLL
#include <poll.h>
#else
#include "pal/fakepoll.h"
#endif  // HAVE_POLL
#include <limits.h>
#if HAVE_SYS_LWP_H
#include <sys/lwp.h>
// If we don't have sys/lwp.h but do expect to use _lwp_self, declare it to silence compiler warnings
#elif HAVE__LWP_SELF
extern "C" int _lwp_self ();
#endif // HAVE_LWP_H

using namespace CorUnix;

#ifdef __APPLE__
#define EXPECTED_ALIGNMENT 16 * 1024
static void GetInternalStackLimit(pthread_t thread, ULONG_PTR *highLimit, ULONG_PTR *lowLimit)
{
    size_t stack = pthread_get_stacksize_np(thread);

    // osx 10.9(+ ?) pthread_get_stacksize_np bug
    if (pthread_main_np())
    {
#ifndef __IOS__
        // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
        stack = max(8 * 1024 * 1024, stack);
#else
        pthread_attr_t pat;
        pthread_attr_init(&pat);
        size_t gs = 0;
        pthread_attr_getguardsize(&pat, &gs);
        stack = max((1024 * 1024) - gs, stack);
#endif
    }

    *highLimit = (ULONG_PTR)pthread_get_stackaddr_np(thread);
    stack = *highLimit - stack;

    *lowLimit = ((stack + (EXPECTED_ALIGNMENT - 1)) & ~(EXPECTED_ALIGNMENT - 1));
}
#undef EXPECTED_ALIGNMENT
#endif

/* ------------------- Definitions ------------------------------*/
SET_DEFAULT_DEBUG_CHANNEL(THREAD);

// The default stack size of a newly created thread (currently 256KB)
// when the dwStackSize parameter of PAL_CreateThread()
// is zero. This value can be set by setting the
// environment variable PAL_THREAD_DEFAULT_STACK_SIZE
// (the value should be in bytes and in hex).
DWORD CPalThread::s_dwDefaultThreadStackSize = 256*1024;

/* list of free CPalThread objects */
static Volatile<CPalThread*> free_threads_list PAL_GLOBAL = NULL;

/* lock to access list of free THREAD structures */
/* NOTE: can't use a CRITICAL_SECTION here (see comment in FreeTHREAD) */
static CCSpinLock<false> free_threads_spinlock;

/* lock to access iEndingThreads counter, condition variable to signal shutdown
thread when any remaining threads have died, and count of exiting threads that
can't be suspended. */
pthread_mutex_t ptmEndThread PAL_GLOBAL;
pthread_cond_t ptcEndThread PAL_GLOBAL;
static int iEndingThreads = 0;

// Activation function that gets called when an activation is injected into a thread.
PAL_ActivationFunction g_activationFunction = NULL;
// Function to check if an activation can be safely injected at a specified context
PAL_SafeActivationCheckFunction g_safeActivationCheckFunction = NULL;

void
ThreadCleanupRoutine(
    CPalThread *pThread,
    IPalObject *pObjectToCleanup,
    bool fShutdown,
    bool fCleanupSharedState
    );

PAL_ERROR
ThreadInitializationRoutine(
    CPalThread *pThread,
    CObjectType *pObjectType,
    void *pImmutableData,
    void *pSharedData,
    void *pProcessLocalData
    );

void
IncrementEndingThreadCount(
    void
    );

void
DecrementEndingThreadCount(
    void
    );

CObjectType CorUnix::otThread PAL_GLOBAL (
                otiThread,
                ThreadCleanupRoutine,
                ThreadInitializationRoutine,
                0, //sizeof(CThreadImmutableData),
                sizeof(CThreadProcessLocalData),
                0, //sizeof(CThreadSharedData),
                0, // THREAD_ALL_ACCESS,
                CObjectType::SecuritySupported,
                CObjectType::SecurityInfoNotPersisted,
                CObjectType::UnnamedObject,
                CObjectType::LocalDuplicationOnly,
                CObjectType::WaitableObject,
                CObjectType::SingleTransitionObject,
                CObjectType::ThreadReleaseHasNoSideEffects,
                CObjectType::NoOwner
                );

CAllowedObjectTypes aotThread PAL_GLOBAL (otiThread);

/*++
Function:
  InternalEndCurrentThreadWrapper

  Destructor for the thread-specific data representing the current PAL thread.
  Called from pthread_exit.  (pthread_exit is not called from the thread on which
  main() was first invoked.  This is not a problem, though, since when main()
  returns, this results in an implicit call to exit().)

  arg: the PAL thread
*/
static void InternalEndCurrentThreadWrapper(void *arg)
{
    CPalThread *pThread = (CPalThread *) arg;

    // When pthread_exit calls us, it has already removed the PAL thread
    // from TLS.  Since InternalEndCurrentThread calls functions that assert
    // that the current thread is known to this PAL, and that pThread
    // actually is the current PAL thread, put it back in TLS temporarily.
    pthread_setspecific(thObjKey, pThread);
    (void)PAL_Enter(PAL_BoundaryTop);

    /* Call entry point functions of every attached modules to
       indicate the thread is exiting */
    /* note : no need to enter a critical section for serialization, the loader
       will lock its own critical section */
    LOADCallDllMain(DLL_THREAD_DETACH, NULL);

    // PAL_Leave will be called just before we release the thread reference
    // in InternalEndCurrentThread.
    InternalEndCurrentThread(pThread);
    pthread_setspecific(thObjKey, NULL);
}

/*++
Function:
  TLSInitialize

  Initialize the TLS subsystem
--*/
BOOL TLSInitialize()
{
    // This will be called once and can't be called during/after another lock
    // in place due to PAL is not yet initialized. The underlying issue here is
    // related to whole lib/pal initialization on start.
    free_threads_spinlock.Reset();
    /* Create the pthread key for thread objects, which we use
       for fast access to the current thread object. */
    if (pthread_key_create(&thObjKey, InternalEndCurrentThreadWrapper))
    {
        ERROR("Couldn't create the thread object key\n");
        return FALSE;
    }

    return TRUE;
}

/*++
Function:
    AllocTHREAD

Abstract:
    Allocate CPalThread instance

Return:
    The fresh thread structure, NULL otherwise
--*/
CPalThread* AllocTHREAD()
{
    CPalThread* pThread = NULL;

    free_threads_spinlock.Enter();

    pThread = free_threads_list;
    if (pThread != NULL)
    {
        free_threads_list = pThread->GetNext();
    }

    free_threads_spinlock.Leave();

    if (pThread == NULL)
    {
        pThread = InternalNew<CPalThread>();
    }
    else
    {
        pThread = new (pThread) CPalThread;
    }

    return pThread;
}

/*++
Function:
    FreeTHREAD

Abstract:
    Free THREAD structure

--*/
static void FreeTHREAD(CPalThread *pThread)
{
    //
    // Run the destructors for this object
    //

    pThread->~CPalThread();

#ifdef _DEBUG
    // Fill value so we can find code re-using threads after they're dead. We
    // check against pThread->dwGuard when getting the current thread's data.
    memset((void*)pThread, 0xcc, sizeof(*pThread));
#endif

    // We SHOULD be doing the following, but it causes massive problems. See the
    // comment below.
    //pthread_setspecific(thObjKey, NULL); // Make sure any TLS entry is removed.

    //
    // Never actually free the THREAD structure to make the TLS lookaside cache work.
    // THREAD* for terminated thread can be stuck in the lookaside cache code for an
    // arbitrary amount of time. The unused THREAD* structures has to remain in a
    // valid memory and thus can't be returned to the heap.
    //
    // TODO: is this really true? Why would the entry remain in the cache for
    // an indefinite period of time after we've flushed it?
    //

    /* NOTE: can't use a CRITICAL_SECTION here: EnterCriticalSection(&cs,TRUE) and
       LeaveCriticalSection(&cs,TRUE) need to access the thread private data
       stored in the very THREAD structure that we just destroyed. Entering and
       leaving the critical section with internal==FALSE leads to possible hangs
       in the PROCSuspendOtherThreads logic, at shutdown time

       Update: [TODO] PROCSuspendOtherThreads has been removed. Can this
       code be changed?*/

    free_threads_spinlock.Enter();

    pThread->SetNext(free_threads_list);
    free_threads_list = pThread;

    free_threads_spinlock.Leave();
}


/*++
Function:
  GetThreadId

See MSDN doc.
--*/
DWORD
PALAPI
GetThreadId(
    HANDLE hThread
    // UNIXTODO Should take pThread parameter here (modify callers)
    )
{
    DWORD dwThreadId = 0;
    CPalThread *pThread;
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjThread = 0;

    // TODO: not sure if this could be done in a more efficient way.
    PERF_ENTRY(GetThreadId);
    ENTRY("GetThreadId()\n");

    pThread = InternalGetCurrentThread();

    palError = InternalGetThreadDataFromHandle(
        pThread,
        hThread,
        0,
        0,
        &pobjThread
        );

    if (NO_ERROR != palError)
    {
        dwThreadId = (DWORD)pThread->GetThreadId();
    }

    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pThread);
    }

    LOGEXIT("GetThreadId returns DWORD %#x\n", dwThreadId);
    PERF_EXIT(GetThreadId);

    return dwThreadId;
}

static THREAD_LOCAL DWORD cachedCurrentThreadId = 0;
/*++
Function:
  GetCurrentThreadId

See MSDN doc.
--*/
DWORD
PALAPI
GetCurrentThreadId(
            VOID)
{
    if (cachedCurrentThreadId != 0) return cachedCurrentThreadId;
    DWORD dwThreadId;

    PERF_ENTRY(GetCurrentThreadId);
    ENTRY("GetCurrentThreadId()\n");

    dwThreadId = (DWORD)THREADSilentGetCurrentThreadId();

    LOGEXIT("GetCurrentThreadId returns DWORD %#x\n", dwThreadId);
    PERF_EXIT(GetCurrentThreadId);

    cachedCurrentThreadId = dwThreadId;
    return dwThreadId;
}



/*++
Function:
  GetCurrentThread

See MSDN doc.
--*/
HANDLE
PALAPI
PAL_GetCurrentThread(
          VOID)
{
    PERF_ENTRY(GetCurrentThread);
    ENTRY("GetCurrentThread()\n");

    LOGEXIT("GetCurrentThread returns HANDLE %p\n", hPseudoCurrentThread);
    PERF_EXIT(GetCurrentThread);

    /* return a pseudo handle */
    return (HANDLE) hPseudoCurrentThread;
}

/*++
Function:
  SwitchToThread

See MSDN doc.
--*/
BOOL
PALAPI
SwitchToThread(
    VOID)
{
    BOOL ret;

    PERF_ENTRY(SwitchToThread);
    ENTRY("SwitchToThread(VOID)\n");

    /* sched_yield yields to another thread in the current process. This implementation
       won't work well for cross-process synchronization. */
    ret = (sched_yield() == 0);

    LOGEXIT("SwitchToThread returns BOOL %d\n", ret);
    PERF_EXIT(SwitchToThread);

    return ret;
}

/*++
Function:
  CreateThread

Note:
  lpThreadAttributes could be ignored.

See MSDN doc.

--*/
HANDLE
PALAPI
CreateThread(
    IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
    IN DWORD dwStackSize,
    IN LPTHREAD_START_ROUTINE lpStartAddress,
    IN LPVOID lpParameter,
    IN DWORD dwCreationFlags,
    OUT LPDWORD lpThreadId)
{
    PAL_ERROR palError;
    CPalThread *pThread;
    HANDLE hNewThread = NULL;

    PERF_ENTRY(CreateThread);
    ENTRY("CreateThread(lpThreadAttr=%p, dwStackSize=%u, lpStartAddress=%p, "
          "lpParameter=%p, dwFlags=%#x, lpThreadId=%#x)\n",
          lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter,
          dwCreationFlags, lpThreadId);

    pThread = InternalGetCurrentThread();

    palError = InternalCreateThread(
        pThread,
        lpThreadAttributes,
        dwStackSize,
        lpStartAddress,
        lpParameter,
        dwCreationFlags,
        UserCreatedThread,
        lpThreadId,
        &hNewThread
        );

    if (NO_ERROR != palError)
    {
        pThread->SetLastError(palError);
    }

    LOGEXIT("CreateThread returns HANDLE %p\n", hNewThread);
    PERF_EXIT(CreateThread);

    return hNewThread;
}

PAL_ERROR
CorUnix::InternalCreateThread(
    CPalThread *pThread,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    DWORD dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    PalThreadType eThreadType,
    LPDWORD lpThreadId,
    HANDLE *phThread
    )
{
    PAL_ERROR palError;
    CPalThread *pNewThread = NULL;
    CObjectAttributes oa;
    bool fAttributesInitialized = FALSE;
    bool fThreadDataAddedToProcessList = FALSE;
    HANDLE hNewThread = NULL;

    pthread_t pthread;
    pthread_attr_t pthreadAttr;
    size_t pthreadStackSize;
#if PTHREAD_CREATE_MODIFIES_ERRNO
    int storedErrno;
#endif  // PTHREAD_CREATE_MODIFIES_ERRNO
    BOOL fHoldingProcessLock = FALSE;
    int iError = 0;

    if (0 != terminator)
    {
        //
        // Since the PAL is in the middle of shutting down we don't want to
        // create any new threads (since it's possible for that new thread
        // to create another thread before the shutdown thread gets around
        // to suspending it, and so on). We don't want to return an error
        // here, though, as some programs (in particular, build) do not
        // handle CreateThread errors properly -- instead, we just put
        // the calling thread to sleep (unless it is the shutdown thread,
        // which could occur if a DllMain PROCESS_DETACH handler tried to
        // create a new thread for some odd reason).
        //

        ERROR("process is terminating, can't create new thread.\n");

        if (pThread->GetThreadId() != static_cast<DWORD>(terminator))
        {
            while (true)
            {
                poll(NULL, 0, INFTIM);
                sched_yield();
            }
        }
        else
        {
            //
            // This is the shutdown thread, so just return an error
            //

            palError = ERROR_PROCESS_ABORTED;
            goto EXIT;
        }
    }

    /* Validate parameters */

    if (lpThreadAttributes != NULL)
    {
        ASSERT("lpThreadAttributes parameter must be NULL (%p)\n",
               lpThreadAttributes);
        palError = ERROR_INVALID_PARAMETER;
        goto EXIT;
    }

    // Ignore the STACK_SIZE_PARAM_IS_A_RESERVATION flag
    dwCreationFlags &= ~STACK_SIZE_PARAM_IS_A_RESERVATION;

    if ((dwCreationFlags != 0) && (dwCreationFlags != CREATE_SUSPENDED))
    {
        ASSERT("dwCreationFlags parameter is invalid (%#x)\n", dwCreationFlags);
        palError = ERROR_INVALID_PARAMETER;
        goto EXIT;
    }

    //
    // Create the CPalThread for the thread
    //

    pNewThread = AllocTHREAD();
    if (NULL == pNewThread)
    {
        palError = ERROR_OUTOFMEMORY;
        goto EXIT;
    }

    palError = pNewThread->RunPreCreateInitializers();
    if (NO_ERROR != palError)
    {
        goto EXIT;
    }

    pNewThread->m_lpStartAddress = lpStartAddress;
    pNewThread->m_lpStartParameter = lpParameter;
    pNewThread->m_bCreateSuspended = (dwCreationFlags & CREATE_SUSPENDED) == CREATE_SUSPENDED;
    pNewThread->m_eThreadType = eThreadType;

    if (0 != pthread_attr_init(&pthreadAttr))
    {
        ERROR("couldn't initialize pthread attributes\n");
        palError = ERROR_INTERNAL_ERROR;
        goto EXIT;
    }

    fAttributesInitialized = TRUE;

    /* adjust the stack size if necessary */
    if (0 != pthread_attr_getstacksize(&pthreadAttr, &pthreadStackSize))
    {
        ERROR("couldn't set thread stack size\n");
        palError = ERROR_INTERNAL_ERROR;
        goto EXIT;
    }

    TRACE("default pthread stack size is %d, caller requested %d (default is %d)\n",
          pthreadStackSize, dwStackSize, CPalThread::s_dwDefaultThreadStackSize);

    if (0 == dwStackSize)
    {
        dwStackSize = CPalThread::s_dwDefaultThreadStackSize;
    }

    if (PTHREAD_STACK_MIN > pthreadStackSize)
    {
        WARN("default stack size is reported as %d, but PTHREAD_STACK_MIN is "
             "%d\n", pthreadStackSize, PTHREAD_STACK_MIN);
    }

    if (pthreadStackSize < dwStackSize)
    {
        TRACE("setting thread stack size to %d\n", dwStackSize);
        if (0 != pthread_attr_setstacksize(&pthreadAttr, dwStackSize))
        {
            ERROR("couldn't set pthread stack size to %d\n", dwStackSize);
            palError = ERROR_INTERNAL_ERROR;
            goto EXIT;
        }
    }
    else
    {
        TRACE("using the system default thread stack size of %d\n", pthreadStackSize);
    }

#if HAVE_THREAD_SELF || HAVE__LWP_SELF
    /* Create new threads as "bound", so each pthread is permanently bound
       to an LWP.  Get/SetThreadContext() depend on this 1:1 mapping. */
    pthread_attr_setscope(&pthreadAttr, PTHREAD_SCOPE_SYSTEM);
#endif // HAVE_THREAD_SELF || HAVE__LWP_SELF

    //
    // We never call pthread_join, so create the new thread as detached
    //

    iError = pthread_attr_setdetachstate(&pthreadAttr, PTHREAD_CREATE_DETACHED);
    _ASSERTE(0 == iError);

    //
    // Create the IPalObject for the thread and store it in the object
    //

    palError = CreateThreadObject(
        pThread,
        pNewThread,
        &hNewThread);

    if (NO_ERROR != palError)
    {
        goto EXIT;
    }

    //
    // Add the thread to the process list
    //

    //
    // We use the process lock to ensure that we're not interrupted
    // during the creation process. After adding the CPalThread reference
    // to the process list, we want to make sure the actual thread has been
    // started. Otherwise, there's a window where the thread can be found
    // in the process list but doesn't yet exist in the system.
    //

    PROCProcessLock();
    fHoldingProcessLock = TRUE;

    PROCAddThread(pThread, pNewThread);
    fThreadDataAddedToProcessList = TRUE;

    //
    // Spawn the new pthread
    //

#if PTHREAD_CREATE_MODIFIES_ERRNO
    storedErrno = errno;
#endif  // PTHREAD_CREATE_MODIFIES_ERRNO

#ifdef FEATURE_PAL_SXS
    _ASSERT_MSG(pNewThread->IsInPal(), "New threads we're about to spawn should always be in the PAL.\n");
#endif // FEATURE_PAL_SXS
    iError = pthread_create(&pthread, &pthreadAttr, CPalThread::ThreadEntry, pNewThread);

#if PTHREAD_CREATE_MODIFIES_ERRNO
    if (iError == 0)
    {
        // Restore errno if pthread_create succeeded.
        errno = storedErrno;
    }
#endif  // PTHREAD_CREATE_MODIFIES_ERRNO

    if (0 != iError)
    {
        ERROR("pthread_create failed, error is %d (%s)\n", iError, strerror(iError));
        palError = ERROR_NOT_ENOUGH_MEMORY;
        goto EXIT;
    }

    //
    // Wait for the new thread to finish its initial startup tasks
    // (i.e., the ones that might fail)
    //
    if (pNewThread->WaitForStartStatus())
    {
        //
        // Everything succeeded. Store the handle for the new thread and
        // the thread's ID in the out params
        //
        *phThread = hNewThread;

        if (NULL != lpThreadId)
        {
            *lpThreadId = pNewThread->GetThreadId();
        }
    }
    else
    {
        ERROR("error occurred in THREADEntry, thread creation failed.\n");
        palError = ERROR_INTERNAL_ERROR;
        goto EXIT;
    }

    //
    // If we're here, then we've locked the process list and both pthread_create
    // and WaitForStartStatus succeeded. Thus, we can now unlock the process list.
    // Since palError == NO_ERROR, we won't call this again in the exit block.
    //
    PROCProcessUnlock();
    fHoldingProcessLock = FALSE;

EXIT:

    if (fAttributesInitialized)
    {
        if (0 != pthread_attr_destroy(&pthreadAttr))
        {
            WARN("pthread_attr_destroy() failed\n");
        }
    }

    if (NO_ERROR != palError)
    {
        //
        // We either were not able to create the new thread, or a failure
        // occurred in the new thread's entry routine. Free up the associated
        // resources here
        //

        if (fThreadDataAddedToProcessList)
        {
            PROCRemoveThread(pThread, pNewThread);
        }
        //
        // Once we remove the thread from the process list, we can call
        // PROCProcessUnlock.
        //
        if (fHoldingProcessLock)
        {
            PROCProcessUnlock();
        }
        fHoldingProcessLock = FALSE;
    }

    _ASSERT_MSG(!fHoldingProcessLock, "Exiting InternalCreateThread while still holding the process critical section.\n");

    return palError;
}



/*++
Function:
  ExitThread

See MSDN doc.
--*/
PAL_NORETURN
VOID
PALAPI
ExitThread(
       IN DWORD dwExitCode)
{
    CPalThread *pThread;

    ENTRY("ExitThread(dwExitCode=%u)\n", dwExitCode);
    PERF_ENTRY_ONLY(ExitThread);

    pThread = InternalGetCurrentThread();

    /* store the exit code */
    pThread->SetExitCode(dwExitCode);

    /* pthread_exit runs TLS destructors and cleanup routines,
       possibly registered by foreign code.  The right thing
       to do here is to leave the PAL.  Our own TLS destructor
       re-enters us explicitly. */
    PAL_Leave(PAL_BoundaryTop);

    /* kill the thread (itself), resulting in a call to InternalEndCurrentThread */
    pthread_exit(NULL);

    ASSERT("pthread_exit should not return!\n");
    for (;;);
}

/*++
Function:
  GetExitCodeThread

See MSDN doc.
--*/
BOOL
PALAPI
GetExitCodeThread(
           IN HANDLE hThread,
           IN LPDWORD lpExitCode)
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pthrCurrent = NULL;
    CPalThread *pthrTarget = NULL;
    IPalObject *pobjThread = NULL;
    BOOL fExitCodeSet;

    PERF_ENTRY(GetExitCodeThread);
    ENTRY("GetExitCodeThread(hThread = %p, lpExitCode = %p)\n",
          hThread, lpExitCode);

    if (NULL == lpExitCode)
    {
        WARN("Got NULL lpExitCode\n");
        palError = ERROR_INVALID_PARAMETER;
        goto done;
    }

    pthrCurrent = InternalGetCurrentThread();
    palError = InternalGetThreadDataFromHandle(
        pthrCurrent,
        hThread,
        0,
        &pthrTarget,
        &pobjThread
        );

    pthrTarget->Lock(pthrCurrent);

    fExitCodeSet = pthrTarget->GetExitCode(lpExitCode);
    if (!fExitCodeSet)
    {
        if (TS_DONE == pthrTarget->synchronizationInfo.GetThreadState())
        {
#ifdef FEATURE_PAL_SXS
            // The thread exited without ever calling ExitThread.
            // It must have wandered in.
            *lpExitCode = 0;
#else // FEATURE_PAL_SXS
            ASSERT("exit code not set but thread is dead\n");
#endif // FEATURE_PAL_SXS
        }
        else
        {
            *lpExitCode = STILL_ACTIVE;
        }
    }

    pthrTarget->Unlock(pthrCurrent);

done:
    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pthrCurrent);
    }

    LOGEXIT("GetExitCodeThread returns BOOL %d\n", NO_ERROR == palError);
    PERF_EXIT(GetExitCodeThread);

    return NO_ERROR == palError;
}


/*++
Function:
  InternalEndCurrentThread

Does any necessary memory clean up, signals waiting threads, and then forces
the current thread to exit.
--*/

VOID
CorUnix::InternalEndCurrentThread(
    CPalThread *pThread
    )
{
    PAL_ERROR palError = NO_ERROR;
    ISynchStateController *pSynchStateController = NULL;

#ifdef PAL_PERF
    PERFDisableThreadProfile(UserCreatedThread != pThread->GetThreadType());
#endif

    //
    // Abandon any objects owned by this thread
    //

    palError = g_pSynchronizationManager->AbandonObjectsOwnedByThread(
        pThread,
        pThread
        );

    if (NO_ERROR != palError)
    {
        ERROR("Failure abandoning owned objects");
    }

    //
    // Need to synchronize setting the thread state to TS_DONE since
    // this is checked for in InternalSuspendThreadFromData.
    // TODO: Is this still needed after removing InternalSuspendThreadFromData?
    //

    pThread->suspensionInfo.AcquireSuspensionLock(pThread);
    IncrementEndingThreadCount();
    pThread->synchronizationInfo.SetThreadState(TS_DONE);
    pThread->suspensionInfo.ReleaseSuspensionLock(pThread);

    //
    // Mark the thread object as signaled
    //

    palError = pThread->GetThreadObject()->GetSynchStateController(
        pThread,
        &pSynchStateController
        );

    if (NO_ERROR == palError)
    {
        palError = pSynchStateController->SetSignalCount(1);
        if (NO_ERROR != palError)
        {
            ASSERT("Unable to mark thread object as signaled");
        }

        pSynchStateController->ReleaseController();
    }
    else
    {
        ASSERT("Unable to obtain state controller for thread");
    }

#ifndef FEATURE_PAL_SXS
    // If this is the last thread then delete the process' data,
    // but don't exit because the application hosting the PAL
    // might have its own threads.
    if (PROCGetNumberOfThreads() == 1)
    {
        TRACE("Last thread is exiting\n");
        DecrementEndingThreadCount();
        TerminateCurrentProcessNoExit(FALSE);
    }
    else
#endif // !FEATURE_PAL_SXS
    {
        /* Do this ONLY if we aren't the last thread -> otherwise
           it gets done by TerminateProcess->
           PROCCleanupProcess->PALShutdown->PAL_Terminate */

        //
        // Add a reference to the thread data before releasing the
        // thread object, so we can still use it
        //

        pThread->AddThreadReference();

        //
        // Release the reference to the IPalObject for this thread
        //

        pThread->GetThreadObject()->ReleaseReference(pThread);

        /* Remove thread for the thread list of the process
           (don't do if this is the last thread -> gets handled by
            TerminateProcess->PROCCleanupProcess->PROCTerminateOtherThreads) */

        PROCRemoveThread(pThread, pThread);

#ifdef FEATURE_PAL_SXS
        PAL_Leave(PAL_BoundaryTop);
#endif // FEATURE_PAL_SXS


        //
        // Now release our reference to the thread data. We cannot touch
        // it after this point
        //

        pThread->ReleaseThreadReference();
        DecrementEndingThreadCount();

    }
}

/*++
Function:
  GetThreadPriority

See MSDN doc.
--*/
int
PALAPI
GetThreadPriority(
          IN HANDLE hThread)
{
    CPalThread *pThread;
    PAL_ERROR palError;
    int iPriority = THREAD_PRIORITY_ERROR_RETURN;

    PERF_ENTRY(GetThreadPriority);
    ENTRY("GetThreadPriority(hThread=%p)\n", hThread);

    pThread = InternalGetCurrentThread();

    palError = InternalGetThreadPriority(
        pThread,
        hThread,
        &iPriority
        );

    if (NO_ERROR != palError)
    {
        pThread->SetLastError(palError);
    }

    LOGEXIT("GetThreadPriorityExit returns int %d\n", iPriority);
    PERF_EXIT(GetThreadPriority);

    return iPriority;
}

PAL_ERROR
CorUnix::InternalGetThreadPriority(
    CPalThread *pThread,
    HANDLE hThread,
    int *piPriority
    )
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pTargetThread;
    IPalObject *pobjThread = NULL;

    palError = InternalGetThreadDataFromHandle(
        pThread,
        hThread,
        0,  // THREAD_QUERY_INFORMATION
        &pTargetThread,
        &pobjThread
        );

    if (NO_ERROR != palError)
    {
        goto InternalGetThreadPriorityExit;
    }

    pTargetThread->Lock(pThread);

    *piPriority = pTargetThread->GetThreadPriority();

    pTargetThread->Unlock(pThread);

InternalGetThreadPriorityExit:

    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pThread);
    }

    return palError;
}


/*++
Function:
  SetThreadPriority

See MSDN doc.
--*/
BOOL
PALAPI
SetThreadPriority(
          IN HANDLE hThread,
          IN int nPriority)
{
    CPalThread *pThread;
    PAL_ERROR palError = NO_ERROR;

#if !defined(__IOS__) && !defined(__ANDROID__)
    PERF_ENTRY(SetThreadPriority);
    ENTRY("SetThreadPriority(hThread=%p, nPriority=%#x)\n", hThread, nPriority);

    pThread = InternalGetCurrentThread();

    palError = InternalSetThreadPriority(
        pThread,
        hThread,
        nPriority
        );

    if (NO_ERROR != palError)
    {
        pThread->SetLastError(palError);
    }

    LOGEXIT("SetThreadPriority returns BOOL %d\n", NO_ERROR == palError);
    PERF_EXIT(SetThreadPriority);
#endif
    return NO_ERROR == palError;
}

PAL_ERROR
CorUnix::InternalSetThreadPriority(
    CPalThread *pThread,
    HANDLE hTargetThread,
    int iNewPriority
    )
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pTargetThread = NULL;
    IPalObject *pobjThread = NULL;

    int policy;
    int set_sched_error;
    struct sched_param schedParam;
    int max_priority;
    int min_priority;
    float posix_priority;


    palError = InternalGetThreadDataFromHandle(
        pThread,
        hTargetThread,
        0, // THREAD_SET_INFORMATION
        &pTargetThread,
        &pobjThread
        );

    if (NO_ERROR != palError)
    {
        goto InternalSetThreadPriorityExit;
    }

    pTargetThread->Lock(pThread);

    /* validate the requested priority */
    switch (iNewPriority)
    {
    case THREAD_PRIORITY_TIME_CRITICAL: /* fall through */
    case THREAD_PRIORITY_IDLE:
        break;

    case THREAD_PRIORITY_HIGHEST:       /* fall through */
    case THREAD_PRIORITY_ABOVE_NORMAL:  /* fall through */
    case THREAD_PRIORITY_NORMAL:        /* fall through */
    case THREAD_PRIORITY_BELOW_NORMAL:  /* fall through */
    case THREAD_PRIORITY_LOWEST:
        break;

    default:
        ASSERT("Priority %d not supported\n", iNewPriority);
        palError = ERROR_INVALID_PARAMETER;
        goto InternalSetThreadPriorityExit;
    }

    /* check if the thread is still running */
    if (TS_DONE == pTargetThread->synchronizationInfo.GetThreadState())
    {
        /* the thread has exited, set the priority in the thread structure
           and exit */
        pTargetThread->m_iThreadPriority = iNewPriority;
        goto InternalSetThreadPriorityExit;
    }

    /* get the previous thread schedule parameters.  We need to know the
       scheduling policy to determine the priority range */
    if (pthread_getschedparam(
            pTargetThread->GetPThreadSelf(),
            &policy,
            &schedParam
            ) != 0)
    {
        // permission denied ? do not assert
        TRACE("Unable to get current thread scheduling information\n");
        palError = ERROR_INTERNAL_ERROR;
        goto InternalSetThreadPriorityExit;
    }

#if HAVE_SCHED_GET_PRIORITY
    max_priority = sched_get_priority_max(policy);
    min_priority = sched_get_priority_min(policy);
    if( -1 == max_priority || -1 == min_priority)
    {
        ASSERT("sched_get_priority_min/max failed; error is %d (%s)\n",
               errno, strerror(errno));
        palError = ERROR_INTERNAL_ERROR;
        goto InternalSetThreadPriorityExit;
    }
#else
    max_priority = PAL_THREAD_PRIORITY_MAX;
    min_priority = PAL_THREAD_PRIORITY_MIN;
#endif

    TRACE("Pthread priorities for policy %d must be in the range %d to %d\n",
          policy, min_priority, max_priority);

    /* explanation for fancy maths below :
       POSIX doesn't specify the range of thread priorities that can be used
       with pthread_setschedparam. Instead, one must use sched_get_priority_min
       and sched_get_priority_max to obtain the lower and upper bounds of this
       range. Since the PAL also uses a range of values (from Idle [-15] to
       Time Critical [+15]), we have to do a mapping from a known range to an
       unknown (at compilation) range.
       We do this by :
       -substracting the minimal PAL priority from the desired priority. this
        gives a value between 0 and the PAL priority range
       -dividing this value by the PAL priority range. this allows us to
        express the desired priority as a floating-point value between 0 and 1
       -multiplying this value by the PTHREAD priority range. This gives a
        value between 0 and the PTHREAD priority range
       -adding the minimal PTHREAD priority range. This will give us a value
        between the minimal and maximla pthread priority, which should be
        equivalent to the original PAL value.

        example : suppose a pthread range 100 to 200, and a desired priority
                  of 0 (halfway between PAL minimum and maximum)
            0 - (IDLE [-15]) = 15
            15 / (TIMECRITICAL[15] - IDLE[-15]) = 0.5
            0.5 * (pthreadmax[200]-pthreadmin[100]) = 50
            50 + pthreadmin[100] = 150 -> halfway between pthread min and max
    */
    posix_priority =  (iNewPriority - THREAD_PRIORITY_IDLE);
    posix_priority /= (THREAD_PRIORITY_TIME_CRITICAL - THREAD_PRIORITY_IDLE);
    posix_priority *= (max_priority-min_priority);
    posix_priority += min_priority;

    schedParam.sched_priority = (int)posix_priority;

    TRACE("PAL priority %d is mapped to pthread priority %d\n",
          iNewPriority, schedParam.sched_priority);

    /* Finally, set the new priority into place */
    set_sched_error = pthread_setschedparam(
          pTargetThread->GetPThreadSelf(),
          policy,
          &schedParam
          );
    if (set_sched_error != 0)
    {
        if (EPERM == set_sched_error)
        {
            TRACE("Caller does not have OS privileges to call pthread_setschedparam\n");
            pTargetThread->m_iThreadPriority = iNewPriority;
            goto InternalSetThreadPriorityExit;
        }

        // do not assert fail here. OS may not update the priority...
        TRACE("Unable to set thread priority (error %d)\n", set_sched_error);
        if (ESRCH == set_sched_error) goto InternalSetThreadPriorityExit;

        palError = ERROR_INTERNAL_ERROR;
        goto InternalSetThreadPriorityExit;
    }

    pTargetThread->m_iThreadPriority = iNewPriority;

InternalSetThreadPriorityExit:

    if (NULL != pTargetThread)
    {
        pTargetThread->Unlock(pThread);
    }

    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pThread);
    }

    return palError;
}

BOOL
CorUnix::GetThreadTimesInternal(
    IN HANDLE hThread,
    OUT LPFILETIME lpKernelTime,
    OUT LPFILETIME lpUserTime)
{
    __int64 calcTime;
    BOOL retval = FALSE;
    const __int64 SECS_TO_NS = 1000000000; /* 10^9 */

#if HAVE_MACH_THREADS
    thread_basic_info resUsage;
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pthrCurrent = NULL;
    CPalThread *pthrTarget = NULL;
    IPalObject *pobjThread = NULL;
    mach_msg_type_number_t resUsage_count = THREAD_BASIC_INFO_COUNT;

    const __int64 USECS_TO_NS = 1000;      /* 10^3 */

    pthrCurrent = InternalGetCurrentThread();
    palError = InternalGetThreadDataFromHandle(
        pthrCurrent,
        hThread,
        0,
        &pthrTarget,
        &pobjThread
        );

    if (palError != NO_ERROR)
    {
        ASSERT("Unable to get thread data from handle %p"
              "thread\n", hThread);
        SetLastError(ERROR_INTERNAL_ERROR);
        goto SetTimesToZero;
    }

    pthrTarget->Lock(pthrCurrent);

    mach_port_t mhThread;
    mhThread = pthread_mach_thread_np(pthrTarget->GetPThreadSelf());

    kern_return_t status;
    status = thread_info(
        mhThread,
        THREAD_BASIC_INFO,
        (thread_info_t)&resUsage,
        &resUsage_count);

    pthrTarget->Unlock(pthrCurrent);

    if (status != KERN_SUCCESS)
    {
        ASSERT("Unable to get resource usage information for the current "
              "thread\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        goto SetTimesToZero;
    }

    /* Get the time of user mode execution, in nanoseconds */
    calcTime = (__int64)resUsage.user_time.seconds * SECS_TO_NS;
    calcTime += (__int64)resUsage.user_time.microseconds * USECS_TO_NS;
    /* Assign the time into lpUserTime */
    lpUserTime->dwLowDateTime = (DWORD)calcTime;
    lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32);

    /* Get the time of kernel mode execution, in nanoseconds */
    calcTime = (__int64)resUsage.system_time.seconds * SECS_TO_NS;
    calcTime += (__int64)resUsage.system_time.microseconds * USECS_TO_NS;
    /* Assign the time into lpKernelTime */
    lpKernelTime->dwLowDateTime = (DWORD)calcTime;
    lpKernelTime->dwHighDateTime = (DWORD)(calcTime >> 32);

    retval = TRUE;

    goto GetThreadTimesInternalExit;

#else //HAVE_MACH_THREADS

    PAL_ERROR palError;
    CPalThread *pThread;
    CPalThread *pTargetThread;
    IPalObject *pobjThread = NULL;
    clockid_t cid;

    pThread = InternalGetCurrentThread();

    palError = InternalGetThreadDataFromHandle(
        pThread,
        hThread,
        0, // THREAD_GET_CONTEXT
        &pTargetThread,
        &pobjThread
        );
    if (palError != NO_ERROR)
    {
        ASSERT("Unable to get thread data from handle %p"
              "thread\n", hThread);
        SetLastError(ERROR_INTERNAL_ERROR);
        goto SetTimesToZero;
    }

    pTargetThread->Lock(pThread);

    if (pthread_getcpuclockid(pTargetThread->GetPThreadSelf(), &cid) != 0)
    {
        ASSERT("Unable to get clock from thread\n", hThread);
        SetLastError(ERROR_INTERNAL_ERROR);
        pTargetThread->Unlock(pThread);
        goto SetTimesToZero;
    }

    struct timespec ts;
    if (clock_gettime(cid, &ts) != 0)
    {
        ASSERT("clock_gettime() failed; errno is %d (%s)\n", errno, strerror(errno));
        SetLastError(ERROR_INTERNAL_ERROR);
        pTargetThread->Unlock(pThread);
        goto SetTimesToZero;
    }

    pTargetThread->Unlock(pThread);

    /* Calculate time in nanoseconds and assign to user time */
    calcTime = (__int64) ts.tv_sec * SECS_TO_NS;
    calcTime += (__int64) ts.tv_nsec;
    lpUserTime->dwLowDateTime = (DWORD)calcTime;
    lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32);

    /* Set kernel time to zero, for now */
    lpKernelTime->dwLowDateTime = 0;
    lpKernelTime->dwHighDateTime = 0;

    retval = TRUE;
    goto GetThreadTimesInternalExit;

#endif //HAVE_MACH_THREADS

SetTimesToZero:

    lpUserTime->dwLowDateTime = 0;
    lpUserTime->dwHighDateTime = 0;
    lpKernelTime->dwLowDateTime = 0;
    lpKernelTime->dwHighDateTime = 0;
    goto GetThreadTimesInternalExit;

GetThreadTimesInternalExit:
    return retval;
}

/*++
Function:
  GetThreadTimes

See MSDN doc.
--*/
BOOL
PALAPI
GetThreadTimes(
        IN HANDLE hThread,
        OUT LPFILETIME lpCreationTime,
        OUT LPFILETIME lpExitTime,
        OUT LPFILETIME lpKernelTime,
        OUT LPFILETIME lpUserTime)
{
    PERF_ENTRY(GetThreadTimes);
    ENTRY("GetThreadTimes(hThread=%p, lpExitTime=%p, lpKernelTime=%p,"
          "lpUserTime=%p)\n",
          hThread, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime );

    FILETIME KernelTime, UserTime;

    BOOL retval = GetThreadTimesInternal(hThread, &KernelTime, &UserTime);

    /* Not sure if this still needs to be here */
    /*
    TRACE ("thread_info User: %ld sec,%ld microsec. Kernel: %ld sec,%ld"
           " microsec\n",
           resUsage.user_time.seconds, resUsage.user_time.microseconds,
           resUsage.system_time.seconds, resUsage.system_time.microseconds);
    */

    __int64 calcTime;
    if (lpUserTime)
    {
        /* Produce the time in 100s of ns */
        calcTime = ((ULONG64)UserTime.dwHighDateTime << 32);
        calcTime += (ULONG64)UserTime.dwLowDateTime;
        calcTime /= 100;
        lpUserTime->dwLowDateTime = (DWORD)calcTime;
        lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32);
    }
    if (lpKernelTime)
    {
        /* Produce the time in 100s of ns */
        calcTime = ((ULONG64)KernelTime.dwHighDateTime << 32);
        calcTime += (ULONG64)KernelTime.dwLowDateTime;
        calcTime /= 100;
        lpKernelTime->dwLowDateTime = (DWORD)calcTime;
        lpKernelTime->dwHighDateTime = (DWORD)(calcTime >> 32);
    }
    //Set CreationTime and Exit time to zero for now - maybe change this later?
    if (lpCreationTime)
    {
        lpCreationTime->dwLowDateTime = 0;
        lpCreationTime->dwHighDateTime = 0;
    }

    if (lpExitTime)
    {
        lpExitTime->dwLowDateTime = 0;
        lpExitTime->dwHighDateTime = 0;
    }

    LOGEXIT("GetThreadTimes returns BOOL %d\n", retval);
    PERF_EXIT(GetThreadTimes);
    return (retval);
}



void *
CPalThread::ThreadEntry(
    void *pvParam
    )
{
    PAL_ERROR palError;
    CPalThread *pThread;
    PTHREAD_START_ROUTINE pfnStartRoutine;
    LPVOID pvPar;
    DWORD retValue;

    pThread = reinterpret_cast<CPalThread*>(pvParam);

    if (NULL == pThread)
    {
        ASSERT("THREAD pointer is NULL!\n");
        goto fail;
    }

#if defined(FEATURE_PAL_SXS) && defined(_DEBUG)
    // We cannot assert yet, as we haven't set in this thread into the TLS, and so __ASSERT_ENTER
    // will fail if the assert fails and we'll crash.
    //_ASSERT_MSG(pThread->m_fInPal == 1, "New threads should always be in the PAL upon ThreadEntry.\n");
    if (g_Dbg_asserts_enabled && pThread->m_fInPal != 1)
        DebugBreak();
#endif // FEATURE_PAL_SXS && _DEBUG

    pThread->m_threadId = THREADSilentGetCurrentThreadId();
    pThread->m_pthreadSelf = pthread_self();
#if HAVE_MACH_THREADS
    pThread->m_machPortSelf = pthread_mach_thread_np(pThread->m_pthreadSelf);
#endif
#if HAVE_THREAD_SELF
    pThread->m_dwLwpId = (DWORD) thread_self();
#elif HAVE__LWP_SELF
    pThread->m_dwLwpId = (DWORD) _lwp_self();
#else
    pThread->m_dwLwpId = 0;
#endif

    palError = pThread->RunPostCreateInitializers();
    if (NO_ERROR != palError)
    {
        ASSERT("Error %i initializing thread data (post creation)\n", palError);
        goto fail;
    }

    // Check if the thread should be started suspended.
    if (pThread->GetCreateSuspended())
    {
        palError = pThread->suspensionInfo.InternalSuspendNewThreadFromData(pThread);
        if (NO_ERROR != palError)
        {
            ASSERT("Error %i attempting to suspend new thread\n", palError);
            goto fail;
        }

        //
        // We need to run any APCs that have already been queued for
        // this thread.
        //

        (void) g_pSynchronizationManager->DispatchPendingAPCs(pThread);
    }
    else
    {
        //
        // All startup operations that might have failed have succeeded,
        // so thread creation is successful. Let CreateThread return.
        //

        pThread->SetStartStatus(TRUE);
    }

    pThread->synchronizationInfo.SetThreadState(TS_RUNNING);

    if (UserCreatedThread == pThread->GetThreadType())
    {
        /* Inform all loaded modules that a thread has been created */
        /* note : no need to take a critical section to serialize here; the loader
           will take the module critical section */
        LOADCallDllMain(DLL_THREAD_ATTACH, NULL);
    }

#ifdef PAL_PERF
    PERFAllocThreadInfo();
    PERFEnableThreadProfile(UserCreatedThread != pThread->GetThreadType());
#endif

    /* call the startup routine */
    pfnStartRoutine = pThread->GetStartAddress();
    pvPar = pThread->GetStartParameter();

    retValue = (*pfnStartRoutine)(pvPar);

    TRACE("Thread exited (%u)\n", retValue);
    ExitThread(retValue);

    /* Note: never get here */
    ASSERT("ExitThread failed!\n");
    for (;;);

fail:

    //
    // Notify InternalCreateThread that a failure occurred
    //

    if (NULL != pThread)
    {
        pThread->synchronizationInfo.SetThreadState(TS_FAILED);
        pThread->SetStartStatus(FALSE);
    }

    /* do not call ExitThread : we don't want to call DllMain(), and the thread
       isn't in a clean state (e.g. lpThread isn't in TLS). the cleanup work
       above should release all resources */
    return NULL;
}


#define PAL_THREAD_DEFAULT_STACK_SIZE "PAL_THREAD_DEFAULT_STACK_SIZE"

PAL_ERROR
CorUnix::InitializeGlobalThreadData(
    void
    )
{
    PAL_ERROR palError = NO_ERROR;
    char *pszStackSize = NULL;

    //
    // Read in the environment to see whether we need to change the default
    // thread stack size.
    //
    pszStackSize = MiscGetenv(PAL_THREAD_DEFAULT_STACK_SIZE);
    if (NULL != pszStackSize)
    {
        // Environment variable exists
        char *pszEnd;
        DWORD dw = PAL_strtoul(pszStackSize, &pszEnd, 16); // treat it as hex
        if ( (pszStackSize != pszEnd) && (0 != dw) )
        {
            CPalThread::s_dwDefaultThreadStackSize = dw;
        }
    }

    return palError;
}


/*++
Function:
    CreateThreadData

Abstract:
    Create the CPalThread for the startup thread
    or another external thread entering the PAL
    for the first time

Parameters:
    ppThread - on success, receives the CPalThread

Return:
   PAL_ERROR
--*/

PAL_ERROR
CorUnix::CreateThreadData(
    CPalThread **ppThread
    )
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pThread = NULL;

    /* Create the thread object */
    pThread = AllocTHREAD();

    if (NULL == pThread)
    {
       palError = ERROR_OUTOFMEMORY;
       goto CreateThreadDataExit;
    }

    palError = pThread->RunPreCreateInitializers();

    if (NO_ERROR != palError)
    {
        goto CreateThreadDataExit;
    }

    pThread->SetLastError(0);

    pThread->m_threadId = THREADSilentGetCurrentThreadId();
    pThread->m_pthreadSelf = pthread_self();
#if HAVE_MACH_THREADS
    pThread->m_machPortSelf = pthread_mach_thread_np(pThread->m_pthreadSelf);
#endif
#if HAVE_THREAD_SELF
    pThread->m_dwLwpId = (DWORD) thread_self();
#elif HAVE__LWP_SELF
    pThread->m_dwLwpId = (DWORD) _lwp_self();
#else
    pThread->m_dwLwpId = 0;
#endif

    palError = pThread->RunPostCreateInitializers();
    if (NO_ERROR != palError)
    {
        goto CreateThreadDataExit;
    }

    *ppThread = pThread;

CreateThreadDataExit:

    if (NO_ERROR != palError)
    {
        if (NULL != pThread)
        {
            pThread->ReleaseThreadReference();
        }
    }

    return palError;
}

/*++
Function:
    CreateThreadData

Abstract:
    Creates the IPalObject for a thread, storing
    the reference in the CPalThread

Parameters:
    pThread - the thread data for the creating thread
    pNewThread - the thread data for the thread being initialized

Return:
   PAL_ERROR
--*/

PAL_ERROR
CorUnix::CreateThreadObject(
    CPalThread *pThread,
    CPalThread *pNewThread,
    HANDLE *phThread
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjThread = NULL;
    IDataLock *pDataLock;
    HANDLE hThread = NULL;
    CThreadProcessLocalData *pLocalData = NULL;
    CObjectAttributes oa;
    BOOL fThreadDataStoredInObject = FALSE;
    IPalObject *pobjRegisteredThread = NULL;

    //
    // Create the IPalObject for the thread
    //

    palError = g_pObjectManager->AllocateObject(
        pThread,
        &otThread,
        &oa,
        &pobjThread
        );

    if (NO_ERROR != palError)
    {
        goto CreateThreadObjectExit;
    }

    //
    // Store the CPalThread inside of the IPalObject
    //

    palError = pobjThread->GetProcessLocalData(
        pThread,
        WriteLock,
        &pDataLock,
        reinterpret_cast<void **>(&pLocalData)
        );

    if (NO_ERROR != palError)
    {
        goto CreateThreadObjectExit;
    }

    pLocalData->pThread = pNewThread;
    pDataLock->ReleaseLock(pThread, TRUE);
    fThreadDataStoredInObject = TRUE;

    //
    // Register the IPalObject (obtaining a handle)
    //

    palError = g_pObjectManager->RegisterObject(
        pThread,
        pobjThread,
        &aotThread,
        0, //THREAD_ALL_ACCESS,
        &hThread,
        &pobjRegisteredThread
        );

    //
    // pobjThread is invalidated by the call to RegisterObject, so NULL
    // it out here to prevent it from being released
    //

    pobjThread = NULL;

    if (NO_ERROR != palError)
    {
        goto CreateThreadObjectExit;
    }

    //
    // Store the registered object inside of the thread object,
    // adding a reference for the thread itself
    //

    pNewThread->m_pThreadObject = pobjRegisteredThread;
    pNewThread->m_pThreadObject->AddReference();

    *phThread = hThread;

CreateThreadObjectExit:

    if (NO_ERROR != palError)
    {
        if (NULL != hThread)
        {
            g_pObjectManager->RevokeHandle(pThread, hThread);
        }

        if (NULL != pNewThread->m_pThreadObject)
        {
            //
            // Release the new thread's reference on the underlying thread
            // object
            //

            pNewThread->m_pThreadObject->ReleaseReference(pThread);
        }

        if (!fThreadDataStoredInObject)
        {
            //
            // The CPalThread for the new thread was never stored in
            // an IPalObject instance, so we need to release the initial
            // reference here. (If it has been stored it will get freed in
            // the owning object's cleanup routine)
            //

            pNewThread->ReleaseThreadReference();
        }
    }

    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pThread);
    }

    if (NULL != pobjRegisteredThread)
    {
        pobjRegisteredThread->ReleaseReference(pThread);
    }

    return palError;
}

PAL_ERROR
CorUnix::InternalCreateDummyThread(
    CPalThread *pThread,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    CPalThread **ppDummyThread,
    HANDLE *phThread
    )
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pDummyThread = NULL;
    IPalObject *pobjThread = NULL;
    IPalObject *pobjThreadRegistered = NULL;
    IDataLock *pDataLock;
    CThreadProcessLocalData *pLocalData;
    CObjectAttributes oa(NULL, lpThreadAttributes);
    bool fThreadDataStoredInObject = FALSE;

    pDummyThread = AllocTHREAD();
    if (NULL == pDummyThread)
    {
        palError = ERROR_OUTOFMEMORY;
        goto InternalCreateDummyThreadExit;
    }

    pDummyThread->m_fIsDummy = TRUE;

    palError = g_pObjectManager->AllocateObject(
        pThread,
        &otThread,
        &oa,
        &pobjThread
        );

    if (NO_ERROR != palError)
    {
        goto InternalCreateDummyThreadExit;
    }

    palError = pobjThread->GetProcessLocalData(
        pThread,
        WriteLock,
        &pDataLock,
        reinterpret_cast<void **>(&pLocalData)
        );

    if (NO_ERROR != palError)
    {
        goto InternalCreateDummyThreadExit;
    }

    pLocalData->pThread = pDummyThread;
    pDataLock->ReleaseLock(pThread, TRUE);
    fThreadDataStoredInObject = TRUE;

    palError = g_pObjectManager->RegisterObject(
        pThread,
        pobjThread,
        &aotThread,
        0, // THREAD_ALL_ACCESS
        phThread,
        &pobjThreadRegistered
        );

    //
    // pobjThread is invalidated by the above call, so NULL
    // it out here
    //

    pobjThread = NULL;

    if (NO_ERROR != palError)
    {
        goto InternalCreateDummyThreadExit;
    }

    //
    // Note the we do NOT store the registered object for the
    // thread w/in pDummyThread. Since this thread is not actually
    // executing that reference would never be released (and thus
    // the thread object would never be cleaned up...)
    //

    *ppDummyThread = pDummyThread;

InternalCreateDummyThreadExit:

    if (NULL != pobjThreadRegistered)
    {
        pobjThreadRegistered->ReleaseReference(pThread);
    }

    if (NULL != pobjThread)
    {
        pobjThread->ReleaseReference(pThread);
    }

    if (NO_ERROR != palError
        && NULL != pDummyThread
        && !fThreadDataStoredInObject)
    {
        pDummyThread->ReleaseThreadReference();
    }

    return palError;
}

PAL_ERROR
CorUnix::InternalGetThreadDataFromHandle(
    CPalThread *pThread,
    HANDLE hThread,
    DWORD dwRightsRequired,
    CPalThread **ppTargetThread,
    IPalObject **ppobjThread
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobj;
    IDataLock *pLock;
    CThreadProcessLocalData *pData;

    *ppobjThread = NULL;

    if (hPseudoCurrentThread == hThread)
    {
        *ppTargetThread = pThread;
    }
    else
    {
        palError = g_pObjectManager->ReferenceObjectByHandle(
            pThread,
            hThread,
            &aotThread,
            dwRightsRequired,
            &pobj
            );

        if (NO_ERROR == palError)
        {
            palError = pobj->GetProcessLocalData(
                pThread,
                ReadLock,
                &pLock,
                reinterpret_cast<void**>(&pData)
                );

            if (NO_ERROR == palError)
            {
                *ppTargetThread = pData->pThread;
                pLock->ReleaseLock(pThread, FALSE);

                //
                // Transfer object reference to out param
                //

                *ppobjThread = pobj;
            }
            else
            {
                pobj->ReleaseReference(pThread);
            }
        }
    }

    return palError;
}

PAL_ERROR
CPalThread::RunPreCreateInitializers(
    void
    )
{
    PAL_ERROR palError = NO_ERROR;
    int iError;

    //
    // First, perform initialization of CPalThread private members
    //

    InternalInitializeCriticalSection(&m_csLock);
    m_fLockInitialized = TRUE;

    iError = pthread_mutex_init(&m_startMutex, NULL);
    if (0 != iError)
    {
        goto RunPreCreateInitializersExit;
    }

    iError = pthread_cond_init(&m_startCond, NULL);
    if (0 != iError)
    {
        pthread_mutex_destroy(&m_startMutex);
        goto RunPreCreateInitializersExit;
    }

    m_fStartItemsInitialized = TRUE;

    //
    // Call the pre-create initializers for embedded classes
    //

    palError = synchronizationInfo.InitializePreCreate();
    if (NO_ERROR != palError)
    {
        goto RunPreCreateInitializersExit;
    }

    palError = suspensionInfo.InitializePreCreate();
    if (NO_ERROR != palError)
    {
        goto RunPreCreateInitializersExit;
    }

    palError = sehInfo.InitializePreCreate();
    if (NO_ERROR != palError)
    {
        goto RunPreCreateInitializersExit;
    }

    palError = apcInfo.InitializePreCreate();
    if (NO_ERROR != palError)
    {
        goto RunPreCreateInitializersExit;
    }

    palError = crtInfo.InitializePreCreate();
    if (NO_ERROR != palError)
    {
        goto RunPreCreateInitializersExit;
    }

RunPreCreateInitializersExit:

    return palError;
}

CPalThread::~CPalThread()
{
    // @UNIXTODO: This is our last chance to unlink our Mach exception handler from the pseudo-chain we're trying
    // to maintain. Unfortunately we don't have enough data or control to do this at all well (and we can't
    // guarantee that another component hasn't chained to us, about which we can do nothing). If the kernel or
    // another component forwards an exception notification to us for this thread things will go badly (we'll
    // terminate the process when trying to look up this CPalThread in order to find forwarding information).
    // On the flip side I don't believe we'll get here currently unless the thread has been terminated (in
    // which case it's not an issue). If we start supporting unload or early disposal of CPalThread objects
    // (say when we return from an outer reverse p/invoke) then we'll need to revisit this. But hopefully by
    // then we'll have an alternative design for handling hardware exceptions.

    if (m_fLockInitialized)
    {
        InternalDeleteCriticalSection(&m_csLock);
    }

    if (m_fStartItemsInitialized)
    {
        int iError;

        iError = pthread_cond_destroy(&m_startCond);
        _ASSERTE(0 == iError);

        iError = pthread_mutex_destroy(&m_startMutex);
        _ASSERTE(0 == iError);
    }
}

void
CPalThread::AddThreadReference(
    void
    )
{
    InterlockedIncrement(&m_lRefCount);
}

void
CPalThread::ReleaseThreadReference(
    void
    )
{
    LONG lRefCount = InterlockedDecrement(&m_lRefCount);
    _ASSERT_MSG(lRefCount >= 0, "Released a thread and ended with a negative refcount (%ld)\n", lRefCount);
    if (0 == lRefCount)
    {
        FreeTHREAD(this);
    }

}

static
PAL_ERROR
InitializePostCreate(
    CPalThread *pThread,
    SIZE_T threadId,
    DWORD dwLwpId
    )
{
    PAL_ERROR palError = NO_ERROR;

    if (pthread_setspecific(thObjKey, reinterpret_cast<void*>(pThread)))
    {
        ASSERT("Unable to set the thread object key's value\n");
        palError = ERROR_INTERNAL_ERROR;
    }

    return palError;
}

PAL_ERROR
CPalThread::RunPostCreateInitializers(
    void
    )
{
    PAL_ERROR palError = NO_ERROR;

    //
    // Call the post-create initializers for embedded classes
    //

    palError = synchronizationInfo.InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

    palError = suspensionInfo.InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

    palError = sehInfo.InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

    palError = InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

    palError = apcInfo.InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

    palError = crtInfo.InitializePostCreate(this, m_threadId, m_dwLwpId);
    if (NO_ERROR != palError)
    {
        goto RunPostCreateInitializersExit;
    }

RunPostCreateInitializersExit:

    return palError;
}

void
CPalThread::SetStartStatus(
    bool fStartSucceeded
    )
{
    int iError;

#if _DEBUG
    if (m_fStartStatusSet)
    {
        ASSERT("Multiple calls to CPalThread::SetStartStatus\n");
    }
#endif

    //
    // This routine may get called from CPalThread::ThreadEntry
    //
    // If we've reached this point there are no further thread
    // suspensions that happen at creation time, so reset
    // m_bCreateSuspended
    //

    m_bCreateSuspended = FALSE;

    iError = pthread_mutex_lock(&m_startMutex);
    if (0 != iError)
    {
        ASSERT("pthread primitive failure\n");
        // bugcheck?
    }

    m_fStartStatus = fStartSucceeded;
    m_fStartStatusSet = TRUE;

    iError = pthread_cond_signal(&m_startCond);
    if (0 != iError)
    {
        ASSERT("pthread primitive failure\n");
        // bugcheck?
    }

    iError = pthread_mutex_unlock(&m_startMutex);
    if (0 != iError)
    {
        ASSERT("pthread primitive failure\n");
        // bugcheck?
    }
}

bool
CPalThread::WaitForStartStatus(
    void
    )
{
    int iError;

    iError = pthread_mutex_lock(&m_startMutex);
    if (0 != iError)
    {
        ASSERT("pthread primitive failure\n");
        // bugcheck?
    }

    while (!m_fStartStatusSet)
    {
        iError = pthread_cond_wait(&m_startCond, &m_startMutex);
        if (0 != iError)
        {
            ASSERT("pthread primitive failure\n");
            // bugcheck?
        }
    }

    iError = pthread_mutex_unlock(&m_startMutex);
    if (0 != iError)
    {
        ASSERT("pthread primitive failure\n");
        // bugcheck?
    }

    return m_fStartStatus;
}

/* IncrementEndingThreadCount and DecrementEndingThreadCount are used
to control a global counter that indicates if any threads are about to die.
Once a thread's state is set to TS_DONE, it cannot be suspended. However,
the dying thread can still access PAL resources, which is dangerous if the
thread dies during PAL cleanup. To avoid this, the shutdown thread calls
WaitForEndingThreads after suspending all other threads. WaitForEndingThreads
uses a condition variable along with the global counter to wait for remaining
PAL threads to die before proceeding with cleanup. As threads die, they
decrement the counter and signal the condition variable. */

void
IncrementEndingThreadCount(
    void
    )
{
    int iError;

    iError = pthread_mutex_lock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_lock returned %d\n", iError);

    iEndingThreads++;

    iError = pthread_mutex_unlock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_unlock returned %d\n", iError);
}

void
DecrementEndingThreadCount(
    void
    )
{
    int iError;

    iError = pthread_mutex_lock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_lock returned %d\n", iError);

    iEndingThreads--;
    _ASSERTE(iEndingThreads >= 0);

    if (iEndingThreads == 0)
    {
        iError = pthread_cond_signal(&ptcEndThread);
        _ASSERT_MSG(iError == 0, "pthread_cond_signal returned %d\n", iError);
    }

    iError = pthread_mutex_unlock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_unlock returned %d\n", iError);
}

void
WaitForEndingThreads(
    void
    )
{
    int iError;

    iError = pthread_mutex_lock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_lock returned %d\n", iError);

    while (iEndingThreads > 0)
    {
        iError = pthread_cond_wait(&ptcEndThread, &ptmEndThread);
        _ASSERT_MSG(iError == 0, "pthread_cond_wait returned %d\n", iError);
    }

    iError = pthread_mutex_unlock(&ptmEndThread);
    _ASSERT_MSG(iError == 0, "pthread_mutex_unlock returned %d\n", iError);
}

PAL_ERROR
CorUnix::InitializeEndingThreadsData(
    void
    )
{
    PAL_ERROR palError = ERROR_INTERNAL_ERROR;
    int iError;

    iError = pthread_mutex_init(&ptmEndThread, NULL);
    if (0 != iError)
    {
        goto InitializeEndingThreadsDataExit;
    }

    iError = pthread_cond_init(&ptcEndThread, NULL);
    if (0 != iError)
    {
        //
        // Don't bother checking the return value of pthread_mutex_destroy
        // since PAL initialization will now fail.
        //

        pthread_mutex_destroy(&ptmEndThread);
        goto InitializeEndingThreadsDataExit;
    }

    palError = NO_ERROR;

InitializeEndingThreadsDataExit:

    return palError;
}

void
ThreadCleanupRoutine(
    CPalThread *pThread,
    IPalObject *pObjectToCleanup,
    bool fShutdown,
    bool fCleanupSharedState
    )
{
    CThreadProcessLocalData *pThreadData = NULL;
    CPalThread *pThreadToCleanup = NULL;
    IDataLock *pDataLock = NULL;
    PAL_ERROR palError = NO_ERROR;

    //
    // Free the CPalThread data for the passed in thread
    //

    palError = pObjectToCleanup->GetProcessLocalData(
        pThread,
        WriteLock,
        &pDataLock,
        reinterpret_cast<void**>(&pThreadData)
        );

    if (NO_ERROR == palError)
    {
        //
        // Note that we may be cleaning up the data for the calling
        // thread (i.e., pThread == pThreadToCleanup), so the release
        // of the thread reference needs to be the last thing that
        // we do (though in that case it's very likely that the person
        // calling us will be holding an extra reference to allow
        // for the thread data to be available while the rest of the
        // object cleanup takes place).
        //

        pThreadToCleanup = pThreadData->pThread;
        pThreadData->pThread = NULL;
        pDataLock->ReleaseLock(pThread, TRUE);
        pThreadToCleanup->ReleaseThreadReference();
    }
    else
    {
        ASSERT("Unable to obtain thread data");
    }

}

PAL_ERROR
ThreadInitializationRoutine(
    CPalThread *pThread,
    CObjectType *pObjectType,
    void *pImmutableData,
    void *pSharedData,
    void *pProcessLocalData
    )
{
    return NO_ERROR;
}

// Get base address of this thread's stack
// Can be called only for the current thread.
void *
CPalThread::GetStackBase()
{
    _ASSERT_MSG(this == InternalGetCurrentThread(), "CPalThread::GetStackBase called from foreign thread");

    if (m_stackBase == NULL)
    {
#ifdef __APPLE__
        // This is a Mac specific method
        m_stackBase = pthread_get_stackaddr_np(pthread_self());
#else
        pthread_attr_t attr;
        void* stackAddr;
        size_t stackSize;
        int status;

        pthread_t thread = pthread_self();

        status = pthread_attr_init(&attr);
        _ASSERT_MSG(status == 0, "pthread_attr_init call failed");

#if HAVE_PTHREAD_ATTR_GET_NP
        status = pthread_attr_get_np(thread, &attr);
        _ASSERT_MSG(status != 0, "pthread_attr_get_np call failed");
#elif HAVE_PTHREAD_GETATTR_NP
        status = pthread_getattr_np(thread, &attr);
        _ASSERT_MSG(status == 0, "pthread_getattr_np call failed");
#else
#error Dont know how to get thread attributes on this platform!
#endif

        status = pthread_attr_getstack(&attr, &stackAddr, &stackSize);
        _ASSERT_MSG(status == 0, "pthread_attr_getstack call failed");

        status = pthread_attr_destroy(&attr);
        _ASSERT_MSG(status == 0, "pthread_attr_destroy call failed");

        m_stackBase = (void*)((size_t)stackAddr + stackSize);
#endif
    }

    return m_stackBase;
}

// Get limit address of this thread's stack.
// Can be called only for the current thread.
void *
CPalThread::GetStackLimit()
{
    _ASSERT_MSG(this == InternalGetCurrentThread(), "CPalThread::GetStackLimit called from foreign thread");

    if (m_stackLimit == NULL)
    {
        pthread_t thread = pthread_self();

#ifdef __APPLE__
        ULONG_PTR _, low;
        GetInternalStackLimit(thread, &_, &low);
        m_stackLimit = (BYTE *) low;
#else
        pthread_attr_t attr;
        size_t stackSize;
        int status;

        status = pthread_attr_init(&attr);
        _ASSERT_MSG(status == 0, "pthread_attr_init call failed");

#if HAVE_PTHREAD_ATTR_GET_NP
        status = pthread_attr_get_np(thread, &attr);
        _ASSERT_MSG(status != 0, "pthread_attr_get_np call failed");
#elif HAVE_PTHREAD_GETATTR_NP
        status = pthread_getattr_np(thread, &attr);
        _ASSERT_MSG(status == 0, "pthread_getattr_np call failed");
#else
#error Dont know how to get thread attributes on this platform!
#endif

        status = pthread_attr_getstack(&attr, &m_stackLimit, &stackSize);
        _ASSERT_MSG(status == 0, "pthread_attr_getstack call failed");

        status = pthread_attr_destroy(&attr);
        _ASSERT_MSG(status == 0, "pthread_attr_destroy call failed");
#endif
    }

    return m_stackLimit;
}

void *
PALAPI
PAL_GetStackBase()
{
    CPalThread* thread = InternalGetCurrentThread();
    return thread->GetStackBase();
}

void *
PALAPI
PAL_GetStackLimit()
{
    CPalThread* thread = InternalGetCurrentThread();
    return thread->GetStackLimit();
}

PAL_ERROR InjectActivationInternal(CorUnix::CPalThread* pThread);

/*++
Function:
    PAL_SetActivationFunction

    Register an activation function that gets called when an activation is injected
    into a thread.

Parameters:
    pActivationFunction - activation function
    pSafeActivationCheckFunction - function to check if an activation can be safely
                                   injected at a specified context
Return value:
    None
--*/
PALIMPORT
VOID
PALAPI
PAL_SetActivationFunction(
    IN PAL_ActivationFunction pActivationFunction,
    IN PAL_SafeActivationCheckFunction pSafeActivationCheckFunction)
{
    g_activationFunction = pActivationFunction;
    g_safeActivationCheckFunction = pSafeActivationCheckFunction;
}

/*++
Function:
PAL_InjectActivation

Interrupt the specified thread and have it call an activation function registered
using the PAL_SetActivationFunction

Parameters:
hThread            - handle of the target thread

Return:
TRUE if it succeeded, FALSE otherwise.
--*/
BOOL
PALAPI
PAL_InjectActivation(
    IN HANDLE hThread)
{
    PERF_ENTRY(PAL_InjectActivation);
    ENTRY("PAL_InjectActivation(hThread=%p)\n", hThread);

    CPalThread *pCurrentThread;
    CPalThread *pTargetThread;
    IPalObject *pobjThread = NULL;

    pCurrentThread = InternalGetCurrentThread();

    PAL_ERROR palError = InternalGetThreadDataFromHandle(
        pCurrentThread,
        hThread,
        0,
        &pTargetThread,
        &pobjThread
        );

    if (palError == NO_ERROR)
    {
        palError = InjectActivationInternal(pTargetThread);
    }

    if (palError == NO_ERROR)
    {
        pCurrentThread->SetLastError(palError);
    }

    if (pobjThread != NULL)
    {
        pobjThread->ReleaseReference(pCurrentThread);
    }

    BOOL success = (palError == NO_ERROR);
    LOGEXIT("PAL_InjectActivation returns:d\n", success);
    PERF_EXIT(PAL_InjectActivation);

    return success;
}

#if HAVE_MACH_EXCEPTIONS

extern mach_port_t s_ExceptionPort;

// Returns a pointer to the handler node that should be initialized next. The first time this is called for a
// thread the bottom node will be returned. Thereafter the top node will be returned. Also returns the Mach
// exception port that should be registered.
CorUnix::CThreadMachExceptionHandlerNode *CorUnix::CThreadMachExceptionHandlers::GetNodeForInitialization(mach_port_t *pExceptionPort)
{
    *pExceptionPort = s_ExceptionPort;
    return &m_bottom;
}

// Get handler details for a given type of exception. If successful the structure pointed at by pHandler is
// filled in and true is returned. Otherwise false is returned. The fTopException argument indicates whether
// the handlers found at the time of a call to ICLRRuntimeHost2::RegisterMacEHPort() should be searched (if
// not, or a handler is not found there, we'll fallback to looking at the handlers discovered at the point
// when the CLR first saw this thread).
bool CorUnix::CThreadMachExceptionHandlers::GetHandler(exception_type_t eException,
                                                       bool fTopException,
                                                       CorUnix::MachExceptionHandler *pHandler)
{
    exception_mask_t bmExceptionMask = (1 << eException);
    int idxHandler = -1;
    CThreadMachExceptionHandlerNode *pNode = NULL;

    // Check top handlers first if we've been asked to and they have been initialized.
    if (fTopException && m_top.m_nPorts != -1)
    {
        pNode = &m_top;
        idxHandler = GetIndexOfHandler(bmExceptionMask, pNode);
    }

    // If we haven't identified a handler yet continue looking with the bottom handlers.
    if (idxHandler == -1)
    {
        pNode = &m_bottom;
        idxHandler = GetIndexOfHandler(bmExceptionMask, pNode);
    }

    // Did we find a handler?
    if (idxHandler == -1)
        return false;

    // Found one, so initialize the output structure with the details.
    pHandler->m_mask = pNode->m_masks[idxHandler];
    pHandler->m_handler = pNode->m_handlers[idxHandler];
    pHandler->m_behavior = pNode->m_behaviors[idxHandler];
    pHandler->m_flavor = pNode->m_flavors[idxHandler];

    return true;
}

// Look for a handler for the given exception within the given handler node. Return its index if successful or
// -1 otherwise.
int CorUnix::CThreadMachExceptionHandlers::GetIndexOfHandler(exception_mask_t bmExceptionMask,
                                                             CorUnix::CThreadMachExceptionHandlerNode *pNode)
{
    // Check all handler entries for one handling the exception mask.
    for (int i = 0; i < pNode->m_nPorts; i++)
    {
        if (pNode->m_masks[i] & bmExceptionMask &&      // Entry covers this exception type
            pNode->m_handlers[i] != MACH_PORT_NULL &&   // And the handler isn't null
            pNode->m_handlers[i] != s_ExceptionPort)    // And the handler isn't ourselves
        {
            // One more check; has the target handler port become dead?
            mach_port_type_t ePortType;
            if (mach_port_type(mach_task_self(), pNode->m_handlers[i], &ePortType) == KERN_SUCCESS &&
                !(ePortType & MACH_PORT_TYPE_DEAD_NAME))
            {
                // Got a matching entry.
                return i;
            }
        }
    }

    // Didn't find a handler.
    return -1;
}

#endif // HAVE_MACH_EXCEPTIONS

#ifndef __IOS__
static THREAD_LOCAL ULONG_PTR s_cachedHighLimit = 0;
static THREAD_LOCAL ULONG_PTR s_cachedLowLimit = 0;
#endif

void GetCurrentThreadStackLimits(ULONG_PTR* lowLimit, ULONG_PTR* highLimit)
{
#ifndef __IOS__
    if (s_cachedLowLimit)
    {
        *lowLimit = s_cachedLowLimit;
        *highLimit = s_cachedHighLimit;
        return;
    }
#endif
    pthread_t currentThreadHandle = pthread_self();

#ifdef __APPLE__
    GetInternalStackLimit(currentThreadHandle, highLimit, lowLimit);
#else
    pthread_attr_t attr;
    size_t stacksize;
    void* stackend;
    int status;

#if HAVE_PTHREAD_ATTR_GET_NP
    status = pthread_attr_get_np(currentThreadHandle, &attr);
    _ASSERT_MSG(status != 0, "pthread_attr_get_np call failed");
#elif HAVE_PTHREAD_GETATTR_NP
    status = pthread_getattr_np(currentThreadHandle, &attr);
    _ASSERT_MSG(status == 0, "pthread_getattr_np call failed");
#else
#   error "Dont know how to get thread attributes on this platform!"
#endif

    status = pthread_attr_getstack(&attr, &stackend, &stacksize);
    _ASSERT_MSG(status == 0, "pthread_attr_getstack call failed");

    void* stackbase = (void*) ((char*) stackend + stacksize);

    pthread_attr_destroy(&attr);

    *lowLimit = (ULONG_PTR) stackend;
    *highLimit = (ULONG_PTR) stackbase;
#endif

#ifndef __IOS__
    s_cachedLowLimit = *lowLimit;
    s_cachedHighLimit = *highLimit;
#endif
}

bool IsAddressOnStack(ULONG_PTR address)
{
    // Assumption: Stack always grows, never shrinks and the high limit is stable
    // Assumption: pthread_getattr_np is slow, so we need to cache the current stack
    // bounds to speed up checking if a given address is on the stack
    // The semantics of IsAddressOnStack is that we care if a given address is
    // in the range of the current stack pointer
    ULONG_PTR lowLimit, highLimit;
    GetCurrentThreadStackLimits(&lowLimit, &highLimit);

    ULONG_PTR currentStackPtr = GetCurrentSP();

    if (currentStackPtr <= address && address < highLimit)
    {
        return true;
    }

    return false;
}

#ifndef __IOS__
// why _Thread_local ? Because it is faster(.)
// why not replace PAL to use _Thread_local instead of front caching? _Thread_local is not cross platform

// Why ULONG_PTR? keeping type for localThread `simple` may affect implementation hence the perf. positively.
THREAD_LOCAL ULONG_PTR localThread = 0;
CPalThread *CorUnix::GetCurrentPalThread(bool force)
{
    ULONG_PTR pThread = localThread;
    if (pThread == 0)
    {
        pThread = (ULONG_PTR) reinterpret_cast<CPalThread*>(pthread_getspecific(thObjKey));
#ifdef FEATURE_PAL_SXS
        if (pThread == 0 && force)
        {
            pThread = (ULONG_PTR) CreateCurrentThreadData();
        }
#endif
        localThread = pThread;
    }

    return (CPalThread*)pThread;
}
#else // !__IOS__
CPalThread *CorUnix::GetCurrentPalThread(bool force)
{
    CPalThread *pThread = reinterpret_cast<CPalThread*>(pthread_getspecific(thObjKey));

#ifdef FEATURE_PAL_SXS
    if (pThread == nullptr && force)
    {
        pThread = CreateCurrentThreadData();
    }
#endif

    return pThread;
}
#endif

CPalThread *CorUnix::InternalGetCurrentThread()
{
    return GetCurrentPalThread(true);
}
