/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 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/struct/sigaction.h"
#include "libc/log/check.h"
#include "libc/runtime/gc.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
#include "tool/build/lib/divmul.h"
#include "tool/build/lib/endian.h"
#include "tool/build/lib/flags.h"

#define CX     1
#define OSZ    00000000040
#define REXW   00000000100
#define RM(x)  (0000001600 & ((x) << 007))
#define MOD(x) (0060000000 & ((x) << 026))

jmp_buf sigfpejmp;
struct Machine m[1];
struct sigaction oldsigfpe[1];
struct XedDecodedInst xedd[1];

void OnSigFpe(void) {
  /* ProTip: gdb -ex 'handle SIGFPE nostop noprint pass' */
  longjmp(sigfpejmp, 1);
}

void SetUp(void) {
  m->xedd = xedd;
  CHECK_NE(-1, xsigaction(SIGFPE, OnSigFpe, SA_NODEFER, 0, oldsigfpe));
}

void TearDown(void) {
  m->xedd = xedd;
  CHECK_NE(-1, sigaction(SIGFPE, oldsigfpe, NULL));
}

TEST(imul8, test) {
  static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
  int i, j;
  int16_t ax;
  bool cf, of;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write8(m->ax, A[i]);
      Write8(m->cx, A[j]);
      OpMulAxAlEbSigned(m, MOD(3) | RM(CX));
      asm volatile("imulb\t%3"
                   : "=a"(ax), "=@ccc"(cf), "=@cco"(of)
                   : "q"(A[j]), "0"(A[i])
                   : "cc");
      EXPECT_EQ(ax, (int16_t)Read16(m->ax));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(imul16, test) {
  static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, 0x8001, 0x7FFE,
                               0xFFFF, 0xBeef, 0x00b5, 0x00b6, 0xb504, 0xb505};
  int i, j;
  bool cf, of;
  uint16_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write16(m->ax, A[i]);
      Write16(m->cx, A[j]);
      OpMulRdxRaxEvqpSigned(m, OSZ | MOD(3) | RM(CX));
      asm("imulw\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((int32_t)((uint32_t)dx << 16 | ax),
                (int32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(imul32, test) {
  static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef,
                               0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
  int i, j;
  bool cf, of;
  uint32_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write32(m->ax, A[i]);
      Write32(m->cx, A[j]);
      OpMulRdxRaxEvqpSigned(m, MOD(3) | RM(CX));
      asm("imull\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((int64_t)((uint64_t)dx << 32 | ax),
                (int64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(imul64, test) {
  static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef,
                               0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
  int i, j;
  bool cf, of;
  uint64_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write64(m->ax, A[i]);
      Write64(m->cx, A[j]);
      OpMulRdxRaxEvqpSigned(m, REXW | MOD(3) | RM(CX));
      asm("imulq\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((int128_t)((uint128_t)dx << 64 | ax),
                (int128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(mul8, test) {
  static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xb6};
  int i, j;
  uint16_t ax;
  bool cf, of;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write8(m->ax, A[i]);
      Write8(m->cx, A[j]);
      OpMulAxAlEbUnsigned(m, MOD(3) | RM(CX));
      asm volatile("mulb\t%3"
                   : "=a"(ax), "=@ccc"(cf), "=@cco"(of)
                   : "q"(A[j]), "0"(A[i])
                   : "cc");
      EXPECT_EQ(ax, Read16(m->ax));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(mul16, test) {
  static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
                               0x8001, 0x7FFE, 0xFFFF, 0x00b6};
  int i, j;
  bool cf, of;
  uint16_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write16(m->ax, A[i]);
      Write16(m->cx, A[j]);
      OpMulRdxRaxEvqpUnsigned(m, OSZ | MOD(3) | RM(CX));
      asm("mulw\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((uint32_t)((uint32_t)dx << 16 | ax),
                (uint32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(mul32, test) {
  static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b5,
                               0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
  int i, j;
  bool cf, of;
  uint32_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write32(m->ax, A[i]);
      Write32(m->cx, A[j]);
      OpMulRdxRaxEvqpUnsigned(m, MOD(3) | RM(CX));
      asm("mull\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((uint64_t)((uint64_t)dx << 32 | ax),
                (uint64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(mul64, test) {
  static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b6,
                               0x0000b504, 0x0000b505, 0xb504f333, 0xb504f334};
  int i, j;
  bool cf, of;
  uint64_t dx, ax;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      Write64(m->ax, A[i]);
      Write64(m->cx, A[j]);
      OpMulRdxRaxEvqpUnsigned(m, REXW | MOD(3) | RM(CX));
      asm("mulq\t%4"
          : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
          : "r"(A[j]), "1"(A[i])
          : "cc");
      EXPECT_EQ((uint128_t)((uint128_t)dx << 64 | ax),
                (uint128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax)));
      EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
      EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
    }
  }
}

TEST(idiv8, test) {
  if (IsWindows()) return; /* TODO */
  static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
  uint16_t remquo;
  bool gotthrow, gotsigfpe;
  int8_t i, j, k, w, x, a, b;
  int8_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        m->ax[1] = A[i];
        m->ax[0] = A[j];
        m->cx[0] = A[k];
        gotthrow = false;
        gotsigfpe = false;
        if (!setjmp(m->onhalt)) {
          OpDivAlAhAxEbSigned(m, MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          asm("idivb\t%1"
              : "=a"(remquo)
              : "q"(A[k]), "0"((int16_t)(A[i] << 8 | A[j]))
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          quotient = (int8_t)remquo;
          remainder = (int8_t)(remquo >> 8);
          EXPECT_EQ(quotient, (int8_t)m->ax[0]);
          EXPECT_EQ(remainder, (int8_t)m->ax[1]);
        }
      }
    }
  }
}

TEST(idiv16, test) {
  if (IsWindows()) return; /* TODO */
  static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
                               0x8001, 0x7FFE, 0xFFFF, 0xBeef};
  bool gotthrow, gotsigfpe;
  int16_t i, j, k, w, x, a, b;
  int16_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 2);
        memcpy(m->ax, &A[j], 2);
        memcpy(m->cx, &A[k], 2);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpSigned(m, OSZ | MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("idivw\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (int16_t)Read16(m->ax));
          EXPECT_EQ(remainder, (int16_t)Read16(m->dx));
        }
      }
    }
  }
}

TEST(idiv32, test) {
  if (IsWindows()) return; /* TODO */
  static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef};
  bool gotthrow, gotsigfpe;
  int32_t i, j, k, w, x, a, b;
  int32_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 4);
        memcpy(m->ax, &A[j], 4);
        memcpy(m->cx, &A[k], 4);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpSigned(m, MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("idivl\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (int32_t)Read32(m->ax));
          EXPECT_EQ(remainder, (int32_t)Read32(m->dx));
        }
      }
    }
  }
}

TEST(idiv64, test) {
  if (IsWindows()) return; /* TODO */
  static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001,
                               0x8000000000000000, 0x7FFFFFFFFFFFFFFF,
                               0x8000000000000001, 0x7FFFFFFFFFFFFFFE,
                               0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00};
  bool gotthrow, gotsigfpe;
  int64_t i, j, k, w, x, a, b;
  int64_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 8);
        memcpy(m->ax, &A[j], 8);
        memcpy(m->cx, &A[k], 8);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpSigned(m, REXW | MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("idivq\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (int64_t)Read64(m->ax));
          EXPECT_EQ(remainder, (int64_t)Read64(m->dx));
        }
      }
    }
  }
}

TEST(div, test) {
  if (IsWindows()) return; /* TODO */
  static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
  uint16_t remquo;
  bool gotthrow, gotsigfpe;
  uint8_t i, j, k, w, x, a, b;
  uint8_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        m->ax[1] = A[i];
        m->ax[0] = A[j];
        m->cx[0] = A[k];
        gotthrow = false;
        gotsigfpe = false;
        if (!setjmp(m->onhalt)) {
          OpDivAlAhAxEbUnsigned(m, MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          asm("divb\t%1"
              : "=a"(remquo)
              : "q"(A[k]), "0"((uint16_t)(A[i] << 8 | A[j]))
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          quotient = (uint8_t)remquo;
          remainder = (uint8_t)(remquo >> 8);
          EXPECT_EQ(quotient, (uint8_t)m->ax[0]);
          EXPECT_EQ(remainder, (uint8_t)m->ax[1]);
        }
      }
    }
  }
}

TEST(div16, test) {
  if (IsWindows()) return; /* TODO */
  static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
                               0x8001, 0x7FFE, 0xFFFF, 0xBeef};
  bool gotthrow, gotsigfpe;
  uint16_t i, j, k, w, x, a, b;
  uint16_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 2);
        memcpy(m->ax, &A[j], 2);
        memcpy(m->cx, &A[k], 2);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpUnsigned(m, OSZ | MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("divw\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (uint16_t)Read16(m->ax));
          EXPECT_EQ(remainder, (uint16_t)Read16(m->dx));
        }
      }
    }
  }
}

TEST(div32, test) {
  if (IsWindows()) return; /* TODO */
  static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
                               0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef};
  bool gotthrow, gotsigfpe;
  uint32_t i, j, k, w, x, a, b;
  uint32_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 4);
        memcpy(m->ax, &A[j], 4);
        memcpy(m->cx, &A[k], 4);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpUnsigned(m, MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("divl\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (uint32_t)Read32(m->ax));
          EXPECT_EQ(remainder, (uint32_t)Read32(m->dx));
        }
      }
    }
  }
}

TEST(div64, test) {
  if (IsWindows()) return; /* TODO */
  static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001,
                               0x8000000000000000, 0x7FFFFFFFFFFFFFFF,
                               0x8000000000000001, 0x7FFFFFFFFFFFFFFE,
                               0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00};
  bool gotthrow, gotsigfpe;
  uint64_t i, j, k, w, x, a, b;
  uint64_t quotient, remainder;
  for (i = 0; i < ARRAYLEN(A); ++i) {
    for (j = 0; j < ARRAYLEN(A); ++j) {
      for (k = 0; k < ARRAYLEN(A); ++k) {
        memcpy(m->dx, &A[i], 8);
        memcpy(m->ax, &A[j], 8);
        memcpy(m->cx, &A[k], 8);
        if (!setjmp(m->onhalt)) {
          gotthrow = false;
          OpDivRdxRaxEvqpUnsigned(m, REXW | MOD(3) | RM(CX));
        } else {
          gotthrow = true;
        }
        if (!setjmp(sigfpejmp)) {
          gotsigfpe = false;
          asm("divq\t%2"
              : "=d"(remainder), "=a"(quotient)
              : "r"(A[k]), "0"(A[i]), "1"(A[j])
              : "cc");
        } else {
          gotsigfpe = true;
        }
        EXPECT_EQ(gotsigfpe, gotthrow);
        if (!gotsigfpe && !gotthrow) {
          EXPECT_EQ(quotient, (uint64_t)Read64(m->ax));
          EXPECT_EQ(remainder, (uint64_t)Read64(m->dx));
        }
      }
    }
  }
}