https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92926
Bug ID: 92926 Summary: Wrong code generated because of shared tree node in gimplify Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: amker at gcc dot gnu.org Target Milestone: --- Following code is reduced from cppcoro but is irrelevant to coroutine. #include <cassert> #include <string> class ipv6_address { public: constexpr ipv6_address( std::uint16_t part0, std::uint16_t part1, std::uint16_t part2, std::uint16_t part3, std::uint16_t part4, std::uint16_t part5, std::uint16_t part6, std::uint16_t part7); static constexpr ipv6_address loopback(); std::string to_string() const; private: alignas(std::uint64_t) std::uint8_t m_bytes[16]; }; constexpr ipv6_address::ipv6_address( std::uint16_t part0, std::uint16_t part1, std::uint16_t part2, std::uint16_t part3, std::uint16_t part4, std::uint16_t part5, std::uint16_t part6, std::uint16_t part7) : m_bytes{ static_cast<std::uint8_t>(part0 >> 8), static_cast<std::uint8_t>(part0), static_cast<std::uint8_t>(part1 >> 8), static_cast<std::uint8_t>(part1), static_cast<std::uint8_t>(part2 >> 8), static_cast<std::uint8_t>(part2), static_cast<std::uint8_t>(part3 >> 8), static_cast<std::uint8_t>(part3), static_cast<std::uint8_t>(part4 >> 8), static_cast<std::uint8_t>(part4), static_cast<std::uint8_t>(part5 >> 8), static_cast<std::uint8_t>(part5), static_cast<std::uint8_t>(part6 >> 8), static_cast<std::uint8_t>(part6), static_cast<std::uint8_t>(part7 >> 8), static_cast<std::uint8_t>(part7) } {} constexpr ipv6_address ipv6_address::loopback() { return ipv6_address{ 0, 0, 0, 0, 0, 0, 0, 1 }; } char hex_char(std::uint8_t value) { return value < 10 ? static_cast<char>('0' + value) : static_cast<char>('a' + value - 10); } std::string ipv6_address::to_string() const { std::uint32_t longestZeroRunStart = 0; std::uint32_t longestZeroRunLength = 0; for (std::uint32_t i = 0; i < 8; ) { if (m_bytes[2 * i] == 0 && m_bytes[2 * i + 1] == 0) { const std::uint32_t zeroRunStart = i; ++i; while (i < 8 && m_bytes[2 * i] == 0 && m_bytes[2 * i + 1] == 0) { ++i; } std::uint32_t zeroRunLength = i - zeroRunStart; if (zeroRunLength > longestZeroRunLength) { longestZeroRunLength = zeroRunLength; longestZeroRunStart = zeroRunStart; } } else { ++i; } } char buffer[40]; char* c = &buffer[0]; auto appendPart = [&](std::uint32_t index) { const std::uint8_t highByte = m_bytes[index * 2]; const std::uint8_t lowByte = m_bytes[index * 2 + 1]; if (highByte > 0 || lowByte > 15) { if (highByte > 0) { if (highByte > 15) { *c++ = hex_char(highByte >> 4); } *c++ = hex_char(highByte & 0xF); } *c++ = hex_char(lowByte >> 4); } *c++ = hex_char(lowByte & 0xF); }; if (longestZeroRunLength >= 2) { for (std::uint32_t i = 0; i < longestZeroRunStart; ++i) { if (i > 0) { *c++ = ':'; } appendPart(i); } *c++ = ':'; *c++ = ':'; for (std::uint32_t i = longestZeroRunStart + longestZeroRunLength; i < 8; ++i) { appendPart(i); if (i < 7) { *c++ = ':'; } } } else { appendPart(0); for (std::uint32_t i = 1; i < 8; ++i) { *c++ = ':'; appendPart(i); } } assert((c - &buffer[0]) <= sizeof(buffer)); return std::string{ &buffer[0], c }; } std::string __attribute__((noinline)) foo () { return ipv6_address::loopback().to_string(); } ipv6_address __attribute__((noinline)) bar () { return ipv6_address::loopback(); } int main() { std::string s = foo (); ipv6_address a = bar (); assert(a.to_string() == s); return 0; } Compiling using following command line: $ ./g++ -std=c++17 -m64 -O3 z.cc -o a.out $ ./a.out a.out: z.cc:168: int main(): Assertion `a.to_string() == s' failed. Aborted Root cause is ipv6_address::loopback as a constexpr function, what it returns is folded into const ctor by C++ FE, also the const ctor is shared translation unit wide by constexpr_call_table. As a result the ctor as well as its vector elements are shared between foo and bar. In gimplify, CONSTRUCTOR_ELTS is optimized and cleared, causing shared node changed in the other function. Will send a patch for discussion.