https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97273
Bug ID: 97273 Summary: Strange behaviour of unordered_set when vector is included (i686) Product: gcc Version: 10.2.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: rascmoo at gmail dot com Target Milestone: --- Created attachment 49302 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=49302&action=edit A few simple source files demonstrating the problem, plus a .ii file generated on my system Hello, I have recently been diagnosing a fault with the 32-bit build of an application, and this has led me to find some strange behaviour with unordered_set, when compiling for 32-bit x86 (i686). I have included an example which demonstrates the behaviour. Consider the following small program: #include <unordered_set> #include <cstdint> uint64_t getBegin(const std::unordered_set<uint64_t> & set) { return *set.begin(); } It returns the first value in a uint64_t unordered_set. This can be compiled into a shared library with the following command: g++ -Wall -Wextra -fpic -shared -Os Working.cpp -o libworking.so This works as intended on all 32 and 64 bit systems which I have tested it on, and on all optimisation levels. However, consider the next small program: #include <vector> #include <unordered_set> #include <cstdint> uint64_t getBegin(const std::unordered_set<uint64_t> & set) { return *set.begin(); } The only change is the addition of a "#include <vector>". This ought not to have any impact on the program, and it ought to work exactly the same. It can also be compiled into a shared library with the following command: g++ -Wall -Wextra -fpic -shared -Os Broken.cpp -o libbroken.so Indeed, when compiled on a 64 bit x86 system there is no difference - both functions work as intended. However, when compiled either natively on a 32-bit x86 system, or cross compiled to 32-bit using the "-m32" flag, "*set.begin()" returns an incorrect value. I tested both of these with the following simple test program: #include <unordered_set> #include <iostream> uint64_t getBegin(const std::unordered_set<uint64_t> & set); int main(void) { std::unordered_set<uint64_t> set({1, 2, 3, 4, 5, 6, 7}); std::cout << getBegin(set) << "\n"; } Which can be compiled using the following commands (linking to either working or broken versions respectively): g++ -Wall -Wextra -Os test.cpp -L. -lworking -o test g++ -Wall -Wextra -Os test.cpp -L. -lbroken -o test Both versions work correctly when compiled and running natively on a 64-bit x86 system, (they print "7"). But, when when either compiled with "-m32" on a 64-bit system, or compiled and running natively on a 32-bit x86 system, the second (broken) variant prints "30064771072". This is certainly not one of the numbers in the unordered_set. I have reproduced the problem across several different 32-bit and 64-bit systems. These are the versions reported by g++ --version, on the systems which I have tried: Debian 10 (stable): g++ (Debian 8.3.0-6) 8.3.0 Debian 11 (testing): g++ (Debian 10.2.0-9) 10.2.0 Fedora 32 (Workstation Edition): g++ (GCC) 10.2.1 20200723 (Red Hat 10.2.1-1) Some source files and a simple makefile, which can reproduce the problem, are attached. I have also included the .ii file which is produced when running the following command, on the broken variant of the function: g++ -save-temps -m32 -Wall -Wextra -fpic -shared -Os Broken.cpp -o libbroken.so Other things which I have noticed: > The problem happens with optimisation levels O1, O2, O3, Os and Ofast, but does not occur with O0. > The problem occurs whenever vector is included BEFORE unordered_set. If vector is included after unordered_set, behaviour is correct. > When broken, the memory address pointed to by the iterator returned by begin(), seems to always be wrong by 4 bytes, (4 bytes before the address which it should be). > When I compared the assembly output of the two functions, the offsets in three movl instructions differ - see below. I assume this is causing the wrong address to be returned. Working version: movl 8(%eax), %eax # MEM[(struct _Hash_node_base * *)set_2(D) + 8B], MEM[(struct _Hash_node_base * *)set_2(D) + 8B] movl 12(%eax), %edx # MEM[(const long long unsigned int &)_4 + 8], MEM[(const long long unsigned int &)_4 + 8] movl 8(%eax), %eax # MEM[(const long long unsigned int &)_4 + 8], MEM[(const long long unsigned int &)_4 + 8] Broken version: movl 8(%eax), %eax # MEM[(struct _Hash_node_base * *)set_2(D) + 8B], MEM[(struct _Hash_node_base * *)set_2(D) + 8B] movl 8(%eax), %edx # MEM[(const long long unsigned int &)_4 + 4], MEM[(const long long unsigned int &)_4 + 4] movl 4(%eax), %eax # MEM[(const long long unsigned int &)_4 + 4], MEM[(const long long unsigned int &)_4 + 4] If there is any more information about this problem which you require, then please let me know. Thank you very much, Dan Wilson.