//===--- CrashHandlerWindows.cpp - Swift crash handler for Windows ------- ===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// The Windows crash handler implementation.
//
//===----------------------------------------------------------------------===//

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "swift/Runtime/Backtrace.h"
#include "BacktracePrivate.h"

using namespace swift::runtime::backtrace;

namespace {

SRWLOCK crashLock = SRWLOCK_INIT;

CrashInfo crashInfo;

void *altStackTop;

void *pcFromContext(PCONTEXT Context);
LONG NTAPI handleException(EXCEPTION_POINTERS *ExceptionInfo);
LONG NTAPI reinstallUnhandledExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo);
LONG callWithAltStack(EXCEPTION_POINTERS *arg,
                      LONG (*fn)(EXCEPTION_POINTERS *));
LONG reallyHandleException(EXCEPTION_POINTERS *ExceptionInfo);

}

namespace swift {
namespace runtime {
namespace backtrace {

SWIFT_RUNTIME_STDLIB_INTERNAL ErrorCode
_swift_installCrashHandler()
{
  // Allocate an alternate stack to use when handling exceptions
  const size_t altStackSize = 65536;
  void *altStackBase = VirtualAlloc(NULL, altStackSize,
                                    MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  altStackTop = (char *)altStackBase + altStackSize;

  // Install the exception handler
  AddVectoredExceptionHandler(0, reinstallUnhandledExceptionFilter);
  SetUnhandledExceptionFilter(handleException);

  return ERROR_SUCCESS;
}

} // namespace backtrace
} // namespace runtime
} // namespace swift

namespace {

LPTOP_LEVEL_EXCEPTION_FILTER pOldFilter = NULL;
bool reinstalled = false;

// Yuck.  This is horrible because the C runtime's startup code sets the
// unhandled exception filter, but that happens *after*
// _swift_installCrashHandler() has run because *that* is called during DLL
// initialization.
//
// Since we don't have a top-of-main() entry point for Swift, for now we
// hack things by reinstalling our filter from the vectored exception
// handler.  This is utterly horrible.
LONG reinstallUnhandledExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo) {
  if (reinstalled)
    return EXCEPTION_CONTINUE_SEARCH;

  reinstalled = true;

  LPTOP_LEVEL_EXCEPTION_FILTER pFilter = SetUnhandledExceptionFilter(handleException);

  if (pFilter != handleException)
    pOldFilter = pFilter;
  return EXCEPTION_CONTINUE_SEARCH;
}

// Call a function, passing an argument, but using the alternate stack
LONG callWithAltStack(EXCEPTION_POINTERS *arg,
                      LONG (*fn)(EXCEPTION_POINTERS *)) {
#if defined(__x86_64__)
  register LONG result __asm__("rax");

  asm volatile (
    "    xchg  %%rsp, %3;      \
         subq  $16, %%rsp;     \
         movq  %3, (%%rsp);    \
         callq *%2;            \
         movq  (%%rsp), %%rsp; \
    "
    : "=a" (result)
    : "c" (arg), "d" (fn), "r" (altStackTop)
    : "memory"
  );

  return result;
#elif defined(__arm64__)
  register LONG result __asm__("x0");
  register LONG (*fn_reg)(EXCEPTION_POINTERS *) __asm__("x1");

  asm volatile (
    "    str sp, [%3, -16]!; \
         mov sp, %3;         \
         bl  %2;             \
         ldr sp, [sp];       \
    "
    : "=r" (result)
    : "0" (arg), "r" (fn_reg), "r" (altStackTop)
    : "memory"
  );

  return result;
#else
#warning You should fill this out with code for your architecture
  // Don't actually switch stack
  return fn(arg);
#endif
}

LONG handleException(EXCEPTION_POINTERS *ExceptionInfo) {
  LONG result;

  switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
  case DBG_PRINTEXCEPTION_C:
  case DBG_PRINTEXCEPTION_WIDE_C:
  case 0x406d1388:      // Set debugger thread name
    result = EXCEPTION_CONTINUE_EXECUTION;
    break;
  default:
    AcquireSRWLockExclusive(&crashLock);
    result = callWithAltStack(ExceptionInfo, reallyHandleException);
    ReleaseSRWLockExclusive(&crashLock);
    break;
  }

  if (pOldFilter && result == EXCEPTION_CONTINUE_SEARCH) {
    return pOldFilter(ExceptionInfo);
  }

  return result;
}

LONG reallyHandleException(EXCEPTION_POINTERS *ExceptionInfo) {
  HANDLE hOutput;
  if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
    hOutput = GetStdHandle(STD_ERROR_HANDLE);
  else
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

  crashInfo.crashing_thread = GetCurrentThreadId();
  crashInfo.signal = ExceptionInfo->ExceptionRecord->ExceptionCode;

  // For access violations or in-page-errors, the fault address is the
  // address of the inaccessible data; for others, it's the instruction
  // address.  We do this to match the behaviour on other platforms.
  switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
  case EXCEPTION_ACCESS_VIOLATION:
  case EXCEPTION_IN_PAGE_ERROR:
    crashInfo.fault_address = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
    break;
  default:
    crashInfo.fault_address = (uint64_t)(ExceptionInfo->ExceptionRecord->ExceptionAddress);
    break;
  }

  crashInfo.exception_info = (uint64_t)ExceptionInfo;

  _swift_displayCrashMessage(ExceptionInfo->ExceptionRecord->ExceptionCode,
                             pcFromContext(ExceptionInfo->ContextRecord));

  // It isn't safe to try to stop all the threads here on Windows, so we
  // delegate doing that to the backtracer process.

  // Also, closing open file descriptors is
  // not something we can easily do on Windows.

  if (!_swift_spawnBacktracer(&crashInfo)) {
    const char *message = _swift_backtraceSettings.color == OnOffTty::On
      ? " failed\n\n" : " failed ***\n\n";
    WriteFile(hOutput, message, strlen(message), NULL, NULL);
  }

  return EXCEPTION_CONTINUE_SEARCH;
}

void *pcFromContext(PCONTEXT Context) {
#if defined(_M_X64)
  return (void *)(Context->Rip);
#elif defined(_M_IX86)
  return (void *)(Context->Eip);
#elif defined(_M_ARM) || defined(_M_ARM64)
  return (void *)(Context->Pc);
#endif
}

} // namespace

#endif // _WIN32
