]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Adding coverage support:
authorWolfgang Bumiller <wry.git@bumiller.com>
Tue, 7 Jan 2014 13:33:26 +0000 (14:33 +0100)
committerWolfgang Bumiller <wry.git@bumiller.com>
Tue, 7 Jan 2014 13:36:00 +0000 (14:36 +0100)
The -coverage option causes all values have AST_FLAG_BLOCK_COVERAGE
set by default.
The coverage attribute can be used to control coverage:
It takes an optional list of coverage types, currently only "block"
and "none" is recognized.
[[coverage]] defaults to [[coverage(block)]].
Use [[coverage(none)]] or [[coverage()]] to disable.

ast.c
ast.h
ir.c
ir.h
main.c
opts.def
parser.c

diff --git a/ast.c b/ast.c
index 1f793a11bc01d12cebf04c2a065cfd9067c50f24..d7e3d7a198cae3df58a8e2c0e0d87372e534e074 100644 (file)
--- a/ast.c
+++ b/ast.c
@@ -113,8 +113,10 @@ static void ast_expression_init(ast_expression *self,
     self->outr     = NULL;
     self->params   = NULL;
     self->count    = 0;
-    self->flags    = 0;
     self->varparam = NULL;
+    self->flags    = 0;
+    if (OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        self->flags |= AST_FLAG_BLOCK_COVERAGE;
 }
 
 static void ast_expression_delete(ast_expression *self)
@@ -1430,6 +1432,8 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
             self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
         if (self->expression.flags & AST_FLAG_ERASEABLE)
             self->ir_v->flags |= IR_FLAG_ERASEABLE;
+        if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
+            func->flags |= IR_FLAG_BLOCK_COVERAGE;
         /* The function is filled later on ast_function_codegen... */
         return true;
     }
diff --git a/ast.h b/ast.h
index 2c83e4193db518590d216b4741aa72d58ad9d342..7cc7a6c89b13975549307d2a269c3fde4bb4ec85 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -56,27 +56,37 @@ typedef struct ast_goto_s        ast_goto;
 typedef struct ast_argpipe_s     ast_argpipe;
 
 enum {
-    AST_FLAG_VARIADIC      = 1 << 0,
-    AST_FLAG_NORETURN      = 1 << 1,
-    AST_FLAG_INLINE        = 1 << 2,
-    AST_FLAG_INITIALIZED   = 1 << 3,
-    AST_FLAG_DEPRECATED    = 1 << 4,
-    AST_FLAG_INCLUDE_DEF   = 1 << 5,
-    AST_FLAG_IS_VARARG     = 1 << 6,
-    AST_FLAG_ALIAS         = 1 << 7,
-    AST_FLAG_ERASEABLE     = 1 << 8,
-    AST_FLAG_ACCUMULATE    = 1 << 9,
-
-    /*
-     * An array declared as []
+    AST_FLAG_VARIADIC       = 1 << 0,
+    AST_FLAG_NORETURN       = 1 << 1,
+    AST_FLAG_INLINE         = 1 << 2,
+    AST_FLAG_INITIALIZED    = 1 << 3,
+    AST_FLAG_DEPRECATED     = 1 << 4,
+    AST_FLAG_INCLUDE_DEF    = 1 << 5,
+    AST_FLAG_IS_VARARG      = 1 << 6,
+    AST_FLAG_ALIAS          = 1 << 7,
+    AST_FLAG_ERASEABLE      = 1 << 8,
+    AST_FLAG_ACCUMULATE     = 1 << 9,
+
+    /* An array declared as []
      * so that the size is taken from the initializer
      */
-    AST_FLAG_ARRAY_INIT    = 1 << 10,
+    AST_FLAG_ARRAY_INIT     = 1 << 10,
 
-    AST_FLAG_FINAL_DECL    = 1 << 11,
+    AST_FLAG_FINAL_DECL     = 1 << 11,
+
+    /* Several coverage options
+     * AST_FLAG_COVERAGE means there was an explicit [[coverage]] attribute,
+     * which will overwrite the default set via the commandline switches.
+     * BLOCK_COVERAGE inserts coverage() calls into every basic block.
+     * In the future there might be more options like tracking variable access
+     * by creating get/set wrapper functions.
+     */
+    AST_FLAG_COVERAGE       = 1 << 12,
+    AST_FLAG_BLOCK_COVERAGE = 1 << 13,
 
     AST_FLAG_LAST,
-    AST_FLAG_TYPE_MASK     = (AST_FLAG_VARIADIC | AST_FLAG_NORETURN)
+    AST_FLAG_TYPE_MASK      = (AST_FLAG_VARIADIC | AST_FLAG_NORETURN),
+    AST_FLAG_COVERAGE_MASK  = (AST_FLAG_BLOCK_COVERAGE)
 };
 
 enum {
diff --git a/ir.c b/ir.c
index 65bed5e943cdb6716f24400506a02f5e65e8773c..7df8bdbff6d7a51b33561a80edb4c96097dc167c 100644 (file)
--- a/ir.c
+++ b/ir.c
@@ -355,6 +355,8 @@ ir_builder* ir_builder_new(const char *modulename)
     }
 
     self->reserved_va_count = NULL;
