Richard Biener <richard.guent...@gmail.com> writes: > On Thu, Apr 4, 2019 at 4:05 PM Vladislav Ivanishin <v...@ispras.ru> wrote: >> >> Richard Biener <richard.guent...@gmail.com> writes: >> >> > On Mon, Apr 1, 2019 at 5:36 PM Vladislav Ivanishin <v...@ispras.ru> wrote: >> >> >> >> Hi! >> >> >> >> This is a fairly trivial change fixing a false negative in >> >> -Wmaybe-uninitialized. I am pretty sure this is simply an overlooked >> >> case (is_value_included_in() is not meant to deal with the case where >> >> both compare codes are NE_EXPRs, neither does it need to, since their >> >> handling is trivial). >> >> >> >> In a nutshell, what happens is values of v restricted by (v != 2) are >> >> incorrectly considered a subset of values of v restricted by (v != 1). >> >> As if "v != 2, therefore v != 1". >> >> >> >> This is by no means a gcc-9 regression; I'll ping the patch once stage4 >> >> is over, if needed. >> >> >> >> This came up when I was experimenting with moving the uninit passes >> >> around. On mainline, the late uninit pass runs very late, so reliably >> >> triggering the affected path is a bit tricky. So I created a GIMPLE >> >> test (it reproduces the behavior precisely, but might be fragile >> >> w.r.t. future versions of the textual representation) and then with a >> >> hint from Alexander managed to produce a simple C test. [By the way, >> >> the first take was to insert an asm with a lot of newlines to prevent >> >> the dom pass from rewriting the CFG due to high cost of duplicating >> >> instructions. This didn't work out; I think the dom pass does not >> >> respect sizes of inline asms. I plan to create a testcase and file a >> >> bug later.] >> >> >> >> I ran regression testing on x86_64-pc-linux-gnu and saw no new >> >> regressions modulo a handful of flaky UNRESOLVED tests under >> >> gcc.dg/tree-prof. `BOOT_CFLAGS="-O -Wno-error=maybe-uninitialized >> >> -Wmaybe-uninitialized" bootstrap` also succeeded producing no new >> >> warnings. >> >> >> >> OK for stage1? >> > >> > Hum. While definitely two NE_EXPR do not work correctly I'd like >> > to see a positive test since LT_EXPR doesn't work either? >> >> Right, thanks. The case that was not covered well is actually when >> cond2 == NE_EXPR (arbitrary cond1). I created 2 new tests: uninit-26.c >> demonstrates a false negative, and uninit-27-gimple.c a false positive >> with trunk. >> >> > Specifically the code falls through to test is_value_included_in which >> > seems to assume code1 == code2. >> >> The function is_value_included_in itself only cares about one condition >> code (I'll expound on this below). I agree though that the second part >> of the comment seems to assume that. >> >> Please take a look at the updated patch. The rationale is as follows. >> >> When we have 2 potentially comparable predicates in >> is_pred_expr_subset_of, there are basically two things we want to check. >> >> 1) Whether two ranges with identical condition codes are nested. This >> is done straightforwardly with is_value_included_in. >> >> 2) Whether X CMPC VAL1 is nested in X != VAL2. Which is easy: this >> happens iff the set (a.k.a "range") {X: X CMPC VAL1 is true} doesn't >> contain ("cover") VAL2, in other words iff VAL2 CMPC VAL1 is false. >> >> Only, the logic of 2) is faulty when X CMPC VAL1 is not a range bounded >> on one end (this happens when, and only when CMPC is NE_EXPR; the range >> is then unbounded on both ends and can only be nested in X != VAL2, if >> VAL1 == VAL2). >> >> 3) Whether X != VAL1 is nested in X CMPC VAL2, but that is always false >> unless CMPC is NE_EXPR, which is already considered. > > OK. Your patch does > > + if (code2 == NE_EXPR && code1 == NE_EXPR) > + return false; > > but it should instead return operand_equal_p (expr1.pred_rhs, > expr2.pred_rhs, 0)?
This doesn't change the semantics, because the case with equal rhs's is already considered at the beginning of this function: static bool is_pred_expr_subset_of (pred_info expr1, pred_info expr2) { enum tree_code code1, code2; if (pred_equal_p (expr1, expr2)) return true; So I think, leaving this part of the patch as is results in less localized/explicit code, but better matches the overall style of this function. Or perhaps add a checking assert? if (code1 == code2) gcc_checking_assert (!operand_equal_p (expr1.pred_rhs, expr2.pred_rhs, 0)) >> > To me it looks like is_value_includeds comment should be clarified to >> > say >> > >> > /* Returns true if all values X satisfying X CMPC VAL satisfy >> > X CMPC BOUNDARY. */ >> >> This is indeed more clear than the current comment, and the meaning is >> the same. I suggest however to not discuss nestedness of ranges in this >> comment at all. >> >> > That is, "all values in the range" in the current comment is >> > under-specified since VAL is just a single value. >> >> The range is implied, since only one CMPC is passed. (If CMPC is >> NE_EXPR, however, then I guess the range would only be known in the >> caller, but the function does not work correctly for this purpose -- >> hence the patch to not call it then.) But is_value_included_in actually >> only cares about one value and one set anyway, as the name suggests. >> >> So I think the second part of the comment is supposed to help to think >> about applying this function for its main use-case (this function is >> used twice, actually: in the case we are discussing there is a range >> whose nestedness the calling code is testing, in the other case there is >> just a constant), whereas the first part simply states what the function >> does. I'd say, the second part of the comment should be rewritten or >> discarded, while the first should be kept. OTOH, it might have been >> helpful to the person who wrote this code. >> >> > So I wonder what testcases regress if we remove the && code2 != NE_EXPR >> > case? That way we see what the intention was. A patch should then >> > change that to >> > >> > if ((code1 != code2) >> > || !(<condition on code1> && code2 == NE_EXPR)) >> > return false; >> > >> > to explicitely spell out what case was meant here. >> >> make check-gcc RUNTESTFLAGS='dg.exp=uninit*' gives one regression: >> >> gcc.dg/uninit-pred-9_b.c bogus warning (test for bogus messages, line 24) >> >> This test boils down to this: >> >> if (m != 100) >> v = sth; >> ... >> if (m < 99) >> use (v); >> >> So with the code2 != NE_EXPR check in place, expr1 = {m, 98, LE_EXPR}, >> expr2 = {m, 100, NE_EXPR}, and code2 = NE_EXPR are passed to >> is_value_included_in, which returns true: 98 is included in m != 100 >> and true means "no warning". This does not clarify the intention for >> me, since this only works by luck; the condition that needs to be tested >> cannot be tested with passing NE_EXPR to is_value_included_in, as the >> new uninit-26.c test shows. > > The new patch is OK with the change suggested above and the new > comment for is_value_included_in spelling out how BOUNDARY and CMPC > form the domain, all x so that x CMPC BOUNDARY is true vs. the also > possible all x so that BOUNDARY CMPC x is true. I updated this to say /* Returns whether VAL CMPC BOUNDARY is true. */ As actually there is no need to define any "domain" entity here. If this looks good, I'll ping the patch when stage1 opens, or ask someone to apply. > Thanks for explaining and the extra testcases. > Richard. Thank you for reviewing. Vlad
gcc/ChangeLog: * tree-ssa-uninit.c (is_pred_expr_subset_of): Correctly handle cases where cond2 is NE_EXPR. (is_value_included_in): Update comment. gcc/testsuite/ChangeLog: * gcc.dg/uninit-25-gimple.c: New test. * gcc.dg/uninit-25.c: New test. * gcc.dg/uninit-26.c: New test. * gcc.dg/uninit-27-gimple.c: New test. --- gcc/testsuite/gcc.dg/uninit-25-gimple.c | 41 +++++++++++++++++++++++++ gcc/testsuite/gcc.dg/uninit-25.c | 23 ++++++++++++++ gcc/testsuite/gcc.dg/uninit-26.c | 23 ++++++++++++++ gcc/testsuite/gcc.dg/uninit-27-gimple.c | 41 +++++++++++++++++++++++++ gcc/tree-ssa-uninit.c | 11 +++++-- 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/uninit-25-gimple.c create mode 100644 gcc/testsuite/gcc.dg/uninit-25.c create mode 100644 gcc/testsuite/gcc.dg/uninit-26.c create mode 100644 gcc/testsuite/gcc.dg/uninit-27-gimple.c diff --git a/gcc/testsuite/gcc.dg/uninit-25-gimple.c b/gcc/testsuite/gcc.dg/uninit-25-gimple.c new file mode 100644 index 00000000000..0c0acd6b83e --- /dev/null +++ b/gcc/testsuite/gcc.dg/uninit-25-gimple.c @@ -0,0 +1,41 @@ +/* { dg-do compile } */ +/* { dg-options "-fgimple -O -Wmaybe-uninitialized" } */ + +unsigned int __GIMPLE (ssa,startwith("uninit1")) +foo (unsigned int v) +{ + unsigned int undef; /* { dg-warning "may be used uninitialized" } */ + unsigned int _2; + unsigned int _9; + unsigned int _10; + + __BB(2): + if (v_4(D) != 1u) + goto __BB3; + else + goto __BB4; + + __BB(3): + undef_8 = 8u; // 'undef' is defined conditionally (under 'v != 1' predicate) + goto __BB4; + + __BB(4): + // An undef value flows into a phi. + undef_1 = __PHI (__BB2: undef_5(D), __BB3: undef_8); + if (v_4(D) != 2u) // This condition is neither a superset nor a subset of 'v != 1'. + goto __BB5; + else + goto __BB6; + + __BB(5): + _9 = undef_1; // The phi value is used here (under 'v != 2' predicate). + goto __BB7; + + __BB(6): + _10 = v_4(D); + goto __BB7; + + __BB(7): + _2 = __PHI (__BB5: _9, __BB6: _10); + return _2; +} diff --git a/gcc/testsuite/gcc.dg/uninit-25.c b/gcc/testsuite/gcc.dg/uninit-25.c new file mode 100644 index 00000000000..ffffce3f91c --- /dev/null +++ b/gcc/testsuite/gcc.dg/uninit-25.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-O -Wmaybe-uninitialized" } */ + +extern unsigned bar (void); +extern void quux (void); + +unsigned foo (unsigned v) +{ + unsigned u; + if (v != 1) + u = bar (); + + // Prevent the "dom" pass from changing the CFG layout based on the inference + // 'if (v != 1) is false then (v != 2) is true'. (Now it would have to + // duplicate the loop in order to do so, which is deemed expensive.) + for (int i = 0; i < 10; i++) + quux (); + + if (v != 2) + return u; /* { dg-warning "may be used uninitialized" } */ + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/uninit-26.c b/gcc/testsuite/gcc.dg/uninit-26.c new file mode 100644 index 00000000000..60ac48cdc50 --- /dev/null +++ b/gcc/testsuite/gcc.dg/uninit-26.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-O -Wmaybe-uninitialized" } */ + +extern unsigned bar (void); +extern void quux (void); + +unsigned foo (unsigned v) +{ + unsigned u; + if (v != 100) + u = bar (); + + // Prevent the "dom" pass from changing the CFG layout based on the inference + // 'if (v != 100) is false then (v < 105) is true'. (Now it would have to + // duplicate the loop in order to do so, which is deemed expensive.) + for (int i = 0; i < 10; i++) + quux (); + + if (v < 105) /* v == 100 falls into this range. */ + return u; /* { dg-warning "may be used uninitialized" } */ + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/uninit-27-gimple.c b/gcc/testsuite/gcc.dg/uninit-27-gimple.c new file mode 100644 index 00000000000..f75469d8ef8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/uninit-27-gimple.c @@ -0,0 +1,41 @@ +/* { dg-do compile } */ +/* { dg-options "-fgimple -O -Wmaybe-uninitialized" } */ + +unsigned int __GIMPLE (ssa,startwith("uninit1")) +foo (unsigned int v) +{ + unsigned int undef; /* { dg-bogus "may be used uninitialized" } */ + unsigned int _2; + unsigned int _9; + unsigned int _10; + + __BB(2): + if (v_4(D) != 100u) + goto __BB3; + else + goto __BB4; + + __BB(3): + undef_8 = 8u; // 'undef' is defined conditionally (under 'v != 100' predicate) + goto __BB4; + + __BB(4): + // An undef value flows into a phi. + undef_1 = __PHI (__BB2: undef_5(D), __BB3: undef_8); + if (v_4(D) < 100u) + goto __BB5; + else + goto __BB6; + + __BB(5): + _9 = undef_1; // The phi value is used here (under 'v < 100' predicate). + goto __BB7; + + __BB(6): + _10 = v_4(D); + goto __BB7; + + __BB(7): + _2 = __PHI (__BB5: _9, __BB6: _10); + return _2; +} diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c index b263d0a3fb9..b65189d64c6 100644 --- a/gcc/tree-ssa-uninit.c +++ b/gcc/tree-ssa-uninit.c @@ -1011,8 +1011,7 @@ get_cmp_code (enum tree_code orig_cmp_code, bool swap_cond, bool invert) return tc; } -/* Returns true if VAL falls in the range defined by BOUNDARY and CMPC, i.e. - all values in the range satisfies (x CMPC BOUNDARY) == true. */ +/* Returns whether VAL CMPC BOUNDARY is true. */ static bool is_value_included_in (tree val, tree boundary, enum tree_code cmpc) @@ -1488,11 +1487,17 @@ is_pred_expr_subset_of (pred_info expr1, pred_info expr2) if (expr2.invert) code2 = invert_tree_comparison (code2, false); + if (code2 == NE_EXPR && code1 == NE_EXPR) + return false; + + if (code2 == NE_EXPR) + return !is_value_included_in (expr2.pred_rhs, expr1.pred_rhs, code1); + if ((code1 == EQ_EXPR || code1 == BIT_AND_EXPR) && code2 == BIT_AND_EXPR) return (wi::to_wide (expr1.pred_rhs) == (wi::to_wide (expr1.pred_rhs) & wi::to_wide (expr2.pred_rhs))); - if (code1 != code2 && code2 != NE_EXPR) + if (code1 != code2) return false; if (is_value_included_in (expr1.pred_rhs, expr2.pred_rhs, code2)) -- 2.20.1