1+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+ #include " perspective/base.h"
14+ #include " perspective/heap_instruments.h"
15+ #include < cstddef>
16+ #include < cstdio>
17+ #include < cstdlib>
18+ #include < emscripten/emscripten.h>
19+ #include < emscripten/heap.h>
20+ #include < emscripten/em_asm.h>
21+ #include < emscripten/stack.h>
22+ #include < string>
23+
24+ static std::uint64_t USED_MEMORY = 0 ;
25+
26+ static constexpr std::uint64_t MIN_RELEVANT_SIZE = 5 * 1024 * 1024 ;
27+
28+ extern " C" void
29+ psp_print_used_memory () {
30+ printf (" Used memory: %llu\n " , USED_MEMORY);
31+ }
32+
33+ struct Header {
34+ std::uint64_t size;
35+ };
36+
37+ using UnderlyingString =
38+ std::basic_string<char , std::char_traits<char >, UnderlyingAllocator<char >>;
39+
40+ using UnderlyingIStringStream = std::basic_istringstream<
41+ char ,
42+ std::char_traits<char >,
43+ UnderlyingAllocator<char >>;
44+
45+ struct AllocMeta {
46+ Header header;
47+ const UnderlyingString* trace;
48+ std::uint64_t size;
49+ };
50+
51+ static std::unordered_map<
52+ UnderlyingString,
53+ AllocMeta,
54+ std::hash<UnderlyingString>,
55+ std::equal_to<>,
56+ UnderlyingAllocator<std::pair<UnderlyingString const , AllocMeta>>>
57+ stack_traces;
58+
59+ static UnderlyingString IRRELEVANT = " irrelevant" ;
60+
61+ static inline void
62+ record_stack_trace (Header* header, std::uint64_t size) {
63+ if (size >= MIN_RELEVANT_SIZE) {
64+ const char * stack_c_str = perspective::psp_stack_trace ();
65+ UnderlyingIStringStream stack (stack_c_str);
66+ UnderlyingString line;
67+ UnderlyingString out;
68+
69+ while (std::getline (stack, line)) {
70+ line = line.substr (0 , line.find_last_of (" (" ));
71+ out += line + " \n " ;
72+ }
73+
74+ emscripten_builtin_free (const_cast <char *>(stack_c_str));
75+
76+ // stack_traces[ptr] = {.header = *header, .trace = out};
77+ if (stack_traces.find (out) == stack_traces.end ()) {
78+ stack_traces[out] =
79+ AllocMeta{.header = *header, .trace = nullptr , .size = size};
80+
81+ stack_traces[out].trace = &stack_traces.find (out)->first ;
82+ } else {
83+ stack_traces[out].size += size;
84+ }
85+ } else {
86+ if (stack_traces.find (IRRELEVANT) == stack_traces.end ()) {
87+ stack_traces[IRRELEVANT] = AllocMeta{
88+ .header = *header, .trace = &IRRELEVANT, .size = size
89+ };
90+ } else {
91+ stack_traces[IRRELEVANT].size += size;
92+ }
93+ }
94+ }
95+
96+ extern " C" void
97+ psp_dump_stack_traces () {
98+ std::vector<AllocMeta, UnderlyingAllocator<AllocMeta>> metas;
99+ metas.reserve (stack_traces.size ());
100+ for (const auto & [_, meta] : stack_traces) {
101+ metas.push_back (meta);
102+ }
103+ std::sort (
104+ metas.begin (),
105+ metas.end (),
106+ [](const AllocMeta& a, const AllocMeta& b) {
107+ return a.header .size > b.header .size ;
108+ }
109+ );
110+ for (const auto & meta : metas) {
111+ printf (" Allocated %llu bytes\n " , meta.header .size );
112+ printf (" Stacktrace:\n %s\n " , meta.trace ->c_str ());
113+ }
114+ }
115+
116+ extern " C" void
117+ psp_clear_stack_traces () {
118+ stack_traces.clear ();
119+ }
120+
121+ void *
122+ malloc (size_t size) {
123+ if (size > MIN_RELEVANT_SIZE) {
124+ printf (" Allocating %zu bytes\n " , size);
125+ }
126+ USED_MEMORY += size;
127+ const size_t total_size = size + sizeof (Header);
128+ auto * header = static_cast <Header*>(emscripten_builtin_malloc (total_size));
129+ if (header == nullptr ) {
130+ fprintf (stderr, " Failed to allocate %zu bytes\n " , size);
131+ }
132+ header->size = size;
133+ record_stack_trace (header, size);
134+ return header + 1 ;
135+ }
136+
137+ void *
138+ calloc (size_t nmemb, size_t size) {
139+ // printf("Allocating array: %zu elements of size %zu\n", nmemb, size);
140+ USED_MEMORY += nmemb * size;
141+ // return emscripten_builtin_calloc(nmemb, size);
142+ const size_t total_size = (nmemb * size) + sizeof (Header);
143+ auto * header = static_cast <Header*>(emscripten_builtin_malloc (total_size));
144+ if (header == nullptr ) {
145+ fprintf (stderr, " Failed to allocate %zu bytes\n " , size);
146+ }
147+ header->size = nmemb * size;
148+ memset (header + 1 , 0 , nmemb * size);
149+ record_stack_trace (header, size);
150+ return header + 1 ;
151+ }
152+
153+ void
154+ free (void * ptr) {
155+ // printf("Freeing memory at %p\n", ptr);
156+
157+ if (ptr == nullptr ) {
158+ emscripten_builtin_free (ptr);
159+ } else {
160+ auto * header = static_cast <Header*>(ptr) - 1 ;
161+ auto old_memory = USED_MEMORY;
162+ USED_MEMORY -= header->size ;
163+ if (USED_MEMORY > old_memory) {
164+ std::abort ();
165+ }
166+ emscripten_builtin_free (header);
167+ }
168+ }
169+
170+ void *
171+ memalign (size_t alignment, size_t size) {
172+ const size_t total_size = size + sizeof (Header);
173+ auto * header =
174+ static_cast <Header*>(emscripten_builtin_memalign (alignment, total_size)
175+ );
176+ if (header == nullptr ) {
177+ fprintf (stderr, " Failed to allocate %zu bytes\n " , size);
178+ }
179+ header->size = size;
180+ record_stack_trace (header, size);
181+ return header + 1 ;
182+ }
183+
184+ int
185+ posix_memalign (void ** memptr, size_t alignment, size_t size) {
186+ auto * header = static_cast <Header*>(
187+ emscripten_builtin_memalign (alignment, size + sizeof (Header))
188+ );
189+ if (header == nullptr ) {
190+ fprintf (stderr, " Failed to allocate %zu bytes\n " , size);
191+ }
192+ header->size = size;
193+ USED_MEMORY += size;
194+ record_stack_trace (header, size);
195+ *memptr = header + 1 ;
196+ return 0 ;
197+ }
198+
199+ void *
200+ realloc (void * ptr, size_t new_size) {
201+ if (ptr == nullptr ) {
202+ // If ptr is nullptr, realloc behaves like malloc
203+ return malloc (new_size);
204+ }
205+
206+ if (new_size == 0 ) {
207+ // If new_size is 0, realloc behaves like free
208+ free (ptr);
209+ return nullptr ;
210+ }
211+
212+ auto * header = static_cast <Header*>(ptr) - 1 ;
213+ const size_t old_size = header->size ;
214+
215+ if (new_size <= old_size) {
216+ USED_MEMORY -= old_size - new_size;
217+ // If the new size is smaller or equal, we can potentially shrink the
218+ // block in place. For simplicity, we don't actually shrink the block
219+ // here.
220+ header->size = new_size; // Update the size in the header
221+ return ptr; // Return the same pointer
222+ }
223+
224+ // If the new size is larger, allocate a new block
225+ void * new_ptr = malloc (new_size);
226+ if (new_ptr == nullptr ) {
227+ return nullptr ;
228+ }
229+
230+ memcpy (new_ptr, ptr, old_size);
231+ free (ptr);
232+
233+ return new_ptr;
234+ }
0 commit comments