Skip to content

Commit b3e392c

Browse files
committed
[flang] Implement Posix version of DATE_AND_TIME runtime
Use gettimeofday and localtime_r to implement DATE_AND_TIME intrinsic. The Windows version fallbacks to the "no date and time information available" defined by the standard (strings set to blanks and values to -HUGE). The implementation uses an ifdef between windows and the rest because from my tests, the SFINAE approach leads to undeclared name bogus errors with clang 8 that seems to ignore failure to instantiate is not an error for the function names (i.e., it understands it should not instantiate the version using gettimeofday if it is not there, but still yields an error that it is not declared on the spot where it is called in the uninstantiated version). Differential Revision: https://reviews.llvm.org/D108622
1 parent b5088cb commit b3e392c

File tree

5 files changed

+295
-5
lines changed

5 files changed

+295
-5
lines changed

flang/runtime/time-intrinsic.cpp

+195-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010

1111
#include "time-intrinsic.h"
1212

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>
1321
#include <ctime>
22+
#ifndef _WIN32
23+
#include <sys/time.h> // gettimeofday
24+
#endif
1425

1526
// CPU_TIME (Fortran 2018 16.9.57)
1627
// SYSTEM_CLOCK (Fortran 2018 16.9.168)
@@ -163,23 +174,204 @@ count_t GetSystemClockCountMax(preferred_implementation,
163174
count_t max_secs{std::numeric_limits<count_t>::max() / NSECS_PER_SEC};
164175
return max_secs * NSECS_PER_SEC - 1;
165176
}
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
166346
} // anonymous namespace
167347

168348
namespace Fortran::runtime {
169349
extern "C" {
170350

171351
double RTNAME(CpuTime)() { return GetCpuTime(0); }
172352

173-
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCount)() {
353+
CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
354+
SystemClockCount)() {
174355
return GetSystemClockCount(0);
175356
}
176357

177-
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCountRate)() {
358+
CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
359+
SystemClockCountRate)() {
178360
return GetSystemClockCountRate(0);
179361
}
180362

181-
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCountMax)() {
363+
CppTypeFor<Fortran::common::TypeCategory::Integer, 8> RTNAME(
364+
SystemClockCountMax)() {
182365
return GetSystemClockCountMax(0);
183366
}
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+
184376
} // extern "C"
185377
} // namespace Fortran::runtime

flang/runtime/time-intrinsic.h

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "entry-names.h"
1717

1818
namespace Fortran::runtime {
19+
20+
class Descriptor;
21+
1922
extern "C" {
2023

2124
// Lowering may need to cast this result to match the precision of the default
@@ -28,6 +31,13 @@ double RTNAME(CpuTime)();
2831
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCount)();
2932
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCountRate)();
3033
CppTypeFor<TypeCategory::Integer, 8> RTNAME(SystemClockCountMax)();
34+
35+
// Interface for DATE_AND_TIME intrinsic.
36+
void RTNAME(DateAndTime)(char *date, std::size_t dateChars, char *time,
37+
std::size_t timeChars, char *zone, std::size_t zoneChars,
38+
const char *source = nullptr, int line = 0,
39+
const Descriptor *values = nullptr);
40+
3141
} // extern "C"
3242
} // namespace Fortran::runtime
3343
#endif // FORTRAN_RUNTIME_TIME_INTRINSIC_H_

flang/test/Runtime/no-cpp-dep.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ a C compiler.
55
66
REQUIRES: c-compiler
77
8-
RUN: %cc -std=c90 %s -I%runtimeincludes %libruntime -o /dev/null
8+
RUN: %cc -std=c90 %s -I%runtimeincludes %libruntime %libdecimal -o /dev/null
99
*/
1010

1111
#include "entry-names.h"

flang/test/lit.cfg.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@
7777
# we don't have one, we can just disable the test.
7878
if config.cc:
7979
libruntime = os.path.join(config.flang_lib_dir, 'libFortranRuntime.a')
80+
libdecimal = os.path.join(config.flang_lib_dir, 'libFortranDecimal.a')
8081
includes = os.path.join(config.flang_src_dir, 'runtime')
8182

82-
if os.path.isfile(libruntime) and os.path.isdir(includes):
83+
if os.path.isfile(libruntime) and os.path.isfile(libdecimal) and os.path.isdir(includes):
8384
config.available_features.add('c-compiler')
8485
tools.append(ToolSubst('%cc', command=config.cc, unresolved='fatal'))
8586
tools.append(ToolSubst('%libruntime', command=libruntime,
8687
unresolved='fatal'))
88+
tools.append(ToolSubst('%libdecimal', command=libdecimal,
89+
unresolved='fatal'))
8790
tools.append(ToolSubst('%runtimeincludes', command=includes,
8891
unresolved='fatal'))
8992