+    self->coverage_func     = NULL;
+
     self->code              = code_init();
 
     return self;
@@ -602,6 +604,10 @@ ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char
     ir_block* bn = ir_block_new(self, label);
     bn->context = ctx;
     vec_push(self->blocks, bn);
+
+    if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func)
+        (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false);
+
     return bn;
 }
 
diff --git a/ir.h b/ir.h
index 006a0875fafe75b43a3f6127d8755c65c567abef..5b948358fdee732766bf818a66653350460c823a 100644 (file)
--- a/ir.h
+++ b/ir.h
@@ -48,6 +48,7 @@ enum {
     IR_FLAG_HAS_GOTO             = 1 << 2,
     IR_FLAG_INCLUDE_DEF          = 1 << 3,
     IR_FLAG_ERASEABLE            = 1 << 4,
+    IR_FLAG_BLOCK_COVERAGE       = 1 << 5,
 
     IR_FLAG_LAST,
     IR_FLAG_MASK_NO_OVERLAP      = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED),
@@ -273,6 +274,7 @@ struct ir_builder_s {
     /* there should just be this one nil */
     ir_value    *nil;
     ir_value    *reserved_va_count;
+    ir_value    *coverage_func;
     /* some virtual instructions require temps, and their code is isolated
      * so that we don't need to keep track of their liveness.
      */
diff --git a/main.c b/main.c
index a5e915a0fdbf5405929b93b027bd90da60cb0adc..156a3f0c1af5c81c4c72d7ac70dc64087ea5c29c 100644 (file)
--- a/main.c
+++ b/main.c
@@ -85,6 +85,7 @@ static int usage(void) {
             "  -Ono-<name>            disable specific optimization\n"
             "  -Ohelp                 list optimizations\n");
     con_out("  -force-crc=num         force a specific checksum into the header\n");
+    con_out("  -coverage              add coverage support\n");
     return -1;
 }
 
@@ -280,6 +281,10 @@ static bool options_parse(int argc, char **argv) {
                 con_color(0);
                 continue;
             }
+            if (!strcmp(argv[0]+1, "coverage")) {
+                OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
+                continue;
+            }
 
             switch (argv[0][1]) {
                 /* -h, show usage but exit with 0 */
index ea391ebd98db6a7d302ffdbbf89f785918906604..b2292452b2a63ec940c89325bd59d1d0ad9ee107 100644 (file)
--- a/opts.def
+++ b/opts.def
     GMQCC_DEFINE_FLAG(CORRECTION)
     GMQCC_DEFINE_FLAG(STATISTICS)
     GMQCC_DEFINE_FLAG(PROGSRC)
+    GMQCC_DEFINE_FLAG(COVERAGE)
 #endif
 
 /* some cleanup so we don't have to */
index 8af91e20458433a35039fa2ee4e74f73bbe115d9..956d88dd5e76d214b9a7b368314ec7137c8a223d 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -2944,6 +2944,47 @@ static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *
                     return false;
                 }
             }
