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_THREAD_CPUTIME_ID
60 #define CLOCKID CLOCK_THREAD_CPUTIME_ID
61 #elif defined CLOCK_PROCESS_CPUTIME_ID
62 #define CLOCKID CLOCK_PROCESS_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. This is only enabled where
131 // clock_gettime is available.  Use a millisecond CLOCK_RATE for kinds
132 // of COUNT/COUNT_MAX less than 64 bits, and nanoseconds otherwise.
133 constexpr unsigned_count_t MILLIS_PER_SEC{1'000u};
134 constexpr unsigned_count_t NSECS_PER_SEC{1'000'000'000u};
135 constexpr unsigned_count_t maxSecs{
136     std::numeric_limits<unsigned_count_t>::max() / NSECS_PER_SEC};
137 
138 // Use a millisecond clock rate for smaller COUNT= kinds.
139 static inline unsigned_count_t ScaleResult(unsigned_count_t nsecs, int kind) {
140   return kind >= 8 ? nsecs : nsecs / (NSECS_PER_SEC / MILLIS_PER_SEC);
141 }
142 
143 template <typename T = int, typename U = struct timespec>
144 count_t GetSystemClockCount(int kind, preferred_implementation,
145     // We need some dummy parameters to pass to decltype(clock_gettime).
146     T ClockId = 0, U *Timespec = nullptr,
147     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
148   struct timespec tspec;
149   if (clock_gettime(CLOCKID, &tspec) != 0) {
150     // Return -HUGE() to represent failure.
151     return -GetHUGE(kind);
152   }
153   // Wrap around to avoid overflows.
154   unsigned_count_t wrappedSecs{
155       static_cast<unsigned_count_t>(tspec.tv_sec) % maxSecs};
156   unsigned_count_t unsignedNsecs{static_cast<unsigned_count_t>(tspec.tv_nsec) +
157       wrappedSecs * NSECS_PER_SEC};
158   unsigned_count_t unsignedCount{ScaleResult(unsignedNsecs, kind)};
159   // Return the modulus of the unsigned integral count with HUGE(COUNT)+1.
160   // The result is a signed integer but never negative.
161   return static_cast<count_t>(unsignedCount % (GetHUGE(kind) + 1));
162 }
163 
164 template <typename T = int, typename U = struct timespec>
165 count_t GetSystemClockCountRate(int kind, preferred_implementation,
166     // We need some dummy parameters to pass to decltype(clock_gettime).
167     T ClockId = 0, U *Timespec = nullptr,
168     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
169   return kind >= 8 ? static_cast<count_t>(NSECS_PER_SEC) : MILLIS_PER_SEC;
170 }
171 
172 template <typename T = int, typename U = struct timespec>
173 count_t GetSystemClockCountMax(int kind, preferred_implementation,
174     // We need some dummy parameters to pass to decltype(clock_gettime).
175     T ClockId = 0, U *Timespec = nullptr,
176     decltype(clock_gettime(ClockId, Timespec)) *Enabled = nullptr) {
177   unsigned_count_t maxClockNsec{maxSecs * NSECS_PER_SEC + NSECS_PER_SEC - 1};
178   unsigned_count_t maxClock{ScaleResult(maxClockNsec, kind)};
179   unsigned_count_t maxCount{GetHUGE(kind)};
180   return static_cast<count_t>(maxClock <= maxCount ? maxClock : maxCount);
181 }
182 
183 // DATE_AND_TIME (Fortran 2018 16.9.59)
184 
185 // Helper to store integer value in result[at].
186 template <int KIND> struct StoreIntegerAt {
187   void operator()(const Fortran::runtime::Descriptor &result, std::size_t at,
188       std::int64_t value) const {
189     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
190         Fortran::common::TypeCategory::Integer, KIND>>(at) = value;
191   }
192 };
193 
194 // Helper to set an integer value to -HUGE
195 template <int KIND> struct StoreNegativeHugeAt {
196   void operator()(
197       const Fortran::runtime::Descriptor &result, std::size_t at) const {
198     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
199         Fortran::common::TypeCategory::Integer, KIND>>(at) =
200         -std::numeric_limits<Fortran::runtime::CppTypeFor<
201             Fortran::common::TypeCategory::Integer, KIND>>::max();
202   }
203 };
204 
205 // Default implementation when date and time information is not available (set
206 // strings to blanks and values to -HUGE as defined by the standard).
207 static void DateAndTimeUnavailable(Fortran::runtime::Terminator &terminator,
208     char *date, std::size_t dateChars, char *time, std::size_t timeChars,
209     char *zone, std::size_t zoneChars,
210     const Fortran::runtime::Descriptor *values) {
211   if (date) {
212     std::memset(date, static_cast<int>(' '), dateChars);
213   }
214   if (time) {
215     std::memset(time, static_cast<int>(' '), timeChars);
216   }
217   if (zone) {
218     std::memset(zone, static_cast<int>(' '), zoneChars);
219   }
220   if (values) {
221     auto typeCode{values->type().GetCategoryAndKind()};
222     RUNTIME_CHECK(terminator,
223         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
224             typeCode &&
225             typeCode->first == Fortran::common::TypeCategory::Integer);
226     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
227     // KIND 1 here.
228     int kind{typeCode->second};
229     RUNTIME_CHECK(terminator, kind != 1);
230     for (std::size_t i = 0; i < 8; ++i) {
231       Fortran::runtime::ApplyIntegerKind<StoreNegativeHugeAt, void>(
232           kind, terminator, *values, i);
233     }
234   }
235 }
236 
237 #ifndef _WIN32
238 
239 // SFINAE helper to return the struct tm.tm_gmtoff which is not a POSIX standard
240 // field.
241 template <int KIND, typename TM = struct tm>
242 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
243 GetGmtOffset(const TM &tm, preferred_implementation,
244     decltype(tm.tm_gmtoff) *Enabled = nullptr) {
245   // Returns the GMT offset in minutes.
246   return tm.tm_gmtoff / 60;
247 }
248 template <int KIND, typename TM = struct tm>
249 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
250 GetGmtOffset(const TM &tm, fallback_implementation) {
251   // tm.tm_gmtoff is not available, there may be platform dependent alternatives
252   // (such as using timezone from <time.h> when available), but so far just
253   // return -HUGE to report that this information is not available.
254   return -std::numeric_limits<Fortran::runtime::CppTypeFor<
255       Fortran::common::TypeCategory::Integer, KIND>>::max();
256 }
257 template <typename TM = struct tm> struct GmtOffsetHelper {
258   template <int KIND> struct StoreGmtOffset {
259     void operator()(const Fortran::runtime::Descriptor &result, std::size_t at,
260         TM &tm) const {
261       *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
262           Fortran::common::TypeCategory::Integer, KIND>>(at) =
263           GetGmtOffset<KIND>(tm, 0);
264     }
265   };
266 };
267 
268 // Dispatch to posix implementation where gettimeofday and localtime_r are
269 // available.
270 static void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
271     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
272     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
273 
274   timeval t;
275   if (gettimeofday(&t, nullptr) != 0) {
276     DateAndTimeUnavailable(
277         terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
278     return;
279   }
280   time_t timer{t.tv_sec};
281   tm localTime;
282   localtime_r(&timer, &localTime);
283   std::intmax_t ms{t.tv_usec / 1000};
284 
285   static constexpr std::size_t buffSize{16};
286   char buffer[buffSize];
287   auto copyBufferAndPad{
288       [&](char *dest, std::size_t destChars, std::size_t len) {
289         auto copyLen{std::min(len, destChars)};
290         std::memcpy(dest, buffer, copyLen);
291         for (auto i{copyLen}; i < destChars; ++i) {
292           dest[i] = ' ';
293         }
294       }};
295   if (date) {
296     auto len = std::strftime(buffer, buffSize, "%Y%m%d", &localTime);
297     copyBufferAndPad(date, dateChars, len);
298   }
299   if (time) {
300     auto len{std::snprintf(buffer, buffSize, "%02d%02d%02d.%03jd",
301         localTime.tm_hour, localTime.tm_min, localTime.tm_sec, ms)};
302     copyBufferAndPad(time, timeChars, len);
303   }
304   if (zone) {
305     // Note: this may leave the buffer empty on many platforms. Classic flang
306     // has a much more complex way of doing this (see __io_timezone in classic
307     // flang).
308     auto len{std::strftime(buffer, buffSize, "%z", &localTime)};
309     copyBufferAndPad(zone, zoneChars, len);
310   }
311   if (values) {
312     auto typeCode{values->type().GetCategoryAndKind()};
313     RUNTIME_CHECK(terminator,
314         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
315             typeCode &&
316             typeCode->first == Fortran::common::TypeCategory::Integer);
317     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
318     // KIND 1 here.
319     int kind{typeCode->second};
320     RUNTIME_CHECK(terminator, kind != 1);
321     auto storeIntegerAt = [&](std::size_t atIndex, std::int64_t value) {
322       Fortran::runtime::ApplyIntegerKind<StoreIntegerAt, void>(
323           kind, terminator, *values, atIndex, value);
324     };
325     storeIntegerAt(0, localTime.tm_year + 1900);
326     storeIntegerAt(1, localTime.tm_mon + 1);
327     storeIntegerAt(2, localTime.tm_mday);
328     Fortran::runtime::ApplyIntegerKind<
329         GmtOffsetHelper<struct tm>::StoreGmtOffset, void>(
330         kind, terminator, *values, 3, localTime);
331     storeIntegerAt(4, localTime.tm_hour);
332     storeIntegerAt(5, localTime.tm_min);
333     storeIntegerAt(6, localTime.tm_sec);
334     storeIntegerAt(7, ms);
335   }
336 }
337 
338 #else
339 // Fallback implementation where gettimeofday or localtime_r are not both
340 // available (e.g. windows).
341 static void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
342     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
343     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
344   // TODO: An actual implementation for non Posix system should be added.
345   // So far, implement as if the date and time is not available on those
346   // platforms.
347   DateAndTimeUnavailable(
348       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
349 }
350 #endif
351 } // namespace
352 
353 namespace Fortran::runtime {
354 extern "C" {
355 
356 double RTNAME(CpuTime)() { return GetCpuTime(0); }
357 
358 std::int64_t RTNAME(SystemClockCount)(int kind) {
359   return GetSystemClockCount(kind, 0);
360 }
361 
362 std::int64_t RTNAME(SystemClockCountRate)(int kind) {
363   return GetSystemClockCountRate(kind, 0);
364 }
365 
366 std::int64_t RTNAME(SystemClockCountMax)(int kind) {
367   return GetSystemClockCountMax(kind, 0);
368 }
369 
370 void RTNAME(DateAndTime)(char *date, std::size_t dateChars, char *time,
371     std::size_t timeChars, char *zone, std::size_t zoneChars,
372     const char *source, int line, const Descriptor *values) {
373   Fortran::runtime::Terminator terminator{source, line};
374   return GetDateAndTime(
375       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
376 }
377 
378 } // extern "C"
379 } // namespace Fortran::runtime
380