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