Hi,

When trying to build r17-2056-g44e33b2d621d6d, I get the following compilation 
error:

libtool: compile: /build/r17-2056-g44e33b2d621d6dgcc-final/./gcc/xgcc -shared-libgcc -B/build/r17-2056-g44e33b2d621d6dgcc-final/./gcc -nostdinc++ -L/build/r17-2056-g44e33b2d621d6dgcc-final/arm-none-eabi/thumb/v7/nofp/libstdc++-v3/src -L/build/r17-2056-g44e33b2d621d6dgcc-final/arm-none-eabi/thumb/v7/nofp/libstdc++-v3/src/.libs -L/build/r17-2056-g44e33b2d621d6dgcc-final/arm-none-eabi/thumb/v7/nofp/libstdc++-v3/libsupc++/.libs -B/build/r17-2056-g44e33b2d621d6d/arm-none-eabi/bin/ -B/build/r17-2056-g44e33b2d621d6d/arm-none-eabi/lib/thumb/v7/nofp -B/build/r17-2056-g44e33b2d621d6d/arm-none-eabi/lib/ -isystem /build/r17-2056-g44e33b2d621d6d/arm-none-eabi/include -isystem /build/r17-2056-g44e33b2d621d6d/arm-none-eabi/sys-include -mthumb -march=armv7 -mfloat-abi=soft -I/build/gcc_src/libstdc++-v3/../libgcc -I/build/r17-2056-g44e33b2d621d6dgcc-final/arm-none-eabi/thumb/v7/nofp/libstdc++-v3/include/arm-none-eabi -I/build/r17-2056-g44e33b2d621d6dgcc-final/arm-none-eabi/thumb/v7/nofp/libstdc++-v3/include -I/build/gcc_src/libstdc++-v3/libsupc++ -std=gnu++20 -fno-implicit-templates -Wall -Wextra -Wwrite-strings -Wcast-qual -Wabi=19 -fdiagnostics-show-location=once -ffunction-sections -fdata-sections -frandom-seed=tzdb.lo -fimplicit-templates -g -O2 -mthumb -march=armv7 -mfloat-abi=soft -c /build/gcc_src/libstdc++-v3/src/c++20/tzdb.cc -o tzdb.o
...
/build/gcc_src/libstdc++-v3/src/c++20/tzdb.cc: In static member function 'static const 
std::chrono::tzdb& 
std::chrono::tzdb_list::_Node::_S_replace_head(std::shared_ptr<std::chrono::tzdb_list::_Node>,
 std::shared_ptr<std::chrono::tzdb_list::_Node>)':
/build/gcc_src/libstdc++-v3/src/c++20/tzdb.cc:1617:22: error: 'struct 
std::chrono::tzdb_list::_Node::NumLeapSeconds' has no member named 'set_locked'
 1617 |     num_leap_seconds.set_locked(new_head_ptr->db.leap_seconds.size(), 
lock);
      |                      ^~~~~~~~~~
Makefile:575: recipe for target 'tzdb.lo' failed
make[9]: *** [tzdb.lo] Error 1
make[9]: *** Waiting for unfinished jobs....


I am configured GCC with the following options:

/build/gcc_src/configure \
        --target=arm-none-eabi \
        --prefix=/build/r17-2056-g44e33b2d621d6d \
        --libexecdir=/build/r17-2056-g44e33b2d621d6d/lib \
        
--infodir=/build/r17-2056-g44e33b2d621d6d/share/doc/gcc-arm-none-eabi/info \
        
--mandir=/build/r17-2056-g44e33b2d621d6d/share/doc/gcc-arm-none-eabi/man \
        
--htmldir=/build/r17-2056-g44e33b2d621d6d/share/doc/gcc-arm-none-eabi/html \
        
--pdfdir=/build/r17-2056-g44e33b2d621d6d/share/doc/gcc-arm-none-eabi/pdf \
        --enable-checking=release \
        --enable-languages=c,c++ \
        --enable-plugins \
        --disable-decimal-float \
        --disable-libffi \
        --disable-libgomp \
        --disable-libmudflap \
        --disable-libquadmath \
        --disable-libssp \
        --disable-libstdcxx-pch \
        --disable-nls \
        --disable-shared \
        --disable-threads \
        --disable-tls \
        --with-gnu-as \
        --with-gnu-ld \
        --with-newlib \
        --with-headers=yes \
        --with-python-dir=share/gcc-arm-none-eabi \
        --with-sysroot=/build/r17-2056-g44e33b2d621d6d/arm-none-eabi \
        --with-zstd=no \
        --build=x86_64-linux-gnu \
        --host=x86_64-linux-gnu \
        --with-gmp=/build/host-libs/usr \
        --with-mpfr=/build/host-libs/usr \
        --with-mpc=/build/host-libs/usr \
        --with-isl=/build/host-libs/usr \
        '--with-host-libstdcxx=-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic 
