diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2023-11-19 18:41:55 +0200 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2023-11-19 18:41:55 +0200 |
| commit | 8f754f8b13e55e4bb92d72f105c5aaaba1754b7d (patch) | |
| tree | b70b813c091691d6645f8cb82295f41f24f3be93 /src | |
| parent | 5304dbcf4df1fc211b5099f4d6eb7f23dfa8c454 (diff) | |
| download | ek-8f754f8b13e55e4bb92d72f105c5aaaba1754b7d.tar.gz ek-8f754f8b13e55e4bb92d72f105c5aaaba1754b7d.zip | |
start working on instruction lowering
Diffstat (limited to 'src')
| -rw-r--r-- | src/actualize.c | 296 | ||||
| -rw-r--r-- | src/ast.c | 18 | ||||
| -rw-r--r-- | src/compiler.c | 33 | ||||
| -rw-r--r-- | src/debug.c | 7 | ||||
| -rw-r--r-- | src/main.c | 36 | ||||
| -rw-r--r-- | src/ops.c | 395 | ||||
| -rw-r--r-- | src/parser.y | 19 | ||||
| -rw-r--r-- | src/scope.c | 42 |
8 files changed, 586 insertions, 260 deletions
diff --git a/src/actualize.c b/src/actualize.c index aa4d409..ab72589 100644 --- a/src/actualize.c +++ b/src/actualize.c @@ -26,9 +26,7 @@ struct act_stack { enum act_flags { ACT_IN_LOOP = (1 << 0), ACT_IN_SWITCH = (1 << 1), - ACT_ONLY_TYPES = (1 << 2), - ACT_HAS_RETURN = (1 << 3), - ACT_REQUIRE_FULLY_QUALIFIED = (1 << 4), + ACT_HAS_RETURN = (1 << 2), }; struct act_state { @@ -42,6 +40,21 @@ struct act_state { struct act_stack *label_stack; }; +static int is_lvalue(struct ast_node *n) +{ + if (n->node_type == AST_ARR_ACCESS) + return 1; + + if (n->node_type == AST_ID) + return 1; + + if (n->node_type == AST_UNOP && AST_UNOP(n).op == AST_DEREF) + return 1; + + return 0; +} + + static void act_set_flags(struct act_state *state, enum act_flags flags) { state->flags |= flags; @@ -65,7 +78,7 @@ static int is_void(struct ast_node *type) static struct ast_node *void_type() { struct ast_node *v = gen_primitive(AST_VOID, NULL_LOC()); - ast_set_flags(v, AST_FLAG_ACTUAL); + ast_set_flags(v, AST_FLAG_INIT | AST_FLAG_ACTUAL); v->type = v; return v; } @@ -73,7 +86,7 @@ static struct ast_node *void_type() static struct ast_node *i27_type() { struct ast_node *a = gen_primitive(AST_I27, NULL_LOC()); - ast_set_flags(a, AST_FLAG_ACTUAL); + ast_set_flags(a, AST_FLAG_INIT | AST_FLAG_ACTUAL); a->type = a; return a; } @@ -189,7 +202,6 @@ static void clear_labels(struct act_state *state, struct act_stack *to) static void destroy_act_state(struct act_state *state) { - /* clears all defers */ clear_defers(state, NULL); clear_labels(state, NULL); clear_gotos(state, NULL); @@ -442,11 +454,7 @@ static int _replace_id(struct ast_node *node, void *data) clone->next = node->next; clone->scope = node->scope; - /* we know this is an ID node, so we know exactly what to free */ - free((void *)node->_id.id); - *node = *clone; - free(clone); /* a succesful replacement needs no futher replacements, I think */ return 0; @@ -504,50 +512,6 @@ static int actualize_proc_call(struct act_state *state, struct scope *scope, struct ast_node *call, struct ast_node *proc) { - /* proc has already been actualized, so we don't have to do anything */ - if (ast_flags(proc, AST_FLAG_ACTUAL)) { - /* call node type is the return type */ - struct ast_node *sign = proc->type; - assert(sign->node_type == AST_TYPE); - - call->type = AST_SIGN_TYPE(sign).ret; - return 0; - } - - if (act_flags(state, ACT_ONLY_TYPES)) { - /* TODO: better cleanup */ - /* at this point we're really only interested in the return - * type, but if it's a typeof expression we need to actualize - * the arguments as well, unfortunately */ - struct ast_node *sign = clone_ast_node(proc->_proc.sign); - if (!sign) { - internal_error("failed cloning proc signature"); - return -1; - } - - struct scope *tmp = create_scope(); - if (!tmp) { - internal_error( - "failed creating temporary signature scope"); - return -1; - } - - /* if the function is in some completely different file for - * example, we want to use that file's scope */ - scope_add_scope(proc->scope, tmp); - - struct ast_node *params = AST_SIGN_TYPE(sign).params; - struct ast_node *args = call->_call.args; - /* fuck, analyze_proc gobbles up the return type typeof */ - actualize_trait_types(params, args); - - if (actualize(state, tmp, sign)) - return -1; - - call->type = AST_SIGN_TYPE(sign).ret; - return 0; - } - /* clone procedure definition to * replace trait types with actual types and actualize it */ struct ast_node *def = clone_ast_node(proc); @@ -557,9 +521,9 @@ static int actualize_proc_call(struct act_state *state, return -1; } - struct ast_node *sign = def->_proc.sign; + struct ast_node *sign = AST_PROC(def).sign; struct ast_node *params = AST_SIGN_TYPE(sign).params; - struct ast_node *args = call->_call.args; + struct ast_node *args = AST_CALL(call).args; actualize_trait_types(params, args); if (actualize(state, def->scope, def)) @@ -569,11 +533,18 @@ static int actualize_proc_call(struct act_state *state, return 0; } -static int actualize_macro_call(struct act_state *state, - struct scope *scope, struct ast_node *call, - struct ast_node *macro) +static int actualize_macro_expand(struct act_state *state, + struct scope *scope, struct ast_node *macro_expand) { - assert(call->node_type == AST_CALL && macro->node_type == AST_MACRO_CONSTRUCT); + assert(macro_expand->node_type == AST_MACRO_EXPAND); + struct ast_node *id = AST_MACRO_EXPAND(macro_expand).id; + struct ast_node *macro = file_scope_resolve_macro(scope, id); + if (!macro) { + semantic_error(scope->fctx, macro_expand, "no such macro"); + return -1; + } + + assert(macro->node_type == AST_MACRO_CONSTRUCT); if (ast_flags(macro, AST_FLAG_VARIADIC)) { semantic_error(scope->fctx, macro, "variadic macros not yet implemented"); @@ -586,11 +557,12 @@ static int actualize_macro_call(struct act_state *state, return -1; } - body->scope = call->scope; - body->next = call->next; + /** @todo update all macro IDs to the correct scope */ + body->scope = macro_expand->scope; + body->next = macro_expand->next; struct ast_node *param = AST_MACRO_CONSTRUCT(macro).params; - struct ast_node *arg = AST_CALL(call).args; + struct ast_node *arg = AST_MACRO_EXPAND(macro_expand).args; /* TODO: actual replacements */ while (param && arg) { @@ -602,7 +574,7 @@ static int actualize_macro_call(struct act_state *state, arg->next = NULL; if (replace_id(body, param, arg)) { - semantic_error(scope->fctx, call, + semantic_error(scope->fctx, macro_expand, "failed replacing params with args"); arg->next = next_arg; return -1; @@ -612,18 +584,9 @@ static int actualize_macro_call(struct act_state *state, arg = arg->next = next_arg; } - /* variadic macros could just replace this bit with with replacing the - * rest of the list with the parameter? */ - if (param || arg) { - /* TODO: internal error more like */ - semantic_error(scope->fctx, call, "uneven number of arguments"); - return -1; - } - - *call = *body; - free(body); + *macro_expand = *body; /* actualize the new content */ - return actualize(state, scope, call); + return actualize(state, scope, macro_expand); } static int actualize_call(struct act_state *state, @@ -637,12 +600,6 @@ static int actualize_call(struct act_state *state, if (ret) return ret; - /* this label implements a kind of on demand signature actualization, - * might work? */ - /* if it does, I might flesh this out into its own procedure that - * reinserts the found proc after actualization, see if maybe that way - * we get rid of duplicates? */ -get_callable: struct ast_node *callable = file_scope_resolve_call(scope, call); if (!callable) { char *str = call_str(call); @@ -651,56 +608,41 @@ get_callable: return -1; } - if (act_flags(state, ACT_ONLY_TYPES)) { - if (callable->node_type == AST_PROC - && !ast_flags(callable->_proc.sign, AST_FLAG_ACTUAL)) { - /* since we're in types only mode, this should be fine */ - if (actualize(state, callable->scope, callable)) - return -1; - - goto get_callable; - } - } - - if (callable->node_type == AST_PROC) - return actualize_proc_call(state, scope, call, callable); - - if (callable->node_type == AST_MACRO_EXPAND) - return actualize_macro_call(state, scope, call, callable); - - /* TODO: add lambdas and arrays */ - semantic_error(scope->fctx, call, - "currently only procedures and macros are callable"); - return -1; + assert(callable->node_type == AST_PROC); + return actualize_proc_call(state, scope, call, callable); } static void warn_unused_labels(struct act_state *state, struct scope *scope) { struct act_stack *labels = state->label_stack; - if (labels) - do { - struct ast_node *label = labels->node; - if (!ast_flags(label, AST_FLAG_ACTUAL)) - semantic_warn(scope->fctx, label, - "unused label"); + if (!labels) + return; + + do { + struct ast_node *label = labels->node; + if (!ast_flags(label, AST_FLAG_ACTUAL)) + semantic_warn(scope->fctx, label, + "unused label"); - } while ((labels = labels->next)); + } while ((labels = labels->next)); } static int undefined_gotos(struct act_state *state, struct scope *scope) { int ret = 0; struct act_stack *gotos = state->goto_stack; - if (gotos) - do { - struct ast_node *got = gotos->node; - if (!ast_flags(got, AST_FLAG_ACTUAL)) { - semantic_warn(scope->fctx, got, - "undefined label"); - ret = -1; - } + if (!gotos) + return ret; - } while ((gotos = gotos->next)); + do { + struct ast_node *got = gotos->node; + if (!ast_flags(got, AST_FLAG_ACTUAL)) { + semantic_warn(scope->fctx, got, + "undefined label"); + ret = -1; + } + + } while ((gotos = gotos->next)); return ret; } @@ -714,41 +656,27 @@ static int actualize_proc(struct act_state *state, * that's fine? */ assert(proc && proc->node_type == AST_PROC); int ret = 0; - /* mark the function as initialized, assume previously allocated for us */ - struct ast_node *actual = proc; - ast_set_flags(actual, AST_FLAG_INIT); - if (!act_flags(state, ACT_ONLY_TYPES)) { - ret = scope_add_actual(scope, actual); - if (ret) { - return ret; - } - } - struct ast_node *sign = actual->_proc.sign; + struct ast_node *actual = proc; + struct ast_node *sign = AST_PROC(actual).sign; struct scope *param_scope = create_scope(); scope_add_scope(scope, param_scope); /* procedure type is the signature */ sign->scope = param_scope; - actual->type = sign; - - /* TODO: if we're actualizing the types here, how can we avoid - * duplicates in the scope proc list? Or can we at all? */ /* actualize types in signature */ if (actualize(state, param_scope, sign)) return -1; - if (act_flags(state, ACT_ONLY_TYPES)) - return 0; + actual->type = sign; /* we don't have to maintain the same flags as the parent state, I don't * think */ struct act_state new_state = {0}; new_state.cur_proc = actual; - act_set_flags(&new_state, ACT_REQUIRE_FULLY_QUALIFIED); /* actualize body */ - ret |= actualize(&new_state, sign->scope, actual->_proc.body); + ret |= actualize(&new_state, sign->scope, AST_PROC(actual).body); if (!act_flags(&new_state, ACT_HAS_RETURN)) { if (!is_void(AST_SIGN_TYPE(sign).ret)) { semantic_error(scope->fctx, actual, @@ -772,8 +700,8 @@ static int actualize_proc(struct act_state *state, printf("Actualized func:\n"); dump_ast(0, actual); + scope_add_actual(scope, actual); - ast_set_flags(actual, AST_FLAG_ACTUAL); /* we have successfully actualized the procedure */ return 0; } @@ -964,18 +892,8 @@ static int actualize_type(struct act_state *state, * etc. */ assert(type->node_type == AST_TYPE); ENTER_ACT(); - act_set_flags(state, ACT_ONLY_TYPES); - if (ast_flags(type, - AST_FLAG_INIT) && !ast_flags(type, AST_FLAG_ACTUAL)) { - semantic_error(scope->fctx, type, "detected type loop"); - EXIT_ACT(-1); - } - - /* mark type initialize. We can detect type loops if this is set but - * AST_FLAG_ACTUAL isn't. */ - ast_set_flags(type, AST_FLAG_INIT); - if (actualize(state, scope, type->_type.next)) { + if (actualize(state, scope, AST_TYPE(type).next)) { EXIT_ACT(-1); } @@ -1000,11 +918,8 @@ static int actualize_type(struct act_state *state, break; } - /* actualize whatever type we have on demand, either alias or - * trait */ - if (!ast_flags(exists, AST_FLAG_ACTUAL)) - if (actualize(state, exists->scope, exists)) - EXIT_ACT(-1); + if (actualize(state, exists->scope, exists)) + EXIT_ACT(-1); assert(AST_TYPE(type).next == NULL); if (exists->node_type == AST_ALIAS) { @@ -1047,8 +962,9 @@ static int actualize_type(struct act_state *state, } case AST_TYPE_POINTER: - assert(type->_type.next); - type->loc = type->_type.next->loc; + assert(AST_PTR_TYPE(type).base); + if (actualize(state, scope, AST_PTR_TYPE(type).base)) + EXIT_ACT(-1); break; case AST_TYPE_SIGN: { @@ -1082,7 +998,6 @@ static int actualize_type(struct act_state *state, EXIT_ACT(-1); } - ast_set_flags(type, AST_FLAG_ACTUAL); /* generally speaking */ EXIT_ACT(0); } @@ -1136,13 +1051,11 @@ static int pointer_conversion(struct ast_node *a, struct ast_node *b) assert(a->node_type == AST_TYPE); assert(b->node_type == AST_TYPE); if (pointer_type(a)) { - if (b->_type.kind != AST_TYPE_ID) + if (AST_TYPE(b).kind != AST_TYPE_PRIMITIVE) return 0; - struct ast_node *id = AST_ID_TYPE(b).id; - /* there's gotta be a better way */ - if (strcmp(id->_id.id, "usize") == 0) - return 1; + /* for now */ + return AST_PRIMITIVE_TYPE(b).type == AST_I27; } return 0; @@ -1387,7 +1300,7 @@ static int actualize_cast(struct act_state *state, return 0; } - /* TODO: arrays? lambdas? */ + /* TODO: arrays? */ char *left_type = type_str(expr); char *right_type = type_str(type); @@ -1420,12 +1333,6 @@ static int actualize_alias(struct act_state *state, struct scope *scope, * aliases might be a bit cumbersome to work with. Still, this works * well enough I suppose. */ assert(alias->node_type == AST_ALIAS); - if (ast_flags(alias, AST_FLAG_INIT)) { - semantic_error(scope->fctx, alias, "alias loop"); - return -1; - } - - ast_set_flags(alias, AST_FLAG_INIT); if (actualize(state, scope, AST_ALIAS(alias).type)) { /* usually we don't want to output errors upon errors, but this * is likely a useful message as it might show where a loop is @@ -1449,7 +1356,6 @@ static int actualize_trait(struct act_state *state, struct scope *scope, /* will still have to figure out how I want to inform the actualizer * that a some_type in a some_type just means whichever type we're * testing for */ - act_set_flags(state, ACT_ONLY_TYPES); if (actualize(state, trait->scope, trait->_trait.body)) { semantic_error(scope->fctx, trait, "failed actualizing trait"); @@ -1641,14 +1547,14 @@ static int actualize_unop(struct act_state *state, switch (node->_unop.op) { case AST_DEREF: { struct ast_node *type = expr->type; - if (type->_type.kind != AST_TYPE_POINTER) { + if (AST_TYPE(type).kind != AST_TYPE_POINTER) { /* TODO: or array */ semantic_error(scope->fctx, expr, "trying to dereference something that's not a pointer"); return -1; } - node->type = type->_type.next; + node->type = AST_PTR_TYPE(type).base; assert(node->type); break; } @@ -1671,13 +1577,7 @@ static int actualize_as(struct act_state *state, struct scope *scope, struct ast_node *as) { assert(as->node_type == AST_AS); - if (!act_flags(state, ACT_ONLY_TYPES)) { - semantic_error(scope->fctx, as, - "as used outside of type context"); - return -1; - } - - struct ast_node *type = as->_as.type; + struct ast_node *type = AST_AS(as).type; if (actualize(state, scope, type)) return -1; @@ -1689,7 +1589,6 @@ static int actualize_struct(struct act_state *state, struct scope *scope, struct ast_node *node) { assert(node->node_type == AST_STRUCT); - ast_set_flags(node, AST_FLAG_INIT); struct ast_node *generics = node->_struct.generics; struct scope *struct_scope = create_scope(); if (!struct_scope) @@ -1749,7 +1648,7 @@ static int actualize_dot(struct act_state *state, if (actualize(state, scope, expr)) return -1; - struct ast_node *id = node->_dot.id; + struct ast_node *id = AST_DOT(node).id; if (!has_members(expr->type)) { char *tstr = type_str(expr->type); semantic_error(scope->fctx, node, "%s does not have member %s", @@ -1759,7 +1658,6 @@ static int actualize_dot(struct act_state *state, } /* TODO: actually look through stuff */ - /* TODO: figure out how to match traitd structures to actual */ return 0; } @@ -1771,7 +1669,6 @@ static int actualize_init(struct act_state *state, /* TODO: how to make sure all members are initialized? */ int ret = 0; enum act_flags old_flags = state->flags; - act_set_flags(state, ACT_ONLY_TYPES); ret = actualize(state, scope, node->_init.body); state->flags = old_flags; return ret; @@ -1804,6 +1701,12 @@ static int actualize_assign(struct act_state *state, struct scope *scope, return -1; } + /** @todo rvalue vs lvalue? */ + if (!is_lvalue(to)) { + semantic_error(scope->fctx, node, "rvalue used where lvalue required"); + return -1; + } + node->type = to->type; return 0; } @@ -1898,6 +1801,14 @@ static int actualize(struct act_state *state, struct scope *scope, if (!node->scope) node->scope = scope; + if (ast_flags(node, AST_FLAG_INIT) && !ast_flags(node, AST_FLAG_ACTUAL)) { + semantic_error(scope->fctx, node, "detected dependency loop"); + return -1; + } + + /* actualization started */ + ast_set_flags(node, AST_FLAG_INIT); + switch (node->node_type) { case AST_PROC: /* procedure definitions only allowed at file scope */ @@ -1908,6 +1819,7 @@ static int actualize(struct act_state *state, struct scope *scope, case AST_TRAIT: ret |= actualize_trait(state, scope, node); break; case AST_ALIAS: ret |= actualize_alias(state, scope, node); break; case AST_MACRO_CONSTRUCT: ret |= actualize_macro_construct(state, scope, node); break; + case AST_MACRO_EXPAND: ret |= actualize_macro_expand(state, scope, node); break; case AST_CALL: ret |= actualize_call(state, scope, node); break; case AST_BINOP: ret |= actualize_binop(state, scope, node); break; case AST_BLOCK: ret |= actualize_block(state, scope, node); break; @@ -1937,6 +1849,9 @@ static int actualize(struct act_state *state, struct scope *scope, break; } + /* actualization done for this node */ + ast_set_flags(node, AST_FLAG_ACTUAL); + /* should it automatically handle next nodes or should it be saved to * actualize_block or something? */ ret |= actualize(state, scope, node->next); @@ -1957,7 +1872,7 @@ int actualize_main(struct scope *root) error("no main"); return -1; } - int ret = actualize(&state, root, clone_ast_node(main)); + int ret = actualize(&state, root, main); destroy_act_state(&state); return ret; } @@ -1972,12 +1887,10 @@ void replace_type(struct ast_node *type, struct ast_node *from, assert(from->node_type == AST_TYPE); assert(to->node_type == AST_TYPE); - /* TODO: cleanup? */ if (types_match(type, from)) { assert(type->_type.next == NULL); struct ast_node *clone = clone_ast_node(to); *type = *clone; - free(clone); return; } @@ -1997,16 +1910,3 @@ void replace_param_types(struct ast_node *param, struct ast_node *param_type, param = param->next; } } - -int actualize_temp_type(struct scope *scope, struct ast_node *type) -{ - struct act_state state; - act_set_flags(&state, ACT_ONLY_TYPES); - struct ast_node *next = type->next; - - type->next = NULL; - int ret = actualize(&state, scope, type); - type->next = next; - - return ret; -} @@ -453,14 +453,14 @@ struct ast_node *gen_var(struct ast_node *id, struct ast_node *type, } struct ast_node *gen_proc(struct ast_node *id, struct ast_node *sign, - struct ast_node *body) + struct ast_node *body, struct src_loc loc) { ALLOC_NODE(n, "proc"); n->node_type = AST_PROC; - n->_proc.id = id; - n->_proc.sign = sign; - n->_proc.body = body; - n->loc = id->loc; + AST_PROC(n).id = id; + AST_PROC(n).sign = sign; + AST_PROC(n).body = body; + n->loc = loc; return n; } @@ -1215,7 +1215,8 @@ struct ast_node *clone_ast_node(struct ast_node *node) case AST_PROC: new = gen_proc(clone_ast_node(node->_proc.id), clone_ast_node(node->_proc.sign), - clone_ast_node(node->_proc.body)); + clone_ast_node(node->_proc.body), + node->loc); break; case AST_VAR: { @@ -1307,9 +1308,10 @@ struct ast_node *clone_ast_node(struct ast_node *node) break; case AST_TYPE_SIGN: - new = gen_type(AST_TYPE_SIGN, NULL, + new = gen_type(AST_TYPE_SIGN, clone_ast_node(AST_SIGN_TYPE(node).params), - clone_ast_node(AST_SIGN_TYPE(node).ret)); + clone_ast_node(AST_SIGN_TYPE(node).ret), + NULL); break; } diff --git a/src/compiler.c b/src/compiler.c index 5ad67c3..8801c7f 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -22,6 +22,7 @@ #include <ek/debug.h> #include <ek/scope.h> #include <ek/path.h> +#include <ek/ops.h> #include <ek/res.h> /** @@ -163,18 +164,40 @@ out: return res; } -int compile(const char *file) { +int compile(const char *input, const char *output) { int ret = -1; struct scope *root = NULL; - if (process_file(&root, 0, file)) { + if (process_file(&root, 0, input)) { destroy_scope(root); - error("compilation of %s stopped due to errors", file); + error("compilation of %s stopped due to errors", input); return ret; } ret = actualize_main(root); - /** @todo backend */ + if (ret) { + destroy_scope(root); + destroy_ast_nodes(); + error("compilation of %s stopped due to errors", input); + return ret; + } + + struct ops *ops = create_ops(); + ret = lower_ops(root, ops); destroy_scope(root); destroy_ast_nodes(); - return ret; + + if (ret) { + destroy_ops(ops); + error("compilation of %s stopped due to errors", input); + return ret; + } + + ret = analyze_lifetime(ops); + if (ret) { + destroy_ops(ops); + error("compilation of %s stopped due to errors", input); + return ret; + } + + return print_asm(ops, output); } diff --git a/src/debug.c b/src/debug.c index e6e2b60..b110b9b 100644 --- a/src/debug.c +++ b/src/debug.c @@ -192,9 +192,10 @@ static void _type_str(FILE *fp, struct ast_node *type) assert(type->node_type == AST_TYPE); - switch (type->_type.kind) { + switch (AST_TYPE(type).kind) { case AST_TYPE_POINTER: - fputc('\'', fp); + fputc('*', fp); + _type_str(fp, AST_PTR_TYPE(type).base); break; case AST_TYPE_ID: { @@ -238,7 +239,7 @@ static void _type_str(FILE *fp, struct ast_node *type) fprintf(fp, "NOT YET IMPLEMENTED"); } - _type_str(fp, type->_type.next); + _type_str(fp, AST_TYPE(type).next); } char *type_str(struct ast_node *node) @@ -23,12 +23,12 @@ */ static const char *cmdline_usage = "ek compiler usage:\n" - " ek [-I <dir>...] [-D <var>...] infile...\n" - " -h Show usage (this)\n" - " -I <dir> Add directory to import path\n" - " -D <var> Add predefined variable\n" - " -o Name of output\n" - " infile Top file(s) to compile\n" + " ek [-I <dir>...] [-o <outfile>] infile\n" + " -h Show usage (this)\n" + " -I <dir> Add directory to import path\n" + " infile Top file(s) to compile\n" + " -o <outfile> Name of output assembly file\n" + " (infile minus file extension if not given)\n" ; /** Print usage of compiler. */ @@ -50,24 +50,21 @@ static void usage() int main(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hI:D:o:")) != -1) { + const char *output = "e.out"; + while ((opt = getopt(argc, argv, "hI:o:")) != -1) { switch (opt) { case 'o': - error("not yet implemented"); + output = optarg; break; case 'I': add_import_path(optarg); break; - case 'D': - /* TODO */ - error("not yet implemented"); - break; - case 'h': usage(); exit(EXIT_SUCCESS); + default: usage(); exit(EXIT_FAILURE); @@ -75,16 +72,17 @@ int main(int argc, char *argv[]) } if (optind >= argc) { - error("no input files"); + error("no input file"); usage(); exit(EXIT_FAILURE); } - for (int i = optind; i < argc; ++i) { - debug("starting compilation of '%s'", argv[i]); - if (compile(argv[i])) - return -1; + if (optind != argc - 1) { + error("too many arguments"); + usage(); + exit(EXIT_FAILURE); } - return 0; + const char *input = argv[optind]; + return compile(input, output); } diff --git a/src/ops.c b/src/ops.c new file mode 100644 index 0000000..d237ae0 --- /dev/null +++ b/src/ops.c @@ -0,0 +1,395 @@ +#include <ek/ops.h> +#include <ek/scope.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +/* hopefully not too difficult to follow what's goind on, but to start with we + * assume we have an effectively infinite amount of virtual registers and we + * move everything down into them. Typically the top output is used as input in + * some other step. */ + +enum loc_kind { + LOC_NONE, LOC_REG, LOC_MEM +}; + +struct loc { + enum loc_kind kind; + struct loc *next; + size_t start; + size_t end; + size_t reg; + long long off; + size_t width; +}; + +static void set_reg(struct loc *loc, size_t reg) +{ + loc->kind = LOC_REG; + loc->reg = reg; +} + +static void set_mem(struct loc *loc, size_t reg, long long off, size_t width) +{ + loc->kind = LOC_MEM; + loc->reg = reg; + loc->off = off; + loc->width = width; +} + +static size_t trivial_type_width(struct ast_node *type) +{ + switch (AST_TYPE(type).kind) { + case AST_TYPE_POINTER: return 3; + case AST_TYPE_PRIMITIVE: { + if (AST_PRIMITIVE_TYPE(type).type == AST_I27) + return 3; + + if (AST_PRIMITIVE_TYPE(type).type == AST_I9) + return 1; + + abort(); + break; + } + default: abort(); + } + return 3; +} + +enum opcode { + /* small subset for now */ + OP_LI, + OP_LA, + OP_ADD, + OP_ADDI, + OP_STT, + OP_LDT, + OP_STW, + OP_LDW, + OP_MV, /* meta op, will be realized as either load/store or register move */ + OP_LABEL, + OP_COMMENT, +}; + +struct op { + enum opcode opcode; + struct loc inputs; + struct loc outputs; + size_t loc; + union { + long long constant; + const char *string; + }; + struct op *next; +}; + +struct ops { + struct op *base; + struct op *head; +}; + +struct ops *create_ops() +{ + struct ops *ops = calloc(1, sizeof(struct ops)); + + struct op *op = calloc(1, sizeof(struct op)); + op->opcode = OP_COMMENT; + op->string = strdup("start"); + + ops->base = op; + ops->head = op; + return ops; +} + +void destroy_ops(struct ops *ops) +{ + if (!ops) + return; + + struct op *base = ops->base; + while (base) { + struct op *prev = base; + base = base->next; + free(prev); + } + + free(ops); +} + +static size_t next_virtual_reg() +{ + static size_t reg = 1; + return reg++; +} + +#define HEAD_OUTPUTS(ops) ops->head->outputs + +static int lower_op(struct ast_node *n, struct ops *ops); +static struct op *op_head(struct ops *ops) +{ + return ops->head; +} + +static struct op *append_op(struct ops *ops, enum opcode opcode) +{ + static size_t i = 1; + struct op *op = op_head(ops); + struct op *n = calloc(1, sizeof(struct op)); + n->opcode = opcode; + op->next = n; + op->loc = i++; + ops->head = n; + return n; +} + +static int lower_proc(struct ast_node *n, struct ops *ops) +{ + struct op *op = append_op(ops, OP_LABEL); + /** @todo name mangling */ + struct ast_node *id = AST_PROC(n).id; + op->string = strdup(AST_ID(id).id); + return lower_op(AST_PROC(n).body, ops); +} + +static int lower_block(struct ast_node *n, struct ops *ops) +{ + struct ast_node *b = AST_BLOCK(n).body; + while (b) { + int ret = lower_op(b, ops); + if (ret) + return ret; + + b = b->next; + } + + return 0; +} + +static int lower_var(struct ast_node *n, struct ops *ops) +{ + struct ast_node *d = scope_find_var(n->scope, AST_VAR(n).id); + /* structs should be handled as well, would it be better to try and fit + * them into regs or just dump them on the stack to make sure we don't + * immediately run out of registers? */ + struct ast_node *type = d->type; + if (AST_TYPE(type).kind != AST_TYPE_PRIMITIVE + && AST_TYPE(type).kind != AST_TYPE_POINTER) { + semantic_error(n->scope->fctx, n, + "only trivial type lowering implemented"); + return -1; + } + + d->reg = next_virtual_reg(); + + if (AST_VAR(n).init) { + int ret = lower_op(AST_VAR(n).init, ops); + if (ret) + return ret; + + struct loc inputs = HEAD_OUTPUTS(ops); + /* work with only primitive types for now */ + assert(inputs.next == NULL); + + struct op *op = append_op(ops, OP_MV); + op->inputs = inputs; + set_reg(&op->outputs, d->reg); + } + + return 0; +} + +static int lower_cast(struct ast_node *n, struct ops *ops) +{ + /** @todo lob off extra trits or something? */ + return lower_op(AST_CAST(n).expr, ops); +} + +static int lower_const(struct ast_node *n, struct ops *ops) +{ + if (AST_CONST(n).kind != AST_CONST_INTEGER) { + semantic_error(n->scope->fctx, n, "only integer constant lowering implemented"); + return -1; + } + + struct op *op = append_op(ops, OP_LI); + op->constant = AST_CONST(n).integer; + set_reg(&op->outputs, next_virtual_reg()); + return 0; +} + +static int lower_assign(struct ast_node *n, struct ops *ops) +{ + int ret = lower_op(AST_ASSIGN(n).from, ops); + if (ret) + return ret; + + struct loc from = HEAD_OUTPUTS(ops); + + ret = lower_op(AST_ASSIGN(n).to, ops); + if (ret) + return ret; + + struct loc to = HEAD_OUTPUTS(ops); + struct op *op = append_op(ops, OP_MV); + op->inputs = from; + op->outputs = to; + return 0; +} + +static int lower_unop(struct ast_node *n, struct ops *ops) +{ + if (AST_UNOP(n).op != AST_DEREF) { + semantic_error(n->scope->fctx, n, "unop lowering not implemented"); + return -1; + } + + if (AST_UNOP(n).op == AST_DEREF) { + int ret = lower_op(AST_UNOP(n).expr, ops); + if (ret) + return ret; + + struct loc *l = &HEAD_OUTPUTS(ops); + if (l->kind == LOC_MEM) { + /* load value from memory and use it as the next step + * location */ + struct op *op = append_op(ops, OP_LDW); + op->inputs = *l; + /* hard coded 3 for now, pointer is three trytes */ + set_mem(&op->outputs, next_virtual_reg(), 0, 3); + return 0; + } + + /* use the value as if it was a memory location */ + size_t w = trivial_type_width(n->type); + set_mem(l, l->reg, 0, w); + return 0; + } + + return -1; +} + +static int lower_id(struct ast_node *n, struct ops *ops) +{ + struct ast_node *d = scope_find_var(n->scope, n); + assert(d->reg); + + /* feels like a massive hack, but gives fairly readable debug output so + * I'll keep it for now. Eventually once I figure out what I'm doing + * this whole system will probably have to be rewritten anyway. */ + struct op *op = append_op(ops, OP_COMMENT); + op->string = strdup(AST_ID(n).id); + set_reg(&op->outputs, d->reg); + return 0; +} + +static int lower_op(struct ast_node *n, struct ops *ops) +{ + int ret = 0; + switch (n->node_type) { + case AST_PROC: ret = lower_proc(n, ops); break; + case AST_BLOCK: ret = lower_block(n, ops); break; + case AST_VAR: ret = lower_var(n, ops); break; + case AST_CAST: ret = lower_cast(n, ops); break; + case AST_CONST: ret = lower_const(n, ops); break; + case AST_ASSIGN: ret = lower_assign(n, ops); break; + case AST_UNOP: ret = lower_unop(n, ops); break; + case AST_ID: ret = lower_id(n, ops); break; + default: + semantic_error(n->scope->fctx, n, "unimplemented lowering"); + return -1; + } + + if (ret) + return ret; + + return 0; +} + +static void print_locs(struct loc *locs) +{ + if (locs->kind == LOC_NONE) + return; + + printf(" ( "); + + while (locs) { + if (locs->kind == LOC_MEM) + printf("%lld(", locs->off); + + printf("r%zd", locs->reg); + + if (locs->kind == LOC_MEM) + printf(", %zd)", locs->width); + + printf(" "); + locs = locs->next; + } + + printf(")"); +} + +static void print_op(struct op *op) +{ + if (!op) + return; + + print_locs(&op->outputs); + putchar(' '); + switch (op->opcode) { + case OP_LABEL: printf("%s:", op->string); break; + case OP_COMMENT: printf("/* %s */", op->string); break; + case OP_LI: printf("li %lld", op->constant); break; + case OP_LA: printf("la %s", op->string); break; + case OP_ADD: printf("add"); break; + case OP_ADDI: printf("addi %lld", op->constant); break; + case OP_STT: printf("stt"); break; + case OP_LDT: printf("ldt"); break; + case OP_STW: printf("stw"); break; + case OP_LDW: printf("ldw"); break; + case OP_MV: printf("mv"); break; + default: printf("unimp"); break; + } + putchar(' '); + print_locs(&op->inputs); + printf("\n"); +} + +static void print_ops(struct ops *ops) +{ + if (!ops) + return; + + struct op *base = ops->base; + while (base) { + print_op(base); + base = base->next; + } +} + +int lower_ops(struct scope *root, struct ops *ops) +{ + for (struct actual *a = root->actuals; a; a = a->next) { + assert(a->node); + int ret = lower_op(a->node, ops); + if (ret) + return ret; + } + + printf("Lowered ops before lifetime analysis:\n"); + print_ops(ops); + + return 0; +} + +int analyze_lifetime(struct ops *ops) +{ + return 0; +} + +/* this is probably going to balloon up, should probably move this into another + * file */ +int print_asm(struct ops *ops, const char *output) +{ + return 0; +} diff --git a/src/parser.y b/src/parser.y index 433a5ab..4def5fd 100644 --- a/src/parser.y +++ b/src/parser.y @@ -446,10 +446,11 @@ statement statements : statement statements { $$ = $1; $1->next = $2; } | statement + | statelet + body : "{" statements "}" { $$ = gen_block($2); } - | "{" statelet "}" { $$ = gen_block($2); } | "{" "}" { $$ = gen_block(gen_empty()); } references @@ -549,11 +550,11 @@ const func_sign : "(" decls "=>" type ")" { - $$ = gen_type(AST_TYPE_SIGN, NULL, $2, $4); + $$ = gen_type(AST_TYPE_SIGN, $2, $4, NULL); } - | "(" decls ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, $2, NULL); } - | "(" decls "=>" ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, $2, NULL); } - | "(" "=>" type ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, NULL, $3); } + | "(" decls ")" { $$ = gen_type(AST_TYPE_SIGN, $2, NULL, NULL); } + | "(" decls "=>" ")" { $$ = gen_type(AST_TYPE_SIGN, $2, NULL, NULL); } + | "(" "=>" type ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, $3, NULL); } | "(" "=>" ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, NULL, NULL); } | "(" ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, NULL, NULL); } @@ -571,7 +572,7 @@ type $$ = gen_type(AST_TYPE_ARR, $2, $4, NULL); } | "typeof" expr { - $$ = gen_type(AST_TYPE_TYPEOF, NULL, $2, NULL); + $$ = gen_type(AST_TYPE_TYPEOF, $2, NULL, NULL); } | "const" type { $$ = $2; @@ -608,13 +609,13 @@ var_init proc : id func_sign body { - $$ = gen_proc($1, $2, $3); + $$ = gen_proc($1, $2, $3, src_loc(@$)); ast_set_flags($$, $2->flags); ast_set_flags($3, AST_FLAG_UNHYGIENIC); } | "extern" id func_sign { /* todo check that we don't have a variadic function */ - $$ = gen_proc($2, $3, NULL); + $$ = gen_proc($2, $3, NULL, src_loc(@$)); ast_set_flags($$, AST_FLAG_EXTERN); } @@ -650,7 +651,7 @@ anon_struct trait_elem : id /* trait */ - | id func_sign { $$ = gen_proc($1, $2, NULL); } /* proc */ + | id func_sign { $$ = gen_proc($1, $2, NULL, src_loc(@$)); } /* proc */ | var_decl /* member */ | type_expand /* type construction */ diff --git a/src/scope.c b/src/scope.c index 369a89b..6c94aec 100644 --- a/src/scope.c +++ b/src/scope.c @@ -40,14 +40,9 @@ static struct param_node *find_matching_param(struct resolve_node *node, static int traits_resolve(struct ast_node *arg_type, struct ast_node *param_type) { - /** @todo are more checks required? */ - return AST_TYPE(arg_type).as == AST_TRAIT_TYPE(param_type).def; -} - -static int typeofs_resolve(struct ast_node *arg_type, struct ast_node *param_type) -{ - internal_error("typeof resolve unimplemented"); - return 0; + /** @todo are more checks required? arg_type should already be in + * `as`-form*/ + return AST_TRAIT_TYPE(arg_type).def == AST_TRAIT_TYPE(param_type).def; } static int types_resolve(struct ast_node *arg_type, struct ast_node *param_type) @@ -56,11 +51,16 @@ static int types_resolve(struct ast_node *arg_type, struct ast_node *param_type) if (!param_type) return 1; + /* if arg is specifying to be matches as `as`, then do it */ + if (AST_TYPE(arg_type).as) + return types_resolve(AST_TYPE(arg_type).as, param_type); + if (AST_TYPE(param_type).kind == AST_TYPE_TRAIT) return traits_resolve(arg_type, param_type); + /* typeof is matched later */ if (AST_TYPE(param_type).kind == AST_TYPE_TYPEOF) - return typeofs_resolve(arg_type, param_type); + return 1; return types_match(arg_type, param_type); } @@ -110,8 +110,6 @@ static int add_next_resolve(struct scope *scope, struct ast_node *resolve, struct resolve_node *node, struct ast_node *params) { assert(node); - if (params && actualize_temp_type(scope, params)) - return -1; /* TODO: variadics in macros? */ /* we've run out of params, check if this is a suitable node */ @@ -855,7 +853,13 @@ void scope_add_scope(struct scope *parent, struct scope *child) static int add_actual(struct actual *actuals, struct ast_node *node) { - /* TODO: check that there isn't already an actual like ours */ + if (!actuals->node) { + /* fill empty first element */ + actuals->node = node; + return 0; + } + + /* TODO: check that there isn't already an actual like ours? */ struct actual *actual = calloc(1, sizeof(struct actual)); if (!actual) return -1; @@ -876,13 +880,15 @@ static struct ast_node *find_actual(struct actual *actuals, { assert(node->node_type == AST_ID); - if (actuals) - do { - struct ast_node *actual = actuals->node; - if (identical_ast_nodes(0, actual->_proc.id, node)) - return actual; + if (!actuals) + return NULL; + + do { + struct ast_node *actual = actuals->node; + if (identical_ast_nodes(0, actual->_proc.id, node)) + return actual; - } while ((actuals = actuals->next)); + } while ((actuals = actuals->next)); return NULL; } |
