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

588 lines
20 KiB
C++

#include "il2cpp-config.h"
#include "gc/GarbageCollector.h"
#include <utils/dynamic_array.h>
#include "vm/Array.h"
#include "vm/Class.h"
#include "vm/ClassInlines.h"
#include "vm/Field.h"
#include "vm/Liveness.h"
#include "vm/Type.h"
#include "il2cpp-tabledefs.h"
#include "il2cpp-class-internals.h"
#include "il2cpp-object-internals.h"
#define MARK_OBJ(obj) \
do { \
(obj)->klass = (Il2CppClass*)(((size_t)(obj)->klass) | (size_t)1); \
} while (0)
#define CLEAR_OBJ(obj) \
do { \
(obj)->klass = (Il2CppClass*)(((size_t)(obj)->klass) & ~(size_t)1); \
} while (0)
#define IS_MARKED(obj) \
(((size_t)(obj)->klass) & (size_t)1)
#define GET_CLASS(obj) \
((Il2CppClass*)(((size_t)(obj)->klass) & ~(size_t)1))
namespace il2cpp
{
namespace vm
{
/* number of sub elements of an array to process before recursing
* we take a depth first approach to use stack space rather than re-allocating
* processing array which requires restarting world to ensure allocator lock is not held
*/
const int kArrayElementsPerChunk = 256;
/* how far we recurse processing array elements before we stop. Prevents stack overflow */
const int kMaxTraverseRecursionDepth = 128;
struct CustomGrowableBlockArray;
struct LivenessState
{
LivenessState(Il2CppClass* filter, uint32_t maxCount, Liveness::register_object_callback callback, void*callback_userdata, Liveness::ReallocateArrayCallback reallocateArray);
~LivenessState();
void Finalize();
void Reset();
void TraverseObjects();
void FilterObjects();
static void TraverseGenericObject(Il2CppObject* object, LivenessState* state);
static void TraverseObject(Il2CppObject* object, LivenessState* state);
static void TraverseGCDescriptor(Il2CppObject* object, LivenessState* state);
static bool TraverseObjectInternal(Il2CppObject* object, bool isStruct, Il2CppClass* klass, LivenessState* state);
static void TraverseArray(Il2CppArray* array, LivenessState* state);
static bool AddProcessObject(Il2CppObject* object, LivenessState* state);
static bool ShouldProcessValue(Il2CppObject* val, Il2CppClass* filter);
static bool FieldCanContainReferences(FieldInfo* field);
static bool ShouldTraverseObjects(size_t index, int32_t recursion_depth)
{
// Add kArrayElementsPerChunk objects at a time and then traverse
return ((index + 1) & (kArrayElementsPerChunk - 1)) == 0 && recursion_depth < kMaxTraverseRecursionDepth;
}
CustomGrowableBlockArray* all_objects;
Il2CppClass* filter;
CustomGrowableBlockArray* process_array;
void* callback_userdata;
Liveness::register_object_callback filter_callback;
Liveness::ReallocateArrayCallback reallocateArray;
int32_t traverse_depth; // track recursion. Prevent stack overflow by limiting recurion
};
#define kBlockSize (8 * 1024)
#define kArrayElementsPerBlock ((kBlockSize - 3 *sizeof (void*)) / sizeof (void*))
struct CustomArrayBlock
{
Il2CppObject** next_item;
CustomArrayBlock *prev_block;
CustomArrayBlock *next_block;
Il2CppObject* p_data[kArrayElementsPerBlock];
};
struct CustomBlockArrayIterator;
struct CustomGrowableBlockArray
{
CustomArrayBlock *first_block;
CustomArrayBlock *current_block;
CustomBlockArrayIterator *iterator;
CustomGrowableBlockArray(LivenessState *state);
bool IsEmpty();
void PushBack(Il2CppObject* value, LivenessState *state);
Il2CppObject* PopBack();
void ResetIterator();
Il2CppObject* Next();
void Clear();
void Destroy(LivenessState *state);
};
struct CustomBlockArrayIterator
{
CustomGrowableBlockArray *array;
CustomArrayBlock *current_block;
Il2CppObject** current_position;
};
CustomGrowableBlockArray::CustomGrowableBlockArray(LivenessState *state)
{
current_block = (CustomArrayBlock*)state->reallocateArray(NULL, kBlockSize, state->callback_userdata);
current_block->prev_block = NULL;
current_block->next_block = NULL;
current_block->next_item = current_block->p_data;
first_block = current_block;
iterator = new CustomBlockArrayIterator();
iterator->array = this;
iterator->current_block = first_block;
iterator->current_position = first_block->p_data;
}
bool CustomGrowableBlockArray::IsEmpty()
{
return first_block->next_item == first_block->p_data;
}
void CustomGrowableBlockArray::PushBack(Il2CppObject* value, LivenessState *state)
{
if (current_block->next_item == current_block->p_data + kArrayElementsPerBlock)
{
CustomArrayBlock* new_block = current_block->next_block;
if (current_block->next_block == NULL)
{
new_block = (CustomArrayBlock*)state->reallocateArray(NULL, kBlockSize, state->callback_userdata);
new_block->next_block = NULL;
new_block->prev_block = current_block;
new_block->next_item = new_block->p_data;
current_block->next_block = new_block;
}
current_block = new_block;
}
*current_block->next_item++ = value;
}
Il2CppObject* CustomGrowableBlockArray::PopBack()
{
if (current_block->next_item == current_block->p_data)
{
if (current_block->prev_block == NULL)
return NULL;
current_block = current_block->prev_block;
current_block->next_item = current_block->p_data + kArrayElementsPerBlock;
}
return *--current_block->next_item;
}
void CustomGrowableBlockArray::ResetIterator()
{
iterator->current_block = first_block;
iterator->current_position = first_block->p_data;
}
Il2CppObject* CustomGrowableBlockArray::Next()
{
if (iterator->current_position != iterator->current_block->next_item)
return *iterator->current_position++;
if (iterator->current_block->next_block == NULL)
return NULL;
iterator->current_block = iterator->current_block->next_block;
iterator->current_position = iterator->current_block->p_data;
if (iterator->current_position == iterator->current_block->next_item)
return NULL;
return *iterator->current_position++;
}
void CustomGrowableBlockArray::Clear()
{
CustomArrayBlock *block = first_block;
while (block != NULL)
{
block->next_item = block->p_data;
block = block->next_block;
}
}
void CustomGrowableBlockArray::Destroy(LivenessState *state)
{
CustomArrayBlock *block = first_block;
while (block != NULL)
{
CustomArrayBlock *data_block = block;
block = block->next_block;
state->reallocateArray(data_block, 0, state->callback_userdata);
}
delete iterator;
delete this;
}
LivenessState::LivenessState(Il2CppClass* filter, uint32_t maxCount, Liveness::register_object_callback callback, void*callback_userdata, Liveness::ReallocateArrayCallback reallocateArray) :
all_objects(NULL),
filter(NULL),
process_array(NULL),
callback_userdata(NULL),
filter_callback(NULL),
reallocateArray(reallocateArray),
traverse_depth(0)
{
// construct liveness_state;
// allocate memory for the following structs
// all_objects: contains a list of all referenced objects to be able to clean the vtable bits after the traversal
// process_array. array that contains the objcets that should be processed. this should run depth first to reduce memory usage
// if all_objects run out of space, run through list, add objects that match the filter, clear bit in vtable and then clear the array.
this->filter = filter;
this->callback_userdata = callback_userdata;
this->filter_callback = callback;
all_objects = new CustomGrowableBlockArray(this);
process_array = new CustomGrowableBlockArray(this);
}
LivenessState::~LivenessState()
{
all_objects->Destroy(this);
process_array->Destroy(this);
}
void LivenessState::Finalize()
{
all_objects->ResetIterator();
Il2CppObject* object = all_objects->Next();
while (object != NULL)
{
CLEAR_OBJ(object);
object = all_objects->Next();
}
}
void LivenessState::Reset()
{
process_array->Clear();
}
void LivenessState::TraverseObjects()
{
Il2CppObject* object = NULL;
traverse_depth++;
while (!process_array->IsEmpty())
{
object = process_array->PopBack();
TraverseGenericObject(object, this);
}
traverse_depth--;
}
void LivenessState::FilterObjects()
{
Il2CppObject* filtered_objects[64];
int32_t num_objects = 0;
Il2CppObject* value = all_objects->Next();
while (value)
{
Il2CppObject* object = value;
if (ShouldProcessValue(object, filter))
filtered_objects[num_objects++] = object;
if (num_objects == 64)
{
filter_callback(filtered_objects, 64, callback_userdata);
num_objects = 0;
}
value = all_objects->Next();
}
if (num_objects != 0)
filter_callback(filtered_objects, num_objects, callback_userdata);
}
void LivenessState::TraverseGenericObject(Il2CppObject* object, LivenessState* state)
{
IL2CPP_NOT_IMPLEMENTED_NO_ASSERT(LivenessState::TraverseGenericObject, "Use GC bitmap when we have one");
#if IL2CPP_HAS_GC_DESCRIPTORS
size_t gc_desc = (size_t)(GET_CLASS(object)->gc_desc);
if (gc_desc & (size_t)1)
TraverseGCDescriptor(object, state);
else
#endif
if (GET_CLASS(object)->rank)
TraverseArray((Il2CppArray*)object, state);
else
TraverseObject(object, state);
}
void LivenessState::TraverseObject(Il2CppObject* object, LivenessState* state)
{
TraverseObjectInternal(object, false, GET_CLASS(object), state);
}
void LivenessState::TraverseGCDescriptor(Il2CppObject* object, LivenessState* state)
{
#define WORDSIZE ((int)sizeof(size_t)*8)
int i = 0;
size_t mask = (size_t)(GET_CLASS(object)->gc_desc);
IL2CPP_ASSERT(mask & (size_t)1);
for (i = 0; i < WORDSIZE - 2; i++)
{
size_t offset = ((size_t)1 << (WORDSIZE - 1 - i));
if (mask & offset)
{
Il2CppObject* val = *(Il2CppObject**)(((char*)object) + i * sizeof(void*));
AddProcessObject(val, state);
}
}
}
bool LivenessState::TraverseObjectInternal(Il2CppObject* object, bool isStruct, Il2CppClass* klass, LivenessState* state)
{
FieldInfo *field;
Il2CppClass *p;
bool added_objects = false;
IL2CPP_ASSERT(object);
if (!klass->size_inited)
{
IL2CPP_ASSERT(isStruct);
return false;
}
// subtract the added offset for the vtable. This is added to the offset even though it is a struct
if (isStruct)
object--;
for (p = klass; p != NULL; p = p->parent)
{
void* iter = NULL;
while ((field = Class::GetFields(p, &iter)))
{
if (field->type->attrs & FIELD_ATTRIBUTE_STATIC)
continue;
if (!FieldCanContainReferences(field))
continue;
if (Type::IsStruct(field->type))
{
char* offseted = (char*)object;
offseted += field->offset;
if (Type::IsGenericInstance(field->type))
{
IL2CPP_ASSERT(field->type->data.generic_class->cached_class);
added_objects |= TraverseObjectInternal((Il2CppObject*)offseted, true, field->type->data.generic_class->cached_class, state);
}
else
added_objects |= TraverseObjectInternal((Il2CppObject*)offseted, true, Type::GetClass(field->type), state);
continue;
}
if (field->offset == THREAD_STATIC_FIELD_OFFSET)
{
IL2CPP_ASSERT(0);
}
else
{
Il2CppObject* val = NULL;
Field::GetValue(object, field, &val);
added_objects |= AddProcessObject(val, state);
}
}
}
return added_objects;
}
void LivenessState::TraverseArray(Il2CppArray* array, LivenessState* state)
{
size_t i = 0;
bool has_references;
Il2CppObject* object = (Il2CppObject*)array;
Il2CppClass* element_class;
size_t elementClassSize;
size_t array_length;
IL2CPP_ASSERT(object);
element_class = GET_CLASS(object)->element_class;
has_references = !Class::IsValuetype(element_class);
IL2CPP_ASSERT(element_class->size_inited != 0);
FieldInfo* field;
void* iter = NULL;
while ((field = Class::GetFields(element_class, &iter)))
{
has_references |= FieldCanContainReferences(field);
if (has_references)
break;
}
if (!has_references)
return;
array_length = Array::GetLength(array);
if (element_class->byval_arg.valuetype)
{
size_t items_processed = 0;
elementClassSize = Class::GetArrayElementSize(element_class);
for (i = 0; i < array_length; i++)
{
Il2CppObject* object = (Il2CppObject*)il2cpp_array_addr_with_size(array, (int32_t)elementClassSize, i);
if (TraverseObjectInternal(object, 1, element_class, state))
items_processed++;
// Add 64 objects at a time and then traverse
if (ShouldTraverseObjects(items_processed, state->traverse_depth))
state->TraverseObjects();
}
}
else
{
size_t items_processed = 0;
for (i = 0; i < array_length; i++)
{
Il2CppObject* val = il2cpp_array_get(array, Il2CppObject*, i);
if (AddProcessObject(val, state))
items_processed++;
// Add 64 objects at a time and then traverse
if (ShouldTraverseObjects(items_processed, state->traverse_depth))
state->TraverseObjects();
}
}
}
bool LivenessState::AddProcessObject(Il2CppObject* object, LivenessState* state)
{
if (!object || IS_MARKED(object))
return false;
bool has_references = GET_CLASS(object)->has_references;
if (has_references || ShouldProcessValue(object, state->filter))
{
state->all_objects->PushBack(object, state);
MARK_OBJ(object);
}
// Check if klass has further references - if not skip adding
if (has_references)
{
state->process_array->PushBack(object, state);
return true;
}
return false;
}
bool LivenessState::ShouldProcessValue(Il2CppObject* val, Il2CppClass* filter)
{
Il2CppClass* val_class = GET_CLASS(val);
if (filter && !ClassInlines::HasParentUnsafe(val_class, filter))
return false;
return true;
}
bool LivenessState::FieldCanContainReferences(FieldInfo* field)
{
if (Type::IsStruct(field->type))
return true;
if (field->type->attrs & FIELD_ATTRIBUTE_LITERAL)
return false;
if (field->type->type == IL2CPP_TYPE_STRING)
return false;
return Type::IsReference(field->type);
}
void* Liveness::AllocateStruct(Il2CppClass* filter, int max_object_count, register_object_callback callback, void* userdata, ReallocateArrayCallback reallocateArray)
{
// ensure filter is initialized so we can do fast (and lock free) check HasParentUnsafe
Class::SetupTypeHierarchy(filter);
LivenessState* state = new LivenessState(filter, max_object_count, callback, userdata, reallocateArray);
// no allocations can happen beyond this point
return state;
}
void Liveness::FreeStruct(void* state)
{
LivenessState* lstate = (LivenessState*)state;
delete lstate;
}
void Liveness::Finalize(void* state)
{
LivenessState* lstate = (LivenessState*)state;
lstate->Finalize();
}
void Liveness::FromRoot(Il2CppObject* root, void* state)
{
LivenessState* liveness_state = (LivenessState*)state;
liveness_state->Reset();
liveness_state->process_array->PushBack(root, liveness_state);
liveness_state->TraverseObjects();
//Filter objects and call callback to register found objects
liveness_state->FilterObjects();
}
void Liveness::FromStatics(void* state)
{
LivenessState* liveness_state = (LivenessState*)state;
const il2cpp::utils::dynamic_array<Il2CppClass*>& classesWithStatics = Class::GetStaticFieldData();
liveness_state->Reset();
for (il2cpp::utils::dynamic_array<Il2CppClass*>::const_iterator iter = classesWithStatics.begin();
iter != classesWithStatics.end();
iter++)
{
Il2CppClass* klass = *iter;
FieldInfo *field;
if (!klass)
continue;
if (klass->image == il2cpp_defaults.corlib)
continue;
if (klass->size_inited == 0)
continue;
void* fieldIter = NULL;
while ((field = Class::GetFields(klass, &fieldIter)))
{
if (!vm::Field::IsNormalStatic(field))
continue;
if (!LivenessState::FieldCanContainReferences(field))
continue;
if (Type::IsStruct(field->type))
{
char* offseted = (char*)klass->static_fields;
offseted += field->offset;
if (Type::IsGenericInstance(field->type))
{
IL2CPP_ASSERT(field->type->data.generic_class->cached_class);
LivenessState::TraverseObjectInternal((Il2CppObject*)offseted, true, field->type->data.generic_class->cached_class, liveness_state);
}
else
{
LivenessState::TraverseObjectInternal((Il2CppObject*)offseted, true, Type::GetClass(field->type), liveness_state);
}
}
else
{
Il2CppObject* val = NULL;
Field::StaticGetValue(field, &val);
if (val)
{
LivenessState::AddProcessObject(val, liveness_state);
}
}
}
}
liveness_state->TraverseObjects();
//Filter objects and call callback to register found objects
liveness_state->FilterObjects();
}
} /* namespace vm */
} /* namespace il2cpp */