1 //===-- runtime/time-intrinsic.cpp ----------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // Implements time-related intrinsic subroutines.
11 #include "flang/Runtime/time-intrinsic.h"
12 #include "terminator.h"
14 #include "flang/Runtime/cpp-type.h"
15 #include "flang/Runtime/descriptor.h"
23 #include "flang/Common/windows-include.h"
25 #include <sys/time.h> // gettimeofday
26 #include <sys/times.h>
30 // CPU_TIME (Fortran 2018 16.9.57)
31 // SYSTEM_CLOCK (Fortran 2018 16.9.168)
33 // We can use std::clock() from the <ctime> header as a fallback implementation
34 // that should be available everywhere. This may not provide the best resolution
35 // and is particularly troublesome on (some?) POSIX systems where CLOCKS_PER_SEC
36 // is defined as 10^6 regardless of the actual precision of std::clock().
37 // Therefore, we will usually prefer platform-specific alternatives when they
40 // We can use SFINAE to choose a platform-specific alternative. To do so, we
41 // introduce a helper function template, whose overload set will contain only
42 // implementations relying on interfaces which are actually available. Each
43 // overload will have a dummy parameter whose type indicates whether or not it
44 // should be preferred. Any other parameters required for SFINAE should have
45 // default values provided.
47 // Types for the dummy parameter indicating the priority of a given overload.
48 // We will invoke our helper with an integer literal argument, so the overload
49 // with the highest priority should have the type int.
50 using fallback_implementation
= double;
51 using preferred_implementation
= int;
53 // This is the fallback implementation, which should work everywhere.
54 template <typename Unused
= void> double GetCpuTime(fallback_implementation
) {
55 std::clock_t timestamp
{std::clock()};
56 if (timestamp
!= static_cast<std::clock_t>(-1)) {
57 return static_cast<double>(timestamp
) / CLOCKS_PER_SEC
;
59 // Return some negative value to represent failure.
63 #if defined __MINGW32__
64 // clock_gettime is implemented in the pthread library for MinGW.
65 // Using it here would mean that all programs that link libFortranRuntime are
66 // required to also link to pthread. Instead, don't use the function.
67 #undef CLOCKID_CPU_TIME
68 #undef CLOCKID_ELAPSED_TIME
70 // Determine what clock to use for CPU time.
71 #if defined CLOCK_PROCESS_CPUTIME_ID
72 #define CLOCKID_CPU_TIME CLOCK_PROCESS_CPUTIME_ID
73 #elif defined CLOCK_THREAD_CPUTIME_ID
74 #define CLOCKID_CPU_TIME CLOCK_THREAD_CPUTIME_ID
76 #undef CLOCKID_CPU_TIME
79 // Determine what clock to use for elapsed time.
80 #if defined CLOCK_MONOTONIC
81 #define CLOCKID_ELAPSED_TIME CLOCK_MONOTONIC
82 #elif defined CLOCK_REALTIME
83 #define CLOCKID_ELAPSED_TIME CLOCK_REALTIME
85 #undef CLOCKID_ELAPSED_TIME
89 #ifdef CLOCKID_CPU_TIME
90 // POSIX implementation using clock_gettime. This is only enabled where
91 // clock_gettime is available.
92 template <typename T
= int, typename U
= struct timespec
>
93 double GetCpuTime(preferred_implementation
,
94 // We need some dummy parameters to pass to decltype(clock_gettime).
95 T ClockId
= 0, U
*Timespec
= nullptr,
96 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
97 struct timespec tspec
;
98 if (clock_gettime(CLOCKID_CPU_TIME
, &tspec
) == 0) {
99 return tspec
.tv_nsec
* 1.0e-9 + tspec
.tv_sec
;
101 // Return some negative value to represent failure.
104 #endif // CLOCKID_CPU_TIME
106 using count_t
= std::int64_t;
107 using unsigned_count_t
= std::uint64_t;
109 // POSIX implementation using clock_gettime where available. The clock_gettime
110 // result is in nanoseconds, which is converted as necessary to
111 // - deciseconds for kind 1
112 // - milliseconds for kinds 2, 4
113 // - nanoseconds for kinds 8, 16
114 constexpr unsigned_count_t DS_PER_SEC
{10u};
115 constexpr unsigned_count_t MS_PER_SEC
{1'000u};
116 constexpr unsigned_count_t NS_PER_SEC
{1'000'000'000u};
118 // Computes HUGE(INT(0,kind)) as an unsigned integer value.
119 static constexpr inline unsigned_count_t
GetHUGE(int kind
) {
123 return (unsigned_count_t
{1} << ((8 * kind
) - 1)) - 1;
126 // Function converts a std::timespec_t into the desired count to
127 // be returned by the timing functions in accordance with the requested
128 // kind at the call site.
129 count_t
ConvertTimeSpecToCount(int kind
, const struct timespec
&tspec
) {
130 const unsigned_count_t huge
{GetHUGE(kind
)};
131 unsigned_count_t sec
{static_cast<unsigned_count_t
>(tspec
.tv_sec
)};
132 unsigned_count_t nsec
{static_cast<unsigned_count_t
>(tspec
.tv_nsec
)};
134 return (sec
* NS_PER_SEC
+ nsec
) % (huge
+ 1);
135 } else if (kind
>= 2) {
136 return (sec
* MS_PER_SEC
+ (nsec
/ (NS_PER_SEC
/ MS_PER_SEC
))) % (huge
+ 1);
137 } else { // kind == 1
138 return (sec
* DS_PER_SEC
+ (nsec
/ (NS_PER_SEC
/ DS_PER_SEC
))) % (huge
+ 1);
143 // This is the fallback implementation, which should work everywhere.
144 template <typename Unused
= void>
145 count_t
GetSystemClockCount(int kind
, fallback_implementation
) {
146 struct timespec tspec
;
148 if (timespec_get(&tspec
, TIME_UTC
) < 0) {
149 // Return -HUGE(COUNT) to represent failure.
150 return -static_cast<count_t
>(GetHUGE(kind
));
153 // Compute the timestamp as seconds plus nanoseconds in accordance
154 // with the requested kind at the call site.
155 return ConvertTimeSpecToCount(kind
, tspec
);
159 template <typename Unused
= void>
160 count_t
GetSystemClockCountRate(int kind
, fallback_implementation
) {
161 return kind
>= 8 ? NS_PER_SEC
: kind
>= 2 ? MS_PER_SEC
: DS_PER_SEC
;
164 template <typename Unused
= void>
165 count_t
GetSystemClockCountMax(int kind
, fallback_implementation
) {
166 unsigned_count_t maxCount
{GetHUGE(kind
)};
170 #ifdef CLOCKID_ELAPSED_TIME
171 template <typename T
= int, typename U
= struct timespec
>
172 count_t
GetSystemClockCount(int kind
, preferred_implementation
,
173 // We need some dummy parameters to pass to decltype(clock_gettime).
174 T ClockId
= 0, U
*Timespec
= nullptr,
175 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
176 struct timespec tspec
;
177 const unsigned_count_t huge
{GetHUGE(kind
)};
178 if (clock_gettime(CLOCKID_ELAPSED_TIME
, &tspec
) != 0) {
179 return -huge
; // failure
182 // Compute the timestamp as seconds plus nanoseconds in accordance
183 // with the requested kind at the call site.
184 return ConvertTimeSpecToCount(kind
, tspec
);
186 #endif // CLOCKID_ELAPSED_TIME
188 template <typename T
= int, typename U
= struct timespec
>
189 count_t
GetSystemClockCountRate(int kind
, preferred_implementation
,
190 // We need some dummy parameters to pass to decltype(clock_gettime).
191 T ClockId
= 0, U
*Timespec
= nullptr,
192 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
193 return kind
>= 8 ? NS_PER_SEC
: kind
>= 2 ? MS_PER_SEC
: DS_PER_SEC
;
196 template <typename T
= int, typename U
= struct timespec
>
197 count_t
GetSystemClockCountMax(int kind
, preferred_implementation
,
198 // We need some dummy parameters to pass to decltype(clock_gettime).
199 T ClockId
= 0, U
*Timespec
= nullptr,
200 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
201 return GetHUGE(kind
);
204 // DATE_AND_TIME (Fortran 2018 16.9.59)
206 // Helper to set an integer value to -HUGE
207 template <int KIND
> struct StoreNegativeHugeAt
{
209 const Fortran::runtime::Descriptor
&result
, std::size_t at
) const {
210 *result
.ZeroBasedIndexedElement
<Fortran::runtime::CppTypeFor
<
211 Fortran::common::TypeCategory::Integer
, KIND
>>(at
) =
212 -std::numeric_limits
<Fortran::runtime::CppTypeFor
<
213 Fortran::common::TypeCategory::Integer
, KIND
>>::max();
217 // Default implementation when date and time information is not available (set
218 // strings to blanks and values to -HUGE as defined by the standard).
219 static void DateAndTimeUnavailable(Fortran::runtime::Terminator
&terminator
,
220 char *date
, std::size_t dateChars
, char *time
, std::size_t timeChars
,
221 char *zone
, std::size_t zoneChars
,
222 const Fortran::runtime::Descriptor
*values
) {
224 std::memset(date
, static_cast<int>(' '), dateChars
);
227 std::memset(time
, static_cast<int>(' '), timeChars
);
230 std::memset(zone
, static_cast<int>(' '), zoneChars
);
233 auto typeCode
{values
->type().GetCategoryAndKind()};
234 RUNTIME_CHECK(terminator
,
235 values
->rank() == 1 && values
->GetDimension(0).Extent() >= 8 &&
237 typeCode
->first
== Fortran::common::TypeCategory::Integer
);
238 // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
240 int kind
{typeCode
->second
};
241 RUNTIME_CHECK(terminator
, kind
!= 1);
242 for (std::size_t i
= 0; i
< 8; ++i
) {
243 Fortran::runtime::ApplyIntegerKind
<StoreNegativeHugeAt
, void>(
244 kind
, terminator
, *values
, i
);
251 // Compute the time difference from GMT/UTC to get around the behavior of
252 // strfname on AIX that requires setting an environment variable for numeric
254 // The ZONE and the VALUES(4) arguments of the DATE_AND_TIME intrinsic has
255 // the resolution to the minute.
256 static int computeUTCDiff(const tm
&localTime
, bool *err
) {
258 const time_t timer
{mktime(const_cast<tm
*>(&localTime
))};
264 // Get the GMT/UTC time
265 if (gmtime_r(&timer
, &utcTime
) == nullptr) {
270 // Adjust for day difference
271 auto dayDiff
{localTime
.tm_mday
- utcTime
.tm_mday
};
272 auto localHr
{localTime
.tm_hour
};
277 utcTime
.tm_hour
+= 24;
279 } else if (dayDiff
< 0) {
281 utcTime
.tm_hour
+= 24;
286 return (localHr
* 60 + localTime
.tm_min
) -
287 (utcTime
.tm_hour
* 60 + utcTime
.tm_min
);
291 static std::size_t getUTCOffsetToBuffer(
292 char *buffer
, const std::size_t &buffSize
, tm
*localTime
) {
294 // format: +HHMM or -HHMM
296 auto utcOffset
{computeUTCDiff(*localTime
, &err
)};
297 auto hour
{utcOffset
/ 60};
298 auto hrMin
{hour
* 100 + (utcOffset
- hour
* 60)};
299 auto n
{sprintf(buffer
, "%+05d", hrMin
)};
300 return err
? 0 : n
+ 1;
302 return std::strftime(buffer
, buffSize
, "%z", localTime
);
306 // SFINAE helper to return the struct tm.tm_gmtoff which is not a POSIX standard
308 template <int KIND
, typename TM
= struct tm
>
309 Fortran::runtime::CppTypeFor
<Fortran::common::TypeCategory::Integer
, KIND
>
310 GetGmtOffset(const TM
&tm
, preferred_implementation
,
311 decltype(tm
.tm_gmtoff
) *Enabled
= nullptr) {
312 // Returns the GMT offset in minutes.
313 return tm
.tm_gmtoff
/ 60;
315 template <int KIND
, typename TM
= struct tm
>
316 Fortran::runtime::CppTypeFor
<Fortran::common::TypeCategory::Integer
, KIND
>
317 GetGmtOffset(const TM
&tm
, fallback_implementation
) {
318 // tm.tm_gmtoff is not available, there may be platform dependent alternatives
319 // (such as using timezone from <time.h> when available), but so far just
320 // return -HUGE to report that this information is not available.
321 const auto negHuge
{-std::numeric_limits
<Fortran::runtime::CppTypeFor
<
322 Fortran::common::TypeCategory::Integer
, KIND
>>::max()};
325 auto diff
{computeUTCDiff(tm
, &err
)};
335 template <typename TM
= struct tm
> struct GmtOffsetHelper
{
336 template <int KIND
> struct StoreGmtOffset
{
337 void operator()(const Fortran::runtime::Descriptor
&result
, std::size_t at
,
339 *result
.ZeroBasedIndexedElement
<Fortran::runtime::CppTypeFor
<
340 Fortran::common::TypeCategory::Integer
, KIND
>>(at
) =
341 GetGmtOffset
<KIND
>(tm
, 0);
346 // Dispatch to posix implementation where gettimeofday and localtime_r are
348 static void GetDateAndTime(Fortran::runtime::Terminator
&terminator
, char *date
,
349 std::size_t dateChars
, char *time
, std::size_t timeChars
, char *zone
,
350 std::size_t zoneChars
, const Fortran::runtime::Descriptor
*values
) {
353 if (gettimeofday(&t
, nullptr) != 0) {
354 DateAndTimeUnavailable(
355 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
358 time_t timer
{t
.tv_sec
};
360 localtime_r(&timer
, &localTime
);
361 std::intmax_t ms
{t
.tv_usec
/ 1000};
363 static constexpr std::size_t buffSize
{16};
364 char buffer
[buffSize
];
365 auto copyBufferAndPad
{
366 [&](char *dest
, std::size_t destChars
, std::size_t len
) {
367 auto copyLen
{std::min(len
, destChars
)};
368 std::memcpy(dest
, buffer
, copyLen
);
369 for (auto i
{copyLen
}; i
< destChars
; ++i
) {
374 auto len
= std::strftime(buffer
, buffSize
, "%Y%m%d", &localTime
);
375 copyBufferAndPad(date
, dateChars
, len
);
378 auto len
{std::snprintf(buffer
, buffSize
, "%02d%02d%02d.%03jd",
379 localTime
.tm_hour
, localTime
.tm_min
, localTime
.tm_sec
, ms
)};
380 copyBufferAndPad(time
, timeChars
, len
);
383 // Note: this may leave the buffer empty on many platforms. Classic flang
384 // has a much more complex way of doing this (see __io_timezone in classic
386 auto len
{getUTCOffsetToBuffer(buffer
, buffSize
, &localTime
)};
387 copyBufferAndPad(zone
, zoneChars
, len
);
390 auto typeCode
{values
->type().GetCategoryAndKind()};
391 RUNTIME_CHECK(terminator
,
392 values
->rank() == 1 && values
->GetDimension(0).Extent() >= 8 &&
394 typeCode
->first
== Fortran::common::TypeCategory::Integer
);
395 // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
397 int kind
{typeCode
->second
};
398 RUNTIME_CHECK(terminator
, kind
!= 1);
399 auto storeIntegerAt
= [&](std::size_t atIndex
, std::int64_t value
) {
400 Fortran::runtime::ApplyIntegerKind
<Fortran::runtime::StoreIntegerAt
,
401 void>(kind
, terminator
, *values
, atIndex
, value
);
403 storeIntegerAt(0, localTime
.tm_year
+ 1900);
404 storeIntegerAt(1, localTime
.tm_mon
+ 1);
405 storeIntegerAt(2, localTime
.tm_mday
);
406 Fortran::runtime::ApplyIntegerKind
<
407 GmtOffsetHelper
<struct tm
>::StoreGmtOffset
, void>(
408 kind
, terminator
, *values
, 3, localTime
);
409 storeIntegerAt(4, localTime
.tm_hour
);
410 storeIntegerAt(5, localTime
.tm_min
);
411 storeIntegerAt(6, localTime
.tm_sec
);
412 storeIntegerAt(7, ms
);
417 // Fallback implementation where gettimeofday or localtime_r are not both
418 // available (e.g. windows).
419 static void GetDateAndTime(Fortran::runtime::Terminator
&terminator
, char *date
,
420 std::size_t dateChars
, char *time
, std::size_t timeChars
, char *zone
,
421 std::size_t zoneChars
, const Fortran::runtime::Descriptor
*values
) {
422 // TODO: An actual implementation for non Posix system should be added.
423 // So far, implement as if the date and time is not available on those
425 DateAndTimeUnavailable(
426 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
431 namespace Fortran::runtime
{
434 double RTNAME(CpuTime
)() { return GetCpuTime(0); }
436 std::int64_t RTNAME(SystemClockCount
)(int kind
) {
437 return GetSystemClockCount(kind
, 0);
440 std::int64_t RTNAME(SystemClockCountRate
)(int kind
) {
441 return GetSystemClockCountRate(kind
, 0);
444 std::int64_t RTNAME(SystemClockCountMax
)(int kind
) {
445 return GetSystemClockCountMax(kind
, 0);
448 void RTNAME(DateAndTime
)(char *date
, std::size_t dateChars
, char *time
,
449 std::size_t timeChars
, char *zone
, std::size_t zoneChars
,
450 const char *source
, int line
, const Descriptor
*values
) {
451 Fortran::runtime::Terminator terminator
{source
, line
};
452 return GetDateAndTime(
453 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
456 void RTNAME(Etime
)(const Descriptor
*values
, const Descriptor
*time
,
457 const char *sourceFile
, int line
) {
458 Fortran::runtime::Terminator terminator
{sourceFile
, line
};
460 double usrTime
= -1.0, sysTime
= -1.0, realTime
= -1.0;
463 FILETIME creationTime
;
468 if (GetProcessTimes(GetCurrentProcess(), &creationTime
, &exitTime
,
469 &kernelTime
, &userTime
) == 0) {
470 ULARGE_INTEGER userSystemTime
;
471 ULARGE_INTEGER kernelSystemTime
;
473 memcpy(&userSystemTime
, &userTime
, sizeof(FILETIME
));
474 memcpy(&kernelSystemTime
, &kernelTime
, sizeof(FILETIME
));
476 usrTime
= ((double)(userSystemTime
.QuadPart
)) / 10000000.0;
477 sysTime
= ((double)(kernelSystemTime
.QuadPart
)) / 10000000.0;
478 realTime
= usrTime
+ sysTime
;
482 if (times(&tms
) != (clock_t)-1) {
483 usrTime
= ((double)(tms
.tms_utime
)) / sysconf(_SC_CLK_TCK
);
484 sysTime
= ((double)(tms
.tms_stime
)) / sysconf(_SC_CLK_TCK
);
485 realTime
= usrTime
+ sysTime
;
490 auto typeCode
{values
->type().GetCategoryAndKind()};
491 // ETIME values argument must have decimal range == 2.
492 RUNTIME_CHECK(terminator
,
493 values
->rank() == 1 && typeCode
&&
494 typeCode
->first
== Fortran::common::TypeCategory::Real
);
495 // Only accept KIND=4 here.
496 int kind
{typeCode
->second
};
497 RUNTIME_CHECK(terminator
, kind
== 4);
498 auto extent
{values
->GetDimension(0).Extent()};
500 ApplyFloatingPointKind
<StoreFloatingPointAt
, void>(
501 kind
, terminator
, *values
, /* atIndex = */ 0, usrTime
);
504 ApplyFloatingPointKind
<StoreFloatingPointAt
, void>(
505 kind
, terminator
, *values
, /* atIndex = */ 1, sysTime
);
510 auto typeCode
{time
->type().GetCategoryAndKind()};
511 // ETIME time argument must have decimal range == 0.
512 RUNTIME_CHECK(terminator
,
513 time
->rank() == 0 && typeCode
&&
514 typeCode
->first
== Fortran::common::TypeCategory::Real
);
515 // Only accept KIND=4 here.
516 int kind
{typeCode
->second
};
517 RUNTIME_CHECK(terminator
, kind
== 4);
519 ApplyFloatingPointKind
<StoreFloatingPointAt
, void>(
520 kind
, terminator
, *time
, /* atIndex = */ 0, realTime
);
525 } // namespace Fortran::runtime