Skip to content

Commit b342d18

Browse files
[libc] add timeout and clock conversion utilities (llvm#91905)
This PR: - Make `clock_gettime` a header-only library - Add `clock_conversion` header library to allow conversion between clocks relative to the time of call - Add `timeout` header library to manage the absolute timeout used in POSIX's timed locking/waiting APIs
1 parent ec3bc2f commit b342d18

File tree

12 files changed

+267
-48
lines changed

12 files changed

+267
-48
lines changed

libc/src/__support/threads/linux/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ add_header_library(
1919
libc.src.__support.CPP.atomic
2020
libc.src.__support.CPP.limits
2121
libc.src.__support.CPP.optional
22-
libc.hdr.types.struct_timespec
22+
libc.src.__support.time.linux.abs_timeout
2323
)
2424

2525
add_header_library(

libc/src/__support/threads/linux/futex_utils.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,20 @@
99
#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
1010
#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
1111

12-
#include "hdr/types/struct_timespec.h"
1312
#include "src/__support/CPP/atomic.h"
1413
#include "src/__support/CPP/limits.h"
1514
#include "src/__support/CPP/optional.h"
1615
#include "src/__support/OSUtil/syscall.h"
1716
#include "src/__support/macros/attributes.h"
1817
#include "src/__support/threads/linux/futex_word.h"
18+
#include "src/__support/time/linux/abs_timeout.h"
1919
#include <linux/errno.h>
2020
#include <linux/futex.h>
2121

2222
namespace LIBC_NAMESPACE {
2323
class Futex : public cpp::Atomic<FutexWordType> {
2424
public:
25-
struct Timeout {
26-
timespec abs_time;
27-
bool is_realtime;
28-
};
25+
using Timeout = internal::AbsTimeout;
2926
LIBC_INLINE constexpr Futex(FutexWordType value)
3027
: cpp::Atomic<FutexWordType>(value) {}
3128
LIBC_INLINE Futex &operator=(FutexWordType value) {
@@ -37,7 +34,7 @@ class Futex : public cpp::Atomic<FutexWordType> {
3734
bool is_shared = false) {
3835
// use bitset variants to enforce abs_time
3936
uint32_t op = is_shared ? FUTEX_WAIT_BITSET : FUTEX_WAIT_BITSET_PRIVATE;
40-
if (timeout && timeout->is_realtime) {
37+
if (timeout && timeout->is_realtime()) {
4138
op |= FUTEX_CLOCK_REALTIME;
4239
}
4340
for (;;) {
@@ -49,7 +46,7 @@ class Futex : public cpp::Atomic<FutexWordType> {
4946
/* futex address */ this,
5047
/* futex operation */ op,
5148
/* expected value */ expected,
52-
/* timeout */ timeout ? &timeout->abs_time : nullptr,
49+
/* timeout */ timeout ? &timeout->get_timespec() : nullptr,
5350
/* ignored */ nullptr,
5451
/* bitset */ FUTEX_BITSET_MATCH_ANY);
5552

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
add_object_library(
1+
add_header_library(
22
clock_gettime
33
HDRS
44
clock_gettime.h
5-
SRCS
6-
clock_gettime.cpp
75
DEPENDS
86
libc.include.sys_syscall
97
libc.hdr.types.struct_timespec
@@ -12,3 +10,32 @@ add_object_library(
1210
libc.src.__support.error_or
1311
libc.src.__support.OSUtil.osutil
1412
)
13+
14+
add_header_library(
15+
clock_conversion
16+
HDRS
17+
clock_conversion.h
18+
DEPENDS
19+
.clock_gettime
20+
libc.src.__support.time.units
21+
)
22+
23+
add_header_library(
24+
abs_timeout
25+
HDRS
26+
abs_timeout.h
27+
DEPENDS
28+
libc.hdr.types.struct_timespec
29+
libc.src.__support.time.units
30+
libc.src.__support.CPP.expected
31+
)
32+
33+
add_header_library(
34+
monotonicity
35+
HDRS
36+
monotonicity.h
37+
DEPENDS
38+
.clock_conversion
39+
.abs_timeout
40+
libc.hdr.time_macros
41+
)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===--- Linux absolute timeout ---------------------------------*- C++ -*-===//
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+
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_ABS_TIMEOUT_H
10+
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_ABS_TIMEOUT_H
11+
12+
#include "hdr/time_macros.h"
13+
#include "hdr/types/struct_timespec.h"
14+
#include "src/__support/CPP/expected.h"
15+
#include "src/__support/time/units.h"
16+
17+
namespace LIBC_NAMESPACE {
18+
namespace internal {
19+
// We use AbsTimeout to remind ourselves that the timeout is an absolute time.
20+
// This is a simple wrapper around the timespec struct that also keeps track of
21+
// whether the time is in realtime or monotonic time.
22+
class AbsTimeout {
23+
timespec timeout;
24+
bool realtime_flag;
25+
LIBC_INLINE constexpr explicit AbsTimeout(timespec ts, bool realtime)
26+
: timeout(ts), realtime_flag(realtime) {}
27+
28+
public:
29+
enum class Error { Invalid, BeforeEpoch };
30+
LIBC_INLINE const timespec &get_timespec() const { return timeout; }
31+
LIBC_INLINE bool is_realtime() const { return realtime_flag; }
32+
LIBC_INLINE static constexpr cpp::expected<AbsTimeout, Error>
33+
from_timespec(timespec ts, bool realtime) {
34+
using namespace time_units;
35+
if (ts.tv_nsec < 0 || ts.tv_nsec >= 1_s_ns)
36+
return cpp::unexpected<Error>(Error::Invalid);
37+
38+
// POSIX allows tv_sec to be negative. We interpret this as an expired
39+
// timeout.
40+
if (ts.tv_sec < 0)
41+
return cpp::unexpected<Error>(Error::BeforeEpoch);
42+
43+
return AbsTimeout{ts, realtime};
44+
}
45+
};
46+
} // namespace internal
47+
} // namespace LIBC_NAMESPACE
48+
49+
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_ABS_TIMEOUT_H
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- clock conversion linux implementation ------------------*- C++ -*-===//
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+
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_CONVERSION_H
10+
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_CONVERSION_H
11+
12+
#include "src/__support/time/linux/clock_gettime.h"
13+
#include "src/__support/time/units.h"
14+
15+
namespace LIBC_NAMESPACE {
16+
namespace internal {
17+
18+
LIBC_INLINE timespec convert_clock(timespec input, clockid_t from,
19+
clockid_t to) {
20+
using namespace time_units;
21+
timespec from_time;
22+
timespec to_time;
23+
timespec output;
24+
internal::clock_gettime(from, &from_time);
25+
internal::clock_gettime(to, &to_time);
26+
output.tv_sec = input.tv_sec - from_time.tv_sec + to_time.tv_sec;
27+
output.tv_nsec = input.tv_nsec - from_time.tv_nsec + to_time.tv_nsec;
28+
29+
if (output.tv_nsec > 1_s_ns) {
30+
output.tv_sec++;
31+
output.tv_nsec -= 1_s_ns;
32+
} else if (output.tv_nsec < 0) {
33+
output.tv_sec--;
34+
output.tv_nsec += 1_s_ns;
35+
}
36+
return output;
37+
}
38+
39+
} // namespace internal
40+
} // namespace LIBC_NAMESPACE
41+
42+
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_CONVERSION_H

libc/src/__support/time/linux/clock_gettime.cpp

Lines changed: 0 additions & 35 deletions
This file was deleted.

libc/src/__support/time/linux/clock_gettime.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,37 @@
88

99
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
1010
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
11+
1112
#include "hdr/types/clockid_t.h"
1213
#include "hdr/types/struct_timespec.h"
14+
#include "src/__support/OSUtil/syscall.h"
1315
#include "src/__support/common.h"
14-
1516
#include "src/__support/error_or.h"
17+
#include <sys/syscall.h>
1618

1719
namespace LIBC_NAMESPACE {
1820
namespace internal {
19-
ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts);
21+
LIBC_INLINE ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
22+
#if SYS_clock_gettime
23+
int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_gettime,
24+
static_cast<long>(clockid),
25+
reinterpret_cast<long>(ts));
26+
#elif defined(SYS_clock_gettime64)
27+
static_assert(
28+
sizeof(time_t) == sizeof(int64_t),
29+
"SYS_clock_gettime64 requires struct timespec with 64-bit members.");
30+
int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_gettime64,
31+
static_cast<long>(clockid),
32+
reinterpret_cast<long>(ts));
33+
#else
34+
#error "SYS_clock_gettime and SYS_clock_gettime64 syscalls not available."
35+
#endif
36+
if (ret < 0)
37+
return Error(-ret);
38+
return ret;
2039
}
40+
41+
} // namespace internal
2142
} // namespace LIBC_NAMESPACE
2243

2344
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===--- timeout linux implementation ---------------------------*- C++ -*-===//
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+
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_MONOTONICITY_H
10+
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_MONOTONICITY_H
11+
12+
#include "hdr/time_macros.h"
13+
#include "src/__support/libc_assert.h"
14+
#include "src/__support/time/linux/abs_timeout.h"
15+
#include "src/__support/time/linux/clock_conversion.h"
16+
namespace LIBC_NAMESPACE {
17+
namespace internal {
18+
// This function is separated from abs_timeout.
19+
// This function pulls in the dependency to clock_conversion.h,
20+
// which may transitively depend on vDSO hence futex. However, this structure
21+
// would be passed to futex, so we need to avoid cyclic dependencies.
22+
// This function is going to be used in timed locks. Pthread generally uses
23+
// realtime clocks for timeouts. However, due to non-monotoncity, realtime
24+
// clocks reportedly lead to undesired behaviors. Therefore, we also provide a
25+
// method to convert the timespec to a monotonic clock relative to the time of
26+
// function call.
27+
LIBC_INLINE void ensure_monotonicity(AbsTimeout &timeout) {
28+
if (timeout.is_realtime()) {
29+
auto res = AbsTimeout::from_timespec(
30+
convert_clock(timeout.get_timespec(), CLOCK_REALTIME, CLOCK_MONOTONIC),
31+
false);
32+
33+
LIBC_ASSERT(res.has_value());
34+
if (!res.has_value())
35+
__builtin_unreachable();
36+
37+
timeout = *res;
38+
}
39+
}
40+
} // namespace internal
41+
} // namespace LIBC_NAMESPACE
42+
43+
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_MONOTONICITY_H

libc/test/src/__support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,4 @@ add_subdirectory(OSUtil)
206206
add_subdirectory(FPUtil)
207207
add_subdirectory(fixed_point)
208208
add_subdirectory(HashTable)
209+
add_subdirectory(time)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_custom_target(libc-support-time-tests)
2+
3+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
4+
add_subdirectory(${LIBC_TARGET_OS})
5+
endif()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
add_libc_test(
2+
timeout_test
3+
SUITE libc-support-time-tests
4+
SRCS timeout_test.cpp
5+
DEPENDS
6+
libc.src.__support.time.linux.abs_timeout
7+
libc.src.__support.time.linux.monotonicity
8+
libc.src.__support.CPP.expected
9+
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===-- unit tests for linux's timeout utilities --------------------------===//
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+
#include "src/__support/CPP/expected.h"
10+
#include "src/__support/time/linux/abs_timeout.h"
11+
#include "src/__support/time/linux/monotonicity.h"
12+
#include "test/UnitTest/Test.h"
13+
14+
template <class T, class E>
15+
using expected = LIBC_NAMESPACE::cpp::expected<T, E>;
16+
using AbsTimeout = LIBC_NAMESPACE::internal::AbsTimeout;
17+
18+
TEST(LlvmLibcSupportLinuxTimeoutTest, NegativeSecond) {
19+
timespec ts = {-1, 0};
20+
expected<AbsTimeout, AbsTimeout::Error> result =
21+
AbsTimeout::from_timespec(ts, false);
22+
ASSERT_FALSE(result.has_value());
23+
ASSERT_EQ(result.error(), AbsTimeout::Error::BeforeEpoch);
24+
}
25+
TEST(LlvmLibcSupportLinuxTimeoutTest, OverflowNano) {
26+
using namespace LIBC_NAMESPACE::time_units;
27+
timespec ts = {0, 2_s_ns};
28+
expected<AbsTimeout, AbsTimeout::Error> result =
29+
AbsTimeout::from_timespec(ts, false);
30+
ASSERT_FALSE(result.has_value());
31+
ASSERT_EQ(result.error(), AbsTimeout::Error::Invalid);
32+
}
33+
TEST(LlvmLibcSupportLinuxTimeoutTest, UnderflowNano) {
34+
timespec ts = {0, -1};
35+
expected<AbsTimeout, AbsTimeout::Error> result =
36+
AbsTimeout::from_timespec(ts, false);
37+
ASSERT_FALSE(result.has_value());
38+
ASSERT_EQ(result.error(), AbsTimeout::Error::Invalid);
39+
}
40+
TEST(LlvmLibcSupportLinuxTimeoutTest, NoChangeIfClockIsMonotonic) {
41+
timespec ts = {10000, 0};
42+
expected<AbsTimeout, AbsTimeout::Error> result =
43+
AbsTimeout::from_timespec(ts, false);
44+
ASSERT_TRUE(result.has_value());
45+
ensure_monotonicity(*result);
46+
ASSERT_FALSE(result->is_realtime());
47+
ASSERT_EQ(result->get_timespec().tv_sec, static_cast<time_t>(10000));
48+
ASSERT_EQ(result->get_timespec().tv_nsec, static_cast<time_t>(0));
49+
}
50+
TEST(LlvmLibcSupportLinuxTimeoutTest, ValidAfterConversion) {
51+
timespec ts;
52+
LIBC_NAMESPACE::internal::clock_gettime(CLOCK_REALTIME, &ts);
53+
expected<AbsTimeout, AbsTimeout::Error> result =
54+
AbsTimeout::from_timespec(ts, true);
55+
ASSERT_TRUE(result.has_value());
56+
ensure_monotonicity(*result);
57+
ASSERT_FALSE(result->is_realtime());
58+
ASSERT_TRUE(
59+
AbsTimeout::from_timespec(result->get_timespec(), false).has_value());
60+
}

0 commit comments

Comments
 (0)