-lm' \
        '--with-pkgversion=r17-2056-g44e33b2d621d6d' \
        --with-multilib-list=rmprofile,aprofile


On 2026-06-30 19:41, Jonathan Wakely wrote:
My r17-471-ge79f0f818c0e42 change to optimize handling of leap seconds
introduced a hard dependency on std::atomic<unsigned>, which causes
problems for targets without atomic word operations, like Cortex-M0.:
https://gcc.gnu.org/pipermail/gcc-patches/2026-June/719704.html

This patch replaces the num_leap_seconds variable with a struct which
decides whether to use std::atomic_ref<unsigned> or perform all accesses
while holding a lock on the pre-existing mutex used for the tzdb_list
singleton.

The workaround is a bit ugly, because it assumes that there is only one
caller of num_leap_seconds.set and that the list_mutex() is locked by
that caller iff the tzdb_list doesn't use atomic<shared_ptr<>>. To make
the assumption explicit, there are two different functions used to
update the value, depending on whether the mutex is used or not.

libstdc++-v3/ChangeLog:

        * src/c++20/tzdb.cc (_Node::NumLeapSeconds): New class.
        (_Node::num_leap_seconds): New static variable.
        (num_leap_seconds): Remove.
        (__detail::__recent_leap_second_info): Replace uses of
        num_leap_seconds with _Node::num_leap_seconds.
        (_Node::_S_replace_head): Likewise.
---

v2: Replace NumLeapSeconds::set by two members, set_atomically and
set_locked.

Tested x86_64-linux.

  libstdc++-v3/src/c++20/tzdb.cc | 119 ++++++++++++++++++++++++++-------
  1 file changed, 94 insertions(+), 25 deletions(-)

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 5793155b6d89..9e601fc176f3 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -67,6 +67,7 @@
  #endif
#if USE_ATOMIC_SHARED_PTR && ! USE_ATOMIC_LIST_HEAD
+// Cannot use atomic<shared_ptr<T>> without lock-free atomic<T*>.
  # error Unsupported combination
  #endif
@@ -200,6 +201,10 @@ namespace std::chrono // This is here because _Node is a friend so can call private constructor.
      static const leap_second fixed_leaps[];
+
+    // This is a member so that it can access fixed_leaps.
+    struct NumLeapSeconds;
+    static NumLeapSeconds num_leap_seconds;
    };
// Implementation of the private constructor used for the singleton object.
@@ -1301,12 +1306,85 @@ namespace
    // The expiry date corresponding to the list above.
    // tzdata 2026a leapseconds list expires at 2026-12-28 00:00:00 UTC
    constexpr seconds fixed_expiry{1798416000u};
-
-  // This holds the most up-to-date number of leap seconds known at runtime.
-  // Initially zero, updated when _S_read_leap_seconds() is called.
-  constinit atomic<unsigned> num_leap_seconds{0};
  }
