[flang] [llvm] [flang-rt] Runtime implementation of extended intrinsic function SECNDS() (PR #152021)

Eugene Epshteyn via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 5 17:59:21 PDT 2025


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/152021

>From 68a1a813d46719382da3a67a973eb310bccb6e2a Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
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<float>(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 <eepshteyn at nvidia.com>
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 <cerrno>
 #include <cstdlib>
 #include <limits>
@@ -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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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<float>(diffStartingPoint) - *refTime;
 }
 

>From 9c538783b3947458405912de0740d4497b548c40 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <cerrno>
 #include <cstdlib>
 #include <limits>
@@ -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<float>(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 <eepshteyn at nvidia.com>
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 <atomic>
 #include <chrono>
 #include <cstdio>
 #include <cstring>
@@ -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<std::time_t> 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<float>(diffStartingPoint) - *refTime;
 }
 

>From 9940825dc31baa5962dcf181bb115c1ed3047e3d Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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<std::time_t> 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<float>(diffStartingPoint) - *refTime;
 }

>From 5cdb74d455928cd3b790a77a01ec003516480d80 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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 <eepshteyn at nvidia.com>
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<std::time_t> 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<float>(diffStartingPoint) - *refTime;

>From aa3d3d4a11e61cd08cda3ca7a15d8584c1343356 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
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<std::time_t> 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.



More information about the llvm-commits mailing list