/*-*- 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/assert.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/nexgen32e/nexgen32e.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/runtime.h"
#include "libc/stdalign.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"

typedef double vect __attribute__((__vector_size__(16), __aligned__(16)));

struct Gadget {
  void (*func)(long, long, long, long, long, long,  //
               vect, vect, vect, vect, vect, vect);
  long longs[6];
  vect vects[6];
};

static void runcontext(struct Gadget *call, ucontext_t *link) {
  call->func(call->longs[0], call->longs[1], call->longs[2], call->longs[3],
             call->longs[4], call->longs[5], call->vects[0], call->vects[1],
             call->vects[2], call->vects[3], call->vects[4], call->vects[5]);
  if (link) {
    setcontext(link);
    abort();
  } else {
    pthread_exit(0);
  }
}

/**
 * Creates coroutine gadget, e.g.
 *
 *     ucontext_t uc;
 *     getcontext(&uc);
 *     uc.uc_link = 0;
 *     uc.uc_stack.ss_sp = NewCosmoStack();
 *     uc.uc_stack.ss_size = GetStackSize();
 *     makecontext(&uc, exit, 1, 42);
 *     setcontext(&uc);
 *
 * Is equivalent to:
 *
 *     exit(42);
 *
 * The safest way to allocate stack memory is to use NewCosmoStack() and
 * GetStackSize(), which will mmap() a fresh region of memory per a link
 * time configuration, mprotect() some guard pages at the bottom, poison
 * them if ASAN is in play, and then tell the OS that it's stack memory.
 * If that's overkill for your use case, then you could potentially pass
 * stacks as small as 1024 bytes; however they need to come from a stack
 * allocation Cosmo granted to your main process and threads. It needn't
 * be aligned, since this function takes care of that automatically. The
 * address selected shall be `uc_stack.ss_ip + uc_stack.ss_size` and all
 * the action happens beneath that address.
 *
 * On AMD64 and ARM64 you may pass up to six `long` integer args, and up
 * to six vectors (e.g. double, floats, __m128i, uint8x16_t). Thou shall
 * not call code created by Microsoft compilers, even though this should
 * work perfectly fine on Windows, as it is written in the System V ABI,
 * which specifies your parameters are always being passed in registers.
 *
 * @param uc stores processor state; the caller must have:
 *     1. initialized it using `getcontext(uc)`
 *     2. allocated new values for `uc->uc_stack`
 *     3. specified a successor context in `uc->uc_link`
 * @param func is the function to call when `uc` is activated;
 *     when `func` returns, control is passed to `uc->uc_link`,
 *     which if null will result in pthread_exit() being called
 * @param argc is effectively ignored (see notes above)
 * @see setcontext(), getcontext(), swapcontext()
 */
void makecontext(ucontext_t *uc, void *func, int argc, ...) {
  va_list va;
  long sp, sb;
  struct Gadget *call;

  // allocate call
  sp = sb = (long)uc->uc_stack.ss_sp;
  sp += uc->uc_stack.ss_size;
  sp -= 16;  // openbsd:stackbound
  sp -= sizeof(*call);
  sp &= -alignof(struct Gadget);
  call = (struct Gadget *)sp;

  // get arguments
  call->func = func;
  va_start(va, argc);
  call->longs[0] = va_arg(va, long);
  call->longs[1] = va_arg(va, long);
  call->longs[2] = va_arg(va, long);
  call->longs[3] = va_arg(va, long);
  call->longs[4] = va_arg(va, long);
  call->longs[5] = va_arg(va, long);
  call->vects[0] = va_arg(va, vect);
  call->vects[1] = va_arg(va, vect);
  call->vects[2] = va_arg(va, vect);
  call->vects[3] = va_arg(va, vect);
  call->vects[4] = va_arg(va, vect);
  call->vects[5] = va_arg(va, vect);
  va_end(va);

  // constructs fake function call on new stack
  //
  // the location where getcontext() was called shall be the previous
  // entry in the backtrace when runcontext was called, e.g.
  //
  //     1000800fff90 405299 func+58
  //     1000800fffb0 40d98c runcontext+42
  //     1000800fffd0 40b308 makecontext_backtrace+20
  //     7fff22a7ff50 40c2d5 testlib_runtestcases+218
  //     7fff22a7ff90 40b1c3 testlib_runalltests+79
  //     7fff22a7ffa0 4038cb main+475
  //     7fff22a7ffe0 403cfb cosmo+69
  //     7fff22a7fff0 4034e2 _start+137
  //
  // is about what it should look like.
  sp &= -(sizeof(long) * 2);
#ifdef __x86__
  *(long *)(sp -= sizeof(long)) = uc->uc_mcontext.PC;
#elif defined(__aarch64__)
  *(long *)(sp -= sizeof(long)) = uc->uc_mcontext.regs[30];
  *(long *)(sp -= sizeof(long)) = uc->uc_mcontext.regs[29];
  uc->uc_mcontext.BP = uc->uc_mcontext.SP;
#else
#error "unsupported architecture"
#endif

  // program context
  uc->uc_mcontext.SP = sp;
  uc->uc_mcontext.PC = (long)runcontext;
  uc->uc_mcontext.ARG0 = (long)call;
  uc->uc_mcontext.ARG1 = (long)uc->uc_link;
}