flang/unittests/Runtime/Time.cpp

+85
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
#include "gtest/gtest.h"
1010
#include "../../runtime/time-intrinsic.h"
11+
#include <algorithm>
12+
#include <cctype>
13+
#include <charconv>
14+
#include <string>
1115

1216
using namespace Fortran::runtime;
1317

@@ -56,3 +60,84 @@ TEST(TimeIntrinsics, SystemClock) {
5660
EXPECT_GE(end, start);
5761
}
5862
}
63+
64+
TEST(TimeIntrinsics, DateAndTime) {
65+
constexpr std::size_t bufferSize{16};
66+
std::string date(bufferSize, 'Z'), time(bufferSize, 'Z'),
67+
zone(bufferSize, 'Z');
68+
RTNAME(DateAndTime)
69+
(date.data(), date.size(), time.data(), time.size(), zone.data(), zone.size(),
70+
/*source=*/nullptr, /*line=*/0, /*values=*/nullptr);
71+
auto isBlank = [](const std::string &s) -> bool {
72+
return std::all_of(
73+
s.begin(), s.end(), [](char c) { return std::isblank(c); });
74+
};
75+
// Validate date is blank or YYYYMMDD.
76+
if (isBlank(date)) {
77+
EXPECT_TRUE(true);
78+
} else {
79+
count_t number{-1};
80+
auto [_, ec]{
81+
std::from_chars(date.data(), date.data() + date.size(), number)};
82+
ASSERT_TRUE(ec != std::errc::invalid_argument &&
83+
ec != std::errc::result_out_of_range);
84+
EXPECT_GE(number, 0);
85+
auto year = number / 10000;
86+
auto month = (number - year * 10000) / 100;
87+
auto day = number % 100;
88+
// Do not assume anything about the year, the test could be
89+
// run on system with fake/outdated dates.
90+
EXPECT_LE(month, 12);
91+
EXPECT_GT(month, 0);
92+
EXPECT_LE(day, 31);
93+
EXPECT_GT(day, 0);
94+
}
95+
96+
// Validate time is hhmmss.sss or blank.
97+
if (isBlank(time)) {
98+
EXPECT_TRUE(true);
99+
} else {
100+
count_t number{-1};
101+
auto [next, ec]{
102+
std::from_chars(time.data(), time.data() + date.size(), number)};
103+
ASSERT_TRUE(ec != std::errc::invalid_argument &&
104+
ec != std::errc::result_out_of_range);
105+
ASSERT_GE(number, 0);
106+
auto hours = number / 10000;
107+
auto minutes = (number - hours * 10000) / 100;
108+
auto seconds = number % 100;
109+
EXPECT_LE(hours, 23);
110+
EXPECT_LE(minutes, 59);
111+
// Accept 60 for leap seconds.
112+
EXPECT_LE(seconds, 60);
113+
ASSERT_TRUE(next != time.data() + time.size());
114+
EXPECT_EQ(*next, '.');
115+
116+
count_t milliseconds{-1};
117+
ASSERT_TRUE(next + 1 != time.data() + time.size());
118+
auto [_, ec2]{
119+
std::from_chars(next + 1, time.data() + date.size(), milliseconds)};
120+
ASSERT_TRUE(ec2 != std::errc::invalid_argument &&
121+
ec2 != std::errc::result_out_of_range);
122+
EXPECT_GE(milliseconds, 0);
123+
EXPECT_LE(milliseconds, 999);
124+
}
125+
126+
// Validate zone is +hhmm or -hhmm or blank.
127+
if (isBlank(zone)) {
128+
EXPECT_TRUE(true);
129+
} else {
130+
ASSERT_TRUE(zone.size() > 1);
131+
EXPECT_TRUE(zone[0] == '+' || zone[0] == '-');
132+
count_t number{-1};
133+
auto [next, ec]{
134+
std::from_chars(zone.data() + 1, zone.data() + zone.size(), number)};
135+
ASSERT_TRUE(ec != std::errc::invalid_argument &&
136+
ec != std::errc::result_out_of_range);
137+
ASSERT_GE(number, 0);
138+
auto hours = number / 100;
139+
auto minutes = number % 100;
140+
EXPECT_LE(hours, 23);
141+
EXPECT_LE(minutes, 59);
142+
}
143+
}

0 commit comments

Comments
 (0)