/** * ReXGlue runtime - AC6 Recompilation project * Copyright (c) 2026 Tom Clay. All rights reserved. */ #include #include #include #include #include #include namespace rex::system::util { ObjectTable::ObjectTable() {} ObjectTable::~ObjectTable() { Reset(); } void ObjectTable::Reset() { auto global_lock = global_critical_region_.Acquire(); // Release all objects. for (uint32_t n = 0; n < table_capacity_; n++) { ObjectTableEntry& entry = table_[n]; if (entry.object) { entry.object->Release(); } } table_capacity_ = 0; last_free_entry_ = 0; free(table_); table_ = nullptr; } X_STATUS ObjectTable::FindFreeSlot(uint32_t* out_slot) { // Find a free slot. uint32_t slot = last_free_entry_; uint32_t scan_count = 0; while (scan_count < table_capacity_) { ObjectTableEntry& entry = table_[slot]; if (!entry.object) { *out_slot = slot; return X_STATUS_SUCCESS; } scan_count++; slot = (slot + 1) % table_capacity_; if (slot == 0) { // Never allow 0 handles. scan_count++; slot++; } } // Table out of slots, expand. uint32_t new_table_capacity = std::max(16 * 1024u, table_capacity_ * 2); if (!Resize(new_table_capacity)) { return X_STATUS_NO_MEMORY; } // Never allow 0 handles. slot = ++last_free_entry_; *out_slot = slot; return X_STATUS_SUCCESS; } bool ObjectTable::Resize(uint32_t new_capacity) { uint32_t new_size = new_capacity * sizeof(ObjectTableEntry); uint32_t old_size = table_capacity_ * sizeof(ObjectTableEntry); auto new_table = reinterpret_cast(realloc(table_, new_size)); if (!new_table) { return false; } // Zero out new entries. if (new_size > old_size) { std::memset(reinterpret_cast(new_table) + old_size, 0, new_size - old_size); } last_free_entry_ = table_capacity_; table_capacity_ = new_capacity; table_ = new_table; return true; } X_STATUS ObjectTable::AddHandle(XObject* object, X_HANDLE* out_handle) { X_STATUS result = X_STATUS_SUCCESS; uint32_t handle = 0; { auto global_lock = global_critical_region_.Acquire(); // Find a free slot. uint32_t slot = 0; result = FindFreeSlot(&slot); // Stash. if (XSUCCEEDED(result)) { ObjectTableEntry& entry = table_[slot]; entry.object = object; entry.handle_ref_count = 1; handle = XObject::kHandleBase + (slot << 2); object->handles().push_back(handle); // Retain so long as the object is in the table. object->Retain(); REXSYS_DEBUG("Added handle:{:08X} for {}", handle, typeid(*object).name()); } } if (XSUCCEEDED(result)) { if (out_handle) { *out_handle = handle; } } return result; } X_STATUS ObjectTable::DuplicateHandle(X_HANDLE handle, X_HANDLE* out_handle) { X_STATUS result = X_STATUS_SUCCESS; handle = TranslateHandle(handle); XObject* object = LookupObject(handle, false); if (object) { result = AddHandle(object, out_handle); object->Release(); // Release the ref that LookupObject took } else { result = X_STATUS_INVALID_HANDLE; } return result; } X_STATUS ObjectTable::RetainHandle(X_HANDLE handle) { auto global_lock = global_critical_region_.Acquire(); ObjectTableEntry* entry = LookupTable(handle); if (!entry) { return X_STATUS_INVALID_HANDLE; } entry->handle_ref_count++; return X_STATUS_SUCCESS; } X_STATUS ObjectTable::ReleaseHandle(X_HANDLE handle) { auto global_lock = global_critical_region_.Acquire(); ObjectTableEntry* entry = LookupTable(handle); if (!entry) { return X_STATUS_INVALID_HANDLE; } if (--entry->handle_ref_count == 0) { // No more references. Remove it from the table. return RemoveHandle(handle); } // FIXME: Return a status code telling the caller it wasn't released // (but not a failure code) return X_STATUS_SUCCESS; } X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) { X_STATUS result = X_STATUS_SUCCESS; handle = TranslateHandle(handle); if (!handle) { return X_STATUS_INVALID_HANDLE; } ObjectTableEntry* entry = LookupTable(handle); if (!entry) { return X_STATUS_INVALID_HANDLE; } auto global_lock = global_critical_region_.Acquire(); if (entry->object) { auto object = entry->object; entry->object = nullptr; assert_zero(entry->handle_ref_count); entry->handle_ref_count = 0; // Walk the object's handles and remove this one. auto handle_entry = std::find(object->handles().begin(), object->handles().end(), handle); if (handle_entry != object->handles().end()) { object->handles().erase(handle_entry); } REXSYS_DEBUG("Removed handle:{:08X} for {}", handle, typeid(*object).name()); // Remove object name from mapping to prevent naming collision. if (!object->name().empty()) { RemoveNameMapping(object->name()); } // Release now that the object has been removed from the table. object->Release(); } return X_STATUS_SUCCESS; } std::vector> ObjectTable::GetAllObjects() { auto lock = global_critical_region_.Acquire(); std::vector> results; for (uint32_t slot = 0; slot < table_capacity_; slot++) { auto& entry = table_[slot]; if (entry.object && std::find(results.begin(), results.end(), entry.object) == results.end()) { entry.object->Retain(); results.push_back(object_ref(entry.object)); } } return results; } void ObjectTable::PurgeAllObjects() { auto lock = global_critical_region_.Acquire(); for (uint32_t slot = 0; slot < table_capacity_; slot++) { auto& entry = table_[slot]; if (entry.object && !entry.object->is_host_object()) { entry.handle_ref_count = 0; entry.object->Release(); entry.object = nullptr; } } } ObjectTable::ObjectTableEntry* ObjectTable::LookupTable(X_HANDLE handle) { handle = TranslateHandle(handle); if (!handle) { return nullptr; } auto global_lock = global_critical_region_.Acquire(); // Lower 2 bits are ignored. uint32_t slot = GetHandleSlot(handle); if (slot <= table_capacity_) { return &table_[slot]; } return nullptr; } // Generic lookup template <> object_ref ObjectTable::LookupObject(X_HANDLE handle) { auto object = ObjectTable::LookupObject(handle, false); auto result = object_ref(reinterpret_cast(object)); return result; } XObject* ObjectTable::LookupObject(X_HANDLE handle, bool already_locked) { handle = TranslateHandle(handle); if (!handle) { return nullptr; } XObject* object = nullptr; if (!already_locked) { global_critical_region_.mutex().lock(); } // Lower 2 bits are ignored. uint32_t slot = GetHandleSlot(handle); // Verify slot. if (slot < table_capacity_) { ObjectTableEntry& entry = table_[slot]; if (entry.object) { object = entry.object; } } // Retain the object pointer. if (object) { object->Retain(); } if (!already_locked) { global_critical_region_.mutex().unlock(); } return object; } void ObjectTable::GetObjectsByType(XObject::Type type, std::vector>* results) { auto global_lock = global_critical_region_.Acquire(); for (uint32_t slot = 0; slot < table_capacity_; ++slot) { auto& entry = table_[slot]; if (entry.object) { if (entry.object->type() == type) { entry.object->Retain(); results->push_back(object_ref(entry.object)); } } } } X_HANDLE ObjectTable::TranslateHandle(X_HANDLE handle) { if (handle == 0xFFFFFFFF) { // CurrentProcess // assert_always(); return 0; } else if (handle == 0xFFFFFFFE) { // CurrentThread return XThread::GetCurrentThreadHandle(); } else { return handle; } } X_STATUS ObjectTable::AddNameMapping(const std::string_view name, X_HANDLE handle) { std::lock_guard name_lock(name_mutex_); if (name_table_.count(string::string_key_case(name))) { return X_STATUS_OBJECT_NAME_COLLISION; } name_table_.insert({string::string_key_case::create(name), handle}); return X_STATUS_SUCCESS; } void ObjectTable::RemoveNameMapping(const std::string_view name) { // Names are case-insensitive. std::lock_guard name_lock(name_mutex_); auto it = name_table_.find(string::string_key_case(name)); if (it != name_table_.end()) { name_table_.erase(it); } } X_STATUS ObjectTable::GetObjectByName(const std::string_view name, X_HANDLE* out_handle) { // Names are case-insensitive. // Look up handle under name lock only -- do NOT hold name_mutex_ while // acquiring global lock (RemoveHandle takes global -> name ordering). X_HANDLE handle; { std::lock_guard name_lock(name_mutex_); auto it = name_table_.find(string::string_key_case(name)); if (it == name_table_.end()) { *out_handle = X_INVALID_HANDLE_VALUE; return X_STATUS_OBJECT_NAME_NOT_FOUND; } handle = it->second; } *out_handle = handle; // Retain under global lock via normal LookupObject path. // The handle may have been removed between releasing name_mutex_ and // acquiring global lock -- LookupObject returns nullptr in that case. auto obj = LookupObject(handle, false); if (obj) { obj->RetainHandle(); obj->Release(); } return X_STATUS_SUCCESS; } } // namespace rex::system::util