From: Wolfgang Bumiller Date: Tue, 7 Jan 2014 13:33:26 +0000 (+0100) Subject: Adding coverage support: X-Git-Tag: xonotic-v0.8.0~19 X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=commitdiff_plain;h=4ff68e07e8cc5980c3f4be880d0e81f0bdba5e7c Adding coverage support: 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. --- diff --git a/ast.c b/ast.c index 1f793a1..d7e3d7a 100644 --- 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 2c83e41..7cc7a6c 100644 --- 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 65bed5e..7df8bdb 100644 --- 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 006a087..5b94835 100644 --- 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 a5e915a..156a3f0 100644 --- a/main.c +++ b/main.c @@ -85,6 +85,7 @@ static int usage(void) { " -Ono- 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 */ diff --git a/opts.def b/opts.def index ea391eb..b229245 100644 --- a/opts.def +++ b/opts.def @@ -134,6 +134,7 @@ 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 */ diff --git a/parser.c b/parser.c index 8af91e2..956d88d 100644 --- 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))