Skip to content

Tracking allocator#24

Open
VictorSohier wants to merge 1 commit into
Redot-Engine:masterfrom
VictorSohier:trackingAllocator
Open

Tracking allocator#24
VictorSohier wants to merge 1 commit into
Redot-Engine:masterfrom
VictorSohier:trackingAllocator

Conversation

@VictorSohier
Copy link
Copy Markdown
Contributor

@VictorSohier VictorSohier commented May 19, 2026

New Features

  • A tracking allocator which is meant to be composed with other allocators. Depending on where it sits in the allocator stack, it will track the relevant allocations.
  • A nicer interface which allows for default parameters and makes passing in the wrong allocator into the vtable less error prone.

Summary by CodeRabbit

  • New Features
    • Introduced a tracking allocator module for real-time memory allocation monitoring and analytics
    • Provides statistics on total allocated bytes and active allocation counts
    • Automatically captures source location information for each allocation in debug builds
    • Enables detailed queries of currently active allocations and allocation metrics

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

This PR extends the allocator framework to capture allocation call-site information in debug builds and introduces a new tracking allocator that maintains a doubly-linked list of active allocations for analytics. Core allocator interfaces, concrete implementations, and tests are updated to support the new source-location parameter.

Changes

Allocator Source-Location Tracking and Analytics

Layer / File(s) Summary
Core allocator interface — source location threading
engine/native/core/memory/allocator.cpp, engine/native/core/memory/allocator.cppm
Allocator::alloc and AllocatorVTbl::AllocFn are extended with an optional std::source_location loc parameter under #ifdef DEBUG. The nilAlloc helper forwards this parameter through the vtable, establishing the contract for all allocator implementations.
Concrete allocator implementations — bump, fixed, page
engine/native/core/memory/bumpAllocator.cpp, engine/native/core/memory/bumpAllocator.cppm, engine/native/core/memory/fixedAllocator.cpp, engine/native/core/memory/fixedAllocator.cppm, engine/native/core/memory/pageAllocator.cpp, engine/native/core/memory/pageAllocator.cppm
Bump, fixed, and page allocators are updated with debug-only std::source_location loc parameters in their alloc signatures. Each implementation forwards the location through to its underlying base allocator call via the vtable.
Tracking allocator module — types and contract
engine/native/core/memory/trackingAllocator.cppm
Introduces Analytics, AllocationDetails (with optional source location), Node, and TrackingAllocator structs. Declares operations for initialization, allocation, deallocation, and metrics retrieval. Exports a vtable-wired interface via asAllocator for seamless integration with the base allocator framework.
Tracking allocator implementation — allocation and analytics
engine/native/core/memory/trackingAllocator.cpp
Implements tracking allocator operations: init clears the node list; alloc computes padded size, delegates to the base allocator, inserts a linked-list node with allocation metadata, and returns the trimmed user-facing slice; free unlinks the matching node and forwards to the base allocator with adjusted size; freeAll, getAnalytics, and getActiveAllocations traverse the node list to provide bookkeeping and detailed allocation snapshots.
Module exports and allocator test updates
engine/native/core/memory/root.cppm, engine/native/core/memory/bumpAllocator.test.cpp, engine/native/core/memory/fixedAllocator.test.cpp, engine/native/core/memory/trackingAllocator.test.cpp
core.memory.trackingAllocator is exported from the root module. Existing bump and fixed allocator tests are migrated from direct vtable calls to the alloc() wrapper method. A new comprehensive test exercises tracking allocator initialization, three allocations, selective frees with detail validation, analytics verification, and freeAll cleanup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Redot-Engine/DraconicEngine#22: Main PR extends the function signatures of the same basic allocator APIs introduced in PR #22 (e.g., nilAlloc / AllocatorVTbl::alloc and bump/fixed/page alloc under #ifdef DEBUG) to thread std::source_location through debug builds.

Suggested reviewers

  • OldDev78
  • mcdubhghlas
  • JoltedJon

Poem

