From 68a1a813d46719382da3a67a973eb310bccb6e2a Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:00:56 -0400 Subject: [PATCH 01/18] [flang-rt] Runtime implementation of extended intrinsic function SECNDS() --- flang-rt/lib/runtime/command.cpp | 7 +++++ flang-rt/lib/runtime/extensions.cpp | 39 ++++++++++++++++++++++++ flang/include/flang/Runtime/command.h | 2 ++ flang/include/flang/Runtime/extensions.h | 3 ++ 4 files changed, 51 insertions(+) diff --git a/flang-rt/lib/runtime/command.cpp b/flang-rt/lib/runtime/command.cpp index a4e8e31ad0274..40233392f2497 100644 --- a/flang-rt/lib/runtime/command.cpp +++ b/flang-rt/lib/runtime/command.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "flang/Runtime/command.h" +#include "flang/Runtime/extensions.h" #include "flang-rt/runtime/descriptor.h" #include "flang-rt/runtime/environment.h" #include "flang-rt/runtime/stat.h" @@ -309,6 +310,12 @@ std::int32_t RTNAME(Hostnm)( return status; } +float RTNAME(Secnds)(float* refTime, const char *sourceFile, int line) { + Terminator terminator{sourceFile, line}; + RUNTIME_CHECK(terminator, refTime != nullptr); + return FORTRAN_PROCEDURE_NAME(secnds)(refTime); +} + std::int32_t RTNAME(PutEnv)( const char *str, size_t str_length, const char *sourceFile, int line) { Terminator terminator{sourceFile, line}; diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index f6c39468d5655..4751fb24af849 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -303,6 +303,45 @@ void FORTRAN_PROCEDURE_NAME(qsort)(int *array, int *len, int *isize, // PERROR(STRING) void RTNAME(Perror)(const char *str) { perror(str); } +// GNU extension function SECNDS(refTime) +float FORTRAN_PROCEDURE_NAME(secnds)(float* refTime) { + constexpr float FAIL_SECNDS{1.0f}; + if (!refTime) { + return FAIL_SECNDS; + } + std::time_t now{std::time(nullptr)}; + if (now == std::time_t{-1}) { + return FAIL_SECNDS; + } + // In float result, we can only precisely store 2^24 seconds, which + // comes out to about 194 days. Thus, need to peek a starting point. + // Given the description of this function, midnight of the current + // day is the best starting point. + static time_t startingPoint{0}; + if (!startingPoint) { + struct tm timeInfo; +#ifdef _WIN32 + if (localtime_s(&timeInfo, &now)) { + return FAIL_SECNDS; + } +#else + if (!localtime_r(&now, &timeInfo)) { + return FAIL_SECNDS; + } +#endif + // Back to midnight + timeInfo.tm_hour = 0; + timeInfo.tm_min = 0; + timeInfo.tm_sec = 0; + startingPoint = std::mktime(&timeInfo); + if (startingPoint == std::time_t(-1)) { + return FAIL_SECNDS; + } + } + double diffStartingPoint = std::difftime(now, startingPoint); + return static_cast(diffStartingPoint) - *refTime; +} + // GNU extension function TIME() std::int64_t RTNAME(time)() { return time(nullptr); } diff --git a/flang/include/flang/Runtime/command.h b/flang/include/flang/Runtime/command.h index 19b486094da17..21ca1fa8457ee 100644 --- a/flang/include/flang/Runtime/command.h +++ b/flang/include/flang/Runtime/command.h @@ -67,6 +67,8 @@ std::int32_t RTNAME(Hostnm)( std::int32_t RTNAME(PutEnv)( const char *str, size_t str_length, const char *sourceFile, int line); +float RTNAME(Secnds)(float* refTime, const char *sourceFile, int line); + // Calls unlink() std::int32_t RTNAME(Unlink)( const char *path, size_t pathLength, const char *sourceFile, int line); diff --git a/flang/include/flang/Runtime/extensions.h b/flang/include/flang/Runtime/extensions.h index b350204714431..a5bfa8b535286 100644 --- a/flang/include/flang/Runtime/extensions.h +++ b/flang/include/flang/Runtime/extensions.h @@ -90,5 +90,8 @@ void RTNAME(Perror)(const char *str); // MCLOCK -- returns accumulated time in ticks int FORTRAN_PROCEDURE_NAME(mclock)(); +// GNU extension subroutine SECNDS(refTime) +float FORTRAN_PROCEDURE_NAME(secnds)(float* refTime); + } // extern "C" #endif // FORTRAN_RUNTIME_EXTENSIONS_H_ From 716f82d03cb17a493891c2af761cdf4461e5e13a Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:01:50 -0400 Subject: [PATCH 02/18] clang-format --- flang-rt/lib/runtime/command.cpp | 4 ++-- flang-rt/lib/runtime/extensions.cpp | 2 +- flang/include/flang/Runtime/command.h | 2 +- flang/include/flang/Runtime/extensions.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flang-rt/lib/runtime/command.cpp b/flang-rt/lib/runtime/command.cpp index 40233392f2497..ade77e9dd8d0f 100644 --- a/flang-rt/lib/runtime/command.cpp +++ b/flang-rt/lib/runtime/command.cpp @@ -7,12 +7,12 @@ //===----------------------------------------------------------------------===// #include "flang/Runtime/command.h" -#include "flang/Runtime/extensions.h" #include "flang-rt/runtime/descriptor.h" #include "flang-rt/runtime/environment.h" #include "flang-rt/runtime/stat.h" #include "flang-rt/runtime/terminator.h" #include "flang-rt/runtime/tools.h" +#include "flang/Runtime/extensions.h" #include #include #include @@ -310,7 +310,7 @@ std::int32_t RTNAME(Hostnm)( return status; } -float RTNAME(Secnds)(float* refTime, const char *sourceFile, int line) { +float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line) { Terminator terminator{sourceFile, line}; RUNTIME_CHECK(terminator, refTime != nullptr); return FORTRAN_PROCEDURE_NAME(secnds)(refTime); diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 4751fb24af849..a5642b628cbfe 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -304,7 +304,7 @@ void FORTRAN_PROCEDURE_NAME(qsort)(int *array, int *len, int *isize, void RTNAME(Perror)(const char *str) { perror(str); } // GNU extension function SECNDS(refTime) -float FORTRAN_PROCEDURE_NAME(secnds)(float* refTime) { +float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { constexpr float FAIL_SECNDS{1.0f}; if (!refTime) { return FAIL_SECNDS; diff --git a/flang/include/flang/Runtime/command.h b/flang/include/flang/Runtime/command.h index 21ca1fa8457ee..d22c2bc3956fe 100644 --- a/flang/include/flang/Runtime/command.h +++ b/flang/include/flang/Runtime/command.h @@ -67,7 +67,7 @@ std::int32_t RTNAME(Hostnm)( std::int32_t RTNAME(PutEnv)( const char *str, size_t str_length, const char *sourceFile, int line); -float RTNAME(Secnds)(float* refTime, const char *sourceFile, int line); +float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line); // Calls unlink() std::int32_t RTNAME(Unlink)( diff --git a/flang/include/flang/Runtime/extensions.h b/flang/include/flang/Runtime/extensions.h index a5bfa8b535286..355808a846f13 100644 --- a/flang/include/flang/Runtime/extensions.h +++ b/flang/include/flang/Runtime/extensions.h @@ -91,7 +91,7 @@ void RTNAME(Perror)(const char *str); int FORTRAN_PROCEDURE_NAME(mclock)(); // GNU extension subroutine SECNDS(refTime) -float FORTRAN_PROCEDURE_NAME(secnds)(float* refTime); +float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime); } // extern "C" #endif // FORTRAN_RUNTIME_EXTENSIONS_H_ From 8bbe4ebb083f8a707e75318456ad520fdff3213f Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:05:06 -0400 Subject: [PATCH 03/18] Fixed a typo --- flang-rt/lib/runtime/extensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index a5642b628cbfe..a218456e0cbb4 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -314,7 +314,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { return FAIL_SECNDS; } // In float result, we can only precisely store 2^24 seconds, which - // comes out to about 194 days. Thus, need to peek a starting point. + // comes out to about 194 days. Thus, need to pick a starting point. // Given the description of this function, midnight of the current // day is the best starting point. static time_t startingPoint{0}; From 5f671e74d65d406d3fd576da7fe29dfbcdb904a8 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:06:22 -0400 Subject: [PATCH 04/18] Fixed init form --- flang-rt/lib/runtime/extensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index a218456e0cbb4..cc3be56019b02 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -338,7 +338,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { return FAIL_SECNDS; } } - double diffStartingPoint = std::difftime(now, startingPoint); + double diffStartingPoint{std::difftime(now, startingPoint)}; return static_cast(diffStartingPoint) - *refTime; } From 9c538783b3947458405912de0740d4497b548c40 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:08:33 -0400 Subject: [PATCH 05/18] The failure code should be negative --- flang-rt/lib/runtime/extensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index cc3be56019b02..01cd13dbf84d5 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -305,7 +305,7 @@ void RTNAME(Perror)(const char *str) { perror(str); } // GNU extension function SECNDS(refTime) float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { - constexpr float FAIL_SECNDS{1.0f}; + constexpr float FAIL_SECNDS{-1.0f}; if (!refTime) { return FAIL_SECNDS; } From 80b61edbf543a2894466ebc6a9a79265b3674fed Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 16:19:06 -0400 Subject: [PATCH 06/18] Introduce FAIL_TIME --- flang-rt/lib/runtime/extensions.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 01cd13dbf84d5..e438d335a6658 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -305,12 +305,14 @@ void RTNAME(Perror)(const char *str) { perror(str); } // GNU extension function SECNDS(refTime) float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { - constexpr float FAIL_SECNDS{-1.0f}; + constexpr float FAIL_SECNDS{-1.0f}; // Failure code for this function + // Failure code for time functions that return std::time_t + constexpr time_t FAIL_TIME{std::time_t{-1}}; if (!refTime) { return FAIL_SECNDS; } std::time_t now{std::time(nullptr)}; - if (now == std::time_t{-1}) { + if (now == FAIL_TIME) { return FAIL_SECNDS; } // In float result, we can only precisely store 2^24 seconds, which @@ -334,7 +336,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { timeInfo.tm_min = 0; timeInfo.tm_sec = 0; startingPoint = std::mktime(&timeInfo); - if (startingPoint == std::time_t(-1)) { + if (startingPoint == FAIL_TIME) { return FAIL_SECNDS; } } From 78a8d079aebbd28b60415b8ba383cc377f0c8828 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 23:05:39 -0400 Subject: [PATCH 07/18] Code review: moved RTNAME(Secnds) to extensions.cpp. Added missing to time_t declarations --- flang-rt/lib/runtime/command.cpp | 7 ------- flang-rt/lib/runtime/extensions.cpp | 10 ++++++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/flang-rt/lib/runtime/command.cpp b/flang-rt/lib/runtime/command.cpp index ade77e9dd8d0f..a4e8e31ad0274 100644 --- a/flang-rt/lib/runtime/command.cpp +++ b/flang-rt/lib/runtime/command.cpp @@ -12,7 +12,6 @@ #include "flang-rt/runtime/stat.h" #include "flang-rt/runtime/terminator.h" #include "flang-rt/runtime/tools.h" -#include "flang/Runtime/extensions.h" #include #include #include @@ -310,12 +309,6 @@ std::int32_t RTNAME(Hostnm)( return status; } -float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line) { - Terminator terminator{sourceFile, line}; - RUNTIME_CHECK(terminator, refTime != nullptr); - return FORTRAN_PROCEDURE_NAME(secnds)(refTime); -} - std::int32_t RTNAME(PutEnv)( const char *str, size_t str_length, const char *sourceFile, int line) { Terminator terminator{sourceFile, line}; diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index e438d335a6658..93054c11848a7 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -307,7 +307,7 @@ void RTNAME(Perror)(const char *str) { perror(str); } float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { constexpr float FAIL_SECNDS{-1.0f}; // Failure code for this function // Failure code for time functions that return std::time_t - constexpr time_t FAIL_TIME{std::time_t{-1}}; + constexpr std::time_t FAIL_TIME{std::time_t{-1}}; if (!refTime) { return FAIL_SECNDS; } @@ -319,7 +319,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // comes out to about 194 days. Thus, need to pick a starting point. // Given the description of this function, midnight of the current // day is the best starting point. - static time_t startingPoint{0}; + static std::time_t startingPoint{0}; if (!startingPoint) { struct tm timeInfo; #ifdef _WIN32 @@ -344,6 +344,12 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { return static_cast(diffStartingPoint) - *refTime; } +float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line) { + Terminator terminator{sourceFile, line}; + RUNTIME_CHECK(terminator, refTime != nullptr); + return FORTRAN_PROCEDURE_NAME(secnds)(refTime); +} + // GNU extension function TIME() std::int64_t RTNAME(time)() { return time(nullptr); } From 2f3a274a6d3c4547ab0c52a9f91ebcf7d94408da Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 23:34:48 -0400 Subject: [PATCH 08/18] Attempt at using atomic operations in secnds_() to make it reentrant --- flang-rt/lib/runtime/extensions.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 93054c11848a7..e6e2725427846 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -18,6 +18,7 @@ #include "flang/Runtime/entry-names.h" #include "flang/Runtime/io-api.h" #include "flang/Runtime/iostat-consts.h" +#include #include #include #include @@ -308,6 +309,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { constexpr float FAIL_SECNDS{-1.0f}; // Failure code for this function // Failure code for time functions that return std::time_t constexpr std::time_t FAIL_TIME{std::time_t{-1}}; + constexpr std::time_t TIME_UNINITIALIZED{std::time_t{0}}; + constexpr std::time_t TIME_INITIALIZING{std::time_t{1}}; if (!refTime) { return FAIL_SECNDS; } @@ -319,8 +322,11 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // comes out to about 194 days. Thus, need to pick a starting point. // Given the description of this function, midnight of the current // day is the best starting point. - static std::time_t startingPoint{0}; - if (!startingPoint) { + static std::atomic startingPoint{TIME_UNINITIALIZED}; + std::time_t expected{TIME_UNINITIALIZED}; + std::time_t localStartingPoint{TIME_UNINITIALIZED}; + if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING)) { + // This thread is doing initialization of startingPoint struct tm timeInfo; #ifdef _WIN32 if (localtime_s(&timeInfo, &now)) { @@ -335,12 +341,20 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { timeInfo.tm_hour = 0; timeInfo.tm_min = 0; timeInfo.tm_sec = 0; - startingPoint = std::mktime(&timeInfo); - if (startingPoint == FAIL_TIME) { + std::time_t midnight = std::mktime(&timeInfo); + if (midnight == FAIL_TIME) { return FAIL_SECNDS; } + localStartingPoint = midnight; + startingPoint.store(midnight, std::memory_order_release); + } else { + // This thread is not doing initialization of startingPoint, need to wait + // for initialization to complete. + while ((localStartingPoint = startingPoint.load(std::memory_order_acquire)) <= TIME_INITIALIZING) { + std::this_thread::yield(); + } } - double diffStartingPoint{std::difftime(now, startingPoint)}; + double diffStartingPoint{std::difftime(now, localStartingPoint)}; return static_cast(diffStartingPoint) - *refTime; } From 9940825dc31baa5962dcf181bb115c1ed3047e3d Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 23:35:23 -0400 Subject: [PATCH 09/18] clang-format --- flang-rt/lib/runtime/extensions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index e6e2725427846..0070431105163 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -350,7 +350,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { } else { // This thread is not doing initialization of startingPoint, need to wait // for initialization to complete. - while ((localStartingPoint = startingPoint.load(std::memory_order_acquire)) <= TIME_INITIALIZING) { + while ((localStartingPoint = startingPoint.load( + std::memory_order_acquire)) <= TIME_INITIALIZING) { std::this_thread::yield(); } } From 7268c8c82948b3eba3b3469e4a6924afbc8d81de Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Mon, 4 Aug 2025 23:38:01 -0400 Subject: [PATCH 10/18] Fixed init --- flang-rt/lib/runtime/extensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 0070431105163..80df0b8eadf61 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -341,7 +341,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { timeInfo.tm_hour = 0; timeInfo.tm_min = 0; timeInfo.tm_sec = 0; - std::time_t midnight = std::mktime(&timeInfo); + std::time_t midnight{std::mktime(&timeInfo)}; if (midnight == FAIL_TIME) { return FAIL_SECNDS; } From 205818e162adec186a93c835c6c5e7436dd9d284 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 07:21:38 -0400 Subject: [PATCH 11/18] Reset startingPoint to 'uninitialized' on failure --- flang-rt/lib/runtime/extensions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 80df0b8eadf61..e15ec6c535fc0 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -330,10 +330,12 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { struct tm timeInfo; #ifdef _WIN32 if (localtime_s(&timeInfo, &now)) { + startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); return FAIL_SECNDS; } #else if (!localtime_r(&now, &timeInfo)) { + startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); return FAIL_SECNDS; } #endif From 59452645984b53d56df401bd142b4afd6d6feb5a Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 14:34:25 -0400 Subject: [PATCH 12/18] Changed currentStartingPoint initialization to use loop in all cases --- flang-rt/lib/runtime/extensions.cpp | 80 +++++++++++++++++++---------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index e15ec6c535fc0..f96584d73a859 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -322,41 +322,65 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // comes out to about 194 days. Thus, need to pick a starting point. // Given the description of this function, midnight of the current // day is the best starting point. + // + // In addition, use atomic operations for thread safety. startingPoint + // also acts as a state variable that can take on the following values: + // TIME_UNINITIALIZED to indicate that it's not initialized, + // TIME_INITIALIZING to indicate that it is being initialized, + // any other value to indicate the starting point time. static std::atomic startingPoint{TIME_UNINITIALIZED}; - std::time_t expected{TIME_UNINITIALIZED}; std::time_t localStartingPoint{TIME_UNINITIALIZED}; - if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING)) { - // This thread is doing initialization of startingPoint - struct tm timeInfo; + // Use retry logic to ensure that in case of multiple threads, one thread + // will perform initialization and the other threads wait their turn. + for (;;) { + // "Acquire" will show writes from other threads. + std::time_t currentStartingPoint = startingPoint.load( + std::memory_order_acquire); + if (currentStartingPoint > TIME_INITIALIZING) { + // Initialization was already done, use the starting point value + localStartingPoint = currentStartingPoint; + break; + } else if (currentStartingPoint == TIME_INITIALIZING) { + // Some other thread is currently initializing + std::this_thread::yield(); + continue; + } else if (currentStartingPoint == TIME_UNINITIALIZED) { + // Try to start initialization + std::time_t expected{TIME_UNINITIALIZED}; + if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING, + std::memory_order_acq_rel, // "Aquire and release" on success + std::memory_order_acquire)) { // "Aquire" on failure + // This thread is doing initialization of startingPoint + struct tm timeInfo; #ifdef _WIN32 - if (localtime_s(&timeInfo, &now)) { - startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); - return FAIL_SECNDS; - } + if (localtime_s(&timeInfo, &now)) { #else - if (!localtime_r(&now, &timeInfo)) { - startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); - return FAIL_SECNDS; - } + if (!localtime_r(&now, &timeInfo)) { #endif - // Back to midnight - timeInfo.tm_hour = 0; - timeInfo.tm_min = 0; - timeInfo.tm_sec = 0; - std::time_t midnight{std::mktime(&timeInfo)}; - if (midnight == FAIL_TIME) { - return FAIL_SECNDS; - } - localStartingPoint = midnight; - startingPoint.store(midnight, std::memory_order_release); - } else { - // This thread is not doing initialization of startingPoint, need to wait - // for initialization to complete. - while ((localStartingPoint = startingPoint.load( - std::memory_order_acquire)) <= TIME_INITIALIZING) { - std::this_thread::yield(); + // "Relaxed" ensures atomicity, but not ordering + startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); + return FAIL_SECNDS; + } + // Back to midnight + timeInfo.tm_hour = 0; + timeInfo.tm_min = 0; + timeInfo.tm_sec = 0; + localStartingPoint = std::mktime(&timeInfo); + if (localStartingPoint == FAIL_TIME) { + startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); + return FAIL_SECNDS; + } + // "Release" will make this value available to other threads + startingPoint.store(localStartingPoint, std::memory_order_release); + } else { + // This thread couln't start initialization. Try again. + continue; + } } } + if (localStartingPoint <= TIME_INITIALIZING) { + return FAIL_SECNDS; + } double diffStartingPoint{std::difftime(now, localStartingPoint)}; return static_cast(diffStartingPoint) - *refTime; } From 5cdb74d455928cd3b790a77a01ec003516480d80 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 14:36:02 -0400 Subject: [PATCH 13/18] clang-format --- flang-rt/lib/runtime/extensions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index f96584d73a859..98e41964344e7 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -334,8 +334,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // will perform initialization and the other threads wait their turn. for (;;) { // "Acquire" will show writes from other threads. - std::time_t currentStartingPoint = startingPoint.load( - std::memory_order_acquire); + std::time_t currentStartingPoint = + startingPoint.load(std::memory_order_acquire); if (currentStartingPoint > TIME_INITIALIZING) { // Initialization was already done, use the starting point value localStartingPoint = currentStartingPoint; @@ -348,8 +348,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // Try to start initialization std::time_t expected{TIME_UNINITIALIZED}; if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING, - std::memory_order_acq_rel, // "Aquire and release" on success - std::memory_order_acquire)) { // "Aquire" on failure + std::memory_order_acq_rel, // "Aquire and release" on success + std::memory_order_acquire)) { // "Aquire" on failure // This thread is doing initialization of startingPoint struct tm timeInfo; #ifdef _WIN32 From 599713e1cb986d73f6776802649a13301145ebf7 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 16:17:20 -0400 Subject: [PATCH 14/18] Code review feedback --- flang-rt/lib/runtime/extensions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 98e41964344e7..ca8d5ac9cbdfa 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -334,8 +334,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // will perform initialization and the other threads wait their turn. for (;;) { // "Acquire" will show writes from other threads. - std::time_t currentStartingPoint = - startingPoint.load(std::memory_order_acquire); + std::time_t currentStartingPoint{startingPoint.load( + std::memory_order_acquire)}; if (currentStartingPoint > TIME_INITIALIZING) { // Initialization was already done, use the starting point value localStartingPoint = currentStartingPoint; @@ -348,8 +348,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // Try to start initialization std::time_t expected{TIME_UNINITIALIZED}; if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING, - std::memory_order_acq_rel, // "Aquire and release" on success - std::memory_order_acquire)) { // "Aquire" on failure + std::memory_order_acq_rel, // "Acquire and release" on success + std::memory_order_acquire)) { // "Acquire" on failure // This thread is doing initialization of startingPoint struct tm timeInfo; #ifdef _WIN32 @@ -372,6 +372,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { } // "Release" will make this value available to other threads startingPoint.store(localStartingPoint, std::memory_order_release); + break; } else { // This thread couln't start initialization. Try again. continue; From 4ae3cd37c31ee283d29c062ef573184197e6d45e Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 16:32:40 -0400 Subject: [PATCH 15/18] clang-format --- flang-rt/lib/runtime/extensions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index ca8d5ac9cbdfa..b118046dc0002 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -334,8 +334,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // will perform initialization and the other threads wait their turn. for (;;) { // "Acquire" will show writes from other threads. - std::time_t currentStartingPoint{startingPoint.load( - std::memory_order_acquire)}; + std::time_t currentStartingPoint{ + startingPoint.load(std::memory_order_acquire)}; if (currentStartingPoint > TIME_INITIALIZING) { // Initialization was already done, use the starting point value localStartingPoint = currentStartingPoint; @@ -348,8 +348,8 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // Try to start initialization std::time_t expected{TIME_UNINITIALIZED}; if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING, - std::memory_order_acq_rel, // "Acquire and release" on success - std::memory_order_acquire)) { // "Acquire" on failure + std::memory_order_acq_rel, // "Acquire and release" on success + std::memory_order_acquire)) { // "Acquire" on failure // This thread is doing initialization of startingPoint struct tm timeInfo; #ifdef _WIN32 From fdd310a3768cd46585d56837a7da9003ea2653a2 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 18:20:24 -0400 Subject: [PATCH 16/18] Moved RTNAME(Secnds) declaration to extensions.h --- flang/include/flang/Runtime/command.h | 2 -- flang/include/flang/Runtime/extensions.h | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/flang/include/flang/Runtime/command.h b/flang/include/flang/Runtime/command.h index d22c2bc3956fe..19b486094da17 100644 --- a/flang/include/flang/Runtime/command.h +++ b/flang/include/flang/Runtime/command.h @@ -67,8 +67,6 @@ std::int32_t RTNAME(Hostnm)( std::int32_t RTNAME(PutEnv)( const char *str, size_t str_length, const char *sourceFile, int line); -float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line); - // Calls unlink() std::int32_t RTNAME(Unlink)( const char *path, size_t pathLength, const char *sourceFile, int line); diff --git a/flang/include/flang/Runtime/extensions.h b/flang/include/flang/Runtime/extensions.h index 355808a846f13..9a100cec9e6b9 100644 --- a/flang/include/flang/Runtime/extensions.h +++ b/flang/include/flang/Runtime/extensions.h @@ -92,6 +92,7 @@ int FORTRAN_PROCEDURE_NAME(mclock)(); // GNU extension subroutine SECNDS(refTime) float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime); +float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line); } // extern "C" #endif // FORTRAN_RUNTIME_EXTENSIONS_H_ From 1cf3a462e561212e98da84caed50ea02927c943c Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 20:54:58 -0400 Subject: [PATCH 17/18] Simplified the implementation based on Peter's suggestion --- flang-rt/lib/runtime/extensions.cpp | 94 ++++++++++++----------------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index b118046dc0002..092c2e3abd80e 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -310,7 +310,6 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // Failure code for time functions that return std::time_t constexpr std::time_t FAIL_TIME{std::time_t{-1}}; constexpr std::time_t TIME_UNINITIALIZED{std::time_t{0}}; - constexpr std::time_t TIME_INITIALIZING{std::time_t{1}}; if (!refTime) { return FAIL_SECNDS; } @@ -319,68 +318,51 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { return FAIL_SECNDS; } // In float result, we can only precisely store 2^24 seconds, which - // comes out to about 194 days. Thus, need to pick a starting point. + // comes out to about 194 days. Thus, need to pick a starting point, + // which will allow us to keep the time diffs as precise as possible. // Given the description of this function, midnight of the current // day is the best starting point. - // - // In addition, use atomic operations for thread safety. startingPoint - // also acts as a state variable that can take on the following values: - // TIME_UNINITIALIZED to indicate that it's not initialized, - // TIME_INITIALIZING to indicate that it is being initialized, - // any other value to indicate the starting point time. static std::atomic startingPoint{TIME_UNINITIALIZED}; - std::time_t localStartingPoint{TIME_UNINITIALIZED}; - // Use retry logic to ensure that in case of multiple threads, one thread - // will perform initialization and the other threads wait their turn. - for (;;) { - // "Acquire" will show writes from other threads. - std::time_t currentStartingPoint{ - startingPoint.load(std::memory_order_acquire)}; - if (currentStartingPoint > TIME_INITIALIZING) { - // Initialization was already done, use the starting point value - localStartingPoint = currentStartingPoint; - break; - } else if (currentStartingPoint == TIME_INITIALIZING) { - // Some other thread is currently initializing - std::this_thread::yield(); - continue; - } else if (currentStartingPoint == TIME_UNINITIALIZED) { - // Try to start initialization - std::time_t expected{TIME_UNINITIALIZED}; - if (startingPoint.compare_exchange_strong(expected, TIME_INITIALIZING, - std::memory_order_acq_rel, // "Acquire and release" on success - std::memory_order_acquire)) { // "Acquire" on failure - // This thread is doing initialization of startingPoint - struct tm timeInfo; + // "Acquire" will give us writes from other threads. + std::time_t localStartingPoint{ + startingPoint.load(std::memory_order_acquire)}; + // Initialize startingPoint if we haven't initialized it yet or + // if we were passed 0.0f, which indicates to compute seconds from + // current day's midnight. + if (localStartingPoint == TIME_UNINITIALIZED || *refTime < 0.5f) { + // Compute midnight in the current timezone and try to initialize + // startingPoint with it. If there are any errors during computation, + // exit with error and hope that the other threads have better luck + // (or the user retries the call). + struct tm timeInfo; #ifdef _WIN32 - if (localtime_s(&timeInfo, &now)) { + if (localtime_s(&timeInfo, &now)) { #else - if (!localtime_r(&now, &timeInfo)) { + if (!localtime_r(&now, &timeInfo)) { #endif - // "Relaxed" ensures atomicity, but not ordering - startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); - return FAIL_SECNDS; - } - // Back to midnight - timeInfo.tm_hour = 0; - timeInfo.tm_min = 0; - timeInfo.tm_sec = 0; - localStartingPoint = std::mktime(&timeInfo); - if (localStartingPoint == FAIL_TIME) { - startingPoint.store(TIME_UNINITIALIZED, std::memory_order_relaxed); - return FAIL_SECNDS; - } - // "Release" will make this value available to other threads - startingPoint.store(localStartingPoint, std::memory_order_release); - break; - } else { - // This thread couln't start initialization. Try again. - continue; - } + return FAIL_SECNDS; + } + // Back to midnight + timeInfo.tm_hour = 0; + timeInfo.tm_min = 0; + timeInfo.tm_sec = 0; + localStartingPoint = std::mktime(&timeInfo); + if (localStartingPoint == FAIL_TIME) { + return FAIL_SECNDS; + } + INTERNAL_CHECK(localStartingPoint > TIME_UNINITIALIZED); + // Attempt to atomically set startingPoint to localStartingPoint + std::time_t expected{TIME_UNINITIALIZED}; + if (startingPoint.compare_exchange_strong(expected, localStartingPoint, + std::memory_order_acq_rel, // "Acquire and release" on success + std::memory_order_acquire)) { // "Acquire" on failure + // startingPoint was set to localStartingPoint + } else { + // startingPoint was already initialized and its value was loaded + // into `expected`. Discard our precomputed midnight value in favor + // of the one from startingPoint. + localStartingPoint = expected; } - } - if (localStartingPoint <= TIME_INITIALIZING) { - return FAIL_SECNDS; } double diffStartingPoint{std::difftime(now, localStartingPoint)}; return static_cast(diffStartingPoint) - *refTime; From aa3d3d4a11e61cd08cda3ca7a15d8584c1343356 Mon Sep 17 00:00:00 2001 From: Eugene Epshteyn Date: Tue, 5 Aug 2025 20:59:02 -0400 Subject: [PATCH 18/18] clang-format --- flang-rt/lib/runtime/extensions.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index 092c2e3abd80e..bc86917c00828 100644 --- a/flang-rt/lib/runtime/extensions.cpp +++ b/flang-rt/lib/runtime/extensions.cpp @@ -324,8 +324,7 @@ float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { // day is the best starting point. static std::atomic startingPoint{TIME_UNINITIALIZED}; // "Acquire" will give us writes from other threads. - std::time_t localStartingPoint{ - startingPoint.load(std::memory_order_acquire)}; + std::time_t localStartingPoint{startingPoint.load(std::memory_order_acquire)}; // Initialize startingPoint if we haven't initialized it yet or // if we were passed 0.0f, which indicates to compute seconds from // current day's midnight.