crt: tls: Move _register_thread_local_exe_atexit_callback into own source unit and do not call it for DLL builds

Add a new internal function __mingw_register_thread_local_exe_atexit_callback
which will call the CRT _register_thread_local_exe_atexit_callback() function
with the mingw-w64 supplied callback function. Supplied callback function
for now just calls the __mingw_cxa_atexit_callback_ptr (which is pointer to
the tls_atexit_callback) previously directly passed as argument to
_register_thread_local_exe_atexit_callback().

Stores pointer of the __mingw_register_thread_local_exe_atexit_callback
into the tentative __mingw_register_thread_local_exe_atexit_callback_ptr
variable and call it from crtexe.c on demand when is linked into final
exectuable. And reference the __mingw_register_thread_local_exe_atexit_callback
symbol from tls_atexit.c where is the __mingw_cxa_atexit_callback_ptr
defined and expected to be __mingw_register_thread_local_exe_atexit_callback
called from CRT EXE startup code.

With these tenative definitions, the _register_thread_local_exe_atexit_callback()
would be included in final binary image only if the callback is really
required and only for EXE builds.

Before this change the _register_thread_local_exe_atexit_callback() with
tls_atexit_callback argument was called from PE TLS DLL_PROCESS_ATTACH
callback. After this change, _register_thread_local_exe_atexit_callback()
(with new mingw-w64 callback which calls tls_atexit_callback via
__mingw_cxa_atexit_callback_ptr) is called from new function
__mingw_register_thread_local_exe_atexit_callback() which is called from
CRT EXE startup code (crtexe.c).

Also extend documentation for tls_atexit_callback and tls_callback.

Signed-off-by: LIU Hao <lh_mouse@126.com>
diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 07c050d..b196372 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -128,6 +128,7 @@
   crt/usermatherr.c   \
   crt/xtxtmode.c      \
   crt/crtdll_atexit_register.c \
+  crt/tls_exe_atexit_callback.c \
   crt/tlsdyn.c        crt/tlsdtor.c        \
   crt/tlsthrd_register.c crt/tls_atexit_register.c \
   crt/tlsthrd.c       crt/tlsmthread.c     crt/tlsmcrt.c   \
diff --git a/mingw-w64-crt/crt/crtdll.c b/mingw-w64-crt/crt/crtdll.c
index af7a477..15ee52a 100644
--- a/mingw-w64-crt/crt/crtdll.c
+++ b/mingw-w64-crt/crt/crtdll.c
@@ -216,4 +216,8 @@
   return retcode ;
 }
 
+/* not available for DLL builds, must be non-tentative to not be overridden */
+void (__cdecl *const __mingw_register_thread_local_exe_atexit_callback_ptr)(void) = NULL;
+extern const uintptr_t __mingw_register_thread_local_exe_atexit_callback_provider __attribute__((alias("__mingw_register_thread_local_exe_atexit_callback_ptr")));
+
 const char __mingw_module_is_dll = 1;
diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
index e7dbcff..ca9b006 100644
--- a/mingw-w64-crt/crt/crtexe.c
+++ b/mingw-w64-crt/crt/crtexe.c
@@ -51,6 +51,8 @@
 /* TLS initialization hook.  */
 const PIMAGE_TLS_CALLBACK __dyn_tls_init_callback __attribute__((common)); /* tentative */
 
+void (__cdecl *const __mingw_register_thread_local_exe_atexit_callback_ptr)(void) __attribute__((common)); /* tentative */
+
 extern int __mingw_app_type;
 
 static int argc;
@@ -258,6 +260,9 @@
     if (__dyn_tls_init_callback != NULL)
       __dyn_tls_init_callback (NULL, DLL_THREAD_ATTACH, NULL);
 
+    if (__mingw_register_thread_local_exe_atexit_callback_ptr != NULL)
+      __mingw_register_thread_local_exe_atexit_callback_ptr ();
+
 #ifdef _UNICODE
     __winitenv = envp;
 #else
diff --git a/mingw-w64-crt/crt/tls_atexit.c b/mingw-w64-crt/crt/tls_atexit.c
index 44dd5c8..bb0390f 100644
--- a/mingw-w64-crt/crt/tls_atexit.c
+++ b/mingw-w64-crt/crt/tls_atexit.c
@@ -93,10 +93,38 @@
   TlsSetValue(tls_dtors_slot, NULL);
 }
 
