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