| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // 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. |
| |
| #include "starboard/client_porting/pr_starboard/pr_starboard.h" |
| |
| #include "starboard/condition_variable.h" |
| #include "starboard/log.h" |
| #include "starboard/memory.h" |
| #include "starboard/mutex.h" |
| #include "starboard/once.h" |
| #include "starboard/queue.h" |
| #include "starboard/thread.h" |
| #include "starboard/time.h" |
| #include "starboard/types.h" |
| |
| namespace { |
| |
| typedef starboard::Queue<bool> SetupSignalQueue; |
| |
| // Utility function to convert a PRInterval to signed 64 bit integer |
| // microseconds. |
| int64_t PR_IntervalToMicrosecondsInt64(PRIntervalTime ticks) { |
| uint32_t microseconds_as_uint32 = PR_IntervalToMicroseconds(ticks); |
| int64_t microseconds_as_int64 = static_cast<int64_t>(microseconds_as_uint32); |
| return microseconds_as_int64; |
| } |
| |
| // Struct to bundle up arguments to be passed into SbThreadCreate. |
| struct ThreadEntryPointWrapperContext { |
| ThreadEntryPointWrapperContext(void* pr_context, |
| PRThread* pr_thread, |
| PRThreadEntryPoint pr_entry_point, |
| SetupSignalQueue* setup_signal_queue) |
| : pr_context(pr_context), |
| pr_thread(pr_thread), |
| pr_entry_point(pr_entry_point), |
| setup_signal_queue(setup_signal_queue) {} |
| void* pr_context; |
| PRThread* pr_thread; |
| PRThreadEntryPoint pr_entry_point; |
| SetupSignalQueue* setup_signal_queue; |
| }; |
| |
| // The thread local key that corresponds to where local PRThread* data is held. |
| SbThreadLocalKey g_pr_thread_local_key = kSbThreadLocalKeyInvalid; |
| // The SbOnceControlStructure to to ensure that the local key is only created |
| // once. |
| SbOnceControl g_pr_thread_key_once_control = SB_ONCE_INITIALIZER; |
| |
| void PrThreadDtor(void* value) { |
| PRThread* pr_thread = reinterpret_cast<PRThread*>(value); |
| delete pr_thread; |
| } |
| |
| void InitPrThreadKey() { |
| g_pr_thread_local_key = SbThreadCreateLocalKey(PrThreadDtor); |
| } |
| |
| SbThreadLocalKey GetPrThreadKey() { |
| SB_CHECK(SbOnce(&g_pr_thread_key_once_control, InitPrThreadKey)); |
| return g_pr_thread_local_key; |
| } |
| |
| void* ThreadEntryPointWrapper(void* context_as_void_pointer) { |
| ThreadEntryPointWrapperContext* context = |
| reinterpret_cast<ThreadEntryPointWrapperContext*>( |
| context_as_void_pointer); |
| void* pr_context = context->pr_context; |
| PRThreadEntryPoint pr_entry_point = context->pr_entry_point; |
| PRThread* pr_thread = context->pr_thread; |
| SetupSignalQueue* setup_signal_queue = context->setup_signal_queue; |
| |
| delete context; |
| |
| pr_thread->sb_thread = SbThreadGetCurrent(); |
| SbThreadLocalKey key = GetPrThreadKey(); |
| SB_CHECK(SbThreadIsValidLocalKey(key)); |
| SbThreadSetLocalValue(key, pr_thread); |
| |
| setup_signal_queue->Put(true); |
| pr_entry_point(pr_context); |
| |
| return NULL; |
| } |
| |
| } // namespace |
| |
| PRLock* PR_NewLock() { |
| PRLock* lock = new PRLock(); |
| if (!SbMutexCreate(lock)) { |
| delete lock; |
| return NULL; |
| } |
| return lock; |
| } |
| |
| void PR_DestroyLock(PRLock* lock) { |
| SB_DCHECK(lock); |
| SbMutexDestroy(lock); |
| delete lock; |
| } |
| |
| PRCondVar* PR_NewCondVar(PRLock* lock) { |
| SB_DCHECK(lock); |
| PRCondVar* cvar = new PRCondVar(); |
| if (!SbConditionVariableCreate(&cvar->sb_condition_variable, lock)) { |
| delete cvar; |
| return NULL; |
| } |
| cvar->lock = lock; |
| return cvar; |
| } |
| |
| void PR_DestroyCondVar(PRCondVar* cvar) { |
| SbConditionVariableDestroy(&cvar->sb_condition_variable); |
| delete cvar; |
| } |
| |
| PRStatus PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout) { |
| SbConditionVariableResult result; |
| if (timeout == PR_INTERVAL_NO_WAIT) { |
| result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable, |
| cvar->lock, 0); |
| } else if (timeout == PR_INTERVAL_NO_TIMEOUT) { |
| result = SbConditionVariableWait(&cvar->sb_condition_variable, cvar->lock); |
| } else { |
| int64_t microseconds = PR_IntervalToMicrosecondsInt64(timeout); |
| result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable, |
| cvar->lock, microseconds); |
| } |
| return pr_starboard::ToPRStatus(result != kSbConditionVariableFailed); |
| } |
| |
| PRThread* PR_GetCurrentThread() { |
| SbThreadLocalKey key = GetPrThreadKey(); |
| SB_CHECK(SbThreadIsValidLocalKey(key)); |
| |
| PRThread* value = static_cast<PRThread*>(SbThreadGetLocalValue(key)); |
| // We could potentially be a thread that was not created through |
| // PR_CreateThread. In this case, we must allocate a PRThread and do the |
| // setup that would normally have been done in PR_CreateThread. |
| if (!value) { |
| PRThread* pr_thread = new PRThread(SbThreadGetCurrent()); |
| SbThreadSetLocalValue(key, pr_thread); |
| value = pr_thread; |
| } |
| |
| return value; |
| } |
| |
| uint32_t PR_snprintf(char* out, uint32_t outlen, const char* fmt, ...) { |
| va_list args; |
| va_start(args, fmt); |
| uint32_t ret = PR_vsnprintf(out, outlen, fmt, args); |
| va_end(args); |
| return ret; |
| } |
| |
| PRThread* PR_CreateThread(PRThreadType type, |
| PRThreadEntryPoint start, |
| void* arg, |
| PRThreadPriority priority, |
| PRThreadScope scope, |
| PRThreadState state, |
| PRUint32 stackSize) { |
| int64_t sb_stack_size = static_cast<int64_t>(stackSize); |
| |
| SbThreadPriority sb_priority; |
| switch (priority) { |
| case PR_PRIORITY_LOW: |
| sb_priority = kSbThreadPriorityLow; |
| break; |
| case PR_PRIORITY_NORMAL: |
| sb_priority = kSbThreadPriorityNormal; |
| break; |
| case PR_PRIORITY_HIGH: |
| sb_priority = kSbThreadPriorityHigh; |
| break; |
| case PR_PRIORITY_LAST: |
| sb_priority = kSbThreadPriorityHighest; |
| break; |
| default: |
| sb_priority = kSbThreadNoPriority; |
| } |
| |
| SbThreadAffinity sb_affinity = kSbThreadNoAffinity; |
| |
| SB_DCHECK(state == PR_JOINABLE_THREAD || state == PR_UNJOINABLE_THREAD); |
| bool sb_joinable = (state == PR_JOINABLE_THREAD); |
| |
| // This heap allocated PRThread object will have a pointer to it stored in |
| // the newly created child thread's thread local storage. Once the newly |
| // created child thread finishes, it will be freed in the destructor |
| // associated with it through thread local storage. |
| PRThread* pr_thread = new PRThread(kSbThreadInvalid); |
| |
| // Utility queue for the ThreadEntryWrapper to signal us once it's done |
| // running its initial setup and we can safely exit. |
| SetupSignalQueue setup_signal_queue; |
| |
| // This heap allocated context object is freed after |
| // ThreadEntryPointWrapper's initial setup is complete, right before the |
| // nspr level entry point is run. |
| ThreadEntryPointWrapperContext* context = new ThreadEntryPointWrapperContext( |
| arg, pr_thread, start, &setup_signal_queue); |
| |
| // Note that pr_thread->sb_thread will be set to the correct value in the |
| // setup section of ThreadEntryPointWrapper. It is done there rather than |
| // here to account for the unlikely but possible case in which we enter the |
| // newly created child thread, and then the child thread passes references |
| // to itself off into its potential children or co-threads that interact |
| // with it before we can copy what SbThreadCreate returns into |
| // pr_thread->sb_thread from this current thread. |
| SbThreadCreate(sb_stack_size, sb_priority, sb_affinity, sb_joinable, NULL, |
| ThreadEntryPointWrapper, context); |
| |
| // Now we must wait for the setup section of ThreadEntryPointWrapper to run |
| // and initialize pr_thread (both the struct itself and the corresponding |
| // new thread's private data) before we can safely return. We expect to |
| // receive true rather than false by convention. |
| bool setup_signal = setup_signal_queue.Get(); |
| SB_DCHECK(setup_signal); |
| |
| return pr_thread; |
| } |
| |
| PRStatus PR_CallOnceWithArg(PRCallOnceType* once, |
| PRCallOnceWithArgFN func, |
| void* arg) { |
| SB_NOTREACHED() << "Not implemented"; |
| return PR_FAILURE; |
| } |
| |
| PRStatus PR_NewThreadPrivateIndex(PRTLSIndex* newIndex, |
| PRThreadPrivateDTOR destructor) { |
| SbThreadLocalKey key = SbThreadCreateLocalKey(destructor); |
| if (!SbThreadIsValidLocalKey(key)) { |
| return pr_starboard::ToPRStatus(false); |
| } |
| *newIndex = key; |
| return pr_starboard::ToPRStatus(true); |
| } |
| |
| PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli) { |
| PRUint64 tock = static_cast<PRUint64>(milli); |
| PRUint64 msecPerSec = static_cast<PRInt64>(PR_MSEC_PER_SEC); |
| PRUint64 rounding = static_cast<PRInt64>(PR_MSEC_PER_SEC >> 1); |
| PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond()); |
| |
| tock *= tps; |
| tock += rounding; |
| tock /= msecPerSec; |
| |
| PRUint64 ticks = static_cast<PRUint64>(tock); |
| return ticks; |
| } |
| |
| PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks) { |
| PRUint64 tock = static_cast<PRInt64>(ticks); |
| PRUint64 usecPerSec = static_cast<PRInt64>(PR_USEC_PER_SEC); |
| PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond()); |
| PRUint64 rounding = static_cast<PRUint64>(tps) >> 1; |
| |
| tock *= usecPerSec; |
| tock += rounding; |
| tock /= tps; |
| |
| PRUint32 micro = static_cast<PRUint32>(tock); |
| return micro; |
| } |