#include "il2cpp-config.h" #include #include "icalls/mscorlib/System.Threading/Thread.h" #include "il2cpp-class-internals.h" #include "gc/GarbageCollector.h" #include "os/Atomic.h" #include "os/Thread.h" #include "os/Mutex.h" #include "os/Semaphore.h" #include "utils/StringUtils.h" #include "vm/Array.h" #include "vm/Domain.h" #include "vm/Object.h" #include "vm/Runtime.h" #include "vm/String.h" #include "vm/Thread.h" #include "vm/Exception.h" #include "vm/String.h" #include "vm/StackTrace.h" #include "utils/Memory.h" #include "utils/StringUtils.h" #include "vm/Atomic.h" using il2cpp::gc::GarbageCollector; namespace il2cpp { namespace icalls { namespace mscorlib { namespace System { namespace Threading { #define NUM_CACHED_CULTURES 4 #define CULTURES_START_IDX 0 #define UICULTURES_START_IDX NUM_CACHED_CULTURES static Il2CppObject* lookup_cached_culture(Il2CppThread *thisPtr, int32_t start_idx); static void cache_culture(Il2CppThread *thisPtr, Il2CppObject *culture, int start_idx); void Thread::ClrState(Il2CppThread* thisPtr, uint32_t state) { il2cpp::vm::Thread::ClrState(thisPtr, (il2cpp::vm::ThreadState)state); } Il2CppThread * Thread::CurrentThread_internal(void) { return il2cpp::vm::Thread::Current(); } int32_t Thread::GetDomainID() { return il2cpp::vm::Domain::GetCurrent()->domain_id; } uint32_t Thread::GetState(Il2CppThread * thisPtr) { il2cpp::os::FastAutoLock lock(thisPtr->GetInternalThread()->synch_cs); return thisPtr->GetInternalThread()->state; } bool Thread::Join_internal(Il2CppThread * thisPtr, int32_t ms, void* thread) { // Throw ThreadStateException if thread has not been started yet. if (il2cpp::vm::Thread::GetState(thisPtr) & vm::kThreadStateUnstarted) il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetThreadStateException("Thread has not been started.")); // Mark current thread as blocked. Il2CppThread* currentThread = il2cpp::vm::Thread::Current(); SetState(currentThread, vm::kThreadStateWaitSleepJoin); // Join with other thread. il2cpp::os::Thread* osThread = (il2cpp::os::Thread*)thisPtr->GetInternalThread()->handle; IL2CPP_ASSERT(osThread != NULL); il2cpp::os::WaitStatus status = osThread->Join(ms); // Unblock current thread. ClrState(currentThread, vm::kThreadStateWaitSleepJoin); if (status == kWaitStatusSuccess) return true; return false; } void Thread::ResetAbort_internal() { il2cpp::vm::Thread::ResetAbort(il2cpp::vm::Thread::Current()); } Il2CppString* Thread::GetName_internal(Il2CppThread* thisPtr) { il2cpp::os::FastAutoLock lock(thisPtr->GetInternalThread()->synch_cs); if (thisPtr->GetInternalThread()->name_len == 0) return NULL; return il2cpp::vm::String::NewUtf16(thisPtr->GetInternalThread()->name, thisPtr->GetInternalThread()->name_len); } void Thread::SetName_internal(Il2CppThread* thisPtr, Il2CppString* name) { vm::Thread::SetName(thisPtr, name); } void Thread::SetState(Il2CppThread* thisPtr, uint32_t state) { il2cpp::os::FastAutoLock lock(thisPtr->GetInternalThread()->synch_cs); il2cpp::vm::Thread::SetState(thisPtr, (il2cpp::vm::ThreadState)state); } void Thread::Sleep_internal(int32_t milliseconds) { Il2CppThread* thread = il2cpp::vm::Thread::Current(); SetState(thread, vm::kThreadStateWaitSleepJoin); il2cpp::os::Thread::Sleep(milliseconds, true); ClrState(thread, vm::kThreadStateWaitSleepJoin); } void Thread::Thread_init(Il2CppThread * thisPtr) { il2cpp::vm::Thread::Setup(thisPtr); } struct StartData { Il2CppThread* m_Thread; Il2CppDomain* m_Domain; Il2CppDelegate* m_Delegate; Il2CppObject* m_StartArg; il2cpp::os::Semaphore* m_Semaphore; }; static void ThreadStart(void* arg) { StartData* startData = (StartData*)arg; startData->m_Semaphore->Wait(); { int temp = 0; if (!GarbageCollector::RegisterThread(&temp)) IL2CPP_ASSERT(0 && "GarbageCollector::RegisterThread failed"); il2cpp::vm::StackTrace::InitializeStackTracesForCurrentThread(); il2cpp::vm::Thread::Initialize(startData->m_Thread, startData->m_Domain); il2cpp::vm::Thread::SetState(startData->m_Thread, vm::kThreadStateRunning); try { Il2CppException* exc = NULL; void* args[1] = { startData->m_StartArg }; vm::Runtime::DelegateInvoke(startData->m_Delegate, args, &exc); if (exc) vm::Runtime::UnhandledException(exc); } catch (il2cpp::vm::Thread::NativeThreadAbortException) { // Nothing to do. We've successfully aborted the thread. il2cpp::vm::Thread::SetState(startData->m_Thread, vm::kThreadStateAborted); } il2cpp::vm::Thread::ClrState(startData->m_Thread, vm::kThreadStateRunning); il2cpp::vm::Thread::SetState(startData->m_Thread, vm::kThreadStateStopped); il2cpp::vm::Thread::Uninitialize(startData->m_Thread); il2cpp::vm::StackTrace::CleanupStackTracesForCurrentThread(); } delete startData->m_Semaphore; GarbageCollector::FreeFixed(startData); } intptr_t Thread::Thread_internal(Il2CppThread * thisPtr, Il2CppDelegate * start) { IL2CPP_ASSERT(thisPtr->GetInternalThread()->synch_cs != NULL); il2cpp::os::FastAutoLock lock(thisPtr->GetInternalThread()->synch_cs); if (il2cpp::vm::Thread::GetState(thisPtr) & vm::kThreadStateAborted) { return reinterpret_cast(thisPtr->GetInternalThread()->handle); } // use fixed GC memory since we are storing managed object pointers StartData* startData = (StartData*)GarbageCollector::AllocateFixed(sizeof(StartData), NULL); startData->m_Thread = thisPtr; GarbageCollector::SetWriteBarrier((void**)&startData->m_Thread); startData->m_Domain = vm::Domain::GetCurrent(); startData->m_Delegate = start; GarbageCollector::SetWriteBarrier((void**)&startData->m_Delegate); startData->m_StartArg = thisPtr->start_obj; GarbageCollector::SetWriteBarrier((void**)&startData->m_StartArg); startData->m_Semaphore = new il2cpp::os::Semaphore(0); il2cpp::os::Thread* thread = new il2cpp::os::Thread(); thread->SetStackSize(thisPtr->GetInternalThread()->stack_size); thread->SetExplicitApartment(static_cast(thisPtr->GetInternalThread()->apartment_state)); il2cpp::os::ErrorCode status = thread->Run(&ThreadStart, startData); if (status != il2cpp::os::kErrorCodeSuccess) { delete thread; return 0; } uint32_t existingPriority = il2cpp::vm::Thread::GetPriority(thisPtr); thisPtr->GetInternalThread()->handle = thread; thisPtr->GetInternalThread()->state &= ~vm::kThreadStateUnstarted; thisPtr->GetInternalThread()->tid = thread->Id(); if (!thisPtr->GetInternalThread()->managed_id) thisPtr->GetInternalThread()->managed_id = il2cpp::vm::Thread::GetNewManagedId(); startData->m_Semaphore->Post(1, NULL); il2cpp::vm::Thread::SetPriority(thisPtr, existingPriority); // this is just checked against 0 in the calling code return reinterpret_cast(thisPtr->GetInternalThread()->handle); } static void cache_culture(Il2CppThread *thisPtr, Il2CppObject *culture, int start_idx) { int i; Il2CppObject *obj; int free_slot = -1; int same_domain_slot = -1; il2cpp::os::FastAutoLock lock(thisPtr->GetInternalThread()->synch_cs); if (!thisPtr->GetInternalThread()->cached_culture_info) IL2CPP_OBJECT_SETREF(thisPtr->GetInternalThread(), cached_culture_info, il2cpp::vm::Array::NewCached(il2cpp_defaults.object_class, NUM_CACHED_CULTURES * 2)); for (i = start_idx; i < start_idx + NUM_CACHED_CULTURES; ++i) { obj = il2cpp_array_get(thisPtr->GetInternalThread()->cached_culture_info, Il2CppObject*, i); /* Free entry */ if (!obj) { free_slot = i; /* we continue, because there may be a slot used with the same domain */ continue; } /* Replace */ same_domain_slot = i; break; } if (same_domain_slot >= 0) il2cpp_array_setref(thisPtr->GetInternalThread()->cached_culture_info, same_domain_slot, culture); else if (free_slot >= 0) il2cpp_array_setref(thisPtr->GetInternalThread()->cached_culture_info, free_slot, culture); /* we may want to replace an existing entry here, even when no suitable slot is found */ } static Il2CppObject* lookup_cached_culture(Il2CppThread *thisPtr, int32_t start_idx) { Il2CppObject *res; int i; if (thisPtr->GetInternalThread()->cached_culture_info) { for (i = start_idx; i < start_idx + NUM_CACHED_CULTURES; ++i) { res = il2cpp_array_get(thisPtr->GetInternalThread()->cached_culture_info, Il2CppObject*, i); if (res) return res; } } return NULL; } void Thread::FreeLocalSlotValues(int32_t slot, bool use_thread_local) { IL2CPP_NOT_IMPLEMENTED_ICALL(Thread::FreeLocalSlotValues); } mscorlib_System_Globalization_CultureInfo* Thread::GetCachedCurrentCulture(Il2CppThread* thisPtr) { return (mscorlib_System_Globalization_CultureInfo*)lookup_cached_culture(thisPtr, CULTURES_START_IDX); } void Thread::SetCachedCurrentCulture(Il2CppThread* thisPtr, Il2CppObject* culture) { cache_culture(thisPtr, culture, CULTURES_START_IDX); } mscorlib_System_Globalization_CultureInfo* Thread::GetCachedCurrentUICulture(Il2CppThread* thisPtr) { return (mscorlib_System_Globalization_CultureInfo*)lookup_cached_culture(thisPtr, UICULTURES_START_IDX); } void Thread::SetCachedCurrentUICulture(Il2CppThread* thisPtr, Il2CppObject* culture) { cache_culture(thisPtr, culture, UICULTURES_START_IDX); } static Il2CppArray* GetSerializedCulture(uint8_t*& data, uint32_t& length) { if (!data) return NULL; Il2CppArray* result = il2cpp::vm::Array::New(il2cpp_defaults.byte_class, length); memcpy(il2cpp_array_addr(result, uint8_t, 0), data, length); return result; } static void SetSerializedCulture(uint8_t*& data, uint32_t& length, Il2CppArray* culture) { if (data != NULL) delete data; int arrayLength = il2cpp::vm::Array::GetByteLength(culture); data = new uint8_t[arrayLength]; length = arrayLength; memcpy(data, il2cpp_array_addr(culture, uint8_t, 0), arrayLength); } int32_t Thread::GetNewManagedId_internal() { return il2cpp::vm::Thread::GetNewManagedId(); } void Thread::Abort_internal(Il2CppThread* thisPtr, Il2CppObject* stateInfo) { il2cpp::vm::Thread::RequestAbort(thisPtr); } Il2CppObject* Thread::GetAbortExceptionState(void* /* System.Threading.Thread */ self) { NOT_SUPPORTED_IL2CPP(Thread::GetAbortExceptionState, "Thread abortion is currently not implemented on IL2CPP; it is recommended to use safer mechanisms to terminate threads."); return 0; } void Thread::Interrupt_internal(Il2CppThread* thisPtr) { il2cpp::vm::Thread::RequestInterrupt(thisPtr); } void Thread::MemoryBarrier_() { il2cpp::os::Atomic::FullMemoryBarrier(); } void Thread::Resume_internal(void* /* System.Threading.Thread */ self) { NOT_SUPPORTED_IL2CPP(Thread::Resume_internal, "Thread suspension is obsolete and not supported on IL2CPP."); } void Thread::SpinWait_nop() { } void Thread::Suspend_internal(void* /* System.Threading.Thread */ self) { NOT_SUPPORTED_IL2CPP(Thread::Suspend_internal, "Thread suspension is obsolete and not supported on IL2CPP."); } // Unlike Mono we cannot just do a memory read/write of the correct type and reuse that for the // floating-point types because we are compiling to C++ and have to account for its type conversion // rules. For example, if we read a double as a uint64_t and return it as such, the compiler will // perform a default conversion from uint64_t to double -- whereas what we want is to simply interpret // the memory contents as a double. int8_t Thread::VolatileReadInt8(volatile void* address) { int8_t tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } int16_t Thread::VolatileReadInt16(volatile void* address) { int16_t tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } int32_t Thread::VolatileReadInt32(volatile void* address) { int32_t tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } int64_t Thread::VolatileReadInt64(volatile void* address) { int64_t tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } float Thread::VolatileReadFloat(volatile void* address) { float tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } double Thread::VolatileReadDouble(volatile void* address) { double tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } void* Thread::VolatileReadPtr(volatile void* address) { void* tmp = *reinterpret_cast(address); il2cpp::os::Atomic::FullMemoryBarrier(); return tmp; } intptr_t Thread::VolatileReadIntPtr(volatile void* address) { return reinterpret_cast(VolatileReadPtr(address)); } void Thread::VolatileWriteInt8(volatile void* address, int8_t value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteInt16(volatile void* address, int16_t value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteInt32(volatile void* address, int32_t value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteInt64(volatile void* address, int64_t value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteFloat(volatile void* address, float value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteDouble(volatile void* address, double value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWritePtr(volatile void* address, void* value) { il2cpp::os::Atomic::FullMemoryBarrier(); *reinterpret_cast(address) = value; } void Thread::VolatileWriteIntPtr(volatile void* address, intptr_t value) { VolatileWritePtr(address, reinterpret_cast(value)); } Il2CppArray* Thread::ByteArrayToCurrentDomain(Il2CppArray* arr) { // IL2CPP only has one domain, so just return the same array. return arr; } Il2CppArray* Thread::ByteArrayToRootDomain(Il2CppArray* arr) { // IL2CPP only has one domain, so just return the same array. return arr; } bool Thread::YieldInternal() { return vm::Thread::YieldInternal(); } bool Thread::JoinInternal(Il2CppThread* _this, int32_t millisecondsTimeout) { return Join_internal(_this, millisecondsTimeout, NULL); } int32_t Thread::GetPriorityNative(Il2CppThread* _this) { return il2cpp::vm::Thread::GetPriority(_this); } int32_t Thread::SystemMaxStackStize() { return il2cpp::os::Thread::GetMaxStackSize(); } Il2CppString* Thread::GetName_internal40(Il2CppInternalThread* thread) { il2cpp::os::FastAutoLock lock(thread->synch_cs); if (thread->name_len == 0) return NULL; return il2cpp::vm::String::NewUtf16(thread->name, thread->name_len); } Il2CppInternalThread* Thread::CurrentInternalThread_internal() { return CurrentThread_internal()->GetInternalThread(); } int32_t Thread::GetState40(Il2CppInternalThread* thread) { // There is a chance that the managed thread object can be used from code (like a // finalizer) after it has been destroyed. In that case, the objects that // the runtime uses to track this thread may have been freed. Try to check for // that case here and return early. if (thread == NULL || thread->synch_cs == NULL) return vm::kThreadStateStopped; il2cpp::os::FastAutoLock lock(thread->synch_cs); return (il2cpp::vm::ThreadState)thread->state; } void Thread::Abort_internal40(Il2CppInternalThread* thread, Il2CppObject* stateInfo) { il2cpp::vm::Thread::RequestAbort(thread); } void Thread::ClrState40(Il2CppInternalThread* thread, uint32_t clr) { il2cpp::vm::Thread::ClrState(thread, (il2cpp::vm::ThreadState)clr); } void Thread::ConstructInternalThread(Il2CppThread* _this) { os::Thread* osThread = new os::Thread(); // Create managed object representing the current thread. Il2CppInternalThread* internal = (Il2CppInternalThread*)vm::Object::New(il2cpp_defaults.internal_thread_class); internal->state = vm::kThreadStateUnstarted; internal->handle = osThread; internal->tid = osThread->Id(); internal->synch_cs = new il2cpp::os::FastMutex(); internal->apartment_state = il2cpp::os::kApartmentStateUnknown; internal->managed_id = GetNewManagedId_internal(); vm::Atomic::CompareExchangePointer(&_this->internal_thread, internal, NULL); il2cpp::gc::GarbageCollector::SetWriteBarrier((void**)&_this->internal_thread); } void Thread::GetStackTraces(Il2CppArray** threads, Il2CppArray** stack_frames) { IL2CPP_NOT_IMPLEMENTED_ICALL(Thread::GetStackTraces); IL2CPP_UNREACHABLE; } void Thread::InterruptInternal(Il2CppThread* _this) { Interrupt_internal(_this); } void Thread::ResetAbortNative(Il2CppObject* _this) { vm::Thread::ResetAbort(vm::Thread::CurrentInternal()); } void Thread::ResumeInternal(Il2CppObject* _this) { NOT_SUPPORTED_IL2CPP(Thread::Resume_internal, "Thread suspension is obsolete and not supported on IL2CPP."); } void Thread::SetName_internal40(Il2CppInternalThread* thread, Il2CppString* name) { il2cpp::os::FastAutoLock lock(thread->synch_cs); // Throw if already set. if (thread->name_len != 0) il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetInvalidOperationException("Thread name can only be set once.")); // Store name. thread->name_len = utils::StringUtils::GetLength(name); thread->name = il2cpp::utils::StringUtils::StringDuplicate(utils::StringUtils::GetChars(name), thread->name_len); // Hand over to OS layer, if thread has been started already. if (thread->handle) { std::string utf8Name = il2cpp::utils::StringUtils::Utf16ToUtf8(thread->name); thread->handle->SetName(utf8Name.c_str()); } } void Thread::SetPriorityNative(Il2CppThread* _this, int32_t priority) { vm::Thread::SetPriority(_this, priority); } void Thread::SetState40(Il2CppInternalThread* thread, uint32_t state) { vm::Thread::SetState(thread, (il2cpp::vm::ThreadState)state); } void Thread::SleepInternal(int32_t millisecondsTimeout) { Sleep_internal(millisecondsTimeout); } void Thread::SuspendInternal(Il2CppObject* _this) { NOT_SUPPORTED_IL2CPP(Thread::SuspendInternal, "Thread suspension is obsolete and not supported on IL2CPP."); } Il2CppThread* Thread::GetCurrentThread() { return il2cpp::vm::Thread::Current(); } } /* namespace Threading */ } /* namespace System */ } /* namespace mscorlib */ } /* namespace icalls */ } /* namespace il2cpp */