From 3825e72a3c0988ad0802ff665d04e64939632ba2 Mon Sep 17 00:00:00 2001 From: Arne Coucheron Date: Tue, 18 Apr 2017 14:44:08 +0200 Subject: [PATCH] compat: libc: Transition to OSS libc shim Change-Id: I607a6016bb3e290713b0faa10c6ee2f875d61158 --- libc/Android.mk | 30 ++++ libc/bionic/bionic_time_conversions.cpp | 22 +++ libc/bionic/pthread_cond.cpp | 220 ++++++++++++++++++++++++ libc/private/bionic_futex.h | 45 +++++ libc/private/bionic_time_conversions.h | 14 ++ 5 files changed, 331 insertions(+) create mode 100644 libc/Android.mk create mode 100644 libc/bionic/bionic_time_conversions.cpp create mode 100644 libc/bionic/pthread_cond.cpp create mode 100644 libc/private/bionic_futex.h create mode 100644 libc/private/bionic_time_conversions.h diff --git a/libc/Android.mk b/libc/Android.mk new file mode 100644 index 0000000..1276c4d --- /dev/null +++ b/libc/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2013-2016, The CyanogenMod Project +# Copyright (C) 2018, The LineageOS Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + bionic/bionic_time_conversions.cpp \ + bionic/pthread_cond.cpp +LOCAL_SHARED_LIBRARIES := libc +LOCAL_MODULE := libshim_c +LOCAL_VENDOR_MODULE := true +LOCAL_CLANG := false +LOCAL_CXX_STL := none +LOCAL_SANITIZE := never +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := SHARED_LIBRARIES +include $(BUILD_SHARED_LIBRARY) diff --git a/libc/bionic/bionic_time_conversions.cpp b/libc/bionic/bionic_time_conversions.cpp new file mode 100644 index 0000000..67d08dd --- /dev/null +++ b/libc/bionic/bionic_time_conversions.cpp @@ -0,0 +1,22 @@ +#include "private/bionic_time_conversions.h" + +#define NS_PER_S 1000000000 + +void timespec_from_ms(timespec& ts, const int ms) { + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; +} + +bool timespec_from_absolute_timespec(timespec& ts, const timespec& abs_ts, clockid_t clock) { + clock_gettime(clock, &ts); + ts.tv_sec = abs_ts.tv_sec - ts.tv_sec; + ts.tv_nsec = abs_ts.tv_nsec - ts.tv_nsec; + if (ts.tv_nsec < 0) { + ts.tv_sec--; + ts.tv_nsec += NS_PER_S; + } + if (ts.tv_nsec < 0 || ts.tv_sec < 0) { + return false; + } + return true; +} diff --git a/libc/bionic/pthread_cond.cpp b/libc/bionic/pthread_cond.cpp new file mode 100644 index 0000000..1e63def --- /dev/null +++ b/libc/bionic/pthread_cond.cpp @@ -0,0 +1,220 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "private/bionic_futex.h" +#include "private/bionic_time_conversions.h" + +// XXX *technically* there is a race condition that could allow +// XXX a signal to be missed. If thread A is preempted in _wait() +// XXX after unlocking the mutex and before waiting, and if other +// XXX threads call signal or broadcast UINT_MAX/2 times (exactly), +// XXX before thread A is scheduled again and calls futex_wait(), +// XXX then the signal will be lost. + +// We use one bit in pthread_condattr_t (long) values as the 'shared' flag +// and one bit for the clock type (CLOCK_REALTIME is ((clockid_t) 1), and +// CLOCK_MONOTONIC is ((clockid_t) 0).). The rest of the bits are a counter. +// +// The 'value' field pthread_cond_t has the same layout. + +#define COND_SHARED_MASK 0x0001 +#define COND_CLOCK_MASK 0x0002 +#define COND_COUNTER_STEP 0x0004 +#define COND_FLAGS_MASK (COND_SHARED_MASK | COND_CLOCK_MASK) +#define COND_COUNTER_MASK (~COND_FLAGS_MASK) + +#define COND_IS_SHARED(c) (((c) & COND_SHARED_MASK) != 0) +#define COND_GET_CLOCK(c) (((c) & COND_CLOCK_MASK) >> 1) +#define COND_SET_CLOCK(attr, c) ((attr) | (c << 1)) + +int pthread_condattr_init(pthread_condattr_t* attr) { + *attr = 0; + *attr |= PTHREAD_PROCESS_PRIVATE; + *attr |= (CLOCK_REALTIME << 1); + return 0; +} + +int pthread_condattr_getpshared(const pthread_condattr_t* attr, int* pshared) { + *pshared = static_cast(COND_IS_SHARED(*attr)); + return 0; +} + +int pthread_condattr_setpshared(pthread_condattr_t* attr, int pshared) { + if (pshared != PTHREAD_PROCESS_SHARED && pshared != PTHREAD_PROCESS_PRIVATE) { + return EINVAL; + } + + *attr |= pshared; + return 0; +} + +int pthread_condattr_getclock(const pthread_condattr_t* attr, clockid_t* clock) { + *clock = COND_GET_CLOCK(*attr); + return 0; +} + +int pthread_condattr_setclock(pthread_condattr_t* attr, clockid_t clock) { + if (clock != CLOCK_MONOTONIC && clock != CLOCK_REALTIME) { + return EINVAL; + } + + *attr = COND_SET_CLOCK(*attr, clock); + return 0; +} + +int pthread_condattr_destroy(pthread_condattr_t* attr) { + *attr = 0xdeada11d; + return 0; +} + +struct pthread_cond_internal_t { + atomic_uint state; + + bool process_shared() { + return COND_IS_SHARED(atomic_load_explicit(&state, memory_order_relaxed)); + } + + int get_clock() { + return COND_GET_CLOCK(atomic_load_explicit(&state, memory_order_relaxed)); + } + +#if defined(__LP64__) + char __reserved[44]; +#endif +}; + +static_assert(sizeof(pthread_cond_t) == sizeof(pthread_cond_internal_t), + "pthread_cond_t should actually be pthread_cond_internal_t in implementation."); + +// For binary compatibility with old version of pthread_cond_t, we can't use more strict alignment +// than 4-byte alignment. +static_assert(alignof(pthread_cond_t) == 4, + "pthread_cond_t should fulfill the alignment requirement of pthread_cond_internal_t."); + +static pthread_cond_internal_t* __get_internal_cond(pthread_cond_t* cond_interface) { + return reinterpret_cast(cond_interface); +} + +int pthread_cond_init(pthread_cond_t* cond_interface, const pthread_condattr_t* attr) { + pthread_cond_internal_t* cond = __get_internal_cond(cond_interface); + + unsigned int init_state = 0; + if (attr != NULL) { + init_state = (*attr & COND_FLAGS_MASK); + } + atomic_init(&cond->state, init_state); + + return 0; +} + +int pthread_cond_destroy(pthread_cond_t* cond_interface) { + pthread_cond_internal_t* cond = __get_internal_cond(cond_interface); + atomic_store_explicit(&cond->state, 0xdeadc04d, memory_order_relaxed); + return 0; +} + +// This function is used by pthread_cond_broadcast and +// pthread_cond_signal to atomically decrement the counter +// then wake up thread_count threads. +static int __pthread_cond_pulse(pthread_cond_internal_t* cond, int thread_count) { + // We don't use a release/seq_cst fence here. Because pthread_cond_wait/signal can't be + // used as a method for memory synchronization by itself. It should always be used with + // pthread mutexes. Note that Spurious wakeups from pthread_cond_wait/timedwait may occur, + // so when using condition variables there is always a boolean predicate involving shared + // variables associated with each condition wait that is true if the thread should proceed. + // If the predicate is seen true before a condition wait, pthread_cond_wait/timedwait will + // not be called. That's why pthread_wait/signal pair can't be used as a method for memory + // synchronization. And it doesn't help even if we use any fence here. + + // The increase of value should leave flags alone, even if the value can overflows. + atomic_fetch_add_explicit(&cond->state, COND_COUNTER_STEP, memory_order_relaxed); + + __futex_wake_ex(&cond->state, cond->process_shared(), thread_count); + return 0; +} + +static int __pthread_cond_timedwait_relative(pthread_cond_internal_t* cond, pthread_mutex_t* mutex, + const timespec* rel_timeout_or_null) { + unsigned int old_state = atomic_load_explicit(&cond->state, memory_order_relaxed); + + pthread_mutex_unlock(mutex); + int status = __futex_wait_ex(&cond->state, cond->process_shared(), old_state, rel_timeout_or_null); + pthread_mutex_lock(mutex); + + if (status == -ETIMEDOUT) { + return ETIMEDOUT; + } + return 0; +} + +static int __pthread_cond_timedwait(pthread_cond_internal_t* cond, pthread_mutex_t* mutex, + const timespec* abs_timeout_or_null, clockid_t clock) { + timespec ts; + timespec* rel_timeout = NULL; + + if (abs_timeout_or_null != NULL) { + rel_timeout = &ts; + if (!timespec_from_absolute_timespec(*rel_timeout, *abs_timeout_or_null, clock)) { + return ETIMEDOUT; + } + } + + return __pthread_cond_timedwait_relative(cond, mutex, rel_timeout); +} + +int pthread_cond_broadcast(pthread_cond_t* cond_interface) { + return __pthread_cond_pulse(__get_internal_cond(cond_interface), INT_MAX); +} + +int pthread_cond_signal(pthread_cond_t* cond_interface) { + return __pthread_cond_pulse(__get_internal_cond(cond_interface), 1); +} + +int pthread_cond_wait(pthread_cond_t* cond_interface, pthread_mutex_t* mutex) { + pthread_cond_internal_t* cond = __get_internal_cond(cond_interface); + return __pthread_cond_timedwait(cond, mutex, NULL, cond->get_clock()); +} + +int pthread_cond_timedwait(pthread_cond_t *cond_interface, pthread_mutex_t * mutex, + const timespec *abstime) { + + pthread_cond_internal_t* cond = __get_internal_cond(cond_interface); + return __pthread_cond_timedwait(cond, mutex, abstime, cond->get_clock()); +} + +#if !defined(__LP64__) +// TODO: this exists only for backward binary compatibility on 32 bit platforms. +extern "C" int pthread_cond_timedwait_monotonic(pthread_cond_t* cond_interface, + pthread_mutex_t* mutex, + const timespec* abs_timeout) { + + return __pthread_cond_timedwait(__get_internal_cond(cond_interface), mutex, abs_timeout, + CLOCK_MONOTONIC); +} + +extern "C" int pthread_cond_timedwait_monotonic_np(pthread_cond_t* cond_interface, + pthread_mutex_t* mutex, + const timespec* abs_timeout) { + return pthread_cond_timedwait_monotonic(cond_interface, mutex, abs_timeout); +} + +extern "C" int pthread_cond_timedwait_relative_np(pthread_cond_t* cond_interface, + pthread_mutex_t* mutex, + const timespec* rel_timeout) { + + return __pthread_cond_timedwait_relative(__get_internal_cond(cond_interface), mutex, rel_timeout); +} + +extern "C" int pthread_cond_timeout_np(pthread_cond_t* cond_interface, + pthread_mutex_t* mutex, unsigned ms) { + timespec ts; + timespec_from_ms(ts, ms); + return pthread_cond_timedwait_relative_np(cond_interface, mutex, &ts); +} +#endif // !defined(__LP64__) diff --git a/libc/private/bionic_futex.h b/libc/private/bionic_futex.h new file mode 100644 index 0000000..7fe90e3 --- /dev/null +++ b/libc/private/bionic_futex.h @@ -0,0 +1,45 @@ +#ifndef _BIONIC_FUTEX_H +#define _BIONIC_FUTEX_H + +#include +#include +#include +#include +#include +#include +#include + +__BEGIN_DECLS + +struct timespec; + +static inline __always_inline int __futex(volatile void* ftx, int op, int value, const struct timespec* timeout) { + // Our generated syscall assembler sets errno, but our callers (pthread functions) don't want to. + int saved_errno = errno; + int result = syscall(__NR_futex, ftx, op, value, timeout); + if (__predict_false(result == -1)) { + result = -errno; + errno = saved_errno; + } + return result; +} + +static inline int __futex_wake(volatile void* ftx, int count) { + return __futex(ftx, FUTEX_WAKE, count, NULL); +} + +static inline int __futex_wake_ex(volatile void* ftx, bool shared, int count) { + return __futex(ftx, shared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE, count, NULL); +} + +static inline int __futex_wait(volatile void* ftx, int value, const struct timespec* timeout) { + return __futex(ftx, FUTEX_WAIT, value, timeout); +} + +static inline int __futex_wait_ex(volatile void* ftx, bool shared, int value, const struct timespec* timeout) { + return __futex(ftx, shared ? FUTEX_WAIT : FUTEX_WAIT_PRIVATE, value, timeout); +} + +__END_DECLS + +#endif /* _BIONIC_FUTEX_H */ diff --git a/libc/private/bionic_time_conversions.h b/libc/private/bionic_time_conversions.h new file mode 100644 index 0000000..e4dfc0c --- /dev/null +++ b/libc/private/bionic_time_conversions.h @@ -0,0 +1,14 @@ +#ifndef _BIONIC_TIME_CONVERSIONS_H +#define _BIONIC_TIME_CONVERSIONS_H + +#include +#include + +__BEGIN_DECLS + +__LIBC_HIDDEN__ void timespec_from_ms(timespec& ts, const int ms); +__LIBC_HIDDEN__ bool timespec_from_absolute_timespec(timespec& ts, const timespec& abs_ts, clockid_t clock); + +__END_DECLS + +#endif