🐰 Allocation tracks find their way,
Source locations logged each day,
Linked nodes dance in debug's light,
Analytics shine shiny bright!
Free them all without a care,
We knew exactly what was there.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Tracking allocator' directly matches the PR's primary objective of adding a tracking allocator module with supporting infrastructure changes across multiple allocator implementations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@engine/native/core/memory/fixedAllocator.test.cpp`:
- Around line 15-16: The test calls alloc.alloc(&block, 512, 16) but never
verifies it succeeded before inspecting block.data; change the test to assert
the allocation returned success (e.g., REQUIRE on the boolean/result of
alloc.alloc) before the alignment check, so verify alloc.alloc(...) is true and
only then perform REQUIRE((((uintptr_t)block.data) & 15) == 0); reference
alloc.alloc and block in the fixedAllocator.test.cpp test.

In `@engine/native/core/memory/trackingAllocator.cpp`:
- Around line 88-89: The code currently clears allocData->nodes before calling
allocData->base.vtbl->freeAll(allocData->base); change this so you call freeAll
first, capture its return/result, and only set allocData->nodes = nullptr (and
any other tracking-state resets) if freeAll succeeded; if freeAll fails, leave
allocData->nodes unchanged and return the error result from
allocData->base.vtbl->freeAll(allocData->base) so tracking still reflects live
blocks. Ensure you reference allocData->base.vtbl->freeAll and allocData->nodes
when making the change.
- Around line 69-82: The unlink logic in the free path dereferences prev/next
blindly and always adjusts block.size even when no matching Node was found;
update the code in trackingAllocator.cpp (the Node list manipulation around
node, prev, next) to: only perform memset(node) and add sizeof(Node) to
block.size when a matching node is found; when unlinking, check for nullptr
before dereferencing prev or next and update head/tail pointers appropriately to
handle removing the head, tail, or the sole node; if node is not found, do not
modify block.size or attempt unlinking—simply call
allocData->base.vtbl->free(allocData->base, block) (optionally log or assert the
unexpected free) to avoid crashes and allocator corruption.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17559e75-eb06-4c1a-8335-b6bf804b8709

📥 Commits

Reviewing files that changed from the base of the PR and between fdb254d and 2dfc6ed.

📒 Files selected for processing (14)
  • engine/native/core/memory/allocator.cpp
  • engine/native/core/memory/allocator.cppm
  • engine/native/core/memory/bumpAllocator.cpp
  • engine/native/core/memory/bumpAllocator.cppm
  • engine/native/core/memory/bumpAllocator.test.cpp
  • engine/native/core/memory/fixedAllocator.cpp
  • engine/native/core/memory/fixedAllocator.cppm
  • engine/native/core/memory/fixedAllocator.test.cpp
  • engine/native/core/memory/pageAllocator.cpp
  • engine/native/core/memory/pageAllocator.cppm
  • engine/native/core/memory/root.cppm
  • engine/native/core/memory/trackingAllocator.cpp
  • engine/native/core/memory/trackingAllocator.cppm
  • engine/native/core/memory/trackingAllocator.test.cpp

Comment on lines +15 to 16
alloc.alloc(&block, 512, 16);
REQUIRE((((uintptr_t)block.data) & 15) == 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert allocation success before checking alignment.

Line 15 ignores the alloc.alloc(...) result. If allocation fails, Line 16 may assert on an invalid block.

Proposed fix
-	alloc.alloc(&block, 512, 16);
+	Error err = alloc.alloc(&block, 512, 16);
+	REQUIRE(err == Error::Okay);
 	REQUIRE((((uintptr_t)block.data) & 15) == 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/native/core/memory/fixedAllocator.test.cpp` around lines 15 - 16, The
test calls alloc.alloc(&block, 512, 16) but never verifies it succeeded before
inspecting block.data; change the test to assert the allocation returned success
(e.g., REQUIRE on the boolean/result of alloc.alloc) before the alignment check,
so verify alloc.alloc(...) is true and only then perform
REQUIRE((((uintptr_t)block.data) & 15) == 0); reference alloc.alloc and block in
the fixedAllocator.test.cpp test.

Comment on lines +69 to +82
while (node && (node->details.data.data != block.data))
{
node = node->next;
}
if (node)
{
prev = node->prev;
next = node->next;
prev->next = next;
next->prev = prev;
memset(node, 0, sizeof(Node));
}
block.size += sizeof(Node);
return allocData->base.vtbl->free(allocData->base, block);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix free unlink/not-found paths to prevent crashes and allocator corruption.

Line 77 and Line 78 dereference prev/next without null checks, which crashes when freeing head/tail (or the only node). Also, when no node matches, the code still increases block.size and forwards free to the base allocator.

Proposed fix
-		if (node)
-		{
-			prev = node->prev;
-			next = node->next;
-			prev->next = next;
-			next->prev = prev;
-			memset(node, 0, sizeof(Node));
-		}
-		block.size += sizeof(Node);
-		return allocData->base.vtbl->free(allocData->base, block);
+		if (!node)
+		{
+			// Return the project-specific non-Okay error for invalid/untracked frees.
+			return Error::Unknown;
+		}
+
+		prev = node->prev;
+		next = node->next;
+		if (prev) { prev->next = next; }
+		else { allocData->nodes = next; }
+		if (next) { next->prev = prev; }
+
+		memset(node, 0, sizeof(Node));
+		block.size += sizeof(Node);
+		return allocData->base.vtbl->free(allocData->base, block);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/native/core/memory/trackingAllocator.cpp` around lines 69 - 82, The
unlink logic in the free path dereferences prev/next blindly and always adjusts
block.size even when no matching Node was found; update the code in
trackingAllocator.cpp (the Node list manipulation around node, prev, next) to:
only perform memset(node) and add sizeof(Node) to block.size when a matching
node is found; when unlinking, check for nullptr before dereferencing prev or
next and update head/tail pointers appropriately to handle removing the head,
tail, or the sole node; if node is not found, do not modify block.size or
attempt unlinking—simply call allocData->base.vtbl->free(allocData->base, block)
(optionally log or assert the unexpected free) to avoid crashes and allocator
corruption.

Comment on lines +88 to +89
allocData->nodes = nullptr;
return allocData->base.vtbl->freeAll(allocData->base);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only clear tracking state after base.freeAll succeeds.

Line 88 clears allocData->nodes before checking Line 89’s result. If base.freeAll fails, analytics/reporting will say zero active allocations while blocks may still be live.

Proposed fix
-		allocData->nodes = nullptr;
-		return allocData->base.vtbl->freeAll(allocData->base);
+		Error err = allocData->base.vtbl->freeAll(allocData->base);
+		if (err == Error::Okay)
+		{
+			allocData->nodes = nullptr;
+		}
+		return err;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
allocData->nodes = nullptr;
return allocData->base.vtbl->freeAll(allocData->base);
Error err = allocData->base.vtbl->freeAll(allocData->base);
if (err == Error::Okay)
{
allocData->nodes = nullptr;
}
return err;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/native/core/memory/trackingAllocator.cpp` around lines 88 - 89, The
code currently clears allocData->nodes before calling
allocData->base.vtbl->freeAll(allocData->base); change this so you call freeAll
first, capture its return/result, and only set allocData->nodes = nullptr (and
any other tracking-state resets) if freeAll succeeded; if freeAll fails, leave
allocData->nodes unchanged and return the error result from
allocData->base.vtbl->freeAll(allocData->base) so tracking still reflects live
blocks. Ensure you reference allocData->base.vtbl->freeAll and allocData->nodes
when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant