This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

optimiser bug dealing with assert(), v6.1.19



hi there

it seems like the optimiser removes assert() statements which it clearly shouldn't. assume the following file main.cpp:

#include <cassert>

int main(int argc, char** argv)
{
  assert((argc & 1 ) == 0);
  assert((argc & 2 ) == 0);
  assert((argc & 4 ) == 0);
  return argc;
}

compiled with no optimisation, all the asserts end up in the object file:

$ cl6x -O0 main.cpp
$ strings main.obj | grep Assertion
Assertion failed, ((argc & 1 ) == 0), file main.cpp, line 5
Assertion failed, ((argc & 2 ) == 0), file main.cpp, line 6
Assertion failed, ((argc & 4 ) == 0), file main.cpp, line 7

with optimisation, one of the asserts is removed:
$ cl6x -O3 main.cpp
$ strings main.obj | grep Assertion
Assertion failed, ((argc & 2 ) == 0), file main.cpp, line 6
Assertion failed, ((argc & 4 ) == 0), file main.cpp, line 7
my compiler version is v6.1.19 for linux
cheers,
sam

  • Unfortunately, you are correct.  Thank you for the well defined test case.  I filed SDSCM00041434 in the SDOWP system to have this fixed.  Feel free to track it with the SDOWP link in my sig below.

    Thanks and regards,

    -George

  • I ran into similar issues with 7.0.5. I'm just surprised that this is flagged as minor in the incident report, as it shows that the compiler randomly optimizes code out that it shouldn't, without even a warning. There is also no work around shown in the incident report. Is TI stating that we will only get correct code without any optimization?

  • It's minor because it affects only assertions, and only this specific assertion (and its equivalent, assert(x%2 == 0)), and because the effect of omitting an assertion is that the program will not abort as early when given incorrect input.

    We know what the problem is, but have not yet figured out how to address it efficiently.

    One workaround is to do "if (!((x&1) == 0)) assert(0);" instead, though it won't produce the same error message when failing.

  • Thanks for the reply. Would this work around work, assert((int)Ptr & 0x3 != 0)? I'm asserting for double word alignment.

    Dieter

  • no, because

    a) to assert for double word alignment you would want to do assert((int)Ptr & 0x7 == 0);

    b) since this type of assert potentially causes the bug to occur, you'd have to write if (!(((int)Ptr & 0x7) == 0)) assert(0);

    sam

     

    to clarify my terminology (YMMV):

    double word: 8 bytes

    word: 4 bytes

    half-word: 2 bytes

  •  

    Thanks Samuel for your reply. I'm still concerned with the fact that the assert is optimized out with -O2. I do not quite follow why this would just affect assertions.  

    "It's minor because it affects only assertions, and only this specific assertion (and its equivalent, assert(x%2 == 0)), and because the effect of omitting an assertion is that the program will not abort as early when given incorrect input."

    This statement would only be correct if the optimization invokes different code for assert (i.e. like a different macro) and that the optimized code has a known bug. Otherwise it does not make sense. The same code should not create this side effect. If so, it is quite possible that other code sections could have the same code sequence as the assert and would be optimized out. I agree in the context of assert this is minor, but in the context of code optimization it is not. Please confirm.

    Dieter

     

  • An assertion does two things:  it tests that a condition is true, and it implicitly informs the compiler that the condition is true -- if the condition were false, the program would exit with an error message, and a correct program should not do that.

    (See _nassert, which exists to do the informing without the checking.)

    When the compiler knows that a condition is true, it can take advantage of it to improve the code it produces.  The nature of the bug in this case is that the compiler sees the x&1 assertion, notes that x&1 is therefore true, and then uses that information to simplify the assert().  Since it already knows that x&1 is true, it determines that the assert() is redundant and removes it.  (Yes, that's circular logic.  That's part of what's wrong.)  Thus the problem only affects uses of assert(), and only a subset of the conditions that the compiler knows how to exploit.

    As for your other question, asserting for pointer alignment, I can tell you that our test loop kernels are littered with _nasserts that inform the compiler  usefully about pointer alignment, but I haven't investigated whether pointer-alignment assert() calls are removed by the same flawed mechanism as x&1.

     

  • Dear pf,

    Mohsen Khayami (TI FAE) asked me to post this question.

    I do not follow your logic. In the example provided by Samuel the argument is coming from outside the scope of the program. How could the compiler make the assumption that the condition is true?

    "The nature of the bug in this case is that the compiler sees the x&1 assertion, notes that x&1 is therefore true, and then uses that information to simplify the assert()"

    How can this problem be only limited to assert? Typical assert code would do the following:

    #ifdef NDEBUG
     #define assert(expr)    ((void)0)
     
    #else
    void
    m_assert(char *expr, char *file, unsigned line)
    {
        fprintf(stderr,"Assertion failed: [%s], file %s, line %d\n", expr, file, line);
        abort();
    }

    #define assert(expr) (void)( (expr) || (m_assert(#expr, __FILE__, __LINE__), 0) )
    #endif /* NDEBUG */

    So, how can the assertion implicitly tell the compiler that the condition is true? We are worried that there may be other code sequences where the compiler would do this incorrect assumption.

    Dieter

  • The statement "assert((x&1) == 0);" will abort if the condition is not true.  Therefore, if execution reaches a point after the statement, the condition is true.  *Any* execution after the assertion statement must, by definition, operate in a world in which the condition is true.

    In the example given, the assertion is made by the "assert" macro defined in the system include file <cassert>, which expands into a special symbol known by the compiler.  Your example, using m_assert(), does not use that known symbol and thus would not be recognised specially by the compiler.

    Thus, the compiler recognises the assertion because it knows the symbol used, and it assumes that the assertion is true because that's how assertions work.  The bug is specifically that it uses knowledge derived from recognising assert() statements to simplify the assert() statements themselves.  That's how I know the problem is limited to assert().

    If you implemented your own assert(), I would not expect you to see the problem.  Neither would I expect the compiler to take any advantage of the assertions.