#include #include #include #include "ir.h" #include "typ.h" #include "strio.h" /* node lists */ void nodelist_fit(NodeList *l, u32 sz, Arena *a) { if (l->cap) { if (sz > l->cap) { u32 cap = stdc_bit_ceil(sz); l->data = resize(a, l->data, l->cap, cap); l->cap = cap; } } else if (sz > 1) { u32 cap = stdc_bit_ceil(sz); Node **data = new_arr(a, Node *, cap); data[0] = l->sbo; l->data = data; l->cap = cap; } } void nodelist_push(NodeList *l, Node *n, Arena *a) { nodelist_fit(l, l->len + 1, a); Node **p = nodelist_nth(l, l->len++); *p = n; } void nodelist_remove(NodeList *l, Node *n) { Node **data = nodelist_data(l); for (unsigned i = 0; i < l->len; i++) { if (data[i] == n) { l->len--; if (i < l->len) { memmove(&data[i], &data[i+1], (l->len - i) * sizeof(Node *)); } break; } } } void nodelist_remove_unordered(NodeList *l, Node *p) { Node **data = nodelist_data(l); unsigned i = l->len; while (i) { i--; if (data[i] == p) { l->len--; if (i < l->len) { data[i] = data[l->len]; } break; } } } /* nodes */ const char *node_type_name(NodeType t) { static const char *names[] = { #define X(n, s) s, NODE_TYPE_LIST #undef X }; return names[t]; } void node_die(Node *n, Graph *p) { assert(n->op != N_DEAD); n->op = N_DEAD; n->prev_free = p->pool->free_list; p->pool->free_list = n; } void node_del_out(Node *n, Node *p) { nodelist_remove_unordered(&n->out, p); } void node_del_in(Node *n, Node *p) { nodelist_remove(&n->in, p); } static inline int node_should_die(Node *n) { return n->in.len == 0 && n->out.len == 0; } void node_kill(Node *n, Graph *p) { if (p->ctrl == n) { /* probably this is fine */ p->ctrl = CTRL(n); } for (u32 i = 0; i < n->in.len; i++) { Node *o = IN(n, i); if (o) { node_del_out(o, n); if (node_should_die(o)) node_die(o, p); } } for (u32 i = 0; i < n->out.len; i++) { Node *o = OUT(n, i); node_del_in(o, n); if (node_should_die(o)) node_die(o, p); } node_die(n, p); } void node_add_out(Graph *p, Node *a, Node *b) { nodelist_push(&a->out, b, p->pool->arena); } void node_add_in(Graph *p, Node *a, Node *b) { nodelist_push(&a->in, b, p->pool->arena); } void node_set_in(Graph *p, Node *n, int idx, Node *to) { Node *in = IN(n, idx); node_add_out(p, to, n); node_del_out(in, n); IN(n, idx) = to; if (in->out.len < 1) node_kill(in, p); } void node_add(Graph *p, Node *src, Node *dest) { assert(dest->op != N_DEAD); node_add_in(p, dest, src); if (!src) return; assert(src->op != N_DEAD); node_add_out(p, src, dest); if (dest->src_pos.n == 0) dest->src_pos = src->src_pos; else if (src->src_pos.n != 0) { int lo = dest->src_pos.ofs < src->src_pos.ofs ? dest->src_pos.ofs : src->src_pos.ofs; int hi = dest->src_pos.ofs + dest->src_pos.n > src->src_pos.ofs + src->src_pos.n ? dest->src_pos.ofs + dest->src_pos.n : src->src_pos.ofs + src->src_pos.n; dest->src_pos = (LexSpan) { lo, hi - lo }; } } void node_remove(Graph *p, Node *src, Node *dest) { assert(dest->op != N_DEAD); node_del_in(dest, src); if (node_should_die(dest)) node_die(dest, p); if (src) { assert(src->op != N_DEAD); node_del_out(src, dest); if (src->out.len < 1) node_kill(src, p); } } static int global_node_count = 0; Node *node_new_empty(Graph *p, NodeType t) { Node *n; if (p->pool->free_list) { n = p->pool->free_list; assert(n->op == N_DEAD); p->pool->free_list = n->prev_free; n->in.len = 0; n->out.len = 0; n->walked = 0; n->src_pos = (LexSpan) { 0 }; n->val = (Value) { 0 }; } else { n = new(p->pool->arena, Node); } n->op = t; n->id = global_node_count++; return n; } Node *node_newv(Graph *p, NodeType t, Node *ctrl, ...) { Node *node = node_new_empty(p, t); va_list ap; /* do inputs all at once to decrease chance of arena_realloc() fragmenting */ va_start(ap, ctrl); node_add_in(p, node, ctrl); for (;;) { Node *n = va_arg(ap, Node *); if (!n) break; node_add_in(p, node, n); } va_end(ap); va_start(ap, ctrl); if (ctrl) node_add_out(p, ctrl, node); for (;;) { Node *n = va_arg(ap, Node *); if (!n) break; node_add_out(p, n, node); } va_end(ap); return node; } Node *node_dedup_lit(Graph *p, Value v) { /* TODO: this is probably real inefficient for large procedure graphs, * but does it matter? how many nodes are direct children of the start node? * how many literals even usually occur in a procedure? */ for (u32 i = 0; i < p->start->out.len; i++) { Node *t = OUT(p->start, i); if (t->op == N_LIT && type_eql(&t->type, &v.type) && t->val.i == v.i) { return t; } } return NULL; } Node *node_new_lit(Graph *p, Value v) { Node *t = node_dedup_lit(p, v); if (t) return t; Node *n = node_new(p, N_LIT, NULL, p->start); n->val = v; return n; } Node *node_new_lit_i64(Graph *p, int64_t i) { return node_new_lit(p, (Value) { { .lvl = T_CONST, .t = T_INT }, { .i = i } }); } Node *node_new_lit_bool(Graph *p, int b) { return node_new_lit(p, (Value) { { .lvl = T_CONST, .t = T_BOOL }, { .i = b } }); } /* types */ int type_eql(Type *a, Type *b) { if (a->t != b->t) return 0; if (a->lvl != b->lvl) return 0; if (a->next != b->next) return 0; return a->next ? type_eql(a->next, b->next) : 1; } int type_base_eql(Type *a, Type *b) { if (a->t != b->t) return 0; if (a->next != b->next) return 0; return a->next ? type_base_eql(a->next, b->next) : 1; } int value_eql(Value *a, Value *b) { if (!type_eql(&a->type, &b->type)) return 0; return a->i == b->i; } Str type_desc(Type *t, Arena *arena) { (void)arena; switch (t->lvl) { case T_CTRL: return S("ctrl"); case T_XCTRL: return S("~ctrl"); default: break; } switch (t->t) { case T_TUPLE: return S("tuple"); case T_BOOL: return S("bool"); case T_INT: return S("i64"); case T_PTR: return str_fmt(arena, "^%S", type_desc(t->next, arena)); default: return S("N/A"); } } void type_err(Node *n, Lexer *l) { Str s = S(""); for (u32 i = 0; i < n->in.len; i++) { if (i > 0) str_cat(&s, S(", "), &l->arena); str_cat(&s, type_desc(&IN(n, i)->type, &l->arena), &l->arena); } lex_error_at(l, n->src_pos, LE_ERROR, str_fmt(&l->arena, "type error %s (%S)", node_type_name(n->op), s)); } void type_expected(Type *want, Node *n, Lexer *l) { if (type_base_eql(want, &n->type)) return; lex_error_at(l, n->src_pos, LE_ERROR, str_fmt(&l->arena, "type error: expected %S, but got %S", type_desc(want, &l->arena), type_desc(&n->type, &l->arena))); } static int type_ok(Node *n) { switch (n->op) { case N_PHI: n->type = (Type) { .lvl = T_TOP, .t = IN(n, 1)->type.t }; for (u32 i = 2; i < n->in.len; i++) { if (!type_base_eql(&IN(n, i)->type, &n->type)) { return 0; } } return 1; case N_OP_NEG: n->type = (Type) { .lvl = T_TOP, .t = T_INT }; return CAR(n)->type.t == T_INT; case N_OP_NOT: n->type = (Type) { .lvl = T_TOP, .t = CAR(n)->type.t }; return CAR(n)->type.t == T_INT || CAR(n)->type.t == T_BOOL; case N_OP_AND: case N_OP_OR: case N_OP_XOR: n->type = (Type) { .lvl = T_TOP, .t = CAR(n)->type.t }; return (CAR(n)->type.t == T_INT && CDR(n)->type.t == T_INT) || (CAR(n)->type.t == T_BOOL && CDR(n)->type.t == T_BOOL); case N_OP_ADD: case N_OP_SUB: case N_OP_MUL: case N_OP_DIV: case N_OP_SHL: case N_OP_SHR: n->type = (Type) { .lvl = T_TOP, .t = T_INT }; return CAR(n)->type.t == T_INT && CDR(n)->type.t == T_INT; case N_CMP_LES: case N_CMP_GTR: case N_CMP_LTE: case N_CMP_GTE: n->type = (Type) { .lvl = T_TOP, .t = T_BOOL }; return CAR(n)->type.t == T_INT && CDR(n)->type.t == T_INT; case N_CMP_EQL: case N_CMP_NEQ: n->type = (Type) { .lvl = T_TOP, .t = T_BOOL }; return type_base_eql(&CAR(n)->type, &CDR(n)->type); /* (CAR(n)->type.t == T_INT && CDR(n)->type.t == T_INT) || (CAR(n)->type.t == T_BOOL && CDR(n)->type.t == T_BOOL); */ default: return 1; } } void type_check(Node *n, Lexer *l) { if (!type_ok(n)) type_err(n, l); } int node_uninit(Node *n) { return n->op == N_UNINIT; } int node_maybe_uninit(Node *n) { if (node_uninit(n)) return 1; for (u32 i = 0; i < n->in.len; i++) { if (IN(n,i) && node_maybe_uninit(IN(n,i))) { return 1; } } return 0; }