+// This holds the most up-to-date number of leap seconds known at runtime.
+// Initially zero, updated when _S_read_leap_seconds() is called.
+struct tzdb_list::_Node::NumLeapSeconds
+{
+  // Called by __recent_leap_second_info to read num_leap_seconds.
+  unsigned
+  get()
+  {
+#if ATOMIC_INT_LOCK_FREE == 2
+    atomic_ref<unsigned> ref(count);
+    auto num = ref.load(memory_order::relaxed);
+
+    if (num == std::size(_Node::fixed_leaps))
+      // A leapseconds file has been read and has no new leap seconds.
+      return num;
+
+    if (num == 0)
+      // No leapseconds file has been read yet.
+      return 0;
+
+    // The tzdb_list has been initialized and contains a tzdb object with
+    // new leap seconds, which the caller is going to use.
+    // The relaxed load above does not synchronize with anything, so to
+    // ensure that the get_tzdb_list() in the caller will see a tzdb object
+    // set by _S_replace_head, we load num_leap_seconds again with acquire
+    // ordering:
+    return ref.load(memory_order::acquire);
+#else
+    lock_guard<mutex> l(list_mutex()); // This ensures acquire ordering.
+    return count;
+#endif
+  }
+
+  // Called by __recent_leap_second_info to set num_leap_seconds when
+  // we have determined there are no new leap seconds in a leapseconds file.
+  void
+  set_to_fixed_size()
+  {
+#if ATOMIC_INT_LOCK_FREE == 2
+    atomic_ref<unsigned> ref(count);
+    unsigned expected = 0;
+    ref.compare_exchange_strong(expected, std::size(_Node::fixed_leaps),
+                               memory_order::relaxed);
+#else
+    lock_guard<mutex> l(list_mutex());
+    if (count == 0)
+      count = std::size(_Node::fixed_leaps);
+#endif
+  }
+
+  // Called by _Node::_S_replace_head
+  // The two versions are named differently so that caller has to be explicit
+  // about which version it calls, based on whether the mutex is held.
+#if ATOMIC_INT_LOCK_FREE == 2
+  void
+  set_atomically(unsigned val)
+  {
+    atomic_ref<unsigned> ref(count);
+    // The release op here synchronizes with the acquire op in get().
+    ref.store(val, memory_order::release);
+  }
+#else
+  void
+  set_locked(unsigned val, const lock_guard<mutex>&)
+  {
+    // XXX The only caller of this function locks list_mutex() so we would
+    // deadlock if we locked it again here.
+    count = val;
+  }
+#endif
+
+private:
+  unsigned count = 0;
+};
+
+constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
+
    namespace __detail
    {
      // Called by chrono::__detail::__get_leap_second_info in <chrono>
@@ -1368,19 +1446,11 @@ namespace
constexpr auto num_fixed_leaps = std::size(_Node::fixed_leaps); - auto num_leaps = num_leap_seconds.load(memory_order::relaxed);
+      auto num_leaps = _Node::num_leap_seconds.get();
        if (num_leaps == num_fixed_leaps)
        // A leapseconds file has been read and has no new leap seconds:
        return update_info(_Node::fixed_leaps);
-      else if (num_leaps != 0)
-       // The tzdb_list has been initialized and contains a tzdb object
-       // with new leap seconds, which we want to use here.
-       // The relaxed load above does not synchronize with anything, so to
-       // ensure that the get_tzdb_list() below will see a tzdb object set
-       // by _S_replace_head, we load num_leap_seconds again with acquire
-       // ordering:
-       (void) num_leap_seconds.load(memory_order::acquire);
-      else
+      else if (num_leaps == 0)
        {
          // The tzdb_list has not been initialized yet, so we don't know
          // the correct number of leap seconds.
@@ -1389,11 +1459,9 @@ namespace
          // to parse all of tzdata.zi and initialize a whole tzdb object.
          if (_Node::_S_read_leap_seconds().first.size() == num_fixed_leaps)
            {
-             // There are no new leap seconds. remember that so that the next
+             // There are no new leap seconds. Remember that so that the next
              // call to this function can just use fixed_leaps.
-             num_leap_seconds.compare_exchange_strong(num_leaps,
-                                                      num_fixed_leaps,
-                                                      memory_order::relaxed);
+             _Node::num_leap_seconds.set_to_fixed_size();
              return update_info(_Node::fixed_leaps);
            }
          // else there are new leap seconds. We init tzdb_list so that the
@@ -1525,8 +1593,13 @@ namespace
        new_head_ptr->next = curr;
        }
      // XXX small window here where _S_head_cache still points to previous 
tzdb.
+    _S_cache_list_head(new_head_ptr);
+
+    // This allows __recent_leap_second_info() to know that it can use
+    // get_tzdb_list()->begin()->leap_seconds to get new leap seconds.
+    num_leap_seconds.set_atomically(new_head_ptr->db.leap_seconds.size());
  #else
-    lock_guard<mutex> l(list_mutex());
+    lock_guard<mutex> lock(list_mutex());
      if (const _Node* h = _S_head_owner.get())
        {
        if (h->db.version == new_head_ptr->db.version)
@@ -1534,14 +1607,10 @@ namespace
        new_head_ptr->next = _S_head_owner;
        }
      _S_head_owner = std::move(new_head);
-#endif
      _S_cache_list_head(new_head_ptr);
- // This allows __recent_leap_second_info() to know that it can use
-    // get_tzdb_list()->begin()->leap_seconds to get new leap seconds.
-    // The release op here synchronizes with the acquire op there.
-    num_leap_seconds.store(new_head_ptr->db.leap_seconds.size(),
-                          memory_order::release);
+    num_leap_seconds.set_locked(new_head_ptr->db.leap_seconds.size(), lock);

Looks like the pre-processor condition here is missaligned with the 
preprocessor condition for the definition of set_locked().

Hope this helps to figure out how to get trunk buildable again.

Kind regards,
Torbjörn

+#endif
return new_head_ptr->db;
    }

Reply via email to