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 set an integer value to -HUGE
186 template <int KIND> struct StoreNegativeHugeAt {
187   void operator()(
188       const Fortran::runtime::Descriptor &result, std::size_t at) const {
189     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
190         Fortran::common::TypeCategory::Integer, KIND>>(at) =
191         -std::numeric_limits<Fortran::runtime::CppTypeFor<
192             Fortran::common::TypeCategory::Integer, KIND>>::max();
193   }
194 };
195 
196 // Default implementation when date and time information is not available (set
197 // strings to blanks and values to -HUGE as defined by the standard).
198 static void DateAndTimeUnavailable(Fortran::runtime::Terminator &terminator,
199     char *date, std::size_t dateChars, char *time, std::size_t timeChars,
200     char *zone, std::size_t zoneChars,
201     const Fortran::runtime::Descriptor *values) {
202   if (date) {
203     std::memset(date, static_cast<int>(' '), dateChars);
204   }
205   if (time) {
206     std::memset(time, static_cast<int>(' '), timeChars);
207   }
208   if (zone) {
209     std::memset(zone, static_cast<int>(' '), zoneChars);
210   }
211   if (values) {
212     auto typeCode{values->type().GetCategoryAndKind()};
213     RUNTIME_CHECK(terminator,
214         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
215             typeCode &&
216             typeCode->first == Fortran::common::TypeCategory::Integer);
217     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
218     // KIND 1 here.
219     int kind{typeCode->second};
220     RUNTIME_CHECK(terminator, kind != 1);
221     for (std::size_t i = 0; i < 8; ++i) {
222       Fortran::runtime::ApplyIntegerKind<StoreNegativeHugeAt, void>(
223           kind, terminator, *values, i);
224     }
225   }
226 }
227 
228 #ifndef _WIN32
229 
230 // SFINAE helper to return the struct tm.tm_gmtoff which is not a POSIX standard
231 // field.
232 template <int KIND, typename TM = struct tm>
233 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
234 GetGmtOffset(const TM &tm, preferred_implementation,
235     decltype(tm.tm_gmtoff) *Enabled = nullptr) {
236   // Returns the GMT offset in minutes.
237   return tm.tm_gmtoff / 60;
238 }
239 template <int KIND, typename TM = struct tm>
240 Fortran::runtime::CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>
241 GetGmtOffset(const TM &tm, fallback_implementation) {
242   // tm.tm_gmtoff is not available, there may be platform dependent alternatives
243   // (such as using timezone from <time.h> when available), but so far just
244   // return -HUGE to report that this information is not available.
245   return -std::numeric_limits<Fortran::runtime::CppTypeFor<
246       Fortran::common::TypeCategory::Integer, KIND>>::max();
247 }
248 template <typename TM = struct tm> struct GmtOffsetHelper {
249   template <int KIND> struct StoreGmtOffset {
250     void operator()(const Fortran::runtime::Descriptor &result, std::size_t at,
251         TM &tm) const {
252       *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
253           Fortran::common::TypeCategory::Integer, KIND>>(at) =
254           GetGmtOffset<KIND>(tm, 0);
255     }
256   };
257 };
258 
259 // Dispatch to posix implementation where gettimeofday and localtime_r are
260 // available.
261 static void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
262     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
263     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
264 
265   timeval t;
266   if (gettimeofday(&t, nullptr) != 0) {
267     DateAndTimeUnavailable(
268         terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
269     return;
270   }
271   time_t timer{t.tv_sec};
272   tm localTime;
273   localtime_r(&timer, &localTime);
274   std::intmax_t ms{t.tv_usec / 1000};
275 
276   static constexpr std::size_t buffSize{16};
277   char buffer[buffSize];
278   auto copyBufferAndPad{
279       [&](char *dest, std::size_t destChars, std::size_t len) {
280         auto copyLen{std::min(len, destChars)};
281         std::memcpy(dest, buffer, copyLen);
282         for (auto i{copyLen}; i < destChars; ++i) {
283           dest[i] = ' ';
284         }
285       }};
286   if (date) {
287     auto len = std::strftime(buffer, buffSize, "%Y%m%d", &localTime);
288     copyBufferAndPad(date, dateChars, len);
289   }
290   if (time) {
291     auto len{std::snprintf(buffer, buffSize, "%02d%02d%02d.%03jd",
292         localTime.tm_hour, localTime.tm_min, localTime.tm_sec, ms)};
293     copyBufferAndPad(time, timeChars, len);
294   }
295   if (zone) {
296     // Note: this may leave the buffer empty on many platforms. Classic flang
297     // has a much more complex way of doing this (see __io_timezone in classic
298     // flang).
299     auto len{std::strftime(buffer, buffSize, "%z", &localTime)};
300     copyBufferAndPad(zone, zoneChars, len);
301   }
302   if (values) {
303     auto typeCode{values->type().GetCategoryAndKind()};
304     RUNTIME_CHECK(terminator,
305         values->rank() == 1 && values->GetDimension(0).Extent() >= 8 &&
306             typeCode &&
307             typeCode->first == Fortran::common::TypeCategory::Integer);
308     // DATE_AND_TIME values argument must have decimal range > 4. Do not accept
309     // KIND 1 here.
310     int kind{typeCode->second};
311     RUNTIME_CHECK(terminator, kind != 1);
312     auto storeIntegerAt = [&](std::size_t atIndex, std::int64_t value) {
313       Fortran::runtime::ApplyIntegerKind<Fortran::runtime::StoreIntegerAt,
314           void>(kind, terminator, *values, atIndex, value);
315     };
316     storeIntegerAt(0, localTime.tm_year + 1900);
317     storeIntegerAt(1, localTime.tm_mon + 1);
318     storeIntegerAt(2, localTime.tm_mday);
319     Fortran::runtime::ApplyIntegerKind<
320         GmtOffsetHelper<struct tm>::StoreGmtOffset, void>(
321         kind, terminator, *values, 3, localTime);
322     storeIntegerAt(4, localTime.tm_hour);
323     storeIntegerAt(5, localTime.tm_min);
324     storeIntegerAt(6, localTime.tm_sec);
325     storeIntegerAt(7, ms);
326   }
327 }
328 
329 #else
330 // Fallback implementation where gettimeofday or localtime_r are not both
331 // available (e.g. windows).
332 static void GetDateAndTime(Fortran::runtime::Terminator &terminator, char *date,
333     std::size_t dateChars, char *time, std::size_t timeChars, char *zone,
334     std::size_t zoneChars, const Fortran::runtime::Descriptor *values) {
335   // TODO: An actual implementation for non Posix system should be added.
336   // So far, implement as if the date and time is not available on those
337   // platforms.
338   DateAndTimeUnavailable(
339       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
340 }
341 #endif
342 } // namespace
343 
344 namespace Fortran::runtime {
345 extern "C" {
346 
347 double RTNAME(CpuTime)() { return GetCpuTime(0); }
348 
349 std::int64_t RTNAME(SystemClockCount)(int kind) {
350   return GetSystemClockCount(kind, 0);
351 }
352 
353 std::int64_t RTNAME(SystemClockCountRate)(int kind) {
354   return GetSystemClockCountRate(kind, 0);
355 }
356 
357 std::int64_t RTNAME(SystemClockCountMax)(int kind) {
358   return GetSystemClockCountMax(kind, 0);
359 }
360 
361 void RTNAME(DateAndTime)(char *date, std::size_t dateChars, char *time,
362     std::size_t timeChars, char *zone, std::size_t zoneChars,
363     const char *source, int line, const Descriptor *values) {
364   Fortran::runtime::Terminator terminator{source, line};
365   return GetDateAndTime(
366       terminator, date, dateChars, time, timeChars, zone, zoneChars, values);
367 }
368 
369 } // extern "C"
370 } // namespace Fortran::runtime
371