On Wed, 2 Apr 2025 at 07:16, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > On Tue, Apr 1, 2025 at 2:46 PM Jonathan Wakely <jwak...@redhat.com> wrote: >> >> On Tue, 1 Apr 2025 at 11:34, Tomasz Kaminski <tkami...@redhat.com> wrote: >> > >> > >> > >> > On Mon, Mar 31, 2025 at 7:28 PM Jonathan Wakely <jwak...@redhat.com> wrote: >> >> >> >> In r15-8491-g778c28c70f8573 I added a use of the Autoconf macro >> >> AC_STRUCT_TIMEZONE, but that requires a link-test for the global tzname >> >> object if tm.tm_zone isn't supported. That link-test isn't allowed for >> >> cross-compilation, so bootstrap fails if tm.tm_zone isn't supported. >> >> >> >> Since libstdc++ only cares about tm.tm_zone and won't use tzname anyway, >> >> we don't need the link-test. Replace AC_STRUCT_TIMEZONE with a custom >> >> macro that only checks for tm.tm_zone. We can improve on the Autoconf >> >> macro by checking it's a suitable type, which isn't actually checked by >> >> AC_STRUCT_TIMEZONE. >> >> >> >> libstdc++-v3/ChangeLog: >> >> >> >> PR libstdc++/119550 >> >> * acinclude.m4 (GLIBCXX_STRUCT_TM_TM_ZONE): New macro. >> >> * config.h.in: Regenerate. >> >> * configure: Regenerate. >> >> * configure.ac: Use GLIBCXX_STRUCT_TM_TM_ZONE. >> >> * include/bits/chrono_io.h (__formatter_chrono::_M_c): Check >> >> _GLIBCXX_USE_STRUCT_TM_TM_ZONE instead of >> >> _GLIBCXX_HAVE_STRUCT_TM_TM_ZONE. >> >> --- >> >> >> >> Testing x86_64-linux and sparc-solaris2.11, looks fine so far. >> >> >> >> libstdc++-v3/acinclude.m4 | 35 ++++ >> >> libstdc++-v3/config.h.in | 21 +-- >> >> libstdc++-v3/configure | 238 ++++++++------------------ >> >> libstdc++-v3/configure.ac | 5 +- >> >> libstdc++-v3/include/bits/chrono_io.h | 2 +- >> >> 5 files changed, 109 insertions(+), 192 deletions(-) >> >> >> >> diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4 >> >> index e668d2dba27..02fd349e11d 100644 >> >> --- a/libstdc++-v3/acinclude.m4 >> >> +++ b/libstdc++-v3/acinclude.m4 >> >> @@ -5744,6 +5744,41 @@ AC_DEFUN([GLIBCXX_ZONEINFO_DIR], [ >> >> fi >> >> ]) >> >> >> >> +dnl >> >> +dnl Check for a tm_zone member in struct tm. >> >> +dnl >> >> +dnl This member is defined as const char* in Glibc, newlib, POSIX.1-2024, >> >> +dnl and as char* in BSD (including macOS). >> >> +dnl >> >> +dnl Defines: >> >> +dnl _GLIBCXX_USE_STRUCT_TM_TM_ZONE if struct tm has a tm_zone member. >> >> +dnl >> >> +AC_DEFUN([GLIBCXX_STRUCT_TM_TM_ZONE], [ >> >> + >> >> + AC_LANG_SAVE >> > >> > From documentation this is deprecated in favor of AC_LANG_PUSH. >> >> It looks like that is supported by autoconf 2.69 and is already used >> elsewhere in GCC. We don't currently use it in libstdc++ but it should >> be safe to do so. >> >> >> + AC_LANG_CPLUSPLUS >> >> + ac_save_CXXFLAGS="$CXXFLAGS" >> >> + CXXFLAGS="$CXXFLAGS -std=c++20" >> > >> > The program that is compiled does not seem to require C++20. >> > If we change the declaration of "t" to use "= {}", we could do it in C++98. >> > Any reason to adjust the flags at all? >> >> We only need to use the tm_zone member in C++20 mode, and it's >> possible that on some platform it's not exposed for older standards >> (very unlikely, but possible). I wanted to test for exactly what we >> require. I don't feel strongly about it though. > > I would found it surprising if the layout of the structure would fiffer that > much, > wouldn't this cause ABI compatibility problems for TU that memcopies struct tm > and are compiled with different language versions?
Not if it does: #if _USE_POSIX24 const char* tm_zone; #else const void* __tm_zone_padding; #endif > I have slight preference towards not overriding CXXFLAGS, as for me this > suggest > that struct tm was changed in C++20 (or linked C standard), which is not the > case. > But I am fine either way. IMHO the test should be understood as "can C++20 chrono formatting use tm_zone", and not "is tm_zone defined in C++20", which is partly why I defined a USE macro nto a HAVE macro (but also to avoid conflicting with the name that AC_STRUCT_TIMEZONE uses). I should have said that in the comments above the GLIBCXX_STRUCT_TM_TM_ZONE definition though! I'll improve that (and revisit the AC_PUSH_LANG point in stage 1). >> >> >> >> >> >> + >> >> + AC_CACHE_CHECK([for tm_zone member of struct tm], glibcxx_cv_tm_zone, [ >> >> + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include <time.h> >> >> + ], >> >> + [struct tm t{}; t.tm_zone = (char*)0;] >> >> + )], >> >> + [glibcxx_cv_tm_zone=yes], >> >> + [glibcxx_cv_tm_zone=no] >> >> + ) >> >> + ]) >> >> + >> >> + if test $glibcxx_cv_tm_zone = yes; then >> >> + AC_DEFINE(_GLIBCXX_USE_STRUCT_TM_TM_ZONE, 1, >> >> + [Define if struct tm has a tm_zone member.]) >> >> + fi >> >> + >> >> + CXXFLAGS="$ac_save_CXXFLAGS" >> >> + AC_LANG_RESTORE >> >> +]) >> >> + >> >> dnl >> >> dnl Check whether lock tables can be aligned to avoid false sharing. >> >> dnl >> >> diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in >> >> index be151f43dd6..77bbaf1beaa 100644 >> >> --- a/libstdc++-v3/config.h.in >> >> +++ b/libstdc++-v3/config.h.in >> >> @@ -74,10 +74,6 @@ >> >> don't. */ >> >> #undef HAVE_DECL_STRNLEN >> >> >> >> -/* Define to 1 if you have the declaration of `tzname', and to 0 if you >> >> don't. >> >> - */ >> >> -#undef HAVE_DECL_TZNAME >> >> - >> >> /* Define to 1 if you have the <dirent.h> header file. */ >> >> #undef HAVE_DIRENT_H >> >> >> >> @@ -412,9 +408,6 @@ >> >> /* Define to 1 if `d_type' is a member of `struct dirent'. */ >> >> #undef HAVE_STRUCT_DIRENT_D_TYPE >> >> >> >> -/* Define to 1 if `tm_zone' is a member of `struct tm'. */ >> >> -#undef HAVE_STRUCT_TM_TM_ZONE >> >> - >> >> /* Define if strxfrm_l is available in <string.h>. */ >> >> #undef HAVE_STRXFRM_L >> >> >> >> @@ -506,17 +499,9 @@ >> >> /* Define to 1 if the target supports thread-local storage. */ >> >> #undef HAVE_TLS >> >> >> >> -/* Define to 1 if your `struct tm' has `tm_zone'. Deprecated, use >> >> - `HAVE_STRUCT_TM_TM_ZONE' instead. */ >> >> -#undef HAVE_TM_ZONE >> >> - >> >> /* Define if truncate is available in <unistd.h>. */ >> >> #undef HAVE_TRUNCATE >> >> >> >> -/* Define to 1 if you don't have `tm_zone' but do have the external array >> >> - `tzname'. */ >> >> -#undef HAVE_TZNAME >> >> - >> >> /* Define to 1 if you have the <uchar.h> header file. */ >> >> #undef HAVE_UCHAR_H >> >> >> >> @@ -605,9 +590,6 @@ >> >> /* Define to 1 if you have the ANSI C header files. */ >> >> #undef STDC_HEADERS >> >> >> >> -/* Define to 1 if your <sys/time.h> declares `struct tm'. */ >> >> -#undef TM_IN_SYS_TIME >> >> - >> >> /* Version number of package */ >> >> #undef VERSION >> >> >> >> @@ -906,6 +888,9 @@ >> >> /* Define to restrict std::__basic_file<> to stdio APIs. */ >> >> #undef _GLIBCXX_USE_STDIO_PURE >> >> >> >> +/* Define if struct tm has a tm_zone member. */ >> >> +#undef _GLIBCXX_USE_STRUCT_TM_TM_ZONE >> >> + >> >> /* Define if struct stat has timespec members. */ >> >> #undef _GLIBCXX_USE_ST_MTIM >> >> >> >> diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure >> >> index 67d2b8c7b72..56d0bcb297e 100755 >> >> --- a/libstdc++-v3/configure >> >> +++ b/libstdc++-v3/configure >> >> @@ -2731,63 +2731,6 @@ $as_echo "$ac_res" >&6; } >> >> eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno >> >> >> >> } # ac_fn_c_check_decl >> >> - >> >> -# ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES >> >> -# ---------------------------------------------------- >> >> -# Tries to find if the field MEMBER exists in type AGGR, after including >> >> -# INCLUDES, setting cache variable VAR accordingly. >> >> -ac_fn_c_check_member () >> >> -{ >> >> - as_lineno=${as_lineno-"$1"} >> >> as_lineno_stack=as_lineno_stack=$as_lineno_stack >> >> - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5 >> >> -$as_echo_n "checking for $2.$3... " >&6; } >> >> -if eval \${$4+:} false; then : >> >> - $as_echo_n "(cached) " >&6 >> >> -else >> >> - cat confdefs.h - <<_ACEOF >conftest.$ac_ext >> >> -/* end confdefs.h. */ >> >> -$5 >> >> -int >> >> -main () >> >> -{ >> >> -static $2 ac_aggr; >> >> -if (ac_aggr.$3) >> >> -return 0; >> >> - ; >> >> - return 0; >> >> -} >> >> -_ACEOF >> >> -if ac_fn_c_try_compile "$LINENO"; then : >> >> - eval "$4=yes" >> >> -else >> >> - cat confdefs.h - <<_ACEOF >conftest.$ac_ext >> >> -/* end confdefs.h. */ >> >> -$5 >> >> -int >> >> -main () >> >> -{ >> >> -static $2 ac_aggr; >> >> -if (sizeof ac_aggr.$3) >> >> -return 0; >> >> - ; >> >> - return 0; >> >> -} >> >> -_ACEOF >> >> -if ac_fn_c_try_compile "$LINENO"; then : >> >> - eval "$4=yes" >> >> -else >> >> - eval "$4=no" >> >> -fi >> >> -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext >> >> -fi >> >> -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext >> >> -fi >> >> -eval ac_res=\$$4 >> >> - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >> >> >&5 >> >> -$as_echo "$ac_res" >&6; } >> >> - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno >> >> - >> >> -} # ac_fn_c_check_member >> >> cat >config.log <<_ACEOF >> >> This file contains any messages produced by compilers while >> >> running configure, to aid debugging if configure makes a mistake. >> >> @@ -12337,7 +12280,7 @@ else >> >> lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 >> >> lt_status=$lt_dlunknown >> >> cat > conftest.$ac_ext <<_LT_EOF >> >> -#line 12340 "configure" >> >> +#line 12283 "configure" >> >> #include "confdefs.h" >> >> >> >> #if HAVE_DLFCN_H >> >> @@ -12443,7 +12386,7 @@ else >> >> lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 >> >> lt_status=$lt_dlunknown >> >> cat > conftest.$ac_ext <<_LT_EOF >> >> -#line 12446 "configure" >> >> +#line 12389 "configure" >> >> #include "confdefs.h" >> >> >> >> #if HAVE_DLFCN_H >> >> @@ -16239,7 +16182,7 @@ $as_echo "$glibcxx_cv_atomic_long_long" >&6; } >> >> # Fake what AC_TRY_COMPILE does. >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16242 "configure" >> >> +#line 16185 "configure" >> >> int main() >> >> { >> >> typedef bool atomic_type; >> >> @@ -16274,7 +16217,7 @@ $as_echo "$glibcxx_cv_atomic_bool" >&6; } >> >> rm -f conftest* >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16277 "configure" >> >> +#line 16220 "configure" >> >> int main() >> >> { >> >> typedef short atomic_type; >> >> @@ -16309,7 +16252,7 @@ $as_echo "$glibcxx_cv_atomic_short" >&6; } >> >> rm -f conftest* >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16312 "configure" >> >> +#line 16255 "configure" >> >> int main() >> >> { >> >> // NB: _Atomic_word not necessarily int. >> >> @@ -16345,7 +16288,7 @@ $as_echo "$glibcxx_cv_atomic_int" >&6; } >> >> rm -f conftest* >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16348 "configure" >> >> +#line 16291 "configure" >> >> int main() >> >> { >> >> typedef long long atomic_type; >> >> @@ -16501,7 +16444,7 @@ $as_echo "mutex" >&6; } >> >> # unnecessary for this test. >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16504 "configure" >> >> +#line 16447 "configure" >> >> int main() >> >> { >> >> _Decimal32 d1; >> >> @@ -16543,7 +16486,7 @@ ac_compiler_gnu=$ac_cv_cxx_compiler_gnu >> >> # unnecessary for this test. >> >> >> >> cat > conftest.$ac_ext << EOF >> >> -#line 16546 "configure" >> >> +#line 16489 "configure" >> >> template<typename T1, typename T2> >> >> struct same >> >> { typedef T2 type; }; >> >> @@ -54482,6 +54425,65 @@ _ACEOF >> >> fi >> >> >> >> >> >> +# For std::chrono formatters to use tm::tm_zone >> >> + >> >> + >> >> + >> >> + ac_ext=cpp >> >> +ac_cpp='$CXXCPP $CPPFLAGS' >> >> +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' >> >> +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS >> >> conftest.$ac_ext $LIBS >&5' >> >> +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu >> >> + >> >> + ac_save_CXXFLAGS="$CXXFLAGS" >> >> + CXXFLAGS="$CXXFLAGS -std=c++20" >> >> + >> >> + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for tm_zone member >> >> of struct tm" >&5 >> >> +$as_echo_n "checking for tm_zone member of struct tm... " >&6; } >> >> +if ${glibcxx_cv_tm_zone+:} false; then : >> >> + $as_echo_n "(cached) " >&6 >> >> +else >> >> + >> >> + cat confdefs.h - <<_ACEOF >conftest.$ac_ext >> >> +/* end confdefs.h. */ >> >> +#include <time.h> >> >> + >> >> +int >> >> +main () >> >> +{ >> >> +struct tm t{}; t.tm_zone = (char*)0; >> >> + >> >> + ; >> >> + return 0; >> >> +} >> >> +_ACEOF >> >> +if ac_fn_cxx_try_compile "$LINENO"; then : >> >> + glibcxx_cv_tm_zone=yes >> >> +else >> >> + glibcxx_cv_tm_zone=no >> >> + >> >> +fi >> >> +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext >> >> + >> >> +fi >> >> +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_tm_zone" >&5 >> >> +$as_echo "$glibcxx_cv_tm_zone" >&6; } >> >> + >> >> + if test $glibcxx_cv_tm_zone = yes; then >> >> + >> >> +$as_echo "#define _GLIBCXX_USE_STRUCT_TM_TM_ZONE 1" >>confdefs.h >> >> + >> >> + fi >> >> + >> >> + CXXFLAGS="$ac_save_CXXFLAGS" >> >> + ac_ext=c >> >> +ac_cpp='$CPP $CPPFLAGS' >> >> +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' >> >> +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS >> >> conftest.$ac_ext $LIBS >&5' >> >> +ac_compiler_gnu=$ac_cv_c_compiler_gnu >> >> + >> >> + >> >> + >> >> # For src/c++11/shared_ptr.cc alignment. >> >> >> >> >> >> @@ -54697,112 +54699,6 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu >> >> >> >> >> >> >> >> -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether struct tm is >> >> in sys/time.h or time.h" >&5 >> >> -$as_echo_n "checking whether struct tm is in sys/time.h or time.h... " >> >> >&6; } >> >> -if ${ac_cv_struct_tm+:} false; then : >> >> - $as_echo_n "(cached) " >&6 >> >> -else >> >> - cat confdefs.h - <<_ACEOF >conftest.$ac_ext >> >> -/* end confdefs.h. */ >> >> -#include <sys/types.h> >> >> -#include <time.h> >> >> - >> >> -int >> >> -main () >> >> -{ >> >> -struct tm tm; >> >> - int *p = &tm.tm_sec; >> >> - return !p; >> >> - ; >> >> - return 0; >> >> -} >> >> -_ACEOF >> >> -if ac_fn_c_try_compile "$LINENO"; then : >> >> - ac_cv_struct_tm=time.h >> >> -else >> >> - ac_cv_struct_tm=sys/time.h >> >> -fi >> >> -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext >> >> -fi >> >> -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_tm" >&5 >> >> -$as_echo "$ac_cv_struct_tm" >&6; } >> >> -if test $ac_cv_struct_tm = sys/time.h; then >> >> - >> >> -$as_echo "#define TM_IN_SYS_TIME 1" >>confdefs.h >> >> - >> >> -fi >> >> - >> >> -ac_fn_c_check_member "$LINENO" "struct tm" "tm_zone" >> >> "ac_cv_member_struct_tm_tm_zone" "#include <sys/types.h> >> >> -#include <$ac_cv_struct_tm> >> >> - >> >> -" >> >> -if test "x$ac_cv_member_struct_tm_tm_zone" = xyes; then : >> >> - >> >> -cat >>confdefs.h <<_ACEOF >> >> -#define HAVE_STRUCT_TM_TM_ZONE 1 >> >> -_ACEOF >> >> - >> >> - >> >> -fi >> >> - >> >> -if test "$ac_cv_member_struct_tm_tm_zone" = yes; then >> >> - >> >> -$as_echo "#define HAVE_TM_ZONE 1" >>confdefs.h >> >> - >> >> -else >> >> - ac_fn_c_check_decl "$LINENO" "tzname" "ac_cv_have_decl_tzname" >> >> "#include <time.h> >> >> -" >> >> -if test "x$ac_cv_have_decl_tzname" = xyes; then : >> >> - ac_have_decl=1 >> >> -else >> >> - ac_have_decl=0 >> >> -fi >> >> - >> >> -cat >>confdefs.h <<_ACEOF >> >> -#define HAVE_DECL_TZNAME $ac_have_decl >> >> -_ACEOF >> >> - >> >> - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for tzname" >&5 >> >> -$as_echo_n "checking for tzname... " >&6; } >> >> -if ${ac_cv_var_tzname+:} false; then : >> >> - $as_echo_n "(cached) " >&6 >> >> -else >> >> - if test x$gcc_no_link = xyes; then >> >> - as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." >> >> "$LINENO" 5 >> >> -fi >> >> -cat confdefs.h - <<_ACEOF >conftest.$ac_ext >> >> -/* end confdefs.h. */ >> >> -#include <time.h> >> >> -#if !HAVE_DECL_TZNAME >> >> -extern char *tzname[]; >> >> -#endif >> >> - >> >> -int >> >> -main () >> >> -{ >> >> -return tzname[0][0]; >> >> - ; >> >> - return 0; >> >> -} >> >> -_ACEOF >> >> -if ac_fn_c_try_link "$LINENO"; then : >> >> - ac_cv_var_tzname=yes >> >> -else >> >> - ac_cv_var_tzname=no >> >> -fi >> >> -rm -f core conftest.err conftest.$ac_objext \ >> >> - conftest$ac_exeext conftest.$ac_ext >> >> -fi >> >> -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_var_tzname" >&5 >> >> -$as_echo "$ac_cv_var_tzname" >&6; } >> >> - if test $ac_cv_var_tzname = yes; then >> >> - >> >> -$as_echo "#define HAVE_TZNAME 1" >>confdefs.h >> >> - >> >> - fi >> >> -fi >> >> - >> >> - >> >> # Define documentation rules conditionally. >> >> >> >> # See if makeinfo has been installed and is modern enough >> >> diff --git a/libstdc++-v3/configure.ac b/libstdc++-v3/configure.ac >> >> index fe0cdde1f7a..a6c01b29e94 100644 >> >> --- a/libstdc++-v3/configure.ac >> >> +++ b/libstdc++-v3/configure.ac >> >> @@ -572,6 +572,9 @@ GLIBCXX_EMERGENCY_EH_ALLOC >> >> # For src/c++20/tzdb.cc defaults. >> >> GLIBCXX_ZONEINFO_DIR >> >> >> >> +# For std::chrono formatters to use tm::tm_zone >> >> +GLIBCXX_STRUCT_TM_TM_ZONE >> >> + >> >> # For src/c++11/shared_ptr.cc alignment. >> >> GLIBCXX_CHECK_ALIGNAS_CACHELINE >> >> >> >> @@ -584,8 +587,6 @@ GLIBCXX_CHECK_FILEBUF_NATIVE_HANDLES >> >> # For std::text_encoding >> >> GLIBCXX_CHECK_TEXT_ENCODING >> >> >> >> -AC_STRUCT_TIMEZONE >> >> - >> >> # Define documentation rules conditionally. >> >> >> >> # See if makeinfo has been installed and is modern enough >> >> diff --git a/libstdc++-v3/include/bits/chrono_io.h >> >> b/libstdc++-v3/include/bits/chrono_io.h >> >> index 3a5bc5695fb..d8721093706 100644 >> >> --- a/libstdc++-v3/include/bits/chrono_io.h >> >> +++ b/libstdc++-v3/include/bits/chrono_io.h >> >> @@ -905,7 +905,7 @@ namespace __format >> >> // time zone info available for the time in __tm. >> >> __tm.tm_isdst = -1; >> >> >> >> -#ifdef _GLIBCXX_HAVE_STRUCT_TM_TM_ZONE >> >> +#ifdef _GLIBCXX_USE_STRUCT_TM_TM_ZONE >> >> // POSIX.1-2024 adds tm.tm_zone which will be used for %Z. >> >> // BSD has had tm_zone since 1987 but as char* so cast away >> >> const. >> >> if constexpr (__is_time_point_v<_Tp>) >> >> -- >> >> 2.49.0 >> >> >>