// This file is a part of Julia. License is MIT: https://julialang.org/license

// Windows
// Note that this file is `#include`d by "signal-handling.c"
#include <mmsystem.h> // hidden by LEAN_AND_MEAN

static const size_t sig_stack_size = 131072; // 128k reserved for backtrace_fiber for stack overflow handling

// Copied from MINGW_FLOAT_H which may not be found due to a collision with the builtin gcc float.h
// eventually we can probably integrate this into OpenLibm.
#if defined(_COMPILER_GCC_)
void __cdecl __MINGW_NOTHROW _fpreset (void);
void __cdecl __MINGW_NOTHROW fpreset (void);
#else
void __cdecl _fpreset (void);
void __cdecl fpreset (void);
#endif
#define _FPE_INVALID        0x81
#define _FPE_DENORMAL       0x82
#define _FPE_ZERODIVIDE     0x83
#define _FPE_OVERFLOW       0x84
#define _FPE_UNDERFLOW      0x85
#define _FPE_INEXACT        0x86
#define _FPE_UNEMULATED     0x87
#define _FPE_SQRTNEG        0x88
#define _FPE_STACKOVERFLOW  0x8a
#define _FPE_STACKUNDERFLOW 0x8b
#define _FPE_EXPLICITGEN    0x8c    /* raise( SIGFPE ); */

void __cdecl crt_sig_handler(int sig, int num)
{
    CONTEXT Context;
    switch (sig) {
    case SIGFPE:
        fpreset();
        signal(SIGFPE, (void (__cdecl *)(int))crt_sig_handler);
        switch(num) {
        case _FPE_INVALID:
        case _FPE_OVERFLOW:
        case _FPE_UNDERFLOW:
        default:
            jl_errorf("Unexpected FPE Error 0x%X", num);
            break;
        case _FPE_ZERODIVIDE:
            jl_throw(jl_diverror_exception);
            break;
        }
        break;
    case SIGINT:
        signal(SIGINT, (void (__cdecl *)(int))crt_sig_handler);
        if (!jl_ignore_sigint()) {
            if (exit_on_sigint)
                jl_exit(130); // 128 + SIGINT
            // This handler runs synchronously on the raising thread. If that
            // is a GC-unsafe Julia mutator, the request path's GC exclusion
            // could deadlock against a collection waiting for this very
            // thread - transition to GC-safe for the duration (the exclusion
            // then holds off any new collection while the sources are
            // touched).
            jl_task_t *ct = jl_get_current_task();
            if (ct != NULL && ct->ptls != NULL) {
                int8_t gc_state = jl_gc_safe_enter(ct->ptls);
                jl_sigint_request_cancellation();
                jl_gc_safe_leave(ct->ptls, gc_state);
            }
            else {
                jl_sigint_request_cancellation();
            }
        }
        break;
    default: // SIGSEGV, SIGTERM, SIGILL, SIGABRT
        if (sig == SIGSEGV) { // restarting jl_ or profile
            jl_jmp_buf *saferestore = jl_get_safe_restore();
            if (saferestore) {
                signal(sig, (void (__cdecl *)(int))crt_sig_handler);
                jl_longjmp(*saferestore, 1);
                return;
            }
        }
        memset(&Context, 0, sizeof(Context));
        RtlCaptureContext(&Context);

        ios_t s;
        ios_mem(&s, 0);
        if (sig == SIGILL)
            jl_fprint_sigill(&s, &Context);
        jl_fprint_critical_error(&s, sig, 0, &Context, jl_get_current_task());

        // First write to stderr
        ios_write_direct(ios_safe_stderr, &s);

        // Then write to Application log
        HANDLE event_source = RegisterEventSourceW(NULL, L"julia");
        if (event_source != INVALID_HANDLE_VALUE) {
            ios_putc('\0', &s);
            const wchar_t *strings[] = { ios_utf8_to_wchar(s.buf) };
            ReportEventW(
                event_source, EVENTLOG_ERROR_TYPE, /* category */ 0, /* event_id */ (DWORD)0xE0000000L,
               /* user_sid */ NULL, /* n_strings */ 1, /* data_size */ 0, strings, /* data */ NULL
            );
            free((void *)strings[0]);

            if (jl_options.alert_on_critical_error) {
                MessageBoxW(NULL, /* message */ L"error: libjulia received a fatal signal.\n\n"
                                                L"See Application log in Event Viewer for more information.",
                            /* title */ L"fatal error in libjulia", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
            }
        }
        raise(sig);
    }
}

// StackOverflowException needs extra stack space to record the backtrace
// so we keep one around, shared by all threads
static uv_mutex_t backtrace_lock;
static win32_ucontext_t collect_backtrace_fiber;
static win32_ucontext_t error_return_fiber;
static PCONTEXT stkerror_ctx;
static jl_ptls_t stkerror_ptls;
static int have_backtrace_fiber;
static void JL_NORETURN start_backtrace_fiber(void)
{
    // print the warning (this mysteriously needs a lot of stack for the WriteFile syscall)
    stack_overflow_warning();
    // collect the backtrace
    stkerror_ptls->bt_size =
        rec_backtrace_ctx(stkerror_ptls->bt_data, JL_MAX_BT_SIZE, stkerror_ctx,
                          NULL /*current_task?*/);
    // switch back to the execution fiber
    jl_setcontext(&error_return_fiber);
    abort();
}

void restore_signals(void)
{
    // turn on ctrl-c handler
    SetConsoleCtrlHandler(NULL, 0);
}

int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c, int val) JL_NOTSAFEPOINT;

