/**
 * This file has no copyright assigned and is placed in the Public Domain.
 * This file is part of the mingw-w64 runtime package.
 * No warranty is given; refer to the file DISCLAIMER.PD within this package.
 */

#include <sect_attribs.h>

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <corecrt_startup.h>
#include <process.h>


typedef void (__thiscall * dtor_fn)(void*);
int __mingw_cxa_atexit(dtor_fn dtor, void *obj, void *dso);
int __mingw_cxa_thread_atexit(dtor_fn dtor, void *obj, void *dso);

typedef struct dtor_obj dtor_obj;
struct dtor_obj {
  dtor_fn dtor;
  void *obj;
  dtor_obj *next;
};

HANDLE __dso_handle;
extern char __mingw_module_is_dll;

static CRITICAL_SECTION lock;
static int inited = 0;
static dtor_obj *global_dtors = NULL;
static DWORD tls_dtors_slot = TLS_OUT_OF_INDEXES;

int __mingw_cxa_atexit(dtor_fn dtor, void *obj, void *dso) {
  if (!inited)
    return 1;
  assert(!dso || dso == &__dso_handle);
  dtor_obj *handler = (dtor_obj *) calloc(1, sizeof(*handler));
  if (!handler)
    return 1;
  handler->dtor = dtor;
  handler->obj = obj;
  EnterCriticalSection(&lock);
  handler->next = global_dtors;
  global_dtors = handler;
  LeaveCriticalSection(&lock);
  return 0;
}

static void run_dtor_list(dtor_obj **ptr) {
  if (!ptr)
    return;
  while (*ptr) {
    dtor_obj *cur = *ptr;
    *ptr = cur->next;
    cur->dtor(cur->obj);
    free(cur);
  }
}

int __mingw_cxa_thread_atexit(dtor_fn dtor, void *obj, void *dso) {
  if (!inited)
    return 1;
  assert(!dso || dso == &__dso_handle);

  dtor_obj **head = (dtor_obj **)TlsGetValue(tls_dtors_slot);
  if (!head) {
    head = (dtor_obj **) calloc(1, sizeof(*head));
    if (!head)
      return 1;
    TlsSetValue(tls_dtors_slot, head);
  }
  dtor_obj *handler = (dtor_obj *) calloc(1, sizeof(*handler));
  if (!handler)
    return 1;
  handler->dtor = dtor;
  handler->obj = obj;
  handler->next = *head;
  *head = handler;
  return 0;
}

static void WINAPI tls_atexit_callback(HANDLE __UNUSED_PARAM(hDllHandle), DWORD dwReason, LPVOID __UNUSED_PARAM(lpReserved)) {
  if (dwReason == DLL_PROCESS_DETACH) {
    dtor_obj **p = (dtor_obj **)TlsGetValue(tls_dtors_slot);
    run_dtor_list(p);
    free(p);
    TlsSetValue(tls_dtors_slot, NULL);
    TlsFree(tls_dtors_slot);
    run_dtor_list(&global_dtors);
  }
}

static void WINAPI tls_callback(HANDLE hDllHandle, DWORD dwReason, LPVOID __UNUSED_PARAM(lpReserved)) {
  dtor_obj **p;
  switch (dwReason) {
  case DLL_PROCESS_ATTACH:
    if (inited == 0) {
      InitializeCriticalSection(&lock);
      __dso_handle = hDllHandle;
      tls_dtors_slot = TlsAlloc();
      /*
       * We can only call _register_thread_local_exe_atexit_callback once
       * in a process; if we call it a second time the process terminates.
       * When DLLs are unloaded, this callback is invoked before we run the
       * _onexit tables, but for exes, we need to ask this to be called before
       * all other registered atexit functions.
       * Since we are registered as a normal TLS callback, we will be called
       * another time later as well, but that doesn't matter, it's safe to
       * invoke this with DLL_PROCESS_DETACH twice.
       */
      if (!__mingw_module_is_dll)
        _register_thread_local_exe_atexit_callback(tls_atexit_callback);
    }
    inited = 1;
    break;
  case DLL_PROCESS_DETACH:
    /*
     * If there are other threads still running that haven't been detached,
     * we don't attempt to run their destructors (MSVC doesn't either), but
     * simply leak the destructor list and whatever resources the destructors
     * would have released.
     *
     * From Vista onwards, we could have used FlsAlloc to get a TLS key that
     * runs a destructor on each thread that has a value attached ot it, but
     * since MSVC doesn't run destructors on other threads in this case,
     * users shouldn't assume it and we don't attempt to do anything potentially
     * risky about it. TL;DR, threads with pending TLS destructors for a DLL
     * need to be joined before unloading the DLL.
     *
     * This gets called both when exiting cleanly (via exit or returning from
     * main, or when a DLL is unloaded), and when exiting bypassing some of
     * the cleanup, by calling _exit or ExitProcess. In the latter cases,
     * destructors (both TLS and global) in loaded DLLs still get called,
     * but none get called for the main executable. This matches what the
     * standard says, but differs from what MSVC does with a dynamically
     * linked CRT (which still runs TLS destructors for the main thread).
     */
    if (__mingw_module_is_dll) {
      p = (dtor_obj **)TlsGetValue(tls_dtors_slot);
      run_dtor_list(p);
      free(p);
      TlsSetValue(tls_dtors_slot, NULL);
      /* For DLLs, run dtors when detached. For EXEs, run dtors via the
       * thread local atexit callback, to make sure they don't run when
       * exiting the process with _exit or ExitProcess. */
      run_dtor_list(&global_dtors);
      TlsFree(tls_dtors_slot);
    }
    if (inited == 1) {
      inited = 0;
      DeleteCriticalSection(&lock);
    }
    break;
  case DLL_THREAD_ATTACH:
    break;
  case DLL_THREAD_DETACH:
    p = (dtor_obj **)TlsGetValue(tls_dtors_slot);
    run_dtor_list(p);
    free(p);
    TlsSetValue(tls_dtors_slot, NULL);
    break;
  }
}

_CRTALLOC(".CRT$XLB") PIMAGE_TLS_CALLBACK __xl_b = tls_callback;
