Skip to content

Miscompilation with noalias and pointer equality #59679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
carbotaniuman opened this issue Dec 23, 2022 · 6 comments
Open

Miscompilation with noalias and pointer equality #59679

carbotaniuman opened this issue Dec 23, 2022 · 6 comments

Comments

@carbotaniuman
Copy link

Reproducer

// test.c
#include <stdio.h>

int x;

__attribute__((noinline))
int test(int * restrict ptr) {
    *ptr = 1;
    if (ptr == &x) {
        *ptr = 2;
    }
    return *ptr;
}

int main() {
    printf("%d\n", test(&x));
}
$ clang -O3  test.c
$ ./a.out
1

Tested with clang 15.0.0 and trunk

The issue appears to be that Clang's constant propagation replaces *ptr = 2 with x = 2, which breaks the noalias based on analysis and allows the return *ptr to be simplified to return 1.

@nikic
Copy link
Contributor

nikic commented Dec 23, 2022

This general class of problem is known (see e.g. #34577), but this is a particularly nice example of the issue.

Did you encounter a real-world miscompile, or is this a constructed test case?

@LegionMammal978
Copy link

LegionMammal978 commented Dec 23, 2022

Did you encounter a real-world miscompile, or is this a constructed test case?

I stumbled upon this example while discussing the formal definition of restrict with u/flatfinger on Reddit. They noted that Clang unconditionally returns 1 from a similar function:

#include <stdint.h>
int x[1];
int test(int *restrict p, int i)
{
    int *q = p;
    p[i] = 1;
    if ((uintptr_t)p == (uintptr_t)x)
    {
        int *r = q;
        *r = 2;
    }
    return p[i];
}

Executing test(x, 0) arguably invokes UB under the current formal definition (since r is arguably not based on p), but I noticed that the proposed modifications to the restrict definition in N3025 and N3058 would require test(x, 0) to return 2.

So I tried simplifying the example to one that doesn't invoke UB under any definition, resulting in the version of test() posted in the reproducer above. Both GCC and Clang assume that *ptr = 2 does not affect the return value, even though ptr is clearly based on itself.

Finally, I discussed the example with @carbotaniuman and others, who found that this issue can also be reproduced with the Rust compiler, making this issue attributable to LLVM rather than the Clang frontend.

@carbotaniuman
Copy link
Author

carbotaniuman commented Dec 23, 2022 via email

@sfc-gh-rayu
Copy link

I think you should not expect a defined behavior for the code.
As ptr is defined as restrict, which means only ptr can access the memory x in function test().

You should not use x in function test(). Otherwise there will be an undefined behavior.

@LegionMammal978
Copy link

LegionMammal978 commented Sep 10, 2024

You should not use x in function test(). Otherwise there will be an undefined behavior.

The restrict qualifier only controls how the value of an object is accessed. In ISO C, "to access" is defined as "to read or modify the value of an object" (C99, C11, C17, C21 § 3.1). Taking the address of an object does not read or modify its value, since the lvalue operand of a unary & operator is never converted to its value (C99, C11, C17, C23 § 6.3.2.1 ¶ 2).

@sfc-gh-rayu
Copy link

@LegionMammal978 Thanks for your clarification.

I found some more explanations on this topic: https://www.open-std.org/jtc1/SC22/wg14/www/docs/n2260.pdf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants