#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <stdint.h>

typedef uint8_t byte;
typedef uint32_t val_t;

#define ITERATIONS 1
#define LOOP_COUNT 1000000

void run();

int main(int argc, char* argv[]) {
  struct timeval t;

  gettimeofday(&t, NULL);
  double before = t.tv_sec * 1000000.0 + t.tv_usec;
  for (int i = 0; i < ITERATIONS; i++) {
    run();
  }
  gettimeofday(&t, NULL);
  double after = t.tv_sec * 1000000.0 + t.tv_usec;
  double diff = after - before;

  double it_s = 1000000.0 * ITERATIONS / diff;
  double count_s = 1000000.0 * ITERATIONS * LOOP_COUNT / diff;
  printf("time: %.3lf μs (%lf it/s, %lf count/s)\n", diff, it_s, count_s);
  
  return 0;
}

#define OP_CONST_1 0
#define OP_ADD 1
#define OP_GET_LOCAL 2
#define OP_SET_LOCAL 3
#define OP_SUB 4
#define OP_CJUMP 5
#define OP_NOT 6
#define OP_JUMP 7
#define OP_RET 8

#define THREADED 0

#define TRACE 
#define HANDLE(op) case OP_##op: handle_##op: TRACE("@%-3d %s\n", (int)(ip - start_ip - 1), #op); 

#if THREADED
#define END_HANDLER() goto *handlers[*ip++]
#else
#define END_HANDLER() break
#endif

#define PUSH(x) *stack++ = (x)
#define POP() *(--stack)

__attribute__((noinline))
void run_program(byte* ip, val_t* locals, val_t* stack) {
  byte* start_ip = ip;
  val_t* start_stack = stack;

  const void* handlers[] = {
    [OP_CONST_1] = &&handle_CONST_1,
    [OP_ADD] = &&handle_ADD,
    [OP_GET_LOCAL] = &&handle_GET_LOCAL,
    [OP_SET_LOCAL] = &&handle_SET_LOCAL,
    [OP_SUB] = &&handle_SUB,
    [OP_CJUMP] = &&handle_CJUMP,
    [OP_NOT] = &&handle_NOT,
    [OP_JUMP] = &&handle_JUMP,
    [OP_RET] = &&handle_RET,
  };
  
  while (1) {
    switch(*ip++) {
    HANDLE(CONST_1) {
      PUSH(1);
      END_HANDLER();
    }
    HANDLE(ADD) {
      val_t b = POP();
      val_t a = POP();
      PUSH(a + b);
      END_HANDLER();
    }
    HANDLE(GET_LOCAL) {
      byte index = *ip++;
      PUSH(locals[index]);
      END_HANDLER();
    }
    HANDLE(SET_LOCAL) {
      byte index = *ip++;
      locals[index] = POP();
      END_HANDLER();
    }
    HANDLE(SUB) {
      val_t b = POP();
      val_t a = POP();
      PUSH(a - b);
      END_HANDLER();
    }
    HANDLE(CJUMP) {
      int8_t delta = *(int8_t*)ip;
      ip++;
      val_t a = POP();
      if (a != 0) {
	ip = ip + delta;
      }
      END_HANDLER();
    }
    HANDLE(NOT) {
      val_t a = POP();
      PUSH(a == 0 ? 1 : 0);
      END_HANDLER();
    }
    HANDLE(JUMP) {
      int8_t delta = *(int8_t*)ip;
      ip++;
      ip = ip + delta;
      END_HANDLER();
    }
    HANDLE(RET) {
      return;
    }
  default:
    printf("!illegal\n");
    return;
  }
  }
}

void run() {
  val_t locals[32] = { LOOP_COUNT, 55 };
  val_t stack[32];
  
  byte loop_program[] = {
    OP_GET_LOCAL, 0,
    OP_NOT,
    OP_CJUMP, 8,
    OP_GET_LOCAL, 0,
    OP_CONST_1,
    OP_SUB,
    OP_SET_LOCAL, 0,
    OP_JUMP, -13,
    OP_GET_LOCAL, 0,
    OP_RET,
  };

  byte add_program[] = {
    OP_GET_LOCAL, 0,
    OP_GET_LOCAL, 1,
    OP_ADD,
    OP_RET
  };
  
  run_program(loop_program, locals, stack);
  printf("%d\n", stack[0]);
}

