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