/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2023 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#ifndef __x86_64__

// todo(jart): dismal llvm
register long x0 asm("x0");
register long x1 asm("x1");
register long x2 asm("x2");
register long x3 asm("x3");
register long x4 asm("x4");
register long x5 asm("x5");
register long sysv_ordinal asm("x8");
register long freebsd_ordinal asm("x9");
register long xnu_ordinal asm("x16");
register long cosmo_tls_register asm("x28");

void report_cancelation_point(int, int);

dontinline long systemfive_cancel(void) {
  return _weaken(_pthread_cancel_ack)();
}

// special region of executable memory where cancelation is safe
dontinline long systemfive_cancellable(void) {

  // check (1) this is a cancelation point
  // plus (2) cancelations aren't disabled
  struct PosixThread *pth = 0;
  if (cosmo_tls_register &&            //
      _weaken(_pthread_cancel_ack) &&  //
      (pth = _pthread_self())) {
    // check if cancelation is already pending
    if (!(pth->pt_flags & PT_NOCANCEL) &&
        atomic_load_explicit(&pth->pt_canceled, memory_order_acquire)) {
      return systemfive_cancel();
    }
#if IsModeDbg()
    if (!(pth->pt_flags & PT_INCANCEL) && !(pth->pt_flags & PT_NOCANCEL)) {
      if (_weaken(report_cancelation_point)) {
        _weaken(report_cancelation_point)(sysv_ordinal, xnu_ordinal);
      }
      __builtin_trap();
    }
#endif
  }

  // invoke cancellable system call
  // this works for both linux and bsd
  asm volatile("mov\tx9,0\n\t"      // clear carry flag (for linux)
               "adds\tx9,x9,0\n\t"  // clear carry flag
               "svc\t0\n"
               "systemfive_cancellable_end:\n\t"
               ".globl\tsystemfive_cancellable_end\n\t"
               "bcs\t1f\n\t"
               "b\t2f\n1:\t"
               "neg\tx0,x0\n2:"
               : /* global output */
               : /* global inputs */
               : "x9", "memory");

  // if it succeeded then we're done
  if (x0 < -4095ul) {
    return x0;
  }

  // check if i/o call was interrupted by sigthr
  if (pth && x0 == -EINTR && !(pth->pt_flags & PT_NOCANCEL) &&
      atomic_load_explicit(&pth->pt_canceled, memory_order_acquire)) {
    return systemfive_cancel();
  }

  // otherwise go down error path
  return _sysret(x0);
}

/**
 * System Five System Call Support.
 *
 * This supports POSIX thread cancelation only when the caller flips a
 * bit in TLS storage that indicates we're inside a cancelation point.
 *
 * @param x0 is first argument
 * @param x1 is second argument
 * @param x2 is third argument
 * @param x3 is fourth argument
 * @param x4 is fifth argument
 * @param x5 is sixth argument
 * @param sysv_ordinal is linux ordinal
 * @param xnu_ordinal is xnu ordinal
 * @return x0
 */
long systemfive(void) {

  // handle special cases
  if (IsLinux() || IsFreebsd()) {
    if (IsFreebsd()) {
      sysv_ordinal = freebsd_ordinal;
    }
    if (sysv_ordinal == 0xfff) {
      return _sysret(-ENOSYS);
    }
    if (sysv_ordinal & 0x800) {
      sysv_ordinal &= ~0x800;
      return systemfive_cancellable();
    }
  }
  if (IsXnu()) {
    if (xnu_ordinal == 0xfff) {
      return _sysret(-ENOSYS);
    }
    if (xnu_ordinal & 0x800) {
      xnu_ordinal &= ~0x800;
      return systemfive_cancellable();
    }
  }

  // invoke non-blocking system call
  // this works for both linux and bsd
  asm volatile("svc\t0\n\t"
               "bcs\t1f\n\t"
               "b\t2f\n1:\t"
               "neg\tx0,x0\n2:"
               : /* global output */
               : /* global inputs */
               : "x9", "memory");

  // check result
  if (x0 < -4095ul) {
    return x0;
  } else {
    return _sysret(x0);
  }
}

#endif /* __x86_64__ */
