]> git.xonotic.org Git - xonotic/gmqcc.git/blobdiff - ir.c
type_store_instr[] to get an INSTR_STORE_x by type
[xonotic/gmqcc.git] / ir.c
diff --git a/ir.c b/ir.c
index a9f0c9e1217b3231a54dc560f39f379467b9e031..48c4ccf032da0602350408537fc0dab8cd7bc4dc 100644 (file)
--- a/ir.c
+++ b/ir.c
 #include "gmqcc.h"
 #include "ir.h"
 
+/***********************************************************************
+ * Type sizes used at multiple points in the IR codegen
+ */
+
+size_t type_sizeof[TYPE_COUNT] = {
+    1, /* TYPE_VOID     */
+    1, /* TYPE_STRING   */
+    1, /* TYPE_FLOAT    */
+    3, /* TYPE_VECTOR   */
+    1, /* TYPE_ENTITY   */
+    1, /* TYPE_FIELD    */
+    1, /* TYPE_FUNCTION */
+    1, /* TYPE_POINTER  */
+#if 0
+    1, /* TYPE_INTEGER  */
+#endif
+    3, /* TYPE_VARIANT  */
+};
+
+uint16_t type_store_instr[TYPE_COUNT] = {
+    INSTR_STORE_F, /* should use I when having integer support */
+    INSTR_STORE_S,
+    INSTR_STORE_F,
+    INSTR_STORE_V,
+    INSTR_STORE_ENT,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FNC,
+    INSTR_STORE_ENT, /* should use I */
+#if 0
+    INSTR_STORE_ENT, /* integer type */
+#endif
+    INSTR_STORE_V, /* variant, should never be accessed */
+};
+
+MEM_VEC_FUNCTIONS(ir_value_vector, ir_value*, v)
+
 /***********************************************************************
  *IR Builder
  */
@@ -89,20 +125,32 @@ ir_function* ir_builder_get_function(ir_builder *self, const char *name)
     return NULL;
 }
 
-ir_function* ir_builder_create_function(ir_builder *self, const char *name)
+ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype)
 {
     ir_function *fn = ir_builder_get_function(self, name);
     if (fn) {
         return NULL;
     }
 
-    fn = ir_function_new(self);
+    fn = ir_function_new(self, outtype);
     if (!ir_function_set_name(fn, name) ||
         !ir_builder_functions_add(self, fn) )
     {
         ir_function_delete(fn);
         return NULL;
     }
+
+    fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION);
+    if (!fn->value) {
+        ir_function_delete(fn);
+        return NULL;
+    }
+
+    fn->value->isconst = true;
+    fn->value->outtype = outtype;
+    fn->value->constval.vfunc = fn;
+    fn->value->context = fn->context;
+
     return fn;
 }
 
@@ -138,8 +186,9 @@ ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype
 bool ir_function_naive_phi(ir_function*);
 void ir_function_enumerate(ir_function*);
 bool ir_function_calculate_liferanges(ir_function*);
+bool ir_function_allocate_locals(ir_function*);
 
-ir_function* ir_function_new(ir_builder* owner)
+ir_function* ir_function_new(ir_builder* owner, int outtype)
 {
     ir_function *self;
     self = (ir_function*)mem_a(sizeof(*self));
@@ -155,7 +204,8 @@ ir_function* ir_function_new(ir_builder* owner)
     self->owner = owner;
     self->context.file = "<@no context>";
     self->context.line = 0;
-    self->retype = TYPE_VOID;
+    self->outtype = outtype;
+    self->value = NULL;
     MEM_VECTOR_INIT(self, params);
     MEM_VECTOR_INIT(self, blocks);
     MEM_VECTOR_INIT(self, values);
@@ -195,6 +245,8 @@ void ir_function_delete(ir_function *self)
         ir_value_delete(self->locals[i]);
     MEM_VECTOR_CLEAR(self, locals);
 
+    /* self->value is deleted by the builder */
+
     mem_d(self);
 }
 
