Perform SSA conversion of locals. Much, *much* better code now, at least
authorDavid Given <dg@cowlark.com>
Sun, 2 Oct 2016 15:50:34 +0000 (17:50 +0200)
committerDavid Given <dg@cowlark.com>
Sun, 2 Oct 2016 15:50:34 +0000 (17:50 +0200)
inasmuch as it looks better before register allocation. Basic blocks now know
their own successors and predecessors (after a certain point in the IR
processing).

mach/proto/mcg/basicblock.c
mach/proto/mcg/basicblock.h [new file with mode: 0644]
mach/proto/mcg/ir.c
mach/proto/mcg/main.c
mach/proto/mcg/mcg.h
mach/proto/mcg/pass_convertstackops.c
mach/proto/mcg/pass_ssa.c [new file with mode: 0644]
mach/proto/mcg/procedure.c
mach/proto/mcg/table
util/mcgg/ir.dat

index e44f553..5a30d9a 100644 (file)
@@ -24,7 +24,7 @@ struct basicblock* bb_get(const char* name)
        p = str2idf((char*) name, 0);
        if (!p->block)
        {
-               p->block = calloc(sizeof(struct basicblock), 1);
+               p->block = calloc(1, sizeof(*p->block));
                p->block->name = name;
        }
        return p->block;
@@ -39,8 +39,4 @@ void bb_alias(struct basicblock* block, const char* name)
        p->block = block;
 }
 
-void bb_print(char k, struct basicblock* block)
-{
-}
-
 /* vim: set sw=4 ts=4 expandtab : */
diff --git a/mach/proto/mcg/basicblock.h b/mach/proto/mcg/basicblock.h
new file mode 100644 (file)
index 0000000..8f36e5e
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef BASICBLOCK_H
+#define BASICBLOCK_H
+
+struct basicblock
+{
+    const char* name;
+    ARRAYOF(struct em) ems;
+    ARRAYOF(struct ir) irs;
+    ARRAYOF(struct hop) hops;
+
+    ARRAYOF(struct basicblock) prevs;
+    ARRAYOF(struct basicblock) nexts;
+    int order; /* used by SSA code */
+
+    bool is_fake : 1;
+    bool is_root : 1;
+    bool is_terminated : 1;
+};
+
+extern void bb_init(void);
+extern struct basicblock* bb_get(const char* name);
+extern void bb_alias(struct basicblock* block, const char* name);
+
+#endif
+
index 0aea3ea..de3ec39 100644 (file)
@@ -107,7 +107,7 @@ static void print_expr(char k, const struct ir* ir)
     tracef(k, "%s", ir_data[ir->opcode].name);
     if (ir->size)
         tracef(k, "%d", ir->size);
-    tracef(k, ":%d(", ir->id);
+    tracef(k, "(");
 
        switch (ir->opcode)
        {
index 5d450bd..bf722f4 100644 (file)
@@ -5,10 +5,15 @@ bool tracing(char k)
     switch (k)
     {
         case 0:   return true;
+        case 'S': return true;
         case 'E': return false;
+        case 'G': return true;
         case '0': return false;
         case '1': return false;
         case '2': return false;
+        case '3': return false;
+        case '4': return false;
+        case '5': return false;
         case 'I': return true;
         default:  return true;
     }
index 7856049..ba23e2d 100644 (file)
@@ -24,6 +24,7 @@
 #include "ir.h"
 #include "mcgg.h"
 #include "hop.h"
+#include "basicblock.h"
 #include "procedure.h"
 
 extern char em_pseu[][4];
@@ -73,17 +74,6 @@ struct em
     } u;
 };
     
-struct basicblock
-{
-    const char* name;
-    ARRAYOF(struct em) ems;
-    ARRAYOF(struct ir) irs;
-    ARRAYOF(struct hop) hops;
-    bool is_fake : 1;
-    bool is_root : 1;
-    bool is_terminated : 1;
-};
-
 extern const char* aprintf(const char* fmt, ...);
 extern void tracef(char k, const char* fmt, ...);
 extern bool tracing(char k);
@@ -104,11 +94,6 @@ extern void data_block(const uint8_t* data, size_t size, bool is_ro);
 extern void data_offset(const char* label, arith offset, bool is_ro);
 extern void data_bss(arith size, int init);
 
-extern void bb_init(void);
-extern struct basicblock* bb_get(const char* name);
-extern void bb_alias(struct basicblock* block, const char* name);
-extern void bb_print(char k, struct basicblock* block);
-
 extern void tb_filestart(void);
 extern void tb_fileend(void);
 extern void tb_procedure(struct procedure* proc);
@@ -120,6 +105,7 @@ extern void pass_eliminate_trivial_blocks(struct procedure* proc);
 extern void pass_instruction_selector(struct procedure* proc);
 extern void pass_promote_float_ops(struct procedure* proc);
 extern void pass_group_irs(struct procedure* proc);
+extern void pass_convert_locals_to_ssa(struct procedure* proc);
 
 #endif
 
index f576448..d555b58 100644 (file)
@@ -41,28 +41,6 @@ static struct ir* get_first_pop(struct basicblock* bb)
     return NULL;
 }
 
-static bool collect_outputs_cb(struct ir* ir, void* user)
-{
-    struct basicblock* caller = user;
-
-    if (ir->opcode == IR_BLOCK)
-        pmap_add(&graph, caller, ir->u.bvalue);
-    return false;
-}
-
-static void make_bb_graph(struct procedure* proc)
-{
-    int i, j;
-
-    graph.count = 0;
-    for (i=0; i<proc->blocks.count; i++)
-    {
-        struct basicblock* bb = proc->blocks.item[i];
-        for (j=0; j<bb->irs.count; j++)
-            ir_walk(bb->irs.item[j], collect_outputs_cb, bb);
-    }
-}
-
 static void convert_block(struct procedure* proc, struct basicblock* bb)
 {
     int i, j;
@@ -78,32 +56,26 @@ static void convert_block(struct procedure* proc, struct basicblock* bb)
         /* Abort unless *every* successor block of this one starts with a pop
          * of the same size... */
 
-        for (i=0; i<graph.count; i++)
+        for (i=0; i<bb->nexts.count; i++)
         {
-            if (graph.item[i].left == bb)
+            struct basicblock* outbb = bb->nexts.item[i];
+
+            ir = get_first_pop(outbb);
+            if (!ir || (ir->size != lastpush->size))
+                return;
+            array_appendu(&pops, ir);
+
+            /* Also abort unless *every* predecessor block of the one we've
+             * just found *also* ends in a push of the same size. */
+
+            for (j=0; j<outbb->prevs.count; j++)
             {
-                struct basicblock* outbb = graph.item[i].right;
+                struct basicblock* inbb = outbb->prevs.item[j];
 
-                ir = get_first_pop(outbb);
+                ir = get_last_push(inbb);
                 if (!ir || (ir->size != lastpush->size))
                     return;
-                array_appendu(&pops, ir);
-
-                /* Also abort unless *every* predecessor block of the one we've
-                 * just found *also* ends in a push of the same size. */
-
-                for (j=0; j<graph.count; j++)
-                {
-                    if (graph.item[j].right == outbb)
-                    {
-                        struct basicblock* inbb = graph.item[j].left;
-
-                        ir = get_last_push(inbb);
-                        if (!ir || (ir->size != lastpush->size))
-                            return;
-                        array_appendu(&pushes, ir);
-                    }
-                }
+                array_appendu(&pushes, ir);
             }
         }
 
@@ -139,8 +111,6 @@ void pass_convert_stack_ops(struct procedure* proc)
 {
     int i;
 
-    make_bb_graph(proc);
-
     for (i=0; i<proc->blocks.count; i++)
         convert_block(proc, proc->blocks.item[i]);
 }
diff --git a/mach/proto/mcg/pass_ssa.c b/mach/proto/mcg/pass_ssa.c
new file mode 100644 (file)
index 0000000..87a0b97
--- /dev/null
@@ -0,0 +1,340 @@
+#include "mcg.h"
+
+static struct basicblock* entry;
+static ARRAYOF(struct basicblock) postorder;
+static PMAPOF(struct basicblock, struct basicblock) dominators;
+static PMAPOF(struct basicblock, struct basicblock) dominancefrontiers;
+
+static struct local* current_local;
+static ARRAYOF(struct basicblock) defining;
+static ARRAYOF(struct basicblock) needsphis;
+static ARRAYOF(struct ir) definitions;
+static ARRAYOF(struct basicblock) rewritten;
+
+static void recursively_walk_blocks(struct basicblock* bb);
+
+static void recursively_walk_graph_postorder(struct basicblock* bb)
+{
+    static ARRAYOF(struct basicblock) pending;
+    int i;
+
+    if (array_contains(&postorder, bb) || array_contains(&pending, bb))
+        return;
+
+    array_appendu(&pending, bb);
+
+    i = 0;
+    for (i=0; i<bb->nexts.count; i++)
+        recursively_walk_graph_postorder(bb->nexts.item[i]);
+
+    array_remove(&pending, bb);
+    bb->order = postorder.count;
+    array_appendu(&postorder, bb);
+}
+
+static void walk_graph_postorder()
+{
+    int i;
+
+    postorder.count = 0;
+    recursively_walk_graph_postorder(entry);
+
+    for (i=0; i<postorder.count; i++)
+    {
+        tracef('S', "S: postorder: %s\n",
+            postorder.item[i]->name);
+    }
+}
+
+static struct basicblock* intersect(struct basicblock* p1, struct basicblock* p2)
+{
+    while (p1 != p2)
+    {
+        while (p1->order < p2->order)
+            p1 = pmap_get(&dominators, p1);
+
+        while (p2->order < p1->order)
+            p2 = pmap_get(&dominators, p2);
+    }
+
+    return p1;
+}
+
+static void calculate_dominance_graph(void)
+{
+    /* This is the algorithm described here:
+     *
+     * Cooper, Keith D., Timothy J. Harvey, and Ken Kennedy.
+     * "A simple, fast dominance algorithm."
+     * Software Practice & Experience 4.1-10 (2001): 1-8.
+     *
+     * https://www.cs.rice.edu/~keith/EMBED/dom.pdf
+     */
+
+    int i, j;
+    bool changed;
+
+    dominators.count = 0;
+
+    /* The entry block dominates itself. */
+
+    pmap_put(&dominators, entry, entry);
+
+    do
+    {
+        changed = false;
+
+        for (i = postorder.count-2; i >= 0; i--)
+        {
+            struct basicblock* b = postorder.item[i];
+            struct basicblock* new_idom = NULL;
+            for (j=0; j<b->prevs.count; j++)
+            {
+                struct basicblock* p = b->prevs.item[j];
+
+                if (!new_idom)
+                    new_idom = p;
+                else if (pmap_get(&dominators, p))
+                    new_idom = intersect(p, new_idom);
+            }
+
+            if (pmap_get(&dominators, b) != new_idom)
+            {
+                pmap_put(&dominators, b, new_idom);
+                changed = true;
+            }
+        }
+    }
+    while (changed);
+
+    for (i=0; i<dominators.count; i++)
+    {
+        tracef('S', "S: domination: %s -> %s\n",
+            dominators.item[i].left->name,
+            dominators.item[i].right->name);
+    }
+}
+
+static void calculate_dominance_frontier_graph(void)
+{
+    /* This is the algorithm described here:
+     *
+     * Cooper, Keith D., Timothy J. Harvey, and Ken Kennedy.
+     * "A simple, fast dominance algorithm."
+     * Software Practice & Experience 4.1-10 (2001): 1-8.
+     *
+     * https://www.cs.rice.edu/~keith/EMBED/dom.pdf
+     */
+
+    int i, j;
+
+    dominancefrontiers.count = 0;
+
+    for (i=0; i<postorder.count; i++)
+    {
+        struct basicblock* b = postorder.item[i];
+        struct basicblock* dominator = pmap_get(&dominators, b);
+        if (b->prevs.count >= 2)
+        {
+            for (j=0; j<b->prevs.count; j++)
+            {
+                struct basicblock* runner = b->prevs.item[j];
+                while (runner != dominator)
+                {
+                    tracef('S', "S: %s is in %s's dominance frontier\n",
+                        b->name, runner->name);
+                    pmap_add(&dominancefrontiers, runner, b);
+                    runner = pmap_get(&dominators, runner);
+                }
+            }
+        }
+    }
+
+}
+
+static bool is_local(struct ir* ir)
+{
+    return ((ir->opcode == IR_LOAD) &&
+            (ir->left->opcode == IR_LOCAL) &&
+            (ir->left->u.ivalue == current_local->offset));
+}
+
+static bool rewrite_loads_cb(struct ir* ir, void* user)
+{
+    struct ir* definition = user;
+
+    /* Rewrite in place where possible. */
+
+    if (ir->left && is_local(ir->left))
+        ir->left = definition;
+    if (ir->right && is_local(ir->right))
+        ir->right = definition;
+
+    /* Otherwise, go via a IR_REG (which should, with luck, turn into no code). */
+    if (is_local(ir))
+    {
+        ir->opcode = IR_NOP;
+        ir->left = definition;
+        ir->right = NULL;
+    }
+
+    return false;
+}
+
+/* Walks the tree, rewriting IRs to push new definitions downwards. */
+
+static void recursively_rewrite_tree(struct basicblock* bb)
+{
+    int i;
+    int defcount = definitions.count;
+
+    if (array_contains(&rewritten, bb))
+        return;
+    array_appendu(&rewritten, bb);
+
+    for (i=0; i<bb->irs.count; i++)
+    {
+        struct ir* ir = bb->irs.item[i];
+        
+        if (definitions.count > 0)
+        {
+            ir_walk(ir, rewrite_loads_cb, definitions.item[definitions.count-1]);
+        }
+
+        if (((ir->opcode == IR_STORE) &&
+             (ir->left->opcode == IR_LOCAL) &&
+             (ir->left->u.ivalue == current_local->offset)
+            ) ||
+            ((i == 0) &&
+             (ir->opcode == IR_PHI) &&
+             array_contains(&needsphis, bb)))
+        {
+            /* This is a definition. */
+
+            if (ir->opcode == IR_STORE)
+            {
+                ir->opcode = IR_NOP;
+                ir->left = ir->right;
+                ir->right = NULL;
+            }
+            array_push(&definitions, ir);
+        }
+    }
+
+    for (i=0; i<bb->nexts.count; i++)
+    {
+        struct basicblock* nextbb = bb->nexts.item[i];
+        struct ir* ir = nextbb->irs.item[0];
+
+        if ((definitions.count > 0) &&
+            (ir->opcode == IR_PHI) &&
+            array_contains(&needsphis, nextbb))
+        {
+            array_appendu(&ir->u.phivalue, definitions.item[definitions.count-1]);
+        }
+
+        recursively_rewrite_tree(nextbb);
+    }
+
+    definitions.count = defcount;
+}
+
+static void ssa_convert(void)
+{
+    int i, j;
+
+    /* If this is a parameter, synthesise a load/store at the beginning of the
+     * program to force it into a register. (Unless it's written to it'll
+     * always be read from the frame.) */
+
+    if (current_local->offset >= 0)
+    {
+        struct ir* ir = new_ir2(
+            IR_STORE, current_local->size,
+            new_localir(current_local->offset),
+            new_ir1(
+                IR_LOAD, current_local->size,
+                new_localir(current_local->offset)
+            )
+        );
+
+        ir->root = ir;
+        ir->left->root = ir;
+        ir->right->root = ir;
+        ir->right->left->root = ir;
+        array_insert(&entry->irs, ir, 0);
+    }
+
+    defining.count = 0;
+    needsphis.count = 0;
+
+    /* Find everwhere where the variable is *defined*. */
+
+    for (i=0; i<postorder.count; i++)
+    {
+        struct basicblock* bb = postorder.item[i];
+        for (j=0; j<bb->irs.count; j++)
+        {
+            struct ir* ir = bb->irs.item[j];
+            if ((ir->opcode == IR_STORE) &&
+                (ir->left->opcode == IR_LOCAL) &&
+                (ir->left->u.ivalue == current_local->offset))
+            {
+                array_appendu(&defining, bb);
+            }
+        }
+    }
+
+    /* Every block which is in one of the defining block's dominance frontiers
+     * requires a phi. Remember that adding a phi also adds a definition. */
+
+    for (i=0; i<defining.count; i++)
+    {
+        struct basicblock* bb = defining.item[i];
+        struct basicblock* dominates = pmap_get(&dominancefrontiers, bb);
+        if (dominates)
+        {
+            array_appendu(&needsphis, dominates);
+            array_appendu(&defining, dominates);
+            tracef('S', "S: local %d needs phi in block %s\n", current_local->offset, dominates->name);
+        }
+    }
+
+    /* Add empty phi nodes. */
+
+    for (i=0; i<needsphis.count; i++)
+    {
+        struct basicblock* bb = needsphis.item[i];
+        struct ir* ir = new_ir0(IR_PHI, current_local->size);
+        ir->root = ir;
+        array_insert(&bb->irs, ir, 0);
+    }
+
+    /* Now do the rewriting by walking the tree, pushing definitions down the tree. */
+
+    definitions.count = 0;
+    rewritten.count = 0;
+    recursively_rewrite_tree(entry);
+}
+
+void pass_convert_locals_to_ssa(struct procedure* proc)
+{
+    int i;
+
+    entry = proc->blocks.item[0];
+    walk_graph_postorder();
+    assert(postorder.count == proc->blocks.count);
+    calculate_dominance_graph();
+    calculate_dominance_frontier_graph();
+
+    for (i=0; i<proc->locals.count; i++)
+    {
+        current_local = proc->locals.item[i].right;
+        if (current_local->is_register)
+            ssa_convert();
+    }
+}
+
+/* vim: set sw=4 ts=4 expandtab : */
+
+
index dc11e0a..5ea0372 100644 (file)
@@ -27,15 +27,67 @@ void procedure_compile(struct procedure* proc)
        print_blocks('1', proc);
 
     pass_group_irs(proc);
+    /* Passes from here on must preserve IR grouping */
+
     pass_eliminate_trivial_blocks(proc);
     pass_remove_dead_blocks(proc);
+
+    procedure_update_bb_graph(proc);
+    /* Passes from here on can't alter the BB graph */
+
+    print_blocks('2', proc);
     pass_convert_stack_ops(proc);
+    print_blocks('3', proc);
+    pass_convert_locals_to_ssa(proc);
+    print_blocks('4', proc);
     pass_promote_float_ops(proc);
+    print_blocks('5', proc);
 
-    print_blocks('2', proc);
 
     pass_instruction_selector(proc);
 }
 
