From 7181c152a21cb99f136cb78fcbd4d8072bbc940d Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:25:27 +0300 Subject: [PATCH 01/59] feat: create interfaces for gc and basic reference scanners --- lib/runtime/ArrayReferenceScanner.hpp | 26 +++++++++++++++++++ lib/runtime/DefaultReferenceScanner.hpp | 33 +++++++++++++++++++++++++ lib/runtime/EmptyReferenceScanner.hpp | 17 +++++++++++++ lib/runtime/IGarbageCollector.hpp | 21 ++++++++++++++++ lib/runtime/IReferenceScanner.hpp | 18 ++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 lib/runtime/ArrayReferenceScanner.hpp create mode 100644 lib/runtime/DefaultReferenceScanner.hpp create mode 100644 lib/runtime/EmptyReferenceScanner.hpp create mode 100644 lib/runtime/IGarbageCollector.hpp create mode 100644 lib/runtime/IReferenceScanner.hpp diff --git a/lib/runtime/ArrayReferenceScanner.hpp b/lib/runtime/ArrayReferenceScanner.hpp new file mode 100644 index 0000000..6efd086 --- /dev/null +++ b/lib/runtime/ArrayReferenceScanner.hpp @@ -0,0 +1,26 @@ +#ifndef RUNTIME_ARRAYREFERENCESCANNER_HPP +#define RUNTIME_ARRAYREFERENCESCANNER_HPP + +#include + +#include "IReferenceScanner.hpp" +#include "ObjectDescriptor.hpp" // Для GetDataPointer + +namespace ovum::vm::runtime { + +template +class ArrayReferenceScanner : public IReferenceScanner { +public: + void Scan(void* obj, const ReferenceVisitor& visitor) const override { + const auto& vec = *GetDataPointer>(obj); + for (auto p : vec) { + if (p) { + visitor(p); + } + } + } +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_ARRAYREFERENCESCANNER_HPP diff --git a/lib/runtime/DefaultReferenceScanner.hpp b/lib/runtime/DefaultReferenceScanner.hpp new file mode 100644 index 0000000..2bc4113 --- /dev/null +++ b/lib/runtime/DefaultReferenceScanner.hpp @@ -0,0 +1,33 @@ +#ifndef RUNTIME_DEFAULTREFERENCESCANNER_HPP +#define RUNTIME_DEFAULTREFERENCESCANNER_HPP + +#include "IReferenceScanner.hpp" +#include "VirtualTable.hpp" + +namespace ovum::vm::runtime { + +class DefaultReferenceScanner : public IReferenceScanner { +public: + explicit DefaultReferenceScanner(const VirtualTable* vt) : vt_(vt) {} + + void Scan(void* obj, const ReferenceVisitor& visitor) const override { + for (size_t i = 0; i < vt_->GetFieldCount(); ++i) { + if (vt_->IsFieldReferenceType(i)) { + auto var_res = vt_->GetVariableByIndex(obj, i); + if (var_res.has_value() && std::holds_alternative(var_res.value())) { + void* ptr = std::get(var_res.value()); + if (ptr) { + visitor(ptr); + } + } + } + } + } + +private: + const VirtualTable* vt_; +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_DEFAULTREFERENCESCANNER_HPP diff --git a/lib/runtime/EmptyReferenceScanner.hpp b/lib/runtime/EmptyReferenceScanner.hpp new file mode 100644 index 0000000..0eaef36 --- /dev/null +++ b/lib/runtime/EmptyReferenceScanner.hpp @@ -0,0 +1,17 @@ +#ifndef RUNTIME_EMPTYREFERENCESCANNER_HPP +#define RUNTIME_EMPTYREFERENCESCANNER_HPP + +#include "IReferenceScanner.hpp" + +namespace ovum::vm::runtime { + +class EmptyReferenceScanner : public IReferenceScanner { +public: + void Scan(void*, const ReferenceVisitor&) const override { + // No references to scan + } +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_EMPTYREFERENCESCANNER_HPP diff --git a/lib/runtime/IGarbageCollector.hpp b/lib/runtime/IGarbageCollector.hpp new file mode 100644 index 0000000..ca31f7c --- /dev/null +++ b/lib/runtime/IGarbageCollector.hpp @@ -0,0 +1,21 @@ +#ifndef RUNTIME_GARBAGECOLLECTOR_HPP +#define RUNTIME_GARBAGECOLLECTOR_HPP + +#include +#include + +namespace ovum::vm::execution_tree { struct PassedExecutionData; } // Forward declaration + +namespace ovum::vm::runtime { + +constexpr uint32_t kMarkBit = 1U; // Mark bit in badge + +class IGarbageCollector { // NOLINT(cppcoreguidelines-special-member-functions) +public: + virtual ~IGarbageCollector() = default; + virtual std::expected Collect(execution_tree::PassedExecutionData& data) = 0; +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_GARBAGECOLLECTOR_HPP diff --git a/lib/runtime/IReferenceScanner.hpp b/lib/runtime/IReferenceScanner.hpp new file mode 100644 index 0000000..dd50733 --- /dev/null +++ b/lib/runtime/IReferenceScanner.hpp @@ -0,0 +1,18 @@ +#ifndef RUNTIME_IREFERENCESCANNER_HPP +#define RUNTIME_IREFERENCESCANNER_HPP + +#include + +namespace ovum::vm::runtime { + +using ReferenceVisitor = std::function; + +class IReferenceScanner { // NOLINT(cppcoreguidelines-special-member-functions) +public: + virtual ~IReferenceScanner() = default; + virtual void Scan(void* obj, const ReferenceVisitor& visitor) const = 0; +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_IREFERENCESCANNER_HPP From 1dcdb3709bdd163109d345252b0bf7c65b3a4717 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:25:57 +0300 Subject: [PATCH 02/59] feat: add reference scanning support to VirtualTable --- lib/runtime/VirtualTable.cpp | 23 +++++++++++++++++++++++ lib/runtime/VirtualTable.hpp | 10 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/runtime/VirtualTable.cpp b/lib/runtime/VirtualTable.cpp index aa6f151..00c8e8f 100644 --- a/lib/runtime/VirtualTable.cpp +++ b/lib/runtime/VirtualTable.cpp @@ -2,6 +2,7 @@ #include +#include "DefaultReferenceScanner.hpp" #include "VariableAccessor.hpp" namespace ovum::vm::runtime { @@ -16,6 +17,7 @@ const std::unordered_map> Virtua }; VirtualTable::VirtualTable(std::string name, size_t size) : name_(std::move(name)), size_(size) { + reference_scanner_ = std::make_unique(this); } std::string VirtualTable::GetName() const { @@ -80,4 +82,25 @@ bool VirtualTable::IsType(const std::string& interface_name) const { return interfaces_.contains(interface_name) || interface_name == name_; } +void VirtualTable::SetReferenceScanner(std::unique_ptr scanner) { + reference_scanner_ = std::move(scanner); +} + +size_t VirtualTable::GetFieldCount() const { + return fields_.size(); +} + +bool VirtualTable::IsFieldReferenceType(size_t index) const { + if (index >= fields_.size()) { + return false; + } + return fields_[index].variable_accessor->IsReferenceType(); +} + +void VirtualTable::ScanReferences(void* obj, const ReferenceVisitor& visitor) const { + if (reference_scanner_) { + reference_scanner_->Scan(obj, visitor); + } +} + } // namespace ovum::vm::runtime diff --git a/lib/runtime/VirtualTable.hpp b/lib/runtime/VirtualTable.hpp index 5626a32..9c5e7e9 100644 --- a/lib/runtime/VirtualTable.hpp +++ b/lib/runtime/VirtualTable.hpp @@ -2,6 +2,7 @@ #define RUNTIME_VIRTUAL_TABLE_HPP #include +#include #include #include #include @@ -9,6 +10,7 @@ #include "FieldInfo.hpp" #include "FunctionId.hpp" +#include "IReferenceScanner.hpp" #include "IVariableAccessor.hpp" #include "Variable.hpp" @@ -29,10 +31,16 @@ class VirtualTable { const FunctionId& virtual_function_id) const; [[nodiscard]] bool IsType(const std::string& interface_name) const; + [[nodiscard]] size_t GetFieldCount() const; + [[nodiscard]] bool IsFieldReferenceType(size_t index) const; + void AddFunction(const FunctionId& virtual_function_id, const FunctionId& real_function_id); size_t AddField(const std::string& type_name, int64_t offset); void AddInterface(const std::string& interface_name); + void SetReferenceScanner(std::unique_ptr scanner); + void ScanReferences(void* obj, const ReferenceVisitor& visitor) const; + private: static const std::unordered_map> kVariableAccessorsByTypeName; @@ -41,6 +49,8 @@ class VirtualTable { std::vector fields_; std::unordered_map functions_; std::unordered_set interfaces_; + + std::unique_ptr reference_scanner_; }; } // namespace ovum::vm::runtime From ce98cb6037205d27804307e5642cb6b2ed7d28ba Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:26:44 +0300 Subject: [PATCH 03/59] feat: create MemoryManager --- lib/runtime/MemoryManager.cpp | 152 ++++++++++++++++++++++++++++++++++ lib/runtime/MemoryManager.hpp | 42 ++++++++++ 2 files changed, 194 insertions(+) create mode 100644 lib/runtime/MemoryManager.cpp create mode 100644 lib/runtime/MemoryManager.hpp diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp new file mode 100644 index 0000000..f14d10c --- /dev/null +++ b/lib/runtime/MemoryManager.cpp @@ -0,0 +1,152 @@ +#include "MemoryManager.hpp" + +#include + +#include "ObjectDescriptor.hpp" +#include "lib/execution_tree/PassedExecutionData.hpp" +#include "lib/execution_tree/FunctionRepository.hpp" +#include "MarkAndSweepGC.hpp" + +namespace ovum::vm::runtime { + +MemoryManager::MemoryManager() : max_objects_(kDefaultMaxObjects) { + gc_ = std::make_unique(); // Assume enabled by default +} + +std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, + uint32_t vtable_index, + execution_tree::PassedExecutionData& data) { + const size_t size = sizeof(ObjectDescriptor) + vtable.GetSize(); + char* raw_memory = allocator_.allocate(size); + if (!raw_memory) { + return std::unexpected(std::runtime_error("MemoryManager: Allocation failed")); + } + + auto* descriptor = reinterpret_cast(raw_memory); + descriptor->vtable_index = vtable_index; + descriptor->badge = 0; // Clear mark bit + + auto add_result = repo_.Add(descriptor); + if (!add_result.has_value()) { + allocator_.deallocate(raw_memory, size); + return std::unexpected(add_result.error()); + } + + // Авто-GC если превышен threshold + if (repo_.GetCount() > max_objects_) { + auto gc_result = CollectGarbage(data); + if (!gc_result.has_value()) { + // Log error, but continue + } + } + + return reinterpret_cast(descriptor); +} + +std::expected MemoryManager::DeallocateObject(void* obj, + execution_tree::PassedExecutionData& data) { + if (!obj) { + return std::unexpected(std::runtime_error("DeallocateObject: Null object")); + } + + // Найти индекс в repo + auto index = static_cast(-1); + for (size_t i = 0; i < repo_.GetCount(); ++i) { + auto repo_obj_res = repo_.GetByIndex(i); + if (repo_obj_res.has_value() && repo_obj_res.value() == obj) { + index = i; + break; + } + } + + if (std::cmp_equal(index ,-1)) { + return std::unexpected(std::runtime_error("DeallocateObject: Object not found in repository")); + } + + auto* desc = reinterpret_cast(obj); + + // Вызов деструктора + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (vt_res.has_value()) { + const VirtualTable* vt = vt_res.value(); + auto dtor_id_res = vt->GetRealFunctionId("_" + vt->GetName() + "_destructor_"); + if (dtor_id_res.has_value()) { + auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (func_res.has_value()) { + // Создать frame, push obj, Execute + runtime::StackFrame frame; + frame.function_name = dtor_id_res.value(); + frame.local_variables.emplace_back(obj); + data.memory.stack_frames.push(frame); + func_res.value()->Execute(data); + data.memory.stack_frames.pop(); + } + } + + // Deallocate + const size_t size = sizeof(ObjectDescriptor) + vt->GetSize(); + char* raw = reinterpret_cast(obj); + allocator_.deallocate(raw, size); + } + + // Удалить из repo + auto remove_res = repo_.Remove(index); + if (!remove_res.has_value()) { + return std::unexpected(remove_res.error()); + } + + return {}; +} + +std::expected MemoryManager::CollectGarbage(execution_tree::PassedExecutionData& data) { + if (!gc_.has_value()) { + return std::unexpected(std::runtime_error("MemoryManager: No GC configured")); + } + return gc_.value()->Collect(data); +} + +std::expected MemoryManager::Clear(execution_tree::PassedExecutionData& data) { + for (size_t i = 0; i < repo_.GetCount(); ++i) { + auto obj_res = repo_.GetByIndex(i); + if (!obj_res.has_value()) { + continue; + } + void* obj = obj_res.value(); + auto* desc = reinterpret_cast(obj); + + // Получить VT + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (!vt_res.has_value()) { + continue; + } + const VirtualTable* vt = vt_res.value(); + + // Получить ID деструктора + auto dtor_id_res = vt->GetRealFunctionId("_" + vt->GetName() + "_destructor_"); + if (dtor_id_res.has_value()) { + auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (func_res.has_value()) { + // Создать frame, push obj, Execute + runtime::StackFrame frame; + frame.function_name = dtor_id_res.value(); + frame.local_variables.emplace_back(obj); + data.memory.stack_frames.push(frame); + func_res.value()->Execute(data); + data.memory.stack_frames.pop(); + } + } + + // Deallocate + const size_t size = sizeof(ObjectDescriptor) + vt->GetSize(); + char* raw = reinterpret_cast(obj); + allocator_.deallocate(raw, size); + } + repo_.Clear(); + return {}; +} + +const ObjectRepository& MemoryManager::GetRepository() const { + return repo_; +} + +} // namespace ovum::vm::runtime diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp new file mode 100644 index 0000000..d618fc8 --- /dev/null +++ b/lib/runtime/MemoryManager.hpp @@ -0,0 +1,42 @@ +#ifndef RUNTIME_MEMORYMANAGER_HPP +#define RUNTIME_MEMORYMANAGER_HPP + +#include +#include +#include +#include + +#include "IGarbageCollector.hpp" +#include "ObjectRepository.hpp" +#include "VirtualTable.hpp" + +namespace ovum::vm::execution_tree { struct PassedExecutionData; } // Forward declaration + +namespace ovum::vm::runtime { + +constexpr size_t kDefaultMaxObjects = 10000; // Default threshold for auto-GC + +class MemoryManager { +public: + MemoryManager(); + + std::expected AllocateObject(const VirtualTable& vtable, + uint32_t vtable_index, + execution_tree::PassedExecutionData& data); + std::expected DeallocateObject(void* obj, + execution_tree::PassedExecutionData& data); + std::expected CollectGarbage(execution_tree::PassedExecutionData& data); + std::expected Clear(execution_tree::PassedExecutionData& data); + + [[nodiscard]] const ObjectRepository& GetRepository() const; + +private: + ObjectRepository repo_; + std::allocator allocator_; + std::optional> gc_; + size_t max_objects_; +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_MEMORYMANAGER_HPP From de97b9c589e7891a0aab5e40ba771e4fd5ac9e84 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:27:44 +0300 Subject: [PATCH 04/59] feat: replace all runtime::AllocateObject calls with MemoryManager --- lib/execution_tree/BytecodeCommands.cpp | 46 +++++++++++-------------- lib/executor/BuiltinFunctions.cpp | 24 +++++-------- lib/executor/Executor.cpp | 12 +++---- lib/executor/builtin_factory.cpp | 10 ++++++ 4 files changed, 42 insertions(+), 50 deletions(-) diff --git a/lib/execution_tree/BytecodeCommands.cpp b/lib/execution_tree/BytecodeCommands.cpp index 291a5ed..88edbc2 100644 --- a/lib/execution_tree/BytecodeCommands.cpp +++ b/lib/execution_tree/BytecodeCommands.cpp @@ -1,11 +1,9 @@ #include "BytecodeCommands.hpp" #include -#include #include #include #include -#include #include #include #include @@ -109,10 +107,8 @@ std::expected PushString(PassedExecutionDat return std::unexpected(vtable_index_result.error()); } - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_obj_result = + data.memory_manager.AllocateObject(*string_vtable, static_cast(vtable_index_result.value()), data); if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); @@ -140,8 +136,8 @@ std::expected PushNull(PassedExecutionData& return std::unexpected(vtable_index_result.error()); } - auto null_obj_result = runtime::AllocateObject( - *null_vtable, static_cast(vtable_index_result.value()), data.memory.object_repository, data.allocator); + auto null_obj_result = + data.memory_manager.AllocateObject(*null_vtable, static_cast(vtable_index_result.value()), data); if (!null_obj_result.has_value()) { return std::unexpected(null_obj_result.error()); @@ -1203,8 +1199,7 @@ std::expected CallConstructor(PassedExecuti return std::unexpected(vtable.error()); } - auto obj_ptr = - runtime::AllocateObject(*vtable.value(), vtable_idx.value(), data.memory.object_repository, data.allocator); + auto obj_ptr = data.memory_manager.AllocateObject(*vtable.value(), vtable_idx.value(), data); if (!obj_ptr) { return std::unexpected(obj_ptr.error()); @@ -1594,10 +1589,9 @@ std::expected FormatDateTime(PassedExecutio return std::unexpected(vtable_index_result.error()); } - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_obj_result = + data.memory_manager.AllocateObject(*string_vtable, static_cast(vtable_index_result.value()), data); + if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); } @@ -1651,8 +1645,9 @@ std::expected ParseDateTime(PassedExecution return std::unexpected(vtable_index_result.error()); } - auto int_obj_result = runtime::AllocateObject( - *int_vtable, static_cast(vtable_index_result.value()), data.memory.object_repository, data.allocator); + auto int_obj_result = + data.memory_manager.AllocateObject(*int_vtable, static_cast(vtable_index_result.value()), data); + if (!int_obj_result.has_value()) { return std::unexpected(int_obj_result.error()); } @@ -1813,10 +1808,8 @@ std::expected ListDir(PassedExecutionData& return std::unexpected(vtable_index_result.error()); } - auto string_array_obj_result = runtime::AllocateObject(*string_array_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_array_obj_result = data.memory_manager.AllocateObject( + *string_array_vtable, static_cast(vtable_index_result.value()), data); if (!string_array_obj_result.has_value()) { return std::unexpected(string_array_obj_result.error()); } @@ -1839,10 +1832,9 @@ std::expected ListDir(PassedExecutionData& return std::unexpected(string_vtable_index_result.error()); } - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(string_vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_obj_result = data.memory_manager.AllocateObject( + *string_vtable, static_cast(string_vtable_index_result.value()), data); + if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); } @@ -2086,8 +2078,10 @@ std::expected GetPeakMemoryUsage(PassedExec std::expected ForceGarbageCollection(PassedExecutionData& data) { // Simple garbage collection: remove unreachable objects - // This is a placeholder implementation - // data.memory.object_repository.CollectGarbage(); + auto result = data.memory_manager.CollectGarbage(data); + if (!result.has_value()) { + return std::unexpected(result.error()); + } return ExecutionResult::kNormal; } diff --git a/lib/executor/BuiltinFunctions.cpp b/lib/executor/BuiltinFunctions.cpp index a65bb60..42d870d 100644 --- a/lib/executor/BuiltinFunctions.cpp +++ b/lib/executor/BuiltinFunctions.cpp @@ -172,10 +172,8 @@ std::expected FundamentalTypeToString(Passe return std::unexpected(vtable_index_result.error()); } - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_obj_result = + data.memory_manager.AllocateObject(*string_vtable, static_cast(vtable_index_result.value()), data); if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); @@ -938,10 +936,8 @@ std::expected StringToUtf8Bytes(PassedExecu return std::unexpected(vtable_index_result.error()); } - auto byte_array_obj_result = runtime::AllocateObject(*byte_array_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto byte_array_obj_result = + data.memory_manager.AllocateObject(*byte_array_vtable, static_cast(vtable_index_result.value()), data); if (!byte_array_obj_result.has_value()) { return std::unexpected(byte_array_obj_result.error()); @@ -1919,10 +1915,8 @@ std::expected FileRead(PassedExecutionData& return std::unexpected(vtable_index_result.error()); } - auto byte_array_obj_result = runtime::AllocateObject(*byte_array_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto byte_array_obj_result = + data.memory_manager.AllocateObject(*byte_array_vtable, static_cast(vtable_index_result.value()), data); if (!byte_array_obj_result.has_value()) { return std::unexpected(byte_array_obj_result.error()); @@ -2001,10 +1995,8 @@ std::expected FileReadLine(PassedExecutionD return std::unexpected(vtable_index_result.error()); } - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(vtable_index_result.value()), - data.memory.object_repository, - data.allocator); + auto string_obj_result = + data.memory_manager.AllocateObject(*string_vtable, static_cast(vtable_index_result.value()), data); if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); diff --git a/lib/executor/Executor.cpp b/lib/executor/Executor.cpp index 08a2648..fdc5fe9 100644 --- a/lib/executor/Executor.cpp +++ b/lib/executor/Executor.cpp @@ -31,10 +31,8 @@ std::expected CreateStringArrayFromArgs(execution_tre return std::unexpected(string_vtable_index_result.error()); } - auto default_string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(string_vtable_index_result.value()), - execution_data.memory.object_repository, - execution_data.allocator); + auto default_string_obj_result = execution_data.memory_manager.AllocateObject( + *string_vtable, static_cast(string_vtable_index_result.value()), execution_data); if (!default_string_obj_result.has_value()) { return std::unexpected(default_string_obj_result.error()); @@ -70,10 +68,8 @@ std::expected CreateStringArrayFromArgs(execution_tre } for (size_t i = 0; i < args.size(); ++i) { - auto string_obj_result = runtime::AllocateObject(*string_vtable, - static_cast(string_vtable_index_result.value()), - execution_data.memory.object_repository, - execution_data.allocator); + auto string_obj_result = execution_data.memory_manager.AllocateObject( + *string_vtable, static_cast(string_vtable_index_result.value()), execution_data); if (!string_obj_result.has_value()) { return std::unexpected(string_obj_result.error()); diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index 3066a35..d5230b4 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -20,6 +20,8 @@ #include "lib/runtime/ObjectDescriptor.hpp" #include "lib/runtime/VirtualTable.hpp" #include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/ArrayReferenceScanner.hpp" +#include "lib/runtime/EmptyReferenceScanner.hpp" namespace ovum::vm::runtime { @@ -186,6 +188,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl int_array_vtable.AddFunction("_GetHash_", "_IntArray_GetHash_"); int_array_vtable.AddInterface("IComparable"); int_array_vtable.AddInterface("IHashable"); + int_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(int_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -202,6 +205,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl float_array_vtable.AddFunction("_GetHash_", "_FloatArray_GetHash_"); float_array_vtable.AddInterface("IComparable"); float_array_vtable.AddInterface("IHashable"); + float_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(float_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -218,6 +222,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl char_array_vtable.AddFunction("_GetHash_", "_CharArray_GetHash_"); char_array_vtable.AddInterface("IComparable"); char_array_vtable.AddInterface("IHashable"); + char_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(char_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -235,6 +240,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl byte_array_vtable.AddFunction("_GetHash_", "_ByteArray_GetHash_"); byte_array_vtable.AddInterface("IComparable"); byte_array_vtable.AddInterface("IHashable"); + byte_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(byte_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -251,6 +257,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl bool_array_vtable.AddFunction("_GetHash_", "_BoolArray_GetHash_"); bool_array_vtable.AddInterface("IComparable"); bool_array_vtable.AddInterface("IHashable"); + bool_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(bool_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -267,6 +274,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl object_array_vtable.AddFunction("_GetHash_", "_ObjectArray_GetHash_"); object_array_vtable.AddInterface("IComparable"); object_array_vtable.AddInterface("IHashable"); + object_array_vtable.SetReferenceScanner(std::make_unique>()); auto result = repository.Add(std::move(object_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -283,6 +291,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl string_array_vtable.AddFunction("_GetHash_", "_StringArray_GetHash_"); string_array_vtable.AddInterface("IComparable"); string_array_vtable.AddInterface("IHashable"); + string_array_vtable.SetReferenceScanner(std::make_unique>()); auto result = repository.Add(std::move(string_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -315,6 +324,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl pointer_array_vtable.AddFunction("_GetHash_", "_PointerArray_GetHash_"); pointer_array_vtable.AddInterface("IComparable"); pointer_array_vtable.AddInterface("IHashable"); + pointer_array_vtable.SetReferenceScanner(std::make_unique>()); auto result = repository.Add(std::move(pointer_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); From 17af547817e71e0a84eae3710676dfd5cbee3184 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:28:23 +0300 Subject: [PATCH 05/59] feat: add IsReferenceType method --- lib/runtime/IVariableAccessor.hpp | 1 + lib/runtime/VariableAccessor.hpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/runtime/IVariableAccessor.hpp b/lib/runtime/IVariableAccessor.hpp index 8b3fbd9..3c82200 100644 --- a/lib/runtime/IVariableAccessor.hpp +++ b/lib/runtime/IVariableAccessor.hpp @@ -14,6 +14,7 @@ class IVariableAccessor { // NOLINT(cppcoreguidelines-special-member-functions) virtual Variable GetVariable(void* value_ptr) const = 0; virtual std::expected WriteVariable(void* value_ptr, const Variable& variable) const = 0; + [[nodiscard]] virtual bool IsReferenceType() const = 0; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/VariableAccessor.hpp b/lib/runtime/VariableAccessor.hpp index 6322304..d94315c 100644 --- a/lib/runtime/VariableAccessor.hpp +++ b/lib/runtime/VariableAccessor.hpp @@ -24,6 +24,10 @@ class VariableAccessor : public IVariableAccessor { *reinterpret_cast(value_ptr) = std::get(variable); return {}; } + + [[nodiscard]] bool IsReferenceType() const override { + return std::is_same_v; + } }; } // namespace ovum::vm::runtime From 7d469a13a739d8de5b0ff2954a11cdf0f12e472e Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:29:13 +0300 Subject: [PATCH 06/59] feat: add MemoryManager as field in PassedExecutionData --- lib/execution_tree/PassedExecutionData.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/execution_tree/PassedExecutionData.hpp b/lib/execution_tree/PassedExecutionData.hpp index 01b8ee8..2fed1c5 100644 --- a/lib/execution_tree/PassedExecutionData.hpp +++ b/lib/execution_tree/PassedExecutionData.hpp @@ -7,6 +7,7 @@ #include "lib/runtime/RuntimeMemory.hpp" #include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/MemoryManager.hpp" namespace ovum::vm::execution_tree { @@ -16,7 +17,7 @@ struct PassedExecutionData { runtime::RuntimeMemory& memory; const runtime::VirtualTableRepository& virtual_table_repository; const FunctionRepository& function_repository; - std::allocator allocator; + runtime::MemoryManager& memory_manager; std::istream& input_stream; std::ostream& output_stream; std::ostream& error_stream; From 53999d74cc2b9eb344f66270f7f03a61373cac63 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:29:54 +0300 Subject: [PATCH 07/59] feat: add mark-and-sweep gc implementation --- lib/runtime/CMakeLists.txt | 2 + lib/runtime/MarkAndSweepGC.cpp | 128 +++++++++++++++++++++++++++++++++ lib/runtime/MarkAndSweepGC.hpp | 22 ++++++ 3 files changed, 152 insertions(+) create mode 100644 lib/runtime/MarkAndSweepGC.cpp create mode 100644 lib/runtime/MarkAndSweepGC.hpp diff --git a/lib/runtime/CMakeLists.txt b/lib/runtime/CMakeLists.txt index 4f18707..c8ba6f2 100644 --- a/lib/runtime/CMakeLists.txt +++ b/lib/runtime/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(runtime STATIC VirtualTableRepository.cpp ObjectRepository.cpp ByteArray.cpp + MarkAndSweepGC.cpp + MemoryManager.cpp ) target_include_directories(runtime PUBLIC ${PROJECT_SOURCE_DIR}) diff --git a/lib/runtime/MarkAndSweepGC.cpp b/lib/runtime/MarkAndSweepGC.cpp new file mode 100644 index 0000000..9131a8c --- /dev/null +++ b/lib/runtime/MarkAndSweepGC.cpp @@ -0,0 +1,128 @@ +#include "MarkAndSweepGC.hpp" + +#include +#include +#include + +#include "ObjectDescriptor.hpp" +#include "lib/execution_tree/PassedExecutionData.hpp" +#include "VirtualTableRepository.hpp" + +namespace ovum::vm::runtime { + +std::expected MarkAndSweepGC::Collect(execution_tree::PassedExecutionData& data) { + Mark(data); + Sweep(data); + return {}; +} + +void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { + std::queue worklist; + AddRoots(worklist, data); + + while (!worklist.empty()) { + void* obj = worklist.front(); + worklist.pop(); + + auto* desc = reinterpret_cast(obj); + if (desc->badge & kMarkBit) { + continue; // Already marked + } + desc->badge |= kMarkBit; + + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (!vt_res.has_value()) { + // Error handling: skip for now + continue; + } + const VirtualTable* vt = vt_res.value(); + + vt->ScanReferences(obj, [&](void* ref) { + if (ref) { + worklist.push(ref); + } + }); + } +} + +void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { + std::vector to_delete; + + const ObjectRepository& repo = data.memory_manager.GetRepository(); + for (size_t i = 0; i < repo.GetCount(); ++i) { + auto obj_res = repo.GetByIndex(i); + if (!obj_res.has_value()) { + continue; + } + + const ObjectDescriptor* const_desc = obj_res.value(); + + void* obj = const_cast(static_cast(const_desc)); + + if (!(const_desc->badge & kMarkBit)) { + to_delete.push_back(obj); + } + + // Сброс mark-bit для всех объектов + const_cast(const_desc)->badge &= ~kMarkBit; + } + + for (void* obj : to_delete) { + auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); + if (!dealloc_res.has_value()) { + // Log error, continue + } + } +} + +void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data) { + // Scan global variables + for (const auto& var : data.memory.global_variables) { + if (std::holds_alternative(var)) { + void* ptr = std::get(var); + if (ptr) { + worklist.push(ptr); + } + } + } + + // Scan stack frames + std::vector temp_frames; + while (!data.memory.stack_frames.empty()) { + temp_frames.push_back(data.memory.stack_frames.top()); + data.memory.stack_frames.pop(); + } + for (const auto& frame : temp_frames) { + for (const auto& var : frame.local_variables) { + if (std::holds_alternative(var)) { + void* ptr = std::get(var); + if (ptr) { + worklist.push(ptr); + } + } + } + } + for (auto & temp_frame : std::ranges::reverse_view(temp_frames)) { + data.memory.stack_frames.push(temp_frame); + } + + // Scan machine stack + std::vector temp_stack; + while (!data.memory.machine_stack.empty()) { + temp_stack.push_back(data.memory.machine_stack.top()); + data.memory.machine_stack.pop(); + } + for (const auto& var : temp_stack) { + if (std::holds_alternative(var)) { + void* ptr = std::get(var); + if (ptr) { + worklist.push(ptr); + } + } + } + for (auto & it : std::ranges::reverse_view(temp_stack)) { + data.memory.machine_stack.push(it); + } +} + +} // namespace ovum::vm::runtime diff --git a/lib/runtime/MarkAndSweepGC.hpp b/lib/runtime/MarkAndSweepGC.hpp new file mode 100644 index 0000000..39e2e41 --- /dev/null +++ b/lib/runtime/MarkAndSweepGC.hpp @@ -0,0 +1,22 @@ +#ifndef RUNTIME_MARKANDSWEEPGC_HPP +#define RUNTIME_MARKANDSWEEPGC_HPP + +#include "IGarbageCollector.hpp" + +#include + +namespace ovum::vm::runtime { + +class MarkAndSweepGC : public IGarbageCollector { +public: + std::expected Collect(execution_tree::PassedExecutionData& data) override; + +private: + void Mark(execution_tree::PassedExecutionData& data); + void Sweep(execution_tree::PassedExecutionData& data); + void AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data); +}; + +} // namespace ovum::vm::runtime + +#endif // RUNTIME_MARKANDSWEEPGC_HPP \ No newline at end of file From 0aa26b627d520c41ff6eb7d56a544536f2e9fac4 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:30:10 +0300 Subject: [PATCH 08/59] feat: change vm_ui --- lib/vm_ui/vm_ui_functions.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index 1a8584b..b3cf54f 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -86,10 +86,11 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out ovum::vm::execution_tree::FunctionRepository func_repo; ovum::vm::runtime::VirtualTableRepository vtable_repo; ovum::vm::runtime::RuntimeMemory memory; + ovum::vm::runtime::MemoryManager memory_manager; ovum::vm::execution_tree::PassedExecutionData execution_data{.memory = memory, .virtual_table_repository = vtable_repo, .function_repository = func_repo, - .allocator = std::allocator(), + .memory_manager = memory_manager, .input_stream = in, .output_stream = out, .error_stream = err}; @@ -196,8 +197,12 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out continue; } - size_t object_size = vtable_result.value()->GetSize(); - execution_data.allocator.deallocate(reinterpret_cast(object), object_size); + auto clear_result = memory_manager.Clear(execution_data); + if (!clear_result.has_value()) { + err << "Warning: Failed to clean up objects during shutdown: " + << clear_result.error().what() << "\n"; + return_code = 5; + } } return return_code; From 92be31957c13d2ed23a45aadc2130efbc9d9e4a0 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:33:40 +0300 Subject: [PATCH 09/59] refactor: move some files --- lib/executor/builtin_factory.cpp | 4 ++-- lib/runtime/CMakeLists.txt | 2 +- lib/runtime/MemoryManager.cpp | 4 ++-- lib/runtime/MemoryManager.hpp | 2 +- lib/runtime/VirtualTable.cpp | 2 +- lib/runtime/VirtualTable.hpp | 2 +- lib/runtime/{ => gc}/IGarbageCollector.hpp | 0 lib/runtime/{ => gc}/MarkAndSweepGC.cpp | 4 ++-- lib/runtime/{ => gc}/MarkAndSweepGC.hpp | 2 +- .../{ => gc/reference_scanners}/ArrayReferenceScanner.hpp | 2 +- .../{ => gc/reference_scanners}/DefaultReferenceScanner.hpp | 2 +- .../{ => gc/reference_scanners}/EmptyReferenceScanner.hpp | 0 lib/runtime/{ => gc/reference_scanners}/IReferenceScanner.hpp | 0 13 files changed, 13 insertions(+), 13 deletions(-) rename lib/runtime/{ => gc}/IGarbageCollector.hpp (100%) rename lib/runtime/{ => gc}/MarkAndSweepGC.cpp (97%) rename lib/runtime/{ => gc}/MarkAndSweepGC.hpp (92%) rename lib/runtime/{ => gc/reference_scanners}/ArrayReferenceScanner.hpp (88%) rename lib/runtime/{ => gc/reference_scanners}/DefaultReferenceScanner.hpp (95%) rename lib/runtime/{ => gc/reference_scanners}/EmptyReferenceScanner.hpp (100%) rename lib/runtime/{ => gc/reference_scanners}/IReferenceScanner.hpp (100%) diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index d5230b4..48c7cfa 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -20,8 +20,8 @@ #include "lib/runtime/ObjectDescriptor.hpp" #include "lib/runtime/VirtualTable.hpp" #include "lib/runtime/VirtualTableRepository.hpp" -#include "lib/runtime/ArrayReferenceScanner.hpp" -#include "lib/runtime/EmptyReferenceScanner.hpp" +#include "lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp" +#include "lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/CMakeLists.txt b/lib/runtime/CMakeLists.txt index c8ba6f2..38b761d 100644 --- a/lib/runtime/CMakeLists.txt +++ b/lib/runtime/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(runtime STATIC VirtualTableRepository.cpp ObjectRepository.cpp ByteArray.cpp - MarkAndSweepGC.cpp + gc/MarkAndSweepGC.cpp MemoryManager.cpp ) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index f14d10c..beda53d 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -3,9 +3,9 @@ #include #include "ObjectDescriptor.hpp" -#include "lib/execution_tree/PassedExecutionData.hpp" #include "lib/execution_tree/FunctionRepository.hpp" -#include "MarkAndSweepGC.hpp" +#include "lib/execution_tree/PassedExecutionData.hpp" +#include "lib/runtime/gc/MarkAndSweepGC.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp index d618fc8..5891a72 100644 --- a/lib/runtime/MemoryManager.hpp +++ b/lib/runtime/MemoryManager.hpp @@ -6,9 +6,9 @@ #include #include -#include "IGarbageCollector.hpp" #include "ObjectRepository.hpp" #include "VirtualTable.hpp" +#include "lib/runtime/gc/IGarbageCollector.hpp" namespace ovum::vm::execution_tree { struct PassedExecutionData; } // Forward declaration diff --git a/lib/runtime/VirtualTable.cpp b/lib/runtime/VirtualTable.cpp index 00c8e8f..b920767 100644 --- a/lib/runtime/VirtualTable.cpp +++ b/lib/runtime/VirtualTable.cpp @@ -2,8 +2,8 @@ #include -#include "DefaultReferenceScanner.hpp" #include "VariableAccessor.hpp" +#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/VirtualTable.hpp b/lib/runtime/VirtualTable.hpp index 9c5e7e9..a5e3efe 100644 --- a/lib/runtime/VirtualTable.hpp +++ b/lib/runtime/VirtualTable.hpp @@ -10,9 +10,9 @@ #include "FieldInfo.hpp" #include "FunctionId.hpp" -#include "IReferenceScanner.hpp" #include "IVariableAccessor.hpp" #include "Variable.hpp" +#include "lib/runtime/gc/reference_scanners/IReferenceScanner.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/IGarbageCollector.hpp b/lib/runtime/gc/IGarbageCollector.hpp similarity index 100% rename from lib/runtime/IGarbageCollector.hpp rename to lib/runtime/gc/IGarbageCollector.hpp diff --git a/lib/runtime/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp similarity index 97% rename from lib/runtime/MarkAndSweepGC.cpp rename to lib/runtime/gc/MarkAndSweepGC.cpp index 9131a8c..eab16b9 100644 --- a/lib/runtime/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -4,9 +4,9 @@ #include #include -#include "ObjectDescriptor.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" -#include "VirtualTableRepository.hpp" +#include "lib/runtime/ObjectDescriptor.hpp" +#include "lib/runtime/VirtualTableRepository.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/MarkAndSweepGC.hpp b/lib/runtime/gc/MarkAndSweepGC.hpp similarity index 92% rename from lib/runtime/MarkAndSweepGC.hpp rename to lib/runtime/gc/MarkAndSweepGC.hpp index 39e2e41..2bcaf7c 100644 --- a/lib/runtime/MarkAndSweepGC.hpp +++ b/lib/runtime/gc/MarkAndSweepGC.hpp @@ -1,7 +1,7 @@ #ifndef RUNTIME_MARKANDSWEEPGC_HPP #define RUNTIME_MARKANDSWEEPGC_HPP -#include "IGarbageCollector.hpp" +#include "lib/runtime/gc/IGarbageCollector.hpp" #include diff --git a/lib/runtime/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp similarity index 88% rename from lib/runtime/ArrayReferenceScanner.hpp rename to lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index 6efd086..343d683 100644 --- a/lib/runtime/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -4,7 +4,7 @@ #include #include "IReferenceScanner.hpp" -#include "ObjectDescriptor.hpp" // Для GetDataPointer +#include "lib/runtime/ObjectDescriptor.hpp" // Для GetDataPointer namespace ovum::vm::runtime { diff --git a/lib/runtime/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp similarity index 95% rename from lib/runtime/DefaultReferenceScanner.hpp rename to lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 2bc4113..0ce3edc 100644 --- a/lib/runtime/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -2,7 +2,7 @@ #define RUNTIME_DEFAULTREFERENCESCANNER_HPP #include "IReferenceScanner.hpp" -#include "VirtualTable.hpp" +#include "lib/runtime/VirtualTable.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/EmptyReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp similarity index 100% rename from lib/runtime/EmptyReferenceScanner.hpp rename to lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp diff --git a/lib/runtime/IReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/IReferenceScanner.hpp similarity index 100% rename from lib/runtime/IReferenceScanner.hpp rename to lib/runtime/gc/reference_scanners/IReferenceScanner.hpp From 64512627861a8b6317cc8811a0ad77cd4952ab32 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:50:56 +0300 Subject: [PATCH 10/59] refactor: add reference scanners for string and file --- lib/executor/builtin_factory.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index 48c7cfa..cd3adc0 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -150,6 +150,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl string_vtable.AddInterface("IComparable"); string_vtable.AddInterface("IHashable"); string_vtable.AddInterface("IStringConvertible"); + string_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(string_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -171,6 +172,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl file_vtable.AddFunction("_Seek__Int", "_File_Seek__Int"); file_vtable.AddFunction("_Tell_", "_File_Tell_"); file_vtable.AddFunction("_Eof_", "_File_Eof_"); + file_vtable.SetReferenceScanner(std::make_unique()); // File does not implement IComparable or IHashable (files cannot be meaningfully compared or hashed) auto result = repository.Add(std::move(file_vtable)); if (!result.has_value()) { From 225fe9d27769d6733b8a60d746d3bcf76b819e83 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 16:54:31 +0300 Subject: [PATCH 11/59] refactor: change Clear in vm_ui --- lib/vm_ui/vm_ui_functions.cpp | 58 +++-------------------------------- 1 file changed, 5 insertions(+), 53 deletions(-) diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index b3cf54f..c3805a8 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -150,59 +150,11 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out return_code = 4; } - for (size_t i = 0; i < memory.object_repository.GetCount(); ++i) { - auto object_result = memory.object_repository.GetByIndex(i); - - if (!object_result.has_value()) { - err << "Failed to get object: " << object_result.error().what() << "\n"; - return_code = 4; - continue; - } - - ovum::vm::runtime::ObjectDescriptor* object = object_result.value(); - uint32_t vtable_index = object->vtable_index; - auto vtable_result = vtable_repo.GetByIndex(vtable_index); - - if (!vtable_result.has_value()) { - err << "Failed to get vtable for index " << vtable_index << ": " << vtable_result.error().what() << "\n"; - return_code = 4; - continue; - } - - auto destructor_id_result = vtable_result.value()->GetRealFunctionId("_destructor_"); - - if (!destructor_id_result.has_value()) { - err << "Failed to get destructor for index " << vtable_index << ": " << destructor_id_result.error().what() - << "\n"; - return_code = 4; - continue; - } - - auto destructor_function = execution_data.function_repository.GetById(destructor_id_result.value()); - - if (!destructor_function.has_value()) { - err << "Failed to get destructor function for index " << vtable_index << ": " - << destructor_function.error().what() << "\n"; - return_code = 4; - continue; - } - - execution_data.memory.machine_stack.emplace(object); - auto destructor_exec_result = destructor_function.value()->Execute(execution_data); - - if (!destructor_exec_result.has_value()) { - err << "Failed to execute destructor for index " << vtable_index << ": " << destructor_exec_result.error().what() - << "\n"; - return_code = 4; - continue; - } - - auto clear_result = memory_manager.Clear(execution_data); - if (!clear_result.has_value()) { - err << "Warning: Failed to clean up objects during shutdown: " - << clear_result.error().what() << "\n"; - return_code = 5; - } + auto clear_result = memory_manager.Clear(execution_data); + if (!clear_result.has_value()) { + err << "Warning: Failed to clean up objects during shutdown: " + << clear_result.error().what() << "\n"; + return_code = 4; } return return_code; From 81a6f95c610e73029093d981ed33cc77accb70b4 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 17:27:14 +0300 Subject: [PATCH 12/59] feat: optimize garbage collection --- lib/runtime/MemoryManager.cpp | 128 ++++++++++++------------------ lib/runtime/MemoryManager.hpp | 14 ++-- lib/runtime/ObjectDescriptor.hpp | 1 + lib/runtime/gc/MarkAndSweepGC.cpp | 42 ++++------ lib/runtime/gc/MarkAndSweepGC.hpp | 6 +- 5 files changed, 77 insertions(+), 114 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index beda53d..0ce7d6a 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -2,42 +2,42 @@ #include -#include "ObjectDescriptor.hpp" #include "lib/execution_tree/FunctionRepository.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" #include "lib/runtime/gc/MarkAndSweepGC.hpp" +#include "ObjectDescriptor.hpp" + namespace ovum::vm::runtime { -MemoryManager::MemoryManager() : max_objects_(kDefaultMaxObjects) { - gc_ = std::make_unique(); // Assume enabled by default +MemoryManager::MemoryManager() : gc_threshold_(kDefaultMaxObjects) { + gc_ = std::make_unique(); } std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, execution_tree::PassedExecutionData& data) { - const size_t size = sizeof(ObjectDescriptor) + vtable.GetSize(); - char* raw_memory = allocator_.allocate(size); + const size_t total_size = sizeof(ObjectDescriptor) + vtable.GetSize(); + char* raw_memory = allocator_.allocate(total_size); if (!raw_memory) { - return std::unexpected(std::runtime_error("MemoryManager: Allocation failed")); + return std::unexpected(std::runtime_error("MemoryManager: Allocation failed - out of memory")); } - auto* descriptor = reinterpret_cast(raw_memory); + auto* descriptor = new (raw_memory) ObjectDescriptor{}; descriptor->vtable_index = vtable_index; - descriptor->badge = 0; // Clear mark bit + descriptor->badge = 0; auto add_result = repo_.Add(descriptor); if (!add_result.has_value()) { - allocator_.deallocate(raw_memory, size); + descriptor->~ObjectDescriptor(); + allocator_.deallocate(raw_memory, total_size); return std::unexpected(add_result.error()); } - // Авто-GC если превышен threshold - if (repo_.GetCount() > max_objects_) { - auto gc_result = CollectGarbage(data); - if (!gc_result.has_value()) { - // Log error, but continue - } + descriptor->repo_index = add_result.value(); + + if (repo_.GetCount() > gc_threshold_) { + CollectGarbage(data); } return reinterpret_cast(descriptor); @@ -46,103 +46,73 @@ std::expected MemoryManager::AllocateObject(const Vir std::expected MemoryManager::DeallocateObject(void* obj, execution_tree::PassedExecutionData& data) { if (!obj) { - return std::unexpected(std::runtime_error("DeallocateObject: Null object")); + return std::unexpected(std::runtime_error("DeallocateObject: Null object pointer")); } - // Найти индекс в repo - auto index = static_cast(-1); - for (size_t i = 0; i < repo_.GetCount(); ++i) { - auto repo_obj_res = repo_.GetByIndex(i); - if (repo_obj_res.has_value() && repo_obj_res.value() == obj) { - index = i; - break; - } - } + auto* desc = reinterpret_cast(obj); + const size_t index = desc->repo_index; - if (std::cmp_equal(index ,-1)) { - return std::unexpected(std::runtime_error("DeallocateObject: Object not found in repository")); + if (index >= repo_.GetCount()) { + return std::unexpected(std::runtime_error("DeallocateObject: Invalid repo_index (out of bounds)")); } - auto* desc = reinterpret_cast(obj); + auto check_res = repo_.GetByIndex(index); + if (!check_res.has_value() || check_res.value() != desc) { + return std::unexpected(std::runtime_error("DeallocateObject: repo_index mismatch (corrupted?)")); + } - // Вызов деструктора auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); if (vt_res.has_value()) { const VirtualTable* vt = vt_res.value(); - auto dtor_id_res = vt->GetRealFunctionId("_" + vt->GetName() + "_destructor_"); + + auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); if (dtor_id_res.has_value()) { auto func_res = data.function_repository.GetById(dtor_id_res.value()); if (func_res.has_value()) { - // Создать frame, push obj, Execute runtime::StackFrame frame; - frame.function_name = dtor_id_res.value(); frame.local_variables.emplace_back(obj); - data.memory.stack_frames.push(frame); - func_res.value()->Execute(data); + data.memory.stack_frames.push(std::move(frame)); + + auto exec_res = func_res.value()->Execute(data); + if (!exec_res.has_value()) { + } + data.memory.stack_frames.pop(); } } - // Deallocate - const size_t size = sizeof(ObjectDescriptor) + vt->GetSize(); + const size_t total_size = sizeof(ObjectDescriptor) + vt->GetSize(); char* raw = reinterpret_cast(obj); - allocator_.deallocate(raw, size); + desc->~ObjectDescriptor(); + allocator_.deallocate(raw, total_size); } - // Удалить из repo - auto remove_res = repo_.Remove(index); - if (!remove_res.has_value()) { - return std::unexpected(remove_res.error()); - } + repo_.Remove(index); return {}; } -std::expected MemoryManager::CollectGarbage(execution_tree::PassedExecutionData& data) { - if (!gc_.has_value()) { - return std::unexpected(std::runtime_error("MemoryManager: No GC configured")); - } - return gc_.value()->Collect(data); -} - std::expected MemoryManager::Clear(execution_tree::PassedExecutionData& data) { - for (size_t i = 0; i < repo_.GetCount(); ++i) { - auto obj_res = repo_.GetByIndex(i); + while (repo_.GetCount() > 0) { + const size_t last_index = repo_.GetCount() - 1; + auto obj_res = repo_.GetByIndex(last_index); if (!obj_res.has_value()) { + repo_.Remove(last_index); continue; } - void* obj = obj_res.value(); - auto* desc = reinterpret_cast(obj); - // Получить VT - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); - if (!vt_res.has_value()) { - continue; - } - const VirtualTable* vt = vt_res.value(); + DeallocateObject(obj_res.value(), data); + } - // Получить ID деструктора - auto dtor_id_res = vt->GetRealFunctionId("_" + vt->GetName() + "_destructor_"); - if (dtor_id_res.has_value()) { - auto func_res = data.function_repository.GetById(dtor_id_res.value()); - if (func_res.has_value()) { - // Создать frame, push obj, Execute - runtime::StackFrame frame; - frame.function_name = dtor_id_res.value(); - frame.local_variables.emplace_back(obj); - data.memory.stack_frames.push(frame); - func_res.value()->Execute(data); - data.memory.stack_frames.pop(); - } - } + return {}; +} - // Deallocate - const size_t size = sizeof(ObjectDescriptor) + vt->GetSize(); - char* raw = reinterpret_cast(obj); - allocator_.deallocate(raw, size); +std::expected MemoryManager::CollectGarbage(execution_tree::PassedExecutionData& data) { + if (!gc_) { + return std::unexpected(std::runtime_error("MemoryManager: No GC configured")); } - repo_.Clear(); - return {}; + + return gc_.value()->Collect(data); } const ObjectRepository& MemoryManager::GetRepository() const { diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp index 5891a72..42ea7ad 100644 --- a/lib/runtime/MemoryManager.hpp +++ b/lib/runtime/MemoryManager.hpp @@ -6,15 +6,18 @@ #include #include +#include "lib/runtime/gc/IGarbageCollector.hpp" + #include "ObjectRepository.hpp" #include "VirtualTable.hpp" -#include "lib/runtime/gc/IGarbageCollector.hpp" -namespace ovum::vm::execution_tree { struct PassedExecutionData; } // Forward declaration +namespace ovum::vm::execution_tree { +struct PassedExecutionData; +} namespace ovum::vm::runtime { -constexpr size_t kDefaultMaxObjects = 10000; // Default threshold for auto-GC +constexpr size_t kDefaultMaxObjects = 10000; // Default threshold for auto-GC class MemoryManager { public: @@ -23,8 +26,7 @@ class MemoryManager { std::expected AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, execution_tree::PassedExecutionData& data); - std::expected DeallocateObject(void* obj, - execution_tree::PassedExecutionData& data); + std::expected DeallocateObject(void* obj, execution_tree::PassedExecutionData& data); std::expected CollectGarbage(execution_tree::PassedExecutionData& data); std::expected Clear(execution_tree::PassedExecutionData& data); @@ -34,7 +36,7 @@ class MemoryManager { ObjectRepository repo_; std::allocator allocator_; std::optional> gc_; - size_t max_objects_; + size_t gc_threshold_; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/ObjectDescriptor.hpp b/lib/runtime/ObjectDescriptor.hpp index de8cb36..06d47be 100644 --- a/lib/runtime/ObjectDescriptor.hpp +++ b/lib/runtime/ObjectDescriptor.hpp @@ -8,6 +8,7 @@ namespace ovum::vm::runtime { struct ObjectDescriptor { uint32_t vtable_index; uint32_t badge; + size_t repo_index; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index eab16b9..9a2f55a 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "lib/execution_tree/PassedExecutionData.hpp" @@ -26,13 +27,12 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { auto* desc = reinterpret_cast(obj); if (desc->badge & kMarkBit) { - continue; // Already marked + continue; } desc->badge |= kMarkBit; auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); if (!vt_res.has_value()) { - // Error handling: skip for now continue; } const VirtualTable* vt = vt_res.value(); @@ -47,6 +47,7 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { std::vector to_delete; + to_delete.reserve(data.memory_manager.GetRepository().GetCount() / 4); const ObjectRepository& repo = data.memory_manager.GetRepository(); for (size_t i = 0; i < repo.GetCount(); ++i) { @@ -56,27 +57,21 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { } const ObjectDescriptor* const_desc = obj_res.value(); - void* obj = const_cast(static_cast(const_desc)); if (!(const_desc->badge & kMarkBit)) { to_delete.push_back(obj); } - // Сброс mark-bit для всех объектов const_cast(const_desc)->badge &= ~kMarkBit; } - for (void* obj : to_delete) { + for (auto obj : std::ranges::reverse_view(to_delete)) { auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); - if (!dealloc_res.has_value()) { - // Log error, continue - } } } void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data) { - // Scan global variables for (const auto& var : data.memory.global_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); @@ -86,13 +81,10 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe } } - // Scan stack frames - std::vector temp_frames; - while (!data.memory.stack_frames.empty()) { - temp_frames.push_back(data.memory.stack_frames.top()); - data.memory.stack_frames.pop(); - } - for (const auto& frame : temp_frames) { + std::stack temp_frames = data.memory.stack_frames; + + while (!temp_frames.empty()) { + const StackFrame& frame = temp_frames.top(); for (const auto& var : frame.local_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); @@ -101,17 +93,18 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe } } } - } - for (auto & temp_frame : std::ranges::reverse_view(temp_frames)) { - data.memory.stack_frames.push(temp_frame); + temp_frames.pop(); } - // Scan machine stack std::vector temp_stack; - while (!data.memory.machine_stack.empty()) { - temp_stack.push_back(data.memory.machine_stack.top()); - data.memory.machine_stack.pop(); + temp_stack.reserve(data.memory.machine_stack.size()); + + std::stack temp_machine_stack = data.memory.machine_stack; + while (!temp_machine_stack.empty()) { + temp_stack.push_back(temp_machine_stack.top()); + temp_machine_stack.pop(); } + for (const auto& var : temp_stack) { if (std::holds_alternative(var)) { void* ptr = std::get(var); @@ -120,9 +113,6 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe } } } - for (auto & it : std::ranges::reverse_view(temp_stack)) { - data.memory.machine_stack.push(it); - } } } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/MarkAndSweepGC.hpp b/lib/runtime/gc/MarkAndSweepGC.hpp index 2bcaf7c..9ba9d11 100644 --- a/lib/runtime/gc/MarkAndSweepGC.hpp +++ b/lib/runtime/gc/MarkAndSweepGC.hpp @@ -1,10 +1,10 @@ #ifndef RUNTIME_MARKANDSWEEPGC_HPP #define RUNTIME_MARKANDSWEEPGC_HPP -#include "lib/runtime/gc/IGarbageCollector.hpp" - #include +#include "lib/runtime/gc/IGarbageCollector.hpp" + namespace ovum::vm::runtime { class MarkAndSweepGC : public IGarbageCollector { @@ -19,4 +19,4 @@ class MarkAndSweepGC : public IGarbageCollector { } // namespace ovum::vm::runtime -#endif // RUNTIME_MARKANDSWEEPGC_HPP \ No newline at end of file +#endif // RUNTIME_MARKANDSWEEPGC_HPP From 64a1642d4b132b8dd9b05b131ee6c0a698ccf5d3 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 17:36:43 +0300 Subject: [PATCH 13/59] refactor: fix codestyle --- lib/execution_tree/PassedExecutionData.hpp | 2 +- lib/runtime/ByteArray.cpp | 2 ++ lib/runtime/MemoryManager.cpp | 2 ++ lib/runtime/MemoryManager.hpp | 2 +- lib/runtime/StackFrame.hpp | 1 + lib/runtime/VariableAccessor.hpp | 1 + lib/runtime/VirtualTable.cpp | 4 +++- lib/runtime/VirtualTable.hpp | 3 ++- lib/runtime/VirtualTableRepository.cpp | 2 -- lib/runtime/gc/IGarbageCollector.hpp | 6 ++++-- lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp | 5 +++-- .../gc/reference_scanners/DefaultReferenceScanner.hpp | 6 ++++-- lib/vm_ui/vm_ui_functions.cpp | 3 +-- 13 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/execution_tree/PassedExecutionData.hpp b/lib/execution_tree/PassedExecutionData.hpp index 2fed1c5..6abe089 100644 --- a/lib/execution_tree/PassedExecutionData.hpp +++ b/lib/execution_tree/PassedExecutionData.hpp @@ -5,9 +5,9 @@ #include #include +#include "lib/runtime/MemoryManager.hpp" #include "lib/runtime/RuntimeMemory.hpp" #include "lib/runtime/VirtualTableRepository.hpp" -#include "lib/runtime/MemoryManager.hpp" namespace ovum::vm::execution_tree { diff --git a/lib/runtime/ByteArray.cpp b/lib/runtime/ByteArray.cpp index 5d41df4..1718086 100644 --- a/lib/runtime/ByteArray.cpp +++ b/lib/runtime/ByteArray.cpp @@ -102,6 +102,7 @@ void ByteArray::Insert(size_t index, const uint8_t* data, size_t count) { if (index > size_) { throw std::runtime_error("ByteArray: Insert index out of bounds"); } + if (data == nullptr && count > 0) { throw std::runtime_error("ByteArray: Insert data is null"); } @@ -125,6 +126,7 @@ void ByteArray::Remove(size_t index, size_t count) { if (index >= size_) { throw std::runtime_error("ByteArray: Remove index out of bounds"); } + if (index + count > size_) { throw std::runtime_error("ByteArray: Remove count exceeds available elements"); } diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 0ce7d6a..90d7613 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -66,8 +66,10 @@ std::expected MemoryManager::DeallocateObject(void* ob const VirtualTable* vt = vt_res.value(); auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); + if (dtor_id_res.has_value()) { auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (func_res.has_value()) { runtime::StackFrame frame; frame.local_variables.emplace_back(obj); diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp index 42ea7ad..a60af49 100644 --- a/lib/runtime/MemoryManager.hpp +++ b/lib/runtime/MemoryManager.hpp @@ -13,7 +13,7 @@ namespace ovum::vm::execution_tree { struct PassedExecutionData; -} +} // namespace ovum::vm::execution_tree namespace ovum::vm::runtime { diff --git a/lib/runtime/StackFrame.hpp b/lib/runtime/StackFrame.hpp index f1f9dd9..731e289 100644 --- a/lib/runtime/StackFrame.hpp +++ b/lib/runtime/StackFrame.hpp @@ -14,4 +14,5 @@ struct StackFrame { }; } // namespace ovum::vm::runtime + #endif // RUNTIME_STACKFRAME_HPP diff --git a/lib/runtime/VariableAccessor.hpp b/lib/runtime/VariableAccessor.hpp index d94315c..6a8fd71 100644 --- a/lib/runtime/VariableAccessor.hpp +++ b/lib/runtime/VariableAccessor.hpp @@ -22,6 +22,7 @@ class VariableAccessor : public IVariableAccessor { } *reinterpret_cast(value_ptr) = std::get(variable); + return {}; } diff --git a/lib/runtime/VirtualTable.cpp b/lib/runtime/VirtualTable.cpp index b920767..c468cda 100644 --- a/lib/runtime/VirtualTable.cpp +++ b/lib/runtime/VirtualTable.cpp @@ -2,9 +2,10 @@ #include -#include "VariableAccessor.hpp" #include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" +#include "VariableAccessor.hpp" + namespace ovum::vm::runtime { const std::unordered_map> VirtualTable::kVariableAccessorsByTypeName = { @@ -94,6 +95,7 @@ bool VirtualTable::IsFieldReferenceType(size_t index) const { if (index >= fields_.size()) { return false; } + return fields_[index].variable_accessor->IsReferenceType(); } diff --git a/lib/runtime/VirtualTable.hpp b/lib/runtime/VirtualTable.hpp index a5e3efe..73c97b0 100644 --- a/lib/runtime/VirtualTable.hpp +++ b/lib/runtime/VirtualTable.hpp @@ -8,11 +8,12 @@ #include #include +#include "lib/runtime/gc/reference_scanners/IReferenceScanner.hpp" + #include "FieldInfo.hpp" #include "FunctionId.hpp" #include "IVariableAccessor.hpp" #include "Variable.hpp" -#include "lib/runtime/gc/reference_scanners/IReferenceScanner.hpp" namespace ovum::vm::runtime { diff --git a/lib/runtime/VirtualTableRepository.cpp b/lib/runtime/VirtualTableRepository.cpp index cee0b7c..b9f7d7b 100644 --- a/lib/runtime/VirtualTableRepository.cpp +++ b/lib/runtime/VirtualTableRepository.cpp @@ -38,8 +38,6 @@ std::expected VirtualTableRepository::G } std::expected VirtualTableRepository::GetByName(const std::string& name) { - const auto it = index_by_name_.find(name); - if (index_by_name_.find(name) == index_by_name_.end()) { return std::unexpected(std::runtime_error("VirtualTable not found by name: " + name)); } diff --git a/lib/runtime/gc/IGarbageCollector.hpp b/lib/runtime/gc/IGarbageCollector.hpp index ca31f7c..de78c81 100644 --- a/lib/runtime/gc/IGarbageCollector.hpp +++ b/lib/runtime/gc/IGarbageCollector.hpp @@ -4,11 +4,13 @@ #include #include -namespace ovum::vm::execution_tree { struct PassedExecutionData; } // Forward declaration +namespace ovum::vm::execution_tree { +struct PassedExecutionData; +} // namespace ovum::vm::execution_tree namespace ovum::vm::runtime { -constexpr uint32_t kMarkBit = 1U; // Mark bit in badge +constexpr uint32_t kMarkBit = 1U; class IGarbageCollector { // NOLINT(cppcoreguidelines-special-member-functions) public: diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index 343d683..c3ef253 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -3,12 +3,13 @@ #include +#include "lib/runtime/ObjectDescriptor.hpp" + #include "IReferenceScanner.hpp" -#include "lib/runtime/ObjectDescriptor.hpp" // Для GetDataPointer namespace ovum::vm::runtime { -template +template class ArrayReferenceScanner : public IReferenceScanner { public: void Scan(void* obj, const ReferenceVisitor& visitor) const override { diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 0ce3edc..0cdffe6 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -1,14 +1,16 @@ #ifndef RUNTIME_DEFAULTREFERENCESCANNER_HPP #define RUNTIME_DEFAULTREFERENCESCANNER_HPP -#include "IReferenceScanner.hpp" #include "lib/runtime/VirtualTable.hpp" +#include "IReferenceScanner.hpp" + namespace ovum::vm::runtime { class DefaultReferenceScanner : public IReferenceScanner { public: - explicit DefaultReferenceScanner(const VirtualTable* vt) : vt_(vt) {} + explicit DefaultReferenceScanner(const VirtualTable* vt) : vt_(vt) { + } void Scan(void* obj, const ReferenceVisitor& visitor) const override { for (size_t i = 0; i < vt_->GetFieldCount(); ++i) { diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index c3805a8..748b239 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -152,8 +152,7 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out auto clear_result = memory_manager.Clear(execution_data); if (!clear_result.has_value()) { - err << "Warning: Failed to clean up objects during shutdown: " - << clear_result.error().what() << "\n"; + err << "Warning: Failed to clean up objects during shutdown: " << clear_result.error().what() << "\n"; return_code = 4; } From 4dc2852bf24f6b15c5f1215c5d21382d090351ad Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Mon, 5 Jan 2026 18:41:26 +0300 Subject: [PATCH 14/59] test: changed allocation methods --- tests/builtin_functions_tests.cpp | 4 +-- tests/bytecode_commands_tests.cpp | 5 ++- tests/test_suites/BuiltinTestSuite.cpp | 49 ++++++++++++-------------- tests/test_suites/BuiltinTestSuite.hpp | 2 +- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/tests/builtin_functions_tests.cpp b/tests/builtin_functions_tests.cpp index 7826e44..cd66d9a 100644 --- a/tests/builtin_functions_tests.cpp +++ b/tests/builtin_functions_tests.cpp @@ -27,8 +27,8 @@ void* AllocateObjectByName(BuiltinTestSuite& suite, const std::string& vtable_na if (!idx.has_value()) { return nullptr; } - auto obj_result = ovum::vm::runtime::AllocateObject( - *vt.value(), static_cast(idx.value()), suite.memory_.object_repository, suite.allocator_); + auto obj_result = suite.memory_manager_.AllocateObject( + *vt.value(), static_cast(idx.value()), suite.data_); EXPECT_TRUE(obj_result.has_value()) << "Allocation failed for " << vtable_name; if (!obj_result.has_value()) { return nullptr; diff --git a/tests/bytecode_commands_tests.cpp b/tests/bytecode_commands_tests.cpp index 8e56a38..35b1c15 100644 --- a/tests/bytecode_commands_tests.cpp +++ b/tests/bytecode_commands_tests.cpp @@ -857,10 +857,9 @@ TEST_F(BuiltinTestSuite, CallVirtualConstructorAndFields) { (void) func_idx; (void) destructor_idx; - auto obj_res = ovum::vm::runtime::AllocateObject(*vtable_repo_.GetByIndex(vt_index.value()).value(), + auto obj_res = memory_manager_.AllocateObject(*vtable_repo_.GetByIndex(vt_index.value()).value(), static_cast(vt_index.value()), - memory_.object_repository, - allocator_); + data_); ASSERT_TRUE(obj_res.has_value()); void* obj = obj_res.value(); diff --git a/tests/test_suites/BuiltinTestSuite.cpp b/tests/test_suites/BuiltinTestSuite.cpp index d403a6a..dadaad2 100644 --- a/tests/test_suites/BuiltinTestSuite.cpp +++ b/tests/test_suites/BuiltinTestSuite.cpp @@ -1,14 +1,13 @@ #include "BuiltinTestSuite.hpp" #include -#include #include "lib/execution_tree/ExecutionResult.hpp" -#include "lib/execution_tree/Function.hpp" #include "lib/execution_tree/command_factory.hpp" #include "lib/executor/BuiltinFunctions.hpp" #include "lib/executor/builtin_factory.hpp" #include "lib/runtime/ByteArray.hpp" +#include "lib/runtime/MemoryManager.hpp" #include "lib/runtime/ObjectDescriptor.hpp" using ovum::vm::execution_tree::CreateBooleanCommandByName; @@ -17,17 +16,15 @@ using ovum::vm::execution_tree::CreateIntegerCommandByName; using ovum::vm::execution_tree::CreateSimpleCommandByName; using ovum::vm::execution_tree::CreateStringCommandByName; using ovum::vm::execution_tree::ExecutionResult; -using ovum::vm::execution_tree::Function; using ovum::vm::execution_tree::IExecutable; using ovum::vm::execution_tree::PassedExecutionData; -using ovum::vm::runtime::AllocateObject; using ovum::vm::runtime::GetDataPointer; using ovum::vm::runtime::ObjectDescriptor; using ovum::vm::runtime::StackFrame; using ovum::vm::runtime::Variable; BuiltinTestSuite::BuiltinTestSuite() : - data_(memory_, vtable_repo_, function_repo_, allocator_, input_stream_, output_stream_, error_stream_) { + data_(memory_, vtable_repo_, function_repo_, memory_manager_, input_stream_, output_stream_, error_stream_) { } void BuiltinTestSuite::SetUp() { @@ -43,13 +40,15 @@ void BuiltinTestSuite::SetUp() { } void BuiltinTestSuite::TearDown() { - CleanupObjects(); while (!memory_.machine_stack.empty()) { memory_.machine_stack.pop(); } while (!memory_.stack_frames.empty()) { memory_.stack_frames.pop(); } + + auto clear_result = memory_manager_.Clear(data_); + ASSERT_TRUE(clear_result.has_value()) << clear_result.error().what(); } std::unique_ptr BuiltinTestSuite::MakeSimple(const std::string& name) { @@ -91,6 +90,7 @@ std::unique_ptr BuiltinTestSuite::MakeFloatCmd(const std::string& n std::unique_ptr BuiltinTestSuite::MakeBoolCmd(const std::string& name, bool arg) { auto cmd = CreateBooleanCommandByName(name, arg); EXPECT_TRUE(cmd.has_value()) << "Boolean command not found: " << name; + if (!cmd.has_value()) { return nullptr; } @@ -106,12 +106,12 @@ void* BuiltinTestSuite::MakeString(const std::string& value) { if (!idx.has_value()) { return nullptr; } - auto obj_result = - AllocateObject(*vt.value(), static_cast(idx.value()), memory_.object_repository, data_.allocator); - if (!obj_result.has_value()) { + + auto obj_res = memory_manager_.AllocateObject(*vt.value(), static_cast(idx.value()), data_); + if (!obj_res.has_value()) return nullptr; - } - void* obj = obj_result.value(); + + void* obj = obj_res.value(); auto* str_ptr = GetDataPointer(obj); new (str_ptr) std::string(value); return obj; @@ -126,13 +126,12 @@ void* BuiltinTestSuite::MakeNullable(void* wrapped) { if (!idx.has_value()) { return nullptr; } - auto obj_result = - AllocateObject(*vt.value(), static_cast(idx.value()), memory_.object_repository, data_.allocator); - if (!obj_result.has_value()) { + + auto obj_res = memory_manager_.AllocateObject(*vt.value(), static_cast(idx.value()), data_); + if (!obj_res.has_value()) return nullptr; - } - void* obj = obj_result.value(); + void* obj = obj_res.value(); auto* nullable_ptr = GetDataPointer(obj); *nullable_ptr = wrapped; return obj; @@ -148,12 +147,11 @@ void* BuiltinTestSuite::MakeStringArray(const std::vector& values) return nullptr; } - auto obj_result = - AllocateObject(*vt.value(), static_cast(idx.value()), memory_.object_repository, data_.allocator); - if (!obj_result.has_value()) { + auto obj_res = memory_manager_.AllocateObject(*vt.value(), static_cast(idx.value()), data_); + if (!obj_res.has_value()) return nullptr; - } - void* obj = obj_result.value(); + + void* obj = obj_res.value(); auto* vec_ptr = GetDataPointer>(obj); new (vec_ptr) std::vector(); for (const auto& val : values) { @@ -172,13 +170,12 @@ void* BuiltinTestSuite::MakeByteArray(const std::vector& values) { return nullptr; } - auto obj_result = - AllocateObject(*vt.value(), static_cast(idx.value()), memory_.object_repository, data_.allocator); - if (!obj_result.has_value()) { + auto obj_res = memory_manager_.AllocateObject(*vt.value(), static_cast(idx.value()), data_); + if (!obj_res.has_value()) { return nullptr; } - void* obj = obj_result.value(); + void* obj = obj_res.value(); auto* byte_array = GetDataPointer(obj); new (byte_array) ovum::vm::runtime::ByteArray(values.size()); if (values.size() > 0) { @@ -297,5 +294,5 @@ void BuiltinTestSuite::DestroyObject(void* obj) { data_.memory.machine_stack.emplace(obj); auto destructor_exec_result = destructor_function.value()->Execute(data_); - allocator_.deallocate(reinterpret_cast(obj), vt.value()->GetSize()); + memory_manager_.DeallocateObject(reinterpret_cast(obj), data_); } diff --git a/tests/test_suites/BuiltinTestSuite.hpp b/tests/test_suites/BuiltinTestSuite.hpp index 6b02927..ebcce63 100644 --- a/tests/test_suites/BuiltinTestSuite.hpp +++ b/tests/test_suites/BuiltinTestSuite.hpp @@ -65,7 +65,7 @@ struct BuiltinTestSuite : public testing::Test { std::stringstream input_stream_; std::stringstream output_stream_; std::stringstream error_stream_; - std::allocator allocator_{}; + ovum::vm::runtime::MemoryManager memory_manager_{}; ovum::vm::execution_tree::PassedExecutionData data_; }; From 43c296339bcbd46681b7ddbf35b1d49217f7dc8f Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Tue, 6 Jan 2026 01:59:25 +0300 Subject: [PATCH 15/59] tests: fix codestyle --- tests/builtin_functions_tests.cpp | 3 +-- tests/bytecode_commands_tests.cpp | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/builtin_functions_tests.cpp b/tests/builtin_functions_tests.cpp index cd66d9a..bd1cdd1 100644 --- a/tests/builtin_functions_tests.cpp +++ b/tests/builtin_functions_tests.cpp @@ -27,8 +27,7 @@ void* AllocateObjectByName(BuiltinTestSuite& suite, const std::string& vtable_na if (!idx.has_value()) { return nullptr; } - auto obj_result = suite.memory_manager_.AllocateObject( - *vt.value(), static_cast(idx.value()), suite.data_); + auto obj_result = suite.memory_manager_.AllocateObject(*vt.value(), static_cast(idx.value()), suite.data_); EXPECT_TRUE(obj_result.has_value()) << "Allocation failed for " << vtable_name; if (!obj_result.has_value()) { return nullptr; diff --git a/tests/bytecode_commands_tests.cpp b/tests/bytecode_commands_tests.cpp index 35b1c15..d7495fa 100644 --- a/tests/bytecode_commands_tests.cpp +++ b/tests/bytecode_commands_tests.cpp @@ -857,9 +857,8 @@ TEST_F(BuiltinTestSuite, CallVirtualConstructorAndFields) { (void) func_idx; (void) destructor_idx; - auto obj_res = memory_manager_.AllocateObject(*vtable_repo_.GetByIndex(vt_index.value()).value(), - static_cast(vt_index.value()), - data_); + auto obj_res = memory_manager_.AllocateObject( + *vtable_repo_.GetByIndex(vt_index.value()).value(), static_cast(vt_index.value()), data_); ASSERT_TRUE(obj_res.has_value()); void* obj = obj_res.value(); From d31307a74b6a74d1201e45872ecf8909412b7412 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Wed, 7 Jan 2026 15:59:28 +0300 Subject: [PATCH 16/59] fix: add error handling in memory manager --- lib/runtime/MemoryManager.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 90d7613..9856aa8 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -52,16 +52,29 @@ std::expected MemoryManager::DeallocateObject(void* ob auto* desc = reinterpret_cast(obj); const size_t index = desc->repo_index; + std::string object_type = "Unknown"; + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (vt_res.has_value()) { + object_type = vt_res.value()->GetName(); + } +// data.error_stream << "DeallocateObject: obj=" << obj +// << " repo_index=" << index +// << " vtable_index=" << desc->vtable_index +// << " type=" << object_type << "\n"; + if (index >= repo_.GetCount()) { +// data.error_stream << "ERROR: Invalid repo_index " << index << " (repo size=" << repo_.GetCount() +// << ") for object of type " << object_type << " at " << obj << "\n"; return std::unexpected(std::runtime_error("DeallocateObject: Invalid repo_index (out of bounds)")); } auto check_res = repo_.GetByIndex(index); if (!check_res.has_value() || check_res.value() != desc) { - return std::unexpected(std::runtime_error("DeallocateObject: repo_index mismatch (corrupted?)")); +// data.error_stream << "ERROR: repo_index mismatch for object " << obj +// << " (type=" << object_type << "), expected at index " << index << "\n"; + return std::unexpected(std::runtime_error("DeallocateObject: repo_index mismatch")); } - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); if (vt_res.has_value()) { const VirtualTable* vt = vt_res.value(); @@ -98,12 +111,18 @@ std::expected MemoryManager::Clear(execution_tree::Pas while (repo_.GetCount() > 0) { const size_t last_index = repo_.GetCount() - 1; auto obj_res = repo_.GetByIndex(last_index); - if (!obj_res.has_value()) { + + if (obj_res.has_value()) { + auto dealloc_res = DeallocateObject(obj_res.value(), data); + if (!dealloc_res.has_value()) { +// data.error_stream << "Clear: Deallocate failed for index " << last_index << ": " +// << dealloc_res.error().what() << " — forcing Remove\n"; + repo_.Remove(last_index); + } + } else { +// data.error_stream << "Clear: Invalid object at index " << last_index << " — forcing Remove\n"; repo_.Remove(last_index); - continue; } - - DeallocateObject(obj_res.value(), data); } return {}; From 6b68425cdd9455949c5c05d7ac0981e2cd8394f4 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Wed, 7 Jan 2026 16:08:09 +0300 Subject: [PATCH 17/59] fix: add include --- lib/runtime/gc/IGarbageCollector.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/runtime/gc/IGarbageCollector.hpp b/lib/runtime/gc/IGarbageCollector.hpp index de78c81..def4507 100644 --- a/lib/runtime/gc/IGarbageCollector.hpp +++ b/lib/runtime/gc/IGarbageCollector.hpp @@ -1,6 +1,7 @@ #ifndef RUNTIME_GARBAGECOLLECTOR_HPP #define RUNTIME_GARBAGECOLLECTOR_HPP +#include #include #include From cf5b6d034dd87bef9961b418a2aae903e14c48d8 Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 19:02:03 +0300 Subject: [PATCH 18/59] fix: improve memory management and object repository handling --- lib/runtime/MemoryManager.cpp | 125 +++++++++++++------------ lib/runtime/ObjectDescriptor.hpp | 1 - lib/runtime/ObjectRepository.cpp | 39 ++++---- lib/runtime/ObjectRepository.hpp | 13 ++- lib/runtime/gc/MarkAndSweepGC.cpp | 19 ++-- lib/vm_ui/vm_ui_functions.cpp | 1 - tests/main_test.cpp | 2 +- tests/test_suites/BuiltinTestSuite.cpp | 10 +- 8 files changed, 99 insertions(+), 111 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 9856aa8..de6ca8a 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -1,6 +1,7 @@ #include "MemoryManager.hpp" #include +#include #include "lib/execution_tree/FunctionRepository.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" @@ -17,27 +18,28 @@ MemoryManager::MemoryManager() : gc_threshold_(kDefaultMaxObjects) { std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, execution_tree::PassedExecutionData& data) { - const size_t total_size = sizeof(ObjectDescriptor) + vtable.GetSize(); + const size_t total_size = vtable.GetSize(); char* raw_memory = allocator_.allocate(total_size); if (!raw_memory) { return std::unexpected(std::runtime_error("MemoryManager: Allocation failed - out of memory")); } - auto* descriptor = new (raw_memory) ObjectDescriptor{}; + auto* descriptor = reinterpret_cast(raw_memory); descriptor->vtable_index = vtable_index; descriptor->badge = 0; auto add_result = repo_.Add(descriptor); if (!add_result.has_value()) { - descriptor->~ObjectDescriptor(); allocator_.deallocate(raw_memory, total_size); return std::unexpected(add_result.error()); } - descriptor->repo_index = add_result.value(); - if (repo_.GetCount() > gc_threshold_) { - CollectGarbage(data); + auto collect_res = CollectGarbage(data); + + if (!collect_res.has_value()) { + return std::unexpected(collect_res.error()); + } } return reinterpret_cast(descriptor); @@ -50,82 +52,85 @@ std::expected MemoryManager::DeallocateObject(void* ob } auto* desc = reinterpret_cast(obj); - const size_t index = desc->repo_index; - std::string object_type = "Unknown"; auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); - if (vt_res.has_value()) { - object_type = vt_res.value()->GetName(); - } -// data.error_stream << "DeallocateObject: obj=" << obj -// << " repo_index=" << index -// << " vtable_index=" << desc->vtable_index -// << " type=" << object_type << "\n"; - - if (index >= repo_.GetCount()) { -// data.error_stream << "ERROR: Invalid repo_index " << index << " (repo size=" << repo_.GetCount() -// << ") for object of type " << object_type << " at " << obj << "\n"; - return std::unexpected(std::runtime_error("DeallocateObject: Invalid repo_index (out of bounds)")); + if (!vt_res.has_value()) { + return std::unexpected(std::runtime_error("DeallocateObject: Virtual table not found for index " + + std::to_string(desc->vtable_index))); } - auto check_res = repo_.GetByIndex(index); - if (!check_res.has_value() || check_res.value() != desc) { -// data.error_stream << "ERROR: repo_index mismatch for object " << obj -// << " (type=" << object_type << "), expected at index " << index << "\n"; - return std::unexpected(std::runtime_error("DeallocateObject: repo_index mismatch")); - } + std::string object_type = vt_res.value()->GetName(); - if (vt_res.has_value()) { - const VirtualTable* vt = vt_res.value(); + const VirtualTable* vt = vt_res.value(); - auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); + auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); - if (dtor_id_res.has_value()) { - auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (!dtor_id_res.has_value()) { + return std::unexpected(std::runtime_error("DeallocateObject: Destructor not found for class " + object_type)); + } - if (func_res.has_value()) { - runtime::StackFrame frame; - frame.local_variables.emplace_back(obj); - data.memory.stack_frames.push(std::move(frame)); + auto func_res = data.function_repository.GetById(dtor_id_res.value()); - auto exec_res = func_res.value()->Execute(data); - if (!exec_res.has_value()) { - } + if (!func_res.has_value()) { + return std::unexpected( + std::runtime_error("DeallocateObject: Destructor function not found for class " + object_type)); + } - data.memory.stack_frames.pop(); - } - } + runtime::StackFrame frame = {.function_name = "Object deallocation"}; + data.memory.machine_stack.emplace(obj); + data.memory.stack_frames.push(std::move(frame)); + auto exec_res = func_res.value()->Execute(data); + data.memory.stack_frames.pop(); - const size_t total_size = sizeof(ObjectDescriptor) + vt->GetSize(); - char* raw = reinterpret_cast(obj); - desc->~ObjectDescriptor(); - allocator_.deallocate(raw, total_size); + const size_t total_size = vt->GetSize(); + char* raw = reinterpret_cast(obj); + + // Remove from repository BEFORE deallocating memory + auto remove_res = repo_.Remove(desc); + if (!remove_res.has_value()) { + return std::unexpected(remove_res.error()); } - repo_.Remove(index); + // Now safe to deallocate memory + allocator_.deallocate(raw, total_size); + + if (!exec_res.has_value()) { + return std::unexpected(exec_res.error()); + } return {}; } std::expected MemoryManager::Clear(execution_tree::PassedExecutionData& data) { - while (repo_.GetCount() > 0) { - const size_t last_index = repo_.GetCount() - 1; - auto obj_res = repo_.GetByIndex(last_index); - - if (obj_res.has_value()) { - auto dealloc_res = DeallocateObject(obj_res.value(), data); - if (!dealloc_res.has_value()) { -// data.error_stream << "Clear: Deallocate failed for index " << last_index << ": " -// << dealloc_res.error().what() << " — forcing Remove\n"; - repo_.Remove(last_index); + std::vector objects_to_clear; + objects_to_clear.reserve(repo_.GetCount()); + std::expected clear_res = {}; + + repo_.ForAll([&objects_to_clear](void* obj) { objects_to_clear.push_back(obj); }); + + for (void* obj : objects_to_clear) { + auto* desc = reinterpret_cast(obj); + + auto dealloc_res = DeallocateObject(obj, data); + if (!dealloc_res.has_value()) { + // If deallocation fails, try to remove from repository and deallocate manually + auto remove_res = repo_.Remove(desc); + if (remove_res.has_value()) { + // Try to get vtable to deallocate memory + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (vt_res.has_value()) { + const size_t total_size = vt_res.value()->GetSize(); + char* raw = reinterpret_cast(obj); + allocator_.deallocate(raw, total_size); + } } - } else { -// data.error_stream << "Clear: Invalid object at index " << last_index << " — forcing Remove\n"; - repo_.Remove(last_index); + clear_res = dealloc_res; // Save the error } } - return {}; + repo_.Clear(); + + return clear_res; } std::expected MemoryManager::CollectGarbage(execution_tree::PassedExecutionData& data) { diff --git a/lib/runtime/ObjectDescriptor.hpp b/lib/runtime/ObjectDescriptor.hpp index 06d47be..de8cb36 100644 --- a/lib/runtime/ObjectDescriptor.hpp +++ b/lib/runtime/ObjectDescriptor.hpp @@ -8,7 +8,6 @@ namespace ovum::vm::runtime { struct ObjectDescriptor { uint32_t vtable_index; uint32_t badge; - size_t repo_index; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/ObjectRepository.cpp b/lib/runtime/ObjectRepository.cpp index 0db0ba2..f9ff605 100644 --- a/lib/runtime/ObjectRepository.cpp +++ b/lib/runtime/ObjectRepository.cpp @@ -8,18 +8,25 @@ void ObjectRepository::Reserve(size_t count) { objects_.reserve(count); } -std::expected ObjectRepository::Add(ObjectDescriptor* descriptor) { - objects_.emplace_back(descriptor); - return objects_.size() - 1U; +std::expected ObjectRepository::Add(ObjectDescriptor* descriptor) { + if (!descriptor) { + return std::unexpected(std::runtime_error("ObjectRepository: Cannot add null descriptor")); + } + objects_.insert(descriptor); + return {}; } -std::expected ObjectRepository::Remove(size_t index) { - if (index >= objects_.size()) { - return std::unexpected(std::runtime_error("ObjectDescriptor index out of range")); +std::expected ObjectRepository::Remove(ObjectDescriptor* descriptor) { + if (!descriptor) { + return std::unexpected(std::runtime_error("ObjectRepository: Cannot remove null descriptor")); + } + + auto it = objects_.find(descriptor); + if (it == objects_.end()) { + return std::unexpected(std::runtime_error("ObjectRepository: Descriptor not found")); } - objects_.erase(objects_.begin() + static_cast(index)); - + objects_.erase(it); return {}; } @@ -27,20 +34,10 @@ void ObjectRepository::Clear() { objects_.clear(); } -std::expected ObjectRepository::GetByIndex(size_t index) { - if (index >= objects_.size()) { - return std::unexpected(std::runtime_error("ObjectDescriptor index out of range")); +void ObjectRepository::ForAll(const std::function& func) const { + for (ObjectDescriptor* desc : objects_) { + func(reinterpret_cast(desc)); } - - return objects_[index]; -} - -std::expected ObjectRepository::GetByIndex(size_t index) const { - if (index >= objects_.size()) { - return std::unexpected(std::runtime_error("ObjectDescriptor index out of range")); - } - - return objects_[index]; } size_t ObjectRepository::GetCount() const { diff --git a/lib/runtime/ObjectRepository.hpp b/lib/runtime/ObjectRepository.hpp index e1ed49f..6d66e4e 100644 --- a/lib/runtime/ObjectRepository.hpp +++ b/lib/runtime/ObjectRepository.hpp @@ -3,8 +3,9 @@ #include #include +#include #include -#include +#include #include "ObjectDescriptor.hpp" @@ -16,18 +17,16 @@ class ObjectRepository { void Reserve(size_t count); - [[nodiscard]] std::expected Add(ObjectDescriptor* descriptor); - std::expected Remove(size_t index); + [[nodiscard]] std::expected Add(ObjectDescriptor* descriptor); + std::expected Remove(ObjectDescriptor* descriptor); void Clear(); - [[nodiscard]] std::expected GetByIndex(size_t index); - - [[nodiscard]] std::expected GetByIndex(size_t index) const; + void ForAll(const std::function& func) const; [[nodiscard]] size_t GetCount() const; private: - std::vector objects_; + std::unordered_set objects_; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 9a2f55a..16971ee 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -1,7 +1,6 @@ #include "MarkAndSweepGC.hpp" #include -#include #include #include @@ -50,23 +49,17 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { to_delete.reserve(data.memory_manager.GetRepository().GetCount() / 4); const ObjectRepository& repo = data.memory_manager.GetRepository(); - for (size_t i = 0; i < repo.GetCount(); ++i) { - auto obj_res = repo.GetByIndex(i); - if (!obj_res.has_value()) { - continue; - } - - const ObjectDescriptor* const_desc = obj_res.value(); - void* obj = const_cast(static_cast(const_desc)); + repo.ForAll([&to_delete](void* obj) { + auto* desc = reinterpret_cast(obj); - if (!(const_desc->badge & kMarkBit)) { + if (!(desc->badge & kMarkBit)) { to_delete.push_back(obj); } - const_cast(const_desc)->badge &= ~kMarkBit; - } + desc->badge &= ~kMarkBit; + }); - for (auto obj : std::ranges::reverse_view(to_delete)) { + for (auto obj : to_delete) { auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); } } diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index 748b239..5fc9e60 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -16,7 +16,6 @@ #include "lib/executor/Executor.hpp" #include "lib/executor/IJitExecutorFactory.hpp" #include "lib/executor/builtin_factory.hpp" -#include "lib/runtime/ObjectDescriptor.hpp" #include "lib/runtime/RuntimeMemory.hpp" #include "lib/runtime/VirtualTableRepository.hpp" diff --git a/tests/main_test.cpp b/tests/main_test.cpp index 9ce2568..4c26376 100644 --- a/tests/main_test.cpp +++ b/tests/main_test.cpp @@ -245,5 +245,5 @@ TEST_F(ProjectIntegrationTestSuite, InteropTest3) { int64_t time_from_code = code - kAdded; time_from_code *= kDivisor; time_from_code ^= kBinaryFilter; - ASSERT_TRUE(std::abs(time_from_code - current_nanotime) <= kMaxAllowedError); + ASSERT_LE(std::abs(time_from_code - current_nanotime), kMaxAllowedError); } diff --git a/tests/test_suites/BuiltinTestSuite.cpp b/tests/test_suites/BuiltinTestSuite.cpp index dadaad2..277263e 100644 --- a/tests/test_suites/BuiltinTestSuite.cpp +++ b/tests/test_suites/BuiltinTestSuite.cpp @@ -266,12 +266,7 @@ void BuiltinTestSuite::ExpectTopNullableHasValue(bool has_value) { void BuiltinTestSuite::CleanupObjects() { std::vector objects; - for (size_t i = 0; i < memory_.object_repository.GetCount(); ++i) { - auto obj = memory_.object_repository.GetByIndex(i); - if (obj.has_value()) { - objects.push_back(obj.value()); - } - } + memory_.object_repository.ForAll([&objects](void* obj) { objects.push_back(obj); }); for (void* obj : objects) { DestroyObject(obj); @@ -294,5 +289,6 @@ void BuiltinTestSuite::DestroyObject(void* obj) { data_.memory.machine_stack.emplace(obj); auto destructor_exec_result = destructor_function.value()->Execute(data_); - memory_manager_.DeallocateObject(reinterpret_cast(obj), data_); + auto dealloc_res = memory_manager_.DeallocateObject(obj, data_); + ASSERT_TRUE(dealloc_res.has_value()) << dealloc_res.error().what(); } From a95b9b902c1073606b58f4a7008592f4a16d1ae8 Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 19:09:59 +0300 Subject: [PATCH 19/59] fix: clean up whitespace in ObjectRepository methods --- lib/runtime/ObjectRepository.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/runtime/ObjectRepository.cpp b/lib/runtime/ObjectRepository.cpp index f9ff605..c40c856 100644 --- a/lib/runtime/ObjectRepository.cpp +++ b/lib/runtime/ObjectRepository.cpp @@ -12,6 +12,7 @@ std::expected ObjectRepository::Add(ObjectDescriptor* if (!descriptor) { return std::unexpected(std::runtime_error("ObjectRepository: Cannot add null descriptor")); } + objects_.insert(descriptor); return {}; } @@ -20,7 +21,7 @@ std::expected ObjectRepository::Remove(ObjectDescripto if (!descriptor) { return std::unexpected(std::runtime_error("ObjectRepository: Cannot remove null descriptor")); } - + auto it = objects_.find(descriptor); if (it == objects_.end()) { return std::unexpected(std::runtime_error("ObjectRepository: Descriptor not found")); From 02086e2180888a43a4c36b0901ffa252eabb7c62 Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 19:27:36 +0300 Subject: [PATCH 20/59] fix: enhance memory usage tracking with platform-specific implementations --- lib/execution_tree/BytecodeCommands.cpp | 45 +++++++++++++++++++------ lib/execution_tree/CMakeLists.txt | 4 +++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/execution_tree/BytecodeCommands.cpp b/lib/execution_tree/BytecodeCommands.cpp index 1080547..5b668ce 100644 --- a/lib/execution_tree/BytecodeCommands.cpp +++ b/lib/execution_tree/BytecodeCommands.cpp @@ -1,6 +1,7 @@ #include "BytecodeCommands.hpp" #include +#include #include #include #include @@ -19,8 +20,17 @@ #ifdef _WIN32 #include + +#include +#elif __APPLE__ +#include +#include + +#include +#include #else #include +#include #include #endif @@ -2060,20 +2070,35 @@ std::expected SeedRandom(PassedExecutionDat } std::expected GetMemoryUsage(PassedExecutionData& data) { - // Simple memory usage approximation size_t memory_usage = 0; - // Stack size - memory_usage += data.memory.machine_stack.size() * sizeof(runtime::Variable); - - auto stack_copy = data.memory.stack_frames; - // Local variables in all stack frames - while (!stack_copy.empty()) { - memory_usage += stack_copy.top().local_variables.size() * sizeof(runtime::Variable); - stack_copy.pop(); +#ifdef _WIN32 + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + memory_usage = static_cast(pmc.WorkingSetSize); + } else { + return std::unexpected(std::runtime_error("GetMemoryUsage: failed to get process memory info")); + } +#elif __APPLE__ + struct task_basic_info info; + mach_msg_type_number_t size = sizeof(info); + kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast(&info), &size); + if (kerr == KERN_SUCCESS) { + memory_usage = static_cast(info.resident_size); + } else { + return std::unexpected(std::runtime_error("GetMemoryUsage: failed to get task memory info")); + } +#else + // Linux: Use getrusage() system call + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) != 0) { + return std::unexpected(std::runtime_error("GetMemoryUsage: getrusage() failed")); } - // TODO counter for repositories + // ru_maxrss is the maximum resident set size in kilobytes + // Note: getrusage gives maximum usage, not current usage + memory_usage = static_cast(usage.ru_maxrss) * 1024; // Convert KB to bytes +#endif data.memory.machine_stack.emplace(static_cast(memory_usage)); return ExecutionResult::kNormal; diff --git a/lib/execution_tree/CMakeLists.txt b/lib/execution_tree/CMakeLists.txt index 843bad0..7eb5096 100644 --- a/lib/execution_tree/CMakeLists.txt +++ b/lib/execution_tree/CMakeLists.txt @@ -13,3 +13,7 @@ add_library(execution_tree STATIC target_include_directories(execution_tree PUBLIC ${PROJECT_SOURCE_DIR}) target_link_libraries(execution_tree PUBLIC runtime) + +if(WIN32) + target_link_libraries(execution_tree PRIVATE psapi) +endif() From 28152ea38cb9ac6747115a45d132ca7c33499cfc Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 19:56:14 +0300 Subject: [PATCH 21/59] chore: update subproject commit reference in test examples --- tests/test_data/examples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_data/examples b/tests/test_data/examples index 7c3c96c..294d708 160000 --- a/tests/test_data/examples +++ b/tests/test_data/examples @@ -1 +1 @@ -Subproject commit 7c3c96c065f2e1904f66b3f2d5c926863fb2a201 +Subproject commit 294d70868cdeba0764dc1d397f5340afaa2a9026 From 9d0b55e63acd0eb5872c69a4d936fa63fbb3230a Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 20:06:31 +0300 Subject: [PATCH 22/59] refactor: update MemoryManager to accept custom garbage collector and max object threshold --- lib/runtime/MemoryManager.cpp | 6 ++---- lib/runtime/MemoryManager.hpp | 7 ++----- lib/vm_ui/vm_ui_functions.cpp | 5 ++++- tests/test_suites/BuiltinTestSuite.hpp | 6 +++++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index de6ca8a..0846389 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -5,14 +5,12 @@ #include "lib/execution_tree/FunctionRepository.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" -#include "lib/runtime/gc/MarkAndSweepGC.hpp" #include "ObjectDescriptor.hpp" namespace ovum::vm::runtime { -MemoryManager::MemoryManager() : gc_threshold_(kDefaultMaxObjects) { - gc_ = std::make_unique(); +MemoryManager::MemoryManager(std::unique_ptr gc, size_t max_objects) : gc_(std::move(gc)), gc_threshold_(max_objects) { } std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, @@ -138,7 +136,7 @@ std::expected MemoryManager::CollectGarbage(execution_ return std::unexpected(std::runtime_error("MemoryManager: No GC configured")); } - return gc_.value()->Collect(data); + return gc_->Collect(data); } const ObjectRepository& MemoryManager::GetRepository() const { diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp index a60af49..527fd85 100644 --- a/lib/runtime/MemoryManager.hpp +++ b/lib/runtime/MemoryManager.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include "lib/runtime/gc/IGarbageCollector.hpp" @@ -17,11 +16,9 @@ struct PassedExecutionData; namespace ovum::vm::runtime { -constexpr size_t kDefaultMaxObjects = 10000; // Default threshold for auto-GC - class MemoryManager { public: - MemoryManager(); + MemoryManager(std::unique_ptr gc, size_t max_objects); std::expected AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, @@ -35,7 +32,7 @@ class MemoryManager { private: ObjectRepository repo_; std::allocator allocator_; - std::optional> gc_; + std::unique_ptr gc_; size_t gc_threshold_; }; diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index 5fc9e60..34165e6 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -18,12 +18,14 @@ #include "lib/executor/builtin_factory.hpp" #include "lib/runtime/RuntimeMemory.hpp" #include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/gc/MarkAndSweepGC.hpp" #ifdef JIT_PROVIDED #include #endif constexpr size_t kDefaultJitBoundary = 100000; +constexpr size_t kDefaultMaxObjects = 10000; std::string ReadFileContent(const std::string& file_path, std::ostream& err) { std::ifstream file(file_path); @@ -85,7 +87,8 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out ovum::vm::execution_tree::FunctionRepository func_repo; ovum::vm::runtime::VirtualTableRepository vtable_repo; ovum::vm::runtime::RuntimeMemory memory; - ovum::vm::runtime::MemoryManager memory_manager; + ovum::vm::runtime::MemoryManager memory_manager(std::make_unique(), + kDefaultMaxObjects); ovum::vm::execution_tree::PassedExecutionData execution_data{.memory = memory, .virtual_table_repository = vtable_repo, .function_repository = func_repo, diff --git a/tests/test_suites/BuiltinTestSuite.hpp b/tests/test_suites/BuiltinTestSuite.hpp index ebcce63..ebb890c 100644 --- a/tests/test_suites/BuiltinTestSuite.hpp +++ b/tests/test_suites/BuiltinTestSuite.hpp @@ -15,6 +15,7 @@ #include "lib/execution_tree/PassedExecutionData.hpp" #include "lib/runtime/RuntimeMemory.hpp" #include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/gc/MarkAndSweepGC.hpp" struct BuiltinTestSuite : public testing::Test { BuiltinTestSuite(); @@ -58,6 +59,8 @@ struct BuiltinTestSuite : public testing::Test { void CleanupObjects(); void DestroyObject(void* obj); + constexpr static size_t kDefaultMaxObjects = 10000; + // Repositories for fixtures ovum::vm::runtime::RuntimeMemory memory_{}; ovum::vm::runtime::VirtualTableRepository vtable_repo_{}; @@ -65,7 +68,8 @@ struct BuiltinTestSuite : public testing::Test { std::stringstream input_stream_; std::stringstream output_stream_; std::stringstream error_stream_; - ovum::vm::runtime::MemoryManager memory_manager_{}; + ovum::vm::runtime::MemoryManager memory_manager_{std::make_unique(), + kDefaultMaxObjects}; ovum::vm::execution_tree::PassedExecutionData data_; }; From 17e529d5ff1d4391e290e96e4fbc572fbe378451 Mon Sep 17 00:00:00 2001 From: bialger Date: Wed, 7 Jan 2026 20:22:43 +0300 Subject: [PATCH 23/59] feat: add max-objects argument to VM console UI and update MemoryManager initialization --- lib/runtime/MemoryManager.cpp | 3 ++- lib/vm_ui/vm_ui_functions.cpp | 6 ++++-- tests/main_test.cpp | 26 +++++++++++++++----------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 0846389..8d37d75 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -10,7 +10,8 @@ namespace ovum::vm::runtime { -MemoryManager::MemoryManager(std::unique_ptr gc, size_t max_objects) : gc_(std::move(gc)), gc_threshold_(max_objects) { +MemoryManager::MemoryManager(std::unique_ptr gc, size_t max_objects) : + gc_(std::move(gc)), gc_threshold_(max_objects) { } std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, diff --git a/lib/vm_ui/vm_ui_functions.cpp b/lib/vm_ui/vm_ui_functions.cpp index 34165e6..47f8614 100644 --- a/lib/vm_ui/vm_ui_functions.cpp +++ b/lib/vm_ui/vm_ui_functions.cpp @@ -59,6 +59,8 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out ArgumentParser::ArgParser arg_parser("ovum-vm", PassArgumentTypes()); arg_parser.AddCompositeArgument('f', "file", "Path to the bytecode file").AddIsGood(is_file).AddValidate(is_file); arg_parser.AddUnsignedLongLongArgument('j', "jit-boundary", "JIT compilation boundary").Default(kDefaultJitBoundary); + arg_parser.AddUnsignedLongLongArgument('m', "max-objects", "Maximum number of objects to keep in memory") + .Default(kDefaultMaxObjects); arg_parser.AddHelp('h', "help", "Show this help message"); bool parse_result = arg_parser.Parse(parser_args, {.out_stream = err, .print_messages = true}); @@ -76,6 +78,7 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out file_path = arg_parser.GetCompositeValue("file"); size_t jit_boundary = arg_parser.GetUnsignedLongLongValue("jit-boundary"); + size_t max_objects = arg_parser.GetUnsignedLongLongValue("max-objects"); std::string sample = ReadFileContent(file_path, err); if (sample.empty()) { @@ -87,8 +90,7 @@ int32_t StartVmConsoleUI(const std::vector& args, std::ostream& out ovum::vm::execution_tree::FunctionRepository func_repo; ovum::vm::runtime::VirtualTableRepository vtable_repo; ovum::vm::runtime::RuntimeMemory memory; - ovum::vm::runtime::MemoryManager memory_manager(std::make_unique(), - kDefaultMaxObjects); + ovum::vm::runtime::MemoryManager memory_manager(std::make_unique(), max_objects); ovum::vm::execution_tree::PassedExecutionData execution_data{.memory = memory, .virtual_table_repository = vtable_repo, .function_repository = func_repo, diff --git a/tests/main_test.cpp b/tests/main_test.cpp index 4c26376..7b35b5f 100644 --- a/tests/main_test.cpp +++ b/tests/main_test.cpp @@ -20,12 +20,14 @@ TEST_F(ProjectIntegrationTestSuite, NegitiveOutputTest1) { std::istringstream in; std::ostringstream err; StartVmConsoleUI(SplitString("test"), out, in, err); - ASSERT_EQ(err.str(), - "Not enough values were passed to argument --file.\n" - "ovum-vm\nShow this help message\n\nOPTIONS:\n" - "-f, --file=: Path to the bytecode file\n" - "-j, --jit-boundary=: JIT compilation boundary [default = 100000]\n\n" - "-h, --help: Display this help and exit\n\n"); + ASSERT_EQ( + err.str(), + "Not enough values were passed to argument --file.\n" + "ovum-vm\nShow this help message\n\nOPTIONS:\n" + "-f, --file=: Path to the bytecode file\n" + "-j, --jit-boundary=: JIT compilation boundary [default = 100000]\n" + "-m, --max-objects=: Maximum number of objects to keep in memory [default = 10000]\n\n" + "-h, --help: Display this help and exit\n\n"); } TEST_F(ProjectIntegrationTestSuite, HelpTest) { @@ -33,11 +35,13 @@ TEST_F(ProjectIntegrationTestSuite, HelpTest) { std::istringstream in; std::ostringstream err; StartVmConsoleUI(SplitString("test --help"), out, in, err); - ASSERT_EQ(err.str(), - "ovum-vm\nShow this help message\n\nOPTIONS:\n" - "-f, --file=: Path to the bytecode file\n" - "-j, --jit-boundary=: JIT compilation boundary [default = 100000]\n\n" - "-h, --help: Display this help and exit\n\n"); + ASSERT_EQ( + err.str(), + "ovum-vm\nShow this help message\n\nOPTIONS:\n" + "-f, --file=: Path to the bytecode file\n" + "-j, --jit-boundary=: JIT compilation boundary [default = 100000]\n" + "-m, --max-objects=: Maximum number of objects to keep in memory [default = 10000]\n\n" + "-h, --help: Display this help and exit\n\n"); } TEST_F(ProjectIntegrationTestSuite, FibTest1) { From 06843beea512a6ebb60f1ab64390c597b1a3ab38 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 03:40:45 +0300 Subject: [PATCH 24/59] feat: add getters to field information --- lib/runtime/VirtualTable.cpp | 11 ++++++++--- lib/runtime/VirtualTable.hpp | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/runtime/VirtualTable.cpp b/lib/runtime/VirtualTable.cpp index c468cda..6339a53 100644 --- a/lib/runtime/VirtualTable.cpp +++ b/lib/runtime/VirtualTable.cpp @@ -2,8 +2,6 @@ #include -#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" - #include "VariableAccessor.hpp" namespace ovum::vm::runtime { @@ -18,7 +16,6 @@ const std::unordered_map> Virtua }; VirtualTable::VirtualTable(std::string name, size_t size) : name_(std::move(name)), size_(size) { - reference_scanner_ = std::make_unique(this); } std::string VirtualTable::GetName() const { @@ -91,6 +88,14 @@ size_t VirtualTable::GetFieldCount() const { return fields_.size(); } +int64_t VirtualTable::GetFieldOffset(size_t index) const { + return fields_[index].offset; +} + +std::shared_ptr VirtualTable::GetFieldAccessor(size_t index) const { + return fields_[index].variable_accessor; +} + bool VirtualTable::IsFieldReferenceType(size_t index) const { if (index >= fields_.size()) { return false; diff --git a/lib/runtime/VirtualTable.hpp b/lib/runtime/VirtualTable.hpp index 73c97b0..329a116 100644 --- a/lib/runtime/VirtualTable.hpp +++ b/lib/runtime/VirtualTable.hpp @@ -34,6 +34,8 @@ class VirtualTable { [[nodiscard]] size_t GetFieldCount() const; [[nodiscard]] bool IsFieldReferenceType(size_t index) const; + [[nodiscard]] int64_t GetFieldOffset(size_t index) const; + [[nodiscard]] std::shared_ptr GetFieldAccessor(size_t index) const; void AddFunction(const FunctionId& virtual_function_id, const FunctionId& real_function_id); size_t AddField(const std::string& type_name, int64_t offset); From fec7bca7670fe12466e794782a4a552569d16bb7 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 03:41:10 +0300 Subject: [PATCH 25/59] feat: change constructor and scanning logic --- .../DefaultReferenceScanner.hpp | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 0cdffe6..96df51b 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -9,25 +9,34 @@ namespace ovum::vm::runtime { class DefaultReferenceScanner : public IReferenceScanner { public: - explicit DefaultReferenceScanner(const VirtualTable* vt) : vt_(vt) { + explicit DefaultReferenceScanner(const VirtualTable& vt) { + for (size_t i = 0; i < vt.GetFieldCount(); ++i) { + if (vt.IsFieldReferenceType(i)) { + reference_fields_.emplace_back( + vt.GetFieldOffset(i), + vt.GetFieldAccessor(i) + ); + } + } } void Scan(void* obj, const ReferenceVisitor& visitor) const override { - for (size_t i = 0; i < vt_->GetFieldCount(); ++i) { - if (vt_->IsFieldReferenceType(i)) { - auto var_res = vt_->GetVariableByIndex(obj, i); - if (var_res.has_value() && std::holds_alternative(var_res.value())) { - void* ptr = std::get(var_res.value()); - if (ptr) { - visitor(ptr); - } + for (const FieldInfo& field : reference_fields_) { + auto var_res = field.variable_accessor->GetVariable( + reinterpret_cast(obj) + field.offset + ); + + if (std::holds_alternative(var_res)) { + void* ptr = std::get(var_res); + if (ptr) { + visitor(ptr); } } } } private: - const VirtualTable* vt_; + std::vector reference_fields_; }; } // namespace ovum::vm::runtime From a97f6be54b0aa12d8ff7ba7e735d0b554429cd5d Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 03:42:06 +0300 Subject: [PATCH 26/59] fix: change gc trigger and allocation --- lib/runtime/gc/MarkAndSweepGC.cpp | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 16971ee..faeda62 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -59,18 +59,51 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { desc->badge &= ~kMarkBit; }); + data.error_stream << "[GC Sweep] Starting sweep. Total objects in repo: " + << repo.GetCount() + << ", objects to delete: " << to_delete.size() << "\n"; + + for (auto obj : to_delete) { + auto* desc = reinterpret_cast(obj); + + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string type_name = ""; + if (vt_res.has_value()) { + type_name = vt_res.value()->GetName(); + } + + data.error_stream << "[GC Sweep] Deleting object: " + << type_name + << " at address " << obj << "\n"; + } + for (auto obj : to_delete) { auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); + if (!dealloc_res) { + data.error_stream << dealloc_res.error().what(); + } } } void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data) { + data.error_stream << "[GC Roots] Scanning " << data.memory.stack_frames.size() << " frames\n"; + size_t root_count = 0; + for (const auto& var : data.memory.global_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); if (ptr) { worklist.push(ptr); } + + if (ptr) { + root_count++; + auto* desc = reinterpret_cast(ptr); + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; + data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; + } + data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } @@ -84,6 +117,15 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe if (ptr) { worklist.push(ptr); } + + if (ptr) { + root_count++; + auto* desc = reinterpret_cast(ptr); + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; + data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; + } + data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } temp_frames.pop(); @@ -104,6 +146,15 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe if (ptr) { worklist.push(ptr); } + + if (ptr) { + root_count++; + auto* desc = reinterpret_cast(ptr); + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; + data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; + } + data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } } From ea0f7321db5a2cedd42ba44943fcde0007b10bd9 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 03:42:58 +0300 Subject: [PATCH 27/59] feat: add reference scanner setting --- lib/bytecode_parser/scenarios/VtableParser.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/bytecode_parser/scenarios/VtableParser.cpp b/lib/bytecode_parser/scenarios/VtableParser.cpp index e7aaf0a..e466a81 100644 --- a/lib/bytecode_parser/scenarios/VtableParser.cpp +++ b/lib/bytecode_parser/scenarios/VtableParser.cpp @@ -4,6 +4,7 @@ #include "FunctionFactory.hpp" #include "lib/bytecode_parser/BytecodeParserError.hpp" +#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" namespace ovum::bytecode::parser { @@ -183,6 +184,8 @@ std::expected VtableParser::Handle(std::shared_ptr(vtable)); + e = ctx->ExpectPunct('}'); if (!e) { From be0acc348bafcc55f99715ab4020d1d5af645562 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 04:01:11 +0300 Subject: [PATCH 28/59] feat: add memset --- lib/runtime/MemoryManager.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 8d37d75..f78ddfb 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -17,6 +17,14 @@ MemoryManager::MemoryManager(std::unique_ptr gc, size_t max_o std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, execution_tree::PassedExecutionData& data) { + if (repo_.GetCount() > gc_threshold_) { + auto collect_res = CollectGarbage(data); + + if (!collect_res.has_value()) { + return std::unexpected(collect_res.error()); + } + } + const size_t total_size = vtable.GetSize(); char* raw_memory = allocator_.allocate(total_size); if (!raw_memory) { @@ -27,20 +35,14 @@ std::expected MemoryManager::AllocateObject(const Vir descriptor->vtable_index = vtable_index; descriptor->badge = 0; + std::memset(raw_memory + sizeof(ObjectDescriptor), 0, total_size - sizeof(ObjectDescriptor)); + auto add_result = repo_.Add(descriptor); if (!add_result.has_value()) { allocator_.deallocate(raw_memory, total_size); return std::unexpected(add_result.error()); } - if (repo_.GetCount() > gc_threshold_) { - auto collect_res = CollectGarbage(data); - - if (!collect_res.has_value()) { - return std::unexpected(collect_res.error()); - } - } - return reinterpret_cast(descriptor); } From 77c6860a12618361e1a98cc03311d8139853a364 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 23:09:17 +0300 Subject: [PATCH 29/59] fix: added allocation error check --- lib/runtime/MemoryManager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index f78ddfb..36a44ca 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -26,8 +26,11 @@ std::expected MemoryManager::AllocateObject(const Vir } const size_t total_size = vtable.GetSize(); - char* raw_memory = allocator_.allocate(total_size); - if (!raw_memory) { + + char* raw_memory = nullptr; + try { + raw_memory = allocator_.allocate(total_size); + } catch (const std::bad_alloc&) { return std::unexpected(std::runtime_error("MemoryManager: Allocation failed - out of memory")); } From a8902e78a4523d94183fd668964b18803a32db2f Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 23:22:02 +0300 Subject: [PATCH 30/59] fix: remove stack copying --- lib/runtime/gc/MarkAndSweepGC.cpp | 39 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index faeda62..333b961 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -107,56 +107,51 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe } } - std::stack temp_frames = data.memory.stack_frames; - while (!temp_frames.empty()) { - const StackFrame& frame = temp_frames.top(); + std::stack temp_stack_frames; + std::swap(temp_stack_frames, data.memory.stack_frames); + + while (!temp_stack_frames.empty()) { + const StackFrame& frame = temp_stack_frames.top(); for (const auto& var : frame.local_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); if (ptr) { worklist.push(ptr); - } - - if (ptr) { - root_count++; + ++root_count; auto* desc = reinterpret_cast(ptr); auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } - data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } - temp_frames.pop(); + temp_stack_frames.pop(); } - std::vector temp_stack; - temp_stack.reserve(data.memory.machine_stack.size()); + std::swap(temp_stack_frames, data.memory.stack_frames); - std::stack temp_machine_stack = data.memory.machine_stack; - while (!temp_machine_stack.empty()) { - temp_stack.push_back(temp_machine_stack.top()); - temp_machine_stack.pop(); - } - for (const auto& var : temp_stack) { + std::stack temp_machine_stack; + std::swap(temp_machine_stack, data.memory.machine_stack); + + while (!temp_machine_stack.empty()) { + const Variable& var = temp_machine_stack.top(); if (std::holds_alternative(var)) { void* ptr = std::get(var); if (ptr) { worklist.push(ptr); - } - - if (ptr) { - root_count++; + ++root_count; auto* desc = reinterpret_cast(ptr); auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } - data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } + temp_machine_stack.pop(); } + + std::swap(temp_machine_stack, data.memory.machine_stack); } } // namespace ovum::vm::runtime From b28863dcaa0e620246bc3bbce555924516f8b7bb Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 23:24:27 +0300 Subject: [PATCH 31/59] fix: remove GetDataPointer usage --- .../reference_scanners/ArrayReferenceScanner.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index c3ef253..dc6f3be 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -2,6 +2,7 @@ #define RUNTIME_ARRAYREFERENCESCANNER_HPP #include +#include #include "lib/runtime/ObjectDescriptor.hpp" @@ -13,11 +14,17 @@ template class ArrayReferenceScanner : public IReferenceScanner { public: void Scan(void* obj, const ReferenceVisitor& visitor) const override { - const auto& vec = *GetDataPointer>(obj); - for (auto p : vec) { - if (p) { - visitor(p); + const char* base = reinterpret_cast(obj) + sizeof(ObjectDescriptor); + const auto& vec = *reinterpret_cast*>(base); + + if constexpr (std::is_pointer_v) { + for (T p : vec) { + if (p) { + visitor(reinterpret_cast(p)); + } } + } else { + (void)visitor; } } }; From f1f71917f38a10f34dd597e698754fab17938c71 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 23:30:21 +0300 Subject: [PATCH 32/59] fix: add nullcheck for objects in mark phase --- lib/runtime/gc/MarkAndSweepGC.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 333b961..ac8c746 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -24,6 +24,10 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { void* obj = worklist.front(); worklist.pop(); + if (obj == nullptr) { + continue; + } + auto* desc = reinterpret_cast(obj); if (desc->badge & kMarkBit) { continue; From 9d719a1379498b458d24cb103dc5840cd8a4bf08 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Thu, 8 Jan 2026 23:37:55 +0300 Subject: [PATCH 33/59] fix: add error logging in sweeping phase for dealloc --- lib/runtime/gc/MarkAndSweepGC.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index ac8c746..35eadc1 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -79,12 +79,12 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { data.error_stream << "[GC Sweep] Deleting object: " << type_name << " at address " << obj << "\n"; - } - for (auto obj : to_delete) { auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); if (!dealloc_res) { - data.error_stream << dealloc_res.error().what(); + data.error_stream << "[GC Error] Failed to deallocate object of type '" + << type_name << "' at " << obj << ": " + << dealloc_res.error().what() << "\n"; } } } From 1d707d859d7c967018a1dc87139bbb751c257c99 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:05:08 +0300 Subject: [PATCH 34/59] fix: change unmarking objects in sweep --- lib/runtime/gc/MarkAndSweepGC.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 35eadc1..43da808 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -50,7 +50,6 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { std::vector to_delete; - to_delete.reserve(data.memory_manager.GetRepository().GetCount() / 4); const ObjectRepository& repo = data.memory_manager.GetRepository(); repo.ForAll([&to_delete](void* obj) { @@ -63,8 +62,7 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { desc->badge &= ~kMarkBit; }); - data.error_stream << "[GC Sweep] Starting sweep. Total objects in repo: " - << repo.GetCount() + data.error_stream << "[GC Sweep] Starting sweep. Total objects in repo: " << repo.GetCount() << ", objects to delete: " << to_delete.size() << "\n"; for (auto obj : to_delete) { @@ -76,14 +74,13 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { type_name = vt_res.value()->GetName(); } - data.error_stream << "[GC Sweep] Deleting object: " - << type_name - << " at address " << obj << "\n"; + data.error_stream << "[GC Sweep] Deleting object: " << type_name << " at address " << obj << "\n"; auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); - if (!dealloc_res) { - data.error_stream << "[GC Error] Failed to deallocate object of type '" - << type_name << "' at " << obj << ": " + if (dealloc_res.has_value()) { + desc->badge &= ~kMarkBit; + } else { + data.error_stream << "[GC Error] Failed to deallocate object of type '" << type_name << "' at " << obj << ": " << dealloc_res.error().what() << "\n"; } } @@ -96,46 +93,53 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe for (const auto& var : data.memory.global_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); + if (ptr) { worklist.push(ptr); } if (ptr) { root_count++; + auto* desc = reinterpret_cast(ptr); auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } + data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } - std::stack temp_stack_frames; std::swap(temp_stack_frames, data.memory.stack_frames); while (!temp_stack_frames.empty()) { const StackFrame& frame = temp_stack_frames.top(); + for (const auto& var : frame.local_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); + if (ptr) { worklist.push(ptr); ++root_count; + auto* desc = reinterpret_cast(ptr); auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } } } + temp_stack_frames.pop(); } std::swap(temp_stack_frames, data.memory.stack_frames); - std::stack temp_machine_stack; std::swap(temp_machine_stack, data.memory.machine_stack); @@ -143,11 +147,14 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe const Variable& var = temp_machine_stack.top(); if (std::holds_alternative(var)) { void* ptr = std::get(var); + if (ptr) { worklist.push(ptr); ++root_count; + auto* desc = reinterpret_cast(ptr); auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } From 77c7dd71b5e3472872a120a15d6c15717f00812b Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:08:44 +0300 Subject: [PATCH 35/59] fix: change order between deallocating and removing in DeallocateObject --- lib/runtime/MemoryManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 36a44ca..c83a937 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -86,6 +86,10 @@ std::expected MemoryManager::DeallocateObject(void* ob auto exec_res = func_res.value()->Execute(data); data.memory.stack_frames.pop(); + if (!exec_res.has_value()) { + return std::unexpected(exec_res.error()); + } + const size_t total_size = vt->GetSize(); char* raw = reinterpret_cast(obj); @@ -98,10 +102,6 @@ std::expected MemoryManager::DeallocateObject(void* ob // Now safe to deallocate memory allocator_.deallocate(raw, total_size); - if (!exec_res.has_value()) { - return std::unexpected(exec_res.error()); - } - return {}; } From bf3d5a0791900c9acb5369f7f1053dc96800e312 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:21:05 +0300 Subject: [PATCH 36/59] fix: add error saving in Clear function --- lib/runtime/MemoryManager.cpp | 57 ++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index c83a937..c71f187 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -108,33 +108,62 @@ std::expected MemoryManager::DeallocateObject(void* ob std::expected MemoryManager::Clear(execution_tree::PassedExecutionData& data) { std::vector objects_to_clear; objects_to_clear.reserve(repo_.GetCount()); - std::expected clear_res = {}; repo_.ForAll([&objects_to_clear](void* obj) { objects_to_clear.push_back(obj); }); + std::optional first_error; + for (void* obj : objects_to_clear) { auto* desc = reinterpret_cast(obj); - auto dealloc_res = DeallocateObject(obj, data); - if (!dealloc_res.has_value()) { - // If deallocation fails, try to remove from repository and deallocate manually - auto remove_res = repo_.Remove(desc); - if (remove_res.has_value()) { - // Try to get vtable to deallocate memory - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); - if (vt_res.has_value()) { - const size_t total_size = vt_res.value()->GetSize(); - char* raw = reinterpret_cast(obj); - allocator_.deallocate(raw, total_size); + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (!vt_res.has_value()) { + if (!first_error) + first_error = + std::runtime_error("Clear: Virtual table not found for index " + std::to_string(desc->vtable_index)); + continue; + } + + const VirtualTable* vt = vt_res.value(); + + auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); + if (dtor_id_res.has_value()) { + auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (func_res.has_value()) { + runtime::StackFrame frame = {.function_name = "Object deallocation (Clear)"}; + data.memory.machine_stack.emplace(obj); + data.memory.stack_frames.push(std::move(frame)); + auto exec_res = func_res.value()->Execute(data); + data.memory.stack_frames.pop(); + + if (!exec_res.has_value()) { + if (!first_error) { + first_error = exec_res.error(); + } + continue; } } - clear_res = dealloc_res; // Save the error } + + auto remove_res = repo_.Remove(desc); + if (!remove_res.has_value()) { + if (!first_error) + first_error = remove_res.error(); + continue; + } + + const size_t total_size = vt->GetSize(); + char* raw = reinterpret_cast(obj); + allocator_.deallocate(raw, total_size); } repo_.Clear(); - return clear_res; + if (first_error.has_value()) { + return std::unexpected(*first_error); + } + + return {}; } std::expected MemoryManager::CollectGarbage(execution_tree::PassedExecutionData& data) { From c78816b5acefba71fe7a2b5a7e138509580dd55a Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:22:08 +0300 Subject: [PATCH 37/59] fix: codestyle --- lib/runtime/MemoryManager.cpp | 2 +- .../gc/reference_scanners/ArrayReferenceScanner.hpp | 4 ++-- .../gc/reference_scanners/DefaultReferenceScanner.hpp | 9 ++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index c71f187..ea4880b 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -138,7 +138,7 @@ std::expected MemoryManager::Clear(execution_tree::Pas if (!exec_res.has_value()) { if (!first_error) { - first_error = exec_res.error(); + first_error = exec_res.error(); } continue; } diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index dc6f3be..e081ecc 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -1,8 +1,8 @@ #ifndef RUNTIME_ARRAYREFERENCESCANNER_HPP #define RUNTIME_ARRAYREFERENCESCANNER_HPP -#include #include +#include #include "lib/runtime/ObjectDescriptor.hpp" @@ -24,7 +24,7 @@ class ArrayReferenceScanner : public IReferenceScanner { } } } else { - (void)visitor; + (void) visitor; } } }; diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 96df51b..2c9e55b 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -12,19 +12,14 @@ class DefaultReferenceScanner : public IReferenceScanner { explicit DefaultReferenceScanner(const VirtualTable& vt) { for (size_t i = 0; i < vt.GetFieldCount(); ++i) { if (vt.IsFieldReferenceType(i)) { - reference_fields_.emplace_back( - vt.GetFieldOffset(i), - vt.GetFieldAccessor(i) - ); + reference_fields_.emplace_back(vt.GetFieldOffset(i), vt.GetFieldAccessor(i)); } } } void Scan(void* obj, const ReferenceVisitor& visitor) const override { for (const FieldInfo& field : reference_fields_) { - auto var_res = field.variable_accessor->GetVariable( - reinterpret_cast(obj) + field.offset - ); + auto var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); if (std::holds_alternative(var_res)) { void* ptr = std::get(var_res); From 134e56516f902abf777dcd81604089b352ac64fa Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:45:34 +0300 Subject: [PATCH 38/59] fix: codestyle --- lib/runtime/MemoryManager.cpp | 47 +++++++++------- lib/runtime/gc/MarkAndSweepGC.cpp | 53 +++++++++++-------- .../ArrayReferenceScanner.hpp | 2 +- .../DefaultReferenceScanner.hpp | 7 ++- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index ea4880b..1c6bdbb 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -18,7 +18,7 @@ std::expected MemoryManager::AllocateObject(const Vir uint32_t vtable_index, execution_tree::PassedExecutionData& data) { if (repo_.GetCount() > gc_threshold_) { - auto collect_res = CollectGarbage(data); + std::expected collect_res = CollectGarbage(data); if (!collect_res.has_value()) { return std::unexpected(collect_res.error()); @@ -26,21 +26,22 @@ std::expected MemoryManager::AllocateObject(const Vir } const size_t total_size = vtable.GetSize(); - char* raw_memory = nullptr; + try { raw_memory = allocator_.allocate(total_size); } catch (const std::bad_alloc&) { return std::unexpected(std::runtime_error("MemoryManager: Allocation failed - out of memory")); } - auto* descriptor = reinterpret_cast(raw_memory); + ObjectDescriptor* descriptor = reinterpret_cast(raw_memory); descriptor->vtable_index = vtable_index; descriptor->badge = 0; std::memset(raw_memory + sizeof(ObjectDescriptor), 0, total_size - sizeof(ObjectDescriptor)); - auto add_result = repo_.Add(descriptor); + std::expected add_result = repo_.Add(descriptor); + if (!add_result.has_value()) { allocator_.deallocate(raw_memory, total_size); return std::unexpected(add_result.error()); @@ -55,19 +56,20 @@ std::expected MemoryManager::DeallocateObject(void* ob return std::unexpected(std::runtime_error("DeallocateObject: Null object pointer")); } - auto* desc = reinterpret_cast(obj); + ObjectDescriptor* desc = reinterpret_cast(obj); + + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); if (!vt_res.has_value()) { return std::unexpected(std::runtime_error("DeallocateObject: Virtual table not found for index " + std::to_string(desc->vtable_index))); } std::string object_type = vt_res.value()->GetName(); - const VirtualTable* vt = vt_res.value(); - auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); + std::expected dtor_id_res = vt->GetRealFunctionId("_destructor_"); if (!dtor_id_res.has_value()) { return std::unexpected(std::runtime_error("DeallocateObject: Destructor not found for class " + object_type)); @@ -83,7 +85,7 @@ std::expected MemoryManager::DeallocateObject(void* ob runtime::StackFrame frame = {.function_name = "Object deallocation"}; data.memory.machine_stack.emplace(obj); data.memory.stack_frames.push(std::move(frame)); - auto exec_res = func_res.value()->Execute(data); + std::expected exec_res = func_res.value()->Execute(data); data.memory.stack_frames.pop(); if (!exec_res.has_value()) { @@ -93,13 +95,12 @@ std::expected MemoryManager::DeallocateObject(void* ob const size_t total_size = vt->GetSize(); char* raw = reinterpret_cast(obj); - // Remove from repository BEFORE deallocating memory - auto remove_res = repo_.Remove(desc); + std::expected remove_res = repo_.Remove(desc); + if (!remove_res.has_value()) { return std::unexpected(remove_res.error()); } - // Now safe to deallocate memory allocator_.deallocate(raw, total_size); return {}; @@ -114,26 +115,31 @@ std::expected MemoryManager::Clear(execution_tree::Pas std::optional first_error; for (void* obj : objects_to_clear) { - auto* desc = reinterpret_cast(obj); + ObjectDescriptor* desc = reinterpret_cast(obj); + + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); if (!vt_res.has_value()) { - if (!first_error) + if (!first_error) { first_error = std::runtime_error("Clear: Virtual table not found for index " + std::to_string(desc->vtable_index)); + } continue; } const VirtualTable* vt = vt_res.value(); - auto dtor_id_res = vt->GetRealFunctionId("_destructor_"); + std::expected dtor_id_res = vt->GetRealFunctionId("_destructor_"); + if (dtor_id_res.has_value()) { auto func_res = data.function_repository.GetById(dtor_id_res.value()); + if (func_res.has_value()) { runtime::StackFrame frame = {.function_name = "Object deallocation (Clear)"}; data.memory.machine_stack.emplace(obj); data.memory.stack_frames.push(std::move(frame)); - auto exec_res = func_res.value()->Execute(data); + std::expected exec_res = func_res.value()->Execute(data); data.memory.stack_frames.pop(); if (!exec_res.has_value()) { @@ -145,15 +151,18 @@ std::expected MemoryManager::Clear(execution_tree::Pas } } - auto remove_res = repo_.Remove(desc); + std::expected remove_res = repo_.Remove(desc); + if (!remove_res.has_value()) { - if (!first_error) + if (!first_error) { first_error = remove_res.error(); + } continue; } const size_t total_size = vt->GetSize(); char* raw = reinterpret_cast(obj); + allocator_.deallocate(raw, total_size); } diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 43da808..bf05c77 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -28,16 +28,19 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { continue; } - auto* desc = reinterpret_cast(obj); + ObjectDescriptor* desc = reinterpret_cast(obj); if (desc->badge & kMarkBit) { continue; } desc->badge |= kMarkBit; - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (!vt_res.has_value()) { continue; } + const VirtualTable* vt = vt_res.value(); vt->ScanReferences(obj, [&](void* ref) { @@ -50,10 +53,10 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { std::vector to_delete; - const ObjectRepository& repo = data.memory_manager.GetRepository(); + repo.ForAll([&to_delete](void* obj) { - auto* desc = reinterpret_cast(obj); + ObjectDescriptor* desc = reinterpret_cast(obj); if (!(desc->badge & kMarkBit)) { to_delete.push_back(obj); @@ -65,10 +68,12 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { data.error_stream << "[GC Sweep] Starting sweep. Total objects in repo: " << repo.GetCount() << ", objects to delete: " << to_delete.size() << "\n"; - for (auto obj : to_delete) { - auto* desc = reinterpret_cast(obj); + for (void* obj : to_delete) { + ObjectDescriptor* desc = reinterpret_cast(obj); + + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); std::string type_name = ""; if (vt_res.has_value()) { type_name = vt_res.value()->GetName(); @@ -76,7 +81,8 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { data.error_stream << "[GC Sweep] Deleting object: " << type_name << " at address " << obj << "\n"; - auto dealloc_res = data.memory_manager.DeallocateObject(obj, data); + std::expected dealloc_res = data.memory_manager.DeallocateObject(obj, data); + if (dealloc_res.has_value()) { desc->badge &= ~kMarkBit; } else { @@ -90,35 +96,33 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe data.error_stream << "[GC Roots] Scanning " << data.memory.stack_frames.size() << " frames\n"; size_t root_count = 0; - for (const auto& var : data.memory.global_variables) { + for (const Variable& var : data.memory.global_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); if (ptr) { worklist.push(ptr); - } - - if (ptr) { - root_count++; + ++root_count; - auto* desc = reinterpret_cast(ptr); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + ObjectDescriptor* desc = reinterpret_cast(ptr); + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); - std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; + std::string name = vt_res.has_value() ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } - - data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; } } + data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; + std::stack temp_stack_frames; std::swap(temp_stack_frames, data.memory.stack_frames); while (!temp_stack_frames.empty()) { const StackFrame& frame = temp_stack_frames.top(); - for (const auto& var : frame.local_variables) { + for (const Variable& var : frame.local_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); @@ -127,7 +131,8 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe ++root_count; auto* desc = reinterpret_cast(ptr); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; @@ -145,6 +150,7 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe while (!temp_machine_stack.empty()) { const Variable& var = temp_machine_stack.top(); + if (std::holds_alternative(var)) { void* ptr = std::get(var); @@ -152,10 +158,11 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe worklist.push(ptr); ++root_count; - auto* desc = reinterpret_cast(ptr); - auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + ObjectDescriptor* desc = reinterpret_cast(ptr); + std::expected vt_res = + data.virtual_table_repository.GetByIndex(desc->vtable_index); - std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; + std::string name = vt_res.has_value() ? vt_res.value()->GetName() : "unknown"; data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } } diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index e081ecc..a3c862f 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -15,7 +15,7 @@ class ArrayReferenceScanner : public IReferenceScanner { public: void Scan(void* obj, const ReferenceVisitor& visitor) const override { const char* base = reinterpret_cast(obj) + sizeof(ObjectDescriptor); - const auto& vec = *reinterpret_cast*>(base); + const std::vector& vec = *reinterpret_cast*>(base); if constexpr (std::is_pointer_v) { for (T p : vec) { diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 2c9e55b..47364e9 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -1,6 +1,10 @@ #ifndef RUNTIME_DEFAULTREFERENCESCANNER_HPP #define RUNTIME_DEFAULTREFERENCESCANNER_HPP +#include + +#include "lib/runtime/FieldInfo.hpp" +#include "lib/runtime/Variable.hpp" #include "lib/runtime/VirtualTable.hpp" #include "IReferenceScanner.hpp" @@ -19,10 +23,11 @@ class DefaultReferenceScanner : public IReferenceScanner { void Scan(void* obj, const ReferenceVisitor& visitor) const override { for (const FieldInfo& field : reference_fields_) { - auto var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); + Variable var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); if (std::holds_alternative(var_res)) { void* ptr = std::get(var_res); + if (ptr) { visitor(ptr); } From 2398ad5b436cc8a762ae74a5cb4ed0050309da4b Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 00:46:44 +0300 Subject: [PATCH 39/59] fix: codestyle --- lib/runtime/MemoryManager.cpp | 6 +++--- lib/runtime/gc/MarkAndSweepGC.cpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index 1c6bdbb..bcc42c2 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -34,7 +34,7 @@ std::expected MemoryManager::AllocateObject(const Vir return std::unexpected(std::runtime_error("MemoryManager: Allocation failed - out of memory")); } - ObjectDescriptor* descriptor = reinterpret_cast(raw_memory); + auto* descriptor = reinterpret_cast(raw_memory); descriptor->vtable_index = vtable_index; descriptor->badge = 0; @@ -56,7 +56,7 @@ std::expected MemoryManager::DeallocateObject(void* ob return std::unexpected(std::runtime_error("DeallocateObject: Null object pointer")); } - ObjectDescriptor* desc = reinterpret_cast(obj); + auto* desc = reinterpret_cast(obj); std::expected vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); @@ -115,7 +115,7 @@ std::expected MemoryManager::Clear(execution_tree::Pas std::optional first_error; for (void* obj : objects_to_clear) { - ObjectDescriptor* desc = reinterpret_cast(obj); + auto* desc = reinterpret_cast(obj); std::expected vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index bf05c77..c500bce 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -28,7 +28,7 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { continue; } - ObjectDescriptor* desc = reinterpret_cast(obj); + auto* desc = reinterpret_cast(obj); if (desc->badge & kMarkBit) { continue; } @@ -56,7 +56,7 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { const ObjectRepository& repo = data.memory_manager.GetRepository(); repo.ForAll([&to_delete](void* obj) { - ObjectDescriptor* desc = reinterpret_cast(obj); + auto* desc = reinterpret_cast(obj); if (!(desc->badge & kMarkBit)) { to_delete.push_back(obj); @@ -69,7 +69,7 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { << ", objects to delete: " << to_delete.size() << "\n"; for (void* obj : to_delete) { - ObjectDescriptor* desc = reinterpret_cast(obj); + auto* desc = reinterpret_cast(obj); std::expected vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); @@ -104,7 +104,7 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe worklist.push(ptr); ++root_count; - ObjectDescriptor* desc = reinterpret_cast(ptr); + auto* desc = reinterpret_cast(ptr); std::expected vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); @@ -158,7 +158,7 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe worklist.push(ptr); ++root_count; - ObjectDescriptor* desc = reinterpret_cast(ptr); + auto* desc = reinterpret_cast(ptr); std::expected vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); From 767616c602fdf4ddd7a6437e676eb4744996842d Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 02:03:23 +0300 Subject: [PATCH 40/59] fix: update MarkAndSweepGC to copy stack in AddRoots and improve error handling in Sweep --- lib/runtime/MemoryManager.cpp | 2 + lib/runtime/gc/MarkAndSweepGC.cpp | 75 +++++++------------------------ lib/runtime/gc/MarkAndSweepGC.hpp | 2 +- 3 files changed, 19 insertions(+), 60 deletions(-) diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index bcc42c2..b000ac1 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -1,5 +1,7 @@ #include "MemoryManager.hpp" +#include +#include #include #include diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index c500bce..9cda8bd 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -1,5 +1,6 @@ #include "MarkAndSweepGC.hpp" +#include #include #include #include @@ -12,8 +13,7 @@ namespace ovum::vm::runtime { std::expected MarkAndSweepGC::Collect(execution_tree::PassedExecutionData& data) { Mark(data); - Sweep(data); - return {}; + return Sweep(data); } void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { @@ -51,7 +51,7 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { } } -void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { +std::expected MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { std::vector to_delete; const ObjectRepository& repo = data.memory_manager.GetRepository(); @@ -65,59 +65,36 @@ void MarkAndSweepGC::Sweep(execution_tree::PassedExecutionData& data) { desc->badge &= ~kMarkBit; }); - data.error_stream << "[GC Sweep] Starting sweep. Total objects in repo: " << repo.GetCount() - << ", objects to delete: " << to_delete.size() << "\n"; + std::optional first_error; for (void* obj : to_delete) { - auto* desc = reinterpret_cast(obj); - - std::expected vt_res = - data.virtual_table_repository.GetByIndex(desc->vtable_index); - - std::string type_name = ""; - if (vt_res.has_value()) { - type_name = vt_res.value()->GetName(); - } - - data.error_stream << "[GC Sweep] Deleting object: " << type_name << " at address " << obj << "\n"; - std::expected dealloc_res = data.memory_manager.DeallocateObject(obj, data); - if (dealloc_res.has_value()) { - desc->badge &= ~kMarkBit; - } else { - data.error_stream << "[GC Error] Failed to deallocate object of type '" << type_name << "' at " << obj << ": " - << dealloc_res.error().what() << "\n"; + if (!dealloc_res.has_value() && !first_error) { + first_error = dealloc_res.error(); } } + + if (first_error) { + return std::unexpected(*first_error); + } + + return {}; } void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data) { - data.error_stream << "[GC Roots] Scanning " << data.memory.stack_frames.size() << " frames\n"; - size_t root_count = 0; - for (const Variable& var : data.memory.global_variables) { if (std::holds_alternative(var)) { void* ptr = std::get(var); if (ptr) { worklist.push(ptr); - ++root_count; - - auto* desc = reinterpret_cast(ptr); - std::expected vt_res = - data.virtual_table_repository.GetByIndex(desc->vtable_index); - - std::string name = vt_res.has_value() ? vt_res.value()->GetName() : "unknown"; - data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } } } - data.error_stream << "[GC Roots] Total roots: " << root_count << "\n"; - - std::stack temp_stack_frames; - std::swap(temp_stack_frames, data.memory.stack_frames); + // Note that there is no way to traverse a std::stack without emptying it, so we need to create a temporary stack. + std::stack temp_stack_frames = data.memory.stack_frames; while (!temp_stack_frames.empty()) { const StackFrame& frame = temp_stack_frames.top(); @@ -128,14 +105,6 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe if (ptr) { worklist.push(ptr); - ++root_count; - - auto* desc = reinterpret_cast(ptr); - std::expected vt_res = - data.virtual_table_repository.GetByIndex(desc->vtable_index); - - std::string name = vt_res ? vt_res.value()->GetName() : "unknown"; - data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } } } @@ -143,10 +112,7 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe temp_stack_frames.pop(); } - std::swap(temp_stack_frames, data.memory.stack_frames); - - std::stack temp_machine_stack; - std::swap(temp_machine_stack, data.memory.machine_stack); + std::stack temp_machine_stack = data.memory.machine_stack; while (!temp_machine_stack.empty()) { const Variable& var = temp_machine_stack.top(); @@ -156,20 +122,11 @@ void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::Passe if (ptr) { worklist.push(ptr); - ++root_count; - - auto* desc = reinterpret_cast(ptr); - std::expected vt_res = - data.virtual_table_repository.GetByIndex(desc->vtable_index); - - std::string name = vt_res.has_value() ? vt_res.value()->GetName() : "unknown"; - data.error_stream << "[GC Root] Found live object: " << name << " at " << ptr << "\n"; } } + temp_machine_stack.pop(); } - - std::swap(temp_machine_stack, data.memory.machine_stack); } } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/MarkAndSweepGC.hpp b/lib/runtime/gc/MarkAndSweepGC.hpp index 9ba9d11..48deac7 100644 --- a/lib/runtime/gc/MarkAndSweepGC.hpp +++ b/lib/runtime/gc/MarkAndSweepGC.hpp @@ -13,7 +13,7 @@ class MarkAndSweepGC : public IGarbageCollector { private: void Mark(execution_tree::PassedExecutionData& data); - void Sweep(execution_tree::PassedExecutionData& data); + std::expected Sweep(execution_tree::PassedExecutionData& data); void AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data); }; From 6773f8a2bfd1be43352e5ddaff417d8db2713e23 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 02:03:38 +0300 Subject: [PATCH 41/59] test: add MemcheckTest to validate VM execution with example data --- tests/main_test.cpp | 31 +++++++++++++++++++++++++++++++ tests/test_data/examples | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/main_test.cpp b/tests/main_test.cpp index 7b35b5f..4b6c049 100644 --- a/tests/main_test.cpp +++ b/tests/main_test.cpp @@ -251,3 +251,34 @@ TEST_F(ProjectIntegrationTestSuite, InteropTest3) { time_from_code ^= kBinaryFilter; ASSERT_LE(std::abs(time_from_code - current_nanotime), kMaxAllowedError); } + +TEST_F(ProjectIntegrationTestSuite, MemcheckTest) { + TestData test_data{ + .test_name = "memcheck.oil", + .arguments = "", + .input = "", + .expected_output = "", + .expected_error = "", + .expected_return_code = 0, + }; + + std::filesystem::path test_file = kTestDataDir; + test_file /= "examples"; + test_file /= "compiled"; + test_file /= test_data.test_name; + std::string cmd = "ovum-vm -f \""; + cmd += test_file.string(); + cmd += "\""; + cmd += " -m 10000"; + + if (!test_data.arguments.empty()) { + cmd += " -- "; + cmd += test_data.arguments; + } + + std::istringstream in(test_data.input); + std::ostringstream out; + std::ostringstream err; + ASSERT_EQ(StartVmConsoleUI(SplitString(cmd), out, in, err), test_data.expected_return_code); + ASSERT_EQ(err.str(), test_data.expected_error); +} diff --git a/tests/test_data/examples b/tests/test_data/examples index 294d708..9c86ce5 160000 --- a/tests/test_data/examples +++ b/tests/test_data/examples @@ -1 +1 @@ -Subproject commit 294d70868cdeba0764dc1d397f5340afaa2a9026 +Subproject commit 9c86ce59605f347aa5df2c346faefe81cbf9d72c From 7e4eeed834fad1e81812f285621fdbfa23279ed0 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 03:07:03 +0300 Subject: [PATCH 42/59] refactor: move HashVector implementation to BuiltinFunctions.cpp and clean up unused includes in BuiltinFunctions.hpp --- lib/executor/BuiltinFunctions.cpp | 17 +++++++++++ lib/executor/BuiltinFunctions.hpp | 51 ------------------------------- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/lib/executor/BuiltinFunctions.cpp b/lib/executor/BuiltinFunctions.cpp index 42d870d..0c66632 100644 --- a/lib/executor/BuiltinFunctions.cpp +++ b/lib/executor/BuiltinFunctions.cpp @@ -15,6 +15,23 @@ #include "lib/runtime/Variable.hpp" #include "lib/runtime/VirtualTable.hpp" +namespace ovum::vm::runtime { + +// Helper to hash vector using algorithm from PureFunction.hpp +template +int64_t HashVector(const std::vector& vec) { + static constexpr size_t kHashMultiplier = 0x9e3779b9; + static constexpr size_t kHashShift = 6; + + size_t seed = 0; + for (const T& value : vec) { + seed ^= std::hash{}(value) + kHashMultiplier + (seed << kHashShift) + (seed >> kHashShift); + } + return static_cast(seed); +} + +} // namespace ovum::vm::runtime + namespace ovum::vm::execution_tree { // Helper to check if two objects have the same type (same vtable_index) diff --git a/lib/executor/BuiltinFunctions.hpp b/lib/executor/BuiltinFunctions.hpp index eabed23..9e77b28 100644 --- a/lib/executor/BuiltinFunctions.hpp +++ b/lib/executor/BuiltinFunctions.hpp @@ -1,19 +1,12 @@ #ifndef EXECUTOR_BUILTINFUNCTIONS_HPP #define EXECUTOR_BUILTINFUNCTIONS_HPP -#include -#include #include -#include -#include #include -#include #include "lib/execution_tree/ExecutionResult.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" #include "lib/runtime/ObjectDescriptor.hpp" -#include "lib/runtime/ObjectRepository.hpp" -#include "lib/runtime/VirtualTable.hpp" namespace ovum::vm::runtime { @@ -28,50 +21,6 @@ const T* GetDataPointer(const void* object_ptr) { return reinterpret_cast(reinterpret_cast(object_ptr) + sizeof(ObjectDescriptor)); } -// Helper to hash vector using algorithm from PureFunction.hpp -template -int64_t HashVector(const std::vector& vec) { - static constexpr size_t kHashMultiplier = 0x9e3779b9; - static constexpr size_t kHashShift = 6; - - size_t seed = 0; - for (const T& value : vec) { - seed ^= std::hash{}(value) + kHashMultiplier + (seed << kHashShift) + (seed >> kHashShift); - } - return static_cast(seed); -} - -// Allocation helper: allocates memory for an object and initializes ObjectDescriptor -template> -std::expected AllocateObject(const VirtualTable& vtable, - uint32_t vtable_index, - ObjectRepository& object_repository, - Allocator&& allocator = Allocator{}) { - const size_t size = vtable.GetSize(); - char* memory = allocator.allocate(size); - if (memory == nullptr) { - return std::unexpected(std::runtime_error("AllocateObject: failed to allocate memory")); - } - - // Initialize ObjectDescriptor at the first 8 bytes - ObjectDescriptor* descriptor = reinterpret_cast(memory); - descriptor->vtable_index = vtable_index; - descriptor->badge = 0; - - // Zero-initialize the rest of the memory - std::memset(memory + sizeof(ObjectDescriptor), 0, size - sizeof(ObjectDescriptor)); - - // Add to ObjectRepository - auto add_result = object_repository.Add(descriptor); - if (!add_result.has_value()) { - allocator.deallocate(memory, size); - return std::unexpected(std::runtime_error(std::string("AllocateObject: failed to add object to repository: ") + - add_result.error().what())); - } - - return memory; -} - } // namespace ovum::vm::runtime namespace ovum::vm::execution_tree { From 738433f3a4a69dbc4e7d1703fac225dee251a4cf Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 03:40:58 +0300 Subject: [PATCH 43/59] refactor: simplify reference scanning in MarkAndSweepGC and introduce ArrayReferenceScanner and DefaultReferenceScanner for improved memory management --- lib/executor/builtin_factory.cpp | 6 +- lib/runtime/CMakeLists.txt | 4 +- lib/runtime/gc/MarkAndSweepGC.cpp | 57 +++++++++---------- lib/runtime/gc/MarkAndSweepGC.hpp | 12 +++- .../ArrayReferenceScanner.cpp | 18 ++++++ .../ArrayReferenceScanner.hpp | 21 +------ .../DefaultReferenceScanner.cpp | 32 +++++++++++ .../DefaultReferenceScanner.hpp | 25 +------- 8 files changed, 95 insertions(+), 80 deletions(-) create mode 100644 lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp create mode 100644 lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index cd3adc0..38d037a 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -276,7 +276,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl object_array_vtable.AddFunction("_GetHash_", "_ObjectArray_GetHash_"); object_array_vtable.AddInterface("IComparable"); object_array_vtable.AddInterface("IHashable"); - object_array_vtable.SetReferenceScanner(std::make_unique>()); + object_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(object_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -293,7 +293,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl string_array_vtable.AddFunction("_GetHash_", "_StringArray_GetHash_"); string_array_vtable.AddInterface("IComparable"); string_array_vtable.AddInterface("IHashable"); - string_array_vtable.SetReferenceScanner(std::make_unique>()); + string_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(string_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -326,7 +326,7 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl pointer_array_vtable.AddFunction("_GetHash_", "_PointerArray_GetHash_"); pointer_array_vtable.AddInterface("IComparable"); pointer_array_vtable.AddInterface("IHashable"); - pointer_array_vtable.SetReferenceScanner(std::make_unique>()); + pointer_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(pointer_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); diff --git a/lib/runtime/CMakeLists.txt b/lib/runtime/CMakeLists.txt index 38b761d..7108c04 100644 --- a/lib/runtime/CMakeLists.txt +++ b/lib/runtime/CMakeLists.txt @@ -3,8 +3,10 @@ add_library(runtime STATIC VirtualTableRepository.cpp ObjectRepository.cpp ByteArray.cpp - gc/MarkAndSweepGC.cpp MemoryManager.cpp + gc/MarkAndSweepGC.cpp + gc/reference_scanners/ArrayReferenceScanner.cpp + gc/reference_scanners/DefaultReferenceScanner.cpp ) target_include_directories(runtime PUBLIC ${PROJECT_SOURCE_DIR}) diff --git a/lib/runtime/gc/MarkAndSweepGC.cpp b/lib/runtime/gc/MarkAndSweepGC.cpp index 9cda8bd..6f5d589 100644 --- a/lib/runtime/gc/MarkAndSweepGC.cpp +++ b/lib/runtime/gc/MarkAndSweepGC.cpp @@ -29,9 +29,11 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { } auto* desc = reinterpret_cast(obj); + if (desc->badge & kMarkBit) { continue; } + desc->badge |= kMarkBit; std::expected vt_res = @@ -43,7 +45,7 @@ void MarkAndSweepGC::Mark(execution_tree::PassedExecutionData& data) { const VirtualTable* vt = vt_res.value(); - vt->ScanReferences(obj, [&](void* ref) { + vt->ScanReferences(obj, [&worklist](void* ref) { if (ref) { worklist.push(ref); } @@ -83,49 +85,42 @@ std::expected MarkAndSweepGC::Sweep(execution_tree::Pa } void MarkAndSweepGC::AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data) { - for (const Variable& var : data.memory.global_variables) { - if (std::holds_alternative(var)) { - void* ptr = std::get(var); - - if (ptr) { - worklist.push(ptr); - } - } - } + AddAllVariables(worklist, data.memory.global_variables); // Note that there is no way to traverse a std::stack without emptying it, so we need to create a temporary stack. std::stack temp_stack_frames = data.memory.stack_frames; while (!temp_stack_frames.empty()) { const StackFrame& frame = temp_stack_frames.top(); - - for (const Variable& var : frame.local_variables) { - if (std::holds_alternative(var)) { - void* ptr = std::get(var); - - if (ptr) { - worklist.push(ptr); - } - } - } - + AddAllVariables(worklist, frame.local_variables); temp_stack_frames.pop(); } - std::stack temp_machine_stack = data.memory.machine_stack; + AddAllVariables(worklist, data.memory.machine_stack); +} + +void MarkAndSweepGC::AddAllVariables(std::queue& worklist, const std::vector& variables) { + for (const Variable& var : variables) { + AddRoot(worklist, var); + } +} - while (!temp_machine_stack.empty()) { - const Variable& var = temp_machine_stack.top(); +void MarkAndSweepGC::AddAllVariables(std::queue& worklist, VariableStack variables) { + while (!variables.empty()) { + AddRoot(worklist, variables.top()); + variables.pop(); + } +} - if (std::holds_alternative(var)) { - void* ptr = std::get(var); +void MarkAndSweepGC::AddRoot(std::queue& worklist, const Variable& var) { + if (!std::holds_alternative(var)) { + return; + } - if (ptr) { - worklist.push(ptr); - } - } + void* ptr = std::get(var); - temp_machine_stack.pop(); + if (ptr) { + worklist.push(ptr); } } diff --git a/lib/runtime/gc/MarkAndSweepGC.hpp b/lib/runtime/gc/MarkAndSweepGC.hpp index 48deac7..74f74d2 100644 --- a/lib/runtime/gc/MarkAndSweepGC.hpp +++ b/lib/runtime/gc/MarkAndSweepGC.hpp @@ -2,7 +2,9 @@ #define RUNTIME_MARKANDSWEEPGC_HPP #include +#include +#include "lib/runtime/Variable.hpp" #include "lib/runtime/gc/IGarbageCollector.hpp" namespace ovum::vm::runtime { @@ -12,9 +14,13 @@ class MarkAndSweepGC : public IGarbageCollector { std::expected Collect(execution_tree::PassedExecutionData& data) override; private: - void Mark(execution_tree::PassedExecutionData& data); - std::expected Sweep(execution_tree::PassedExecutionData& data); - void AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data); + static void Mark(execution_tree::PassedExecutionData& data); + static std::expected Sweep(execution_tree::PassedExecutionData& data); + + static void AddRoots(std::queue& worklist, execution_tree::PassedExecutionData& data); + static void AddAllVariables(std::queue& worklist, const std::vector& variables); + static void AddAllVariables(std::queue& worklist, VariableStack variables); + static void AddRoot(std::queue& worklist, const Variable& var); }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp new file mode 100644 index 0000000..08d46d6 --- /dev/null +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp @@ -0,0 +1,18 @@ +#include "ArrayReferenceScanner.hpp" + +#include + +#include "lib/runtime/ObjectDescriptor.hpp" + +namespace ovum::vm::runtime { + +void ArrayReferenceScanner::Scan(void* obj, const ReferenceVisitor& visitor) const { + const char* base = reinterpret_cast(obj) + sizeof(ObjectDescriptor); + const std::vector& vec = *reinterpret_cast*>(base); + + for (void* p : vec) { + visitor(p); + } +} + +} // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index a3c862f..615f250 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -1,32 +1,13 @@ #ifndef RUNTIME_ARRAYREFERENCESCANNER_HPP #define RUNTIME_ARRAYREFERENCESCANNER_HPP -#include -#include - -#include "lib/runtime/ObjectDescriptor.hpp" - #include "IReferenceScanner.hpp" namespace ovum::vm::runtime { -template class ArrayReferenceScanner : public IReferenceScanner { public: - void Scan(void* obj, const ReferenceVisitor& visitor) const override { - const char* base = reinterpret_cast(obj) + sizeof(ObjectDescriptor); - const std::vector& vec = *reinterpret_cast*>(base); - - if constexpr (std::is_pointer_v) { - for (T p : vec) { - if (p) { - visitor(reinterpret_cast(p)); - } - } - } else { - (void) visitor; - } - } + void Scan(void* obj, const ReferenceVisitor& visitor) const override; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp new file mode 100644 index 0000000..6c94743 --- /dev/null +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp @@ -0,0 +1,32 @@ + +#include "DefaultReferenceScanner.hpp" + +#include + +#include "lib/runtime/FieldInfo.hpp" +#include "lib/runtime/Variable.hpp" +#include "lib/runtime/VirtualTable.hpp" + +namespace ovum::vm::runtime { + +DefaultReferenceScanner::DefaultReferenceScanner(const VirtualTable& vt) { + for (size_t i = 0; i < vt.GetFieldCount(); ++i) { + if (vt.IsFieldReferenceType(i)) { + reference_fields_.emplace_back(vt.GetFieldOffset(i), vt.GetFieldAccessor(i)); + } + } +} + +void DefaultReferenceScanner::Scan(void* obj, const ReferenceVisitor& visitor) const { + for (const FieldInfo& field : reference_fields_) { + Variable var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); + + if (!std::holds_alternative(var_res)) { + continue; + } + + visitor(std::get(var_res)); + } +} + +} // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 47364e9..62ba570 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -4,7 +4,6 @@ #include #include "lib/runtime/FieldInfo.hpp" -#include "lib/runtime/Variable.hpp" #include "lib/runtime/VirtualTable.hpp" #include "IReferenceScanner.hpp" @@ -13,27 +12,9 @@ namespace ovum::vm::runtime { class DefaultReferenceScanner : public IReferenceScanner { public: - explicit DefaultReferenceScanner(const VirtualTable& vt) { - for (size_t i = 0; i < vt.GetFieldCount(); ++i) { - if (vt.IsFieldReferenceType(i)) { - reference_fields_.emplace_back(vt.GetFieldOffset(i), vt.GetFieldAccessor(i)); - } - } - } - - void Scan(void* obj, const ReferenceVisitor& visitor) const override { - for (const FieldInfo& field : reference_fields_) { - Variable var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); - - if (std::holds_alternative(var_res)) { - void* ptr = std::get(var_res); - - if (ptr) { - visitor(ptr); - } - } - } - } + explicit DefaultReferenceScanner(const VirtualTable& vt); + + void Scan(void* obj, const ReferenceVisitor& visitor) const override; private: std::vector reference_fields_; From cb3ecfdd274a91533924d1937480b5959244b28d Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 04:53:09 +0300 Subject: [PATCH 44/59] chore: update subproject commit reference in test examples --- tests/test_data/examples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_data/examples b/tests/test_data/examples index 9c86ce5..51cb78d 160000 --- a/tests/test_data/examples +++ b/tests/test_data/examples @@ -1 +1 @@ -Subproject commit 9c86ce59605f347aa5df2c346faefe81cbf9d72c +Subproject commit 51cb78d7778c97ac9d4561a210a003e94b59fe83 From 3a3f89a13dcb115c7aff5abad7fa6f6ab609835c Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 04:57:43 +0300 Subject: [PATCH 45/59] refactor: streamline reference scanning by removing EmptyReferenceScanner and enhancing DefaultReferenceScanner and ArrayReferenceScanner for better OOP --- .../scenarios/VtableParser.cpp | 3 -- lib/executor/builtin_factory.cpp | 27 ++-------------- lib/runtime/VirtualTable.cpp | 31 +++++-------------- lib/runtime/VirtualTable.hpp | 6 +--- .../ArrayReferenceScanner.cpp | 2 +- .../ArrayReferenceScanner.hpp | 2 +- .../DefaultReferenceScanner.cpp | 21 ++++--------- .../DefaultReferenceScanner.hpp | 12 +------ .../EmptyReferenceScanner.hpp | 17 ---------- .../reference_scanners/IReferenceScanner.hpp | 5 ++- 10 files changed, 24 insertions(+), 102 deletions(-) delete mode 100644 lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp diff --git a/lib/bytecode_parser/scenarios/VtableParser.cpp b/lib/bytecode_parser/scenarios/VtableParser.cpp index e466a81..e7aaf0a 100644 --- a/lib/bytecode_parser/scenarios/VtableParser.cpp +++ b/lib/bytecode_parser/scenarios/VtableParser.cpp @@ -4,7 +4,6 @@ #include "FunctionFactory.hpp" #include "lib/bytecode_parser/BytecodeParserError.hpp" -#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" namespace ovum::bytecode::parser { @@ -184,8 +183,6 @@ std::expected VtableParser::Handle(std::shared_ptr(vtable)); - e = ctx->ExpectPunct('}'); if (!e) { diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index 38d037a..40076c4 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -21,7 +21,6 @@ #include "lib/runtime/VirtualTable.hpp" #include "lib/runtime/VirtualTableRepository.hpp" #include "lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp" -#include "lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp" namespace ovum::vm::runtime { @@ -139,7 +138,6 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // String: wrapper around std::string { VirtualTable string_vtable("String", sizeof(ObjectDescriptor) + sizeof(std::string)); - string_vtable.AddField("Object", sizeof(ObjectDescriptor)); string_vtable.AddFunction("_destructor_", "_String_destructor_"); string_vtable.AddFunction("_Equals__Object", "_String_Equals__Object"); string_vtable.AddFunction("_IsLess__Object", "_String_IsLess__Object"); @@ -150,7 +148,6 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl string_vtable.AddInterface("IComparable"); string_vtable.AddInterface("IHashable"); string_vtable.AddInterface("IStringConvertible"); - string_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(string_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -160,7 +157,6 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // File: wrapper around std::fstream { VirtualTable file_vtable("File", sizeof(ObjectDescriptor) + sizeof(std::fstream)); - file_vtable.AddField("Object", sizeof(ObjectDescriptor)); file_vtable.AddFunction("_destructor_", "_File_destructor_"); file_vtable.AddFunction("_Open__String_String", "_File_Open__String_String"); file_vtable.AddFunction("_Close_", "_File_Close_"); @@ -172,7 +168,6 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl file_vtable.AddFunction("_Seek__Int", "_File_Seek__Int"); file_vtable.AddFunction("_Tell_", "_File_Tell_"); file_vtable.AddFunction("_Eof_", "_File_Eof_"); - file_vtable.SetReferenceScanner(std::make_unique()); // File does not implement IComparable or IHashable (files cannot be meaningfully compared or hashed) auto result = repository.Add(std::move(file_vtable)); if (!result.has_value()) { @@ -183,14 +178,12 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // IntArray: wrapper around std::vector { VirtualTable int_array_vtable("IntArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - int_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); int_array_vtable.AddFunction("_destructor_", "_IntArray_destructor_"); int_array_vtable.AddFunction("_Equals__Object", "_IntArray_Equals__Object"); int_array_vtable.AddFunction("_IsLess__Object", "_IntArray_IsLess__Object"); int_array_vtable.AddFunction("_GetHash_", "_IntArray_GetHash_"); int_array_vtable.AddInterface("IComparable"); int_array_vtable.AddInterface("IHashable"); - int_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(int_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -200,14 +193,12 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // FloatArray: wrapper around std::vector { VirtualTable float_array_vtable("FloatArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - float_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); float_array_vtable.AddFunction("_destructor_", "_FloatArray_destructor_"); float_array_vtable.AddFunction("_Equals__Object", "_FloatArray_Equals__Object"); float_array_vtable.AddFunction("_IsLess__Object", "_FloatArray_IsLess__Object"); float_array_vtable.AddFunction("_GetHash_", "_FloatArray_GetHash_"); float_array_vtable.AddInterface("IComparable"); float_array_vtable.AddInterface("IHashable"); - float_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(float_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -217,14 +208,12 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // CharArray: wrapper around std::vector { VirtualTable char_array_vtable("CharArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - char_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); char_array_vtable.AddFunction("_destructor_", "_CharArray_destructor_"); char_array_vtable.AddFunction("_Equals__Object", "_CharArray_Equals__Object"); char_array_vtable.AddFunction("_IsLess__Object", "_CharArray_IsLess__Object"); char_array_vtable.AddFunction("_GetHash_", "_CharArray_GetHash_"); char_array_vtable.AddInterface("IComparable"); char_array_vtable.AddInterface("IHashable"); - char_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(char_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -235,14 +224,12 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // Special handling for byte view casting { VirtualTable byte_array_vtable("ByteArray", sizeof(ObjectDescriptor) + sizeof(ovum::vm::runtime::ByteArray)); - byte_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); byte_array_vtable.AddFunction("_destructor_", "_ByteArray_destructor_"); byte_array_vtable.AddFunction("_Equals__Object", "_ByteArray_Equals__Object"); byte_array_vtable.AddFunction("_IsLess__Object", "_ByteArray_IsLess__Object"); byte_array_vtable.AddFunction("_GetHash_", "_ByteArray_GetHash_"); byte_array_vtable.AddInterface("IComparable"); byte_array_vtable.AddInterface("IHashable"); - byte_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(byte_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -252,14 +239,12 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // BoolArray: wrapper around std::vector { VirtualTable bool_array_vtable("BoolArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - bool_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); bool_array_vtable.AddFunction("_destructor_", "_BoolArray_destructor_"); bool_array_vtable.AddFunction("_Equals__Object", "_BoolArray_Equals__Object"); bool_array_vtable.AddFunction("_IsLess__Object", "_BoolArray_IsLess__Object"); bool_array_vtable.AddFunction("_GetHash_", "_BoolArray_GetHash_"); bool_array_vtable.AddInterface("IComparable"); bool_array_vtable.AddInterface("IHashable"); - bool_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(bool_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -268,15 +253,13 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // ObjectArray: wrapper around std::vector { - VirtualTable object_array_vtable("ObjectArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - object_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); + VirtualTable object_array_vtable("ObjectArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); object_array_vtable.AddFunction("_destructor_", "_ObjectArray_destructor_"); object_array_vtable.AddFunction("_Equals__Object", "_ObjectArray_Equals__Object"); object_array_vtable.AddFunction("_IsLess__Object", "_ObjectArray_IsLess__Object"); object_array_vtable.AddFunction("_GetHash_", "_ObjectArray_GetHash_"); object_array_vtable.AddInterface("IComparable"); object_array_vtable.AddInterface("IHashable"); - object_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(object_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -285,15 +268,13 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // StringArray: more typed ObjectArray { - VirtualTable string_array_vtable("StringArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - string_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); + VirtualTable string_array_vtable("StringArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); string_array_vtable.AddFunction("_destructor_", "_StringArray_destructor_"); string_array_vtable.AddFunction("_Equals__Object", "_StringArray_Equals__Object"); string_array_vtable.AddFunction("_IsLess__Object", "_StringArray_IsLess__Object"); string_array_vtable.AddFunction("_GetHash_", "_StringArray_GetHash_"); string_array_vtable.AddInterface("IComparable"); string_array_vtable.AddInterface("IHashable"); - string_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(string_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); @@ -318,15 +299,13 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // PointerArray: unsafe array of pointers { - VirtualTable pointer_array_vtable("PointerArray", sizeof(ObjectDescriptor) + sizeof(std::vector)); - pointer_array_vtable.AddField("Object", sizeof(ObjectDescriptor)); + VirtualTable pointer_array_vtable("PointerArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); pointer_array_vtable.AddFunction("_destructor_", "_PointerArray_destructor_"); pointer_array_vtable.AddFunction("_Equals__Object", "_PointerArray_Equals__Object"); pointer_array_vtable.AddFunction("_IsLess__Object", "_PointerArray_IsLess__Object"); pointer_array_vtable.AddFunction("_GetHash_", "_PointerArray_GetHash_"); pointer_array_vtable.AddInterface("IComparable"); pointer_array_vtable.AddInterface("IHashable"); - pointer_array_vtable.SetReferenceScanner(std::make_unique()); auto result = repository.Add(std::move(pointer_array_vtable)); if (!result.has_value()) { return std::unexpected(result.error()); diff --git a/lib/runtime/VirtualTable.cpp b/lib/runtime/VirtualTable.cpp index 6339a53..0c5f397 100644 --- a/lib/runtime/VirtualTable.cpp +++ b/lib/runtime/VirtualTable.cpp @@ -3,6 +3,7 @@ #include #include "VariableAccessor.hpp" +#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" namespace ovum::vm::runtime { @@ -15,7 +16,11 @@ const std::unordered_map> Virtua {"Object", std::make_shared>()}, }; -VirtualTable::VirtualTable(std::string name, size_t size) : name_(std::move(name)), size_(size) { +VirtualTable::VirtualTable(std::string name, size_t size, std::unique_ptr scanner) : + name_(std::move(name)), size_(size), reference_scanner_(std::move(scanner)) { + if (!reference_scanner_) { + reference_scanner_ = std::make_unique(); + } } std::string VirtualTable::GetName() const { @@ -80,34 +85,12 @@ bool VirtualTable::IsType(const std::string& interface_name) const { return interfaces_.contains(interface_name) || interface_name == name_; } -void VirtualTable::SetReferenceScanner(std::unique_ptr scanner) { - reference_scanner_ = std::move(scanner); -} - size_t VirtualTable::GetFieldCount() const { return fields_.size(); } -int64_t VirtualTable::GetFieldOffset(size_t index) const { - return fields_[index].offset; -} - -std::shared_ptr VirtualTable::GetFieldAccessor(size_t index) const { - return fields_[index].variable_accessor; -} - -bool VirtualTable::IsFieldReferenceType(size_t index) const { - if (index >= fields_.size()) { - return false; - } - - return fields_[index].variable_accessor->IsReferenceType(); -} - void VirtualTable::ScanReferences(void* obj, const ReferenceVisitor& visitor) const { - if (reference_scanner_) { - reference_scanner_->Scan(obj, visitor); - } + reference_scanner_->Scan(obj, fields_, visitor); } } // namespace ovum::vm::runtime diff --git a/lib/runtime/VirtualTable.hpp b/lib/runtime/VirtualTable.hpp index 329a116..af58e4c 100644 --- a/lib/runtime/VirtualTable.hpp +++ b/lib/runtime/VirtualTable.hpp @@ -19,7 +19,7 @@ namespace ovum::vm::runtime { class VirtualTable { public: - VirtualTable(std::string name, size_t size); + VirtualTable(std::string name, size_t size, std::unique_ptr scanner = nullptr); [[nodiscard]] std::string GetName() const; [[nodiscard]] size_t GetSize() const; @@ -33,15 +33,11 @@ class VirtualTable { [[nodiscard]] bool IsType(const std::string& interface_name) const; [[nodiscard]] size_t GetFieldCount() const; - [[nodiscard]] bool IsFieldReferenceType(size_t index) const; - [[nodiscard]] int64_t GetFieldOffset(size_t index) const; - [[nodiscard]] std::shared_ptr GetFieldAccessor(size_t index) const; void AddFunction(const FunctionId& virtual_function_id, const FunctionId& real_function_id); size_t AddField(const std::string& type_name, int64_t offset); void AddInterface(const std::string& interface_name); - void SetReferenceScanner(std::unique_ptr scanner); void ScanReferences(void* obj, const ReferenceVisitor& visitor) const; private: diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp index 08d46d6..5ead9ea 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.cpp @@ -6,7 +6,7 @@ namespace ovum::vm::runtime { -void ArrayReferenceScanner::Scan(void* obj, const ReferenceVisitor& visitor) const { +void ArrayReferenceScanner::Scan(void* obj, const std::vector&, const ReferenceVisitor& visitor) const { const char* base = reinterpret_cast(obj) + sizeof(ObjectDescriptor); const std::vector& vec = *reinterpret_cast*>(base); diff --git a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp index 615f250..0718c19 100644 --- a/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp @@ -7,7 +7,7 @@ namespace ovum::vm::runtime { class ArrayReferenceScanner : public IReferenceScanner { public: - void Scan(void* obj, const ReferenceVisitor& visitor) const override; + void Scan(void* obj, const std::vector& fields, const ReferenceVisitor& visitor) const override; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp index 6c94743..bbde931 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp @@ -3,29 +3,20 @@ #include -#include "lib/runtime/FieldInfo.hpp" #include "lib/runtime/Variable.hpp" -#include "lib/runtime/VirtualTable.hpp" +#include "lib/runtime/FieldInfo.hpp" namespace ovum::vm::runtime { -DefaultReferenceScanner::DefaultReferenceScanner(const VirtualTable& vt) { - for (size_t i = 0; i < vt.GetFieldCount(); ++i) { - if (vt.IsFieldReferenceType(i)) { - reference_fields_.emplace_back(vt.GetFieldOffset(i), vt.GetFieldAccessor(i)); - } - } -} - -void DefaultReferenceScanner::Scan(void* obj, const ReferenceVisitor& visitor) const { - for (const FieldInfo& field : reference_fields_) { - Variable var_res = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); +void DefaultReferenceScanner::Scan(void* obj, const std::vector& fields, const ReferenceVisitor& visitor) const { + for (const FieldInfo& field : fields) { + const Variable& var = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); - if (!std::holds_alternative(var_res)) { + if (!std::holds_alternative(var)) { continue; } - visitor(std::get(var_res)); + visitor(std::get(var)); } } diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp index 62ba570..a3b619f 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp @@ -1,23 +1,13 @@ #ifndef RUNTIME_DEFAULTREFERENCESCANNER_HPP #define RUNTIME_DEFAULTREFERENCESCANNER_HPP -#include - -#include "lib/runtime/FieldInfo.hpp" -#include "lib/runtime/VirtualTable.hpp" - #include "IReferenceScanner.hpp" namespace ovum::vm::runtime { class DefaultReferenceScanner : public IReferenceScanner { public: - explicit DefaultReferenceScanner(const VirtualTable& vt); - - void Scan(void* obj, const ReferenceVisitor& visitor) const override; - -private: - std::vector reference_fields_; + void Scan(void* obj, const std::vector& fields, const ReferenceVisitor& visitor) const override; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp deleted file mode 100644 index 0eaef36..0000000 --- a/lib/runtime/gc/reference_scanners/EmptyReferenceScanner.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef RUNTIME_EMPTYREFERENCESCANNER_HPP -#define RUNTIME_EMPTYREFERENCESCANNER_HPP - -#include "IReferenceScanner.hpp" - -namespace ovum::vm::runtime { - -class EmptyReferenceScanner : public IReferenceScanner { -public: - void Scan(void*, const ReferenceVisitor&) const override { - // No references to scan - } -}; - -} // namespace ovum::vm::runtime - -#endif // RUNTIME_EMPTYREFERENCESCANNER_HPP diff --git a/lib/runtime/gc/reference_scanners/IReferenceScanner.hpp b/lib/runtime/gc/reference_scanners/IReferenceScanner.hpp index dd50733..21adbbe 100644 --- a/lib/runtime/gc/reference_scanners/IReferenceScanner.hpp +++ b/lib/runtime/gc/reference_scanners/IReferenceScanner.hpp @@ -2,6 +2,9 @@ #define RUNTIME_IREFERENCESCANNER_HPP #include +#include + +#include "lib/runtime/FieldInfo.hpp" namespace ovum::vm::runtime { @@ -10,7 +13,7 @@ using ReferenceVisitor = std::function; class IReferenceScanner { // NOLINT(cppcoreguidelines-special-member-functions) public: virtual ~IReferenceScanner() = default; - virtual void Scan(void* obj, const ReferenceVisitor& visitor) const = 0; + virtual void Scan(void* obj, const std::vector& fields, const ReferenceVisitor& visitor) const = 0; }; } // namespace ovum::vm::runtime From 15c3231966d24f0be09633269d574a4fdf9d55e5 Mon Sep 17 00:00:00 2001 From: bialger Date: Fri, 9 Jan 2026 05:10:10 +0300 Subject: [PATCH 46/59] refactor: improve code readability by formatting constructor calls in BuiltinFactory and optimizing VirtualTableRepository's name lookup --- lib/executor/builtin_factory.cpp | 12 +++++++++--- lib/runtime/IVariableAccessor.hpp | 1 - lib/runtime/VariableAccessor.hpp | 4 ---- lib/runtime/VirtualTableRepository.cpp | 12 ++++++++---- .../reference_scanners/DefaultReferenceScanner.cpp | 6 ++++-- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/executor/builtin_factory.cpp b/lib/executor/builtin_factory.cpp index 40076c4..70ab73c 100644 --- a/lib/executor/builtin_factory.cpp +++ b/lib/executor/builtin_factory.cpp @@ -253,7 +253,9 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // ObjectArray: wrapper around std::vector { - VirtualTable object_array_vtable("ObjectArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); + VirtualTable object_array_vtable("ObjectArray", + sizeof(ObjectDescriptor) + sizeof(std::vector), + std::make_unique()); object_array_vtable.AddFunction("_destructor_", "_ObjectArray_destructor_"); object_array_vtable.AddFunction("_Equals__Object", "_ObjectArray_Equals__Object"); object_array_vtable.AddFunction("_IsLess__Object", "_ObjectArray_IsLess__Object"); @@ -268,7 +270,9 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // StringArray: more typed ObjectArray { - VirtualTable string_array_vtable("StringArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); + VirtualTable string_array_vtable("StringArray", + sizeof(ObjectDescriptor) + sizeof(std::vector), + std::make_unique()); string_array_vtable.AddFunction("_destructor_", "_StringArray_destructor_"); string_array_vtable.AddFunction("_Equals__Object", "_StringArray_Equals__Object"); string_array_vtable.AddFunction("_IsLess__Object", "_StringArray_IsLess__Object"); @@ -299,7 +303,9 @@ std::expected RegisterBuiltinVirtualTables(VirtualTabl // PointerArray: unsafe array of pointers { - VirtualTable pointer_array_vtable("PointerArray", sizeof(ObjectDescriptor) + sizeof(std::vector), std::make_unique()); + VirtualTable pointer_array_vtable("PointerArray", + sizeof(ObjectDescriptor) + sizeof(std::vector), + std::make_unique()); pointer_array_vtable.AddFunction("_destructor_", "_PointerArray_destructor_"); pointer_array_vtable.AddFunction("_Equals__Object", "_PointerArray_Equals__Object"); pointer_array_vtable.AddFunction("_IsLess__Object", "_PointerArray_IsLess__Object"); diff --git a/lib/runtime/IVariableAccessor.hpp b/lib/runtime/IVariableAccessor.hpp index 3c82200..8b3fbd9 100644 --- a/lib/runtime/IVariableAccessor.hpp +++ b/lib/runtime/IVariableAccessor.hpp @@ -14,7 +14,6 @@ class IVariableAccessor { // NOLINT(cppcoreguidelines-special-member-functions) virtual Variable GetVariable(void* value_ptr) const = 0; virtual std::expected WriteVariable(void* value_ptr, const Variable& variable) const = 0; - [[nodiscard]] virtual bool IsReferenceType() const = 0; }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/VariableAccessor.hpp b/lib/runtime/VariableAccessor.hpp index 6a8fd71..b60c390 100644 --- a/lib/runtime/VariableAccessor.hpp +++ b/lib/runtime/VariableAccessor.hpp @@ -25,10 +25,6 @@ class VariableAccessor : public IVariableAccessor { return {}; } - - [[nodiscard]] bool IsReferenceType() const override { - return std::is_same_v; - } }; } // namespace ovum::vm::runtime diff --git a/lib/runtime/VirtualTableRepository.cpp b/lib/runtime/VirtualTableRepository.cpp index b9f7d7b..151bb97 100644 --- a/lib/runtime/VirtualTableRepository.cpp +++ b/lib/runtime/VirtualTableRepository.cpp @@ -38,20 +38,24 @@ std::expected VirtualTableRepository::G } std::expected VirtualTableRepository::GetByName(const std::string& name) { - if (index_by_name_.find(name) == index_by_name_.end()) { + const auto it = index_by_name_.find(name); + + if (it == index_by_name_.end()) { return std::unexpected(std::runtime_error("VirtualTable not found by name: " + name)); } - return &vtables_[index_by_name_.at(name)]; + return &vtables_[it->second]; } std::expected VirtualTableRepository::GetByName( const std::string& name) const { - if (index_by_name_.find(name) == index_by_name_.end()) { + const auto it = index_by_name_.find(name); + + if (it == index_by_name_.end()) { return std::unexpected(std::runtime_error("VirtualTable not found by name: " + name)); } - return &vtables_[index_by_name_.at(name)]; + return &vtables_[it->second]; } std::expected VirtualTableRepository::GetIndexByName(const std::string& name) const { diff --git a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp index bbde931..9aefad8 100644 --- a/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp +++ b/lib/runtime/gc/reference_scanners/DefaultReferenceScanner.cpp @@ -3,12 +3,14 @@ #include -#include "lib/runtime/Variable.hpp" #include "lib/runtime/FieldInfo.hpp" +#include "lib/runtime/Variable.hpp" namespace ovum::vm::runtime { -void DefaultReferenceScanner::Scan(void* obj, const std::vector& fields, const ReferenceVisitor& visitor) const { +void DefaultReferenceScanner::Scan(void* obj, + const std::vector& fields, + const ReferenceVisitor& visitor) const { for (const FieldInfo& field : fields) { const Variable& var = field.variable_accessor->GetVariable(reinterpret_cast(obj) + field.offset); From 4d7a60a50af4f096b84d9eae5286c5eb7bb1b728 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Fri, 9 Jan 2026 22:57:41 +0300 Subject: [PATCH 47/59] fix: changed gc trigger placement --- lib/execution_tree/Command.hpp | 6 ++++++ lib/runtime/MemoryManager.cpp | 36 ++++++++++++++++++++++++---------- lib/runtime/MemoryManager.hpp | 2 ++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/execution_tree/Command.hpp b/lib/execution_tree/Command.hpp index e593079..de9df66 100644 --- a/lib/execution_tree/Command.hpp +++ b/lib/execution_tree/Command.hpp @@ -35,6 +35,12 @@ class Command : public IExecutable { return std::unexpected(std::runtime_error(error_message)); } + auto gc_res = execution_data.memory_manager.CollectGarbageIfRequired(execution_data); + + if (!gc_res) { + return std::unexpected(gc_res.error()); + } + return result.value(); } diff --git a/lib/runtime/MemoryManager.cpp b/lib/runtime/MemoryManager.cpp index b000ac1..7c97feb 100644 --- a/lib/runtime/MemoryManager.cpp +++ b/lib/runtime/MemoryManager.cpp @@ -13,20 +13,12 @@ namespace ovum::vm::runtime { MemoryManager::MemoryManager(std::unique_ptr gc, size_t max_objects) : - gc_(std::move(gc)), gc_threshold_(max_objects) { + gc_(std::move(gc)), gc_threshold_(max_objects), gc_in_progress_(false) { } std::expected MemoryManager::AllocateObject(const VirtualTable& vtable, uint32_t vtable_index, execution_tree::PassedExecutionData& data) { - if (repo_.GetCount() > gc_threshold_) { - std::expected collect_res = CollectGarbage(data); - - if (!collect_res.has_value()) { - return std::unexpected(collect_res.error()); - } - } - const size_t total_size = vtable.GetSize(); char* raw_memory = nullptr; @@ -109,6 +101,8 @@ std::expected MemoryManager::DeallocateObject(void* ob } std::expected MemoryManager::Clear(execution_tree::PassedExecutionData& data) { + gc_in_progress_ = true; + std::vector objects_to_clear; objects_to_clear.reserve(repo_.GetCount()); @@ -174,6 +168,8 @@ std::expected MemoryManager::Clear(execution_tree::Pas return std::unexpected(*first_error); } + gc_in_progress_ = false; + return {}; } @@ -182,7 +178,27 @@ std::expected MemoryManager::CollectGarbage(execution_ return std::unexpected(std::runtime_error("MemoryManager: No GC configured")); } - return gc_->Collect(data); + if (!gc_in_progress_) { + gc_in_progress_ = true; + auto gc_res = gc_->Collect(data); + gc_in_progress_ = false; + return gc_res; + } + + return {}; +} + +std::expected MemoryManager::CollectGarbageIfRequired( + execution_tree::PassedExecutionData& data) { + if (repo_.GetCount() > gc_threshold_) { + std::expected collect_res = CollectGarbage(data); + + if (!collect_res.has_value()) { + return std::unexpected(collect_res.error()); + } + } + + return {}; } const ObjectRepository& MemoryManager::GetRepository() const { diff --git a/lib/runtime/MemoryManager.hpp b/lib/runtime/MemoryManager.hpp index 527fd85..085699b 100644 --- a/lib/runtime/MemoryManager.hpp +++ b/lib/runtime/MemoryManager.hpp @@ -25,6 +25,7 @@ class MemoryManager { execution_tree::PassedExecutionData& data); std::expected DeallocateObject(void* obj, execution_tree::PassedExecutionData& data); std::expected CollectGarbage(execution_tree::PassedExecutionData& data); + std::expected CollectGarbageIfRequired(execution_tree::PassedExecutionData& data); std::expected Clear(execution_tree::PassedExecutionData& data); [[nodiscard]] const ObjectRepository& GetRepository() const; @@ -34,6 +35,7 @@ class MemoryManager { std::allocator allocator_; std::unique_ptr gc_; size_t gc_threshold_; + bool gc_in_progress_; }; } // namespace ovum::vm::runtime From 9a39e496e7d5998260cff0c61c706c609286bfb2 Mon Sep 17 00:00:00 2001 From: bialger Date: Sat, 10 Jan 2026 21:31:57 +0300 Subject: [PATCH 48/59] fix: correct value retrieval in NullCoalesce function to ensure proper stack manipulation --- lib/execution_tree/BytecodeCommands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/execution_tree/BytecodeCommands.cpp b/lib/execution_tree/BytecodeCommands.cpp index 5b668ce..5c30e31 100644 --- a/lib/execution_tree/BytecodeCommands.cpp +++ b/lib/execution_tree/BytecodeCommands.cpp @@ -1457,7 +1457,7 @@ std::expected NullCoalesce(PassedExecutionD if (*tested_result_data != nullptr) { data.memory.machine_stack.pop(); - data.memory.machine_stack.emplace(tested_result.value()); + data.memory.machine_stack.emplace(*tested_result_data); } return ExecutionResult::kNormal; From 8ef755da4294a74c67628ad7ef61bfd3df04d1b9 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sat, 10 Jan 2026 22:48:26 +0300 Subject: [PATCH 49/59] tests: add unit-tests for gc --- tests/CMakeLists.txt | 2 + tests/gc_tests.cpp | 198 ++++++++++++++++++++++++++++++ tests/test_suites/GcTestSuite.cpp | 167 +++++++++++++++++++++++++ tests/test_suites/GcTestSuite.hpp | 61 +++++++++ 4 files changed, 428 insertions(+) create mode 100644 tests/gc_tests.cpp create mode 100644 tests/test_suites/GcTestSuite.cpp create mode 100644 tests/test_suites/GcTestSuite.hpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 16d5c66..11bbab1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,8 @@ add_executable( bytecode_parser_tests.cpp bytecode_commands_tests.cpp builtin_functions_tests.cpp + gc_tests.cpp + test_suites/GcTestSuite.cpp ) target_link_libraries( diff --git a/tests/gc_tests.cpp b/tests/gc_tests.cpp new file mode 100644 index 0000000..c9d73c9 --- /dev/null +++ b/tests/gc_tests.cpp @@ -0,0 +1,198 @@ +#include + +#include "tests/test_suites/GcTestSuite.hpp" + +#include +#include +#include +#include + +#include "lib/execution_tree/ExecutionResult.hpp" +#include "lib/execution_tree/FunctionRepository.hpp" +#include "lib/execution_tree/PassedExecutionData.hpp" +#include "lib/runtime/MemoryManager.hpp" +#include "lib/runtime/ObjectDescriptor.hpp" +#include "lib/runtime/ObjectRepository.hpp" +#include "lib/runtime/VirtualTable.hpp" +#include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/gc/MarkAndSweepGC.hpp" +#include "lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp" +#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" + +TEST_F(GcTestSuite, UnreachableObjectCollected) { + auto data = MakeFreshData(); + + void* obj = AllocateTestObject("Simple", data); + + auto before = SnapshotRepo(mm_.GetRepository()); + ASSERT_EQ(before.size(), 1u); + ASSERT_TRUE(RepoContains(mm_.GetRepository(), obj)); + + CollectGarbage(data); + + auto after = SnapshotRepo(mm_.GetRepository()); + EXPECT_EQ(after.size(), 0u); + EXPECT_FALSE(RepoContains(mm_.GetRepository(), obj)); +} + +TEST_F(GcTestSuite, RootInGlobalVariablesSurvives) { + auto data = MakeFreshData(); + + void* obj = AllocateTestObject("Simple", data); + data.memory.global_variables.emplace_back(obj); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), obj)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 1u); +} + +TEST_F(GcTestSuite, RootInMachineStackSurvives) { + auto data = MakeFreshData(); + + void* obj = AllocateTestObject("Simple", data); + data.memory.machine_stack.emplace(obj); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), obj)); +} + +TEST_F(GcTestSuite, TransitiveReachabilityThroughField) { + auto data = MakeFreshData(); + + void* root = AllocateTestObject("WithRef", data); + void* child = AllocateTestObject("Simple", data); + + SetRef(root, child); + + data.memory.global_variables.emplace_back(root); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), root)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), child)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 2u); +} + +TEST_F(GcTestSuite, TransitiveReachabilityThroughArray) { + auto data = MakeFreshData(); + + void* arr = AllocateTestObject("Array", data); + InitArray(arr); + + void* child1 = AllocateTestObject("Simple", data); + void* child2 = AllocateTestObject("Simple", data); + + AddToArray(arr, child1); + AddToArray(arr, child2); + + data.memory.global_variables.emplace_back(arr); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), arr)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), child1)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), child2)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 3u); +} + +TEST_F(GcTestSuite, CycleWithoutRootsCollected) { + auto data = MakeFreshData(); + + void* objA = AllocateTestObject("WithRef", data); + void* objB = AllocateTestObject("WithRef", data); + + SetRef(objA, objB); + SetRef(objB, objA); + + CollectGarbage(data); + + EXPECT_FALSE(RepoContains(mm_.GetRepository(), objA)); + EXPECT_FALSE(RepoContains(mm_.GetRepository(), objB)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 0u); +} + +TEST_F(GcTestSuite, CycleWithRootPreserved) { + auto data = MakeFreshData(); + + void* objA = AllocateTestObject("WithRef", data); + void* objB = AllocateTestObject("WithRef", data); + + SetRef(objA, objB); + SetRef(objB, objA); + + data.memory.global_variables.emplace_back(objA); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), objA)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), objB)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 2u); +} + +TEST_F(GcTestSuite, MultipleRootsDifferentPlaces) { + auto data = MakeFreshData(); + + void* global = AllocateTestObject("Simple", data); + void* stack = AllocateTestObject("Simple", data); + void* local = AllocateTestObject("Simple", data); + + data.memory.global_variables.emplace_back(global); + data.memory.machine_stack.emplace(stack); + + ovum::vm::runtime::StackFrame frame; + frame.local_variables.emplace_back(local); + data.memory.stack_frames.push(std::move(frame)); + + CollectGarbage(data); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), global)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), stack)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), local)); + EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 3u); +} + +TEST_F(GcTestSuite, SmallThresholdFrequentAllocations) { + auto data = MakeFreshData(3); + + std::vector objects; + std::vector should_survive; + + const int k_iterations = 15; + + for (int i = 0; i < k_iterations; ++i) { + void* obj = AllocateTestObject("Simple", data); + objects.push_back(obj); + + if (i % 3 == 0) { + data.memory.global_variables.emplace_back(obj); + should_survive.push_back(obj); + } + + auto gc_res = data.memory_manager.CollectGarbageIfRequired(data); + ASSERT_TRUE(gc_res.has_value()); + } + + CollectGarbage(data); + + auto snapshot = SnapshotRepo(mm_.GetRepository()); + EXPECT_GE(snapshot.size(), should_survive.size()); + + for (void* obj : should_survive) { + EXPECT_TRUE(RepoContains(mm_.GetRepository(), obj)); + } +} + +TEST_F(GcTestSuite, NullReferencesNotCrashing) { + auto data = MakeFreshData(); + + void* root = AllocateTestObject("WithRef", data); + SetRef(root, nullptr); + + data.memory.global_variables.emplace_back(root); + + ASSERT_NO_FATAL_FAILURE(CollectGarbage(data)); + + EXPECT_TRUE(RepoContains(mm_.GetRepository(), root)); +} diff --git a/tests/test_suites/GcTestSuite.cpp b/tests/test_suites/GcTestSuite.cpp new file mode 100644 index 0000000..53506eb --- /dev/null +++ b/tests/test_suites/GcTestSuite.cpp @@ -0,0 +1,167 @@ +#include "GcTestSuite.hpp" +#include "lib/execution_tree/Block.hpp" +#include "lib/execution_tree/Command.hpp" +#include "lib/execution_tree/Function.hpp" +#include "lib/execution_tree/PureFunction.hpp" +#include "lib/executor/builtin_factory.hpp" + +#include + +GcTestSuite::GcTestSuite() : vtr_(), fr_(), mm_(std::make_unique(), kDefaultGCThreshold), rm_() { +} + +ovum::vm::execution_tree::PassedExecutionData GcTestSuite::MakeFreshData(uint64_t gc_threshold) { + mm_ = ovum::vm::runtime::MemoryManager(std::make_unique(), gc_threshold); + ovum::vm::execution_tree::PassedExecutionData data{.memory = rm_, + .virtual_table_repository = vtr_, + .function_repository = fr_, + .memory_manager = mm_, + .input_stream = std::cin, + .output_stream = std::cout, + .error_stream = std::cerr}; + return data; +} + +ovum::vm::execution_tree::ExecutionResult NoOpDestructor(ovum::vm::execution_tree::PassedExecutionData& /*data*/) { + return ovum::vm::execution_tree::ExecutionResult::kNormal; +} + +void GcTestSuite::SetUp() { + vtr_ = ovum::vm::runtime::VirtualTableRepository(); + + fr_ = ovum::vm::execution_tree::FunctionRepository(); + + vtr_ = ovum::vm::runtime::VirtualTableRepository(); + + fr_ = ovum::vm::execution_tree::FunctionRepository(); + + { + auto vt_res = ovum::vm::runtime::RegisterBuiltinVirtualTables(vtr_); + ASSERT_TRUE(vt_res.has_value()) << "Failed builtin vtables: " << vt_res.error().what(); + } + + { + auto fn_res = ovum::vm::execution_tree::RegisterBuiltinFunctions(fr_); + ASSERT_TRUE(fn_res.has_value()) << "Failed builtin functions: " << fn_res.error().what(); + } + + RegisterTestVtables(); + + RegisterNoOpDestructors(); + + auto gc = std::make_unique(); + mm_ = ovum::vm::runtime::MemoryManager(std::move(gc), 10); +} + +void GcTestSuite::RegisterTestVtables() { + // Simple + { + ovum::vm::runtime::VirtualTable vt("Simple", sizeof(ovum::vm::runtime::ObjectDescriptor)); + vt.AddFunction("_destructor_", "_Simple_destructor_"); + auto res = vtr_.Add(std::move(vt)); + ASSERT_TRUE(res.has_value()) << "Failed Simple: " << res.error().what(); + } + + // WithRef + { + ovum::vm::runtime::VirtualTable vt("WithRef", sizeof(ovum::vm::runtime::ObjectDescriptor) + sizeof(void*)); + vt.AddFunction("_destructor_", "_WithRef_destructor_"); + vt.AddField("Object", sizeof(ovum::vm::runtime::ObjectDescriptor)); + + auto res = vtr_.Add(std::move(vt)); + ASSERT_TRUE(res.has_value()); + } + + // Array + { + auto scanner = std::make_unique(); + ovum::vm::runtime::VirtualTable vt( + "Array", sizeof(ovum::vm::runtime::ObjectDescriptor) + sizeof(std::vector), std::move(scanner)); + vt.AddFunction("_destructor_", "_Array_destructor_"); + auto res = vtr_.Add(std::move(vt)); + ASSERT_TRUE(res.has_value()); + } +} + +void GcTestSuite::RegisterNoOpDestructors() { + const std::vector types = {"Simple", "WithRef", "Array"}; + + for (const auto& type : types) { + std::string func_name = "_" + type + "_destructor_"; + + // Создаём простое тело — блок с одной командой (no-op) + auto body = std::make_unique(); + + auto no_op_cmd = [](ovum::vm::execution_tree::PassedExecutionData& d) + -> std::expected { return NoOpDestructor(d); }; + + body->AddStatement(std::make_unique>(no_op_cmd)); + + auto function = std::make_unique(func_name, 1u, std::move(body)); + + auto res = fr_.Add(std::move(function)); + + ASSERT_TRUE(res.has_value()) << "Failed to register destructor for " << type << ": " << res.error().what(); + } +} + +std::unordered_set GcTestSuite::SnapshotRepo(const ovum::vm::runtime::ObjectRepository& repo) const { + std::unordered_set snapshot; + repo.ForAll([&snapshot](void* obj) { snapshot.insert(obj); }); + return snapshot; +} + +std::string GcTestSuite::TypeOf(void* obj, const ovum::vm::execution_tree::PassedExecutionData& data) const { + if (!obj) + return ""; + auto* desc = reinterpret_cast(obj); + auto vt_res = data.virtual_table_repository.GetByIndex(desc->vtable_index); + if (!vt_res.has_value()) + return ""; + return vt_res.value()->GetName(); +} + +bool GcTestSuite::RepoContains(const ovum::vm::runtime::ObjectRepository& repo, void* obj) const { + bool found = false; + repo.ForAll([&](void* o) { + if (o == obj) + found = true; + }); + return found; +} + +void* GcTestSuite::AllocateTestObject(const std::string& type_name, + ovum::vm::execution_tree::PassedExecutionData& data) { + auto vt_res = data.virtual_table_repository.GetByName(type_name); + EXPECT_TRUE(vt_res.has_value()) << "VTable not found: " << type_name; + + auto idx_res = data.virtual_table_repository.GetIndexByName(type_name); + EXPECT_TRUE(idx_res.has_value()); + + auto alloc_res = data.memory_manager.AllocateObject(*vt_res.value(), static_cast(*idx_res), data); + + EXPECT_TRUE(alloc_res.has_value()) << "Allocation failed for " << type_name; + return alloc_res.value(); +} + +void GcTestSuite::SetRef(void* obj, void* target) { + void** ptr = reinterpret_cast(reinterpret_cast(obj) + sizeof(ovum::vm::runtime::ObjectDescriptor)); + *ptr = target; +} + +void GcTestSuite::InitArray(void* array_obj) { + auto* vec = reinterpret_cast*>(reinterpret_cast(array_obj) + + sizeof(ovum::vm::runtime::ObjectDescriptor)); + new (vec) std::vector(); +} + +void GcTestSuite::AddToArray(void* array_obj, void* item) { + auto* vec = reinterpret_cast*>(reinterpret_cast(array_obj) + + sizeof(ovum::vm::runtime::ObjectDescriptor)); + vec->push_back(item); +} + +void GcTestSuite::CollectGarbage(ovum::vm::execution_tree::PassedExecutionData& data) { + auto res = data.memory_manager.CollectGarbage(data); + ASSERT_TRUE(res.has_value()) << "GC failed: " << res.error().what(); +} diff --git a/tests/test_suites/GcTestSuite.hpp b/tests/test_suites/GcTestSuite.hpp new file mode 100644 index 0000000..50474ee --- /dev/null +++ b/tests/test_suites/GcTestSuite.hpp @@ -0,0 +1,61 @@ +#ifndef GC_TEST_SUITE_HPP +#define GC_TEST_SUITE_HPP + +#include + +#include +#include +#include +#include + +#include "lib/execution_tree/ExecutionResult.hpp" +#include "lib/execution_tree/FunctionRepository.hpp" +#include "lib/execution_tree/PassedExecutionData.hpp" +#include "lib/runtime/MemoryManager.hpp" +#include "lib/runtime/ObjectDescriptor.hpp" +#include "lib/runtime/ObjectRepository.hpp" +#include "lib/runtime/VirtualTable.hpp" +#include "lib/runtime/VirtualTableRepository.hpp" +#include "lib/runtime/gc/MarkAndSweepGC.hpp" +#include "lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp" +#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" + +const uint64_t kDefaultGCThreshold = 100; + +ovum::vm::execution_tree::ExecutionResult NoOpDestructor(ovum::vm::execution_tree::PassedExecutionData& data); + +class GcTestSuite : public ::testing::Test { +protected: + GcTestSuite(); + + void SetUp() override; + + ovum::vm::execution_tree::PassedExecutionData MakeFreshData(uint64_t gc_threshold = kDefaultGCThreshold); + + void RegisterTestVtables(); + void RegisterNoOpDestructors(); + + [[nodiscard]] std::unordered_set SnapshotRepo(const ovum::vm::runtime::ObjectRepository& repo) const; + + std::string TypeOf(void* obj, const ovum::vm::execution_tree::PassedExecutionData& data) const; + + bool RepoContains(const ovum::vm::runtime::ObjectRepository& repo, void* obj) const; + + void* AllocateTestObject(const std::string& type_name, ovum::vm::execution_tree::PassedExecutionData& data); + + void SetRef(void* obj, void* target); + + void InitArray(void* array_obj); + + void AddToArray(void* array_obj, void* item); + + void CollectGarbage(ovum::vm::execution_tree::PassedExecutionData& data); + +protected: + ovum::vm::runtime::VirtualTableRepository vtr_; + ovum::vm::execution_tree::FunctionRepository fr_; + ovum::vm::runtime::MemoryManager mm_; + ovum::vm::runtime::RuntimeMemory rm_; +}; + +#endif // GC_TEST_SUITE_HPP From 32ba5b3c84d7724b0d96d8739a585a492cdc2b0e Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sat, 10 Jan 2026 22:54:37 +0300 Subject: [PATCH 50/59] fix: codestyle --- tests/test_suites/GcTestSuite.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_suites/GcTestSuite.cpp b/tests/test_suites/GcTestSuite.cpp index 53506eb..4404dd6 100644 --- a/tests/test_suites/GcTestSuite.cpp +++ b/tests/test_suites/GcTestSuite.cpp @@ -7,7 +7,8 @@ #include -GcTestSuite::GcTestSuite() : vtr_(), fr_(), mm_(std::make_unique(), kDefaultGCThreshold), rm_() { +GcTestSuite::GcTestSuite() : + vtr_(), fr_(), mm_(std::make_unique(), kDefaultGCThreshold), rm_() { } ovum::vm::execution_tree::PassedExecutionData GcTestSuite::MakeFreshData(uint64_t gc_threshold) { From e055261a5a43873f2432930b38bb3c33494fa518 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sat, 10 Jan 2026 23:36:24 +0300 Subject: [PATCH 51/59] fix: codestyle --- tests/gc_tests.cpp | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/tests/gc_tests.cpp b/tests/gc_tests.cpp index c9d73c9..8aa47b1 100644 --- a/tests/gc_tests.cpp +++ b/tests/gc_tests.cpp @@ -3,21 +3,9 @@ #include "tests/test_suites/GcTestSuite.hpp" #include -#include -#include #include -#include "lib/execution_tree/ExecutionResult.hpp" -#include "lib/execution_tree/FunctionRepository.hpp" #include "lib/execution_tree/PassedExecutionData.hpp" -#include "lib/runtime/MemoryManager.hpp" -#include "lib/runtime/ObjectDescriptor.hpp" -#include "lib/runtime/ObjectRepository.hpp" -#include "lib/runtime/VirtualTable.hpp" -#include "lib/runtime/VirtualTableRepository.hpp" -#include "lib/runtime/gc/MarkAndSweepGC.hpp" -#include "lib/runtime/gc/reference_scanners/ArrayReferenceScanner.hpp" -#include "lib/runtime/gc/reference_scanners/DefaultReferenceScanner.hpp" TEST_F(GcTestSuite, UnreachableObjectCollected) { auto data = MakeFreshData(); @@ -100,34 +88,34 @@ TEST_F(GcTestSuite, TransitiveReachabilityThroughArray) { TEST_F(GcTestSuite, CycleWithoutRootsCollected) { auto data = MakeFreshData(); - void* objA = AllocateTestObject("WithRef", data); - void* objB = AllocateTestObject("WithRef", data); + void* obj_a = AllocateTestObject("WithRef", data); + void* obj_b = AllocateTestObject("WithRef", data); - SetRef(objA, objB); - SetRef(objB, objA); + SetRef(obj_a, obj_b); + SetRef(obj_b, obj_a); CollectGarbage(data); - EXPECT_FALSE(RepoContains(mm_.GetRepository(), objA)); - EXPECT_FALSE(RepoContains(mm_.GetRepository(), objB)); + EXPECT_FALSE(RepoContains(mm_.GetRepository(), obj_a)); + EXPECT_FALSE(RepoContains(mm_.GetRepository(), obj_b)); EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 0u); } TEST_F(GcTestSuite, CycleWithRootPreserved) { auto data = MakeFreshData(); - void* objA = AllocateTestObject("WithRef", data); - void* objB = AllocateTestObject("WithRef", data); + void* obj_a = AllocateTestObject("WithRef", data); + void* obj_b = AllocateTestObject("WithRef", data); - SetRef(objA, objB); - SetRef(objB, objA); + SetRef(obj_a, obj_b); + SetRef(obj_b, obj_a); - data.memory.global_variables.emplace_back(objA); + data.memory.global_variables.emplace_back(obj_a); CollectGarbage(data); - EXPECT_TRUE(RepoContains(mm_.GetRepository(), objA)); - EXPECT_TRUE(RepoContains(mm_.GetRepository(), objB)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), obj_a)); + EXPECT_TRUE(RepoContains(mm_.GetRepository(), obj_b)); EXPECT_EQ(SnapshotRepo(mm_.GetRepository()).size(), 2u); } From c0bf3c0c3a13e4557be83f514e76c035b26d7660 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 00:17:58 +0300 Subject: [PATCH 52/59] fix: add TearDown for gc tests --- tests/test_suites/GcTestSuite.cpp | 27 ++++++++++++++------------- tests/test_suites/GcTestSuite.hpp | 2 ++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/test_suites/GcTestSuite.cpp b/tests/test_suites/GcTestSuite.cpp index 4404dd6..2e3f2d8 100644 --- a/tests/test_suites/GcTestSuite.cpp +++ b/tests/test_suites/GcTestSuite.cpp @@ -2,11 +2,8 @@ #include "lib/execution_tree/Block.hpp" #include "lib/execution_tree/Command.hpp" #include "lib/execution_tree/Function.hpp" -#include "lib/execution_tree/PureFunction.hpp" #include "lib/executor/builtin_factory.hpp" -#include - GcTestSuite::GcTestSuite() : vtr_(), fr_(), mm_(std::make_unique(), kDefaultGCThreshold), rm_() { } @@ -23,7 +20,7 @@ ovum::vm::execution_tree::PassedExecutionData GcTestSuite::MakeFreshData(uint64_ return data; } -ovum::vm::execution_tree::ExecutionResult NoOpDestructor(ovum::vm::execution_tree::PassedExecutionData& /*data*/) { +ovum::vm::execution_tree::ExecutionResult NoOpDestructor(ovum::vm::execution_tree::PassedExecutionData&) { return ovum::vm::execution_tree::ExecutionResult::kNormal; } @@ -32,10 +29,6 @@ void GcTestSuite::SetUp() { fr_ = ovum::vm::execution_tree::FunctionRepository(); - vtr_ = ovum::vm::runtime::VirtualTableRepository(); - - fr_ = ovum::vm::execution_tree::FunctionRepository(); - { auto vt_res = ovum::vm::runtime::RegisterBuiltinVirtualTables(vtr_); ASSERT_TRUE(vt_res.has_value()) << "Failed builtin vtables: " << vt_res.error().what(); @@ -51,11 +44,22 @@ void GcTestSuite::SetUp() { RegisterNoOpDestructors(); auto gc = std::make_unique(); - mm_ = ovum::vm::runtime::MemoryManager(std::move(gc), 10); + mm_ = ovum::vm::runtime::MemoryManager(std::move(gc), kDefaultGCThreshold); +} + +void GcTestSuite::TearDown() { + ovum::vm::execution_tree::PassedExecutionData data{.memory = rm_, + .virtual_table_repository = vtr_, + .function_repository = fr_, + .memory_manager = mm_, + .input_stream = std::cin, + .output_stream = std::cout, + .error_stream = std::cerr}; + auto res = data.memory_manager.Clear(data); + ASSERT_TRUE(res.has_value()) << "GC failed: " << res.error().what(); } void GcTestSuite::RegisterTestVtables() { - // Simple { ovum::vm::runtime::VirtualTable vt("Simple", sizeof(ovum::vm::runtime::ObjectDescriptor)); vt.AddFunction("_destructor_", "_Simple_destructor_"); @@ -63,7 +67,6 @@ void GcTestSuite::RegisterTestVtables() { ASSERT_TRUE(res.has_value()) << "Failed Simple: " << res.error().what(); } - // WithRef { ovum::vm::runtime::VirtualTable vt("WithRef", sizeof(ovum::vm::runtime::ObjectDescriptor) + sizeof(void*)); vt.AddFunction("_destructor_", "_WithRef_destructor_"); @@ -73,7 +76,6 @@ void GcTestSuite::RegisterTestVtables() { ASSERT_TRUE(res.has_value()); } - // Array { auto scanner = std::make_unique(); ovum::vm::runtime::VirtualTable vt( @@ -90,7 +92,6 @@ void GcTestSuite::RegisterNoOpDestructors() { for (const auto& type : types) { std::string func_name = "_" + type + "_destructor_"; - // Создаём простое тело — блок с одной командой (no-op) auto body = std::make_unique(); auto no_op_cmd = [](ovum::vm::execution_tree::PassedExecutionData& d) diff --git a/tests/test_suites/GcTestSuite.hpp b/tests/test_suites/GcTestSuite.hpp index 50474ee..f6bc4d9 100644 --- a/tests/test_suites/GcTestSuite.hpp +++ b/tests/test_suites/GcTestSuite.hpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -29,6 +30,7 @@ class GcTestSuite : public ::testing::Test { GcTestSuite(); void SetUp() override; + void TearDown() override; ovum::vm::execution_tree::PassedExecutionData MakeFreshData(uint64_t gc_threshold = kDefaultGCThreshold); From b3179db6968973759ba37cd7b4731f6af89c08ca Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 01:01:31 +0300 Subject: [PATCH 53/59] fix: add array destructor --- tests/test_suites/GcTestSuite.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/test_suites/GcTestSuite.cpp b/tests/test_suites/GcTestSuite.cpp index 2e3f2d8..087bbcc 100644 --- a/tests/test_suites/GcTestSuite.cpp +++ b/tests/test_suites/GcTestSuite.cpp @@ -56,7 +56,7 @@ void GcTestSuite::TearDown() { .output_stream = std::cout, .error_stream = std::cerr}; auto res = data.memory_manager.Clear(data); - ASSERT_TRUE(res.has_value()) << "GC failed: " << res.error().what(); + ASSERT_TRUE(res.has_value()) << "Clear failed: " << res.error().what(); } void GcTestSuite::RegisterTestVtables() { @@ -87,7 +87,7 @@ void GcTestSuite::RegisterTestVtables() { } void GcTestSuite::RegisterNoOpDestructors() { - const std::vector types = {"Simple", "WithRef", "Array"}; + const std::vector types = {"Simple", "WithRef"}; for (const auto& type : types) { std::string func_name = "_" + type + "_destructor_"; @@ -105,6 +105,31 @@ void GcTestSuite::RegisterNoOpDestructors() { ASSERT_TRUE(res.has_value()) << "Failed to register destructor for " << type << ": " << res.error().what(); } + + std::string func_name = "_Array_destructor_"; + + auto body = std::make_unique(); + + auto no_op_cmd = [](ovum::vm::execution_tree::PassedExecutionData& data) + -> std::expected { + if (!std::holds_alternative(data.memory.stack_frames.top().local_variables[0])) { + return std::unexpected(std::runtime_error("ArrayDestructor: invalid argument types")); + } + + using vector_type = std::vector; + void* obj_ptr = std::get(data.memory.stack_frames.top().local_variables[0]); + auto* vec_data = reinterpret_cast*>(reinterpret_cast(obj_ptr) + sizeof(ovum::vm::runtime::ObjectDescriptor)); + vec_data->~vector_type(); + return NoOpDestructor(data); + }; + + body->AddStatement(std::make_unique>(no_op_cmd)); + + auto function = std::make_unique(func_name, 1u, std::move(body)); + + auto res = fr_.Add(std::move(function)); + + ASSERT_TRUE(res.has_value()) << "Failed to register destructor for " << "Array" << ": " << res.error().what(); } std::unordered_set GcTestSuite::SnapshotRepo(const ovum::vm::runtime::ObjectRepository& repo) const { From de2997f4c609d9deb77b43fe478895c0ec309911 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 01:01:47 +0300 Subject: [PATCH 54/59] fix: codestyle --- tests/test_suites/GcTestSuite.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_suites/GcTestSuite.cpp b/tests/test_suites/GcTestSuite.cpp index 087bbcc..5e2c07a 100644 --- a/tests/test_suites/GcTestSuite.cpp +++ b/tests/test_suites/GcTestSuite.cpp @@ -118,7 +118,8 @@ void GcTestSuite::RegisterNoOpDestructors() { using vector_type = std::vector; void* obj_ptr = std::get(data.memory.stack_frames.top().local_variables[0]); - auto* vec_data = reinterpret_cast*>(reinterpret_cast(obj_ptr) + sizeof(ovum::vm::runtime::ObjectDescriptor)); + auto* vec_data = reinterpret_cast*>(reinterpret_cast(obj_ptr) + + sizeof(ovum::vm::runtime::ObjectDescriptor)); vec_data->~vector_type(); return NoOpDestructor(data); }; From 750c6e38840c2e02d7c15d75e1b8acf57a99192b Mon Sep 17 00:00:00 2001 From: bialger Date: Sun, 11 Jan 2026 01:14:09 +0300 Subject: [PATCH 55/59] fix: initialize structures in GetMemoryUsage for better safety and clarity --- lib/execution_tree/BytecodeCommands.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/execution_tree/BytecodeCommands.cpp b/lib/execution_tree/BytecodeCommands.cpp index 5c30e31..5afc5ed 100644 --- a/lib/execution_tree/BytecodeCommands.cpp +++ b/lib/execution_tree/BytecodeCommands.cpp @@ -2080,7 +2080,7 @@ std::expected GetMemoryUsage(PassedExecutio return std::unexpected(std::runtime_error("GetMemoryUsage: failed to get process memory info")); } #elif __APPLE__ - struct task_basic_info info; + struct task_basic_info info{}; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast(&info), &size); if (kerr == KERN_SUCCESS) { @@ -2090,14 +2090,15 @@ std::expected GetMemoryUsage(PassedExecutio } #else // Linux: Use getrusage() system call - struct rusage usage; + struct rusage usage{}; + constexpr size_t kBytesInRusageUnit = 1024; if (getrusage(RUSAGE_SELF, &usage) != 0) { return std::unexpected(std::runtime_error("GetMemoryUsage: getrusage() failed")); } // ru_maxrss is the maximum resident set size in kilobytes // Note: getrusage gives maximum usage, not current usage - memory_usage = static_cast(usage.ru_maxrss) * 1024; // Convert KB to bytes + memory_usage = static_cast(usage.ru_maxrss) * kBytesInRusageUnit; #endif data.memory.machine_stack.emplace(static_cast(memory_usage)); From 74c47759db06ff192f72a6c1733e71ddeeed4c0c Mon Sep 17 00:00:00 2001 From: bialger Date: Sun, 11 Jan 2026 01:14:44 +0300 Subject: [PATCH 56/59] fix: adjust structure initialization in GetMemoryUsage for consistency and clarity --- lib/execution_tree/BytecodeCommands.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/execution_tree/BytecodeCommands.cpp b/lib/execution_tree/BytecodeCommands.cpp index 5afc5ed..c8a540e 100644 --- a/lib/execution_tree/BytecodeCommands.cpp +++ b/lib/execution_tree/BytecodeCommands.cpp @@ -2080,7 +2080,7 @@ std::expected GetMemoryUsage(PassedExecutio return std::unexpected(std::runtime_error("GetMemoryUsage: failed to get process memory info")); } #elif __APPLE__ - struct task_basic_info info{}; + struct task_basic_info info {}; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast(&info), &size); if (kerr == KERN_SUCCESS) { @@ -2090,7 +2090,7 @@ std::expected GetMemoryUsage(PassedExecutio } #else // Linux: Use getrusage() system call - struct rusage usage{}; + struct rusage usage {}; constexpr size_t kBytesInRusageUnit = 1024; if (getrusage(RUSAGE_SELF, &usage) != 0) { return std::unexpected(std::runtime_error("GetMemoryUsage: getrusage() failed")); From f472477971a7c1aa36db0b470221f5b30199897f Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 01:37:00 +0300 Subject: [PATCH 57/59] fix: terminal symbols handling in BytecodeParser --- lib/bytecode_parser/ParsingSession.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/bytecode_parser/ParsingSession.cpp b/lib/bytecode_parser/ParsingSession.cpp index 967f42f..b1b4b65 100644 --- a/lib/bytecode_parser/ParsingSession.cpp +++ b/lib/bytecode_parser/ParsingSession.cpp @@ -1,6 +1,8 @@ #include "ParsingSession.hpp" #include +#include +#include namespace ovum::bytecode::parser { @@ -118,7 +120,8 @@ std::expected ParsingSession::ConsumeStringLit std::to_string(token->GetPosition().GetColumn()))); } - std::string value = Current()->GetLexeme(); + std::string value = + dynamic_cast(dynamic_cast(Current().get())->GetValue())->ToString(); value = value.substr(1, value.length() - 2); From 9e2ad2fd93bff39a415098003a0a45d5e61b5568 Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 02:09:07 +0300 Subject: [PATCH 58/59] fix: PushChar handling in parser --- .../scenarios/CommandFactory.cpp | 20 ++++++++++++++++++- .../scenarios/CommandFactory.hpp | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/bytecode_parser/scenarios/CommandFactory.cpp b/lib/bytecode_parser/scenarios/CommandFactory.cpp index 1566ec7..76fcd23 100644 --- a/lib/bytecode_parser/scenarios/CommandFactory.cpp +++ b/lib/bytecode_parser/scenarios/CommandFactory.cpp @@ -6,7 +6,8 @@ namespace ovum::bytecode::parser { -const std::unordered_set CommandFactory::kStringCommands = {"PushString", "PushChar"}; +const std::unordered_set CommandFactory::kStringCommands = {"PushString"}; +const std::unordered_set CommandFactory::kCharCommands = {"PushChar"}; const std::unordered_set CommandFactory::kIntegerCommands = { "PushInt", "PushByte", "Rotate", "LoadLocal", "SetLocal", "LoadStatic", "SetStatic", "GetField", "SetField"}; @@ -105,6 +106,23 @@ std::expected, BytecodeParserEr return std::move(cmd.value()); } + if (kCharCommands.contains(cmd_name)) { + std::expected value = ctx->ConsumeIntLiteral(); + + if (!value) { + return std::unexpected(value.error()); + } + + std::expected, std::out_of_range> cmd = + vm::execution_tree::CreateIntegerCommandByName(cmd_name, value.value()); + + if (!cmd) { + return std::unexpected(BytecodeParserError("Failed to create char command: " + cmd_name)); + } + + return std::move(cmd.value()); + } + std::expected, std::out_of_range> cmd = vm::execution_tree::CreateSimpleCommandByName(cmd_name); diff --git a/lib/bytecode_parser/scenarios/CommandFactory.hpp b/lib/bytecode_parser/scenarios/CommandFactory.hpp index 50c7d72..b38c69c 100644 --- a/lib/bytecode_parser/scenarios/CommandFactory.hpp +++ b/lib/bytecode_parser/scenarios/CommandFactory.hpp @@ -27,6 +27,7 @@ class CommandFactory : public ICommandFactory { static const std::unordered_set kFloatCommands; static const std::unordered_set kBooleanCommands; static const std::unordered_set kIdentCommands; + static const std::unordered_set kCharCommands; }; } // namespace ovum::bytecode::parser From 0a1d7ee219dfb76480ffc2a877348e2a6c9f083c Mon Sep 17 00:00:00 2001 From: Alena Mariasova Date: Sun, 11 Jan 2026 02:37:12 +0300 Subject: [PATCH 59/59] tests: + pushchar - newarray --- lib/bytecode_parser/CMakeLists.txt | 1 - .../scenarios/CommandFactory.cpp | 2 +- .../scenarios/PlaceholderCommandFactory.cpp | 79 ------------------- .../scenarios/PlaceholderCommandFactory.hpp | 21 ----- tests/bytecode_parser_tests.cpp | 13 +-- tests/test_suites/BytecodeParserTestSuite.hpp | 4 +- 6 files changed, 5 insertions(+), 115 deletions(-) delete mode 100644 lib/bytecode_parser/scenarios/PlaceholderCommandFactory.cpp delete mode 100644 lib/bytecode_parser/scenarios/PlaceholderCommandFactory.hpp diff --git a/lib/bytecode_parser/CMakeLists.txt b/lib/bytecode_parser/CMakeLists.txt index 2c4e6f8..9ee368e 100644 --- a/lib/bytecode_parser/CMakeLists.txt +++ b/lib/bytecode_parser/CMakeLists.txt @@ -9,7 +9,6 @@ add_library(bytecode_parser STATIC scenarios/VtableParser.cpp scenarios/CommandFactory.cpp scenarios/FunctionFactory.cpp - scenarios/PlaceholderCommandFactory.cpp ) diff --git a/lib/bytecode_parser/scenarios/CommandFactory.cpp b/lib/bytecode_parser/scenarios/CommandFactory.cpp index 76fcd23..ecfc1c4 100644 --- a/lib/bytecode_parser/scenarios/CommandFactory.cpp +++ b/lib/bytecode_parser/scenarios/CommandFactory.cpp @@ -17,7 +17,7 @@ const std::unordered_set CommandFactory::kFloatCommands = {"PushFlo const std::unordered_set CommandFactory::kBooleanCommands = {"PushBool"}; const std::unordered_set CommandFactory::kIdentCommands = { - "NewArray", "Call", "CallVirtual", "CallConstructor", "GetVTable", "SetVTable", "SafeCall", "IsType", "SizeOf"}; + "Call", "CallVirtual", "CallConstructor", "GetVTable", "SetVTable", "SafeCall", "IsType", "SizeOf"}; std::expected, BytecodeParserError> CommandFactory::CreateCommand( const std::string& cmd_name, std::shared_ptr ctx) const { diff --git a/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.cpp b/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.cpp deleted file mode 100644 index c75baae..0000000 --- a/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "PlaceholderCommandFactory.hpp" - -#include - -#include "lib/execution_tree/Command.hpp" - -namespace ovum::bytecode::parser { - -namespace { -const std::unordered_set kStringCommands = {"PushString", "PushChar"}; - -const std::unordered_set kIntegerCommands = { - "PushInt", "PushByte", "Rotate", "LoadLocal", "SetLocal", "LoadStatic", "SetStatic", "GetField", "SetField"}; - -const std::unordered_set kFloatCommands = {"PushFloat"}; - -const std::unordered_set kBooleanCommands = {"PushBool"}; - -const std::unordered_set kIdentCommands = { - "NewArray", "Call", "CallVirtual", "CallConstructor", "GetVTable", "SetVTable", "SafeCall", "IsType", "SizeOf"}; - -static const auto kStubCommand = [](const std::string& full_name) -> std::unique_ptr { - auto cmd_func = [full_name](vm::execution_tree::PassedExecutionData&) - -> std::expected { - return std::unexpected(std::runtime_error("Command not implemented: " + full_name)); - }; - return std::make_unique>(std::move(cmd_func)); -}; -} // namespace - -std::expected, BytecodeParserError> -PlaceholderCommandFactory::CreateCommand(const std::string& cmd_name, std::shared_ptr ctx) const { - // Consume arguments based on command type (same logic as CommandFactory) - // This ensures the parser advances correctly through tokens - if (kStringCommands.contains(cmd_name)) { - std::expected value = ctx->ConsumeStringLiteral(); - if (!value) { - return std::unexpected(value.error()); - } - return kStubCommand(cmd_name); - } - - if (kIntegerCommands.contains(cmd_name)) { - std::expected value = ctx->ConsumeIntLiteral(); - if (!value) { - return std::unexpected(value.error()); - } - return kStubCommand(cmd_name); - } - - if (kFloatCommands.contains(cmd_name)) { - std::expected value = ctx->ConsumeFloatLiteral(); - if (!value) { - return std::unexpected(value.error()); - } - return kStubCommand(cmd_name); - } - - if (kBooleanCommands.contains(cmd_name)) { - std::expected value = ctx->ConsumeBoolLiteral(); - if (!value) { - return std::unexpected(value.error()); - } - return kStubCommand(cmd_name); - } - - if (kIdentCommands.contains(cmd_name)) { - std::expected ident = ctx->ConsumeIdentifier(); - if (!ident) { - return std::unexpected(ident.error()); - } - return kStubCommand(cmd_name); - } - - // Simple commands (no arguments) - return kStubCommand(cmd_name); -} - -} // namespace ovum::bytecode::parser diff --git a/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.hpp b/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.hpp deleted file mode 100644 index 802664b..0000000 --- a/lib/bytecode_parser/scenarios/PlaceholderCommandFactory.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef BYTECODE_PARSER_PLACEHOLDERCOMMANDFACTORY_HPP_ -#define BYTECODE_PARSER_PLACEHOLDERCOMMANDFACTORY_HPP_ - -#include -#include -#include - -#include "ICommandFactory.hpp" -#include "lib/execution_tree/IExecutable.hpp" - -namespace ovum::bytecode::parser { - -class PlaceholderCommandFactory : public ICommandFactory { -public: - std::expected, BytecodeParserError> CreateCommand( - const std::string& cmd_name, std::shared_ptr ctx) const override; -}; - -} // namespace ovum::bytecode::parser - -#endif // BYTECODE_PARSER_PLACEHOLDERCOMMANDFACTORY_HPP_ diff --git a/tests/bytecode_parser_tests.cpp b/tests/bytecode_parser_tests.cpp index 60e3636..b9d6e26 100644 --- a/tests/bytecode_parser_tests.cpp +++ b/tests/bytecode_parser_tests.cpp @@ -67,7 +67,7 @@ TEST_F(BytecodeParserTestSuite, InitStatic_WithNestedWhile) { TEST_F(BytecodeParserTestSuite, InitStatic_AllCommandTypes) { auto parser = CreateParserWithJit(); auto tokens = TokenizeString( - R"(init-static { PushString "hello" PushChar "a" PushInt 42 PushFloat 3.14 PushBool true NewArray arrName Call funcName Return })"); + R"(init-static { PushString "hello" PushChar 42 PushInt 42 PushFloat 3.14 PushBool true Call funcName Return })"); ovum::vm::execution_tree::FunctionRepository func_repo; ovum::vm::runtime::VirtualTableRepository vtable_repo; @@ -739,7 +739,7 @@ TEST_F(BytecodeParserTestSuite, Command_String_PushString) { TEST_F(BytecodeParserTestSuite, Command_String_PushChar) { auto parser = CreateParserWithJit(); - auto tokens = TokenizeString(R"(init-static { PushChar "a" })"); + auto tokens = TokenizeString(R"(init-static { PushChar 42 })"); ovum::vm::execution_tree::FunctionRepository func_repo; ovum::vm::runtime::VirtualTableRepository vtable_repo; @@ -809,15 +809,6 @@ TEST_F(BytecodeParserTestSuite, Command_Boolean_PushBool_False) { auto parsing_result = ParseSuccessfully(parser, tokens, func_repo, vtable_repo); } -TEST_F(BytecodeParserTestSuite, Command_Identifier_NewArray) { - auto parser = CreateParserWithJit(); - auto tokens = TokenizeString("init-static { NewArray arrName }"); - ovum::vm::execution_tree::FunctionRepository func_repo; - ovum::vm::runtime::VirtualTableRepository vtable_repo; - - auto parsing_result = ParseSuccessfully(parser, tokens, func_repo, vtable_repo); -} - TEST_F(BytecodeParserTestSuite, Command_Identifier_Call) { auto parser = CreateParserWithJit(); auto tokens = TokenizeString("init-static { Call funcName }"); diff --git a/tests/test_suites/BytecodeParserTestSuite.hpp b/tests/test_suites/BytecodeParserTestSuite.hpp index e97025e..6834a8d 100644 --- a/tests/test_suites/BytecodeParserTestSuite.hpp +++ b/tests/test_suites/BytecodeParserTestSuite.hpp @@ -10,7 +10,7 @@ #include #include "lib/bytecode_parser/BytecodeParser.hpp" -#include "lib/bytecode_parser/scenarios/PlaceholderCommandFactory.hpp" +#include "lib/bytecode_parser/scenarios/CommandFactory.hpp" #include "lib/execution_tree/Block.hpp" #include "lib/execution_tree/FunctionRepository.hpp" #include "lib/execution_tree/IFunctionExecutable.hpp" @@ -58,7 +58,7 @@ struct BytecodeParserTestSuite : public testing::Test { private: std::unique_ptr jit_factory_; - ovum::bytecode::parser::PlaceholderCommandFactory command_factory_; + ovum::bytecode::parser::CommandFactory command_factory_; ovum::vm::runtime::RuntimeMemory memory_; };