static void jl_throw_in_ctx(jl_task_t *ct, jl_value_t *excpt, PCONTEXT ctxThread) JL_NOTSAFEPOINT
{
    jl_jmp_buf *saferestore = jl_get_safe_restore();
    if (saferestore) { // restarting jl_ or profile
        if (!jl_simulate_longjmp(*saferestore, ctxThread, 1))
            abort();
        return;
    }
    assert(ct && excpt);
    // This redirect abandons every frame between the interrupted context and
    // the handler. A reset context published in one of those frames would
    // dangle - and may be consumed by an off-thread cancellation sender - so
    // clear it before rewriting the context. The matching
    // jl_eh_restore_state republishes the outer context saved at handler
    // entry. The same applies to a foreign-call cancellation-handler guard
    // published in an abandoned frame.
    jl_atomic_store_release(&ct->reset_ctx, NULL);
    jl_atomic_store_release(&ct->cancel_handler_ctx, NULL);
    jl_ptls_t ptls = ct->ptls;
    ptls->bt_size = 0;
    if (excpt != jl_stackovf_exception) {
        ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, ctxThread,
                                          ct->gcstack);
    }
    else if (have_backtrace_fiber) {
        uv_mutex_lock(&backtrace_lock);
        stkerror_ctx = ctxThread;
        stkerror_ptls = ptls;
        jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber);
        uv_mutex_unlock(&backtrace_lock);
    }
    ptls->sig_exception = excpt;
    ptls->io_wait = 0;
    jl_handler_t *eh = ct->eh;
    if (eh != NULL) {
        asan_unpoison_task_stack(ct, &eh->eh_ctx);
        if (!jl_simulate_longjmp(eh->eh_ctx, ctxThread, 1))
            abort();
    }
    else {
        jl_no_exc_handler(excpt, ct);
    }
}

HANDLE hMainThread = INVALID_HANDLE_VALUE;

// Serializes every path that suspends a thread and rewrites its context
// (jl_send_cancellation_signal and jl_send_abandon_signal on any thread) for
// its complete suspend/get/set/resume sequence: two rewriters working from
// the same suspended snapshot would install conflicting continuations and
// task chains. (The profiler does not need it: it only reads contexts, and
// suspend counts nest.) It also prevents two threads delivering
// cancellations at each other from freezing both (the suspend-count analog
// of a deadlock): a rewriter always takes this lock before suspending and
// never blocks while holding a suspension, so a suspended thread can never
// hold it.
static SRWLOCK ctx_rewrite_lock = SRWLOCK_INIT;

// === Cancellation-handler delivery ==========================================
// The suspend-based analog of the Unix implementation (see signals-unix.c):
// the sender saves the interrupted thread's full CONTEXT (including FP
// state) onto the interrupted stack, redirects the thread to a trampoline
// that runs fn(state, sev), and rigs the trampoline's return address to
// fault on a dedicated no-access page. A first-chance vectored exception
// handler recognizes that fault and restores the saved CONTEXT wholesale,
// resuming the originally interrupted instruction. (A vectored handler runs
// before any frame-based SEH search, so the synthetic frame needs no unwind
// information.)

static void *jl_win_restore_page = NULL;

static inline int jl_addr_is_win_restore_trigger(uintptr_t addr)
{
    uintptr_t page_addr = (uintptr_t)jl_win_restore_page;
    return page_addr != 0 && addr >= page_addr && addr < page_addr + jl_page_size;
}

// The return target rigged under the trampoline. An asm stub (rather than a
// C function) so that no prologue moves Rsp before the faulting read: at the
// fault, Rsp still points just below the saved CONTEXT.
extern void jl_win_restore_trigger(void);
#if defined(_CPU_X86_64_)
__asm__(
    "  .globl jl_win_restore_trigger\n"
    "jl_win_restore_trigger:\n"
    "  movq jl_win_restore_page(%rip), %r11\n"
    "  movq (%r11), %r11\n" // EXCEPTION_ACCESS_VIOLATION here
    "  ud2\n"               // should never reach here
);
#elif defined(_CPU_X86_)
__asm__(
    "  .globl _jl_win_restore_trigger\n"
    "_jl_win_restore_trigger:\n"
    "  movl _jl_win_restore_page, %eax\n"
    "  movl (%eax), %eax\n" // EXCEPTION_ACCESS_VIOLATION here
    "  ud2\n"               // should never reach here
);
#endif

// Runs on the interrupted thread with the interrupted CONTEXT saved on the
// stack below: invoke the registered cancellation handler with its
// arguments from the per-thread save area. Returning runs into the restore
// trigger.
static void jl_win_cancel_handler_trampoline(jl_ptls_t ptls)
{
    jl_cancel_handler_save_t *save = &ptls->cancel_handler_save;
    save->fn(save->state, save->sev);
}

