Skip to content

Commit cae7beb

Browse files
[flang-rt] Runtime implementation of extended intrinsic function SECNDS() (#152021)
Until the compiler part is fully hooked up via #151878, tested this using `external`: ``` external secnds real s1, s2 s1 = secnds(0.0) print *, "Seconds from midnight:", s1 call sleep(2) s2 = secnds(s1) print *, "Seconds from s1", s2 print *, "Seconds from midnight:", secnds(0.0) end ```
1 parent 0491d8b commit cae7beb

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

flang-rt/lib/runtime/extensions.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "flang/Runtime/entry-names.h"
1919
#include "flang/Runtime/io-api.h"
2020
#include "flang/Runtime/iostat-consts.h"
21+
#include <atomic>
2122
#include <chrono>
2223
#include <cstdio>
2324
#include <cstring>
@@ -57,10 +58,76 @@ inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time,
5758
#include <direct.h>
5859
#endif
5960

60-
extern "C" {
61-
6261
namespace Fortran::runtime {
6362

63+
// Common implementation that could be used for either SECNDS() or SECNDSD(),
64+
// which are defined for float or double.
65+
template <typename T> T SecndsImpl(T *refTime) {
66+
static_assert(std::is_same<T, float>::value || std::is_same<T, double>::value,
67+
"T must be float or double");
68+
constexpr T FAIL_SECNDS{T{-1.0}}; // Failure code for this function
69+
// Failure code for time functions that return std::time_t
70+
constexpr std::time_t FAIL_TIME{std::time_t{-1}};
71+
constexpr std::time_t TIME_UNINITIALIZED{std::time_t{0}};
72+
if (!refTime) {
73+
return FAIL_SECNDS;
74+
}
75+
std::time_t now{std::time(nullptr)};
76+
if (now == FAIL_TIME) {
77+
return FAIL_SECNDS;
78+
}
79+
// In case we are using a float result, we can only precisely store
80+
// 2^24 seconds, which comes out to about 194 days. Thus, need to pick
81+
// a starting point, which will allow us to keep the time diffs as precise
82+
// as possible. Given the description of this function, midnight of the
83+
// current day is the best starting point.
84+
static std::atomic<std::time_t> startingPoint{TIME_UNINITIALIZED};
85+
// "Acquire" will give us writes from other threads.
86+
std::time_t localStartingPoint{startingPoint.load(std::memory_order_acquire)};
87+
// Initialize startingPoint if we haven't initialized it yet or
88+
// if we were passed 0.0, which indicates to compute seconds from
89+
// current day's midnight.
90+
if (localStartingPoint == TIME_UNINITIALIZED || *refTime == 0.0) {
91+
// Compute midnight in the current timezone and try to initialize
92+
// startingPoint with it. If there are any errors during computation,
93+
// exit with error and hope that the other threads have better luck
94+
// (or the user retries the call).
95+
struct tm timeInfo;
96+
#ifdef _WIN32
97+
if (localtime_s(&timeInfo, &now)) {
98+
#else
99+
if (!localtime_r(&now, &timeInfo)) {
100+
#endif
101+
return FAIL_SECNDS;
102+
}
103+
// Back to midnight
104+
timeInfo.tm_hour = 0;
105+
timeInfo.tm_min = 0;
106+
timeInfo.tm_sec = 0;
107+
localStartingPoint = std::mktime(&timeInfo);
108+
if (localStartingPoint == FAIL_TIME) {
109+
return FAIL_SECNDS;
110+
}
111+
INTERNAL_CHECK(localStartingPoint > TIME_UNINITIALIZED);
112+
// Attempt to atomically set startingPoint to localStartingPoint
113+
std::time_t expected{TIME_UNINITIALIZED};
114+
if (startingPoint.compare_exchange_strong(expected, localStartingPoint,
115+
std::memory_order_acq_rel, // "Acquire and release" on success
116+
std::memory_order_acquire)) { // "Acquire" on failure
117+
// startingPoint was set to localStartingPoint
118+
} else {
119+
// startingPoint was already initialized and its value was loaded
120+
// into `expected`. Discard our precomputed midnight value in favor
121+
// of the one from startingPoint.
122+
localStartingPoint = expected;
123+
}
124+
}
125+
double diffStartingPoint{std::difftime(now, localStartingPoint)};
126+
return static_cast<T>(diffStartingPoint) - *refTime;
127+
}
128+
129+
extern "C" {
130+
64131
gid_t RTNAME(GetGID)() {
65132
#ifdef _WIN32
66133
// Group IDs don't exist on Windows, return 1 to avoid errors
@@ -303,6 +370,17 @@ void FORTRAN_PROCEDURE_NAME(qsort)(int *array, int *len, int *isize,
303370
// PERROR(STRING)
304371
void RTNAME(Perror)(const char *str) { perror(str); }
305372

373+
// GNU extension function SECNDS(refTime)
374+
float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) {
375+
return SecndsImpl(refTime);
376+
}
377+
378+
float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line) {
379+
Terminator terminator{sourceFile, line};
380+
RUNTIME_CHECK(terminator, refTime != nullptr);
381+
return FORTRAN_PROCEDURE_NAME(secnds)(refTime);
382+
}
383+
306384
// GNU extension function TIME()
307385
std::int64_t RTNAME(time)() { return time(nullptr); }
308386

@@ -337,5 +415,6 @@ std::int64_t RTNAME(Ftell)(int unitNumber) {
337415
}
338416
} // namespace io
339417

340-
} // namespace Fortran::runtime
341418
} // extern "C"
419+
420+
} // namespace Fortran::runtime

flang/include/flang/Runtime/extensions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,9 @@ void RTNAME(Perror)(const char *str);
9090
// MCLOCK -- returns accumulated time in ticks
9191
int FORTRAN_PROCEDURE_NAME(mclock)();
9292

93+
// GNU extension subroutine SECNDS(refTime)
94+
float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime);
95+
float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line);
96+
9397
} // extern "C"
9498
#endif // FORTRAN_RUNTIME_EXTENSIONS_H_

0 commit comments

Comments
 (0)