https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97976

Peter Bisroev <peter at int19h dot net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
         Resolution|INVALID                     |---
             Status|RESOLVED                    |UNCONFIRMED

--- Comment #3 from Peter Bisroev <peter at int19h dot net> ---
Hi Andrew,

I was thinking about this a bit more and decided to try the loop in reverse in
a more simplified test case. I know this test case demonstrates a corner case
that no one will probably implement. However I still think it merits some
further investigation just in case this affects some other parts of the
optimizer. You can see this code below:

////////////////////
int containsBackwards(const uint8_t* p, uint8_t target)
{
    for (; p; --p)
    {
        if (*p == target)
        {
            return 1;
        }
    }
    return 0;
}

const uint8_t* findBackwards(const uint8_t* p, uint8_t target)
{
    for (; p; --p)
    {
        if (*p == target)
        {
            break;
        }
    }
    return p;
}

////////////////////

Function containsBackwards(), while searching backwards, should return 1 if
target byte is found and 0 if it was not and p points to address 0. Function
findBackwards() is similar but returns the address of the first byte that
matched the target or pointer to address 0 if a match was not found. Unless I
am mistaken, the sample code above is not hitting any undefined behavior such
as dereferencing a NULL pointer and there is a well defined loop terminating
condition.

This is the code that is generated with gcc trunk and gcc 9.1:
////////////////////
containsBackwards(unsigned char const*, unsigned char):
        xor     eax, eax
        test    rdi, rdi
        setne   al
        ret
findBackwards(unsigned char const*, unsigned char):
        test    rdi, rdi
        jne     .L5
        jmp     .L6
.L8:
        sub     rdi, 1
.L5:
        cmp     BYTE PTR [rdi], sil
        jne     .L8
        mov     rax, rdi
        ret
.L6:
        xor     eax, eax
        ret
////////////////////

I would have expected both functions to be compiled to nearly the same code,
but the looping is missing in containsBackwards() function. And unless I am
mistaken gcc 8.3 generates the output that we would expect to see here.

You can see this example in Compiler Explorer here:

https://godbolt.org/z/hWE4xs

What is also interesting, if we replace uint8_t by uint32_t in
containsBackwards() function it will work with gcc 9.3 but with gcc 10.1 it
will behave in exactly the same way as above returning the result based on the
validity of the p pointer.

Additionally, thinking about my first test case. I know it was technically in
the undefined territory, but just for my personal understanding, is the
compiler allowed to assume that pi can never become null like you have
suggested? In theory it can overflow and become 0 and the loop will terminate
but unfortunately I am not 100% certain of what the standard's view on this is.
In addition, can the compiler assume that callback(*pi) will never return true?

What are your thoughts?

Thank you!
--peter

Reply via email to