|
18 | 18 | #include "flang/Runtime/entry-names.h"
|
19 | 19 | #include "flang/Runtime/io-api.h"
|
20 | 20 | #include "flang/Runtime/iostat-consts.h"
|
| 21 | +#include <atomic> |
21 | 22 | #include <chrono>
|
22 | 23 | #include <cstdio>
|
23 | 24 | #include <cstring>
|
@@ -57,10 +58,76 @@ inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time,
|
57 | 58 | #include <direct.h>
|
58 | 59 | #endif
|
59 | 60 |
|
60 |
| -extern "C" { |
61 |
| - |
62 | 61 | namespace Fortran::runtime {
|
63 | 62 |
|
| 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 | + |
64 | 131 | gid_t RTNAME(GetGID)() {
|
65 | 132 | #ifdef _WIN32
|
66 | 133 | // 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,
|
303 | 370 | // PERROR(STRING)
|
304 | 371 | void RTNAME(Perror)(const char *str) { perror(str); }
|
305 | 372 |
|
| 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 | + |
306 | 384 | // GNU extension function TIME()
|
307 | 385 | std::int64_t RTNAME(time)() { return time(nullptr); }
|
308 | 386 |
|
@@ -337,5 +415,6 @@ std::int64_t RTNAME(Ftell)(int unitNumber) {
|
337 | 415 | }
|
338 | 416 | } // namespace io
|
339 | 417 |
|
340 |
| -} // namespace Fortran::runtime |
341 | 418 | } // extern "C"
|
| 419 | + |
| 420 | +} // namespace Fortran::runtime |
0 commit comments