1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <cmajor/rt/Memory.hpp>
  7 #include <cmajor/rt/Error.hpp>
  8 #include <cmajor/rt/Io.hpp>
  9 #include <cmajor/rt/CallStack.hpp>
 10 #include <algorithm>
 11 #include <sstream>
 12 #include <unordered_map>
 13 #include <memory>
 14 #include <malloc.h>
 15 #include <fstream>
 16 #include <vector>
 17 #include <cstring>
 18 
 19 namespace cmajor { namespace rt {
 20 
 21 class Allocation 
 22 {
 23 public:
 24     Allocation();
 25     Allocation(int serial_int64_t size_const char* info_);
 26     void Print(const std::string& title) const;
 27     int Serial() const { return serial; }
 28     void SetSerial(int serial_) { serial = serial_; }
 29     int64_t Size() const { return size; }
 30     void SetSize(int64_t size_) { size = size_; }
 31     bool Disposed() const { return disposed; }
 32     void SetDisposed(bool disposed_) { disposed = disposed_; }
 33     const char* Info() const { return info; }
 34     void SetInfo(const char* info_) { info = info_; }
 35 private:
 36     int serial;
 37     int64_t size;
 38     bool disposed;
 39     const char* info;
 40 };
 41 
 42 Allocation::Allocation() : serial(0)size(0)disposed(false)info(nullptr)
 43 {
 44 }
 45 
 46 Allocation::Allocation(int serial_int64_t size_const char* info_) : serial(serial_)size(size_)disposed(false)info(info_)
 47 {
 48 }
 49 
 50 void Allocation::Print(const std::string& title) const
 51 {
 52     std::string s = title + " allocation #" + std::to_string(serial) + " : size=" + std::to_string(size) + " : disposed=" + (disposed ? "true" : "false");
 53     if (info != nullptr)
 54     {
 55         s.append(" : info '").append(info).append("'");
 56     }
 57     s.append("\n");
 58     int32_t errorStringHandle = -1;
 59     void* stdError = RtOpenStdFile(2errorStringHandle);
 60     RtWrite(stdError(const uint8_t*)s.c_str()s.length()errorStringHandle);
 61     RtFlush(stdErrorerrorStringHandle);
 62 }
 63 
 64 struct SerialLess 
 65 {
 66     bool operator()(const Allocation* leftconst Allocation* right) const
 67     {
 68         return left->Serial() < right->Serial();
 69     }
 70 };
 71 
 72 class DebugHeap 
 73 {
 74 public:
 75     static void Init();
 76     static void Done();
 77     static DebugHeap& Instance() { return *instance; }
 78     void SetDebugHeap() { debugHeap = true; }
 79     bool GetDebugHeap() const { return debugHeap; }
 80     void SetDebugSerial(int debugSerial_) { debugSerial = debugSerial_; }
 81     int GetDebugSerial() const { return debugSerial; }
 82     void Allocate(void* ptrint64_t sizeconst char* info);
 83     void Dispose(void* ptr);
 84     int NextSerial() { return ++serial; }
 85     void PrintLeaks();
 86 private:
 87     static std::unique_ptr<DebugHeap> instance;
 88     DebugHeap();
 89     bool debugHeap;
 90     int debugSerial;
 91     int serial;
 92     std::unordered_map<void*Allocation> allocationMap;
 93 };
 94 
 95 std::unique_ptr<DebugHeap> DebugHeap::instance;
 96 
 97 DebugHeap::DebugHeap() : debugHeap(false)debugSerial(0)serial(0)
 98 {
 99 }
100 
101 void DebugHeap::Allocate(void* ptrint64_t sizeconst char* info)
102 {
103     auto it = allocationMap.find(ptr);
104     if (it == allocationMap.cend())
105     {
106         allocationMap[ptr] = Allocation(serialsizeinfo);
107         if (serial == debugSerial)
108         {
109             allocationMap[ptr].Print("allocating");
110             int32_t errorStringHandle = -1;
111             void* stdError = RtOpenStdFile(2errorStringHandle);
112             RtPrintCallStack(stdError);
113             RtFlush(stdErrorerrorStringHandle);
114         }
115     }
116     else
117     {
118         Allocation& alloc = it->second;
119         alloc.SetSerial(serial);
120         alloc.SetSize(size);
121         alloc.SetDisposed(false);
122         alloc.SetInfo(info);
123     }
124 }
125 
126 void DebugHeap::Dispose(void* ptr)
127 {
128     if (!ptr) return;
129     auto it = allocationMap.find(ptr);
130     if (it != allocationMap.cend())
131     {
132         Allocation& alloc = it->second;
133         if (alloc.Disposed())
134         {
135             alloc.Print("dangling");
136             if (debugSerial == alloc.Serial())
137             {
138                 int32_t errorStringHandle = -1;
139                 void* stdError = RtOpenStdFile(2errorStringHandle);
140                 RtPrintCallStack(stdError);
141                 RtFlush(stdErrorerrorStringHandle);
142             }
143         }
144         else
145         {
146             if (debugSerial == alloc.Serial())
147             {
148                 alloc.Print("disposing");
149                 int32_t errorStringHandle = -1;
150                 void* stdError = RtOpenStdFile(2errorStringHandle);
151                 RtPrintCallStack(stdError);
152                 RtFlush(stdErrorerrorStringHandle);
153             }
154             alloc.SetDisposed(true);
155         }
156     }
157     else if (debugHeap)
158     {
159         std::string s = "disposing : allocation not found\n";
160         int32_t errorStringHandle = -1;
161         void* stdError = RtOpenStdFile(2errorStringHandle);
162         RtWrite(stdError(const uint8_t*)s.c_str()s.length()errorStringHandle);
163         RtPrintCallStack(stdError);
164         RtFlush(stdErrorerrorStringHandle);
165     }
166 }
167 
168 void DebugHeap::PrintLeaks()
169 {
170     std::vector<const Allocation*> leaks;
171     for (const auto& p : allocationMap)
172     {
173         const Allocation& alloc = p.second;
174         if (!alloc.Disposed())
175         {
176             leaks.push_back(&alloc);
177         }
178     }
179     if (!leaks.empty())
180     {
181         std::string title = std::to_string(leaks.size()) + " memory leaks:\n";
182         int32_t errorStringHandle = -1;
183         void* stdError = RtOpenStdFile(2errorStringHandle);
184         RtWrite(stdError(const uint8_t*)title.c_str()title.size()errorStringHandle);
185         RtFlush(stdErrorerrorStringHandle);
186         std::sort(leaks.begin()leaks.end()SerialLess());
187         int i = 0;
188         for (const Allocation* leak : leaks)
189         {
190             leak->Print(std::to_string(i) + ": leaked");
191             ++i;
192         }
193     }
194 }
195 
196 void DebugHeap::Init()
197 {
198     instance.reset(new DebugHeap());
199 }
200 
201 void DebugHeap::Done()
202 {
203     if (instance->GetDebugHeap())
204     {
205         instance->PrintLeaks();
206     }
207     instance.reset();
208 }
209 
210 void SetDebugHeap()
211 {
212     DebugHeap::Instance().SetDebugHeap();
213 }
214 
215 void SetDebugAllocation(int debugSerial)
216 {
217     DebugHeap::Instance().SetDebugSerial(debugSerial);
218 }
219 
220 void InitMemory()
221 {
222     DebugHeap::Init();
223 }
224 
225 void DoneMemory()
226 {
227     DebugHeap::Done();
228 }
229 
230 } }  // namespace cmajor::rt
231 
232 extern "C" void* RtMemAllocInfo(int64_t size, const char* info)
233 {
234     void* ptr = malloc(size);
235     if (!ptr)
236     {
237         std::stringstream s;
238         s << "program out of memory\n";
239         std::string str = s.str();
240         int32_t errorStringHandle = -1;
241         void* stdError = RtOpenStdFile(2, errorStringHandle);
242         RtWrite(stdError, reinterpret_cast<const uint8_t*>(str.c_str()), str.length(), errorStringHandle);
243         RtPrintCallStack(stdError);
244         RtFlush(stdError, errorStringHandle);
245         exit(exitCodeOutOfMemory);
246     }
247     int serial = cmajor::rt::DebugHeap::Instance().NextSerial();
248     if (cmajor::rt::DebugHeap::Instance().GetDebugHeap() || serial == cmajor::rt::DebugHeap::Instance().GetDebugSerial())
249     {
250         cmajor::rt::DebugHeap::Instance().Allocate(ptr, size, info);
251     }
252     return ptr;
253 }
254 
255 extern "C" void* RtMemAlloc(int64_t size)
256 {
257     return RtMemAllocInfo(size, nullptr);
258 }
259 
260 extern "C" void RtDispose(void* ptr)
261 {
262     if (cmajor::rt::DebugHeap::Instance().GetDebugHeap() || cmajor::rt::DebugHeap::Instance().GetDebugSerial() != 0)
263     {
264         cmajor::rt::DebugHeap::Instance().Dispose(ptr);
265     }
266 }
267 
268 extern "C" void RtMemFree(void* ptr)
269 {
270     free(ptr);
271 }
272 
273 extern "C" void RtMemZero(void* ptr, int64_t size)
274 {
275     std::memset(ptr, 0, size);
276 }