llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.9k stars 11.51k forks source link

Wrong optimization: devirtualization vs. conditional equivalence #43817

Open llvmbot opened 4 years ago

llvmbot commented 4 years ago
Bugzilla Link 44472
Version trunk
OS Linux
Reporter LLVM Bugzilla Contributor
CC @dwblaikie

Extended Description

This is based on bug 34548, comment 49, and bug 44313. It seems there was an effort to fix the problem as it applies to devirtualization independently from the general case, so filing this separately. As mentioned in the referenced comment, GVN changes a pointer to an older one inside the if (it doesn't matter which pointer is on the left in the comparison and which one is on the right). Visible only with -fstrict-vtable-pointers.

Devirtualization seems not to be concerned with past-the-end pointers and with restrict, so the focus is on the case of a living object located at the same place where another, now dead, object was located, e.g.:

It seems comparison of naked pointers and casts to integers are handled. What I spotted missing:

Example with pointers to a class without virtual tables and a placement new:


include

include

struct B { void m() {} };

struct X : B { virtual void foo() { puts("foo"); } };

struct Y : B { virtual void bar() { puts("bar"); } };

static_assert(sizeof(X) == sizeof(Y));

int main() { B q = new Y; X p = new (q) X;

if (p == q)
    p->foo();

}

$ clang++ -std=c++2a -Wall -fstrict-vtable-pointers test.cc && ./a.out foo $ clang++ -std=c++2a -Wall -fstrict-vtable-pointers -O3 test.cc && ./a.out bar

clang x86-64 version: clang version 10.0.0 (https://github.com/llvm/llvm-project.git c5fb73c5d1b3f1adb77d99fc85c594b48bff08f9)

llvmbot commented 4 years ago

Example with memcmp and a placement new:


include

include

include

struct X { virtual void foo() { puts("foo"); } };

struct Y { virtual void bar() { puts("bar"); } };

static_assert(sizeof(X) == sizeof(Y));

int main() { Y q = new Y; X p = new (q) X;

if (memcmp(&p, &q, sizeof p) == 0)
    p->foo();

}

$ clang++ -std=c++2a -Wall -fstrict-vtable-pointers test.cc && ./a.out foo $ clang++ -std=c++2a -Wall -fstrict-vtable-pointers -O3 test.cc && ./a.out bar

clang x86-64 version: clang version 10.0.0 (https://github.com/llvm/llvm-project.git c5fb73c5d1b3f1adb77d99fc85c594b48bff08f9)

llvmbot commented 4 years ago

Example with memcpy and a dead dynamic variable:


include

include

include

struct X { virtual void foo() { puts("foo"); } };

struct Y { virtual void bar() { puts("bar"); } };

attribute((optnone)) // imagine it in a separate TU static void opaque(void p) { return p; }

int main() { unsigned long u, v;

Y *q = new Y;
opaque(q);
memcpy(&v, &q, sizeof v);
delete q;

X *p = new X;
opaque(p);
memcpy(&u, &p, sizeof u);

if (u == v)
    p->foo();

}

$ clang++ -std=c++2a -fstrict-vtable-pointers test.cc && ./a.out foo $ clang++ -std=c++2a -fstrict-vtable-pointers -O3 test.cc && ./a.out bar

clang x86-64 version: clang version 10.0.0 (https://github.com/llvm/llvm-project.git c5fb73c5d1b3f1adb77d99fc85c594b48bff08f9)

There is a similar example with memcpy and a dead local. Please let me know if it's worth adding it here.