]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
ast_switch
authorWolfgang (Blub) Bumiller <blub@speed.at>
Mon, 19 Nov 2012 20:17:44 +0000 (21:17 +0100)
committerWolfgang (Blub) Bumiller <blub@speed.at>
Mon, 19 Nov 2012 20:17:44 +0000 (21:17 +0100)
ast.c
ast.h

diff --git a/ast.c b/ast.c
index 51912e94ca284356004fb62acfc5f6c115754f8c..8083b36e0ca6e8f5783d489e8e2b2d7073108174 100644 (file)
--- a/ast.c
+++ b/ast.c
@@ -716,6 +716,33 @@ void ast_breakcont_delete(ast_breakcont *self)
     mem_d(self);
 }
 
     mem_d(self);
 }
 
+ast_switch* ast_switch_new(lex_ctx ctx, ast_expression *op)
+{
+    ast_instantiate(ast_switch, ctx, ast_switch_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen);
+
+    self->operand = op;
+    self->cases   = NULL;
+
+    return self;
+}
+
+void ast_switch_delete(ast_switch *self)
+{
+    size_t i;
+    ast_unref(self->operand);
+
+    for (i = 0; i < vec_size(self->cases); ++i) {
+        if (self->cases[i].value)
+            ast_unref(self->cases[i].value);
+        ast_unref(self->cases[i].code);
+    }
+    vec_free(self->cases);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
 ast_call* ast_call_new(lex_ctx ctx,
                        ast_expression *funcexpr)
 {
 ast_call* ast_call_new(lex_ctx ctx,
                        ast_expression *funcexpr)
 {
@@ -2229,6 +2256,150 @@ bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue,
     return true;
 }
 
     return true;
 }
 
+bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ast_switch_case *def_case  = NULL;
+    ir_block        *def_bfall = NULL;
+
+    ir_value *dummy     = NULL;
+    ir_value *irop      = NULL;
+    ir_block *old_break = NULL;
+    ir_block *bout      = NULL;
+    ir_block *bfall     = NULL;
+    size_t    bout_id;
+    size_t    c;
+
+    char      typestr[1024];
+    uint16_t  cmpinstr;
+
+    if (lvalue) {
+        asterror(ast_ctx(self), "switch expression is not an l-value");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        asterror(ast_ctx(self), "internal error: ast_switch cannot be reused!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    (void)lvalue;
+    (void)out;
+
+    cgen = self->operand->expression.codegen;
+    if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop))
+        return false;
+
+    if (!vec_size(self->cases))
+        return true;
+
+    cmpinstr = type_eq_instr[irop->vtype];
+    if (cmpinstr >= AINSTR_END) {
+        ast_type_to_string(self->operand, typestr, sizeof(typestr));
+        asterror(ast_ctx(self), "invalid type to perform a switch on: %s", typestr);
+        return false;
+    }
+
+    bout_id = vec_size(func->ir_func->blocks);
+    bout = ir_function_create_block(func->ir_func, ast_function_label(func, "after_switch"));
+    if (!bout)
+        return false;
+
+    /* setup the break block */
+    old_break        = func->breakblock;
+    func->breakblock = bout;
+
+    /* Now create all cases */
+    for (c = 0; c < vec_size(self->cases); ++c) {
+        ir_value *cond, *val;
+        ir_block *bcase, *bnot;
+        size_t bnot_id;
+
+        ast_switch_case *swcase = &self->cases[c];
+
+        if (swcase->value) {
+            /* A regular case */
+            /* generate the condition operand */
+            cgen = swcase->value->expression.codegen;
+            if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val))
+                return false;
+            /* generate the condition */
+            cond = ir_block_create_binop(func->curblock, ast_function_label(func, "switch_eq"), cmpinstr, irop, val);
+            if (!cond)
+                return false;
+
+            bcase = ir_function_create_block(func->ir_func, ast_function_label(func, "case"));
+            bnot = ir_function_create_block(func->ir_func, ast_function_label(func, "not_case"));
+            bnot_id = vec_size(func->ir_func->blocks);
+            if (!bcase || !bnot)
+                return false;
+            if (!ir_block_create_if(func->curblock, cond, bcase, bnot))
+                return false;
+
+            /* Make the previous case-end fall through */
+            if (bfall && !bfall->final) {
+                if (!ir_block_create_jump(bfall, bcase))
+                    return false;
+            }
+
+            /* enter the case */
+            func->curblock = bcase;
+            cgen = swcase->code->expression.codegen;
+            if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy))
+                return false;
+
+            /* remember this block to fall through from */
+            bfall = func->curblock;
+
+            /* enter the else and move it down */
+            func->curblock = bnot;
+            vec_remove(func->ir_func->blocks, bnot_id, 1);
+            vec_push(func->ir_func->blocks, bnot);
+        } else {
+            /* The default case */
+            /* Remember where to fall through from: */
+            def_bfall = bfall;
+            bfall     = NULL;
+            /* remember which case it was */
+            def_case  = swcase;
+        }
+    }
+
+    /* If there was a default case, put it down here */
+    if (def_case) {
+        ir_block *bcase;
+
+        /* No need to create an extra block */
+        bcase = func->curblock;
+
+        /* Insert the fallthrough jump */
+        if (def_bfall && !def_bfall->final) {
+            if (!ir_block_create_jump(def_bfall, bcase))
+                return false;
+        }
+
+        /* Now generate the default code */
+        cgen = def_case->code->expression.codegen;
+        if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy))
+            return false;
+    }
+
+    /* Jump from the last bnot to bout */
+    if (!func->curblock->final && !ir_block_create_jump(func->curblock, bout))
+        return false;
+
+    /* restore the break block */
+    func->breakblock = old_break;
+
+    /* Move 'bout' to the end, it's nicer */
+    vec_remove(func->ir_func->blocks, bout_id, 1);
+    vec_push(func->ir_func->blocks, bout);
+
+    return true;
+}
+
 bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
 {
     ast_expression_codegen *cgen;
 bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
 {
     ast_expression_codegen *cgen;
diff --git a/ast.h b/ast.h
index f9f60afc13d6cb55ec67ea5e568e05ae69cadcc1..989fae727b72965561444bf0289426d4f4f49496 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -47,6 +47,7 @@ typedef struct ast_return_s      ast_return;
 typedef struct ast_member_s      ast_member;
 typedef struct ast_array_index_s ast_array_index;
 typedef struct ast_breakcont_s   ast_breakcont;
 typedef struct ast_member_s      ast_member;
 typedef struct ast_array_index_s ast_array_index;
 typedef struct ast_breakcont_s   ast_breakcont;
+typedef struct ast_switch_s      ast_switch;
 
 enum {
     TYPE_ast_node,
 
 enum {
     TYPE_ast_node,
@@ -66,7 +67,8 @@ enum {
     TYPE_ast_return,
     TYPE_ast_member,
     TYPE_ast_array_index,
     TYPE_ast_return,
     TYPE_ast_member,
     TYPE_ast_array_index,
-    TYPE_ast_breakcont
+    TYPE_ast_breakcont,
+    TYPE_ast_switch
 };
 
 #define ast_istype(x, t) ( ((ast_node_common*)x)->nodetype == (TYPE_##t) )
 };
 
 #define ast_istype(x, t) ( ((ast_node_common*)x)->nodetype == (TYPE_##t) )
@@ -459,6 +461,33 @@ void ast_breakcont_delete(ast_breakcont*);
 
 bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**);
 
 
 bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**);
 
+/* Switch Statements
+ *
+ * A few notes about this: with the original QCVM, no real optimization
+ * is possible. The SWITCH instruction set isn't really helping a lot, since
+ * it only collapes the EQ and IF instructions into one.
+ * Note: Declaring local variables inside caseblocks is normal.
+ * Since we don't have to deal with a stack there's no unnatural behaviour to
+ * be expected from it.
+ * TODO: Ticket #20
+ */
+typedef struct {
+    ast_expression *value; /* #20 will replace this */
+    ast_expression *code;
+} ast_switch_case;
+struct ast_switch_s
+{
+    ast_expression_common expression;
+
+    ast_expression  *operand;
+    ast_switch_case *cases;
+};
+
+ast_switch* ast_switch_new(lex_ctx ctx, ast_expression *op);
+void ast_switch_delete(ast_switch*);
+
+bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**);
+
 /* CALL node
  *
  * Contains an ast_expression as target, rather than an ast_function/value.
 /* CALL node
  *
  * Contains an ast_expression as target, rather than an ast_function/value.