#include #include #include #include #include #include #include static int64_t load(struct loc l, size_t sp, struct vec *stack, struct vec *globals) { /* extra runtime branches is another reason this bytecode instruction * set is slow to interpret. To get rid of this if (), one * option would be to add LOAD/STORE_GLOBAL instructions that move data * between the global array and the stack, but this would make the JIT * slower */ if (l.l) return vect_at(int64_t, *stack, l.o + sp); return vect_at(int64_t, *globals, l.o); } static void store(struct loc l, int64_t v, size_t sp, struct vec *stack, struct vec *globals) { if (l.l) { vect_at(int64_t, *stack, l.o + sp) = v; return; } vect_at(int64_t, *globals, l.o) = v; } static int64_t interpret_func(struct fn *f, struct vec *args, size_t sp, struct vec *stack, struct vec *globals) { /* move args to formal locations */ size_t formal = 0; foreach_vec(ai, *args) { int64_t a = vect_at(int64_t, *args, ai); vect_at(int64_t, *stack, sp + formal) = a; formal += 1; } vec_reset(args); /* load/store from location, selecting between global array and stack. Assumes * parameter names, generally frowned upon. */ #define get(l) \ load(l, sp, stack, globals) #define put(l, v) \ store(l, v, sp, stack, globals) /* use computed gotos to avoid extra overhead of loops/switch statements. * Usually I'm not a huge fan of massive single functions and from a * stylistic viewpoint maybe prefer breaking each instruction out into * its own procedure, but this at least saves a bit of typing */ #define LINK(x) [x] = &&CASE_##x static void *labels[] = { LINK(LABEL), LINK(CALL), LINK(MOVE), LINK(ADD), LINK(SUB), LINK(MUL), LINK(DIV), LINK(ARG), LINK(RETVAL), LINK(PRINT_STRING), LINK(PRINT_INT), LINK(PRINT_BOOL), LINK(PRINT_DATE), LINK(PRINT_NEWLINE), LINK(PRINT_SPACE), LINK(DATE_ADD), LINK(DATE_SUB), LINK(DATE_DIFF), LINK(STORE_DAY), LINK(STORE_MONTH), LINK(STORE_YEAR), LINK(LOAD_DAY), LINK(LOAD_MONTH), LINK(LOAD_YEAR), LINK(LOAD_WEEKDAY), LINK(LOAD_WEEKNUM), LINK(TODAY), LINK(RET), LINK(STOP), LINK(CONST), LINK(EQ), LINK(LT), LINK(NEG), LINK(B), LINK(BZ), LINK(J), }; #undef LINK #define JUMP() i = vect_at(struct insn, insns, pc); goto *labels[i.k]; #define DISPATCH() pc++; JUMP(); #define CASE(x) CASE_##x size_t pc = 0; int64_t retval = 0; struct vec insns = f->insns; struct insn i = {0}; /* jump to first instruction */ JUMP(); CASE(STOP) : {return 0;} CASE(RET) : {return get(i.i0);} CASE(LABEL) : {DISPATCH();} CASE(RETVAL) : {put(i.o, retval); DISPATCH();} CASE(J) : {pc = i.v; JUMP();} CASE(B) : { int64_t i0 = get(i.i0); if (i0) { pc = i.v; JUMP(); } DISPATCH(); } CASE(BZ) : { int64_t i0 = get(i.i0); if (!i0) { pc = i.v; JUMP(); } DISPATCH(); } CASE(ARG) : { int64_t a = get(i.i0); vect_append(int64_t, *args, &a); DISPATCH(); } CASE(CALL) : { struct fn *cf = find_fn(i.v); assert(cf); retval = interpret_func(cf, args, sp + f->max_sp + 1, stack, globals); DISPATCH(); } CASE(MOVE) : { int64_t i0 = get(i.i0); put(i.o, i0); DISPATCH(); } CASE(ADD) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t o = i0 + i1; put(i.o, o); DISPATCH(); } CASE(SUB) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t o = i0 - i1; put(i.o, o); DISPATCH(); } CASE(MUL) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t o = i0 * i1; put(i.o, o); DISPATCH(); } CASE(DIV) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t o = i0 / i1; put(i.o, o); DISPATCH(); } CASE(CONST) : { put(i.o, i.v); DISPATCH(); } CASE(PRINT_DATE) : { int64_t i0 = get(i.i0); char str[11] = {0}; date_to_string(str, i0); printf("%s", str); DISPATCH(); } CASE(PRINT_INT) : { int64_t i0 = get(i.i0); printf("%lli", (long long)i0); DISPATCH(); } CASE(PRINT_BOOL) : { int64_t i0 = get(i.i0); printf("%s", i0 ? "true" : "false"); DISPATCH(); } CASE(PRINT_STRING) : { int64_t i0 = get(i.i0); printf("%s", (const char *)i0); DISPATCH(); } CASE(PRINT_NEWLINE) : { putchar('\n'); DISPATCH(); } CASE(PRINT_SPACE) : { putchar(' '); DISPATCH(); } CASE(LOAD_DAY) : { int64_t i0 = get(i.i0); unsigned day = 0; date_split((ph_date_t)i0, NULL, NULL, &day); put(i.o, (int64_t)day); DISPATCH(); } CASE(LOAD_MONTH) : { int64_t i0 = get(i.i0); unsigned month = 0; date_split((ph_date_t)i0, NULL, &month, NULL); put(i.o, (int64_t)month); DISPATCH(); } CASE(LOAD_YEAR) : { int64_t i0 = get(i.i0); unsigned year = 0; date_split((ph_date_t)i0, &year, NULL, NULL); put(i.o, (int64_t)year); DISPATCH(); } CASE(LOAD_WEEKDAY) : { int64_t i0 = get(i.i0); struct tm time = tm_from_date((ph_date_t)i0); put(i.o, (int64_t)time.tm_wday); DISPATCH(); } CASE(LOAD_WEEKNUM) : { int64_t i0 = get(i.i0); struct tm time = tm_from_date((ph_date_t)i0); put(i.o, time.tm_yday / 7); DISPATCH(); } CASE(STORE_DAY) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); unsigned year = 0; unsigned month = 0; date_split((ph_date_t)i0, &year, &month, NULL); ph_date_t date = date_from_numbers(year, month, i1); put(i.o, (int64_t)date); DISPATCH(); } CASE(STORE_MONTH) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); unsigned year = 0; unsigned day = 0; date_split((ph_date_t)i0, &year, NULL, &day); ph_date_t date = date_from_numbers(year, i1, day); put(i.o, (int64_t)date); DISPATCH(); } CASE(STORE_YEAR) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); unsigned month = 0; unsigned day = 0; date_split((ph_date_t)i0, NULL, &month, &day); ph_date_t date = date_from_numbers(i1, month, day); put(i.o, (int64_t)date); DISPATCH(); } CASE(DATE_ADD) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); struct tm time = tm_from_date((ph_date_t)i0); time.tm_mday += i1; mktime(&time); ph_date_t date = date_from_tm(time); put(i.o, (int64_t)date); DISPATCH(); } CASE(DATE_SUB) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); struct tm time = tm_from_date((ph_date_t)i0); time.tm_mday -= i1; mktime(&time); ph_date_t date = date_from_tm(time); put(i.o, (int64_t)date); DISPATCH(); } CASE(DATE_DIFF) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); struct tm time0 = tm_from_date((ph_date_t)i0); struct tm time1 = tm_from_date((ph_date_t)i1); /* close enough at least */ time_t t0 = mktime(&time0); time_t t1 = mktime(&time1); double seconds = difftime(t0, t1); int64_t days = round(seconds / 86400); put(i.o, days); DISPATCH(); } CASE(TODAY) : { ph_date_t date = current_date(); put(i.o, (int64_t)date); DISPATCH(); } CASE(EQ) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t b = i0 == i1; put(i.o, b); DISPATCH(); } CASE(LT) : { int64_t i0 = get(i.i0); int64_t i1 = get(i.i1); int64_t b = i0 < i1; put(i.o, b); DISPATCH(); } CASE(NEG) : { int64_t i0 = get(i.i0); put(i.o, -i0); DISPATCH(); } #undef CASE #undef JUMP #undef DISPATCH #undef get #undef put } void interpret() { struct fn *f = find_fn(0); assert(f); struct vec stack = vec_create(sizeof(int64_t)); /* arbitrary amount of stack space, could potentially even be expanded * if we run out but that's currently not implemented */ vec_reserve(&stack, 65535); struct vec globals = vec_create(sizeof(int64_t)); vec_reserve(&globals, num_globals()); struct vec args = vec_create(sizeof(int64_t)); interpret_func(f, &args, 0, &stack, &globals); vec_destroy(&args); vec_destroy(&stack); vec_destroy(&globals); }