mirror of
https://github.com/DarkPlacesEngine/gmqcc.git
synced 2025-02-20 18:32:01 +00:00
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.
This commit is contained in:
parent
5a160b0e88
commit
4ff68e07e8
7 changed files with 133 additions and 19 deletions
6
ast.c
6
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;
|
||||
}
|
||||
|
|
40
ast.h
40
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,
|
||||
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 []
|
||||
/* 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 {
|
||||
|
|
6
ir.c
6
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;
|
||||
}
|
||||
|
||||
|
|
2
ir.h
2
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.
|
||||
*/
|
||||
|
|
5
main.c
5
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 */
|
||||
|
|
1
opts.def
1
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 */
|
||||
|
|
92
parser.c
92
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))
|
||||
|
|
Loading…
Reference in a new issue