+static bool collect_outputs_cb(struct ir* ir, void* user)
+{
+    struct basicblock* caller = user;
+
+    if (ir->opcode == IR_BLOCK)
+    {
+        array_appendu(&caller->nexts, ir->u.bvalue);
+        array_appendu(&ir->u.bvalue->prevs, caller);
+    }
+    return false;
+}
+
+void procedure_update_bb_graph(struct procedure* proc)
+{
+    int i, j;
+
+    for (i=0; i<proc->blocks.count; i++)
+    {
+        struct basicblock* bb = proc->blocks.item[i];
+        bb->prevs.count = bb->nexts.count = 0;
+    }
+
+    for (i=0; i<proc->blocks.count; i++)
+    {
+        struct basicblock* bb = proc->blocks.item[i];
+        for (j=0; j<bb->irs.count; j++)
+            ir_walk(bb->irs.item[j], collect_outputs_cb, bb);
+    }
+
+    for (i=0; i<proc->blocks.count; i++)
+    {
+        struct basicblock* bb = proc->blocks.item[i];
+
+        for (j=0; j<bb->nexts.count; j++)
+        {
+            tracef('G', "G: graph %s -> %s\n",
+                bb->name,
+                bb->nexts.item[j]->name);
+        }
+    }
+}
+
 /* vim: set sw=4 ts=4 expandtab : */
 
index 6d3e231..d523cfb 100644 (file)
@@ -39,6 +39,7 @@ PATTERNS
 
        int;
        float;
+       any;
 
        PAIR(BLOCK4, BLOCK4);
 
@@ -69,6 +70,13 @@ PATTERNS
                prefers int(in)
                cost 1;
 
+       int = NOP4(in:int)
+               emit "mov %int, %in"
+               cost 1;
+
+       any = PHI4
+               cost 0;
+
        float = in:REG4
                prefers float(in)
                cost 1;
@@ -146,6 +154,11 @@ PATTERNS
                emit "b $false"
                cost 8;
 
+       CJUMPLT(value:cc, PAIR(true:BLOCK4, false:BLOCK4))
+               emit "blt $true"
+               emit "b $false"
+               cost 8;
+
        CALL(dest:LABEL4)
                emit "bl $dest"
                cost 4;
index b12cf89..baa4617 100644 (file)
@@ -6,6 +6,7 @@
 S CONST # must be followed by float form
 S CONSTF
 S REG
+S NOP
 S LABEL
 S BLOCK
 V PAIR