diff options
Diffstat (limited to 'flang-rt/lib/runtime/extensions.cpp')
-rw-r--r-- | flang-rt/lib/runtime/extensions.cpp | 85 |
1 files changed, 82 insertions, 3 deletions
diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index f6c3946..a24810b 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> @@ -57,10 +58,76 @@ inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time, #include <direct.h> #endif -extern "C" { - namespace Fortran::runtime { +// Common implementation that could be used for either SECNDS() or SECNDSD(), +// which are defined for float or double. +template <typename T> T SecndsImpl(T *refTime) { + static_assert(std::is_same<T, float>::value || std::is_same<T, double>::value, + "T must be float or double"); + constexpr T FAIL_SECNDS{T{-1.0}}; // 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}}; + if (!refTime) { + return FAIL_SECNDS; + } + std::time_t now{std::time(nullptr)}; + if (now == FAIL_TIME) { + return FAIL_SECNDS; + } + // In case we are using a float result, we can only precisely store + // 2^24 seconds, which 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. + 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)}; + // Initialize startingPoint if we haven't initialized it yet or + // if we were passed 0.0, which indicates to compute seconds from + // current day's midnight. + if (localStartingPoint == TIME_UNINITIALIZED || *refTime == 0.0) { + // 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)) { +#else + if (!localtime_r(&now, &timeInfo)) { +#endif + 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; + } + } + double diffStartingPoint{std::difftime(now, localStartingPoint)}; + return static_cast<T>(diffStartingPoint) - *refTime; +} + +extern "C" { + gid_t RTNAME(GetGID)() { #ifdef _WIN32 // 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, // PERROR(STRING) void RTNAME(Perror)(const char *str) { perror(str); } +// GNU extension function SECNDS(refTime) +float FORTRAN_PROCEDURE_NAME(secnds)(float *refTime) { + return SecndsImpl(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); } @@ -337,5 +415,6 @@ std::int64_t RTNAME(Ftell)(int unitNumber) { } } // namespace io -} // namespace Fortran::runtime } // extern "C" + +} // namespace Fortran::runtime |