// TODO: Investigate these failures
// XFAIL: asan, tsan, ubsan

// TODO: Investigate these failures on x86_64 macOS
// XFAIL: target=x86_64-apple-darwin{{.+}}

// TODO: Investigate this failure
// XFAIL: 32bits-on-64bits

#include <libunwind.h>
#include <stdlib.h>
#include <string.h>

void backtrace(int lower_bound) {
  unw_context_t context;
  unw_getcontext(&context);

  unw_cursor_t cursor;
  unw_init_local(&cursor, &context);

  int n = 0;
  do {
    ++n;
    if (n > 100) {
      abort();
    }
  } while (unw_step(&cursor) > 0);

  if (n < lower_bound) {
    abort();
  }
}

void test1(int i) {
  backtrace(i);
}

void test2(int i, int j) {
  backtrace(i);
  test1(j);
}

void test3(int i, int j, int k) {
  backtrace(i);
  test2(j, k);
}

void test_no_info() {
  unw_context_t context;
  unw_getcontext(&context);

  unw_cursor_t cursor;
  unw_init_local(&cursor, &context);

  unw_proc_info_t info;
  int ret = unw_get_proc_info(&cursor, &info);
  if (ret != UNW_ESUCCESS)
    abort();

  // Set the IP to an address clearly outside any function.
  unw_set_reg(&cursor, UNW_REG_IP, (unw_word_t)0);

  ret = unw_get_proc_info(&cursor, &info);
  if (ret != UNW_ENOINFO)
    abort();
}

void test_reg_names() {
  unw_context_t context;
  unw_getcontext(&context);

  unw_cursor_t cursor;
  unw_init_local(&cursor, &context);

  int max_reg_num = -100;
#if defined(__i386__)
  max_reg_num = 7;
#elif defined(__x86_64__)
  max_reg_num = 32;
#endif

  const char prefix[] = "unknown";
  for (int i = -2; i < max_reg_num; ++i) {
    if (strncmp(prefix, unw_regname(&cursor, i), sizeof(prefix) - 1) == 0)
      abort();
  }

  if (strncmp(prefix, unw_regname(&cursor, max_reg_num + 1),
              sizeof(prefix) - 1) != 0)
    abort();
}

#if defined(__x86_64__)
void test_reg_get_set() {
  unw_context_t context;
  unw_getcontext(&context);

  unw_cursor_t cursor;
  unw_init_local(&cursor, &context);

  for (int i = 0; i < 17; ++i) {
    const unw_word_t set_value = 7;
    if (unw_set_reg(&cursor, i, set_value) != UNW_ESUCCESS)
      abort();

    unw_word_t get_value = 0;
    if (unw_get_reg(&cursor, i, &get_value) != UNW_ESUCCESS)
      abort();

    if (set_value != get_value)
      abort();
  }
}

void test_fpreg_get_set() {
  unw_context_t context;
  unw_getcontext(&context);

  unw_cursor_t cursor;
  unw_init_local(&cursor, &context);

  // get/set is not implemented for x86_64 fpregs.
  for (int i = 17; i < 33; ++i) {
    const unw_fpreg_t set_value = 7;
    if (unw_set_fpreg(&cursor, i, set_value) != UNW_EBADREG)
      abort();

    unw_fpreg_t get_value = 0;
    if (unw_get_fpreg(&cursor, i, &get_value) != UNW_EBADREG)
      abort();
  }
}
#else
void test_reg_get_set() {}
void test_fpreg_get_set() {}
#endif

int main(int, char**) {
  test1(1);
  test2(1, 2);
  test3(1, 2, 3);
  test_no_info();
  test_reg_names();
  test_reg_get_set();
  test_fpreg_get_set();
  return 0;
}
