Author: Raman Tenneti Date: 2020-11-30T21:07:16-08:00 New Revision: 6f0f844e9af98dc935f80d8149f6e4fcebddf8f1
URL: https://github.com/llvm/llvm-project/commit/6f0f844e9af98dc935f80d8149f6e4fcebddf8f1 DIFF: https://github.com/llvm/llvm-project/commit/6f0f844e9af98dc935f80d8149f6e4fcebddf8f1.diff LOG: Initial commit of mktime. This introduces mktime to LLVM libc, based on C99/C2X/Single Unix Spec. Co-authored-by: Jeff Bailey <jeffbai...@google.com> This change doesn't handle TIMEZONE, tm_isdst and leap seconds. It returns -1 for invalid dates. I have verified the return results for all the possible dates with glibc's mktime. TODO: + Handle leap seconds. + Handle out of range time and date values that don't overflow or underflow. + Implement the following suggestion Siva - As we start accumulating the seconds, we should be able to check if the next amount of seconds to be added can lead to an overflow. If it does, return the overflow value. If not keep accumulating. The benefit is that, we don't have to validate every input, and also do not need the special cases for sizeof(time_t) == 4. + Handle timezone and update of tm_isdst Reviewed By: sivachandra Differential Revision: https://reviews.llvm.org/D91551 Added: libc/include/time.h.def libc/src/time/CMakeLists.txt libc/src/time/mktime.cpp libc/src/time/mktime.h libc/test/src/time/CMakeLists.txt libc/test/src/time/mktime_test.cpp Modified: libc/config/linux/api.td libc/config/linux/x86_64/entrypoints.txt libc/config/linux/x86_64/headers.txt libc/include/CMakeLists.txt libc/spec/spec.td libc/spec/stdc.td libc/src/CMakeLists.txt libc/test/src/CMakeLists.txt Removed: ################################################################################ diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index 8415e8032d28..d66d55f65027 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -20,6 +20,28 @@ def SSizeT : TypeDecl<"ssize_t"> { }]; } +def StructTm: TypeDecl<"struct tm"> { + let Decl = [{ + struct tm { + int tm_sec; // seconds after the minute + int tm_min; // minutes after the hour + int tm_hour; // hours since midnight + int tm_mday; // day of the month + int tm_mon; // months since January + int tm_year; // years since 1900 + int tm_wday; // days since Sunday + int tm_yday; // days since January + int tm_isdst; // Daylight Saving Time flag + }; + }]; +} + +def TimeT: TypeDecl<"time_t"> { + let Decl = [{ + typedef long time_t; + }]; +} + def OffT : TypeDecl<"off_t"> { let Decl = [{ #define __need_off_t @@ -177,6 +199,17 @@ def StdIOAPI : PublicAPI<"stdio.h"> { def StdlibAPI : PublicAPI<"stdlib.h"> { } +def TimeAPI : PublicAPI<"time.h"> { + let TypeDeclarations = [ + StructTm, + TimeT, + ]; + + let Functions = [ + "mktime", + ]; +} + def ErrnoAPI : PublicAPI<"errno.h"> { let Macros = [ ErrnoMacro, diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index a0773ef0fce6..4eb964b2c047 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -67,6 +67,9 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.threads.thrd_create libc.src.threads.thrd_join + # time.h entrypoints + libc.src.time.mktime + # unistd.h entrypoints libc.src.unistd.write ) diff --git a/libc/config/linux/x86_64/headers.txt b/libc/config/linux/x86_64/headers.txt index 550f172c32c1..2d4cdb11cb8a 100644 --- a/libc/config/linux/x86_64/headers.txt +++ b/libc/config/linux/x86_64/headers.txt @@ -9,5 +9,6 @@ set(TARGET_PUBLIC_HEADERS libc.include.sys_mman libc.include.sys_syscall libc.include.threads + libc.include.time libc.include.unistd ) diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt index f676342a134e..ddfdb8caa2ac 100644 --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -50,6 +50,14 @@ add_gen_header( .llvm_libc_common_h ) +add_gen_header( + time + DEF_FILE time.h.def + GEN_HDR time.h + DEPENDS + .llvm_libc_common_h +) + add_gen_header( threads DEF_FILE threads.h.def diff --git a/libc/include/time.h.def b/libc/include/time.h.def new file mode 100644 index 000000000000..0c90f9b76c2e --- /dev/null +++ b/libc/include/time.h.def @@ -0,0 +1,16 @@ +//===-- C standard library header time.h ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_TIME_H +#define LLVM_LIBC_TIME_H + +#include <__llvm-libc-common.h> + +%%public_api() + +#endif // LLVM_LIBC_TIME_H diff --git a/libc/spec/spec.td b/libc/spec/spec.td index 35d51e206b09..29c11a9c2199 100644 --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -79,6 +79,8 @@ def DoublePtr : PtrType<DoubleType>; def SigHandlerT : NamedType<"__sighandler_t">; +def TimeTType : NamedType<"time_t">; + //added because __assert_fail needs it. def UnsignedType : NamedType<"unsigned">; diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td index 854566ca544b..7275f1e0aacf 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -3,6 +3,8 @@ def StdC : StandardSpec<"stdc"> { NamedType FILE = NamedType<"FILE">; PtrType FILEPtr = PtrType<FILE>; RestrictedPtrType FILERestrictedPtr = RestrictedPtrType<FILE>; + NamedType StructTmType = NamedType<"struct tm">; + PtrType StructTmPtr = PtrType<StructTmType>; HeaderSpec Assert = HeaderSpec< "assert.h", @@ -467,6 +469,23 @@ def StdC : StandardSpec<"stdc"> { ] >; + HeaderSpec Time = HeaderSpec< + "time.h", + [], // Macros + [ // Types + StructTmType, + TimeTType, + ], + [], // Enumerations + [ + FunctionSpec< + "mktime", + RetValSpec<TimeTType>, + [ArgSpec<StructTmPtr>] + >, + ] + >; + let Headers = [ Assert, CType, @@ -477,5 +496,6 @@ def StdC : StandardSpec<"stdc"> { StdLib, Signal, Threads, + Time, ]; } diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt index 9e5b06f201ce..11213e90e579 100644 --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(string) # TODO: Add this target conditional to the target OS. add_subdirectory(sys) add_subdirectory(threads) +add_subdirectory(time) add_subdirectory(unistd) add_subdirectory(__support) diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt new file mode 100644 index 000000000000..dc2d95717d89 --- /dev/null +++ b/libc/src/time/CMakeLists.txt @@ -0,0 +1,11 @@ +add_entrypoint_object( + mktime + SRCS + mktime.cpp + HDRS + mktime.h + DEPENDS + libc.include.errno + libc.include.time + libc.src.errno.__errno_location +) diff --git a/libc/src/time/mktime.cpp b/libc/src/time/mktime.cpp new file mode 100644 index 000000000000..418797158968 --- /dev/null +++ b/libc/src/time/mktime.cpp @@ -0,0 +1,126 @@ +//===-- Implementation of mktime function ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/errno.h" + +#include "src/__support/common.h" +#include "src/errno/llvmlibc_errno.h" +#include "src/time/mktime.h" + +namespace __llvm_libc { + +constexpr int SecondsPerMin = 60; +constexpr int MinutesPerHour = 60; +constexpr int HoursPerDay = 24; +constexpr int DaysPerWeek = 7; +constexpr int MonthsPerYear = 12; +constexpr int DaysPerNonLeapYear = 365; +constexpr int TimeYearBase = 1900; +constexpr int EpochYear = 1970; +constexpr int EpochWeekDay = 4; +// The latest time that can be represented in this form is 03:14:07 UTC on +// Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the +// start of the epoch). This means that systems using a 32-bit time_t type are +// susceptible to the Year 2038 problem. +constexpr int EndOf32BitEpochYear = 2038; + +constexpr int NonLeapYearDaysInMonth[] = {31 /* Jan */, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + +constexpr bool isLeapYear(const time_t year) { + return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); +} + +// POSIX.1-2017 requires this. +static inline time_t outOfRange() { + llvmlibc_errno = EOVERFLOW; + return static_cast<time_t>(-1); +} + +time_t LLVM_LIBC_ENTRYPOINT(mktime)(struct tm *t1) { + // Unlike most C Library functions, mktime doesn't just die on bad input. + // TODO(rtenneti); Handle leap seconds. Handle out of range time and date + // values that don't overflow or underflow. + // TODO (rtenneti): Implement the following suggestion Siva: "As we start + // accumulating the seconds, we should be able to check if the next amount of + // seconds to be added can lead to an overflow. If it does, return the + // overflow value. If not keep accumulating. The benefit is that, we don't + // have to validate every input, and also do not need the special cases for + // sizeof(time_t) == 4". + if (t1->tm_sec < 0 || t1->tm_sec > (SecondsPerMin - 1)) + return outOfRange(); + if (t1->tm_min < 0 || t1->tm_min > (MinutesPerHour - 1)) + return outOfRange(); + if (t1->tm_hour < 0 || t1->tm_hour > (HoursPerDay - 1)) + return outOfRange(); + time_t tmYearFromBase = t1->tm_year + TimeYearBase; + + if (tmYearFromBase < EpochYear) + return outOfRange(); + + // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038. + if (sizeof(time_t) == 4 && tmYearFromBase >= EndOf32BitEpochYear) { + if (tmYearFromBase > EndOf32BitEpochYear) + return outOfRange(); + if (t1->tm_mon > 0) + return outOfRange(); + if (t1->tm_mday > 19) + return outOfRange(); + if (t1->tm_hour > 3) + return outOfRange(); + if (t1->tm_min > 14) + return outOfRange(); + if (t1->tm_sec > 7) + return outOfRange(); + } + + // Years are ints. A 32-bit year will fit into a 64-bit time_t. + // A 64-bit year will not. + static_assert(sizeof(int) == 4, + "ILP64 is unimplemented. This implementation requires " + "32-bit integers."); + + if (t1->tm_mon < 0 || t1->tm_mon > (MonthsPerYear - 1)) + return outOfRange(); + bool tmYearIsLeap = isLeapYear(tmYearFromBase); + time_t daysInMonth = NonLeapYearDaysInMonth[t1->tm_mon]; + // Add one day if it is a leap year and the month is February. + if (tmYearIsLeap && t1->tm_mon == 1) + ++daysInMonth; + if (t1->tm_mday < 1 || t1->tm_mday > daysInMonth) + return outOfRange(); + + time_t totalDays = t1->tm_mday - 1; + for (int i = 0; i < t1->tm_mon; ++i) + totalDays += NonLeapYearDaysInMonth[i]; + // Add one day if it is a leap year and the month is after February. + if (tmYearIsLeap && t1->tm_mon > 1) + totalDays++; + t1->tm_yday = totalDays; + totalDays += (tmYearFromBase - EpochYear) * DaysPerNonLeapYear; + + // Add an extra day for each leap year, starting with 1972 + for (time_t year = EpochYear + 2; year < tmYearFromBase;) { + if (isLeapYear(year)) { + totalDays += 1; + year += 4; + } else { + year++; + } + } + + t1->tm_wday = (EpochWeekDay + totalDays) % DaysPerWeek; + if (t1->tm_wday < 0) + t1->tm_wday += DaysPerWeek; + // TODO(rtenneti): Need to handle timezone and update of tm_isdst. + return t1->tm_sec + t1->tm_min * SecondsPerMin + + t1->tm_hour * MinutesPerHour * SecondsPerMin + + totalDays * HoursPerDay * MinutesPerHour * SecondsPerMin; +} + +} // namespace __llvm_libc diff --git a/libc/src/time/mktime.h b/libc/src/time/mktime.h new file mode 100644 index 000000000000..ecd604c12fc3 --- /dev/null +++ b/libc/src/time/mktime.h @@ -0,0 +1,23 @@ +//===-- Implementation header of mktime -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_TIME_MKTIME_H +#define LLVM_LIBC_SRC_TIME_MKTIME_H + +#include "src/time/mktime.h" +#include <time.h> + +namespace __llvm_libc { + +time_t mktime(struct tm *t1); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_TIME_MKTIME_H + +#include "include/time.h" diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt index aa606ae630bc..026e7eab8aef 100644 --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(stdlib) add_subdirectory(string) add_subdirectory(sys) add_subdirectory(threads) +add_subdirectory(time) add_subdirectory(unistd) set(public_test ${CMAKE_CURRENT_BINARY_DIR}/public_integration_test.cpp) diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt new file mode 100644 index 000000000000..c466af4214e2 --- /dev/null +++ b/libc/test/src/time/CMakeLists.txt @@ -0,0 +1,11 @@ +add_libc_testsuite(libc_time_unittests) + +add_libc_unittest( + mktime + SUITE + libc_time_unittests + SRCS + mktime_test.cpp + DEPENDS + libc.src.time.mktime +) diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp new file mode 100644 index 000000000000..1e4b60a3bb17 --- /dev/null +++ b/libc/test/src/time/mktime_test.cpp @@ -0,0 +1,157 @@ +//===-- Unittests for mktime ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/time/mktime.h" +#include "test/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" + +#include <errno.h> +#include <string.h> + +using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + +static constexpr time_t OutOfRangeReturnValue = -1; + +// A helper function to initialize tm data structure. +static inline void initialize_tm_data(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec) { + struct tm temp = {.tm_sec = sec, + .tm_min = min, + .tm_hour = hour, + .tm_mday = mday, + .tm_mon = month, + .tm_year = year - 1900}; + *tm_data = temp; +} + +static inline time_t call_mktime(struct tm *tm_data, int year, int month, + int mday, int hour, int min, int sec) { + initialize_tm_data(tm_data, year, month, mday, hour, min, sec); + return __llvm_libc::mktime(tm_data); +} + +TEST(MkTime, FailureSetsErrno) { + struct tm tm_data; + initialize_tm_data(&tm_data, 0, 0, 0, 0, 0, -1); + EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW)); +} + +TEST(MkTime, MktimeTestsInvalidSeconds) { + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, -1), OutOfRangeReturnValue); + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, 60), OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidMinutes) { + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, -1, 0), OutOfRangeReturnValue); + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 1, 0, 60, 0), OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidHours) { + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, -1, 0, 0), OutOfRangeReturnValue); + EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 24, 0, 0), OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidYear) { + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 1969, 0, 0, 0, 0, 0), OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidEndOf32BitEpochYear) { + if (sizeof(time_t) != 4) + return; + struct tm tm_data; + // 2038-01-19 03:14:08 tests overflow of the second in 2038. + EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 8), + OutOfRangeReturnValue); + // 2038-01-19 03:15:07 tests overflow of the minute in 2038. + EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 15, 7), + OutOfRangeReturnValue); + // 2038-01-19 04:14:07 tests overflow of the hour in 2038. + EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 4, 14, 7), + OutOfRangeReturnValue); + // 2038-01-20 03:14:07 tests overflow of the day in 2038. + EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 20, 3, 14, 7), + OutOfRangeReturnValue); + // 2038-02-19 03:14:07 tests overflow of the month in 2038. + EXPECT_EQ(call_mktime(&tm_data, 2038, 1, 19, 3, 14, 7), + OutOfRangeReturnValue); + // 2039-01-19 03:14:07 tests overflow of the year. + EXPECT_EQ(call_mktime(&tm_data, 2039, 0, 19, 3, 14, 7), + OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidMonths) { + struct tm tm_data; + // Before Jan of 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, -1, 15, 0, 0, 0), + OutOfRangeReturnValue); + // After Dec of 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, 12, 15, 0, 0, 0), + OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsInvalidDays) { + struct tm tm_data; + // -1 day of Jan, 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, 0, -1, 0, 0, 0), OutOfRangeReturnValue); + // 32 day of Jan, 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 32, 0, 0, 0), OutOfRangeReturnValue); + // 29 day of Feb, 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, 1, 29, 0, 0, 0), OutOfRangeReturnValue); + // 30 day of Feb, 1972 + EXPECT_EQ(call_mktime(&tm_data, 1972, 1, 30, 0, 0, 0), OutOfRangeReturnValue); + // 31 day of Apr, 1970 + EXPECT_EQ(call_mktime(&tm_data, 1970, 3, 31, 0, 0, 0), OutOfRangeReturnValue); +} + +TEST(MkTime, MktimeTestsStartEpochYear) { + // Thu Jan 1 00:00:00 1970 + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 0, 0, 0), static_cast<time_t>(0)); + EXPECT_EQ(4, tm_data.tm_wday); + EXPECT_EQ(0, tm_data.tm_yday); +} + +TEST(MkTime, MktimeTestsEpochYearRandomTime) { + // Thu Jan 1 12:50:50 1970 + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 12, 50, 50), + static_cast<time_t>(46250)); + EXPECT_EQ(4, tm_data.tm_wday); + EXPECT_EQ(0, tm_data.tm_yday); +} + +TEST(MkTime, MktimeTestsEndOf32BitEpochYear) { + struct tm tm_data; + // Test for maximum value of a signed 32-bit integer. + // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC. + EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 7), + static_cast<time_t>(0x7FFFFFFF)); + EXPECT_EQ(2, tm_data.tm_wday); + EXPECT_EQ(18, tm_data.tm_yday); +} + +TEST(MkTime, MktimeTests64BitYear) { + if (sizeof(time_t) == 4) + return; + // Mon Jan 1 12:50:50 2170 + struct tm tm_data; + EXPECT_EQ(call_mktime(&tm_data, 2170, 0, 1, 12, 50, 50), + static_cast<time_t>(6311479850)); + EXPECT_EQ(1, tm_data.tm_wday); + EXPECT_EQ(0, tm_data.tm_yday); + + // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year. + EXPECT_EQ(call_mktime(&tm_data, 2147483647, 0, 1, 12, 50, 50), + static_cast<time_t>(67767976202043050)); + EXPECT_EQ(2, tm_data.tm_wday); + EXPECT_EQ(0, tm_data.tm_yday); +} _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits