2025-07-03 12:53:49 +04:00

403 lines
12 KiB
C++

#include "il2cpp-config.h"
#include "gc/GCHandle.h"
#include "il2cpp-object-internals.h"
#include "GarbageCollector.h"
#include "os/Mutex.h"
#include "utils/Memory.h"
#include <memory>
namespace il2cpp
{
namespace gc
{
#if IL2CPP_SIZEOF_VOID_P == 4
#define HANDLE_COUNT 992
#define HANDLE_DATA_ALIGNMENT 4096
#else
#define HANDLE_COUNT 992
#define HANDLE_DATA_ALIGNMENT 8192
#endif
typedef struct HandleData HandleData;
struct HandleData
{
HandleData *next; //immutable
HandleData *next_free; // next free
uint32_t *bitmap;
uint32_t in_use;
uint32_t size;
uint8_t type;
uint32_t slot_hint : 24; /* starting slot for search in bitmap */
void* entries[HANDLE_COUNT];
};
static HandleData* gc_handles[HANDLE_PINNED + 1];
static HandleData* gc_handles_free[HANDLE_PINNED + 1];
inline bool HandleTypeIsWeak(GCHandleType type)
{
return type == GCHandleType::HANDLE_WEAK || type == GCHandleType::HANDLE_WEAK_TRACK;
}
#define BITMAP_SIZE (sizeof (*((HandleData *)NULL)->bitmap) * CHAR_BIT)
static bool
slot_occupied(HandleData* handles, uint32_t slot)
{
return handles->bitmap[slot / BITMAP_SIZE] & (1 << (slot % BITMAP_SIZE));
}
static void
vacate_slot(HandleData* handles, uint32_t slot)
{
handles->in_use--;
handles->bitmap[slot / BITMAP_SIZE] &= ~(1 << (slot % BITMAP_SIZE));
if (handles->in_use == (handles->size - 1))
{
uint8_t type = handles->type;
HandleData* first = gc_handles_free[type];
handles->next_free = first;
gc_handles_free[type] = handles;
}
}
static void
occupy_slot(HandleData* handles, uint32_t slot)
{
handles->in_use++;
handles->bitmap[slot / BITMAP_SIZE] |= 1 << (slot % BITMAP_SIZE);
if (handles->in_use == handles->size)
{
uint8_t type = handles->type;
IL2CPP_ASSERT(handles == gc_handles_free[type]);
gc_handles_free[type] = gc_handles_free[type]->next_free;
}
}
static int
find_first_unset(uint32_t bitmap)
{
int i;
for (i = 0; i < 32; ++i)
{
if (!(bitmap & (1 << i)))
return i;
}
return -1;
}
static HandleData*
handle_data_alloc_entries(int type)
{
IL2CPP_ASSERT(sizeof(HandleData) < HANDLE_DATA_ALIGNMENT);
IL2CPP_ASSERT(HANDLE_COUNT % BITMAP_SIZE == 0);
HandleData* handles = (HandleData*)utils::Memory::AlignedMalloc(sizeof(HandleData), HANDLE_DATA_ALIGNMENT);
memset(handles, 0, sizeof(HandleData));
handles->type = type;
handles->size = HANDLE_COUNT;
if (!HandleTypeIsWeak((GCHandleType)handles->type))
{
GarbageCollector::RegisterRoot((char*)&handles->entries[0], HANDLE_COUNT * sizeof(void*));
}
handles->bitmap = (uint32_t*)utils::Memory::Calloc(sizeof(char), handles->size / CHAR_BIT);
return handles;
}
static int32_t
handle_data_next_unset(HandleData* handles)
{
uint32_t slot;
for (slot = handles->slot_hint; slot < handles->size / BITMAP_SIZE; ++slot)
{
if (handles->bitmap[slot] == 0xffffffff)
continue;
handles->slot_hint = slot;
return find_first_unset(handles->bitmap[slot]);
}
return -1;
}
static int32_t
handle_data_first_unset(HandleData* handles)
{
uint32_t slot;
for (slot = 0; slot < handles->slot_hint; ++slot)
{
if (handles->bitmap[slot] == 0xffffffff)
continue;
handles->slot_hint = slot;
return find_first_unset(handles->bitmap[slot]);
}
return -1;
}
static int32_t
handle_data_find_slot(HandleData* handles)
{
int32_t slot = 0;
int32_t i = handle_data_next_unset(handles);
if (i == -1 && handles->slot_hint != 0)
i = handle_data_first_unset(handles);
IL2CPP_ASSERT(i != -1);
slot = handles->slot_hint * BITMAP_SIZE + i;
return slot;
}
static Il2CppGCHandle
handle_tag_weak(Il2CppGCHandle handle)
{
return (Il2CppGCHandle)((uintptr_t)handle | (uintptr_t)1);
}
static Il2CppGCHandle
handle_untag_weak(Il2CppGCHandle handle)
{
return (Il2CppGCHandle)((uintptr_t)handle & ~(uintptr_t)1);
}
static uintptr_t AlignDownTo(uintptr_t size, uintptr_t align)
{
return size & ~(align - 1);
}
static HandleData*
get_handle_data_from_handle(Il2CppGCHandle handle)
{
HandleData* handles = (HandleData*)AlignDownTo((uintptr_t)handle, HANDLE_DATA_ALIGNMENT);
return handles;
}
static HandleData*
handle_lookup(Il2CppGCHandle handle, uint32_t* slot)
{
HandleData* handles = get_handle_data_from_handle(handle);
if (slot)
*slot = (uint32_t)(ptrdiff_t)((void**)handle_untag_weak(handle) - &handles->entries[0]);
return handles;
}
static baselib::ReentrantLock g_HandlesMutex;
#define lock_handles(handles) g_HandlesMutex.Acquire ()
#define unlock_handles(handles) g_HandlesMutex.Release ()
static Il2CppGCHandle
alloc_handle(GCHandleType type, Il2CppObject *obj, bool track)
{
int32_t slot = 0;
Il2CppGCHandle res = 0;
HandleData* handles = gc_handles[type];
lock_handles(handles);
handles = gc_handles_free[type];
if (!handles)
{
handles = handle_data_alloc_entries(type);
handles->next = gc_handles[type];
gc_handles[type] = handles;
handles->next_free = gc_handles_free[type];
gc_handles_free[type] = handles;
}
slot = handle_data_find_slot(handles);
occupy_slot(handles, slot);
handles->entries[slot] = NULL;
if (handles->type <= HANDLE_WEAK_TRACK)
{
if (obj)
GarbageCollector::AddWeakLink(&(handles->entries[slot]), obj, track);
}
else
{
handles->entries[slot] = obj;
GarbageCollector::SetWriteBarrier(handles->entries + slot);
}
//mono_perfcounters->gc_num_handles++;
unlock_handles(handles);
res = (Il2CppGCHandle) & handles->entries[slot];
if (HandleTypeIsWeak((GCHandleType)handles->type))
{
/*
* Use lowest bit as an optimization to indicate weak GC handle.
* This allows client code to simply dereference strong GCHandle
* when the bit is not set.
*/
res = handle_tag_weak(res);
}
return res;
}
Il2CppGCHandle GCHandle::New(Il2CppObject *obj, bool pinned)
{
return alloc_handle(pinned ? HANDLE_PINNED : HANDLE_NORMAL, obj, false);
}
utils::Expected<Il2CppGCHandle> GCHandle::NewWeakref(Il2CppObject *obj, bool track_resurrection)
{
Il2CppGCHandle handle = alloc_handle(track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK, obj, track_resurrection);
#ifndef HAVE_SGEN_GC
if (track_resurrection)
return utils::Il2CppError(utils::NotSupported, "IL2CPP does not support resurrection for weak references. Pass the trackResurrection with a value of false.");
#endif
return (Il2CppGCHandle)handle;
}
GCHandleType GCHandle::GetHandleType(Il2CppGCHandle gchandle)
{
HandleData* handles = handle_lookup(gchandle, NULL);
return (GCHandleType)handles->type;
}
Il2CppObject* GCHandle::GetTarget(Il2CppGCHandle gchandle)
{
uint32_t slot = 0;
HandleData* handles = handle_lookup(gchandle, &slot);
Il2CppObject *obj = NULL;
if (handles->type >= HANDLE_TYPE_MAX)
return NULL;
lock_handles(handles);
if (slot < handles->size && slot_occupied(handles, slot))
{
if (handles->type <= HANDLE_WEAK_TRACK)
{
obj = GarbageCollector::GetWeakLink(&handles->entries[slot]);
}
else
{
obj = (Il2CppObject*)handles->entries[slot];
}
}
else
{
/* print a warning? */
}
unlock_handles(handles);
/*g_print ("get target of entry %d of type %d: %p\n", slot, handles->type, obj);*/
return obj;
}
static void
il2cpp_gchandle_set_target(Il2CppGCHandle gchandle, Il2CppObject *obj)
{
uint32_t slot = 0;
HandleData* handles = handle_lookup(gchandle, &slot);
Il2CppObject *old_obj = NULL;
IL2CPP_ASSERT(handles->type < HANDLE_TYPE_MAX);
lock_handles(handles);
if (slot < handles->size && slot_occupied(handles, slot))
{
if (handles->type <= HANDLE_WEAK_TRACK)
{
old_obj = (Il2CppObject*)handles->entries[slot];
if (handles->entries[slot])
GarbageCollector::RemoveWeakLink(&handles->entries[slot]);
if (obj)
GarbageCollector::AddWeakLink(&handles->entries[slot], obj, handles->type == HANDLE_WEAK_TRACK);
}
else
{
handles->entries[slot] = obj;
}
}
else
{
/* print a warning? */
}
unlock_handles(handles);
}
void GCHandle::Free(Il2CppGCHandle gchandle)
{
if (!gchandle)
return;
uint32_t slot = 0;
HandleData* handles = handle_lookup(gchandle, &slot);
if (handles->type >= HANDLE_TYPE_MAX)
return;
lock_handles(handles);
if (slot < handles->size && slot_occupied(handles, slot))
{
if (HandleTypeIsWeak((GCHandleType)handles->type))
{
if (handles->entries[slot])
GarbageCollector::RemoveWeakLink(&handles->entries[slot] /*, handles->type == HANDLE_WEAK_TRACK*/);
}
else
{
handles->entries[slot] = NULL;
}
vacate_slot(handles, slot);
}
else
{
/* print a warning? */
}
unlock_handles(handles);
}
utils::Expected<Il2CppGCHandle> GCHandle::GetTargetHandle(Il2CppObject * obj, Il2CppGCHandle handle, int32_t type)
{
if (type == -1)
{
il2cpp_gchandle_set_target(handle, obj);
/* the handle doesn't change */
return handle;
}
switch (type)
{
case HANDLE_WEAK:
return NewWeakref(obj, false);
case HANDLE_WEAK_TRACK:
return NewWeakref(obj, true);
case HANDLE_NORMAL:
return New(obj, false);
case HANDLE_PINNED:
return New(obj, true);
default:
IL2CPP_ASSERT(0);
}
return 0;
}
void GCHandle::WalkStrongGCHandleTargets(WalkGCHandleTargetsCallback callback, void* context)
{
lock_handles(handles);
const GCHandleType types[] = { HANDLE_NORMAL, HANDLE_PINNED };
for (int gcHandleTypeIndex = 0; gcHandleTypeIndex < 2; gcHandleTypeIndex++)
{
const HandleData* handles = gc_handles[types[gcHandleTypeIndex]];
while (handles != NULL)
{
for (uint32_t i = 0; i < handles->size; i++)
{
if (handles->entries[i] != NULL)
callback(static_cast<Il2CppObject*>(handles->entries[i]), context);
}
handles = handles->next;
}
}
unlock_handles(handles);
}
} /* gc */
} /* il2cpp */