Refactor function argument passing into what it always should have been.

Most visible change is that error messages when a function is called
with the wrong number of arguments are much better.
This commit is contained in:
Stephen Dolan committed 2012-12-04 00:39:21 +00:00
1 parent a88d53d2fd
commit 616e8f9924
8 files changed
+103 -105

No files matched your search

+2 -1
View File
@@ -285,7 +285,8 @@ static block bind_bytecoded_builtins(block b) {
}; };
block builtins = gen_noop(); block builtins = gen_noop();
for (unsigned i=0; i<sizeof(builtin_defs)/sizeof(builtin_defs[0]); i++) { for (unsigned i=0; i<sizeof(builtin_defs)/sizeof(builtin_defs[0]); i++) {
builtins = BLOCK(builtins, gen_function(builtin_defs[i].name, builtin_defs[i].code)); builtins = BLOCK(builtins, gen_function(builtin_defs[i].name, gen_noop(),
builtin_defs[i].code));
} }
return block_bind(builtins, b, OP_IS_CALL_PSEUDO); return block_bind(builtins, b, OP_IS_CALL_PSEUDO);
} }
+3 -3
View File
@@ -6,11 +6,11 @@
#include "opcode.h" #include "opcode.h"
static int bytecode_operation_length(uint16_t* codeptr) { static int bytecode_operation_length(uint16_t* codeptr) {
int length = opcode_describe(*codeptr)->length;
if (*codeptr == CALL_JQ) { if (*codeptr == CALL_JQ) {
return 4 + codeptr[1] * 2; length += codeptr[1] * 2;
} else {
return opcode_length(*codeptr);
} }
return length;
} }
void dump_disassembly(int indent, struct bytecode* bc) { void dump_disassembly(int indent, struct bytecode* bc) {
+91 -89
View File
@@ -12,7 +12,7 @@ struct inst {
opcode op; opcode op;
union { struct {
uint16_t intval; uint16_t intval;
struct inst* target; struct inst* target;
jv constant; jv constant;
@@ -222,33 +222,38 @@ static void block_bind_subblock(block binder, block body, int bindflags) {
} }
} }
block block_bind(block binder, block body, int bindflags) { static void block_bind_each(block binder, block body, int bindflags) {
assert(block_has_only_binders(binder, bindflags)); assert(block_has_only_binders(binder, bindflags));
bindflags |= OP_HAS_BINDING; bindflags |= OP_HAS_BINDING;
for (inst* curr = binder.first; curr; curr = curr->next) { for (inst* curr = binder.first; curr; curr = curr->next) {
block_bind_subblock(inst_block(curr), body, bindflags); block_bind_subblock(inst_block(curr), body, bindflags);
} }
}
block block_bind(block binder, block body, int bindflags) {
block_bind_each(binder, body, bindflags);
return block_join(binder, body); return block_join(binder, body);
} }
block gen_function(const char* name, block formals, block body) {
block gen_function(const char* name, block body) { block_bind_each(formals, body, OP_IS_CALL_PSEUDO);
inst* i = inst_new(CLOSURE_CREATE); inst* i = inst_new(CLOSURE_CREATE);
i->subfn = body; i->subfn = body;
i->symbol = strdup(name); i->symbol = strdup(name);
i->arglist = formals;
block b = inst_block(i); block b = inst_block(i);
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING); block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING);
return b; return b;
} }
block gen_lambda(block body) { block gen_lambda(block body) {
return gen_function("@lambda", body); return gen_function("@lambda", gen_noop(), body);
} }
block gen_call(const char* name, block args) { block gen_call(const char* name, block args) {
inst* i = inst_new(CALL_JQ); block b = gen_op_block_unbound(CALL_JQ, name);
i->arglist = BLOCK(gen_op_block_unbound(CLOSURE_REF, name), args); b.first->arglist = args;
return BLOCK(inst_block(i), inst_block(inst_new(CALLSEQ_END))); return b;
} }
@@ -397,28 +402,30 @@ static int count_cfunctions(block b) {
// Expands call instructions into a calling sequence // Expands call instructions into a calling sequence
// Checking for argument count compatibility happens later static int expand_call_arglist(struct locfile* locations, block* b) {
static block expand_call_arglist(block b) { int errors = 0;
block ret = gen_noop(); block ret = gen_noop();
for (inst* curr; (curr = block_take(&b));) { for (inst* curr; (curr = block_take(b));) {
if (curr->op == CALL_JQ) { if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) {
inst* seq_end = block_take(&b); if (!curr->bound_by) {
assert(seq_end && seq_end->op == CALLSEQ_END); locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol);
// We expand the argument list as a series of instructions errors++;
block arglist = curr->arglist; // don't process this instruction if it's not well-defined
curr->arglist = gen_noop(); ret = BLOCK(ret, inst_block(curr));
assert(arglist.first && "zeroth argument (function to call) must be present"); continue;
inst* function = arglist.first->bound_by; }
assert(function); // FIXME better errors }
switch (function->op) { block prelude = gen_noop();
default: assert(0 && "Unknown parameter type"); break; if (curr->op == CALL_JQ) {
int actual_args = 0, desired_args = 0;
// We expand the argument list as a series of instructions
switch (curr->bound_by->op) {
default: assert(0 && "Unknown function type"); break;
case CLOSURE_CREATE: case CLOSURE_CREATE:
case CLOSURE_PARAM: { case CLOSURE_PARAM: {
block prelude = gen_noop();
block callargs = gen_noop(); block callargs = gen_noop();
int nargs = 0; for (inst* i; (i = block_take(&curr->arglist));) {
for (inst* i; (i = block_take(&arglist));) {
assert(opcode_describe(i->op)->flags & OP_IS_CALL_PSEUDO); assert(opcode_describe(i->op)->flags & OP_IS_CALL_PSEUDO);
block b = inst_block(i); block b = inst_block(i);
switch (i->op) { switch (i->op) {
@@ -431,44 +438,54 @@ static block expand_call_arglist(block b) {
block_append(&callargs, gen_op_block_bound(CLOSURE_REF, b)); block_append(&callargs, gen_op_block_bound(CLOSURE_REF, b));
break; break;
} }
nargs++; actual_args++;
} }
curr->imm.intval = actual_args;
curr->arglist = callargs;
assert(!arglist.first); if (curr->bound_by->op == CLOSURE_CREATE) {
assert(nargs > 0); for (inst* i = curr->bound_by->arglist.first; i; i = i->next) {
curr->imm.intval = nargs - 1; assert(i->op == CLOSURE_PARAM);
ret = BLOCK(ret, prelude, inst_block(curr), callargs, inst_block(seq_end)); desired_args++;
}
}
break; break;
} }
case CLOSURE_CREATE_C: { case CLOSURE_CREATE_C: {
// Arguments to C functions not yet supported for (inst* i; (i = block_take(&curr->arglist)); ) {
inst* cfunction_ref = block_take(&arglist);
block prelude = gen_noop();
int nargs = 1;
for (inst* i; (i = block_take(&arglist)); ) {
assert(i->op == CLOSURE_CREATE); // FIXME assert(i->op == CLOSURE_CREATE); // FIXME
block body = i->subfn; block body = i->subfn;
i->subfn = gen_noop(); i->subfn = gen_noop();
inst_free(i); inst_free(i);
// arguments should be pushed in reverse order, prepend them to prelude // arguments should be pushed in reverse order, prepend them to prelude
prelude = BLOCK(gen_subexp(expand_call_arglist(body)), prelude); errors += expand_call_arglist(locations, &body);
nargs++; prelude = BLOCK(gen_subexp(body), prelude);
actual_args++;
} }
assert(curr->op == CALL_JQ); assert(curr->op == CALL_JQ);
curr->op = CALL_BUILTIN; curr->op = CALL_BUILTIN;
curr->imm.intval = nargs; curr->imm.intval = actual_args + 1 /* include the implicit input in arg count */;
assert(curr->bound_by->op == CLOSURE_CREATE_C);
desired_args = curr->bound_by->imm.cfunc->nargs - 1;
assert(!curr->arglist.first); assert(!curr->arglist.first);
curr->arglist = inst_block(cfunction_ref);
ret = BLOCK(ret, prelude, inst_block(curr), inst_block(seq_end));
break; break;
} }
} }
} else {
ret = BLOCK(ret, inst_block(curr)); if (actual_args != desired_args) {
locfile_locate(locations, curr->source,
"error: %s arguments to %s (expected %d but got %d)",
actual_args > desired_args ? "too many" : "too few",
curr->symbol, desired_args, actual_args);
errors++;
}
} }
ret = BLOCK(ret, prelude, inst_block(curr));
} }
return ret; *b = ret;
return errors;
} }
static int compile(struct locfile* locations, struct bytecode* bc, block b) { static int compile(struct locfile* locations, struct bytecode* bc, block b) {
@@ -476,8 +493,7 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
int pos = 0; int pos = 0;
int var_frame_idx = 0; int var_frame_idx = 0;
bc->nsubfunctions = 0; bc->nsubfunctions = 0;
bc->nclosures = 0; errors += expand_call_arglist(locations, &b);
b = expand_call_arglist(b);
if (bc->parent) { if (bc->parent) {
// functions should end in a return // functions should end in a return
b = BLOCK(b, gen_op_simple(RET)); b = BLOCK(b, gen_op_simple(RET));
@@ -487,18 +503,19 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
} }
for (inst* curr = b.first; curr; curr = curr->next) { for (inst* curr = b.first; curr; curr = curr->next) {
if (!curr->next) assert(curr == b.last); if (!curr->next) assert(curr == b.last);
pos += opcode_length(curr->op); int length = opcode_describe(curr->op)->length;
if (curr->op == CALL_JQ) {
for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
length += 2;
}
}
pos += length;
curr->bytecode_pos = pos; curr->bytecode_pos = pos;
curr->compiled = bc; curr->compiled = bc;
int opflags = opcode_describe(curr->op)->flags; assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
if (opflags & OP_HAS_BINDING) {
if (!curr->bound_by) { if ((opcode_describe(curr->op)->flags & OP_HAS_VARIABLE) &&
locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol);
errors++;
}
}
if ((opflags & OP_HAS_VARIABLE) &&
curr->bound_by == curr) { curr->bound_by == curr) {
curr->imm.intval = var_frame_idx++; curr->imm.intval = var_frame_idx++;
} }
@@ -507,10 +524,6 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
assert(curr->bound_by == curr); assert(curr->bound_by == curr);
curr->imm.intval = bc->nsubfunctions++; 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) { if (curr->op == CLOSURE_CREATE_C) {
assert(curr->bound_by == curr); assert(curr->bound_by == curr);
int idx = bc->globals->ncfunctions++; int idx = bc->globals->ncfunctions++;
@@ -526,6 +539,13 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
bc->subfunctions[curr->imm.intval] = subfn; bc->subfunctions[curr->imm.intval] = subfn;
subfn->globals = bc->globals; subfn->globals = bc->globals;
subfn->parent = bc; subfn->parent = bc;
subfn->nclosures = 0;
for (inst* param = curr->arglist.first; param; param = param->next) {
assert(param->op == CLOSURE_PARAM);
assert(param->bound_by == param);
param->imm.intval = subfn->nclosures++;
param->compiled = subfn;
}
errors += compile(locations, subfn, curr->subfn); errors += compile(locations, subfn, curr->subfn);
curr->subfn = gen_noop(); curr->subfn = gen_noop();
} }
@@ -544,42 +564,23 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
if (op->length == 0) if (op->length == 0)
continue; continue;
code[pos++] = curr->op; code[pos++] = curr->op;
assert(!(op->flags & OP_IS_CALL_PSEUDO)); assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
if (curr->op == CALL_BUILTIN) { if (curr->op == CALL_BUILTIN) {
int nargs = curr->imm.intval; assert(curr->bound_by->op == CLOSURE_CREATE_C);
code[pos++] = (uint16_t)nargs; assert(!curr->arglist.first);
assert(block_is_single(curr->arglist)); code[pos++] = (uint16_t)curr->imm.intval;
inst* cfunc = curr->arglist.first; code[pos++] = curr->bound_by->imm.intval;
assert(cfunc && cfunc->bound_by->op == CLOSURE_CREATE_C);
code[pos++] = cfunc->bound_by->imm.intval;
// FIXME arg errors
assert(nargs == bc->globals->cfunctions[cfunc->bound_by->imm.intval].nargs);
} else if (curr->op == CALL_JQ) { } else if (curr->op == CALL_JQ) {
int nargs = curr->imm.intval;
assert(nargs >= 0 && nargs < 100); //FIXME
code[pos++] = (uint16_t)nargs;
curr = curr->next;
assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
assert(curr->bound_by->op == CLOSURE_CREATE || assert(curr->bound_by->op == CLOSURE_CREATE ||
curr->bound_by->op == CLOSURE_PARAM); curr->bound_by->op == CLOSURE_PARAM);
code[pos++] = (uint16_t)curr->imm.intval;
code[pos++] = nesting_level(bc, curr->bound_by); code[pos++] = nesting_level(bc, curr->bound_by);
if (curr->bound_by->op == CLOSURE_CREATE) { code[pos++] = curr->bound_by->imm.intval |
code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE; (curr->bound_by->op == CLOSURE_CREATE ? ARG_NEWCLOSURE : 0);
inst* i = curr->bound_by; for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
// FIXME arg errors assert(arg->op == CLOSURE_REF && arg->bound_by->op == CLOSURE_CREATE);
assert(nargs == i->compiled->subfunctions[i->imm.intval]->nclosures); code[pos++] = nesting_level(bc, arg->bound_by);
} else { code[pos++] = arg->bound_by->imm.intval | ARG_NEWCLOSURE;
code[pos++] = curr->bound_by->imm.intval;
// FIXME arg errors
assert(nargs == 0);
}
for (int i=0; i<nargs; i++) {
curr = curr->next;
assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
assert(curr->op == CLOSURE_REF);
code[pos++] = nesting_level(bc, curr->bound_by);
assert(curr->bound_by->op == CLOSURE_CREATE);
code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE;
} }
} else if (op->flags & OP_HAS_CONSTANT) { } else if (op->flags & OP_HAS_CONSTANT) {
code[pos++] = jv_array_length(jv_copy(constant_pool)); code[pos++] = jv_array_length(jv_copy(constant_pool));
@@ -607,6 +608,7 @@ static int compile(struct locfile* locations, struct bytecode* bc, block b) {
int block_compile(block b, struct locfile* locations, struct bytecode** out) { int block_compile(block b, struct locfile* locations, struct bytecode** out) {
struct bytecode* bc = malloc(sizeof(struct bytecode)); struct bytecode* bc = malloc(sizeof(struct bytecode));
bc->parent = 0; bc->parent = 0;
bc->nclosures = 0;
bc->globals = malloc(sizeof(struct symbol_table)); bc->globals = malloc(sizeof(struct symbol_table));
int ncfunc = count_cfunctions(b); int ncfunc = count_cfunctions(b);
bc->globals->ncfunctions = 0; bc->globals->ncfunctions = 0;
+1 -1
View File
@@ -27,7 +27,7 @@ block gen_op_var_unbound(opcode op, const char* name);
block gen_op_var_bound(opcode op, block binder); block gen_op_var_bound(opcode op, block binder);
block gen_op_block_unbound(opcode op, const char* name); block gen_op_block_unbound(opcode op, const char* name);
block gen_function(const char* name, block body); block gen_function(const char* name, block formals, block body);
block gen_lambda(block body); block gen_lambda(block body);
block gen_call(const char* name, block body); block gen_call(const char* name, block body);
block gen_subexp(block a); block gen_subexp(block a);
+2 -3
View File
@@ -5,9 +5,8 @@
#define CONSTANT OP_HAS_CONSTANT, 2 #define CONSTANT OP_HAS_CONSTANT, 2
#define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3 #define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3
#define BRANCH OP_HAS_BRANCH, 2 #define BRANCH OP_HAS_BRANCH, 2
#define CFUNC OP_HAS_CFUNC, 3 #define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3
#define UFUNC OP_HAS_UFUNC, 2 #define UFUNC (OP_HAS_UFUNC | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4
#define CALLSEQ_END_IMM (OP_IS_CALL_PSEUDO), 0
#define DEFINITION (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 0 #define DEFINITION (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 0
#define CLOSURE_REF_IMM (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 2 #define CLOSURE_REF_IMM (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 2
-4
View File
@@ -38,8 +38,4 @@ struct opcode_description {
const struct opcode_description* opcode_describe(opcode op); const struct opcode_description* opcode_describe(opcode op);
static inline int opcode_length(opcode op) {
return opcode_describe(op)->length;
}
#endif #endif
-1
View File
@@ -22,7 +22,6 @@ OP(CALL_BUILTIN, CFUNC, -1, 1)
OP(CALL_JQ, UFUNC, 1, 1) OP(CALL_JQ, UFUNC, 1, 1)
OP(RET, NONE, 1, 1) OP(RET, NONE, 1, 1)
OP(CALLSEQ_END, CALLSEQ_END_IMM, 0, 0)
OP(CLOSURE_PARAM, DEFINITION, 0, 0) OP(CLOSURE_PARAM, DEFINITION, 0, 0)
OP(CLOSURE_REF, CLOSURE_REF_IMM, 0, 0) OP(CLOSURE_REF, CLOSURE_REF_IMM, 0, 0)
OP(CLOSURE_CREATE, DEFINITION, 0, 0) OP(CLOSURE_CREATE, DEFINITION, 0, 0)
+4 -3
View File
@@ -299,13 +299,14 @@ Term {
FuncDef: FuncDef:
"def" IDENT ':' Exp ';' { "def" IDENT ':' Exp ';' {
$$ = gen_function(jv_string_value($2), $4); $$ = gen_function(jv_string_value($2), gen_noop(), $4);
jv_free($2); jv_free($2);
} | } |
"def" IDENT '(' IDENT ')' ':' Exp ';' { "def" IDENT '(' IDENT ')' ':' Exp ';' {
block body = block_bind(gen_op_block_unbound(CLOSURE_PARAM, jv_string_value($4)), $7, OP_IS_CALL_PSEUDO); $$ = gen_function(jv_string_value($2),
$$ = gen_function(jv_string_value($2), body); gen_op_block_unbound(CLOSURE_PARAM, jv_string_value($4)),
$7);
jv_free($2); jv_free($2);
jv_free($4); jv_free($4);
} }