// Rewrite the (suspended) thread context so that it runs fptr(arg0) on a
// frame carved below the interrupted stack, with the full interrupted
// CONTEXT saved in that frame for the restore trigger.
static void jl_win_call_in_context(CONTEXT *ctx, void (*fptr)(void), uintptr_t arg0)
{
#if defined(_CPU_X86_64_)
    uintptr_t sp = (uintptr_t)ctx->Rsp; // no red zone in the Win64 ABI
    sp = (sp - sizeof(CONTEXT)) & ~(uintptr_t)15;
    CONTEXT *saved = (CONTEXT*)sp;
    memcpy(saved, ctx, sizeof(CONTEXT));
    sp -= 32;            // the callee's register-home space, above the return address
    sp -= sizeof(void*); // return-address slot: entry Rsp == 8 (mod 16), per the ABI
    *(uintptr_t*)sp = (uintptr_t)&jl_win_restore_trigger;
    ctx->Rsp = sp;
    ctx->Rip = (uintptr_t)fptr;
    ctx->Rcx = arg0;
#elif defined(_CPU_X86_)
    uintptr_t sp = (uintptr_t)ctx->Esp;
    sp = (sp - sizeof(CONTEXT)) & ~(uintptr_t)15;
    CONTEXT *saved = (CONTEXT*)sp;
    memcpy(saved, ctx, sizeof(CONTEXT));
    sp -= sizeof(void*); // the trampoline's (cdecl) argument
    *(uintptr_t*)sp = arg0;
    sp -= sizeof(void*); // return-address slot
    *(uintptr_t*)sp = (uintptr_t)&jl_win_restore_trigger;
    ctx->Esp = sp;
    ctx->Eip = (uintptr_t)fptr;
#endif
}

