1 //===-- runtime/time-intrinsic.cpp ----------------------------------------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // Implements time-related intrinsic subroutines.
10 
11 #include "time-intrinsic.h"
12 
13 #include "descriptor.h"
14 #include "terminator.h"
15 #include "tools.h"
16 #include <algorithm>
17 #include <cstdint>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <ctime>
22 #ifndef _WIN32
23 #include <sys/time.h> // gettimeofday
24 #endif
25 
26 // CPU_TIME (Fortran 2018 16.9.57)
27 // SYSTEM_CLOCK (Fortran 2018 16.9.168)
28 //
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
34 // are available.
35 //
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.
42 namespace {
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;
48 
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;
54   }
55 
56   // Return some negative value to represent failure.
57   return -1.0;
58 }
59 
60 // POSIX implementation using clock_gettime. This is only enabled if
61 // clock_gettime is available.
62 template <typename T = int, typename U = struct timespec>
63 double GetCpuTime(preferred_implementation,
64     // We need some dummy parameters to pass to decltype(clock_gettime).
65     T ClockId = 0, U *Timespec = nullptr,
66     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
67 #if defined CLOCK_THREAD_CPUTIME_ID
68 #define CLOCKID CLOCK_THREAD_CPUTIME_ID
69 #elif defined CLOCK_PROCESS_CPUTIME_ID
70 #define CLOCKID CLOCK_PROCESS_CPUTIME_ID
71 #elif defined CLOCK_MONOTONIC
72 #define CLOCKID CLOCK_MONOTONIC
73 #else
74 #define CLOCKID CLOCK_REALTIME
75 #endif
76   struct timespec tspec;
77   if (clock_gettime(CLOCKID, &tspec) == 0) {
78     return tspec.tv_nsec * 1.0e-9 + tspec.tv_sec;
79   }
80 
81   // Return some negative value to represent failure.
82   return -1.0;
83 }
84 
85 using count_t =
86     Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, 8>;
87 
88 // This is the fallback implementation, which should work everywhere. Note that
89 // in general we can't recover after std::clock has reached its maximum value.
90 template <typename Unused = void>
91 count_t GetSystemClockCount(fallback_implementation) {
92   std::clock_t timestamp{std::clock()};
93   if (timestamp == static_cast<std::clock_t>(-1)) {
94     // Return -HUGE() to represent failure.
95     return -std::numeric_limits<count_t>::max();
96   }
97 
98   // If our return type is large enough to hold any value returned by
99   // std::clock, our work is done. Otherwise, we have to wrap around.
100   static constexpr auto max{std::numeric_limits<count_t>::max()};
101   if constexpr (std::numeric_limits<std::clock_t>::max() <= max) {
102     return static_cast<count_t>(timestamp);
103   } else {
104     // Since std::clock_t could be a floating point type, we can't just use the
105     // % operator, so we have to wrap around manually.
106     return static_cast<count_t>(timestamp - max * std::floor(timestamp / max));
107   }
108 }
109 
110 template <typename Unused = void>
111 count_t GetSystemClockCountRate(fallback_implementation) {
112   return CLOCKS_PER_SEC;
113 }
114 
115 template <typename Unused = void>
116 count_t GetSystemClockCountMax(fallback_implementation) {
117   static constexpr auto max_clock_t = std::numeric_limits<std::clock_t>::max();
118   static constexpr auto max_count_t = std::numeric_limits<count_t>::max();
119   if constexpr (max_clock_t < max_count_t) {
120     return static_cast<count_t>(max_clock_t);
121   } else {
122     return max_count_t;
123   }
124 }
125 
126 constexpr count_t NSECS_PER_SEC{1'000'000'000};
127 
128 // POSIX implementation using clock_gettime. This is only enabled if
129 // clock_gettime is available.
130 template <typename T = int, typename U = struct timespec>
131 count_t GetSystemClockCount(preferred_implementation,
132     // We need some dummy parameters to pass to decltype(clock_gettime).
133     T ClockId = 0, U *Timespec = nullptr,
134     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
135 #if defined CLOCK_THREAD_CPUTIME_ID
136 #define CLOCKID CLOCK_THREAD_CPUTIME_ID
137 #elif defined CLOCK_PROCESS_CPUTIME_ID
138 #define CLOCKID CLOCK_PROCESS_CPUTIME_ID
139 #elif defined CLOCK_MONOTONIC
140 #define CLOCKID CLOCK_MONOTONIC
141 #else
142 #define CLOCKID CLOCK_REALTIME
143 #endif
144   struct timespec tspec;
145   if (clock_gettime(CLOCKID, &tspec) != 0) {
146     // Return -HUGE() to represent failure.
147     return -std::numeric_limits<count_t>::max();
148   }
149 
150   // Wrap around to avoid overflows.
151   constexpr count_t max_secs{
152       std::numeric_limits<count_t>::max() / NSECS_PER_SEC};
153   count_t wrapped_secs{tspec.tv_sec % max_secs};
154 
155   // At this point, wrapped_secs < max_secs, and max_secs has already been
156   // truncated by the division. Therefore, we should still have enough room to
157   // add tv_nsec, since it is < NSECS_PER_SEC.
158   return tspec.tv_nsec + wrapped_secs * NSECS_PER_SEC;
159 }
160 
161 template <typename T = int, typename U = struct timespec>
162 count_t GetSystemClockCountRate(preferred_implementation,
163     // We need some dummy parameters to pass to decltype(clock_gettime).
164     T ClockId = 0, U *Timespec = nullptr,
165     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
166   return NSECS_PER_SEC;
167 }
168 
169 template <typename T = int, typename U = struct timespec>
170 count_t GetSystemClockCountMax(preferred_implementation,
171     // We need some dummy parameters to pass to decltype(clock_gettime).
172     T ClockId = 0, U *Timespec = nullptr,
173     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
174   count_t max_secs{std::numeric_limits<count_t>::max() / NSECS_PER_SEC};
175   return max_secs * NSECS_PER_SEC - 1;
176 }
177 
178 // DATE_AND_TIME (Fortran 2018 16.9.59)
179 
180 // Helper to store integer value in result[at].
181 template <int KIND> struct StoreIntegerAt {
182   void operator()(const Fortran::runtime::Descriptor &result, std::size_t at,
183       std::int64_t value) const {
184     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
185         Fortran::common::TypeCategory::Integer, KIND>>(at) = value;
186   }
187 };
188 
189 // Helper to set an integer value to -HUGE
190 template <int KIND> struct StoreNegativeHugeAt {
191   void operator()(
192       const Fortran::runtime::Descriptor &result, std::size_t at) const {
193     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
194         Fortran::common::TypeCategory::Integer, KIND>>(at) =
195         -std::numeric_limits<Fortran::runtime::CppTypeFor<
196             Fortran::common::TypeCategory::Integer, KIND>>::max();
197   }
198 };
199 
200 // Default implementation when date and time information is not available (set
201 // strings to blanks and values to -HUGE as defined by the standard).
202 void DateAndTimeUnavailable(Fortran::runtime::Terminator &terminator,
203     char *date, std::size_t dateChars, char *time, std::size_t timeChars,
204     char *zone, std::size_t zoneChars,
205     const Fortran::runtime::Descriptor *values) {
206   if (date) {
207     std::memset(date, static_cast<int>(' '), dateChars);
208   }
209   if (time) {
210     std::memset(time, static_cast<int>(' '), timeChars);
211   }
212   if (zone) {
213     std::memset(zone, static_cast<int>(' '), zoneChars);
214   }
215   if (values) {
216     auto typeCode{values->type().GetCategoryAndKind()};
217     RUNTIME_CHECK(terminator,
218         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
219             typeCode &&
220             typeCode->first == Fortran::common::TypeCategory::Integer);
221     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
222     // KIND 1 here.
223     int kind{typeCode->second};
224     RUNTIME_CHECK(terminator, kind != 1);
225     for (std::size_t i = 0; i < 8; ++i) {
226       Fortran::runtime::ApplyIntegerKind<StoreNegativeHugeAt, void>(
227           kind, terminator, *values, i);
228     }
229   }
230 }
231 
232 #ifndef _WIN32
233 
234 // SFINAE helper to return the struct tm.tm_gmtoff which is not a POSIX standard
235 // field.
236 template <int KIND, typename TM = struct tm>
237 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
238 GetGmtOffset(const TM &tm, preferred_implementation,
239     decltype(tm.tm_gmtoff) *Enabled = nullptr) {
240   // Returns the GMT offset in minutes.
241   return tm.tm_gmtoff / 60;
242 }
243 template <int KIND, typename TM = struct tm>
244 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
245 GetGmtOffset(const TM &tm, fallback_implementation) {
246   // tm.tm_gmtoff is not available, there may be platform dependent alternatives
247   // (such as using timezone from <time.h> when available), but so far just
248   // return -HUGE to report that this information is not available.
249   return -std::numeric_limits<Fortran::runtime::CppTypeFor<
250       Fortran::common::TypeCategory::Integer, KIND>>::max();
251 }
252 template <typename TM = struct tm> struct GmtOffsetHelper {
253   template <int KIND> struct StoreGmtOffset {
254     void operator()(const Fortran::runtime::Descriptor &result, std::size_t at,
255         TM &tm) const {
256       *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
257           Fortran::common::TypeCategory::Integer, KIND>>(at) =
258           GetGmtOffset<KIND>(tm, 0);
259     }
260   };
261 };
262 
263 // Dispatch to posix implemetation when gettimeofday and localtime_r are
264 // available.
265 void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
266     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
267     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
268 
269   timeval t;
270   if (gettimeofday(&t, nullptr) != 0) {
271     DateAndTimeUnavailable(
272         terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
273     return;
274   }
275   time_t timer{t.tv_sec};
276   tm localTime;
277   localtime_r(&timer, &localTime);
278   std::intmax_t ms{t.tv_usec / 1000};
279 
280   static constexpr std::size_t buffSize{16};
281   char buffer[buffSize];
282   auto copyBufferAndPad{
283       [&](char *dest, std::size_t destChars, std::size_t len) {
284         auto copyLen{std::min(len, destChars)};
285         std::memcpy(dest, buffer, copyLen);
286         for (auto i{copyLen}; i < destChars; ++i) {
287           dest[i] = ' ';
288         }
289       }};
290   if (date) {
291     auto len = std::strftime(buffer, buffSize, "%Y%m%d", &localTime);
292     copyBufferAndPad(date, dateChars, len);
293   }
294   if (time) {
295     auto len{std::snprintf(buffer, buffSize, "%02d%02d%02d.%03jd",
296         localTime.tm_hour, localTime.tm_min, localTime.tm_sec, ms)};
297     copyBufferAndPad(time, timeChars, len);
298   }
299   if (zone) {
300     // Note: this may leave the buffer empty on many platforms. Classic flang
301     // has a much more complex way of doing this (see __io_timezone in classic
302     // flang).
303     auto len{std::strftime(buffer, buffSize, "%z", &localTime)};
304     copyBufferAndPad(zone, zoneChars, len);
305   }
306   if (values) {
307     auto typeCode{values->type().GetCategoryAndKind()};
308     RUNTIME_CHECK(terminator,
309         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
310             typeCode &&
311             typeCode->first == Fortran::common::TypeCategory::Integer);
312     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
313     // KIND 1 here.
314     int kind{typeCode->second};
315     RUNTIME_CHECK(terminator, kind != 1);
316     auto storeIntegerAt = [&](std::size_t atIndex, std::int64_t value) {
317       Fortran::runtime::ApplyIntegerKind<StoreIntegerAt, void>(
318           kind, terminator, *values, atIndex, value);
319     };
320     storeIntegerAt(0, localTime.tm_year + 1900);
321     storeIntegerAt(1, localTime.tm_mon + 1);
322     storeIntegerAt(2, localTime.tm_mday);
323     Fortran::runtime::ApplyIntegerKind<
324         GmtOffsetHelper<struct tm>::StoreGmtOffset, void>(
325         kind, terminator, *values, 3, localTime);
326     storeIntegerAt(4, localTime.tm_hour);
327     storeIntegerAt(5, localTime.tm_min);
328     storeIntegerAt(6, localTime.tm_sec);
329     storeIntegerAt(7, ms);
330   }
331 }
332 
333 #else
334 // Fallback implementation when gettimeofday or localtime_r is not available
335 // (e.g. windows).
336 void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
337     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
338     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
339   // TODO: An actual implementation for non Posix system should be added.
340   // So far, implement as if the date and time is not available on those
341   // platforms.
342   DateAndTimeUnavailable(
343       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
344 }
345 #endif
346 } // anonymous namespace
347 
348 namespace Fortran::runtime {
349 extern "C" {
350 
351 double RTNAME(CpuTime)() { return GetCpuTime(0); }
352 
353 CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
354     SystemClockCount)() {
355   return GetSystemClockCount(0);
356 }
357 
358 CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
359     SystemClockCountRate)() {
360   return GetSystemClockCountRate(0);
361 }
362 
363 CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
364     SystemClockCountMax)() {
365   return GetSystemClockCountMax(0);
366 }
367 
368 void RTNAME(DateAndTime)(char *date, std::size_t dateChars, char *time,
369     std::size_t timeChars, char *zone, std::size_t zoneChars,
370     const char *source, int line, const Descriptor *values) {
371   Fortran::runtime::Terminator terminator{source, line};
372   return GetDateAndTime(
373       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
374 }
375 
376 } // extern "C"
377 } // namespace Fortran::runtime
378