-#if defined(__i386__)
-/* We need to make sure that we align the stack to 16 bytes for the sake of SSE */
-__attribute__((force_align_arg_pointer))
-#endif
+/*
+ * For EXE builds the tls_atexit_callback() function is called by the exit()
+ * function before it calls atexit callbacks. For UCRT builds this is achieved
+ * by UCRT directly, for non-UCRT builds this is achieved by the mingw-w64
+ * exit() wrapper function. The tls_atexit_callback() function is called
+ * indirectly via the __mingw_cxa_atexit_callback_ptr from the mingw-w64
+ * callback registered by the CRT _register_thread_local_exe_atexit_callback.
+ * If the process does not call CRT exit function and is terminated by different
+ * way (e.g. _exit(), _Exit() or ExitProcess()) then the tls_atexit_callback()
+ * function is not called at all.
+ *
+ * For DLL builds the tls_atexit_callback() function is called explicitly from
+ * the tls_callback() function which is below. This function is called also when
+ * process is terminated by different way (e.g. _exit(), _Exit() or ExitProcess())
+ * not just by exit() function.
+ *
+ * Reason for these differences is the fact that for DLL builds the tls_callback()
+ * function is called before invocation of atexit callbacks, but for EXE builds
+ * it is called after all atexit callbacks. For DLL builds, all atexit callbacks
+ * are executed from the DLL entry point when DLL_PROCESS_DETACH event happens
+ * and for EXE builds, they are executed from the CRT exit() function which is
+ * called before execution of native PE TLS callbacks.
+ *
+ * For EXE builds the tls_callback() function is called PE TLS callback and
+ * hence after all atexit callbacks. For DLL builds the tls_callback() function
+ * is called from DLL entry point before all atexit callbacks. Note that for
+ * DLL builds also PE TLS callbacks are executed before all atexit callbacks.
+ *
+ * mingw cxa dtors (executed by tls_atexit_callback) has to be run before atexit
+ * callbacks because it is required for execution of global C++ destructors.
+ * So this is why the different behavior for DLL and EXE builds is needed.
+ */
 static void WINAPI tls_atexit_callback(HANDLE __UNUSED_PARAM(hDllHandle), DWORD dwReason, LPVOID __UNUSED_PARAM(lpReserved)) {
   if (dwReason == DLL_PROCESS_DETACH) {
     run_thread_dtor_list();
@@ -107,7 +135,23 @@
     }
   }
 }
+const _tls_callback_type __mingw_cxa_atexit_callback_ptr = tls_atexit_callback;
 
+/* Force inclusion of code which registers __mingw_cxa_atexit_callback_ptr for EXE builds */
+extern const uintptr_t __mingw_register_thread_local_exe_atexit_callback_provider;
+static __attribute__((used)) const void *const _include_mingw_cxa_atexit_callback_caller = &__mingw_register_thread_local_exe_atexit_callback_provider;
+
+
+/*
+ * For EXE builds the tls_callback is called PE TLS callback. If the CRT exit
+ * function was called then the PE TLS callback for DLL_PROCESS_DETACH event
+ * is called after execution of exit function and so after execution of all
+ * registered atexit callbacks.
+ *
+ * For DLL builds the tls_callback is called from DLL entry point. And for
+ * the DLL_PROCESS_DETACH event, the tls_callback is called before execution
+ * of atexit callbacks.
+ */
 static WINBOOL WINAPI tls_callback(HANDLE hDllHandle, DWORD dwReason, LPVOID __UNUSED_PARAM(lpReserved)) {
   switch (dwReason) {
   case DLL_PROCESS_ATTACH:
@@ -117,18 +161,6 @@
         return FALSE;
       InitializeCriticalSection(&lock);
       __dso_handle = hDllHandle;
-      /*
-       * 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;
@@ -150,9 +182,11 @@
      * standard says, but differs from what MSVC does with a dynamically
      * linked CRT (which still runs TLS destructors for the main thread).
      *
-     * 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.
+     * For DLLs, run dtors when detached explicitly by tls_atexit_callback().
+     * 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.
+     * mingw-w64 thread local atexit callback calls the tls_atexit_callback()
+     * and is registered for EXEs by _register_thread_local_exe_atexit_callback.
      */
     if (__mingw_module_is_dll)
       tls_atexit_callback(NULL, DLL_PROCESS_DETACH, NULL);
diff --git a/mingw-w64-crt/crt/tls_exe_atexit_callback.c b/mingw-w64-crt/crt/tls_exe_atexit_callback.c
new file mode 100644
index 0000000..fa9d270
--- /dev/null
+++ b/mingw-w64-crt/crt/tls_exe_atexit_callback.c
@@ -0,0 +1,27 @@
+/**
+ * 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 <process.h>
+
+const _tls_callback_type __mingw_cxa_atexit_callback_ptr __attribute__((common)); /* tentative */
+
+#if defined(__i386__)
+/* We need to make sure that we align the stack to 16 bytes for the sake of SSE */
+__attribute__((force_align_arg_pointer))
+#endif
+static void __stdcall tls_exe_atexit_callback(void *handle, unsigned long reason, void *reserved)
+{
+  if (__mingw_cxa_atexit_callback_ptr)
+    __mingw_cxa_atexit_callback_ptr(handle, reason, reserved);
+}
+
+static void __cdecl __mingw_register_thread_local_exe_atexit_callback(void)
+{
+  _register_thread_local_exe_atexit_callback(tls_exe_atexit_callback);
+}
+
+void (__cdecl *const __mingw_register_thread_local_exe_atexit_callback_ptr)(void) = __mingw_register_thread_local_exe_atexit_callback; /* override tentative */
+extern const uintptr_t __mingw_register_thread_local_exe_atexit_callback_provider __attribute__((alias("__mingw_register_thread_local_exe_atexit_callback_ptr"))); /* nontentative alias */