static LONG WINAPI jl_win_restore_veh(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
        ExceptionInfo->ExceptionRecord->ExceptionFlags == 0 &&
        jl_addr_is_win_restore_trigger(ExceptionInfo->ExceptionRecord->ExceptionInformation[1])) {
        // Returning from a cancellation-handler delivery: the trampoline's
        // `ret` left the stack pointer at its home space (x86_64) or its
        // argument slot (x86), with the saved CONTEXT just above. Restore
        // it wholesale and resume the originally interrupted instruction.
#if defined(_CPU_X86_64_)
        CONTEXT *saved = (CONTEXT*)(ExceptionInfo->ContextRecord->Rsp + 32);
#elif defined(_CPU_X86_)
        CONTEXT *saved = (CONTEXT*)(ExceptionInfo->ContextRecord->Esp + sizeof(void*));
#endif
        memcpy(ExceptionInfo->ContextRecord, saved, sizeof(CONTEXT));
        jl_task_t *ct = jl_get_current_task();
        if (ct != NULL && ct->ptls != NULL)
            ct->ptls->cancel_handler_armed = 0;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

static void jl_win_init_cancel_handler_delivery(void)
{
    jl_win_restore_page = VirtualAlloc(NULL, jl_page_size, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
    if (jl_win_restore_page == NULL)
        jl_error("fatal error: could not allocate the cancellation restore-trigger page");
    if (AddVectoredExceptionHandler(1 /* first */, jl_win_restore_veh) == NULL)
        jl_error("fatal error: could not install the cancellation restore handler");
}

// Deliver a cancellation shootdown to the target thread's current task's
// published interruptible-region context, if available: the suspend-based
// analog of the Unix SIGUSR2 request-5 delivery (see signals-unix.c). For a
// compiled reset region, rewrites the suspended thread's context as if the
// reset point's setjmp had returned again (with JL_RESET_CODE_CANCEL as the
// setjmp return), restoring the gcstack and eh saved at establishment; for
// a foreign call carrying a cancellation handler, hijacks the thread to run
// fn(state, sev) on its own stack and resume. Best-effort: any check
// failing simply drops the request (recovery is level-triggered at the
// task's next cancellation point), and the caller retries.
static int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx);
void jl_thread_resume(int tid);

static void jl_send_reset_signal(int16_t tid, int reset_code) JL_NOTSAFEPOINT
{
    jl_value_t *bound;
    jl_reset_ctx_t *reset_ctx;
    jl_cancel_handler_ctx_t *hctx;
    if (tid < 0 || tid >= jl_atomic_load_acquire(&jl_n_threads))
        return;
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL)
        return;
    jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL)
        return;
    // Only proceed if the task has an interruptible-region context
    // published - a purely polling victim between cancellation points never
    // has one. Never self-suspend: a thread's own reset region is always
    // cleared here (the ccall reaching this function is itself an unsafe
    // point), but a protected runtime span (e.g. the GMP allocation hooks)
    // can reach this sender - through a finalizer running cancel! - with
    // its own handler context still published.
    if (GetThreadId(ptls2->system_id) == GetCurrentThreadId())
        return;
    if (jl_atomic_load_relaxed(&ct2->reset_ctx) == NULL &&
        jl_atomic_load_relaxed(&ct2->cancel_handler_ctx) == NULL)
        return;
    AcquireSRWLockExclusive(&ctx_rewrite_lock);
    // The freeze must complete before any state is examined: validating (or
    // consuming) the published context while the victim still runs races
    // with it re-executing the establishing cancellation point (rewriting
    // the buffer) or leaving the frame entirely, and a SetThreadContext
    // computed from such a buffer redirects the thread into garbage.
    // (jl_thread_suspend_and_get_state's GetThreadContext is what completes
    // the asynchronous SuspendThread.) The profile (read) lock guarantees
    // the thread is not frozen while holding the debuginfo write lock; it
    // is only needed around the suspend itself.
    CONTEXT ctxThread;
    jl_lock_profile();
    int suspended = jl_thread_suspend_and_get_state(tid, 0, &ctxThread);
    jl_unlock_profile();
    if (!suspended) {
        ReleaseSRWLockExclusive(&ctx_rewrite_lock);
        return;
    }
    // Re-check now that the thread cannot run (the current task may have
    // switched before the freeze). Delivery is gated on an actual
    // cancellation of the task's bound token source - coherent with the
    // published regions, since everything that may rebind it while a region
    // is live (exception handlers, the finalizer bracket) restores the pair
    // together.
    ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL)
        goto resume;
    hctx = jl_atomic_load_acquire(&ct2->cancel_handler_ctx);
    if (hctx != NULL) {
        // Handler flavor: a published foreign-call cancellation-handler
        // guard takes priority over (and suppresses) the reset - its span
        // (e.g. a protected allocator) is exactly where a longjmp must not
        // land - for cancellations AND preemptions alike (a preemption
        // stays pending in the polled request byte). For an actual
        // cancellation of the bound token, hijack the thread to run
        // fn(state, sev) on its own stack - at most one delivery at a time
        // per thread (the save area holds one; skips recover
        // level-triggered).
        if (reset_code == JL_RESET_CODE_CANCEL && !ptls2->cancel_handler_armed) {
            bound = jl_atomic_load_relaxed(&ct2->bound_cancel_token);
            if (bound != NULL && bound != jl_nothing &&
                jl_atomic_load_relaxed(&((jl_cancel_source_t*)bound)->state) != 0) {
                jl_cancel_handler_save_t *save = &ptls2->cancel_handler_save;
                save->fn = hctx->fn;
                save->state = hctx->state;
                save->sev = jl_atomic_load_relaxed(&((jl_cancel_source_t*)bound)->state);
                ptls2->cancel_handler_armed = 1;
                jl_win_call_in_context(&ctxThread, (void (*)(void))&jl_win_cancel_handler_trampoline,
                                       (uintptr_t)ptls2);
                ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
                if (!SetThreadContext(ptls2->system_id, &ctxThread))
                    ptls2->cancel_handler_armed = 0;
            }
        }
        goto resume;
    }
    // Reset flavor, additionally gated on the thread running Julia code
    // (gc_state == 0): a thread inside a GC-safe region may be raced by a
    // concurrent stop-the-world, and a redirect back into Julia code would
    // break that protocol.
    if (jl_atomic_load_relaxed(&ptls2->gc_state) != JL_GC_STATE_UNSAFE)
        goto resume;
    reset_ctx = jl_atomic_load_acquire(&ct2->reset_ctx);
    if (reset_ctx == NULL || reset_ctx->sp == 0)
        goto resume;
    if (reset_code == JL_RESET_CODE_CANCEL) {
        // A preempt shootdown checks no source; the re-executed point
        // observes the setjmp return code and yields.
        bound = jl_atomic_load_relaxed(&ct2->bound_cancel_token);
        if (bound == NULL || bound == jl_nothing ||
            jl_atomic_load_relaxed(&((jl_cancel_source_t*)bound)->state) == 0)
            goto resume;
    }
    // Consume the reset point (prevents a double reset; unlike on Unix the
    // consumer here is not the victim thread itself, so use an exchange to
    // arbitrate against concurrent senders) and redirect the thread there,
    // restoring the gcstack and eh recorded at establishment: the interrupt
    // may have landed inside a reset-safe callee whose pushes onto either
    // chain die with the abandoned stack region.
    reset_ctx = jl_atomic_exchange(&ct2->reset_ctx, NULL);
    if (reset_ctx == NULL || reset_ctx->sp == 0)
        goto resume;
    // Compute and install the redirected thread context first, and rewind
    // the task's gcstack/eh chains only once the redirect is committed: if
    // either step fails (e.g. jl_simulate_longjmp is unavailable under
    // sanitizers), the thread must resume exactly as it was, with its
    // deeper chains intact and the (unconsumed) region republished.
    if (!jl_simulate_longjmp(reset_ctx->mctx, &ctxThread, reset_code))
        goto republish;
    ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT;
    if (!SetThreadContext(ptls2->system_id, &ctxThread))
        goto republish;
    // Committed: restore the GC-frame chain head and innermost exception
    // handler recorded at establishment - the interrupt may have landed
    // inside a callee whose pushes onto either chain die with the abandoned
    // stack region.
    ct2->gcstack = reset_ctx->gcstack;
    ct2->eh = reset_ctx->eh;
    goto resume;
republish:
    jl_atomic_store_release(&ct2->reset_ctx, reset_ctx);
resume:
    jl_thread_resume(tid);
    ReleaseSRWLockExclusive(&ctx_rewrite_lock);
}

