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.

Reply via email to