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