// Switch the target thread's current (already committed) task to
// ptls->abandon_to (see jl_abandon_task_request): freeze the thread, validate the
// pending request against its frozen state, and on commit redirect it into
// the abandon callback (which never returns). Holds the rewrite lock like
// every other suspend-and-rewrite path; the profile read lock covers the
// suspension itself (see jl_send_reset_signal above).
void jl_send_abandon_signal(int16_t tid) JL_NOTSAFEPOINT
{
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL)
        return;
    AcquireSRWLockExclusive(&ctx_rewrite_lock);
    CONTEXT ctxThread;
    jl_lock_profile();
    int suspended = jl_thread_suspend_and_get_state(tid, 0, &ctxThread);
    jl_unlock_profile();
    if (!suspended) {
        ReleaseSRWLockExclusive(&ctx_rewrite_lock);
        return;
    }
    // The victim thread is frozen: validate the pending request against its
    // state and, on commit, redirect it into the abandon callback. A commit
    // is rolled back to a refusal if the redirect cannot be completed - the
    // callback is what publishes the abandoned task state, so an
    // unredirected victim resumes untouched. On refusal the requester
    // observes the verdict and withdraws.
    if (jl_abandon_try_commit(ptls2)) {
        // Redirect the thread to call jl_abandon_task_cb (which never
        // returns) on a minimal fake frame.
#if defined(_CPU_X86_64_)
        uintptr_t sp = (uintptr_t)ctxThread.Rsp;
        sp = (sp - 256) & ~(uintptr_t)15; // skip resume data, realign
        sp -= sizeof(uintptr_t); // fake return address slot
        *(uintptr_t*)sp = 0;
        ctxThread.Rsp = (DWORD64)sp;
        ctxThread.Rip = (DWORD64)&jl_abandon_task_cb;
#elif defined(_CPU_X86_)
        uintptr_t sp = (uintptr_t)ctxThread.Esp;
        sp = (sp - 64) & ~(uintptr_t)15;
        sp -= sizeof(uintptr_t); // fake return address slot
        *(uintptr_t*)sp = 0;
        ctxThread.Esp = (DWORD)sp;
        ctxThread.Eip = (DWORD)&jl_abandon_task_cb;
#endif
        ctxThread.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
        if (!SetThreadContext(ptls2->system_id, &ctxThread)) {
            // Roll the commit back: nothing observable was published yet
            // (the task state is written by the callback).
            jl_atomic_store_release(&ptls2->abandon_state, JL_ABANDON_REFUSED);
        }
    }
    jl_thread_resume(tid);
    ReleaseSRWLockExclusive(&ctx_rewrite_lock);
}

static BOOL WINAPI sigint_handler(DWORD wsig) //This needs winapi types to guarantee __stdcall
{
    int sig;
    //windows signals use different numbers from unix (raise)
    switch(wsig) {
        case CTRL_C_EVENT: sig = SIGINT; break;
        //case CTRL_BREAK_EVENT: sig = SIGTERM; break;
        // etc.
        default: sig = SIGTERM; break;
    }
    if (!jl_ignore_sigint()) {
        if (exit_on_sigint)
            jl_exit(128 + sig); // 128 + SIGINT
        if (sig == SIGINT) {
            // Deliver the press through the cancellation system (see
            // jl_sigint_request_cancellation).
            jl_sigint_request_cancellation();
        }
        else {
            // Close/logoff/shutdown (and Ctrl+Break): a termination
            // request. Windows kills the process the moment this handler
            // returns for CTRL_CLOSE_EVENT, so run the orderly teardown
            // synchronously here rather than delivering anything to a
            // Julia thread. (Matches unix SIGTERM's exit status.)
            jl_exit(128 + sig);
        }
    }
    return 1;
}

LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
    if (ExceptionInfo->ExceptionRecord->ExceptionFlags != 0)
        return EXCEPTION_CONTINUE_SEARCH;
    jl_task_t *ct = jl_get_current_task();
    if (ct != NULL && ct->ptls != NULL && ct->ptls->gc_state != JL_GC_STATE_WAITING) {
        jl_ptls_t ptls = ct->ptls;
        switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            if (ct->eh != NULL) {
                fpreset();
                jl_throw_in_ctx(ct, jl_diverror_exception, ExceptionInfo->ContextRecord);
                return EXCEPTION_CONTINUE_EXECUTION;
            }
            break;
        case EXCEPTION_STACK_OVERFLOW:
            if (ct->eh != NULL) {
                ptls->needs_resetstkoflw = 1;
                jl_throw_in_ctx(ct, jl_stackovf_exception, ExceptionInfo->ContextRecord);
                return EXCEPTION_CONTINUE_EXECUTION;
            }
            break;
        case EXCEPTION_ACCESS_VIOLATION:
            if (jl_addr_is_safepoint(ExceptionInfo->ExceptionRecord->ExceptionInformation[1])) {
                jl_set_gc_and_wait(ct);
                // (The sigint force-throw that lived here is gone: SIGINT is
                // delivered through the cancellation system - see
                // jl_sigint_request_cancellation - and nothing arms the
                // sigint page anymore.)
                return EXCEPTION_CONTINUE_EXECUTION;
            }
            if (jl_get_safe_restore()) {
                jl_throw_in_ctx(NULL, NULL, ExceptionInfo->ContextRecord);
                return EXCEPTION_CONTINUE_EXECUTION;
            }
            if (ct->eh != NULL) {
                if (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 1) { // writing to read-only memory (e.g. mmap)
                    jl_throw_in_ctx(ct, jl_readonlymemory_exception, ExceptionInfo->ContextRecord);
                    return EXCEPTION_CONTINUE_EXECUTION;
                }
            }
        default:
            break;
        }
    }
    ios_t full_error, summary;
    ios_mem(&full_error, 0);
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
        jl_safe_fprintf(&full_error, "\n");
        jl_fprint_sigill(&full_error, ExceptionInfo->ContextRecord);
    }
    jl_safe_fprintf(&full_error, "\nPlease submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.\n");
    ios_mem(&summary, 128);
    jl_safe_fprintf(&summary, "Exception: ");
    switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
    case EXCEPTION_ACCESS_VIOLATION:
        jl_safe_fprintf(&summary, "EXCEPTION_ACCESS_VIOLATION"); break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        jl_safe_fprintf(&summary, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); break;
    case EXCEPTION_BREAKPOINT:
        jl_safe_fprintf(&summary, "EXCEPTION_BREAKPOINT"); break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        jl_safe_fprintf(&summary, "EXCEPTION_DATATYPE_MISALIGNMENT"); break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_DENORMAL_OPERAND"); break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_DIVIDE_BY_ZERO"); break;
    case EXCEPTION_FLT_INEXACT_RESULT:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_INEXACT_RESULT"); break;
    case EXCEPTION_FLT_INVALID_OPERATION:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_INVALID_OPERATION"); break;
    case EXCEPTION_FLT_OVERFLOW:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_OVERFLOW"); break;
    case EXCEPTION_FLT_STACK_CHECK:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_STACK_CHECK"); break;
    case EXCEPTION_FLT_UNDERFLOW:
        jl_safe_fprintf(&summary, "EXCEPTION_FLT_UNDERFLOW"); break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        jl_safe_fprintf(&summary, "EXCEPTION_ILLEGAL_INSTRUCTION"); break;
    case EXCEPTION_IN_PAGE_ERROR:
        jl_safe_fprintf(&summary, "EXCEPTION_IN_PAGE_ERROR"); break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        jl_safe_fprintf(&summary, "EXCEPTION_INT_DIVIDE_BY_ZERO"); break;
    case EXCEPTION_INT_OVERFLOW:
        jl_safe_fprintf(&summary, "EXCEPTION_INT_OVERFLOW"); break;
    case EXCEPTION_INVALID_DISPOSITION:
        jl_safe_fprintf(&summary, "EXCEPTION_INVALID_DISPOSITION"); break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        jl_safe_fprintf(&summary, "EXCEPTION_NONCONTINUABLE_EXCEPTION"); break;
    case EXCEPTION_PRIV_INSTRUCTION:
        jl_safe_fprintf(&summary, "EXCEPTION_PRIV_INSTRUCTION"); break;
    case EXCEPTION_SINGLE_STEP:
        jl_safe_fprintf(&summary, "EXCEPTION_SINGLE_STEP"); break;
    case EXCEPTION_STACK_OVERFLOW:
        jl_safe_fprintf(&summary, "EXCEPTION_STACK_OVERFLOW"); break;
    default:
        jl_safe_fprintf(&summary, "UNKNOWN"); break;
    }
    jl_safe_fprintf(&summary, " at 0x%zx", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
        ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
        jl_safe_fprintf(&summary, " (%s 0x%zx)",
                        ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 1 ? "writing" :
                        ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 8 ? "executing" : "reading",
                        (size_t)ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
    }
    jl_safe_fprintf(&summary, " -- ");
    jl_fprint_native_codeloc(&summary, (uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress);
    // runtime state that distinguishes crash classes (e.g. a NULL task->ptls
    // from a corrupted pointer) without needing a debugger on the machine
    jl_safe_fprintf(&summary, "current task: 0x%zx (ptls 0x%zx, eh 0x%zx)\n", (size_t)ct,
                    (size_t)(ct == NULL ? NULL : (void*)ct->ptls),
                    (size_t)(ct == NULL ? NULL : (void*)ct->eh));
    ios_write(&full_error, summary.buf, ios_pos(&summary));
    ios_puts("\nSee Application log in Event Viewer for more information.\n", &summary);

    jl_fprint_critical_error(&full_error, 0, 0, ExceptionInfo->ContextRecord, ct);

    // First print to STDERR
    ios_write_direct(ios_safe_stderr, &full_error);

    // Secondly print to Application log
    HANDLE event_source = RegisterEventSourceW(NULL, L"julia");
    if (event_source != INVALID_HANDLE_VALUE) {
        ios_putc('\0', &full_error);
        const wchar_t *strings[] = { ios_utf8_to_wchar(full_error.buf) };
        ReportEventW(
            event_source, EVENTLOG_ERROR_TYPE, /* category */ 0, /* event_id */ (DWORD)0xE0000000L,
           /* user_sid */ NULL, /* n_strings */ 1, /* data_size */ 0, strings, /* data */ NULL
        );
        free((void *)strings[0]);

        if (jl_options.alert_on_critical_error) {
            ios_putc('\0', &summary);
            const wchar_t *message = ios_utf8_to_wchar(summary.buf);
            MessageBoxW(NULL, message, /* title */ L"fatal error in libjulia",
                        MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
            free((void *)message);
        }
    }

    ios_close(&summary);
    ios_close(&full_error);
    static int recursion = 0;
    if (recursion++)
        exit(1);
    else
        jl_exit(1);
}

JL_DLLEXPORT void jl_install_sigint_handler(void)
{
    SetConsoleCtrlHandler((PHANDLER_ROUTINE)sigint_handler,1);
}

static TIMECAPS timecaps;
static HANDLE hBtThread = 0;
static uv_cond_t bt_data_prof_cond = CONDITION_VARIABLE_INIT;

#ifdef _CPU_X86_64_
// Callback data structure for profile timeout
typedef struct {
    _Atomic(int) *abort_ptr;
    int tid;
} profile_timeout_data_t;

static void CALLBACK profile_timeout_cb(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
    profile_timeout_data_t *data = (profile_timeout_data_t*)lpParam;
    if (TimerOrWaitFired && data != NULL && data->abort_ptr != NULL) {
        // Timeout reached, signal an abort should occur
        if (jl_atomic_exchange(data->abort_ptr, 2) == 1) {
            jl_thread_resume(data->tid);
            data->tid = -1;
        }
    }
}
#endif

static int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx)
{
    (void)timeout;
    if (tid < 0 || tid >= jl_atomic_load_acquire(&jl_n_threads))
        return 0;
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL) // this thread is not alive
        return 0;
    jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL) // this thread is already dead
        return 0;
    HANDLE hThread = ptls2->system_id;
    assert(GetCurrentThreadId() != GetThreadId(hThread));
    if ((DWORD)-1 == SuspendThread(hThread)) {
        // jl_safe_fprintf(ios_safe_stderr, "failed to suspend thread %d: %lu\n", tid, GetLastError());
        return 0;
    }
    assert(sizeof(*ctx) == sizeof(CONTEXT));
    memset(ctx, 0, sizeof(CONTEXT));
    ctx->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT;
    if (!GetThreadContext(hThread, ctx)) {
        if ((DWORD)-1 == ResumeThread(hThread))
            abort();
        return 0;
    }
    return 1;
}

