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