Skip to content

Commit e2dbb26

Browse files
committed
feat(WASM): port memory pool, bump allocator, and reserved buffer C API from spz2glb
Add HotObjectPool, BumpAllocator, MemoryTracker (memory_pool.h) and gk_* reserved buffer C API (wasm_buffer.h) to support chunked WASM writes. Update CMakeLists.txt EXPORTED_FUNCTIONS and wasm-pre-check.sh REQUIRED_SYMBOLS.
1 parent 8ddec07 commit e2dbb26

8 files changed

Lines changed: 693 additions & 12 deletions

File tree

cpp/CMakeLists.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ if(SPZ_GATEKEEPER_BUILD_WASM)
159159

160160
add_executable(spz_gatekeeper-wasm src/wasm_main.cc)
161161
target_link_libraries(spz_gatekeeper-wasm PRIVATE spz_gatekeeper_core)
162+
target_include_directories(spz_gatekeeper-wasm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
162163

163164
set(SPZ_GATEKEEPER_WASM_LINK_OPTS
164165
"--bind"
@@ -172,7 +173,7 @@ if(SPZ_GATEKEEPER_BUILD_WASM)
172173
# SINGLE_FILE removed: split JS glue + separate .wasm for faster loading
173174
# Browser streams and compiles WASM while downloading (~60% faster init)
174175
"-sFILESYSTEM=0"
175-
"-sEXPORTED_FUNCTIONS=_malloc,_free"
176+
"-sEXPORTED_FUNCTIONS=_malloc,_free,gk_reserve_buffer,gk_get_buffer_ptr,gk_get_buffer_size,gk_get_buffer_used,gk_set_buffer_used,gk_release_buffer,gk_reset_memory_stats,gk_get_memory_stats"
176177
"-sEXPORTED_RUNTIME_METHODS=HEAPU8"
177178
)
178179

@@ -203,23 +204,29 @@ if(SPZ_GATEKEEPER_BUILD_WASM)
203204
add_custom_command(
204205
OUTPUT "${SPZ_GATEKEEPER_WASM_SITE_DIR}/index.html"
205206
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/spz_gatekeeper.js"
207+
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/smart_memory_manager.js"
206208
COMMAND ${CMAKE_COMMAND} -E make_directory "${SPZ_GATEKEEPER_WASM_SITE_DIR}"
207209
COMMAND ${CMAKE_COMMAND} -E copy_if_different
208210
"${CMAKE_CURRENT_SOURCE_DIR}/../web/index.html"
209211
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/index.html"
210212
COMMAND ${CMAKE_COMMAND} -E copy_if_different
211213
"${CMAKE_CURRENT_SOURCE_DIR}/../web/spz_gatekeeper.js"
212214
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/spz_gatekeeper.js"
215+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
216+
"${CMAKE_CURRENT_SOURCE_DIR}/../web/smart_memory_manager.js"
217+
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/smart_memory_manager.js"
213218
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../web/index.html"
214219
"${CMAKE_CURRENT_SOURCE_DIR}/../web/spz_gatekeeper.js"
215-
COMMENT "Copy web entrypoint and browser audit wrapper into WASM site directory"
220+
"${CMAKE_CURRENT_SOURCE_DIR}/../web/smart_memory_manager.js"
221+
COMMENT "Copy web entrypoints into WASM site directory"
216222
VERBATIM
217223
)
218224

219225
add_custom_target(spz_gatekeeper_wasm_site ALL
220226
DEPENDS spz_gatekeeper-wasm
221227
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/index.html"
222228
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/spz_gatekeeper.js"
229+
"${SPZ_GATEKEEPER_WASM_SITE_DIR}/smart_memory_manager.js"
223230
)
224231
endif()
225232

cpp/src/memory_pool.h

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#ifndef GK_MEMORY_POOL_H
2+
#define GK_MEMORY_POOL_H
3+
4+
#include <algorithm>
5+
#include <cstddef>
6+
#include <cstdint>
7+
#include <new>
8+
9+
namespace gk {
10+
11+
struct MemoryStats {
12+
size_t peak_usage = 0;
13+
size_t current_usage = 0;
14+
size_t total_allocations = 0;
15+
size_t total_frees = 0;
16+
size_t failed_allocations = 0;
17+
size_t hot_available = 0;
18+
size_t work_used = 0;
19+
size_t work_capacity = 0;
20+
size_t work_peak = 0;
21+
};
22+
23+
class MemoryTracker {
24+
public:
25+
void trackExternalAlloc(size_t size) {
26+
if (size == 0) return;
27+
stats_.current_usage += size;
28+
stats_.peak_usage = std::max(stats_.peak_usage, stats_.current_usage);
29+
++stats_.total_allocations;
30+
}
31+
32+
void trackExternalFree(size_t size) {
33+
if (size == 0) return;
34+
stats_.current_usage = stats_.current_usage > size ? stats_.current_usage - size : 0;
35+
++stats_.total_frees;
36+
}
37+
38+
void trackFailedAllocation() {
39+
++stats_.failed_allocations;
40+
}
41+
42+
void trackWorkReserve(size_t size) {
43+
stats_.work_capacity = size;
44+
trackExternalAlloc(size);
45+
}
46+
47+
void trackWorkRelease(size_t reservedSize, size_t usedSize) {
48+
stats_.work_used = stats_.work_used > usedSize ? stats_.work_used - usedSize : 0;
49+
stats_.work_capacity = stats_.work_capacity > reservedSize ? stats_.work_capacity - reservedSize : 0;
50+
trackExternalFree(reservedSize);
51+
}
52+
53+
void trackWorkUsage(size_t usedSize) {
54+
stats_.work_used = usedSize;
55+
stats_.work_peak = std::max(stats_.work_peak, usedSize);
56+
++stats_.total_allocations;
57+
}
58+
59+
void resetWorkUsage() {
60+
stats_.work_used = 0;
61+
}
62+
63+
void setHotAvailable(size_t available) {
64+
stats_.hot_available = available;
65+
}
66+
67+
void resetCountersPreserveLiveAllocations() {
68+
stats_.peak_usage = stats_.current_usage;
69+
stats_.total_allocations = 0;
70+
stats_.total_frees = 0;
71+
stats_.failed_allocations = 0;
72+
stats_.work_peak = std::max(stats_.work_peak, stats_.work_used);
73+
}
74+
75+
MemoryStats snapshot() const {
76+
return stats_;
77+
}
78+
79+
private:
80+
MemoryStats stats_{};
81+
};
82+
83+
inline MemoryTracker& memoryTracker() {
84+
static MemoryTracker tracker;
85+
return tracker;
86+
}
87+
88+
inline void trackExternalAlloc(size_t size) {
89+
memoryTracker().trackExternalAlloc(size);
90+
}
91+
92+
inline void trackExternalFree(size_t size) {
93+
memoryTracker().trackExternalFree(size);
94+
}
95+
96+
inline void trackFailedAllocation() {
97+
memoryTracker().trackFailedAllocation();
98+
}
99+
100+
inline void resetMemoryStats() {
101+
memoryTracker().resetCountersPreserveLiveAllocations();
102+
}
103+
104+
class BumpAllocator {
105+
public:
106+
BumpAllocator() = default;
107+
108+
explicit BumpAllocator(size_t size) {
109+
init(size);
110+
}
111+
112+
BumpAllocator(const BumpAllocator&) = delete;
113+
BumpAllocator& operator=(const BumpAllocator&) = delete;
114+
115+
~BumpAllocator() {
116+
release();
117+
}
118+
119+
bool init(size_t size) {
120+
release();
121+
if (size == 0) return true;
122+
123+
pool_ = new (std::nothrow) std::byte[size];
124+
if (pool_ == nullptr) {
125+
trackFailedAllocation();
126+
return false;
127+
}
128+
129+
capacity_ = size;
130+
current_offset_ = 0;
131+
peak_usage_ = 0;
132+
allocations_ = 0;
133+
memoryTracker().trackWorkReserve(size);
134+
return true;
135+
}
136+
137+
void release() {
138+
if (pool_ == nullptr) return;
139+
memoryTracker().trackWorkRelease(capacity_, current_offset_);
140+
delete[] pool_;
141+
pool_ = nullptr;
142+
capacity_ = 0;
143+
current_offset_ = 0;
144+
peak_usage_ = 0;
145+
allocations_ = 0;
146+
}
147+
148+
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)) {
149+
if (pool_ == nullptr || size == 0) return nullptr;
150+
151+
const size_t aligned_offset = alignUp(current_offset_, alignment);
152+
if (aligned_offset > capacity_ || capacity_ - aligned_offset < size) {
153+
trackFailedAllocation();
154+
return nullptr;
155+
}
156+
157+
void* ptr = pool_ + aligned_offset;
158+
current_offset_ = aligned_offset + size;
159+
peak_usage_ = std::max(peak_usage_, current_offset_);
160+
++allocations_;
161+
memoryTracker().trackWorkUsage(current_offset_);
162+
return ptr;
163+
}
164+
165+
void reset() {
166+
current_offset_ = 0;
167+
memoryTracker().resetWorkUsage();
168+
}
169+
170+
size_t used() const { return current_offset_; }
171+
size_t peak_usage() const { return peak_usage_; }
172+
size_t remaining() const { return capacity_ > current_offset_ ? capacity_ - current_offset_ : 0; }
173+
size_t allocations() const { return allocations_; }
174+
size_t capacity() const { return capacity_; }
175+
176+
private:
177+
static size_t alignUp(size_t value, size_t alignment) {
178+
const size_t mask = alignment - 1;
179+
return (value + mask) & ~mask;
180+
}
181+
182+
std::byte* pool_ = nullptr;
183+
size_t capacity_ = 0;
184+
size_t current_offset_ = 0;
185+
size_t peak_usage_ = 0;
186+
size_t allocations_ = 0;
187+
};
188+
189+
template<size_t ObjectSize, uint32_t PoolSize>
190+
class HotObjectPool {
191+
static_assert(PoolSize > 0, "PoolSize must be > 0");
192+
static_assert(ObjectSize >= sizeof(void*), "ObjectSize must be >= sizeof(void*)");
193+
194+
struct FreeNode {
195+
FreeNode* next;
196+
};
197+
198+
public:
199+
HotObjectPool() : freeList_(nullptr), allocations_(0) {
200+
for (uint32_t i = 0; i < PoolSize; ++i) {
201+
auto* node = reinterpret_cast<FreeNode*>(pool_ + i * ObjectSize);
202+
node->next = freeList_;
203+
freeList_ = node;
204+
}
205+
memoryTracker().setHotAvailable(PoolSize);
206+
}
207+
208+
void* alloc() {
209+
if (freeList_ == nullptr) return nullptr;
210+
auto* node = freeList_;
211+
freeList_ = freeList_->next;
212+
++allocations_;
213+
memoryTracker().setHotAvailable(available());
214+
return node;
215+
}
216+
217+
void dealloc(void* ptr) {
218+
auto* node = reinterpret_cast<FreeNode*>(ptr);
219+
node->next = freeList_;
220+
freeList_ = node;
221+
memoryTracker().setHotAvailable(available());
222+
}
223+
224+
uint32_t available() const {
225+
uint32_t count = 0;
226+
for (auto* node = freeList_; node != nullptr; node = node->next) {
227+
++count;
228+
}
229+
return count;
230+
}
231+
232+
uint32_t allocations() const { return allocations_; }
233+
234+
private:
235+
FreeNode* freeList_;
236+
alignas(std::max_align_t) std::byte pool_[ObjectSize * PoolSize];
237+
uint32_t allocations_;
238+
};
239+
240+
inline MemoryStats getMemoryStats() {
241+
return memoryTracker().snapshot();
242+
}
243+
244+
} // namespace gk
245+
246+
#endif // GK_MEMORY_POOL_H

0 commit comments

Comments
 (0)