void jl_thread_resume(int tid)
{
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    HANDLE hThread = ptls2->system_id;
    if ((DWORD)-1 == ResumeThread(hThread)) {
        jl_safe_fprintf(ios_safe_stderr, "failed to resume main thread! aborting.\n");
        abort();
    }
}

int jl_thread_suspend(int16_t tid, bt_context_t *ctx)
{
    jl_lock_profile(); // prevent concurrent mutation
    uv_mutex_lock(&jl_in_stackwalk); // prevent multi-threaded dbghelp calls
    uv_mutex_lock(&jl_dll_notify_lock);
    jl_profile_process_dll_events();
    int success = jl_thread_suspend_and_get_state(tid, 0, ctx);
    uv_mutex_unlock(&jl_dll_notify_lock);
    uv_mutex_unlock(&jl_in_stackwalk);
    jl_unlock_profile();
    return success;
}

static DWORD WINAPI profile_bt( LPVOID lparam )
{
    // Note: illegal to use jl_* functions from this thread except for profiling-specific functions
    HANDLE hTimerQueue = CreateTimerQueue();
    if (hTimerQueue == NULL) {
        jl_safe_fprintf(ios_safe_stderr, "failed to create profile watchdog timer queue.\n");
        abort();
    }
    while (1) {
        DWORD timeout_ms = nsecprof / (GIGA / 1000);
        Sleep(timeout_ms > 0 ? timeout_ms : 1);
        if (jl_profile_is_buffer_full())
            jl_profile_stop_timer(); // does not change the thread state
        if (!profile_running) {
            uv_mutex_lock(&bt_data_prof_lock);
            while (!profile_running)
                uv_cond_wait(&bt_data_prof_cond, &bt_data_prof_lock);
            uv_mutex_unlock(&bt_data_prof_lock);
        }
        else if (profile_all_tasks) {
            // Don't take the stackwalk lock here since it's already taken in `jl_rec_backtrace`
            jl_profile_task();
        }
        else {
            // Profile all threads, similar to Unix implementation
            bt_context_t c;
            int nthreads = jl_atomic_load_acquire(&jl_n_threads);
            int *randperm = profile_get_randperm(nthreads);
            for (int idx = nthreads; idx-- > 0; ) {
                int tid = randperm[idx];
                if (!profile_running)
                    break;
                if (jl_profile_is_buffer_full()) {
                    jl_profile_stop_timer();
                    break;
                }

                // Set up timeout handler for stackwalk
#ifdef _CPU_X86_64_
                _Atomic(int) abort_profiling = 0;
                profile_timeout_data_t timeout_data;
                timeout_data.abort_ptr = &abort_profiling;
                timeout_data.tid = tid;
                jl_set_profile_abort_ptr(&abort_profiling);
                HANDLE hTimer = NULL;
                if (!CreateTimerQueueTimer(&hTimer, hTimerQueue, profile_timeout_cb,
                                           &timeout_data, 1000 /* milliseconds */, 0,
                                           WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
                    // Failed to register wait, proceed without timeout protection
                    hTimer = NULL;
                }
#endif

                if (!jl_thread_suspend(tid, &c))
                    continue;

                jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
                jl_task_t *t2 = jl_atomic_load_relaxed(&ptls->current_task);
                int state = jl_atomic_load_relaxed(&ptls->sleep_check_state) == 0 ? PROFILE_STATE_THREAD_NOT_SLEEPING : PROFILE_STATE_THREAD_SLEEPING;

                // Get backtrace data
                profile_bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)profile_bt_data_prof + profile_bt_size_cur,
                        profile_bt_size_max - profile_bt_size_cur - 1, &c, NULL);

#ifdef _CPU_X86_64_
                // Clear abort pointer from TLS
                jl_set_profile_abort_ptr(NULL);
                if (timeout_data.tid != -1)
                    jl_thread_resume(tid);
                // Wait for callback to complete or cancel before continuing
                if (hTimer != NULL)
                    DeleteTimerQueueTimer(hTimerQueue, hTimer, INVALID_HANDLE_VALUE);
#else
                jl_thread_resume(tid);
#endif

                // META_OFFSET_THREADID store threadid but add 1 as 0 is preserved to indicate end of block
                profile_bt_data_prof[profile_bt_size_cur++].uintptr = tid + 1;

                // META_OFFSET_TASKID store task id (never null)
                profile_bt_data_prof[profile_bt_size_cur++].jlvalue = (jl_value_t*)t2;

                // META_OFFSET_CPUCYCLECLOCK store cpu cycle clock
                profile_bt_data_prof[profile_bt_size_cur++].uintptr = cycleclock();

                // store whether thread is sleeping (don't ever encode a state as `0` since is preserved to indicate end of block)
                profile_bt_data_prof[profile_bt_size_cur++].uintptr = state;

                // Mark the end of this block with two 0's
                profile_bt_data_prof[profile_bt_size_cur++].uintptr = 0;
                profile_bt_data_prof[profile_bt_size_cur++].uintptr = 0;
            }
            jl_check_profile_autostop();
        }
    }
    // this is unreachable, but would be the relevant cleanup
    uv_mutex_lock(&bt_data_prof_lock);
    hBtThread = NULL;
    uv_mutex_unlock(&bt_data_prof_lock);
    jl_profile_stop_timer();
    DeleteTimerQueue(hTimerQueue);
    return 0;
}

