1 module lwdr.tracking;
2 
3 import rtoslink;
4 
5 version(LWDR_TrackMem)
6 {
7 	/// Keeps track of allocations
8 	struct AllocationList(T, size_t MaxElements)
9 	{
10 		static assert(MaxElements > 0, "MaxElements should be at least 1!");
11 
12 		private T[MaxElements] payload;
13 		private size_t length_ = 0;
14 
15 		@property bool empty() const { return length_ == 0; }
16 		@property size_t length() const { return length_; }
17 
18 		/// Track an allocation
19 		bool add(T t) nothrow @nogc
20 		{
21 			if(length_ + 1 >= MaxElements)
22 				return false;
23 
24 			payload[length_++] = t;
25 			return true;
26 		}
27 
28 		/// Get last allocation
29 		auto getLast() nothrow @nogc
30 		{
31 			return payload[length_ - 1];
32 		}
33 
34 		/// Remove last allocation
35 		void removeLast() nothrow @nogc
36 		{
37 			if(empty) return;
38 
39 			payload[length_ - 1] = T.init;
40 			length_--;
41 		}
42 
43 		/// Find the last occurence of `t` and remove it
44 		bool removeLastOccurenceOf(T t) nothrow @nogc
45 		{
46 			if(empty) return false;
47 
48 			size_t index;
49 			bool found = false;
50 
51 			for(size_t i = length_ - 1; i > 0; i--)
52 			{
53 				if(payload[i] == t)
54 				{
55 					index = i;
56 					found = true;
57 				}
58 			}
59 
60 			if(!found)
61 				return false;
62 
63 			for(size_t i = index; i < length_ - 1; i++)
64 			{
65 				payload[i] = payload[i+1];
66 			}
67 			payload[length_ - 1] = T.init;
68 			length_--;
69 			return true;
70 		}
71 	}
72 
73 	version(LWDR_TLS)
74 		private AllocationList!(void*, 16) trackedAllocations; /// Track allocations per-thread
75 	else
76 		private __gshared AllocationList!(void*, 16) trackedAllocations; /// Track allocations across all threads
77 	
78 	/// Stores state of allocations at specific scope
79 	struct MemAlloc
80 	{
81 		size_t allocsAtScope; /// number of allocations when this struct was initiated
82 
83 		/// free allocations until `trackedAllocations.length` matches `this.allocsAtScope`.
84 		void free() nothrow @nogc
85 		{
86 			assert(allocsAtScope >= trackedAllocations.length);
87 
88 			auto difference = trackedAllocations.length - allocsAtScope;
89 			foreach(d; 0 .. difference)
90 			{
91 				auto ptr = trackedAllocations.getLast;
92 				trackedAllocations.removeLast;
93 				rtosbackend_heapfreealloc(ptr);
94 			}
95 		}
96 	}
97 
98 	/// Get the current state of allocations
99 	MemAlloc enterMemoryTracking() nothrow @nogc
100 	{
101 		return MemAlloc(trackedAllocations.length);
102 	}
103 
104 	version(LWDR_TLS)
105 		private bool currentlyTracking = false; /// Are we currently tracking allocations?
106 	else
107 		private __gshared bool currentlyTracking = false; /// Are we currently tracking allocations?
108 	/// Are we currently allocations?
109 	bool isCurrentTracking() nothrow @nogc { return currentlyTracking; }
110 	/// Begin tracking allocations
111 	void enableMemoryTracking() nothrow @nogc { currentlyTracking = true; }
112 	/// Stop tracking allocations
113 	void disableMemoryTracking() nothrow @nogc { currentlyTracking = false; }
114 
115 	/// Allocate heap memory of `sz` bytes. If tracking allocations, the resultant pointer will be added to `trackedAllocations`
116 	void* lwdrInternal_alloc(size_t sz) nothrow @nogc
117 	{
118 		auto ptr = rtosbackend_heapalloc(sz);
119 		if(currentlyTracking)
120 		{
121 			trackedAllocations.add(ptr);
122 		}
123 		return ptr;
124 	}
125 
126 	/// Dealloc heap memory
127 	void lwdrInternal_free(void* ptr) nothrow @nogc
128 	{
129 		trackedAllocations.removeLastOccurenceOf(ptr);
130 		rtosbackend_heapfreealloc(ptr);
131 	}
132 
133 	/// Allocate `sz` bytes heap memory represented by a slice of ubytes. If tracking allocations, the resultant slice will be added to `trackedAllocations`
134 	ubyte[] lwdrInternal_allocBytes(size_t sz) nothrow @nogc
135 	{
136 		return cast(ubyte[])lwdrInternal_alloc(sz)[0..sz];
137 	}
138 }
139 else
140 {
141 	/// Allocate heap memory of `sz` bytes
142 	void* lwdrInternal_alloc(size_t sz) nothrow @nogc pure
143 	{
144 		return rtosbackend_heapalloc(sz);
145 	}
146 
147 	/// Free heap memory
148 	void lwdrInternal_free(void* ptr) nothrow @nogc pure
149 	{
150 		return rtosbackend_heapfreealloc(ptr);
151 	}
152 
153 	/// Allocate `sz` bytes of heap memory represented by a slice of ubytes.
154 	ubyte[] lwdrInternal_allocBytes(size_t sz) nothrow pure @nogc
155 	{
156 		return cast(ubyte[])lwdrInternal_alloc(sz)[0..sz];
157 	}
158 }