diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp index f6c39468d5655..bc86917c00828 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 @@ -303,6 +304,75 @@ 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}; // 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 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 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.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)) { +#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(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); } diff --git a/flang/include/flang/Runtime/extensions.h b/flang/include/flang/Runtime/extensions.h index b350204714431..9a100cec9e6b9 100644 --- a/flang/include/flang/Runtime/extensions.h +++ b/flang/include/flang/Runtime/extensions.h @@ -90,5 +90,9 @@ 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); +float RTNAME(Secnds)(float *refTime, const char *sourceFile, int line); + } // extern "C" #endif // FORTRAN_RUNTIME_EXTENSIONS_H_