@@ -223,6 +275,9 @@ bool ir_function_finalize(ir_function *self)
 
     if (!ir_function_calculate_liferanges(self))
         return false;
+
+    if (!ir_function_allocate_locals(self))
+        return false;
     return true;
 }
 
@@ -333,11 +388,13 @@ ir_instr* ir_instr_new(ir_block* owner, int op)
     self->bops[0] = NULL;
     self->bops[1] = NULL;
     MEM_VECTOR_INIT(self, phi);
+    MEM_VECTOR_INIT(self, params);
 
     self->eid = 0;
     return self;
 }
 MEM_VEC_FUNCTIONS(ir_instr, ir_phi_entry_t, phi)
+MEM_VEC_FUNCTIONS(ir_instr, ir_value*, params)
 
 void ir_instr_delete(ir_instr *self)
 {
@@ -351,14 +408,22 @@ void ir_instr_delete(ir_instr *self)
     for (i = 0; i < self->phi_count; ++i) {
         size_t idx;
         if (ir_value_writes_find(self->phi[i].value, self, &idx))
-            if (ir_value_writes_remove(self->phi[i].value, idx)) GMQCC_SUPRESS_EMPTY_BODY;
+            if (ir_value_writes_remove(self->phi[i].value, idx)) GMQCC_SUPPRESS_EMPTY_BODY;
         if (ir_value_reads_find(self->phi[i].value, self, &idx))
-            if (ir_value_reads_remove (self->phi[i].value, idx)) GMQCC_SUPRESS_EMPTY_BODY;
+            if (ir_value_reads_remove (self->phi[i].value, idx)) GMQCC_SUPPRESS_EMPTY_BODY;
     }
     MEM_VECTOR_CLEAR(self, phi);
-    if (ir_instr_op(self, 0, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
-    if (ir_instr_op(self, 1, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
-    if (ir_instr_op(self, 2, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
+    for (i = 0; i < self->params_count; ++i) {
+        size_t idx;
+        if (ir_value_writes_find(self->params[i], self, &idx))
+            if (ir_value_writes_remove(self->params[i], idx)) GMQCC_SUPPRESS_EMPTY_BODY;
+        if (ir_value_reads_find(self->params[i], self, &idx))
+            if (ir_value_reads_remove (self->params[i], idx)) GMQCC_SUPPRESS_EMPTY_BODY;
+    }
+    MEM_VECTOR_CLEAR(self, params);
+    if (ir_instr_op(self, 0, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
+    if (ir_instr_op(self, 1, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
+    if (ir_instr_op(self, 2, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
     mem_d(self);
 }
 
@@ -400,6 +465,7 @@ ir_value* ir_value_var(const char *name, int storetype, int vtype)
     self = (ir_value*)mem_a(sizeof(*self));
     self->vtype = vtype;
     self->fieldtype = TYPE_VOID;
+    self->outtype = TYPE_VOID;
     self->store = storetype;
     MEM_VECTOR_INIT(self, reads);
     MEM_VECTOR_INIT(self, writes);
@@ -434,7 +500,8 @@ ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int
 
 void ir_value_delete(ir_value* self)
 {
-    mem_d((void*)self->name);
+    if (self->name)
+        mem_d((void*)self->name);
     if (self->isconst)
     {
         if (self->vtype == TYPE_STRING)
@@ -580,7 +647,84 @@ bool ir_value_life_merge(ir_value *self, size_t s)
     return ir_value_life_insert(self, i, new_entry);
 }
 
-bool ir_values_overlap(ir_value *a, ir_value *b)
+bool ir_value_life_merge_into(ir_value *self, const ir_value *other)
+{
+    size_t i, myi;
+
+    if (!other->life_count)
+        return true;
+
+    if (!self->life_count) {
+        for (i = 0; i < other->life_count; ++i) {
+            if (!ir_value_life_add(self, other->life[i]))
+                return false;
+        }
+        return true;
+    }
+
+    myi = 0;
+    for (i = 0; i < other->life_count; ++i)
+    {
+        const ir_life_entry_t *life = &other->life[i];
+        while (true)
+        {
+            ir_life_entry_t *entry = &self->life[myi];
+
+            if (life->end+1 < entry->start)
+            {
+                /* adding an interval before entry */
+                if (!ir_value_life_insert(self, myi, *life))
+                    return false;
+                ++myi;
+                break;
+            }
+
+            if (life->start <  entry->start &&
+                life->end   >= entry->start)
+            {
+                /* starts earlier and overlaps */
+                entry->start = life->start;
+            }
+
+            if (life->end     >  entry->end &&
+                life->start-1 <= entry->end)
+            {
+                /* ends later and overlaps */
+                entry->end = life->end;
+            }
+
+            /* see if our change combines it with the next ranges */
+            while (myi+1 < self->life_count &&
+                   entry->end+1 >= self->life[1+myi].start)
+            {
+                /* overlaps with (myi+1) */
+                if (entry->end < self->life[1+myi].end)
+                    entry->end = self->life[1+myi].end;
+                if (!ir_value_life_remove(self, myi+1))
+                    return false;
+                entry = &self->life[myi];
+            }
+
+            /* see if we're after the entry */
+            if (life->start > entry->end)
+            {
+                ++myi;
+                /* append if we're at the end */
+                if (myi >= self->life_count) {
+                    if (!ir_value_life_add(self, *life))
+                        return false;
+                    break;
+                }
+                /* otherweise check the next range */
+                continue;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+bool ir_values_overlap(const ir_value *a, const ir_value *b)
 {
     /* For any life entry in A see if it overlaps with
      * any life entry in B.
@@ -619,7 +763,7 @@ bool ir_values_overlap(ir_value *a, ir_value *b)
          * one entry is earlier than the other
          * that earlier entry will be moved forward
          */
-        if (la->end < lb->end)
+        if (la->start < lb->start)
         {
             /* order: A B, move A forward
              * check if we hit the end with A
@@ -627,7 +771,7 @@ bool ir_values_overlap(ir_value *a, ir_value *b)
             if (++la == enda)
                 break;
         }
-        else if (lb->end < la->end)
+        else if (lb->start < la->start)
         {
             /* order: B A, move B forward
              * check if we hit the end with B
@@ -916,6 +1060,47 @@ bool ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
     return ir_instr_phi_add(self, pe);
 }
 
+/* call related code */
+ir_instr* ir_block_create_call(ir_block *self, const char *label, ir_value *func)
+{
+    ir_value *out;
+    ir_instr *in;
+    in = ir_instr_new(self, INSTR_CALL0);
+    if (!in)
+        return NULL;
+    out = ir_value_out(self->owner, label, store_return, func->outtype);
+    if (!out) {
+        ir_instr_delete(in);
+        return NULL;
+    }
+    if (!ir_instr_op(in, 0, out, true) ||
+        !ir_instr_op(in, 1, func, false) ||
+        !ir_block_instr_add(self, in))
+    {
+        ir_instr_delete(in);
+        ir_value_delete(out);
+        return NULL;
+    }
+    return in;
+}
+
+ir_value* ir_call_value(ir_instr *self)
+{
+    return self->_ops[0];
+}
+
+bool ir_call_param(ir_instr* self, ir_value *v)
+{
+    if (!ir_instr_params_add(self, v))
+        return false;
+    if (!ir_value_reads_add(v, self)) {
+        if (!ir_instr_params_remove(self, self->params_count-1))
+            GMQCC_SUPPRESS_EMPTY_BODY;
+        return false;
+    }
+    return true;
+}
+
 /* binary op related code */
 
 ir_value* ir_block_create_binop(ir_block *self,
@@ -1414,6 +1599,132 @@ bool ir_function_calculate_liferanges(ir_function *self)
     return true;
 }
 
+/* Local-value allocator
+ * After finishing creating the liferange of all values used in a function
+ * we can allocate their global-positions.
+ * This is the counterpart to register-allocation in register machines.
+ */
+typedef struct {
+    MEM_VECTOR_MAKE(ir_value*, locals);
+    MEM_VECTOR_MAKE(size_t,    sizes);
+    MEM_VECTOR_MAKE(size_t,    positions);
+} function_allocator;
+MEM_VEC_FUNCTIONS(function_allocator, ir_value*, locals)
+MEM_VEC_FUNCTIONS(function_allocator, size_t,    sizes)
+MEM_VEC_FUNCTIONS(function_allocator, size_t,    positions)
+
+static bool function_allocator_alloc(function_allocator *alloc, const ir_value *var)
+{
+    ir_value *slot;
+    size_t vsize = type_sizeof[var->vtype];
+
+    slot = ir_value_var("reg", store_global, var->vtype);
+    if (!slot)
+        return false;
+
+    if (!ir_value_life_merge_into(slot, var))
+        goto localerror;
+
+    if (!function_allocator_locals_add(alloc, slot))
+        goto localerror;
+
+    if (!function_allocator_sizes_add(alloc, vsize))
+        goto localerror;
+
+    return true;
+
+localerror:
+    ir_value_delete(slot);
+    return false;
+}
+
+bool ir_function_allocate_locals(ir_function *self)
+{
+    size_t i, a;
+    bool   retval = true;
+    size_t pos;
+
+    ir_value *slot;
+    const ir_value *v;
+
+    function_allocator alloc;
+
+    MEM_VECTOR_INIT(&alloc, locals);
+    MEM_VECTOR_INIT(&alloc, sizes);
+    MEM_VECTOR_INIT(&alloc, positions);
+
+    for (i = 0; i < self->locals_count; ++i)
+    {
+        if (!function_allocator_alloc(&alloc, self->locals[i]))
+            goto error;
+    }
+
+    /* Allocate a slot for any value that still exists */
+    for (i = 0; i < self->values_count; ++i)
+    {
+        v = self->values[i];
+
+        if (!v->life_count)
+            continue;
+
+        for (a = 0; a < alloc.locals_count; ++a)
+        {
+            slot = alloc.locals[a];
+
+            if (ir_values_overlap(v, slot))
+                continue;
+
+            if (!ir_value_life_merge_into(slot, v))
+                goto error;
+
+            /* adjust size for this slot */
+            if (alloc.sizes[a] < type_sizeof[v->vtype])
+                alloc.sizes[a] = type_sizeof[v->vtype];
+
+            self->values[i]->code.local = a;
+            break;
+        }
+        if (a >= alloc.locals_count) {
+            self->values[i]->code.local = alloc.locals_count;
+            if (!function_allocator_alloc(&alloc, v))
+                goto error;
+        }
+    }
+
+    /* Adjust slot positions based on sizes */
+    if (!function_allocator_positions_add(&alloc, 0))
+        goto error;
+
+    if (alloc.sizes_count)
+        pos = alloc.positions[0] + alloc.sizes[0];
+    else
+        pos = 0;
+    for (i = 1; i < alloc.sizes_count; ++i)
+    {
+        pos = alloc.positions[i-1] + alloc.sizes[i-1];
+        if (!function_allocator_positions_add(&alloc, pos))
+            goto error;
+    }
+
+    self->allocated_locals = pos + alloc.sizes[alloc.sizes_count-1];
+
+    /* Take over the actual slot positions */
+    for (i = 0; i < self->values_count; ++i)
+        self->values[i]->code.local = alloc.positions[self->values[i]->code.local];
+
+    goto cleanup;
+
+error:
+    retval = false;
+cleanup:
+    for (i = 0; i < alloc.locals_count; ++i)
+        ir_value_delete(alloc.locals[i]);
+    MEM_VECTOR_CLEAR(&alloc, locals);
+    MEM_VECTOR_CLEAR(&alloc, sizes);
+    MEM_VECTOR_CLEAR(&alloc, positions);
+    return retval;
+}
+
 /* Get information about which operand
  * is read from, or written to.
  */
@@ -1783,7 +2094,6 @@ static bool gen_global_pointer(ir_value *global)
 static bool gen_blocks_recursive(ir_function *func, ir_block *block)
 {
     prog_section_statement stmt;
-    prog_section_statement *stptr;
     ir_instr *instr;
     ir_block *target;
     ir_block *ontrue;
@@ -1815,7 +2125,7 @@ tailcall:
 
             /* otherwise we generate a jump instruction */
             stmt.opcode = INSTR_GOTO;
-            stmt.o1.s1 = (target->code_start-1) - code_statements_elements;
+            stmt.o1.s1 = (target->code_start) - code_statements_elements;
             stmt.o2.s1 = 0;
             stmt.o3.s1 = 0;
             if (code_statements_add(stmt) < 0)
@@ -1832,9 +2142,10 @@ tailcall:
              * come first: eg. optimize IFs without ELSE...
              */
 
-            stmt.o1.s1 = instr->_ops[0]->code.globaladdr;
-
+            stmt.o1.u1 = instr->_ops[0]->code.globaladdr;
+            stmt.o2.u1 = 0;
             stmt.o3.s1 = 0;
+
             if (ontrue->generated) {
                 stmt.opcode = INSTR_IF;
                 stmt.o2.s1 = (ontrue->code_start-1) - code_statements_elements;
@@ -1861,22 +2172,23 @@ tailcall:
             }
             /* neither ontrue nor onfalse exist */
             stmt.opcode = INSTR_IFNOT;
-            stidx = code_statements_elements - 1;
+            stidx = code_statements_elements;
             if (code_statements_add(stmt) < 0)
                 return false;
-            stptr = &code_statements_data[stidx];
             /* on false we jump, so add ontrue-path */
             if (!gen_blocks_recursive(func, ontrue))
                 return false;
             /* fixup the jump address */
-            stptr->o2.s1 = (ontrue->code_start-1) - (stidx+1);
+            code_statements_data[stidx].o2.s1 = code_statements_elements - stidx;
             /* generate onfalse path */
             if (onfalse->generated) {
+                /* fixup the jump address */
+                code_statements_data[stidx].o2.s1 = (onfalse->code_start) - (stidx);
                 /* may have been generated in the previous recursive call */
                 stmt.opcode = INSTR_GOTO;
+                stmt.o1.s1 = (onfalse->code_start) - code_statements_elements;
                 stmt.o2.s1 = 0;
                 stmt.o3.s1 = 0;
-                stmt.o1.s1 = (onfalse->code_start-1) - code_statements_elements;
                 return (code_statements_add(stmt) >= 0);
             }
             /* if not, generate now */
@@ -1885,6 +2197,19 @@ tailcall:
         }
 
         if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) {
+            /* Trivial call translation:
+             * copy all params to OFS_PARM*
+             * if the output's storetype is not store_return,
+             * add append a STORE instruction!
+             *
+             * NOTES on how to do it better without much trouble:
+             * -) The liferanges!
+             *      Simply check the liferange of all parameters for
+             *      other CALLs. For each param with no CALL in its
+             *      liferange, we can store it in an OFS_PARM at
+             *      generation already. This would even include later
+             *      reuse.... probably... :)
+             */
             printf("TODO: call instruction\n");
             return false;
         }
@@ -1909,11 +2234,20 @@ tailcall:
         if (instr->_ops[2])
             stmt.o2.u1 = instr->_ops[2]->code.globaladdr;
 
-        if (stmt.opcode == INSTR_RETURN)
+        if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE)
         {
             stmt.o1.u1 = stmt.o3.u1;
             stmt.o3.u1 = 0;
         }
+        else if ((stmt.opcode >= INSTR_STORE_F    &&
+                  stmt.opcode <= INSTR_STORE_FNC)    ||
+                 (stmt.opcode >= INSTR_NOT_F      &&
+                  stmt.opcode <= INSTR_NOT_FNC))
+        {
+            /* 2-operand instructions with A -> B */
+            stmt.o2.u1 = stmt.o3.u1;
+            stmt.o3.u1 = 0;
+        }
 
         if (code_statements_add(stmt) < 0)
             return false;
@@ -1937,8 +2271,10 @@ static bool gen_function_code(ir_function *self)
     if (block->generated)
         return true;
 
-    if (!gen_blocks_recursive(self, block))
+    if (!gen_blocks_recursive(self, block)) {
+        printf("failed to generate blocks for '%s'\n", self->name);
         return false;
+    }
     return true;
 }
 
@@ -1948,6 +2284,7 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
     ir_function          *irfun;
 
     size_t i;
+    size_t local_var_end;
 
     if (!global->isconst ||
         !global->constval.vfunc)
@@ -1972,16 +2309,37 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
             fun.argsize[i] = 1;
     }
 
-    fun.locals = irfun->locals_count;
     fun.firstlocal = code_globals_elements;
+    fun.locals     = irfun->allocated_locals + irfun->locals_count;
+
+    local_var_end = 0;
     for (i = 0; i < irfun->locals_count; ++i) {
-        if (!ir_builder_gen_global(ir, irfun->locals[i]))
+        if (!ir_builder_gen_global(ir, irfun->locals[i])) {
+            printf("Failed to generate global %s\n", irfun->locals[i]->name);
             return false;
+        }
+    }
+    if (irfun->locals_count) {
+        ir_value *last = irfun->locals[irfun->locals_count-1];
+        local_var_end = last->code.globaladdr;
+        local_var_end += type_sizeof[last->vtype];
+    }
+    for (i = 0; i < irfun->values_count; ++i)
+    {
+        /* generate code.globaladdr for ssa values */
+        ir_value *v = irfun->values[i];
+        v->code.globaladdr = local_var_end + v->code.local;
+    }
+    for (i = 0; i < irfun->locals_count; ++i) {
+        /* fill the locals with zeros */
+        code_globals_add(0);
     }
 
     fun.entry      = code_statements_elements;
-    if (!gen_function_code(irfun))
+    if (!gen_function_code(irfun)) {
+        printf("Failed to generate code for function %s\n", irfun->name);
         return false;
+    }
 
     return (code_functions_add(fun) >= 0);
 }
@@ -1991,7 +2349,7 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global)
     int32_t         *iptr;
     prog_section_def def;
 
-    def.type = global->vtype;
+    def.type   = global->vtype;
     def.offset = code_globals_elements;
     def.name   = global->code.name       = code_genstring(global->name);
 
@@ -2006,8 +2364,7 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global)
             return false;
         return gen_global_field(global);
     case TYPE_ENTITY:
-        if (code_defs_add(def) < 0)
-            return false;
+        /* fall through */
     case TYPE_FLOAT:
     {
         if (code_defs_add(def) < 0)
@@ -2051,6 +2408,7 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global)
     case TYPE_FUNCTION:
         if (code_defs_add(def) < 0)
             return false;
+        code_globals_add(code_functions_elements);
         return gen_global_function(self, global);
     case TYPE_VARIANT:
         /* assume biggest type */
@@ -2071,16 +2429,14 @@ bool ir_builder_generate(ir_builder *self, const char *filename)
 
     code_init();
 
-    /* FIXME: generate TYPE_FUNCTION globals and link them
-     * to their ir_function.
-     */
-
     for (i = 0; i < self->globals_count; ++i)
     {
-        if (!ir_builder_gen_global(self, self->globals[i]))
+        if (!ir_builder_gen_global(self, self->globals[i])) {
             return false;
+        }
     }
 
+    printf("writing '%s'...\n", filename);
     return code_write(filename);
 }