#include #include #include #include "opcode.h" #include "compile.h" #include "locfile.h" struct inst { struct inst* next; struct inst* prev; opcode op; union { uint16_t intval; struct inst* target; jv constant; struct cfunction* cfunc; } imm; location source; // Binding // An instruction requiring binding (for parameters/variables) // is in one of three states: // bound_by = NULL - Unbound free variable // bound_by = self - This instruction binds a variable // bound_by = other - Uses variable bound by other instruction // The immediate field is generally not meaningful until instructions // are bound, and even then only for instructions which bind. struct inst* bound_by; char* symbol; block subfn; // This instruction is compiled as part of which function? // (only used during block_compile) struct bytecode* compiled; int bytecode_pos; // position just after this insn }; static inst* inst_new(opcode op) { inst* i = malloc(sizeof(inst)); i->next = i->prev = 0; i->op = op; i->bytecode_pos = -1; i->bound_by = 0; i->symbol = 0; i->subfn = gen_noop(); i->source = UNKNOWN_LOCATION; return i; } static void inst_free(struct inst* i) { free(i->symbol); if (opcode_describe(i->op)->flags & OP_HAS_BLOCK) { block_free(i->subfn); } if (opcode_describe(i->op)->flags & OP_HAS_CONSTANT) { jv_free(i->imm.constant); } free(i); } static block inst_block(inst* i) { block b = {i,i}; return b; } static int block_is_single(block b) { return b.first && b.first == b.last; } static inst* block_take(block* b) { if (b->first == 0) return 0; inst* i = b->first; if (i->next) { i->next->prev = 0; b->first = i->next; i->next = 0; } else { b->first = 0; b->last = 0; } return i; } block gen_location(location loc, block b) { for (inst* i = b.first; i; i = i->next) { if (i->source.start == UNKNOWN_LOCATION.start && i->source.end == UNKNOWN_LOCATION.end) { i->source = loc; } } return b; } block gen_noop() { block b = {0,0}; return b; } block gen_op_simple(opcode op) { assert(opcode_describe(op)->length == 1); return inst_block(inst_new(op)); } block gen_op_const(opcode op, jv constant) { assert(opcode_describe(op)->flags & OP_HAS_CONSTANT); inst* i = inst_new(op); i->imm.constant = constant; return inst_block(i); } block gen_op_target(opcode op, block target) { assert(opcode_describe(op)->flags & OP_HAS_BRANCH); assert(target.last); inst* i = inst_new(op); i->imm.target = target.last; return inst_block(i); } block gen_op_targetlater(opcode op) { assert(opcode_describe(op)->flags & OP_HAS_BRANCH); inst* i = inst_new(op); i->imm.target = 0; return inst_block(i); } void inst_set_target(block b, block target) { assert(block_is_single(b)); assert(opcode_describe(b.first->op)->flags & OP_HAS_BRANCH); assert(target.last); b.first->imm.target = target.last; } block gen_op_var_unbound(opcode op, const char* name) { assert(opcode_describe(op)->flags & OP_HAS_VARIABLE); inst* i = inst_new(op); i->symbol = strdup(name); return inst_block(i); } block gen_op_var_bound(opcode op, block binder) { assert(block_is_single(binder)); block b = gen_op_var_unbound(op, binder.first->symbol); b.first->bound_by = binder.first; return b; } block gen_op_symbol(opcode op, const char* sym) { assert(opcode_describe(op)->flags & OP_HAS_SYMBOL); inst* i = inst_new(op); i->symbol = strdup(sym); return inst_block(i); } block gen_op_block_defn(opcode op, const char* name, block block) { assert(opcode_describe(op)->flags & OP_IS_CALL_PSEUDO); assert(opcode_describe(op)->flags & OP_HAS_BLOCK); inst* i = inst_new(op); i->subfn = block; i->symbol = strdup(name); return inst_block(i); } static void block_bind_subblock(block binder, block body, int bindflags); block gen_op_block_defn_rec(opcode op, const char* name, block blk) { block b = gen_op_block_defn(op, name, blk); block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING); return b; } block gen_op_block_unbound(opcode op, const char* name) { assert(opcode_describe(op)->flags & OP_IS_CALL_PSEUDO); inst* i = inst_new(op); i->symbol = strdup(name); return inst_block(i); } block gen_op_block_bound(opcode op, block binder) { assert(block_is_single(binder)); block b = gen_op_block_unbound(op, binder.first->symbol); b.first->bound_by = binder.first; return b; } block gen_op_call(opcode op, block arglist) { assert(opcode_describe(op)->flags & OP_HAS_VARIABLE_LENGTH_ARGLIST); inst* i = inst_new(op); block prelude = gen_noop(); block call = inst_block(i); int nargs = 0; inst* curr = 0; while ((curr = block_take(&arglist))) { assert(opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO); block bcurr = inst_block(curr); switch (curr->op) { default: assert(0 && "Unknown type of parameter"); break; case CLOSURE_REF: block_append(&call, bcurr); break; case CLOSURE_CREATE: block_append(&prelude, bcurr); block_append(&call, gen_op_block_bound(CLOSURE_REF, bcurr)); break; } nargs++; } assert(nargs < 100); //FIXME i->imm.intval = nargs; return block_join(prelude, call); } static void inst_join(inst* a, inst* b) { assert(a && b); assert(!a->next); assert(!b->prev); a->next = b; b->prev = a; } void block_append(block* b, block b2) { if (b2.first) { if (b->last) { inst_join(b->last, b2.first); } else { b->first = b2.first; } b->last = b2.last; } } block block_join(block a, block b) { block c = a; block_append(&c, b); return c; } int block_has_only_binders(block binders, int bindflags) { bindflags |= OP_HAS_BINDING; for (inst* curr = binders.first; curr; curr = curr->next) { if ((opcode_describe(curr->op)->flags & bindflags) != bindflags) { return 0; } } return 1; } static void block_bind_subblock(block binder, block body, int bindflags) { assert(block_is_single(binder)); assert((opcode_describe(binder.first->op)->flags & bindflags) == bindflags); assert(binder.first->symbol); assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first); binder.first->bound_by = binder.first; for (inst* i = body.first; i; i = i->next) { int flags = opcode_describe(i->op)->flags; if ((flags & bindflags) == bindflags && i->bound_by == 0 && !strcmp(i->symbol, binder.first->symbol)) { // bind this instruction i->bound_by = binder.first; } if (flags & OP_HAS_BLOCK) { // binding recurses into closures block_bind_subblock(binder, i->subfn, bindflags); } } } block block_bind(block binder, block body, int bindflags) { assert(block_has_only_binders(binder, bindflags)); bindflags |= OP_HAS_BINDING; for (inst* curr = binder.first; curr; curr = curr->next) { block_bind_subblock(inst_block(curr), body, bindflags); } return block_join(binder, body); } block gen_subexp(block a) { block c = gen_noop(); block_append(&c, gen_op_simple(DUP)); block_append(&c, a); block_append(&c, gen_op_simple(SWAP)); return c; } block gen_both(block a, block b) { block c = gen_noop(); block jump = gen_op_targetlater(JUMP); block fork = gen_op_targetlater(FORK); block_append(&c, fork); block_append(&c, a); block_append(&c, jump); inst_set_target(fork, c); block_append(&c, b); inst_set_target(jump, c); return c; } block gen_collect(block expr) { block c = gen_noop(); block_append(&c, gen_op_simple(DUP)); block_append(&c, gen_op_const(LOADK, jv_array())); block array_var = block_bind(gen_op_var_unbound(STOREV, "collect"), gen_noop(), OP_HAS_VARIABLE); block_append(&c, array_var); block tail = {0}; block_append(&tail, gen_op_simple(DUP)); block_append(&tail, gen_op_var_bound(LOADV, array_var)); block_append(&tail, gen_op_simple(SWAP)); block_append(&tail, gen_op_simple(APPEND)); block_append(&tail, gen_op_var_bound(STOREV, array_var)); block_append(&tail, gen_op_simple(BACKTRACK)); block_append(&c, gen_op_target(FORK, tail)); block_append(&c, expr); block_append(&c, tail); block_append(&c, gen_op_var_bound(LOADV, array_var)); return c; } block gen_assign(block expr) { block c = gen_noop(); block_append(&c, gen_op_simple(DUP)); block result_var = block_bind(gen_op_var_unbound(STOREV, "result"), gen_noop(), OP_HAS_VARIABLE); block_append(&c, result_var); block loop = gen_noop(); block_append(&loop, gen_op_simple(DUP)); block_append(&loop, expr); block_append(&loop, gen_op_var_bound(ASSIGN, result_var)); block_append(&loop, gen_op_simple(BACKTRACK)); block_append(&c, gen_op_target(FORK, loop)); block_append(&c, loop); block_append(&c, gen_op_var_bound(LOADV, result_var)); return c; } block gen_definedor(block a, block b) { // var found := false block c = gen_op_simple(DUP); block_append(&c, gen_op_const(LOADK, jv_false())); block found_var = block_bind(gen_op_var_unbound(STOREV, "found"), gen_noop(), OP_HAS_VARIABLE); block_append(&c, found_var); // if found, backtrack. Otherwise execute b block tail = gen_op_simple(DUP); block_append(&tail, gen_op_var_bound(LOADV, found_var)); block backtrack = gen_op_simple(BACKTRACK); block_append(&tail, gen_op_target(JUMP_F, backtrack)); block_append(&tail, backtrack); block_append(&tail, gen_op_simple(POP)); block_append(&tail, b); // try again block if_notfound = gen_op_simple(BACKTRACK); // found := true, produce result block if_found = gen_op_simple(DUP); block_append(&if_found, gen_op_const(LOADK, jv_true())); block_append(&if_found, gen_op_var_bound(STOREV, found_var)); block_append(&if_found, gen_op_target(JUMP, tail)); block_append(&c, gen_op_target(FORK, if_notfound)); block_append(&c, a); block_append(&c, gen_op_target(JUMP_F, if_found)); block_append(&c, if_found); block_append(&c, if_notfound); block_append(&c, tail); return c; } block gen_condbranch(block iftrue, block iffalse) { block b = gen_noop(); block_append(&iftrue, gen_op_target(JUMP, iffalse)); block_append(&b, gen_op_target(JUMP_F, iftrue)); block_append(&b, iftrue); block_append(&b, iffalse); return b; } block gen_and(block a, block b) { // a and b = if a then (if b then true else false) else false block code = gen_op_simple(DUP); block_append(&code, a); block if_a_true = gen_op_simple(POP); block_append(&if_a_true, b); block_append(&if_a_true, gen_condbranch(gen_op_const(LOADK, jv_true()), gen_op_const(LOADK, jv_false()))); block_append(&code, gen_condbranch(if_a_true, block_join(gen_op_simple(POP), gen_op_const(LOADK, jv_false())))); return code; } block gen_or(block a, block b) { // a or b = if a then true else (if b then true else false) block code = gen_op_simple(DUP); block_append(&code, a); block if_a_false = gen_op_simple(POP); block_append(&if_a_false, b); block_append(&if_a_false, gen_condbranch(gen_op_const(LOADK, jv_true()), gen_op_const(LOADK, jv_false()))); block_append(&code, gen_condbranch(block_join(gen_op_simple(POP), gen_op_const(LOADK, jv_true())), if_a_false)); return code; } block gen_cond(block cond, block iftrue, block iffalse) { block b = gen_op_simple(DUP); block_append(&b, cond); block_append(&b, gen_condbranch(block_join(gen_op_simple(POP), iftrue), block_join(gen_op_simple(POP), iffalse))); return b; } block gen_cbinding(struct symbol_table* t, block code) { for (int cfunc=0; cfuncncfunctions; cfunc++) { inst* i = inst_new(CLOSURE_CREATE_C); i->imm.cfunc = &t->cfunctions[cfunc]; i->symbol = strdup(i->imm.cfunc->name); code = block_bind(inst_block(i), code, OP_IS_CALL_PSEUDO); } return code; } static uint16_t nesting_level(struct bytecode* bc, inst* target) { uint16_t level = 0; assert(bc && target->compiled); while (bc && target->compiled != bc) { level++; bc = bc->parent; } assert(bc && bc == target->compiled); return level; } static int count_cfunctions(block b) { int n = 0; for (inst* i = b.first; i; i = i->next) { if (i->op == CLOSURE_CREATE_C) n++; if (opcode_describe(i->op)->flags & OP_HAS_BLOCK) n += count_cfunctions(i->subfn); } return n; } static int compile(struct locfile* locations, struct bytecode* bc, block b) { int errors = 0; int pos = 0; int var_frame_idx = 0; bc->nsubfunctions = 0; bc->nclosures = 0; for (inst* curr = b.first; curr; curr = curr->next) { if (!curr->next) assert(curr == b.last); pos += opcode_length(curr->op); curr->bytecode_pos = pos; curr->compiled = bc; int opflags = opcode_describe(curr->op)->flags; if (opflags & OP_HAS_BINDING) { if (!curr->bound_by) { locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol); errors++; } } if ((opflags & OP_HAS_VARIABLE) && curr->bound_by == curr) { curr->imm.intval = var_frame_idx++; } if (opflags & OP_HAS_BLOCK) { assert(curr->bound_by == curr); curr->imm.intval = bc->nsubfunctions++; } if (curr->op == CLOSURE_PARAM) { assert(curr->bound_by == curr); curr->imm.intval = bc->nclosures++; } if (curr->op == CLOSURE_CREATE_C) { assert(curr->bound_by == curr); int idx = bc->globals->ncfunctions++; bc->globals->cfunctions[idx] = *curr->imm.cfunc; curr->imm.intval = idx; } } if (bc->nsubfunctions) { bc->subfunctions = malloc(sizeof(struct bytecode*) * bc->nsubfunctions); for (inst* curr = b.first; curr; curr = curr->next) { if (!(opcode_describe(curr->op)->flags & OP_HAS_BLOCK)) continue; struct bytecode* subfn = malloc(sizeof(struct bytecode)); bc->subfunctions[curr->imm.intval] = subfn; subfn->globals = bc->globals; subfn->parent = bc; errors += compile(locations, subfn, curr->subfn); } } else { bc->subfunctions = 0; } bc->codelen = pos; uint16_t* code = malloc(sizeof(uint16_t) * bc->codelen); bc->code = code; pos = 0; jv constant_pool = jv_array(); int maxvar = -1; if (!errors) for (inst* curr = b.first; curr; curr = curr->next) { const struct opcode_description* op = opcode_describe(curr->op); if (op->length == 0) continue; uint16_t* opcode_rewrite = &code[pos]; code[pos++] = curr->op; int opflags = op->flags; assert(!(op->flags & OP_IS_CALL_PSEUDO)); if (opflags & OP_HAS_VARIABLE_LENGTH_ARGLIST) { int nargs = curr->imm.intval; assert(nargs > 0); code[pos++] = (uint16_t)nargs; int desired_params = 0; for (int i=0; inext; assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO); code[pos++] = nesting_level(bc, curr->bound_by); switch (curr->bound_by->op) { default: assert(0 && "Unknown type of argument"); case CLOSURE_CREATE: code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE; if (i == 0) { inst* i = curr->bound_by; desired_params = i->compiled->subfunctions[i->imm.intval]->nclosures; } break; case CLOSURE_PARAM: code[pos++] = curr->bound_by->imm.intval; if (i == 0) desired_params = 0; break; case CLOSURE_CREATE_C: code[pos++] = curr->bound_by->imm.intval; *opcode_rewrite = bc->globals->cfunctions[curr->bound_by->imm.intval].callop; if (i == 0) desired_params = 0; break; } } assert(nargs - 1 == desired_params); } else if (opflags & OP_HAS_CONSTANT) { code[pos++] = jv_array_length(jv_copy(constant_pool)); constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant)); } else if (opflags & OP_HAS_VARIABLE) { code[pos++] = nesting_level(bc, curr->bound_by); uint16_t var = (uint16_t)curr->bound_by->imm.intval; code[pos++] = var; if (var > maxvar) maxvar = var; } else if (opflags & OP_HAS_BRANCH) { assert(curr->imm.target->bytecode_pos != -1); assert(curr->imm.target->bytecode_pos > pos); // only forward branches code[pos] = curr->imm.target->bytecode_pos - (pos + 1); pos++; } else if (opflags & OP_HAS_CFUNC) { assert(curr->symbol); int found = 0; for (int i=0; iglobals->ncfunctions; i++) { if (!strcmp(curr->symbol, bc->globals->cfunctions[i].name)) { code[pos++] = i; found = 1; break; } } assert(found); } else if (op->length > 1) { assert(0 && "codegen not implemented for this operation"); } } bc->constants = constant_pool; bc->nlocals = maxvar + 2; // FIXME: frames of size zero? return errors; } int block_compile(block b, struct locfile* locations, struct bytecode** out) { struct bytecode* bc = malloc(sizeof(struct bytecode)); bc->parent = 0; bc->globals = malloc(sizeof(struct symbol_table)); int ncfunc = count_cfunctions(b); bc->globals->ncfunctions = 0; bc->globals->cfunctions = malloc(sizeof(struct cfunction) * ncfunc); int nerrors = compile(locations, bc, b); assert(bc->globals->ncfunctions == ncfunc); if (nerrors > 0) { bytecode_free(bc); *out = 0; } else { *out = bc; } return nerrors; } void block_free(block b) { struct inst* next; for (struct inst* curr = b.first; curr; curr = next) { next = curr->next; inst_free(curr); } }