From 31c7ea0165882eaf09040ea7edfc37cc38f0a032 Mon Sep 17 00:00:00 2001 From: Michael Matz Date: Mon, 19 Sep 2016 18:38:12 +0200 Subject: [PATCH] opt: Start optimizing dead code a bit If a condition is always zero/non-zero we can omit the then or else code. This is complicated a bit by having to deal with labels that might make such code reachable without us yet knowing during parsing. --- tccgen.c | 17 ++++- tests/tcctest.c | 27 +++++++ tests/tests2/87_dead_code.c | 122 +++++++++++++++++++++++++++++++ tests/tests2/87_dead_code.expect | 18 +++++ 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 tests/tests2/87_dead_code.c create mode 100644 tests/tests2/87_dead_code.expect diff --git a/tccgen.c b/tccgen.c index a811d11..8b1bcb1 100644 --- a/tccgen.c +++ b/tccgen.c @@ -5367,7 +5367,7 @@ static int gcase(struct case_t **base, int len, int case_reg, int *bsym) static void block(int *bsym, int *csym, int is_expr) { - int a, b, c, d; + int a, b, c, d, cond; Sym *s; /* generate line number info */ @@ -5386,22 +5386,31 @@ static void block(int *bsym, int *csym, int is_expr) if (tok == TOK_IF) { /* if test */ + int saved_nocode_wanted = nocode_wanted; next(); skip('('); gexpr(); skip(')'); - a = gvtst(1, 0); + cond = condition_3way(); + if (cond == 0) + nocode_wanted |= 2; + a = gvtst(1, 0); block(bsym, csym, 0); + nocode_wanted = saved_nocode_wanted; c = tok; if (c == TOK_ELSE) { next(); + if (cond == 1) + nocode_wanted |= 2; d = gjmp(0); gsym(a); block(bsym, csym, 0); gsym(d); /* patch else jmp */ + nocode_wanted = saved_nocode_wanted; } else gsym(a); } else if (tok == TOK_WHILE) { + nocode_wanted &= ~2; next(); d = ind; vla_sp_restore(); @@ -5564,6 +5573,7 @@ static void block(int *bsym, int *csym, int is_expr) skip(';'); } else if (tok == TOK_FOR) { int e; + nocode_wanted &= ~2; next(); skip('('); s = local_stack; @@ -5607,6 +5617,7 @@ static void block(int *bsym, int *csym, int is_expr) } else if (tok == TOK_DO) { + nocode_wanted &= ~2; next(); a = 0; b = 0; @@ -5658,6 +5669,7 @@ static void block(int *bsym, int *csym, int is_expr) struct case_t *cr = tcc_malloc(sizeof(struct case_t)); if (!cur_switch) expect("switch"); + nocode_wanted &= ~2; next(); cr->v1 = cr->v2 = expr_const(); if (gnu_ext && tok == TOK_DOTS) { @@ -5735,6 +5747,7 @@ static void block(int *bsym, int *csym, int is_expr) vla_sp_restore(); /* we accept this, but it is a mistake */ block_after_label: + nocode_wanted &= ~2; if (tok == '}') { tcc_warning("deprecated use of label at end of compound statement"); } else { diff --git a/tests/tcctest.c b/tests/tcctest.c index d1f6c9b..8031af4 100644 --- a/tests/tcctest.c +++ b/tests/tcctest.c @@ -88,6 +88,7 @@ void struct_test(); void array_test(); void expr_ptr_test(); void bool_test(); +void optimize_out(); void expr2_test(); void constant_expr_test(); void expr_cmp_test(); @@ -695,6 +696,7 @@ int main(int argc, char **argv) array_test(); expr_ptr_test(); bool_test(); + optimize_out(); expr2_test(); constant_expr_test(); expr_cmp_test(); @@ -1165,6 +1167,31 @@ void bool_test() printf ("bits = 0x%x\n", calc_vm_flags (0x1)); } +extern int undefined_function(void); +extern int defined_function(void); + +void optimize_out(void) +{ + int i = 0 ? undefined_function() : defined_function(); + printf ("oo:%d\n", i); + int j = 1 ? defined_function() : undefined_function(); + printf ("oo:%d\n", j); + if (0) + printf("oo:%d\n", undefined_function()); + else + printf("oo:%d\n", defined_function()); + if (1) + printf("oo:%d\n", defined_function()); + else + printf("oo:%d\n", undefined_function()); +} + +int defined_function(void) +{ + static int i = 40; + return i++; +} + /* GCC accepts that */ static int tab_reinit[]; static int tab_reinit[10]; diff --git a/tests/tests2/87_dead_code.c b/tests/tests2/87_dead_code.c new file mode 100644 index 0000000..92983f5 --- /dev/null +++ b/tests/tests2/87_dead_code.c @@ -0,0 +1,122 @@ +/* This checks various ways of dead code inside if statements + where there are non-obvious ways of how the code is actually + not dead due to reachable by labels. */ +extern int printf (const char *, ...); +static void kb_wait_1(void) +{ + unsigned long timeout = 2; + do { + /* Here the else arm is a statement expression that's supposed + to be suppressed. The label inside the while would unsuppress + code generation again if not handled correctly. And that + would wreak havok to the cond-expression because there's no + jump-around emitted, the whole statement expression really + needs to not generate code (perhaps except useless forward jumps). */ + (1 ? + printf("timeout=%ld\n", timeout) : + ({ + int i = 1; + while (1) + while (i--) + some_label: + printf("error\n"); + goto some_label; + }) + ); + timeout--; + } while (timeout); +} +int main (void) +{ + int i = 1; + kb_wait_1(); + + /* Simple test of dead code at first sight which isn't actually dead. */ + if (0) { +yeah: + printf ("yeah\n"); + } else { + printf ("boo\n"); + } + if (i--) + goto yeah; + + /* Some more non-obvious uses where the problems are loops, so that even + the first loop statements aren't actually dead. */ + i = 1; + if (0) { + while (i--) { + printf ("once\n"); +enterloop: + printf ("twice\n"); + } + } + if (i >= 0) + goto enterloop; + + /* The same with statement expressions. One might be tempted to + handle them specially by counting if inside statement exprs and + not unsuppressing code at loops at all then. + See kb_wait_1 for the other side of the medal where that wouldn't work. */ + i = ({ + int j = 1; + if (0) { + while (j--) { + printf ("SEonce\n"); + enterexprloop: + printf ("SEtwice\n"); + } + } + if (j >= 0) + goto enterexprloop; + j; }); + + /* The other two loop forms: */ + i = 1; + if (0) { + for (i = 1; i--;) { + printf ("once2\n"); +enterloop2: + printf ("twice2\n"); + } + } + if (i > 0) + goto enterloop2; + + i = 1; + if (0) { + do { + printf ("once3\n"); +enterloop3: + printf ("twice3\n"); + } while (i--); + } + if (i > 0) + goto enterloop3; + + /* And check that case and default labels have the same effect + of disabling code suppression. */ + i = 41; + switch (i) { + if (0) { + printf ("error\n"); + case 42: + printf ("error2\n"); + case 41: + printf ("caseok\n"); + } + } + + i = 41; + switch (i) { + if (0) { + printf ("error3\n"); + default: + printf ("caseok2\n"); + break; + case 42: + printf ("error4\n"); + } + } + return 0; +} diff --git a/tests/tests2/87_dead_code.expect b/tests/tests2/87_dead_code.expect new file mode 100644 index 0000000..0b3ec1d --- /dev/null +++ b/tests/tests2/87_dead_code.expect @@ -0,0 +1,18 @@ +timeout=2 +timeout=1 +boo +yeah +twice +once +twice +SEtwice +SEonce +SEtwice +twice2 +once2 +twice2 +twice3 +once3 +twice3 +caseok +caseok2