#include "il2cpp-config.h" #include "il2cpp-object-internals.h" #include "GarbageCollector.h" #include "os/Event.h" #include "os/Mutex.h" #include "os/Semaphore.h" #include "os/Thread.h" #include "utils/Il2CppHashMap.h" #include "utils/HashUtils.h" #include "Baselib.h" #include "Cpp/ReentrantLock.h" #include "vm/CCW.h" #include "vm/Class.h" #include "vm/Domain.h" #include "vm/Exception.h" #include "vm/RCW.h" #include "vm/Runtime.h" #include "vm/Thread.h" using namespace il2cpp::os; using namespace il2cpp::vm; namespace il2cpp { namespace gc { // So COM Callable Wrapper can be created for any kind of managed object, // whether it has finalizer or not. If it doesn't then it's an easy case: // when creating the CCW, we just register our cleanup method to be the // finalizer method. In case it does, then we need to be able to invoke // both our CCW cleanup method, and the finalizer in question. // We could chain them by registering CCW cleanup method over the finalizer // method and storing the previous finalizer in CCW cache, but it would // screw us over if for example the object is IDisposable and then it // calls `GC.SuppressFinalize` from C# - instead of the finalizer getting // unregistered, our CCW cleanup method gets unregistered, and we now have // a dangling pointer to the managed heap in the CCW cache. Not pretty. // Instead, we made GarbageCollector::RegisterFinalizer and GarbageCollector::SuppressFinalizer // to be CCW cache aware. Now, any managed object goes into 1 of these categories: // // 1. Object has no finalizer and it is not in CCW cache. It gets garbage // collected and no cleanup is needed. // 2. Object has a finalizer and it is not in CCW cache. GarbageCollector::RunFinalizer // gets registered with the GC for such object. // 3. Object has no finalizer and it is in CCW cache. CleanupCCW is // registered with the GC for such object. Once it is called, it removes // the object from the CCW cache. // 4. Object has a finalizer and it is in CCW cache. CleanupCCW is // registered with the GC for such object. Once it is called, it removes // the object from the CCW cache and then calls its finalizer. // // To know whether we have case 3 or 4, we have the "hasFinalizer" field in // the CachedCCW object. // // When GarbageCollector::RegisterFinalizer or GarbageCollector::SuppressFinalizer // is called, we have managed objects fitting in two buckets: // // 1. Those that do not exist in CCW cache. Finalizer is normally registered with // the GC. // 2. Those that are in the CCW cache. In such case, GC won't know about the call, // but instead we'll find the object in the CCW cache and RegisterFinalizer will // set "hasFinalizer" field to true, while SuppressFinalizer will set it to false // // Any methods that interact with s_CCWCache have to lock s_CCWCacheMutex. struct CachedCCW { Il2CppIManagedObjectHolder* managedObjectHolder; bool hasFinalizer; }; typedef Il2CppHashMap > CCWCache; static baselib::ReentrantLock s_CCWCacheMutex; static CCWCache s_CCWCache; #if IL2CPP_SUPPORT_THREADS struct GarbageCollectorContext { GarbageCollectorContext() : m_StopFinalizer(false), m_FinalizerThread(nullptr), m_FinalizerThreadObject(nullptr), m_FinalizerSemaphore(0, 32767), m_FinalizersThreadStartedEvent(), m_FinalizersCompletedEvent(true, false) { } bool m_StopFinalizer; il2cpp::os::Thread* m_FinalizerThread; Il2CppThread* m_FinalizerThreadObject; Semaphore m_FinalizerSemaphore; Event m_FinalizersThreadStartedEvent; Event m_FinalizersCompletedEvent; }; GarbageCollectorContext* s_GarbageCollectorContext = nullptr; static void FinalizerThread(void* arg) { s_GarbageCollectorContext->m_FinalizerThreadObject = il2cpp::vm::Thread::Attach(Domain::GetCurrent()); s_GarbageCollectorContext->m_FinalizerThread->SetName("GC Finalizer"); s_GarbageCollectorContext->m_FinalizersThreadStartedEvent.Set(); while (!s_GarbageCollectorContext->m_StopFinalizer) { s_GarbageCollectorContext->m_FinalizerSemaphore.Wait(); GarbageCollector::InvokeFinalizers(); s_GarbageCollectorContext->m_FinalizersCompletedEvent.Set(); } il2cpp::vm::Thread::Detach(s_GarbageCollectorContext->m_FinalizerThreadObject); } bool GarbageCollector::IsFinalizerThread(Il2CppThread *thread) { return s_GarbageCollectorContext->m_FinalizerThreadObject == thread; } bool GarbageCollector::IsFinalizerInternalThread(Il2CppInternalThread *thread) { return s_GarbageCollectorContext->m_FinalizerThreadObject->GetInternalThread() == thread; } #else bool GarbageCollector::IsFinalizerThread(Il2CppThread *thread) { return false; } bool GarbageCollector::IsFinalizerInternalThread(Il2CppInternalThread *thread) { return false; } #endif void GarbageCollector::InitializeFinalizer() { GarbageCollector::InvokeFinalizers(); #if IL2CPP_SUPPORT_THREADS s_GarbageCollectorContext = new GarbageCollectorContext(); s_GarbageCollectorContext->m_FinalizerThread = new il2cpp::os::Thread; s_GarbageCollectorContext->m_FinalizerThread->Run(&FinalizerThread, NULL); s_GarbageCollectorContext->m_FinalizersThreadStartedEvent.Wait(); #endif } void GarbageCollector::UninitializeFinalizers() { #if IL2CPP_SUPPORT_THREADS s_GarbageCollectorContext->m_StopFinalizer = true; NotifyFinalizers(); s_GarbageCollectorContext->m_FinalizerThread->Join(); delete s_GarbageCollectorContext->m_FinalizerThread; s_GarbageCollectorContext->m_FinalizerThread = NULL; s_GarbageCollectorContext->m_StopFinalizer = false; s_GarbageCollectorContext->m_FinalizerThreadObject = NULL; delete s_GarbageCollectorContext; s_GarbageCollectorContext = nullptr; #endif } void GarbageCollector::NotifyFinalizers() { #if IL2CPP_SUPPORT_THREADS s_GarbageCollectorContext->m_FinalizerSemaphore.Post(1, NULL); #endif } void GarbageCollector::RunFinalizer(void *obj, void *data) { IL2CPP_NOT_IMPLEMENTED_NO_ASSERT(GarbageCollector::RunFinalizer, "Compare to mono implementation special cases"); Il2CppException *exc = NULL; Il2CppObject *o; const MethodInfo* finalizer = NULL; o = (Il2CppObject*)obj; finalizer = Class::GetFinalizer(o->klass); Runtime::Invoke(finalizer, o, NULL, &exc); if (exc) Runtime::UnhandledException(exc); } void GarbageCollector::RegisterFinalizerForNewObject(Il2CppObject* obj) { // Fast path // No need to check CCW cache since it's guaranteed to not be in it for a new object RegisterFinalizerWithCallback(obj, &GarbageCollector::RunFinalizer); } void GarbageCollector::RegisterFinalizer(Il2CppObject* obj) { // Slow path // Check in CCW cache first os::FastAutoLock lock(&s_CCWCacheMutex); CCWCache::iterator it = s_CCWCache.find(obj); if (it != s_CCWCache.end()) { it->second.hasFinalizer = true; } else { RegisterFinalizerWithCallback(obj, &GarbageCollector::RunFinalizer); } } void GarbageCollector::SuppressFinalizer(Il2CppObject* obj) { // Slow path // Check in CCW cache first os::FastAutoLock lock(&s_CCWCacheMutex); CCWCache::iterator it = s_CCWCache.find(obj); if (it != s_CCWCache.end()) { it->second.hasFinalizer = false; } else { RegisterFinalizerWithCallback(obj, NULL); } } void GarbageCollector::WaitForPendingFinalizers() { if (!GarbageCollector::HasPendingFinalizers()) return; #if IL2CPP_SUPPORT_THREADS /* Avoid deadlocks */ if (vm::Thread::Current() == s_GarbageCollectorContext->m_FinalizerThreadObject) return; s_GarbageCollectorContext->m_FinalizersCompletedEvent.Reset(); NotifyFinalizers(); s_GarbageCollectorContext->m_FinalizersCompletedEvent.Wait(); #else GarbageCollector::InvokeFinalizers(); #endif } static void CleanupCCW(void* obj, void* data) { bool hasFinalizer; // We have to destroy CCW before invoking the finalizer, because we cannot know whether the finalizer will revive the object // In cases it does revive it, it's also possible for it to hit CCW cache, and in that case we'd want to create a new CCW object // rather than returning the one that we're about to destroy here { os::FastAutoLock lock(&s_CCWCacheMutex); CCWCache::iterator it = s_CCWCache.find(static_cast(obj)); IL2CPP_ASSERT(it != s_CCWCache.end()); Il2CppIManagedObjectHolder* managedObjectHolder = it->second.managedObjectHolder; hasFinalizer = it->second.hasFinalizer; s_CCWCache.erase(it); managedObjectHolder->Destroy(); } if (hasFinalizer) GarbageCollector::RunFinalizer(obj, data); } // When creating COM Callable Wrappers for classes that project to other Windows Runtime classes // for instance, System.Uri to Windows.Foundation.Uri, we cannot actually create a wrapper against managed object // as the native side actually wants an instance of a real Windows.Foundation.Uri class. So do that, our createCCW // instead of creating a COM Callable Wrapper, will create an instance of that windows runtime class. In that case, // we do not (AND CANNOT!) put it into a CCW cache because it's not a CCW. static bool ShouldInsertIntoCCWCache(Il2CppObject* obj) { if (obj->klass == il2cpp_defaults.system_uri_class && il2cpp_defaults.windows_foundation_uri_class != NULL) return false; /* TODO - Add the rest of the class projections: System.Collections.Specialized.NotifyCollectionChangedEventArgs <-> Windows.UI.Xaml.Interop.NotifyCollectionChangedEventArgs System.ComponentModel.PropertyChangedEventArgs <-> Windows.UI.Xaml.Data.PropertyChangedEventArgs */ return true; } Il2CppIUnknown* GarbageCollector::GetOrCreateCCW(Il2CppObject* obj, const Il2CppGuid& iid) { if (obj == NULL) return NULL; // check for rcw object. COM interface can be extracted from it and there's no need to create ccw if (obj->klass->is_import_or_windows_runtime) { Il2CppIUnknown* result = RCW::QueryInterfaceNoAddRef(static_cast(obj), iid); result->AddRef(); return result; } os::FastAutoLock lock(&s_CCWCacheMutex); CCWCache::iterator it = s_CCWCache.find(obj); Il2CppIUnknown* comCallableWrapper; if (it == s_CCWCache.end()) { comCallableWrapper = CCW::CreateCCW(obj); if (ShouldInsertIntoCCWCache(obj)) { #if IL2CPP_DEBUG // Assert that CCW::CreateCCW actually returns upcasted Il2CppIManagedObjectHolder Il2CppIManagedObjectHolder* managedObjectHolder = NULL; comCallableWrapper->QueryInterface(Il2CppIManagedObjectHolder::IID, reinterpret_cast(&managedObjectHolder)); IL2CPP_ASSERT(static_cast(comCallableWrapper) == static_cast(managedObjectHolder)); managedObjectHolder->Release(); #endif CachedCCW ccw = { static_cast(comCallableWrapper), RegisterFinalizerWithCallback(obj, &CleanupCCW) != NULL }; s_CCWCache.insert(std::make_pair(obj, ccw)); } } else { comCallableWrapper = it->second.managedObjectHolder; } Il2CppIUnknown* result; il2cpp_hresult_t hr = comCallableWrapper->QueryInterface(iid, reinterpret_cast(&result)); vm::Exception::RaiseIfFailed(hr, true); return result; } int32_t GarbageCollector::GetGeneration(void* addr) { return 0; } void GarbageCollector::AddMemoryPressure(int64_t value) { } #if IL2CPP_ENABLE_WRITE_BARRIERS void il2cpp::gc::GarbageCollector::SetWriteBarrier(void **ptr, size_t size) { #if IL2CPP_ENABLE_STRICT_WRITE_BARRIERS for (size_t i = 0; i < size / sizeof(void**); i++) SetWriteBarrier(ptr + i); #else SetWriteBarrier(ptr); #endif } #endif void il2cpp::gc::GarbageCollector::SetSkipThread(bool skip) { } } // namespace gc } // namespace il2cpp