diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/check.c | 29 | ||||
-rw-r--r-- | src/compile.c | 687 | ||||
-rw-r--r-- | src/core.c | 9 | ||||
-rw-r--r-- | src/execute.c | 10 | ||||
-rw-r--r-- | src/interpret.c | 407 | ||||
-rw-r--r-- | src/lower.c | 690 |
6 files changed, 492 insertions, 1340 deletions
diff --git a/src/check.c b/src/check.c index 0858c67..e6dc81e 100644 --- a/src/check.c +++ b/src/check.c @@ -58,7 +58,8 @@ static int analyze_list(struct state *state, struct scope *scope, struct ast *l) return 0; } -static int analyze_statement_list(struct state *state, struct scope *scope, struct ast *l) +static int analyze_statement_list(struct state *state, struct scope *scope, + struct ast *l) { foreach_node(n, l) { if (analyze(state, scope, n)) @@ -69,7 +70,7 @@ static int analyze_statement_list(struct state *state, struct scope *scope, stru if (n->t != TYPE_VOID) { semantic_error(scope, n, - "non-void proc call not allowed as statement"); + "non-void proc call not allowed as statement"); return -1; } } @@ -177,7 +178,8 @@ static int analyze_proc_def(struct state *state, struct scope *scope, * return, but for simplicity require user to add explicit return as * last statement in body */ if (p->t != TYPE_VOID && last->k != AST_RETURN) { - semantic_error(scope, p, "can't prove that proc reaches return statement"); + semantic_error(scope, p, + "can't prove that proc reaches return statement"); return -1; } @@ -200,8 +202,9 @@ static int analyze_var_def(struct state *state, struct scope *scope, return -1; if (!concrete_type(expr->t)) { - semantic_error(scope, n, "illegal type in variable definition: %s", - type_str(expr->t)); + semantic_error(scope, n, + "illegal type in variable definition: %s", + type_str(expr->t)); return -1; } @@ -217,8 +220,9 @@ static int analyze_formal_def(struct state *state, struct scope *scope, return -1; if (!concrete_type(n->t)) { - semantic_error(scope, n, "illegal type for formal parameter: %s", - type_str(n->t)); + semantic_error(scope, n, + "illegal type for formal parameter: %s", + type_str(n->t)); return -1; } @@ -592,7 +596,7 @@ static int analyze_assign(struct state *state, struct scope *scope, if (!concrete_type(l->t)) { semantic_error(scope, n, "illegal type for lefthand side: %s", - type_str(l->t)); + type_str(l->t)); return -1; } @@ -602,7 +606,7 @@ static int analyze_assign(struct state *state, struct scope *scope, if (!concrete_type(r->t)) { semantic_error(scope, n, "illegal type for righthand side: %s", - type_str(r->t)); + type_str(r->t)); return -1; } @@ -762,7 +766,8 @@ static int analyze_unless(struct state *state, struct scope *scope, } struct ast *otherwise = unless_otherwise(n); - if (otherwise && analyze_statement_list(state, otherwise_scope, otherwise)) + if (otherwise && analyze_statement_list(state, otherwise_scope, + otherwise)) return -1; n->t = TYPE_VOID; @@ -849,8 +854,8 @@ static int analyze(struct state *state, struct scope *scope, struct ast *n) case AST_UNLESS: ret = analyze_unless(state, scope, n); break; case AST_UNLESS_EXPR: ret = analyze_unless_expr(state, scope, n); break; default: break; - /* not all nodes are in this switch statement, as the internal - * ones are assumed to be correct */ + /* not all nodes are in this switch statement, as the internal + * ones are assumed to be correct */ } if (ret == 0) { diff --git a/src/compile.c b/src/compile.c deleted file mode 100644 index e5f232b..0000000 --- a/src/compile.c +++ /dev/null @@ -1,687 +0,0 @@ -#include <math.h> -#include <stdio.h> -#include <sys/mman.h> - -#include <posthaste/compile.h> -#include <posthaste/lower.h> -#include <posthaste/utils.h> -#include <posthaste/date.h> - -#include <lightening/lightening.h> - -/* we have a couple assumptions running through this code: - * 1) For each branch/jump, there's always a corresponding label - * 2) The global value array is in the virtual register JIT_V0 - * 3) At the start of a function, we immediately allocate f->max_sp slots, and - * slot indexes are relative to JIT_SP - * - * Lightening is a very cool library that provides a kind of virtual assembler, - * as in there's a number of virtual registers that map to physical registers - * and each virtual instruction maps to some number of physical instructions. - * This means that the user is required to handle register allocations, (virtual) - * code generation and possible optimizations, but with some clever tricks we can - * pretty much ignore these limitations completely. - * - * Each bytecode instruction gets compiled down to some number of virtual - * Lightening instructions that perform the same task 'as if' the instruction was - * being interpreted. This means each operation first loads its arguments from - * memory to virtual registers, does whatever with them, and stores the result into - * some memory location. This is pretty inefficient but very easy to implement, and - * even a poor JIT is almost always faster than the best bytecode. - * - * As for register allocations, as long as we ensure that each bytecode instruction - * effectively 'frees' the registers it used once it's been executed, we don't - * need to worry about it. The values are safely stored in memory where the - * input/output locs can fetch them later, and there are no register-register - * dependencies between bytecode instructions. - * - * Each operation uses at most two caller-save registers to perform its operation. - * The only callee-save register we use is JIT_V0 for the global array, so - * we can ask Lightening to not save the other callee-save registers, saving a bit of - * overhead when entering/exiting functions. - * - * There's no particularly good way to view the generated machine code, as it would - * require adding something like binutils' BFD library as a dependency. You can - * always just dump each code arena to a file and look at it later, but I - * haven't implemented it. - */ - -static void compile_fn(struct fn *f); - -static void *alloc_arena(size_t size) -{ - return mmap(NULL, size, - PROT_EXEC | PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); -} - -static void free_arena(void *arena, size_t size) -{ - munmap(arena, size); -} - -static jit_operand_t formal_at(size_t i) -{ - return jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_SP, - i * sizeof(int64_t)); -} - -/* load value from global array/stack location l and place it into virtual register r */ -static void get(jit_state_t *j, jit_gpr_t r, struct loc l) -{ - if (l.l) { - jit_ldxi_l(j, r, JIT_SP, l.o * sizeof(int64_t)); - return; - } - - jit_ldxi_l(j, r, JIT_V0, l.o * sizeof(int64_t)); -} - -/* store value from virtual register r to global array/stack location l*/ -static void put(jit_state_t *j, jit_gpr_t r, struct loc l) -{ - if (l.l) { - jit_stxi_l(j, l.o * sizeof(int64_t), JIT_SP, r); - return; - } - - jit_stxi_l(j, l.o * sizeof(int64_t), JIT_V0, r); -} - -static void compile_add(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_addr(j, JIT_R0, JIT_R0, JIT_R1); - put(j, JIT_R0, i.o); -} - -static void compile_sub(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_subr(j, JIT_R0, JIT_R0, JIT_R1); - put(j, JIT_R0, i.o); -} - -static void compile_mul(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_mulr(j, JIT_R0, JIT_R0, JIT_R1); - put(j, JIT_R0, i.o); -} - -static void compile_div(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_divr(j, JIT_R0, JIT_R0, JIT_R1); - put(j, JIT_R0, i.o); -} - -static void compile_move(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - put(j, JIT_R0, i.o); -} - -static void compile_const(jit_state_t *j, struct insn i) -{ - jit_movi(j, JIT_R0, i.v); - put(j, JIT_R0, i.o); -} - -static void compile_eq(jit_state_t *j, struct insn i) -{ - /* Lightening doesn't really have any register-register comparison - * instructions, so implement them as local branches */ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_reloc_t branch = jit_beqr(j, JIT_R0, JIT_R1); - - jit_movi(j, JIT_R0, 0); - jit_reloc_t jump = jit_jmp(j); - jit_patch_there(j, branch, jit_address(j)); - - jit_movi(j, JIT_R0, 1); - jit_patch_there(j, jump, jit_address(j)); - - put(j, JIT_R0, i.o); -} - -static void compile_lt(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_reloc_t branch = jit_bltr(j, JIT_R0, JIT_R1); - - jit_movi(j, JIT_R0, 0); - jit_reloc_t jump = jit_jmp(j); - jit_patch_there(j, branch, jit_address(j)); - - jit_movi(j, JIT_R0, 1); - jit_patch_there(j, jump, jit_address(j)); - - put(j, JIT_R0, i.o); -} - -/* helper function for compiling PRINT_DATE, Lightening doesn't handle variadics - * so do the heavy lifting with GCC (or Clang or whatever) */ -static void print_date(int64_t date) -{ - char str[11]; - date_to_string(str, (ph_date_t)date); - printf("%s", str); -} - -static void compile_print_date(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, print_date, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); -} - -static void print_int(int64_t i) -{ - printf("%lli", (long long)i); -} - -static void compile_print_int(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, print_int, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); -} - -static void print_string(const char *s) -{ - printf("%s", s); -} - -static void compile_print_string(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, print_string, - jit_operand_gpr(JIT_OPERAND_ABI_POINTER, JIT_R0)); -} - -static void print_bool(int64_t b) -{ - printf("%s", b ? "true" : "false"); -} - -static void compile_print_bool(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, print_bool, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); -} - -static void compile_print_newline(jit_state_t *j, struct insn i) -{ - UNUSED(i); - jit_calli_1(j, putchar, jit_operand_imm(JIT_OPERAND_ABI_INT8, '\n')); -} - -static void compile_print_space(jit_state_t *j, struct insn i) -{ - UNUSED(i); - jit_calli_1(j, putchar, jit_operand_imm(JIT_OPERAND_ABI_INT8, ' ')); -} - -static void compile_label(jit_state_t *j, size_t ii, struct vec *labels) -{ - /* add a mapping between instruction index (ii) and current address */ - vect_at(jit_addr_t, *labels, ii) = jit_address(j); -} - -struct reloc_helper { - jit_reloc_t r; - size_t to; -}; - -static void compile_j(jit_state_t *j, struct insn i, struct vec *relocs) -{ - jit_reloc_t r = jit_jmp(j); - struct reloc_helper h = {.r = r, .to = i.v}; - vect_append(struct reloc_helper, *relocs, &h); -} - -static void compile_b(jit_state_t *j, struct insn i, struct vec *relocs) -{ - get(j, JIT_R0, i.i0); - jit_reloc_t r = jit_bnei(j, JIT_R0, 0); - struct reloc_helper h = {.r = r, .to = i.v}; - vect_append(struct reloc_helper, *relocs, &h); -} - -static void compile_bz(jit_state_t *j, struct insn i, struct vec *relocs) -{ - get(j, JIT_R0, i.i0); - jit_reloc_t r = jit_beqi(j, JIT_R0, 0); - struct reloc_helper h = {.r = r, .to = i.v}; - vect_append(struct reloc_helper, *relocs, &h); -} - -static void compile_arg(struct insn i, struct vec *params) -{ - jit_operand_t operand; - struct loc l = i.i0; - /* Lightening allows us to specify memory locations as arguments to - * function calls, and will automatically move the value from memory to - * the correct register according to the ABI */ - if (l.l) { - operand = jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_SP, - l.o * sizeof(int64_t)); - } - else { - operand = jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_V0, - l.o * sizeof(int64_t)); - } - - vect_append(jit_operand_t, *params, &operand); -} - -static void compile_call(jit_state_t *j, struct insn i, struct vec *params) -{ - jit_operand_t gp = jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_V0); - vect_append(jit_operand_t, *params, &gp); - - struct fn *f = find_fn(i.v); - assert(f); - - /* if we haven't already compiled this function, do it now */ - if (!f->arena) - compile_fn(f); - - jit_calli(j, f->arena, vec_len(params), params->buf); - /* argument instructions are only ever directly before a call - * instruction, so there's no risk of messing up some other call's - * args and we can avoid allocating a new vector */ - vec_reset(params); -} - -static void compile_retval(jit_state_t *j, struct insn i) -{ - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static void compile_neg(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_negr(j, JIT_R0, JIT_R0); - put(j, JIT_R0, i.o); -} - -static void compile_today(jit_state_t *j, struct insn i) -{ - jit_calli_0(j, current_date); - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -/* lots of these date handling instructions I've implemented as helper functions, mostly - * just for my own sanity */ -static ph_date_t date_add(int64_t i0, int64_t i1) -{ - struct tm time = tm_from_date((ph_date_t)i0); - time.tm_mday += i1; - mktime(&time); - - return date_from_tm(time); -} - -static void compile_date_add(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_calli_2(j, date_add, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static ph_date_t date_sub(int64_t i0, int64_t i1) -{ - struct tm time = tm_from_date((ph_date_t)i0); - time.tm_mday -= i1; - mktime(&time); - - return date_from_tm(time); -} - -static void compile_date_sub(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_calli_2(j, date_sub, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t date_diff(int64_t i0, int64_t i1) -{ - struct tm tm0 = tm_from_date((ph_date_t)i0); - struct tm tm1 = tm_from_date((ph_date_t)i1); - - time_t t0 = mktime(&tm0); - time_t t1 = mktime(&tm1); - - double seconds = difftime(t0, t1); - return round(seconds / 86400); -} - -static void compile_date_diff(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - - jit_calli_2(j, date_diff, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t load_year(int64_t i0) -{ - unsigned year = 0; - date_split((ph_date_t)i0, &year, NULL, NULL); - return year; -} - -static void compile_load_year(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, load_year, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t load_month(int64_t i0) -{ - unsigned month = 0; - date_split((ph_date_t)i0, NULL, &month, NULL); - return month; -} - -static void compile_load_month(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, load_month, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t load_day(int64_t i0) -{ - unsigned day = 0; - date_split((ph_date_t)i0, NULL, NULL, &day); - return day; -} - -static void compile_load_day(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, load_day, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t load_weekday(int64_t i0) -{ - struct tm time = tm_from_date((ph_date_t)i0); - return (int64_t)time.tm_wday; -} - -static void compile_load_weekday(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, load_weekday, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t load_weeknum(int64_t i0) -{ - struct tm time = tm_from_date((ph_date_t)i0); - return time.tm_yday / 7; -} - -static void compile_load_weeknum(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - jit_calli_1(j, load_weeknum, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t store_year(int64_t i0, int64_t i1) -{ - unsigned month = 0; - unsigned day = 0; - date_split((ph_date_t)i0, NULL, &month, &day); - return date_from_numbers(i1, month, day); -} - -static void compile_store_year(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_calli_2(j, store_year, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t store_month(int64_t i0, int64_t i1) -{ - unsigned year = 0; - unsigned day = 0; - date_split((ph_date_t)i0, &year, NULL, &day); - return date_from_numbers(year, i1, day); -} - -static void compile_store_month(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_calli_2(j, store_month, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static int64_t store_day(int64_t i0, int64_t i1) -{ - unsigned year = 0; - unsigned month = 0; - date_split((ph_date_t)i0, &year, &month, NULL); - return date_from_numbers(year, month, i1); -} - -static void compile_store_day(jit_state_t *j, struct insn i) -{ - get(j, JIT_R0, i.i0); - get(j, JIT_R1, i.i1); - jit_calli_2(j, store_day, - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R0), - jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_R1)); - - jit_retval_l(j, JIT_R0); - put(j, JIT_R0, i.o); -} - -static size_t compile_fn_body(struct fn *f, jit_state_t *j) -{ - jit_begin(j, f->arena, f->size); - size_t frame = jit_enter_jit_abi(j, 1, 0, 0); - /* easy off-by-one to miss, damn */ - size_t stack = jit_align_stack(j, (f->max_sp + 1) * sizeof(int64_t)); - - /* move parameters to where they belong */ - struct vec params = vec_create(sizeof(jit_operand_t)); - for (size_t i = 0; i < f->params; ++i) { - jit_operand_t operand = formal_at(i); - vec_append(¶ms, &operand); - } - - jit_operand_t gp = jit_operand_gpr(JIT_OPERAND_ABI_INT64, JIT_V0); - vec_append(¶ms, &gp); - jit_load_args(j, f->params + 1, params.buf); - - /* reuse vector as argument vector */ - vec_reset(¶ms); - - struct vec relocs = vec_create(sizeof(struct reloc_helper)); - struct vec labels = vec_create(sizeof(jit_addr_t)); - vec_reserve(&labels, vec_len(&f->insns)); - - foreach_vec(ii, f->insns) { - struct insn i = vect_at(struct insn, f->insns, ii); - switch (i.k) { - case LABEL: compile_label(j, ii, &labels); break; - case J: compile_j(j, i, &relocs); break; - case B: compile_b(j, i, &relocs); break; - case BZ: compile_bz(j, i, &relocs); break; - case ADD: compile_add(j, i); break; - case SUB: compile_sub(j, i); break; - case MUL: compile_mul(j, i); break; - case DIV: compile_div(j, i); break; - case MOVE: compile_move(j, i); break; - case CONST: compile_const(j, i); break; - case EQ: compile_eq(j, i); break; - case LT: compile_lt(j, i); break; - - case PRINT_DATE: compile_print_date(j, i); break; - case PRINT_INT: compile_print_int(j, i); break; - case PRINT_STRING: compile_print_string(j, i); break; - case PRINT_BOOL: compile_print_bool(j, i); break; - case PRINT_NEWLINE: compile_print_newline(j, i); break; - case PRINT_SPACE: compile_print_space(j, i); break; - - case ARG: compile_arg(i, ¶ms); break; - case CALL: compile_call(j, i, ¶ms); break; - case RETVAL: compile_retval(j, i); break; - - case NEG: compile_neg(j, i); break; - - case TODAY: compile_today(j, i); break; - - case DATE_ADD: compile_date_add(j, i); break; - case DATE_SUB: compile_date_sub(j, i); break; - case DATE_DIFF: compile_date_diff(j, i); break; - - case LOAD_YEAR: compile_load_year(j, i); break; - case LOAD_MONTH: compile_load_month(j, i); break; - case LOAD_DAY: compile_load_day(j, i); break; - case LOAD_WEEKDAY: compile_load_weekday(j, i); break; - case LOAD_WEEKNUM: compile_load_weeknum(j, i); break; - - case STORE_YEAR: compile_store_year(j, i); break; - case STORE_MONTH: compile_store_month(j, i); break; - case STORE_DAY: compile_store_day(j, i); break; - - case RET: { - get(j, JIT_R0, i.i0); - jit_shrink_stack(j, stack); - jit_leave_jit_abi(j, 1, 0, frame); - jit_retr(j, JIT_R0); - break; - } - - case STOP: { - jit_shrink_stack(j, stack); - jit_leave_jit_abi(j, 1, 0, frame); - jit_ret(j); - break; - } - } - } - - /* fix relocs */ - foreach_vec(ri, relocs) { - struct reloc_helper h = vect_at(struct reloc_helper, relocs, - ri); - jit_addr_t a = vect_at(jit_addr_t, labels, h.to); - jit_reloc_t r = h.r; - - assert(a); - jit_patch_there(j, r, a); - } - - vec_destroy(&relocs); - vec_destroy(&labels); - vec_destroy(¶ms); - - size_t size = 0; - void *p = jit_end(j, &size); - /* if we had enough room to finish compilation, return 0 to signify that - * we don't have to try to compile anything again */ - if (p) - return 0; - - /* otherwise, return how many bytes lightening estimates that we would - * need to succesfully compile this function */ - return size; -} - -static void compile_fn(struct fn *f) -{ - init_jit(); - jit_state_t *j = jit_new_state(NULL, NULL); - assert(j); - - void *arena_base = NULL; - size_t arena_size = 4096; - /* technically there should probably be a limit to how many times we - * attempt compilation, but in practice we're unlikely to ever need it. - */ - while (1) { - arena_base = alloc_arena(arena_size); - assert(arena_base && - "failed allocating executable arena, aborting"); - - f->arena = arena_base; - f->size = arena_size; - - size_t required_size = compile_fn_body(f, j); - if (required_size == 0) - break; - - free_arena(arena_base, arena_size); - /* give at least one page more than necessary to be on the safe - * side */ - arena_size = required_size + 4096; - } - - jit_destroy_state(j); -} - -void compile() -{ - struct fn *f = find_fn(0); - compile_fn(f); -} @@ -3,9 +3,7 @@ #include <limits.h> #include <errno.h> -#include <posthaste/interpret.h> #include <posthaste/execute.h> -#include <posthaste/compile.h> #include <posthaste/parser.h> #include <posthaste/debug.h> #include <posthaste/scope.h> @@ -85,14 +83,7 @@ int run(const char *fname) } lower_ast(ast); - -#ifdef JIT - compile(ast); execute(); -#else - interpret(); -#endif - out: free((void *)buf); destroy_lowering(); diff --git a/src/execute.c b/src/execute.c index 7da1a1a..cfa64fb 100644 --- a/src/execute.c +++ b/src/execute.c @@ -1,16 +1,10 @@ #include <posthaste/execute.h> #include <posthaste/lower.h> #include <posthaste/vec.h> +#include <ejit/ejit.h> void execute() { struct fn *f = find_fn(0); - void (*p)(int64_t *globals) = f->arena; - - struct vec globals = vec_create(sizeof(int64_t)); - vec_reserve(&globals, num_globals()); - - p(globals.buf); - - vec_destroy(&globals); + ejit_run_func(f->f, 0, NULL); } diff --git a/src/interpret.c b/src/interpret.c deleted file mode 100644 index 580303e..0000000 --- a/src/interpret.c +++ /dev/null @@ -1,407 +0,0 @@ -#include <math.h> -#include <stdio.h> - -#include <posthaste/execute.h> -#include <posthaste/lower.h> -#include <posthaste/utils.h> -#include <posthaste/date.h> -#include <posthaste/vec.h> - -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); -} diff --git a/src/lower.c b/src/lower.c index af9f9d4..f3c6672 100644 --- a/src/lower.c +++ b/src/lower.c @@ -1,3 +1,4 @@ +#include <math.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> @@ -5,6 +6,7 @@ #include <posthaste/lower.h> #include <posthaste/scope.h> #include <posthaste/utils.h> +#include <posthaste/date.h> /* The bytecode is similar in construction to Lua's, as in there's two arrays, one * for global values and one for local values. The local value array is referred @@ -38,6 +40,10 @@ static struct vec fns = {0}; * so skip two first globals */ static size_t globals = 2; +/* get register/stack slot for AST node */ +#define regno(x) ((x)->l.s) +#define reg(x) EJIT_GPR(regno(x)) + size_t num_globals() { return globals; @@ -51,21 +57,14 @@ static void lower_list(struct fn *f, struct ast *l) } } -static size_t loc_as_int(struct loc l) -{ - size_t a = l.o; - a |= (size_t)(l.l) << (sizeof(size_t) * 8 - 1); - return a; -} - static struct loc build_local_loc(size_t idx) { - return (struct loc){.l = 1, .o = idx}; + return (struct loc){.g = 0, .s = idx}; } static struct loc build_global_loc(size_t idx) { - return (struct loc){.l = 0, .o = idx}; + return (struct loc){.g = idx, .s = 0}; } static struct loc null_loc() @@ -74,16 +73,36 @@ static struct loc null_loc() return build_global_loc(1); } -static void output_insn(struct fn *f, enum insn_kind k, struct loc o, - struct loc i0, struct loc i1, int64_t v) +static enum ejit_type ejit_type_from(enum type_kind t) { - struct insn i = {.k = k, .o = o, .i0 = i0, .i1 = i1, .v = v}; - vect_append(struct insn, f->insns, &i); + switch (t) { + case TYPE_VOID: return EJIT_VOID; + case TYPE_STRING: return EJIT_POINTER; + case TYPE_INT: + case TYPE_BOOL: + case TYPE_DATE: return EJIT_INT64; + default: abort(); + } } -static void output_label(struct fn *f) +static void put(struct fn *f, struct loc l) { - output_insn(f, LABEL, null_loc(), null_loc(), null_loc(), 0); + assert(l.g != 1); + /* local values are already where they should be */ + if (l.g <= 1) + return; + + /* something like this I think, should still check the type width */ + ejit_stxi_64(f->f, EJIT_GPR(l.s), EJIT_GPR(0), l.g); +} + +static void get(struct fn *f, struct loc l) +{ + assert(l.g != 1); + if (l.g <= 1) + return; + + ejit_ldxi_u64(f->f, EJIT_GPR(l.s), EJIT_GPR(0), l.g); } static void lower_var(struct fn *f, struct ast *n) @@ -91,7 +110,7 @@ static void lower_var(struct fn *f, struct ast *n) n->l = build_local_loc(f->sp++); struct ast *expr = var_expr(n); lower(f, expr); - output_insn(f, MOVE, n->l, expr->l, null_loc(), 0); + ejit_movr(f->f, reg(n), reg(expr)); } static void lower_formal(struct fn *f, struct ast *n) @@ -99,22 +118,38 @@ static void lower_formal(struct fn *f, struct ast *n) n->l = build_local_loc(f->sp++); } +static struct vec lower_formals(struct fn *f, struct ast *n) +{ + struct vec formals = vec_create(sizeof(struct ejit_operand)); + foreach_node(fn, n) { + lower_formal(f, fn); + + struct ejit_operand arg = EJIT_OPERAND_GPR( + regno(fn), + ejit_type_from(fn->t) + ); + vec_append(&formals, &arg); + } + + return formals; +} + static void lower_date(struct fn *f, struct ast *n) { n->l = build_local_loc(f->sp); - output_insn(f, CONST, n->l, null_loc(), null_loc(), n->v); + ejit_movi(f->f, reg(n), n->v); } static void lower_string(struct fn *f, struct ast *n) { n->l = build_local_loc(f->sp); - output_insn(f, CONST, n->l, null_loc(), null_loc(), (int64_t)n->id); + ejit_movi(f->f, reg(n), (int64_t)n->id); } static void lower_int(struct fn *f, struct ast *n) { n->l = build_local_loc(f->sp); - output_insn(f, CONST, n->l, null_loc(), null_loc(), n->v); + ejit_movi(f->f, reg(n), n->v); } static void lower_add(struct fn *f, struct ast *n) @@ -135,7 +170,7 @@ static void lower_add(struct fn *f, struct ast *n) * separate output and input stack locations */ f->sp -= 2; - output_insn(f, ADD, n->l, l->l, r->l, 0); + ejit_addr(f->f, reg(n), reg(l), reg(r)); } static void lower_sub(struct fn *f, struct ast *n) @@ -148,7 +183,7 @@ static void lower_sub(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, SUB, n->l, l->l, r->l, 0); + ejit_subr(f->f, reg(n), reg(l), reg(r)); } static void lower_mul(struct fn *f, struct ast *n) @@ -161,7 +196,7 @@ static void lower_mul(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, MUL, n->l, l->l, r->l, 0); + ejit_mulr(f->f, reg(n), reg(l), reg(r)); } static void lower_div(struct fn *f, struct ast *n) @@ -174,7 +209,82 @@ static void lower_div(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, DIV, n->l, l->l, r->l, 0); + ejit_divr(f->f, reg(n), reg(l), reg(r)); +} + +static long escape_store_year(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_INT)); + + unsigned month = 0; + unsigned day = 0; + date_split(args[0].ul, NULL, &month, &day); + return date_from_numbers(args[1].ul, month, day); +} + +static void lower_store_year(struct fn *f, struct ast *base, struct ast* r) +{ + assert(base->t == TYPE_DATE); + assert(r->t == TYPE_INT); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_INT)) + }; + + ejit_escapei(f->f, escape_store_year, 2, args); + ejit_retval(f->f, reg(base)); +} + +static long escape_store_month(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_INT)); + + unsigned year = 0; + unsigned day = 0; + date_split(args[0].ul, &year, NULL, &day); + return date_from_numbers(year, args[1].ul, day); +} + +static void lower_store_month(struct fn *f, struct ast *base, struct ast* r) +{ + assert(base->t == TYPE_DATE); + assert(r->t == TYPE_INT); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_INT)) + }; + + ejit_escapei(f->f, escape_store_month, 2, args); + ejit_retval(f->f, reg(base)); +} + +static long escape_store_day(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_INT)); + + unsigned year = 0; + unsigned month = 0; + date_split(args[0].ul, &year, &month, NULL); + return date_from_numbers(year, month, args[1].ul); +} + +static void lower_store_day(struct fn *f, struct ast *base, struct ast* r) +{ + assert(base->t == TYPE_DATE); + assert(r->t == TYPE_INT); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_INT)) + }; + + ejit_escapei(f->f, escape_store_day, 2, args); + ejit_retval(f->f, reg(base)); } static void lower_dot_assign(struct fn *f, struct ast *n) @@ -187,20 +297,19 @@ static void lower_dot_assign(struct fn *f, struct ast *n) struct ast *base = dot_base(l); lower(f, base); - enum insn_kind store = STORE_DAY; if (same_id(l->id, "day")) - store = STORE_DAY; + lower_store_day(f, l, r); else if (same_id(l->id, "month")) - store = STORE_MONTH; + lower_store_month(f, l, r); else if (same_id(l->id, "year")) - store = STORE_YEAR; + lower_store_year(f, l, r); else abort(); - output_insn(f, store, base->l, base->l, r->l, 0); n->l = base->l; + put(f, n->l); } static void lower_assign(struct fn *f, struct ast *n) @@ -213,7 +322,10 @@ static void lower_assign(struct fn *f, struct ast *n) lower(f, r); lower(f, l); - output_insn(f, MOVE, l->l, r->l, null_loc(), 0); + ejit_movr(f->f, reg(l), reg(r)); + /* maybe store possible global value to global array */ + put(f, l->l); + n->l = null_loc(); } @@ -226,16 +338,126 @@ static void lower_id(struct fn *f, struct ast *n) /* using ast nodes/scope lookup as convenient way to store variable -> * location mappings */ n->l = exists->l; + assert(n->l.g != 1); + if (n->l.g > 1) { + /* global variables should get loaded to the stack for handling */ + n->l.s = f->sp; + } + + get(f, n->l); } static void lower_return(struct fn *f, struct ast *n) { struct ast *expr = return_expr(n); lower(f, expr); - output_insn(f, RET, null_loc(), expr->l, null_loc(), 0); + ejit_ret(f->f, reg(expr)); n->l = null_loc(); } +static long escape_load_weeknum(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + struct tm time = tm_from_date(args[0].ul); + return time.tm_yday / 7; +} + +static void lower_load_weeknum(struct fn *f, struct ast *n, struct ast *base) +{ + assert(base->t == TYPE_DATE); + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_load_weeknum, 1, args); + ejit_retval(f->f, reg(n)); +} + +static long escape_load_weekday(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + struct tm time = tm_from_date(args[0].ul); + return time.tm_wday; +} + +static void lower_load_weekday(struct fn *f, struct ast *n, struct ast *base) +{ + assert(base->t == TYPE_DATE); + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_load_weekday, 1, args); + ejit_retval(f->f, reg(n)); +} + +static long escape_load_year(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + unsigned year = 0; + date_split(args[0].ul, &year, NULL, NULL); + return year; +} + +static void lower_load_year(struct fn *f, struct ast *n, struct ast *base) +{ + assert(base->t == TYPE_DATE); + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_load_year, 1, args); + ejit_retval(f->f, reg(n)); +} + +static long escape_load_month(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + unsigned month = 0; + date_split(args[0].ul, NULL, &month, NULL); + return month; +} + +static void lower_load_month(struct fn *f, struct ast *n, struct ast *base) +{ + assert(base->t == TYPE_DATE); + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_load_month, 1, args); + ejit_retval(f->f, reg(n)); +} + +static long escape_load_day(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + unsigned day = 0; + date_split(args[0].ul, NULL, NULL, &day); + return day; +} + +static void lower_load_day(struct fn *f, struct ast *n, struct ast *base) +{ + assert(base->t == TYPE_DATE); + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(base), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_load_day, 1, args); + ejit_retval(f->f, reg(n)); +} + static void lower_attr(struct fn *f, struct ast *n) { n->l = build_local_loc(f->sp); @@ -243,26 +465,37 @@ static void lower_attr(struct fn *f, struct ast *n) struct ast *base = attr_base(n); lower(f, base); - enum insn_kind load = LOAD_DAY; if (same_id(n->id, "day")) - load = LOAD_DAY; + lower_load_day(f, n, base); else if (same_id(n->id, "month")) - load = LOAD_MONTH; + lower_load_month(f, n, base); else if (same_id(n->id, "year")) - load = LOAD_YEAR; + lower_load_year(f, n, base); else if (same_id(n->id, "weekday")) - load = LOAD_WEEKDAY; + lower_load_weekday(f, n, base); else if (same_id(n->id, "weeknum")) - load = LOAD_WEEKNUM; + lower_load_weeknum(f, n, base); else abort(); +} + +static long escape_print_space(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 0); + putchar(' '); + return 0; +} - output_insn(f, load, n->l, base->l, null_loc(), 0); +static long escape_print_newline(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 0); + putchar('\n'); + return 0; } static void lower_print(struct fn *f, struct ast *p) @@ -272,11 +505,10 @@ static void lower_print(struct fn *f, struct ast *p) /* don't print space on last element */ if (n->n) - output_insn(f, PRINT_SPACE, null_loc(), null_loc(), - null_loc(), 0); + ejit_escapei(f->f, escape_print_space, 0, NULL); } - output_insn(f, PRINT_NEWLINE, null_loc(), null_loc(), null_loc(), 0); + ejit_escapei(f->f, escape_print_newline, 0, NULL); p->l = null_loc(); } @@ -290,20 +522,20 @@ static void lower_proc_call(struct fn *f, struct ast *c) } size_t i = 0; + struct ejit_operand args[count]; foreach_node(n, proc_call_args(c)) { - output_insn(f, ARG, null_loc(), n->l, null_loc(), i); + args[i] = EJIT_OPERAND_GPR(regno(n), ejit_type_from(n->t)); i++; } struct ast *def = file_scope_find(c->scope, proc_call_id(c)); - output_insn(f, CALL, null_loc(), null_loc(), null_loc(), - loc_as_int(def->l)); + ejit_calli(f->f, (struct ejit_func *)def->l.s, count, args); f->sp -= count; c->l = build_local_loc(f->sp); if (c->t != TYPE_VOID) - output_insn(f, RETVAL, c->l, null_loc(), null_loc(), 0); + ejit_retval(f->f, reg(c)); } static void lower_func_call(struct fn *f, struct ast *c) @@ -314,20 +546,22 @@ static void lower_func_call(struct fn *f, struct ast *c) } size_t i = 0; + struct ejit_operand args[count]; foreach_node(n, func_call_args(c)) { - output_insn(f, ARG, null_loc(), n->l, null_loc(), i); + args[i] = EJIT_OPERAND_GPR(regno(n), ejit_type_from(n->t)); i++; } struct ast *def = file_scope_find(c->scope, func_call_id(c)); - output_insn(f, CALL, null_loc(), null_loc(), null_loc(), - loc_as_int(def->l)); + /* note, def->l.s is not actually a register, rather a pointer to the + * struct ejit_func */ + ejit_calli(f->f, (struct ejit_func *)def->l.s, count, args); f->sp -= count; c->l = build_local_loc(f->sp); if (c->t != TYPE_VOID) - output_insn(f, RETVAL, c->l, null_loc(), null_loc(), 0); + ejit_retval(f->f, reg(c)); } static void lower_eq(struct fn *f, struct ast *n) @@ -340,7 +574,7 @@ static void lower_eq(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, EQ, n->l, l->l, r->l, 0); + ejit_eqr(f->f, reg(n), reg(l), reg(r)); } static void lower_lt(struct fn *f, struct ast *n) @@ -353,7 +587,7 @@ static void lower_lt(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, LT, n->l, l->l, r->l, 0); + ejit_ltr(f->f, reg(n), reg(l), reg(r)); } static void lower_pos(struct fn *f, struct ast *n) @@ -370,41 +604,112 @@ static void lower_neg(struct fn *f, struct ast *n) f->sp += 1; lower(f, expr); f->sp -= 1; - output_insn(f, NEG, n->l, expr->l, null_loc(), 0); + ejit_negr(f->f, reg(n), reg(expr)); +} + +static long escape_print_date(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + + char str[11] = {0}; + date_to_string(str, args[0].ul); + printf("%s", str); + return 0; } static void lower_print_date(struct fn *f, struct ast *n) { struct ast *expr = print_date_expr(n); lower(f, expr); - output_insn(f, PRINT_DATE, null_loc(), expr->l, null_loc(), 0); + + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(expr), ejit_type_from(TYPE_DATE)) + }; + + ejit_escapei(f->f, escape_print_date, 1, args); n->l = null_loc(); } +static long escape_print_int(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_INT)); + printf("%lli", (long long)args[0].l); + return 0; +} + static void lower_print_int(struct fn *f, struct ast *n) { struct ast *expr = print_int_expr(n); lower(f, expr); - output_insn(f, PRINT_INT, null_loc(), expr->l, null_loc(), 0); + + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(expr), ejit_type_from(TYPE_INT)) + }; + + ejit_escapei(f->f, escape_print_int, 1, args); n->l = null_loc(); } +static long escape_print_string(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_STRING)); + puts(args[0].p); + return 0; +} + static void lower_print_string(struct fn *f, struct ast *n) { struct ast *expr = print_string_expr(n); lower(f, expr); - output_insn(f, PRINT_STRING, null_loc(), expr->l, null_loc(), 0); + + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(expr), ejit_type_from(TYPE_STRING)) + }; + + ejit_escapei(f->f, escape_print_string, 1, args); n->l = null_loc(); } +static long escape_print_bool(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 1); + assert(args[0].type == ejit_type_from(TYPE_BOOL)); + puts(args[0].l ? "true" : "false"); + return 0; +} + static void lower_print_bool(struct fn *f, struct ast *n) { struct ast *expr = print_bool_expr(n); lower(f, expr); - output_insn(f, PRINT_BOOL, null_loc(), expr->l, null_loc(), 0); + + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(regno(expr), ejit_type_from(TYPE_BOOL)) + }; + + ejit_escapei(f->f, escape_print_bool, 1, args); n->l = null_loc(); } +static ph_date_t date_add(ph_date_t d, long i) +{ + struct tm time = tm_from_date(d); + time.tm_mday = i; + mktime(&time); + return date_from_tm(time); +} + +static long escape_date_add(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_INT)); + return date_add(args[0].ul, args[1].l); +} + static void lower_date_add(struct fn *f, struct ast *n) { n->l = build_local_loc(f->sp); @@ -416,7 +721,29 @@ static void lower_date_add(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, DATE_ADD, n->l, l->l, r->l, 0); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(l), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_INT)), + }; + + ejit_escapei(f->f, escape_date_add, 2, args); + ejit_retval(f->f, reg(n)); +} + +static ph_date_t date_sub(ph_date_t d, long i) +{ + struct tm time = tm_from_date(d); + time.tm_mday -= i; + mktime(&time); + return date_from_tm(time); +} + +static long escape_date_sub(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_INT)); + return date_sub(args[0].ul, args[1].l); } static void lower_date_sub(struct fn *f, struct ast *n) @@ -430,7 +757,29 @@ static void lower_date_sub(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, DATE_SUB, n->l, l->l, r->l, 0); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(l), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_INT)), + }; + + ejit_escapei(f->f, escape_date_sub, 2, args); + ejit_retval(f->f, reg(n)); +} + +static long escape_date_diff(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 2); + assert(args[0].type == ejit_type_from(TYPE_DATE)); + assert(args[1].type == ejit_type_from(TYPE_DATE)); + + struct tm tm0 = tm_from_date((ph_date_t)args[0].ul); + struct tm tm1 = tm_from_date((ph_date_t)args[1].ul); + + time_t t0 = mktime(&tm0); + time_t t1 = mktime(&tm1); + + double seconds = difftime(t0, t1); + return round(seconds / 86400); } static void lower_date_diff(struct fn *f, struct ast *n) @@ -444,59 +793,49 @@ static void lower_date_diff(struct fn *f, struct ast *n) f->sp += 1; lower(f, r); f->sp -= 2; - output_insn(f, DATE_DIFF, n->l, l->l, r->l, 0); + struct ejit_operand args[2] = { + EJIT_OPERAND_GPR(regno(l), ejit_type_from(TYPE_DATE)), + EJIT_OPERAND_GPR(regno(r), ejit_type_from(TYPE_DATE)), + }; + + ejit_escapei(f->f, escape_date_diff, 2, args); + ejit_retval(f->f, reg(n)); } static void lower_until(struct fn *f, struct ast *n) { - size_t off = vec_len(&f->insns); - output_label(f); + struct ejit_label l = ejit_label(f->f); lower_list(f, until_body(n)); struct ast *cond = until_cond(n); lower(f, cond); - output_insn(f, BZ, null_loc(), cond->l, null_loc(), off); + struct ejit_reloc r = ejit_bnei(f->f, reg(cond), 0); + ejit_patch(f->f, r, l); n->l = null_loc(); } -static void patch_branch(struct fn *f, size_t branch, size_t off) -{ - /* the patch destination must always be a label, required by the JIT */ - assert((vect_at(struct insn, f->insns, off)).k == LABEL); - - struct insn i = vect_at(struct insn, f->insns, branch); - i.v = off; - vect_at(struct insn, f->insns, branch) = i; -} - static void lower_unless(struct fn *f, struct ast *n) { struct ast *cond = unless_cond(n); lower(f, cond); - size_t branch = vec_len(&f->insns); - /* placeholder */ - output_insn(f, B, null_loc(), cond->l, null_loc(), 0); + struct ejit_reloc branch = ejit_bnei(f->f, reg(cond), 0); struct ast *body = unless_body(n); lower_list(f, body); - size_t jump = vec_len(&f->insns); - /* placeholder */ - output_insn(f, J, null_loc(), null_loc(), null_loc(), 0); + struct ejit_reloc jump = ejit_jmp(f->f); - size_t off = vec_len(&f->insns); - output_label(f); - patch_branch(f, branch, off); + struct ejit_label l = ejit_label(f->f); + ejit_patch(f->f, branch, l); struct ast *otherwise = unless_otherwise(n); lower_list(f, otherwise); - off = vec_len(&f->insns); - output_label(f); - patch_branch(f, jump, off); + l = ejit_label(f->f); + ejit_patch(f->f, jump, l); n->l = null_loc(); } @@ -508,39 +847,39 @@ static void lower_unless_expr(struct fn *f, struct ast *n) struct ast *cond = unless_expr_cond(n); lower(f, cond); - size_t branch = vec_len(&f->insns); - /* placeholder */ - output_insn(f, B, null_loc(), cond->l, null_loc(), 0); + struct ejit_reloc branch = ejit_bnei(f->f, reg(cond), 0); struct ast *body = unless_expr_body(n); lower(f, body); + ejit_movr(f->f, reg(n), reg(body)); - output_insn(f, MOVE, n->l, body->l, null_loc(), 0); - - size_t jump = vec_len(&f->insns); - /* placeholder */ - output_insn(f, J, null_loc(), null_loc(), null_loc(), 0); + struct ejit_reloc jump = ejit_jmp(f->f); - size_t off = vec_len(&f->insns); - output_label(f); - patch_branch(f, branch, off); + struct ejit_label l = ejit_label(f->f); + ejit_patch(f->f, branch, l); struct ast *otherwise = unless_expr_otherwise(n); lower(f, otherwise); - output_insn(f, MOVE, n->l, otherwise->l, null_loc(), 0); + ejit_movr(f->f, reg(n), reg(otherwise)); - off = vec_len(&f->insns); - output_label(f); - patch_branch(f, jump, off); + l = ejit_label(f->f); + ejit_patch(f->f, jump, l); +} + +static long escape_today(size_t argc, const struct ejit_arg args[argc]) +{ + assert(argc == 0); + return current_date(); } static void lower_builtin_call(struct fn *f, struct ast *n) { /* for now we only support Today(), which doesn't have any args */ assert(same_id(builtin_call_id(n), "Today")); - n->l = build_local_loc(f->sp); - output_insn(f, TODAY, n->l, null_loc(), null_loc(), 0); + + ejit_escapei(f->f, escape_today, 0, NULL); + ejit_retval(f->f, reg(n)); } static void lower(struct fn *f, struct ast *n) @@ -593,14 +932,18 @@ static void lower(struct fn *f, struct ast *n) /* each ast node is assigned some location, regardless of if it actually * needs one as a sanity check */ - assert(loc_as_int(n->l) > 0); + assert(n->l.g); } static void lower_global_var(struct fn *f, struct ast *n) { n->l = build_global_loc(globals++); struct ast *expr = var_expr(n); lower(f, expr); - output_insn(f, MOVE, n->l, expr->l, null_loc(), 0); + + /* move directly from expr result to global */ + struct loc l = expr->l; + l.g = n->l.g; + put(f, l); } static void add_proc(struct ast *n) { @@ -612,7 +955,7 @@ static void add_proc(struct ast *n) { struct fn f = {.name = proc_id(n), .idx = idx, .sp = 0, - .insns = vec_create(sizeof(struct insn))}; + .max_sp = 0}; vect_append(struct fn, fns, &f); } @@ -623,127 +966,46 @@ static void add_func(struct ast *n) { struct fn f = {.name = func_id(n), .idx = idx, .sp = 0, - .insns = vec_create(sizeof(struct insn))}; + .max_sp = 0}; vect_append(struct fn, fns, &f); } static void lower_proc_def(struct ast *d) { - struct fn *f = vec_at(&fns, d->l.o); + struct fn *f = vec_at(&fns, d->l.s); assert(f); - f->params = ast_list_len(proc_formals(d)); + struct vec formals = lower_formals(f, func_formals(d)); + f->f = ejit_create_func(ejit_type_from(d->t), vec_len(&formals), + formals.buf); + f->sp = vec_len(&formals); f->max_sp = vec_len(&formals); + vec_destroy(&formals); - lower_list(f, proc_formals(d)); lower_list(f, proc_vars(d)); lower_list(f, proc_body(d)); if (d->t == TYPE_VOID) - output_insn(f, STOP, null_loc(), null_loc(), null_loc(), 0); + ejit_ret_i(f->f, 0); + + ejit_compile_func(f->f, f->max_sp + 1, 0); } static void lower_func_def(struct ast *d) { - struct fn *f = vec_at(&fns, d->l.o); + struct fn *f = vec_at(&fns, d->l.s); assert(f); - f->params = ast_list_len(func_formals(d)); + struct vec formals = lower_formals(f, func_formals(d)); + f->f = ejit_create_func(EJIT_VOID, vec_len(&formals), formals.buf); + f->sp = vec_len(&formals); f->max_sp = vec_len(&formals); + vec_destroy(&formals); - lower_list(f, func_formals(d)); lower_list(f, func_vars(d)); lower_list(f, func_body(d)); + ejit_compile_func(f->f, f->max_sp + 1, 0); } -#ifdef DEBUG -static void dump_loc(struct loc l) -{ - if (is_null_loc(l)) { - printf("null, "); - return; - } - - bool local = l.l; - if (local) { - printf("l"); - } else { - printf("g"); - } - - size_t val = l.o; - printf("%zd, ", val); -} - -static void dump_val(int64_t v) -{ - printf("%lli", (long long)v); -} - -static void dump_insn(struct insn i, size_t addr) -{ - printf("//%8zd: ", addr); -#define DUMP(x) case x: printf(#x); break; - switch (i.k) { - DUMP(TODAY); - DUMP(CALL); - DUMP(MOVE); - DUMP(ADD); - DUMP(SUB); - DUMP(MUL); - DUMP(DIV); - DUMP(ARG); - DUMP(STOP); - DUMP(RETVAL); - DUMP(PRINT_DATE); - DUMP(PRINT_INT); - DUMP(PRINT_STRING); - DUMP(PRINT_NEWLINE); - DUMP(PRINT_SPACE); - DUMP(PRINT_BOOL); - DUMP(DATE_ADD); - DUMP(DATE_SUB); - DUMP(DATE_DIFF); - DUMP(STORE_DAY); - DUMP(STORE_MONTH); - DUMP(STORE_YEAR); - DUMP(LOAD_DAY); - DUMP(LOAD_MONTH); - DUMP(LOAD_YEAR); - DUMP(LOAD_WEEKDAY); - DUMP(LOAD_WEEKNUM); - DUMP(RET); - DUMP(CONST); - DUMP(EQ); - DUMP(LT); - DUMP(NEG); - DUMP(B); - DUMP(BZ); - DUMP(J); - DUMP(LABEL); - } -#undef DUMP - - printf(" "); - - dump_loc(i.o); - dump_loc(i.i0); - dump_loc(i.i1); - dump_val(i.v); - - printf("\n"); -} - -static void dump_insns(struct fn *f) -{ - size_t addr = 0; - foreach_vec(ii, f->insns) { - struct insn i = vect_at(struct insn, f->insns, ii); - dump_insn(i, addr); - addr++; - } -} -#endif /* DEBUG */ - struct fn *find_fn(size_t idx) { return vec_at(&fns, idx); @@ -758,7 +1020,7 @@ int lower_ast(struct ast *tree) struct fn main = {.name = "main", .idx = 0, .sp = 0, - .insns = vec_create(sizeof(struct insn))}; + .max_sp = 0}; vect_append(struct fn, fns, &main); @@ -772,6 +1034,14 @@ int lower_ast(struct ast *tree) } struct fn *f = &vect_at(struct fn, fns, 0); + + /* reserve arg 0 for global pointer */ + struct ejit_operand args[1] = { + EJIT_OPERAND_GPR(0, EJIT_POINTER) + }; + f->sp = 1; f->max_sp = 1; + f->f = ejit_create_func(EJIT_VOID, 1, args); + /* we can't treat file scope as a regular function, as all variable * definitions must be made global, so we have a little bit of * duplicated code here but that's fine */ @@ -784,27 +1054,13 @@ int lower_ast(struct ast *tree) } } - /* append a return without value so we know when to stop interpretation */ - output_insn(f, STOP, null_loc(), null_loc(), null_loc(), 0); - -#ifdef DEBUG - foreach_vec(fi, fns) { - struct fn *f = vec_at(&fns, fi); - printf("// %s (%zd):\n", f->name, f->idx); - dump_insns(f); - } -#endif + ejit_compile_func(f->f, f->max_sp + 1, 0); return 0; } static void destroy_fn(struct fn *f) { - vec_destroy(&f->insns); - - /* filled in by JIT, but struct fn is still under the jurisdiction of - * lower.c so free arena here */ - if (f->arena) - munmap(f->arena, f->size); + ejit_destroy_func(f->f); } void destroy_lowering() |