dart-sdk/runtime/vm/os_thread_fuchsia.cc
Corey Tabaka db0fa84d59 [vm] Optimize get current thread id in Fuchsia.
OSThread::GetCurrentThreadId() is used in many places throughout
the vm, particularly in facilities that are used heavily, such as
SafepointRwLock. The high frequency of invocations means that the
runtime overhead of getting the current thread id has a significant
impact on the overall performance of the vm.

The current Fuchsia implemention of OSThread::GetCurrentThreadId()
makes a syscall to get the koid of the current thread's handle,
resulting in hundreds of nanoseconds of unnecessary overhead per
invocation.

This patch changes to using the thread's handle as the id, which is
guaranteed to be unique for the lifetime of the thread, unless it
is closed prematurely (nothing in the vm does this). This approach
is similar to what MUSL libc (the libc used by Fuchsia) does for
the same purpose and has significant mileage.

Bug: b/182183059
TEST=existing ci test suite; manually deployed into Fuchsia build.

Change-Id: I274a793a823a717c8dd206b396001c6acb897b9a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193643
Reviewed-by: Chase Latta <chaselatta@google.com>
Commit-Queue: Chase Latta <chaselatta@google.com>
2021-03-31 20:51:32 +00:00

496 lines
14 KiB
C++

// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h" // NOLINT
#if defined(HOST_OS_FUCHSIA)
#include "vm/os.h"
#include "vm/os_thread.h"
#include "vm/os_thread_fuchsia.h"
#include <errno.h> // NOLINT
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <zircon/tls.h>
#include <zircon/types.h>
#include "platform/address_sanitizer.h"
#include "platform/assert.h"
#include "platform/safe_stack.h"
namespace dart {
#define VALIDATE_PTHREAD_RESULT(result) \
if (result != 0) { \
FATAL1("pthread error: %d", result); \
}
#if defined(PRODUCT)
#define VALIDATE_PTHREAD_RESULT_NAMED(result) VALIDATE_PTHREAD_RESULT(result)
#else
#define VALIDATE_PTHREAD_RESULT_NAMED(result) \
if (result != 0) { \
FATAL2("[%s] pthread error: %d", name_, result); \
}
#endif
#if defined(DEBUG)
#define ASSERT_PTHREAD_SUCCESS(result) VALIDATE_PTHREAD_RESULT(result)
#else
// NOTE: This (currently) expands to a no-op.
#define ASSERT_PTHREAD_SUCCESS(result) ASSERT(result == 0)
#endif
#ifdef DEBUG
#define RETURN_ON_PTHREAD_FAILURE(result) \
if (result != 0) { \
fprintf(stderr, "%s:%d: pthread error: %d\n", __FILE__, __LINE__, result); \
return result; \
}
#else
#define RETURN_ON_PTHREAD_FAILURE(result) \
if (result != 0) return result;
#endif
static void ComputeTimeSpecMicros(struct timespec* ts, int64_t micros) {
// time in nanoseconds.
zx_time_t now = zx_clock_get_monotonic();
zx_time_t target = now + (micros * kNanosecondsPerMicrosecond);
int64_t secs = target / kNanosecondsPerSecond;
int64_t nanos = target - (secs * kNanosecondsPerSecond);
ts->tv_sec = secs;
ts->tv_nsec = nanos;
}
class ThreadStartData {
public:
ThreadStartData(const char* name,
OSThread::ThreadStartFunction function,
uword parameter)
: name_(name), function_(function), parameter_(parameter) {}
const char* name() const { return name_; }
OSThread::ThreadStartFunction function() const { return function_; }
uword parameter() const { return parameter_; }
private:
const char* name_;
OSThread::ThreadStartFunction function_;
uword parameter_;
DISALLOW_COPY_AND_ASSIGN(ThreadStartData);
};
// Dispatch to the thread start function provided by the caller. This trampoline
// is used to ensure that the thread is properly destroyed if the thread just
// exits.
static void* ThreadStart(void* data_ptr) {
ThreadStartData* data = reinterpret_cast<ThreadStartData*>(data_ptr);
const char* name = data->name();
OSThread::ThreadStartFunction function = data->function();
uword parameter = data->parameter();
delete data;
// Set the thread name.
zx_handle_t thread_handle = thrd_get_zx_handle(thrd_current());
zx_object_set_property(thread_handle, ZX_PROP_NAME, name, strlen(name) + 1);
// Create new OSThread object and set as TLS for new thread.
OSThread* thread = OSThread::CreateOSThread();
if (thread != NULL) {
OSThread::SetCurrent(thread);
thread->set_name(name);
// Call the supplied thread start function handing it its parameters.
function(parameter);
}
return NULL;
}
int OSThread::Start(const char* name,
ThreadStartFunction function,
uword parameter) {
pthread_attr_t attr;
int result = pthread_attr_init(&attr);
RETURN_ON_PTHREAD_FAILURE(result);
result = pthread_attr_setstacksize(&attr, OSThread::GetMaxStackSize());
RETURN_ON_PTHREAD_FAILURE(result);
ThreadStartData* data = new ThreadStartData(name, function, parameter);
pthread_t tid;
result = pthread_create(&tid, &attr, ThreadStart, data);
RETURN_ON_PTHREAD_FAILURE(result);
result = pthread_attr_destroy(&attr);
RETURN_ON_PTHREAD_FAILURE(result);
return 0;
}
const ThreadId OSThread::kInvalidThreadId = ZX_HANDLE_INVALID;
const ThreadJoinId OSThread::kInvalidThreadJoinId =
static_cast<ThreadJoinId>(0);
ThreadLocalKey OSThread::CreateThreadLocal(ThreadDestructor destructor) {
pthread_key_t key = kUnsetThreadLocalKey;
int result = pthread_key_create(&key, destructor);
VALIDATE_PTHREAD_RESULT(result);
ASSERT(key != kUnsetThreadLocalKey);
return key;
}
void OSThread::DeleteThreadLocal(ThreadLocalKey key) {
ASSERT(key != kUnsetThreadLocalKey);
int result = pthread_key_delete(key);
VALIDATE_PTHREAD_RESULT(result);
}
void OSThread::SetThreadLocal(ThreadLocalKey key, uword value) {
ASSERT(key != kUnsetThreadLocalKey);
int result = pthread_setspecific(key, reinterpret_cast<void*>(value));
VALIDATE_PTHREAD_RESULT(result);
}
intptr_t OSThread::GetMaxStackSize() {
const int kStackSize = (128 * kWordSize * KB);
return kStackSize;
}
ThreadId OSThread::GetCurrentThreadId() {
return thrd_get_zx_handle(thrd_current());
}
#ifdef SUPPORT_TIMELINE
ThreadId OSThread::GetCurrentThreadTraceId() {
return pthread_self();
}
#endif // PRODUCT
ThreadJoinId OSThread::GetCurrentThreadJoinId(OSThread* thread) {
ASSERT(thread != NULL);
// Make sure we're filling in the join id for the current thread.
ASSERT(thread->id() == GetCurrentThreadId());
// Make sure the join_id_ hasn't been set, yet.
DEBUG_ASSERT(thread->join_id_ == kInvalidThreadJoinId);
pthread_t id = pthread_self();
#if defined(DEBUG)
thread->join_id_ = id;
#endif
return id;
}
void OSThread::Join(ThreadJoinId id) {
int result = pthread_join(id, NULL);
ASSERT(result == 0);
}
intptr_t OSThread::ThreadIdToIntPtr(ThreadId id) {
ASSERT(sizeof(id) == sizeof(intptr_t));
return static_cast<intptr_t>(id);
}
ThreadId OSThread::ThreadIdFromIntPtr(intptr_t id) {
return static_cast<ThreadId>(id);
}
bool OSThread::Compare(ThreadId a, ThreadId b) {
return pthread_equal(a, b) != 0;
}
bool OSThread::GetCurrentStackBounds(uword* lower, uword* upper) {
pthread_attr_t attr;
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base;
size_t size;
int error = pthread_attr_getstack(&attr, &base, &size);
pthread_attr_destroy(&attr);
if (error != 0) {
return false;
}
*lower = reinterpret_cast<uword>(base);
*upper = *lower + size;
return true;
}
#if defined(USING_SAFE_STACK)
#define STRINGIFY(s) #s
NO_SANITIZE_ADDRESS
NO_SANITIZE_SAFE_STACK
uword OSThread::GetCurrentSafestackPointer() {
uword result;
#if defined(HOST_ARCH_X64)
#define _loadfsword(index) "movq %%fs:" STRINGIFY(index) ", %0"
asm volatile(
_loadfsword(ZX_TLS_UNSAFE_SP_OFFSET)
: "=r"(result) // outputs
);
#undef _loadfsword
#elif defined(HOST_ARCH_ARM64)
#define _loadword(index) "ldr %0, [%0, " STRINGIFY(index) "]"
asm volatile(
"mrs %0, TPIDR_EL0;\n"
_loadword(ZX_TLS_UNSAFE_SP_OFFSET)
: "=r"(result) // outputs
);
#else
#error "Architecture not supported"
#endif
return result;
}
NO_SANITIZE_ADDRESS
NO_SANITIZE_SAFE_STACK
void OSThread::SetCurrentSafestackPointer(uword ssp) {
#if defined(HOST_ARCH_X64)
#define str(s) #s
#define _storefsword(index) "movq %0, %%fs:" str(index)
asm volatile(
_storefsword(ZX_TLS_UNSAFE_SP_OFFSET)
: // outputs.
: "r"(ssp) // inputs.
: // clobbered.
);
#undef _storefsword
#undef str
#elif defined(HOST_ARCH_ARM64)
#define _storeword(index) "str %1, [%0, " STRINGIFY(index) "]"
uword tmp;
asm volatile(
"mrs %0, TPIDR_EL0;\n"
_storeword(ZX_TLS_UNSAFE_SP_OFFSET)
: "=r"(tmp) // outputs.
: "r"(ssp) // inputs.
: // clobbered.
);
#else
#error "Architecture not supported"
#endif
}
#undef STRINGIFY
#endif
Mutex::Mutex(NOT_IN_PRODUCT(const char* name))
#if !defined(PRODUCT)
: name_(name)
#endif
{
pthread_mutexattr_t attr;
int result = pthread_mutexattr_init(&attr);
VALIDATE_PTHREAD_RESULT_NAMED(result);
#if defined(DEBUG)
result = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
VALIDATE_PTHREAD_RESULT_NAMED(result);
#endif // defined(DEBUG)
result = pthread_mutex_init(data_.mutex(), &attr);
// Verify that creating a pthread_mutex succeeded.
VALIDATE_PTHREAD_RESULT_NAMED(result);
result = pthread_mutexattr_destroy(&attr);
VALIDATE_PTHREAD_RESULT_NAMED(result);
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
owner_ = OSThread::kInvalidThreadId;
#endif // defined(DEBUG)
}
Mutex::~Mutex() {
int result = pthread_mutex_destroy(data_.mutex());
// Verify that the pthread_mutex was destroyed.
VALIDATE_PTHREAD_RESULT_NAMED(result);
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(owner_ == OSThread::kInvalidThreadId);
#endif // defined(DEBUG)
}
void Mutex::Lock() {
int result = pthread_mutex_lock(data_.mutex());
// Specifically check for dead lock to help debugging.
ASSERT(result != EDEADLK);
ASSERT_PTHREAD_SUCCESS(result); // Verify no other errors.
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
owner_ = OSThread::GetCurrentThreadId();
#endif // defined(DEBUG)
}
bool Mutex::TryLock() {
int result = pthread_mutex_trylock(data_.mutex());
// Return false if the lock is busy and locking failed.
if (result == EBUSY) {
return false;
}
ASSERT_PTHREAD_SUCCESS(result); // Verify no other errors.
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
owner_ = OSThread::GetCurrentThreadId();
#endif // defined(DEBUG)
return true;
}
void Mutex::Unlock() {
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(IsOwnedByCurrentThread());
owner_ = OSThread::kInvalidThreadId;
#endif // defined(DEBUG)
int result = pthread_mutex_unlock(data_.mutex());
// Specifically check for wrong thread unlocking to aid debugging.
ASSERT(result != EPERM);
ASSERT_PTHREAD_SUCCESS(result); // Verify no other errors.
}
Monitor::Monitor() {
pthread_mutexattr_t mutex_attr;
int result = pthread_mutexattr_init(&mutex_attr);
VALIDATE_PTHREAD_RESULT(result);
#if defined(DEBUG)
result = pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_ERRORCHECK);
VALIDATE_PTHREAD_RESULT(result);
#endif // defined(DEBUG)
result = pthread_mutex_init(data_.mutex(), &mutex_attr);
VALIDATE_PTHREAD_RESULT(result);
result = pthread_mutexattr_destroy(&mutex_attr);
VALIDATE_PTHREAD_RESULT(result);
pthread_condattr_t cond_attr;
result = pthread_condattr_init(&cond_attr);
VALIDATE_PTHREAD_RESULT(result);
result = pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
VALIDATE_PTHREAD_RESULT(result);
result = pthread_cond_init(data_.cond(), &cond_attr);
VALIDATE_PTHREAD_RESULT(result);
result = pthread_condattr_destroy(&cond_attr);
VALIDATE_PTHREAD_RESULT(result);
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
owner_ = OSThread::kInvalidThreadId;
#endif // defined(DEBUG)
}
Monitor::~Monitor() {
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(owner_ == OSThread::kInvalidThreadId);
#endif // defined(DEBUG)
int result = pthread_mutex_destroy(data_.mutex());
VALIDATE_PTHREAD_RESULT(result);
result = pthread_cond_destroy(data_.cond());
VALIDATE_PTHREAD_RESULT(result);
}
bool Monitor::TryEnter() {
int result = pthread_mutex_trylock(data_.mutex());
// Return false if the lock is busy and locking failed.
if (result == EBUSY) {
return false;
}
ASSERT_PTHREAD_SUCCESS(result); // Verify no other errors.
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(owner_ == OSThread::kInvalidThreadId);
owner_ = OSThread::GetCurrentThreadId();
#endif // defined(DEBUG)
return true;
}
void Monitor::Enter() {
int result = pthread_mutex_lock(data_.mutex());
VALIDATE_PTHREAD_RESULT(result);
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(owner_ == OSThread::kInvalidThreadId);
owner_ = OSThread::GetCurrentThreadId();
#endif // defined(DEBUG)
}
void Monitor::Exit() {
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(IsOwnedByCurrentThread());
owner_ = OSThread::kInvalidThreadId;
#endif // defined(DEBUG)
int result = pthread_mutex_unlock(data_.mutex());
VALIDATE_PTHREAD_RESULT(result);
}
Monitor::WaitResult Monitor::Wait(int64_t millis) {
Monitor::WaitResult retval = WaitMicros(millis * kMicrosecondsPerMillisecond);
return retval;
}
Monitor::WaitResult Monitor::WaitMicros(int64_t micros) {
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(IsOwnedByCurrentThread());
ThreadId saved_owner = owner_;
owner_ = OSThread::kInvalidThreadId;
#endif // defined(DEBUG)
Monitor::WaitResult retval = kNotified;
if (micros == kNoTimeout) {
// Wait forever.
int result = pthread_cond_wait(data_.cond(), data_.mutex());
VALIDATE_PTHREAD_RESULT(result);
} else {
struct timespec ts;
ComputeTimeSpecMicros(&ts, micros);
int result = pthread_cond_timedwait(data_.cond(), data_.mutex(), &ts);
ASSERT((result == 0) || (result == ETIMEDOUT));
if (result == ETIMEDOUT) {
retval = kTimedOut;
}
}
#if defined(DEBUG)
// When running with assertions enabled we track the owner.
ASSERT(owner_ == OSThread::kInvalidThreadId);
owner_ = OSThread::GetCurrentThreadId();
ASSERT(owner_ == saved_owner);
#endif // defined(DEBUG)
return retval;
}
void Monitor::Notify() {
// When running with assertions enabled we track the owner.
ASSERT(IsOwnedByCurrentThread());
int result = pthread_cond_signal(data_.cond());
VALIDATE_PTHREAD_RESULT(result);
}
void Monitor::NotifyAll() {
// When running with assertions enabled we track the owner.
ASSERT(IsOwnedByCurrentThread());
int result = pthread_cond_broadcast(data_.cond());
VALIDATE_PTHREAD_RESULT(result);
}
} // namespace dart
#endif // defined(HOST_OS_FUCHSIA)