#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <time.h>
#include <unistd.h>

#define BT_BUF_SIZE 128

/* just print backtrace */
void dump_current_backtrace() {
  int nptrs;
  void *buffer[BT_BUF_SIZE];
  char **strings;

  nptrs = backtrace(buffer, BT_BUF_SIZE);
  strings = backtrace_symbols(buffer, nptrs);
  if (strings == NULL) {
    perror("backtrace_symbols");
    exit(EXIT_FAILURE);
  }

  for (int j = 0; j < nptrs; j++)
    printf("  %s\n", strings[j]);
  printf("\n");

  free(strings);
}

static int errstatic(int l) {
  printf("errstatic(): %d\n", l); 
  dump_current_backtrace();
  return 3;
}

/* PG uses #define pg_attribute_cold __attribute__((cold)) */
__attribute__ ((cold)) int err_cold(int l) { 
  printf("err_cold(): %d\n", l); 
  errstatic(l);
  return 0;
}

/* it's the same but hot */
int err(int l) { 
  printf("err(): %d\n", l); 
  errstatic(l);
  return 0;
}

void __attribute__ ((noinline)) err_finish(int level) {
  printf("finishing demo() macro\n");
  if(level == 3)
    exit(3);
}

/*
 * Try to mimic what PG does in demo() macro
 */

/*
 * elog.h says:
 * When __builtin_constant_p is available and elevel >= ERROR we make a call
 * to errstart_cold() instead of errstart().  This version of the function is
 * marked with pg_attribute_cold which will coax supporting compilers into
 * generating code which is more optimized towards non-ERROR cases.  Because
 * we use __builtin_constant_p() in the condition, when elevel is not a
 * compile-time constant, or if it is, but it's < ERROR, the compiler has no
 * need to generate any code for this branch.  It can simply call errstart()
 * unconditionally.
 */

#define demo(level)                                                            \
  do {                                                                         \
    if (__builtin_constant_p(level) && (level) >= 2 ? err_cold(level) : err(level)) {                            \
      err_finish(level);                                                       \
    }                                                                          \
    if (__builtin_constant_p(level) && (level) >= 3) {                         \
      __builtin_unreachable();                                                 \
    }                                                                          \
  } while (0)


int f() {
  if(getpid() % 2 != 0)
    demo(3);
  for(int i = 0; i < 10; i++) {
    usleep(10);
    demo(1);
  }
  return 42;
}

/* this serves a purpose to just generate more symbols/use more addresses */
int somemisleading_another_f()
{
  if(getpid() % 99 == 0)
    demo(3);
  return 42;
}

int main() {
  int r;
  printf("starting f(), NOT yet in somemisleading_another_f() !\n");
  r = f();
  printf("%d\n", r*2);
  printf("starting somemisleading_another_f()\n");
  r = somemisleading_another_f();
  printf("%d\n", r*2);
  return 0;
}

