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 <sys/time.h> // gettimeofday
26 // CPU_TIME (Fortran 2018 16.9.57)
27 // SYSTEM_CLOCK (Fortran 2018 16.9.168)
29 // We can use std::clock() from the <ctime> header as a fallback implementation
30 // that should be available everywhere. This may not provide the best resolution
31 // and is particularly troublesome on (some?) POSIX systems where CLOCKS_PER_SEC
32 // is defined as 10^6 regardless of the actual precision of std::clock().
33 // Therefore, we will usually prefer platform-specific alternatives when they
36 // We can use SFINAE to choose a platform-specific alternative. To do so, we
37 // introduce a helper function template, whose overload set will contain only
38 // implementations relying on interfaces which are actually available. Each
39 // overload will have a dummy parameter whose type indicates whether or not it
40 // should be preferred. Any other parameters required for SFINAE should have
41 // default values provided.
43 // Types for the dummy parameter indicating the priority of a given overload.
44 // We will invoke our helper with an integer literal argument, so the overload
45 // with the highest priority should have the type int.
46 using fallback_implementation
= double;
47 using preferred_implementation
= int;
49 // This is the fallback implementation, which should work everywhere.
50 template <typename Unused
= void> double GetCpuTime(fallback_implementation
) {
51 std::clock_t timestamp
{std::clock()};
52 if (timestamp
!= static_cast<std::clock_t>(-1)) {
53 return static_cast<double>(timestamp
) / CLOCKS_PER_SEC
;
55 // Return some negative value to represent failure.
59 #if defined CLOCK_PROCESS_CPUTIME_ID
60 #define CLOCKID CLOCK_PROCESS_CPUTIME_ID
61 #elif defined CLOCK_THREAD_CPUTIME_ID
62 #define CLOCKID CLOCK_THREAD_CPUTIME_ID
63 #elif defined CLOCK_MONOTONIC
64 #define CLOCKID CLOCK_MONOTONIC
65 #elif defined CLOCK_REALTIME
66 #define CLOCKID CLOCK_REALTIME
72 // POSIX implementation using clock_gettime. This is only enabled where
73 // clock_gettime is available.
74 template <typename T
= int, typename U
= struct timespec
>
75 double GetCpuTime(preferred_implementation
,
76 // We need some dummy parameters to pass to decltype(clock_gettime).
77 T ClockId
= 0, U
*Timespec
= nullptr,
78 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
79 struct timespec tspec
;
80 if (clock_gettime(CLOCKID
, &tspec
) == 0) {
81 return tspec
.tv_nsec
* 1.0e-9 + tspec
.tv_sec
;
83 // Return some negative value to represent failure.
88 using count_t
= std::int64_t;
89 using unsigned_count_t
= std::uint64_t;
91 // Computes HUGE(INT(0,kind)) as an unsigned integer value.
92 static constexpr inline unsigned_count_t
GetHUGE(int kind
) {
96 return (unsigned_count_t
{1} << ((8 * kind
) - 1)) - 1;
99 // This is the fallback implementation, which should work everywhere. Note that
100 // in general we can't recover after std::clock has reached its maximum value.
101 template <typename Unused
= void>
102 count_t
GetSystemClockCount(int kind
, fallback_implementation
) {
103 std::clock_t timestamp
{std::clock()};
104 if (timestamp
== static_cast<std::clock_t>(-1)) {
105 // Return -HUGE(COUNT) to represent failure.
106 return -static_cast<count_t
>(GetHUGE(kind
));
108 // Convert the timestamp to std::uint64_t with wrap-around. The timestamp is
109 // most likely a floating-point value (since C'11), so compute the modulus
110 // carefully when one is required.
111 constexpr auto maxUnsignedCount
{std::numeric_limits
<unsigned_count_t
>::max()};
112 if constexpr (std::numeric_limits
<std::clock_t>::max() > maxUnsignedCount
) {
113 timestamp
-= maxUnsignedCount
* std::floor(timestamp
/ maxUnsignedCount
);
115 unsigned_count_t unsignedCount
{static_cast<unsigned_count_t
>(timestamp
)};
116 // Return the modulus of the unsigned integral count with HUGE(COUNT)+1.
117 // The result is a signed integer but never negative.
118 return static_cast<count_t
>(unsignedCount
% (GetHUGE(kind
) + 1));
121 template <typename Unused
= void>
122 count_t
GetSystemClockCountRate(int kind
, fallback_implementation
) {
123 return CLOCKS_PER_SEC
;
126 template <typename Unused
= void>
127 count_t
GetSystemClockCountMax(int kind
, fallback_implementation
) {
128 constexpr auto max_clock_t
{std::numeric_limits
<std::clock_t>::max()};
129 unsigned_count_t maxCount
{GetHUGE(kind
)};
130 return max_clock_t
<= maxCount
? static_cast<count_t
>(max_clock_t
)
131 : static_cast<count_t
>(maxCount
);
134 // POSIX implementation using clock_gettime where available. The clock_gettime
135 // result is in nanoseconds, which is converted as necessary to
136 // - deciseconds for kind 1
137 // - milliseconds for kinds 2, 4
138 // - nanoseconds for kinds 8, 16
139 constexpr unsigned_count_t DS_PER_SEC
{10u};
140 constexpr unsigned_count_t MS_PER_SEC
{1'000u};
141 constexpr unsigned_count_t NS_PER_SEC
{1'000'000'000u};
144 template <typename T
= int, typename U
= struct timespec
>
145 count_t
GetSystemClockCount(int kind
, preferred_implementation
,
146 // We need some dummy parameters to pass to decltype(clock_gettime).
147 T ClockId
= 0, U
*Timespec
= nullptr,
148 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
149 struct timespec tspec
;
150 const unsigned_count_t huge
{GetHUGE(kind
)};
151 if (clock_gettime(CLOCKID
, &tspec
) != 0) {
152 return -huge
; // failure
154 unsigned_count_t sec
{static_cast<unsigned_count_t
>(tspec
.tv_sec
)};
155 unsigned_count_t nsec
{static_cast<unsigned_count_t
>(tspec
.tv_nsec
)};
157 return (sec
* NS_PER_SEC
+ nsec
) % (huge
+ 1);
158 } else if (kind
>= 2) {
159 return (sec
* MS_PER_SEC
+ (nsec
/ (NS_PER_SEC
/ MS_PER_SEC
))) % (huge
+ 1);
160 } else { // kind == 1
161 return (sec
* DS_PER_SEC
+ (nsec
/ (NS_PER_SEC
/ DS_PER_SEC
))) % (huge
+ 1);
166 template <typename T
= int, typename U
= struct timespec
>
167 count_t
GetSystemClockCountRate(int kind
, preferred_implementation
,
168 // We need some dummy parameters to pass to decltype(clock_gettime).
169 T ClockId
= 0, U
*Timespec
= nullptr,
170 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
171 return kind
>= 8 ? NS_PER_SEC
: kind
>= 2 ? MS_PER_SEC
: DS_PER_SEC
;
174 template <typename T
= int, typename U
= struct timespec
>
175 count_t
GetSystemClockCountMax(int kind
, preferred_implementation
,
176 // We need some dummy parameters to pass to decltype(clock_gettime).
177 T ClockId
= 0, U
*Timespec
= nullptr,
178 decltype(clock_gettime(ClockId
, Timespec
)) *Enabled
= nullptr) {
179 return GetHUGE(kind
);
182 // DATE_AND_TIME (Fortran 2018 16.9.59)
184 // Helper to set an integer value to -HUGE
185 template <int KIND
> struct StoreNegativeHugeAt
{
187 const Fortran::runtime::Descriptor
&result
, std::size_t at
) const {
188 *result
.ZeroBasedIndexedElement
<Fortran::runtime::CppTypeFor
<
189 Fortran::common::TypeCategory::Integer
, KIND
>>(at
) =
190 -std::numeric_limits
<Fortran::runtime::CppTypeFor
<
191 Fortran::common::TypeCategory::Integer
, KIND
>>::max();
195 // Default implementation when date and time information is not available (set
196 // strings to blanks and values to -HUGE as defined by the standard).
197 static void DateAndTimeUnavailable(Fortran::runtime::Terminator
&terminator
,
198 char *date
, std::size_t dateChars
, char *time
, std::size_t timeChars
,
199 char *zone
, std::size_t zoneChars
,
200 const Fortran::runtime::Descriptor
*values
) {
202 std::memset(date
, static_cast<int>(' '), dateChars
);
205 std::memset(time
, static_cast<int>(' '), timeChars
);
208 std::memset(zone
, static_cast<int>(' '), zoneChars
);
211 auto typeCode
{values
->type().GetCategoryAndKind()};
212 RUNTIME_CHECK(terminator
,
213 values
->rank() == 1 && values
->GetDimension(0).Extent() >= 8 &&
215 typeCode
->first
== Fortran::common::TypeCategory::Integer
);
216 // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
218 int kind
{typeCode
->second
};
219 RUNTIME_CHECK(terminator
, kind
!= 1);
220 for (std::size_t i
= 0; i
< 8; ++i
) {
221 Fortran::runtime::ApplyIntegerKind
<StoreNegativeHugeAt
, void>(
222 kind
, terminator
, *values
, i
);
229 // SFINAE helper to return the struct tm.tm_gmtoff which is not a POSIX standard
231 template <int KIND
, typename TM
= struct tm
>
232 Fortran::runtime::CppTypeFor
<Fortran::common::TypeCategory::Integer
, KIND
>
233 GetGmtOffset(const TM
&tm
, preferred_implementation
,
234 decltype(tm
.tm_gmtoff
) *Enabled
= nullptr) {
235 // Returns the GMT offset in minutes.
236 return tm
.tm_gmtoff
/ 60;
238 template <int KIND
, typename TM
= struct tm
>
239 Fortran::runtime::CppTypeFor
<Fortran::common::TypeCategory::Integer
, KIND
>
240 GetGmtOffset(const TM
&tm
, fallback_implementation
) {
241 // tm.tm_gmtoff is not available, there may be platform dependent alternatives
242 // (such as using timezone from <time.h> when available), but so far just
243 // return -HUGE to report that this information is not available.
244 return -std::numeric_limits
<Fortran::runtime::CppTypeFor
<
245 Fortran::common::TypeCategory::Integer
, KIND
>>::max();
247 template <typename TM
= struct tm
> struct GmtOffsetHelper
{
248 template <int KIND
> struct StoreGmtOffset
{
249 void operator()(const Fortran::runtime::Descriptor
&result
, std::size_t at
,
251 *result
.ZeroBasedIndexedElement
<Fortran::runtime::CppTypeFor
<
252 Fortran::common::TypeCategory::Integer
, KIND
>>(at
) =
253 GetGmtOffset
<KIND
>(tm
, 0);
258 // Dispatch to posix implementation where gettimeofday and localtime_r are
260 static void GetDateAndTime(Fortran::runtime::Terminator
&terminator
, char *date
,
261 std::size_t dateChars
, char *time
, std::size_t timeChars
, char *zone
,
262 std::size_t zoneChars
, const Fortran::runtime::Descriptor
*values
) {
265 if (gettimeofday(&t
, nullptr) != 0) {
266 DateAndTimeUnavailable(
267 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
270 time_t timer
{t
.tv_sec
};
272 localtime_r(&timer
, &localTime
);
273 std::intmax_t ms
{t
.tv_usec
/ 1000};
275 static constexpr std::size_t buffSize
{16};
276 char buffer
[buffSize
];
277 auto copyBufferAndPad
{
278 [&](char *dest
, std::size_t destChars
, std::size_t len
) {
279 auto copyLen
{std::min(len
, destChars
)};
280 std::memcpy(dest
, buffer
, copyLen
);
281 for (auto i
{copyLen
}; i
< destChars
; ++i
) {
286 auto len
= std::strftime(buffer
, buffSize
, "%Y%m%d", &localTime
);
287 copyBufferAndPad(date
, dateChars
, len
);
290 auto len
{std::snprintf(buffer
, buffSize
, "%02d%02d%02d.%03jd",
291 localTime
.tm_hour
, localTime
.tm_min
, localTime
.tm_sec
, ms
)};
292 copyBufferAndPad(time
, timeChars
, len
);
295 // Note: this may leave the buffer empty on many platforms. Classic flang
296 // has a much more complex way of doing this (see __io_timezone in classic
298 auto len
{std::strftime(buffer
, buffSize
, "%z", &localTime
)};
299 copyBufferAndPad(zone
, zoneChars
, len
);
302 auto typeCode
{values
->type().GetCategoryAndKind()};
303 RUNTIME_CHECK(terminator
,
304 values
->rank() == 1 && values
->GetDimension(0).Extent() >= 8 &&
306 typeCode
->first
== Fortran::common::TypeCategory::Integer
);
307 // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
309 int kind
{typeCode
->second
};
310 RUNTIME_CHECK(terminator
, kind
!= 1);
311 auto storeIntegerAt
= [&](std::size_t atIndex
, std::int64_t value
) {
312 Fortran::runtime::ApplyIntegerKind
<Fortran::runtime::StoreIntegerAt
,
313 void>(kind
, terminator
, *values
, atIndex
, value
);
315 storeIntegerAt(0, localTime
.tm_year
+ 1900);
316 storeIntegerAt(1, localTime
.tm_mon
+ 1);
317 storeIntegerAt(2, localTime
.tm_mday
);
318 Fortran::runtime::ApplyIntegerKind
<
319 GmtOffsetHelper
<struct tm
>::StoreGmtOffset
, void>(
320 kind
, terminator
, *values
, 3, localTime
);
321 storeIntegerAt(4, localTime
.tm_hour
);
322 storeIntegerAt(5, localTime
.tm_min
);
323 storeIntegerAt(6, localTime
.tm_sec
);
324 storeIntegerAt(7, ms
);
329 // Fallback implementation where gettimeofday or localtime_r are not both
330 // available (e.g. windows).
331 static void GetDateAndTime(Fortran::runtime::Terminator
&terminator
, char *date
,
332 std::size_t dateChars
, char *time
, std::size_t timeChars
, char *zone
,
333 std::size_t zoneChars
, const Fortran::runtime::Descriptor
*values
) {
334 // TODO: An actual implementation for non Posix system should be added.
335 // So far, implement as if the date and time is not available on those
337 DateAndTimeUnavailable(
338 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
343 namespace Fortran::runtime
{
346 double RTNAME(CpuTime
)() { return GetCpuTime(0); }
348 std::int64_t RTNAME(SystemClockCount
)(int kind
) {
349 return GetSystemClockCount(kind
, 0);
352 std::int64_t RTNAME(SystemClockCountRate
)(int kind
) {
353 return GetSystemClockCountRate(kind
, 0);
356 std::int64_t RTNAME(SystemClockCountMax
)(int kind
) {
357 return GetSystemClockCountMax(kind
, 0);
360 void RTNAME(DateAndTime
)(char *date
, std::size_t dateChars
, char *time
,
361 std::size_t timeChars
, char *zone
, std::size_t zoneChars
,
362 const char *source
, int line
, const Descriptor
*values
) {
363 Fortran::runtime::Terminator terminator
{source
, line
};
364 return GetDateAndTime(
365 terminator
, date
, dateChars
, time
, timeChars
, zone
, zoneChars
, values
);
369 } // namespace Fortran::runtime