2012-08-16 01:00:30 +01:00
|
|
|
#include <assert.h>
|
|
|
|
#include <string.h>
|
2012-09-02 16:31:59 +01:00
|
|
|
#include <stdlib.h>
|
2012-08-16 01:00:30 +01:00
|
|
|
#include "opcode.h"
|
|
|
|
#include "compile.h"
|
2012-12-02 21:25:54 +00:00
|
|
|
#include "bytecode.h"
|
2012-09-11 12:24:54 +01:00
|
|
|
#include "locfile.h"
|
2012-08-16 01:00:30 +01:00
|
|
|
|
|
|
|
struct inst {
|
|
|
|
struct inst* next;
|
|
|
|
struct inst* prev;
|
|
|
|
|
|
|
|
opcode op;
|
|
|
|
|
|
|
|
union {
|
|
|
|
uint16_t intval;
|
|
|
|
struct inst* target;
|
2012-09-02 16:31:59 +01:00
|
|
|
jv constant;
|
2012-08-27 11:19:42 +01:00
|
|
|
struct cfunction* cfunc;
|
2012-08-16 01:00:30 +01:00
|
|
|
} imm;
|
|
|
|
|
2012-09-11 12:24:54 +01:00
|
|
|
location source;
|
|
|
|
|
2012-08-21 18:14:13 +01:00
|
|
|
// 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;
|
2012-11-25 23:07:43 +00:00
|
|
|
|
2012-08-21 18:14:13 +01:00
|
|
|
block subfn;
|
2012-11-25 23:07:43 +00:00
|
|
|
block arglist;
|
2012-08-21 18:14:13 +01:00
|
|
|
|
|
|
|
// This instruction is compiled as part of which function?
|
|
|
|
// (only used during block_compile)
|
|
|
|
struct bytecode* compiled;
|
2012-08-16 01:00:30 +01:00
|
|
|
|
|
|
|
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;
|
2012-08-21 18:14:13 +01:00
|
|
|
i->bound_by = 0;
|
|
|
|
i->symbol = 0;
|
|
|
|
i->subfn = gen_noop();
|
2012-11-25 23:07:43 +00:00
|
|
|
i->arglist = gen_noop();
|
2012-09-11 12:24:54 +01:00
|
|
|
i->source = UNKNOWN_LOCATION;
|
2012-08-16 01:00:30 +01:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void inst_free(struct inst* i) {
|
2012-08-21 18:14:13 +01:00
|
|
|
free(i->symbol);
|
2012-12-01 19:41:36 +00:00
|
|
|
block_free(i->subfn);
|
|
|
|
block_free(i->arglist);
|
2012-09-02 16:31:59 +01:00
|
|
|
if (opcode_describe(i->op)->flags & OP_HAS_CONSTANT) {
|
|
|
|
jv_free(i->imm.constant);
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
free(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
static block inst_block(inst* i) {
|
|
|
|
block b = {i,i};
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
2012-08-26 14:25:56 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-09-11 12:24:54 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-08-16 01:00:30 +01:00
|
|
|
block gen_noop() {
|
|
|
|
block b = {0,0};
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
block gen_op_simple(opcode op) {
|
2012-08-22 19:05:53 +01:00
|
|
|
assert(opcode_describe(op)->length == 1);
|
2012-08-16 01:00:30 +01:00
|
|
|
return inst_block(inst_new(op));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-09-02 16:31:59 +01:00
|
|
|
block gen_op_const(opcode op, jv constant) {
|
2012-08-16 01:00:30 +01:00
|
|
|
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) {
|
2012-08-26 14:25:56 +01:00
|
|
|
assert(block_is_single(b));
|
2012-08-16 01:00:30 +01:00
|
|
|
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);
|
2012-08-21 18:14:13 +01:00
|
|
|
i->symbol = strdup(name);
|
2012-08-16 01:00:30 +01:00
|
|
|
return inst_block(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
block gen_op_var_bound(opcode op, block binder) {
|
2012-08-26 14:25:56 +01:00
|
|
|
assert(block_is_single(binder));
|
2012-08-21 18:14:13 +01:00
|
|
|
block b = gen_op_var_unbound(op, binder.first->symbol);
|
|
|
|
b.first->bound_by = binder.first;
|
2012-08-16 01:00:30 +01:00
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
block gen_op_symbol(opcode op, const char* sym) {
|
|
|
|
assert(opcode_describe(op)->flags & OP_HAS_SYMBOL);
|
|
|
|
inst* i = inst_new(op);
|
2012-08-21 18:14:13 +01:00
|
|
|
i->symbol = strdup(sym);
|
|
|
|
return inst_block(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-08-26 14:25:56 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-08-21 18:14:13 +01:00
|
|
|
|
2012-08-16 01:00:30 +01:00
|
|
|
static void inst_join(inst* a, inst* b) {
|
|
|
|
assert(a && b);
|
|
|
|
assert(!a->next);
|
|
|
|
assert(!b->prev);
|
|
|
|
a->next = b;
|
|
|
|
b->prev = a;
|
|
|
|
}
|
|
|
|
|
2012-11-25 23:07:43 +00:00
|
|
|
static void block_insert_after(inst* i, block b) {
|
|
|
|
if (b.first) {
|
|
|
|
assert(b.last);
|
|
|
|
if (i->next) {
|
|
|
|
inst* j = i->next;
|
|
|
|
i->next = 0;
|
|
|
|
j->prev = 0;
|
|
|
|
inst_join(b.last, j);
|
|
|
|
}
|
|
|
|
inst_join(i, b.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void block_insert_before(inst* i, block b) {
|
|
|
|
if (b.first) {
|
|
|
|
assert(b.last);
|
|
|
|
if (i->prev) {
|
|
|
|
inst* j = i->prev;
|
|
|
|
i->prev = 0;
|
|
|
|
j->next = 0;
|
|
|
|
inst_join(j, b.first);
|
|
|
|
}
|
|
|
|
inst_join(b.last, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-16 01:00:30 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-09-18 10:17:38 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-08-21 18:14:13 +01:00
|
|
|
static void block_bind_subblock(block binder, block body, int bindflags) {
|
2012-08-26 14:25:56 +01:00
|
|
|
assert(block_is_single(binder));
|
2012-08-21 18:14:13 +01:00
|
|
|
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);
|
2012-08-16 01:00:30 +01:00
|
|
|
|
2012-08-21 18:14:13 +01:00
|
|
|
binder.first->bound_by = binder.first;
|
2012-08-16 01:00:30 +01:00
|
|
|
for (inst* i = body.first; i; i = i->next) {
|
2012-08-21 18:14:13 +01:00
|
|
|
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) {
|
2012-08-26 14:25:56 +01:00
|
|
|
// binding recurses into closures
|
2012-08-21 18:14:13 +01:00
|
|
|
block_bind_subblock(binder, i->subfn, bindflags);
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
2012-11-25 23:07:43 +00:00
|
|
|
if (flags & OP_HAS_VARIABLE_LENGTH_ARGLIST) {
|
|
|
|
// binding recurses into argument list
|
|
|
|
block_bind_subblock(binder, i->arglist, bindflags);
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
2012-08-21 18:14:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
block block_bind(block binder, block body, int bindflags) {
|
2012-09-18 10:17:38 +01:00
|
|
|
assert(block_has_only_binders(binder, bindflags));
|
2012-08-21 18:14:13 +01:00
|
|
|
bindflags |= OP_HAS_BINDING;
|
2012-09-18 10:17:38 +01:00
|
|
|
for (inst* curr = binder.first; curr; curr = curr->next) {
|
|
|
|
block_bind_subblock(inst_block(curr), body, bindflags);
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
return block_join(binder, body);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-11-26 00:39:01 +00:00
|
|
|
block gen_function(const char* name, block body) {
|
|
|
|
inst* i = inst_new(CLOSURE_CREATE);
|
|
|
|
i->subfn = body;
|
|
|
|
i->symbol = strdup(name);
|
|
|
|
block b = inst_block(i);
|
|
|
|
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
2012-11-26 01:36:55 +00:00
|
|
|
block gen_lambda(block body) {
|
|
|
|
return gen_function("@lambda", body);
|
|
|
|
}
|
|
|
|
|
|
|
|
block gen_call(const char* name, block args) {
|
2012-12-02 20:45:55 +00:00
|
|
|
inst* i = inst_new(CALL_JQ);
|
2012-11-26 01:36:55 +00:00
|
|
|
i->arglist = BLOCK(gen_op_block_unbound(CLOSURE_REF, name), args);
|
|
|
|
return BLOCK(inst_block(i), inst_block(inst_new(CALLSEQ_END)));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-11-26 00:39:01 +00:00
|
|
|
|
2012-08-16 01:00:30 +01:00
|
|
|
block gen_subexp(block a) {
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(gen_op_simple(DUP), a, gen_op_simple(SWAP));
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
block gen_both(block a, block b) {
|
|
|
|
block jump = gen_op_targetlater(JUMP);
|
2012-11-25 23:49:57 +00:00
|
|
|
block fork = gen_op_target(FORK, jump);
|
|
|
|
block c = BLOCK(fork, a, jump, b);
|
2012-08-16 01:00:30 +01:00
|
|
|
inst_set_target(jump, c);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block gen_collect(block expr) {
|
|
|
|
block array_var = block_bind(gen_op_var_unbound(STOREV, "collect"),
|
2012-08-21 18:14:13 +01:00
|
|
|
gen_noop(), OP_HAS_VARIABLE);
|
2012-11-25 23:49:57 +00:00
|
|
|
block c = BLOCK(gen_op_simple(DUP), gen_op_const(LOADK, jv_array()), array_var);
|
2012-08-16 01:00:30 +01:00
|
|
|
|
2012-11-25 23:49:57 +00:00
|
|
|
block tail = BLOCK(gen_op_simple(DUP),
|
|
|
|
gen_op_var_bound(LOADV, array_var),
|
|
|
|
gen_op_simple(SWAP),
|
|
|
|
gen_op_simple(APPEND),
|
|
|
|
gen_op_var_bound(STOREV, array_var),
|
|
|
|
gen_op_simple(BACKTRACK));
|
2012-08-16 01:00:30 +01:00
|
|
|
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(c,
|
|
|
|
gen_op_target(FORK, tail),
|
|
|
|
expr,
|
|
|
|
tail,
|
|
|
|
gen_op_var_bound(LOADV, array_var));
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
|
|
|
|
2012-08-27 10:11:55 +01:00
|
|
|
block gen_assign(block expr) {
|
|
|
|
block result_var = block_bind(gen_op_var_unbound(STOREV, "result"),
|
|
|
|
gen_noop(), OP_HAS_VARIABLE);
|
|
|
|
|
2012-11-25 23:49:57 +00:00
|
|
|
block loop = BLOCK(gen_op_simple(DUP),
|
|
|
|
expr,
|
|
|
|
gen_op_var_bound(ASSIGN, result_var),
|
|
|
|
gen_op_simple(BACKTRACK));
|
2012-08-27 10:11:55 +01:00
|
|
|
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(gen_op_simple(DUP),
|
|
|
|
result_var,
|
|
|
|
gen_op_target(FORK, loop),
|
|
|
|
loop,
|
|
|
|
gen_op_var_bound(LOADV, result_var));
|
2012-08-27 10:11:55 +01:00
|
|
|
}
|
|
|
|
|
2012-09-04 15:34:34 +01:00
|
|
|
block gen_definedor(block a, block b) {
|
|
|
|
// var found := false
|
|
|
|
block found_var = block_bind(gen_op_var_unbound(STOREV, "found"),
|
|
|
|
gen_noop(), OP_HAS_VARIABLE);
|
2012-11-25 23:49:57 +00:00
|
|
|
block init = BLOCK(gen_op_simple(DUP), gen_op_const(LOADK, jv_false()), found_var);
|
2012-09-04 15:34:34 +01:00
|
|
|
|
|
|
|
// if found, backtrack. Otherwise execute b
|
|
|
|
block backtrack = gen_op_simple(BACKTRACK);
|
2012-11-25 23:49:57 +00:00
|
|
|
block tail = BLOCK(gen_op_simple(DUP),
|
|
|
|
gen_op_var_bound(LOADV, found_var),
|
|
|
|
gen_op_target(JUMP_F, backtrack),
|
|
|
|
backtrack,
|
|
|
|
gen_op_simple(POP),
|
|
|
|
b);
|
2012-09-04 15:34:34 +01:00
|
|
|
|
|
|
|
// try again
|
|
|
|
block if_notfound = gen_op_simple(BACKTRACK);
|
|
|
|
|
|
|
|
// found := true, produce result
|
2012-11-25 23:49:57 +00:00
|
|
|
block if_found = BLOCK(gen_op_simple(DUP),
|
|
|
|
gen_op_const(LOADK, jv_true()),
|
|
|
|
gen_op_var_bound(STOREV, found_var),
|
|
|
|
gen_op_target(JUMP, tail));
|
2012-09-04 15:34:34 +01:00
|
|
|
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(init,
|
|
|
|
gen_op_target(FORK, if_notfound),
|
|
|
|
a,
|
|
|
|
gen_op_target(JUMP_F, if_found),
|
|
|
|
if_found,
|
|
|
|
if_notfound,
|
|
|
|
tail);
|
2012-09-04 15:34:34 +01:00
|
|
|
}
|
|
|
|
|
2012-09-17 21:08:43 +01:00
|
|
|
block gen_condbranch(block iftrue, block iffalse) {
|
2012-11-25 23:49:57 +00:00
|
|
|
iftrue = BLOCK(iftrue, gen_op_target(JUMP, iffalse));
|
|
|
|
return BLOCK(gen_op_target(JUMP_F, iftrue), iftrue, iffalse);
|
2012-09-04 20:38:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
block gen_and(block a, block b) {
|
|
|
|
// a and b = if a then (if b then true else false) else false
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(gen_op_simple(DUP), a,
|
|
|
|
gen_condbranch(BLOCK(gen_op_simple(POP),
|
|
|
|
b,
|
|
|
|
gen_condbranch(gen_op_const(LOADK, jv_true()),
|
|
|
|
gen_op_const(LOADK, jv_false()))),
|
|
|
|
BLOCK(gen_op_simple(POP), gen_op_const(LOADK, jv_false()))));
|
2012-09-04 20:38:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
block gen_or(block a, block b) {
|
|
|
|
// a or b = if a then true else (if b then true else false)
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(gen_op_simple(DUP), a,
|
|
|
|
gen_condbranch(BLOCK(gen_op_simple(POP), gen_op_const(LOADK, jv_true())),
|
|
|
|
BLOCK(gen_op_simple(POP),
|
|
|
|
b,
|
|
|
|
gen_condbranch(gen_op_const(LOADK, jv_true()),
|
|
|
|
gen_op_const(LOADK, jv_false())))));
|
2012-09-04 20:43:40 +01:00
|
|
|
}
|
|
|
|
|
2012-09-04 15:34:34 +01:00
|
|
|
block gen_cond(block cond, block iftrue, block iffalse) {
|
2012-11-25 23:49:57 +00:00
|
|
|
return BLOCK(gen_op_simple(DUP), cond,
|
|
|
|
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue),
|
|
|
|
BLOCK(gen_op_simple(POP), iffalse)));
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
|
|
|
|
2012-08-27 11:19:42 +01:00
|
|
|
block gen_cbinding(struct symbol_table* t, block code) {
|
|
|
|
for (int cfunc=0; cfunc<t->ncfunctions; 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;
|
|
|
|
}
|
|
|
|
|
2012-08-21 18:14:13 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
|
2012-08-27 11:19:42 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-11-25 23:07:43 +00:00
|
|
|
|
|
|
|
// Expands call instructions into a calling sequence
|
|
|
|
// Checking for argument count compatibility happens later
|
2012-11-26 01:36:55 +00:00
|
|
|
static block expand_call_arglist(block b) {
|
2012-11-26 00:39:01 +00:00
|
|
|
block ret = gen_noop();
|
|
|
|
for (inst* curr; (curr = block_take(&b));) {
|
2012-11-25 23:07:43 +00:00
|
|
|
if (opcode_describe(curr->op)->flags & OP_HAS_VARIABLE_LENGTH_ARGLIST) {
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(curr->op == CALL_JQ);
|
2012-11-26 00:39:01 +00:00
|
|
|
inst* seq_end = block_take(&b);
|
|
|
|
assert(seq_end && seq_end->op == CALLSEQ_END);
|
2012-11-25 23:07:43 +00:00
|
|
|
// We expand the argument list as a series of instructions
|
|
|
|
block arglist = curr->arglist;
|
|
|
|
curr->arglist = gen_noop();
|
|
|
|
assert(arglist.first && "zeroth argument (function to call) must be present");
|
|
|
|
inst* function = arglist.first->bound_by;
|
2012-11-26 01:36:55 +00:00
|
|
|
assert(function); // FIXME better errors
|
2012-11-25 23:07:43 +00:00
|
|
|
|
|
|
|
switch (function->op) {
|
|
|
|
default: assert(0 && "Unknown parameter type"); break;
|
|
|
|
case CLOSURE_CREATE:
|
|
|
|
case CLOSURE_PARAM: {
|
|
|
|
block prelude = gen_noop();
|
|
|
|
block callargs = gen_noop();
|
|
|
|
int nargs = 0;
|
|
|
|
for (inst* i; (i = block_take(&arglist));) {
|
|
|
|
assert(opcode_describe(i->op)->flags & OP_IS_CALL_PSEUDO);
|
|
|
|
block b = inst_block(i);
|
|
|
|
switch (i->op) {
|
|
|
|
default: assert(0 && "Unknown type of parameter"); break;
|
|
|
|
case CLOSURE_REF:
|
|
|
|
block_append(&callargs, b);
|
|
|
|
break;
|
|
|
|
case CLOSURE_CREATE:
|
|
|
|
block_append(&prelude, b);
|
|
|
|
block_append(&callargs, gen_op_block_bound(CLOSURE_REF, b));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nargs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(!arglist.first);
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(nargs > 0);
|
|
|
|
curr->imm.intval = nargs - 1;
|
2012-11-26 00:39:01 +00:00
|
|
|
ret = BLOCK(ret, prelude, inst_block(curr), callargs, inst_block(seq_end));
|
2012-11-25 23:07:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-11-26 01:36:55 +00:00
|
|
|
case CLOSURE_CREATE_C: {
|
2012-11-25 23:07:43 +00:00
|
|
|
// Arguments to C functions not yet supported
|
2012-11-26 01:36:55 +00:00
|
|
|
inst* cfunction_ref = block_take(&arglist);
|
|
|
|
block prelude = gen_noop();
|
2012-12-02 20:45:55 +00:00
|
|
|
int nargs = 1;
|
2012-11-26 01:36:55 +00:00
|
|
|
for (inst* i; (i = block_take(&arglist)); ) {
|
|
|
|
assert(i->op == CLOSURE_CREATE); // FIXME
|
|
|
|
block body = i->subfn;
|
|
|
|
i->subfn = gen_noop();
|
|
|
|
inst_free(i);
|
|
|
|
prelude = BLOCK(prelude, gen_subexp(expand_call_arglist(body)));
|
|
|
|
nargs++;
|
|
|
|
}
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(curr->op == CALL_JQ);
|
|
|
|
curr->op = CALL_BUILTIN;
|
2012-12-01 19:41:36 +00:00
|
|
|
curr->imm.intval = nargs;
|
|
|
|
assert(!curr->arglist.first);
|
|
|
|
curr->arglist = inst_block(cfunction_ref);
|
|
|
|
ret = BLOCK(ret, prelude, inst_block(curr), inst_block(seq_end));
|
2012-11-25 23:07:43 +00:00
|
|
|
break;
|
|
|
|
}
|
2012-11-26 01:36:55 +00:00
|
|
|
}
|
2012-11-26 00:39:01 +00:00
|
|
|
} else {
|
|
|
|
ret = BLOCK(ret, inst_block(curr));
|
2012-11-25 23:07:43 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-26 01:36:55 +00:00
|
|
|
return ret;
|
2012-11-25 23:07:43 +00:00
|
|
|
}
|
|
|
|
|
2012-09-11 12:24:54 +01:00
|
|
|
static int compile(struct locfile* locations, struct bytecode* bc, block b) {
|
|
|
|
int errors = 0;
|
2012-08-16 01:00:30 +01:00
|
|
|
int pos = 0;
|
|
|
|
int var_frame_idx = 0;
|
2012-08-21 18:14:13 +01:00
|
|
|
bc->nsubfunctions = 0;
|
2012-08-26 14:25:56 +01:00
|
|
|
bc->nclosures = 0;
|
2012-11-26 01:36:55 +00:00
|
|
|
b = expand_call_arglist(b);
|
|
|
|
if (bc->parent) {
|
|
|
|
// functions should end in a return
|
|
|
|
b = BLOCK(b, gen_op_simple(RET));
|
|
|
|
} else {
|
|
|
|
// the toplevel should YIELD;BACKTRACK; when it finds an answer
|
|
|
|
b = BLOCK(b, gen_op_simple(YIELD), gen_op_simple(BACKTRACK));
|
|
|
|
}
|
2012-08-21 18:14:13 +01:00
|
|
|
for (inst* curr = b.first; curr; curr = curr->next) {
|
2012-08-16 01:00:30 +01:00
|
|
|
if (!curr->next) assert(curr == b.last);
|
|
|
|
pos += opcode_length(curr->op);
|
|
|
|
curr->bytecode_pos = pos;
|
2012-08-21 18:14:13 +01:00
|
|
|
curr->compiled = bc;
|
|
|
|
|
|
|
|
int opflags = opcode_describe(curr->op)->flags;
|
|
|
|
if (opflags & OP_HAS_BINDING) {
|
2012-09-11 12:24:54 +01:00
|
|
|
if (!curr->bound_by) {
|
|
|
|
locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol);
|
|
|
|
errors++;
|
|
|
|
}
|
2012-08-21 18:14:13 +01:00
|
|
|
}
|
|
|
|
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++;
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
2012-11-25 23:07:43 +00:00
|
|
|
|
2012-08-26 14:25:56 +01:00
|
|
|
if (curr->op == CLOSURE_PARAM) {
|
|
|
|
assert(curr->bound_by == curr);
|
|
|
|
curr->imm.intval = bc->nclosures++;
|
|
|
|
}
|
2012-08-27 11:19:42 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
2012-08-21 18:14:13 +01:00
|
|
|
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;
|
2012-09-11 12:24:54 +01:00
|
|
|
errors += compile(locations, subfn, curr->subfn);
|
2012-11-26 01:36:55 +00:00
|
|
|
curr->subfn = gen_noop();
|
2012-08-21 18:14:13 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bc->subfunctions = 0;
|
|
|
|
}
|
2012-08-16 01:00:30 +01:00
|
|
|
bc->codelen = pos;
|
|
|
|
uint16_t* code = malloc(sizeof(uint16_t) * bc->codelen);
|
|
|
|
bc->code = code;
|
|
|
|
pos = 0;
|
2012-09-02 16:31:59 +01:00
|
|
|
jv constant_pool = jv_array();
|
2012-08-16 01:00:30 +01:00
|
|
|
int maxvar = -1;
|
2012-09-11 12:24:54 +01:00
|
|
|
if (!errors) for (inst* curr = b.first; curr; curr = curr->next) {
|
2012-08-21 18:14:13 +01:00
|
|
|
const struct opcode_description* op = opcode_describe(curr->op);
|
2012-08-22 19:21:35 +01:00
|
|
|
if (op->length == 0)
|
|
|
|
continue;
|
2012-08-16 01:00:30 +01:00
|
|
|
code[pos++] = curr->op;
|
|
|
|
int opflags = op->flags;
|
2012-08-21 18:14:13 +01:00
|
|
|
assert(!(op->flags & OP_IS_CALL_PSEUDO));
|
2012-12-02 20:45:55 +00:00
|
|
|
if (curr->op == CALL_BUILTIN) {
|
2012-12-01 19:41:36 +00:00
|
|
|
int nargs = curr->imm.intval;
|
|
|
|
code[pos++] = (uint16_t)nargs;
|
|
|
|
assert(block_is_single(curr->arglist));
|
|
|
|
inst* cfunc = curr->arglist.first;
|
|
|
|
assert(cfunc && cfunc->bound_by->op == CLOSURE_CREATE_C);
|
2012-12-02 20:45:55 +00:00
|
|
|
//*opcode_rewrite = bc->globals->cfunctions[cfunc->bound_by->imm.intval].callop;
|
2012-12-01 19:41:36 +00:00
|
|
|
code[pos++] = cfunc->bound_by->imm.intval;
|
|
|
|
// FIXME arg errors
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(nargs == bc->globals->cfunctions[cfunc->bound_by->imm.intval].nargs);
|
2012-12-01 19:41:36 +00:00
|
|
|
} else if (opflags & OP_HAS_VARIABLE_LENGTH_ARGLIST) {
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(curr->op == CALL_JQ);
|
2012-08-21 18:14:13 +01:00
|
|
|
int nargs = curr->imm.intval;
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(nargs >= 0 && nargs < 100); //FIXME
|
2012-08-21 18:14:13 +01:00
|
|
|
code[pos++] = (uint16_t)nargs;
|
2012-12-01 19:41:36 +00:00
|
|
|
curr = curr->next;
|
|
|
|
assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
|
|
|
|
assert(curr->bound_by->op == CLOSURE_CREATE ||
|
|
|
|
curr->bound_by->op == CLOSURE_PARAM);
|
|
|
|
code[pos++] = nesting_level(bc, curr->bound_by);
|
|
|
|
if (curr->bound_by->op == CLOSURE_CREATE) {
|
|
|
|
code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE;
|
|
|
|
inst* i = curr->bound_by;
|
|
|
|
// FIXME arg errors
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(nargs == i->compiled->subfunctions[i->imm.intval]->nclosures);
|
2012-12-01 19:41:36 +00:00
|
|
|
} else {
|
|
|
|
code[pos++] = curr->bound_by->imm.intval;
|
|
|
|
// FIXME arg errors
|
2012-12-02 20:45:55 +00:00
|
|
|
assert(nargs == 0);
|
2012-12-01 19:41:36 +00:00
|
|
|
}
|
2012-12-02 20:45:55 +00:00
|
|
|
for (int i=0; i<nargs; i++) {
|
2012-08-21 18:14:13 +01:00
|
|
|
curr = curr->next;
|
|
|
|
assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
|
2012-12-01 19:41:36 +00:00
|
|
|
assert(curr->op == CLOSURE_REF);
|
2012-08-21 18:14:13 +01:00
|
|
|
code[pos++] = nesting_level(bc, curr->bound_by);
|
2012-12-01 19:41:36 +00:00
|
|
|
assert(curr->bound_by->op == CLOSURE_CREATE);
|
|
|
|
code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE;
|
2012-08-21 18:14:13 +01:00
|
|
|
}
|
|
|
|
} else if (opflags & OP_HAS_CONSTANT) {
|
2012-09-02 16:31:59 +01:00
|
|
|
code[pos++] = jv_array_length(jv_copy(constant_pool));
|
|
|
|
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant));
|
2012-08-16 01:00:30 +01:00
|
|
|
} else if (opflags & OP_HAS_VARIABLE) {
|
2012-08-21 18:24:38 +01:00
|
|
|
code[pos++] = nesting_level(bc, curr->bound_by);
|
2012-08-21 18:14:13 +01:00
|
|
|
uint16_t var = (uint16_t)curr->bound_by->imm.intval;
|
2012-08-16 01:00:30 +01:00
|
|
|
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++;
|
2012-08-22 19:05:53 +01:00
|
|
|
} else if (op->length > 1) {
|
|
|
|
assert(0 && "codegen not implemented for this operation");
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
bc->constants = constant_pool;
|
2012-08-21 12:35:36 +01:00
|
|
|
bc->nlocals = maxvar + 2; // FIXME: frames of size zero?
|
2012-11-26 01:36:55 +00:00
|
|
|
block_free(b);
|
2012-09-11 12:24:54 +01:00
|
|
|
return errors;
|
2012-08-21 18:14:13 +01:00
|
|
|
}
|
|
|
|
|
2012-09-11 12:24:54 +01:00
|
|
|
int block_compile(block b, struct locfile* locations, struct bytecode** out) {
|
2012-08-21 18:14:13 +01:00
|
|
|
struct bytecode* bc = malloc(sizeof(struct bytecode));
|
|
|
|
bc->parent = 0;
|
2012-08-27 11:19:42 +01:00
|
|
|
bc->globals = malloc(sizeof(struct symbol_table));
|
|
|
|
int ncfunc = count_cfunctions(b);
|
|
|
|
bc->globals->ncfunctions = 0;
|
|
|
|
bc->globals->cfunctions = malloc(sizeof(struct cfunction) * ncfunc);
|
2012-09-11 12:24:54 +01:00
|
|
|
int nerrors = compile(locations, bc, b);
|
2012-08-27 11:19:42 +01:00
|
|
|
assert(bc->globals->ncfunctions == ncfunc);
|
2012-09-11 12:24:54 +01:00
|
|
|
if (nerrors > 0) {
|
|
|
|
bytecode_free(bc);
|
|
|
|
*out = 0;
|
|
|
|
} else {
|
|
|
|
*out = bc;
|
|
|
|
}
|
|
|
|
return nerrors;
|
2012-08-16 01:00:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void block_free(block b) {
|
|
|
|
struct inst* next;
|
|
|
|
for (struct inst* curr = b.first; curr; curr = next) {
|
|
|
|
next = curr->next;
|
|
|
|
inst_free(curr);
|
|
|
|
}
|
|
|
|
}
|