Our Kea project uses boost::asio. When compiled with gcc 5.3.1 and -O2, the optimizer is incorrectly removing tests against a variable which can be changed from one of the boost::asio functions. 1. gcc --version output: Using built-in specs. COLLECT_GCC=/usr/bin/gcc COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/5.3.1/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux Thread model: posix gcc version 5.3.1 20151207 (Red Hat 5.3.1-2) (GCC) 2. OS info: Linux fedora23-64-1 4.3.4-300.fc23.x86_64 #1 SMP Mon Jan 25 13:39:23 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux 3. Compiler command line: g++ -DHAVE_CONFIG_H -I. -I../../../.. -I../../../../src/lib -I../../../../src/lib -DTEST_DATA_DIR=\"./testdata\" -I/opt/gtest/gtest-1.7.0 -I/opt/gtest/gtest-1.7.0/include -DOS_LINUX -I../../../../ext/coroutine -DBOOST_ASIO_HEADER_ONLY -DBOOST_ASIO_DISABLE_THREADS=1 -DBOOST_ERROR_CODE_HEADER_ONLY -DBOOST_SYSTEM_NO_DEPRECATED -Wall -Wextra -Wnon-virtual-dtor -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare -pthread -Werror -fPIC -Wno-missing-field-initializers -Wno-unused-parameter -g -O2 -save-temps -MT run_unittests-udp_socket_unittest.o -MD -MP -MF .deps/run_unittests-udp_socket_unittest.Tpo -c -o run_unittests-udp_socket_unittest.o `test -f 'udp_socket_unittest.cc' || echo './'`udp_socket_unittest.cc 4. I have attached the .ii file 5. BOOST Info: Version 1.58 The affected function is in the file: /usr/include/boost/asio/detail/impl/socket_ops.ipp and is excerpted below. The tests for ec against interrupted, would_block, and try_again are optimized out, cauing the function to always return true: bool non_blocking_recvfrom(socket_type s, buf* bufs, size_t count, int flags, socket_addr_type* addr, std::size_t* addrlen, boost::system::error_code& ec, size_t& bytes_transferred) { for (;;) { // Read some data. signed_size_type bytes = socket_ops::recvfrom( s, bufs, count, flags, addr, addrlen, ec); // Retry operation if interrupted by signal. if (ec == boost::asio::error::interrupted) continue; // Check if we need to run the operation again. if (ec == boost::asio::error::would_block || ec == boost::asio::error::try_again) return false; // Operation is complete. if (bytes >= 0) { ec = boost::system::error_code(); bytes_transferred = bytes; } else bytes_transferred = 0; return true; } }
Created attachment 37676 [details] compressed compilation temp file
This works in GCC 6 on aarch64 as far as I can tell.
After further analysis we've discovered that the optimizer does not remove the tests, it alters the comparison of (ec == enumerated value). If one replaces the "ec" with "ec.value()" in the expressions, such that you are explicitly comparing the interger error value the code works correctly when optimized: {{{ : // Retry operation if interrupted by signal. if (ec.value() == boost::asio::error::interrupted) continue; // Check if we need to run the operation again. if (ec.value() == boost::asio::error::would_block || ec == boost::asio::error::try_again) return false; : }}} Alternatively, adding an inline operator: {{{ inline friend bool operator==( const error_code & lhs, const int & rhs ) BOOST_SYSTEM_NOEXCEPT { return lhs.m_val == rhs; } }}} to error_code class also avoids the issue.
This issue also manifests itself on fully up-to-date Ubuntu 15.10, which has the following version g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010. Here's also a bug report that confirms this issue on Fedora 23: https://lists.isc.org/pipermail/kea-users/2016-February/000232.html, https://lists.isc.org/pipermail/kea-users/2016-February/000234.html The reported version there is: g++ (GCC) 5.3.1 20151207 (Red Hat 5.3.1-2)
A bit more digging reveals that in the logic expression which fails: {{{ // Check if we need to run the operation again. if (ec == boost::asio::error::would_block || ec == boost::asio::error::try_again) return false; }}} The value for error_category:m_cat differs between the implicitly created instances for would_block and try_again. If one splits the if into two separate if statements, the values for m_cat are the same. This looks a like the behavior described under "Static Variables in Inline Functions" described here: http://processors.wiki.ti.com/index.php/C++_Inlining_Issues
This issue manifests itself also on g++ 5.4.0, the default compiler in Ubuntu 16.04. Exact version: (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609.
(In reply to Thomas Markwalder from comment #5) > A bit more digging reveals that in the logic expression which fails: > > {{{ > // Check if we need to run the operation again. > if (ec == boost::asio::error::would_block > || ec == boost::asio::error::try_again) > return false; > }}} > > The value for error_category:m_cat differs between the implicitly created > instances for would_block and try_again. Do you mean that error_code(boost::asio::error::would_block) != error_code(boost::asio::error::try_again) ? I don't see how that's possible, since in the optimized GIMPLE dump they both use the same constructor, which isn't inlined. The two enums have the same value, and the constructor will call the same asio::make_error_code() overload that sets the category to boost::system_category(). boost::system::error_code::error_code<boost::asio::error::basic_errors> (&D.94931, 11, 0B); cleanup.88 = 1; D.165192 = boost::system::operator== (ec, &D.94931); if (D.165192 != 0) goto <D.165186>; else goto <D.165193>; <D.165193>: boost::system::error_code::error_code<boost::asio::error::basic_errors> (&D.94932, 11, 0B); cleanup.89 = 1; D.165197 = boost::system::operator== (ec, &D.94932); if (D.165197 != 0) goto <D.165186>; else goto <D.165187>; <D.165186>: iftmp.87 = 1; goto <D.165188>; <D.165187>: iftmp.87 = 0; <D.165188>: retval.86 = iftmp.87; So what exactly are you seeing? What digging have you done? Stepped through with the debugger? The most likely explanation seems to be that ec.category() != boost::system::system_category() and so the comparisons are always false. > If one splits the if into two separate if statements, the values for m_cat > are the same. This looks a like the behavior described under "Static > Variables in Inline Functions" described here: > > http://processors.wiki.ti.com/index.php/C++_Inlining_Issues This function has nothing to do with static variables.
(In reply to Jonathan Wakely from comment #7) > (In reply to Thomas Markwalder from comment #5) > > A bit more digging reveals that in the logic expression which fails: > > > > {{{ > > // Check if we need to run the operation again. > > if (ec == boost::asio::error::would_block > > || ec == boost::asio::error::try_again) > > return false; > > }}} > > > > The value for error_category:m_cat differs between the implicitly created > > instances for would_block and try_again. > > Do you mean that error_code(boost::asio::error::would_block) != > error_code(boost::asio::error::try_again) ? > > I don't see how that's possible, since in the optimized GIMPLE dump they > both use the same constructor, which isn't inlined. The two enums have the > same value, and the constructor will call the same asio::make_error_code() > overload that sets the category to boost::system_category(). > > > boost::system::error_code::error_code<boost::asio::error::basic_errors> > (&D.94931, 11, 0B); > cleanup.88 = 1; > D.165192 = boost::system::operator== (ec, &D.94931); > if (D.165192 != 0) goto <D.165186>; else goto <D.165193>; > <D.165193>: > > boost::system::error_code::error_code<boost::asio::error::basic_errors> > (&D.94932, 11, 0B); > cleanup.89 = 1; > D.165197 = boost::system::operator== (ec, &D.94932); > if (D.165197 != 0) goto <D.165186>; else goto <D.165187>; > <D.165186>: > iftmp.87 = 1; > goto <D.165188>; > <D.165187>: > iftmp.87 = 0; > <D.165188>: > retval.86 = iftmp.87; > > > So what exactly are you seeing? What digging have you done? Stepped through > with the debugger? > > The most likely explanation seems to be that ec.category() != > boost::system::system_category() and so the comparisons are always false. > > > > If one splits the if into two separate if statements, the values for m_cat > > are the same. This looks a like the behavior described under "Static > > Variables in Inline Functions" described here: > > > > http://processors.wiki.ti.com/index.php/C++_Inlining_Issues > > This function has nothing to do with static variables. Hello Jonathan: It has been some months since I looked at this so let me try to recap. I did do a great deal of stepping through with the debugger as well as instrumented the Boost code with couts in various places. The basic issue seems to be that when you build Boost such that BOOST_ERROR_CODE_HEADER_ONLY is defined, then the following function in <boost>/system/detail/error_code.ipp is declared as inline: : # ifdef BOOST_ERROR_CODE_HEADER_ONLY # define BOOST_SYSTEM_LINKAGE inline # else # define BOOST_SYSTEM_LINKAGE BOOST_SYSTEM_DECL # endif BOOST_SYSTEM_LINKAGE const error_category & system_category() BOOST_SYSTEM_NOEXCEPT { static const system_error_category system_category_const; return system_category_const; } : Apparently some combination of boost includes along with our own, and building with optimization, we end up with more than once instance of this function each with its own value for the static variable, system_error_category. Thus the value returned by invoking system_category() depends upon which instance is invoked. The result is the aforementioned if-clause: // Check if we need to run the operation again. if (ec == boost::asio::error::would_block || ec == boost::asio::error::try_again) return false; not evaluating to true even though it should. I confirmed with the debugger that ec.m_cat does not reliably equal would_block.m_cat or try_again.m_cat. If you split the clause into two separate statements it works reliably: // Check if we need to run the operation again. if (ec == boost::asio::error::would_block) return false; // Check if we need to run the operation again. if (ec == boost::asio::error::try_again) return false; Not necessarily meaningful, but noteworthy, and of course, I can't use that as a solution as that's within the boost code. If one compiles without optimization or one builds with Boost system library rather than with BOOST_ERROR_CODE_HEADER_ONLY defined, the issue disappears. I did try to replicate it with only boost code and could not do so. I think there is some corner case we are hitting in permits there to be more than one static instance of system_error_category.