JL_DLLEXPORT int jl_profile_start_timer(uint8_t all_tasks)
{
    uv_mutex_lock(&bt_data_prof_lock);
    if (hBtThread == NULL) {
        TIMECAPS _timecaps;
        if (MMSYSERR_NOERROR != timeGetDevCaps(&_timecaps, sizeof(_timecaps))) {
            uv_mutex_unlock(&bt_data_prof_lock);
            jl_safe_fprintf(ios_safe_stderr, "failed to get timer resolution.\n");
            return -2;
        }
        timecaps = _timecaps;

        hBtThread = CreateThread(
            NULL,                   // default security attributes
            0,                      // use default stack size
            profile_bt,             // thread function name
            0,                      // argument to thread function
            0,                      // use default creation flags
            0);                     // returns the thread identifier
        if (hBtThread == NULL) {
            uv_mutex_unlock(&bt_data_prof_lock);
            jl_safe_fprintf(ios_safe_stderr, "failed to allocate profile thread.\n");
            return -1;
        }
        (void)SetThreadPriority(hBtThread, THREAD_PRIORITY_ABOVE_NORMAL);
    }
    if (profile_running == 0) {
        // Failure to change the timer resolution is not fatal. However, it is important to
        // ensure that the timeBeginPeriod/timeEndPeriod is paired.
        if (TIMERR_NOERROR != timeBeginPeriod(timecaps.wPeriodMin))
            timecaps.wPeriodMin = 0;
    }
    profile_all_tasks = all_tasks;
    profile_running = 1; // set `profile_running` finally
    uv_cond_broadcast(&bt_data_prof_cond);
    uv_mutex_unlock(&bt_data_prof_lock);
    return 0;
}
JL_DLLEXPORT void jl_profile_stop_timer(void)
{
    uv_mutex_lock(&bt_data_prof_lock);
    if (profile_running && timecaps.wPeriodMin)
        timeEndPeriod(timecaps.wPeriodMin);
    profile_running = 0;
    profile_all_tasks = 0;
    uv_mutex_unlock(&bt_data_prof_lock);
}

void jl_install_default_signal_handlers(void)
{
    if (signal(SIGFPE, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGFPE");
    }
    if (signal(SIGILL, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGILL");
    }
    if (signal(SIGINT, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGINT");
    }
    if (signal(SIGSEGV, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGSEGV");
    }
    if (signal(SIGTERM, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGTERM");
    }
    if (signal(SIGABRT, (void (__cdecl *)(int))crt_sig_handler) == SIG_ERR) {
        jl_error("fatal error: Couldn't set SIGABRT");
    }
    SetUnhandledExceptionFilter(jl_exception_handler);
    jl_win_init_cancel_handler_delivery();
}

void jl_install_thread_signal_handler(jl_ptls_t ptls)
{
    if (!have_backtrace_fiber) {
        size_t ssize = sig_stack_size;
        void *stk = jl_malloc_stack(&ssize, NULL);
        if (stk == NULL)
            jl_errorf("fatal error allocating signal stack: mmap: %s", strerror(errno));
        collect_backtrace_fiber.uc_stack.ss_sp = (void*)stk;
        collect_backtrace_fiber.uc_stack.ss_size = ssize;
        jl_makecontext(&collect_backtrace_fiber, start_backtrace_fiber);
        uv_mutex_init(&backtrace_lock);
        have_backtrace_fiber = 1;
    }
}

JL_DLLEXPORT void jl_membarrier(void) JL_NOTSAFEPOINT {
    FlushProcessWriteBuffers();
}