+            else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) {
+                flags |= AST_FLAG_COVERAGE;
+                if (!parser_next(parser)) {
+                    error_in_coverage:
+                    parseerror(parser, "parse error in coverage attribute");
+                    *cvq = CV_WRONG;
+                    return false;
+                }
+                if (parser->tok == '(') {
+                    if (!parser_next(parser)) {
+                        bad_coverage_arg:
+                        parseerror(parser, "invalid parameter for coverage() attribute\n"
+                                           "valid are: block");
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                    if (parser->tok != ')') {
+                        do {
+                            if (parser->tok != TOKEN_IDENT)
+                                goto bad_coverage_arg;
+                            if (!strcmp(parser_tokval(parser), "block"))
+                                flags |= AST_FLAG_BLOCK_COVERAGE;
+                            else if (!strcmp(parser_tokval(parser), "none"))
+                                flags &= ~(AST_FLAG_COVERAGE_MASK);
+                            else
+                                goto bad_coverage_arg;
+                            if (!parser_next(parser))
+                                goto error_in_coverage;
+                            if (parser->tok == ',') {
+                                if (!parser_next(parser))
+                                    goto error_in_coverage;
+                            }
+                        } while (parser->tok != ')');
+                    }
+                    if (parser->tok != ')' || !parser_next(parser))
+                        goto error_in_coverage;
+                } else {
+                    /* without parameter [[coverage]] equals [[coverage(block)]] */
+                    flags |= AST_FLAG_BLOCK_COVERAGE;
+                }
+            }
             else
             {
                 /* Skip tokens until we hit a ]] */
@@ -5163,6 +5204,8 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
         }
 
         var->cvq = qualifier;
+        if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */
+            var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
         var->expression.flags |= qflags;
 
         /*
@@ -6201,11 +6244,50 @@ void parser_cleanup(parser_t *parser)
     mem_d(parser);
 }
 
+static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
+    ast_expression *expr;
+    ast_value      *cov;
+    ast_function   *func;
+
+    if (!OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        return true;
+
+    func = NULL;
+    for (size_t i = 0; i != vec_size(parser->functions); ++i) {
+        if (!strcmp(parser->functions[i]->name, "coverage")) {
+            func = parser->functions[i];
+            break;
+        }
+    }
+    if (!func) {
+        if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) {
+            con_out("coverage support requested but no coverage() builtin declared\n");
+            ir_builder_delete(ir);
+            return false;
+        }
+        return true;
+    }
+
+    cov  = func->vtype;
+    expr = (ast_expression*)cov;
+
+    if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) {
+        char ty[1024];
+        ast_type_to_string(expr, ty, sizeof(ty));
+        con_out("invalid type for coverage(): %s\n", ty);
+        ir_builder_delete(ir);
+        return false;
+    }
+
+    ir->coverage_func = func->ir_func->value;
+    return true;
+}
+
 bool parser_finish(parser_t *parser, const char *output)
 {
-    size_t i;
-    ir_builder *ir;
-    bool retval = true;
+    size_t          i;
+    ir_builder     *ir;
+    bool            retval = true;
 
     if (compile_errors) {
         con_out("*** there were compile errors\n");
@@ -6287,6 +6369,10 @@ bool parser_finish(parser_t *parser, const char *output)
     if (!fold_generate(parser->fold, ir))
         return false;
 
+    /* before generating any functions we need to set the coverage_func */
+    if (!parser_set_coverage_func(parser, ir))
+        return false;
+
     for (i = 0; i < vec_size(parser->globals); ++i) {
         ast_value *asvalue;
         if (!ast_istype(parser->globals[i], ast_value))