GCC Code Coverage Report


Directory: ./
File: Svc/WasmSequencer/WasmSequencerHelpers.cpp
Date: 2026-09-23 22:11:34
Exec Total Coverage
Lines: 63 236 26.7%
Functions: 7 26 26.9%
Branches: 16 140 11.4%

Line Branch Exec Source
1 // ======================================================================
2 // \title WasmSequencerHelpers.cpp
3 // \author tumbar
4 // \brief cpp file for WasmSequencer component implementation class helpers
5 // ======================================================================
6
7 #include <cstddef>
8 #include "Fw/Types/Assert.hpp"
9 #include "Fw/Types/StringBase.hpp"
10 #include "Os/Console.hpp"
11 #include "Svc/WasmSequencer/WasmSequencer.hpp"
12 #include "Svc/WasmSequencer/WasmSequencer_MemoryGrowFailReasonEnumAc.hpp"
13 #include "Svc/WasmSequencer/fprime_spacewasm/include/fprime_spacewasm.h"
14 #include "config/FwAssertArgTypeAliasAc.h"
15 #include "config/FwSizeTypeAliasAc.h"
16 #include "config/WasmSequencerConfig.hpp"
17 #include "spacewasm.h"
18
19 namespace Svc {
20 // ----------------------------------------------------------------------
21 // Interpreter store and page-backed allocators
22 // ----------------------------------------------------------------------
23
24 2 U8* WasmSequencer ::globalAlloc(const U32 size, const U32 align) {
25 // The spacewasm PageAllocator only ever requests fixed-size pages of exactly
26 // SPACEWASM_PAGE_SIZE, aligned no more than the pool's alignment.
27 2 FW_ASSERT(size == Svc::WasmSequencerConfig::SPACEWASM_PAGE_SIZE, static_cast<FwAssertArgType>(size));
28 2 FW_ASSERT(align <= 8, static_cast<FwAssertArgType>(align));
29 2 FW_ASSERT(!this->m_heapPoisoned);
30
31
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (this->m_heapPagesUsed < this->m_config.heapPages) {
32 2 auto page = this->m_heapPages[this->m_heapPagesUsed];
33 2 FW_ASSERT(page != nullptr);
34 2 this->m_heapPagesUsed += 1;
35 2 return page;
36 } else {
37 // Out of pages.
38 ✗ return nullptr;
39 }
40 }
41
42 2 void WasmSequencer ::globalDealloc(const U8* ptr) {
43
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ptr == nullptr) {
44 ✗ return;
45 }
46
47 // Make sure the pointer that was given back to us is ours
48 2 bool found = false;
49
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 for (FwSizeType i = 0; i < this->m_config.heapPages; i++) {
50
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (ptr == this->m_heapPages[i]) {
51 2 found = true;
52 2 break;
53 }
54 }
55
56 2 FW_ASSERT(found);
57
58 // Decrement the number of pages used.
59 // Deallocation only happens when the store is being destroyed.
60 // We will assert that the used page count drops to zero once store destruction completes
61 2 FW_ASSERT(this->m_heapPagesUsed > 0);
62 2 this->m_heapPagesUsed -= 1;
63 2 this->m_heapPoisoned = true;
64 }
65
66 ✗ U8* WasmSequencer ::guestAlloc(FwSizeType size, U32 align) {
67 ✗ if (size == 0) {
68 ✗ return nullptr;
69 }
70
71 // Reject any request that cannot possibly fit the guest pool up front.
72 ✗ if (size > this->m_config.guestMemorySize || align > SPACEWASM_MEMORY_ALIGNMENT) {
73 ✗ return nullptr;
74 }
75
76 // Round the current offset up to the requested alignment
77 ✗ const FwSizeType a = (align < 1) ? 1 : static_cast<FwSizeType>(align);
78 ✗ const FwSizeType start = (this->m_guestPoolOffset + a - 1) & ~(a - 1);
79
80 // Compare against the pre-subtracted bound so `start + size` cannot overflow.
81 // `size <= this->m_config.guestMemorySize` (checked above) makes the subtraction non-negative.
82 ✗ if (start > this->m_config.guestMemorySize - size) {
83 ✗ return nullptr;
84 }
85 ✗ this->m_guestPoolOffset = start + size;
86 ✗ return &this->m_guestPool[start];
87 }
88
89 ✗ U8* WasmSequencer ::guestRealloc(U8* ptr, FwSizeType oldSize, FwSizeType newSize, U32 align) {
90 // We have received a memory.grow request.
91 // The following MUST be true for us to actually give the guest it's memory (otherwise fail):
92 // 1. This linear memory is the final allocated memory in the guest bump allocator
93 // 2. We have enough space free to actually do this request
94 // 3. You buy me a donut.
95
96 ✗ std::ptrdiff_t moduleMemOffset = ptr - this->m_guestPool;
97
98 // The pointer must lie within our guest pool.
99 ✗ FW_ASSERT(moduleMemOffset >= 0, static_cast<FwAssertArgType>(moduleMemOffset));
100 ✗ const FwSizeType offset = static_cast<FwSizeType>(moduleMemOffset);
101
102 // Check 1. i.e. the pointer is what we expect given old_size and current guest offset
103 ✗ if (offset + oldSize == this->m_guestPoolOffset) {
104 // Check 2. We have enough space in the guest pool to service this request
105 ✗ if (offset + newSize <= this->m_config.guestMemorySize) {
106 // ...now the donuts
107 // Allocate the new guest memory size
108 ✗ this->m_guestPoolOffset = offset + newSize;
109
110 // We return the same pointer since this is a strict grow and
111 // we already reserved the slot in front of this memory
112 ✗ return ptr;
113 }
114
115 // We are the last allocation, but the grown size does not fit the guest pool.
116 ✗ this->log_WARNING_LO_MemoryGrowRejected(WasmSequencer_MemoryGrowFailReason::INSUFFICIENT_POOL_SPACE,
117 static_cast<U64>(newSize));
118 ✗ return nullptr;
119 }
120
121 // We are not the last allocation in the bump pool, so we cannot grow in place.
122 ✗ this->log_WARNING_LO_MemoryGrowRejected(WasmSequencer_MemoryGrowFailReason::NOT_LAST_ALLOCATION,
123 static_cast<U64>(newSize));
124 ✗ return nullptr;
125 }
126
127 ✗ void WasmSequencer ::guestDealloc(const U8* ptr, const FwSizeType size) {
128 // Bump allocator: individual frees are no-ops. The whole guest pool is reset
129 // when a new store is created (destroyStore).
130 (void)ptr;
131 (void)size;
132 ✗ }
133
134 ✗ U8* WasmSequencer ::guestAllocCallback(void* userdata, size_t size, size_t align) {
135 ✗ FW_ASSERT(userdata != nullptr);
136 ✗ return static_cast<WasmSequencer*>(userdata)->guestAlloc(static_cast<FwSizeType>(size), static_cast<U32>(align));
137 }
138
139 ✗ U8* WasmSequencer ::guestReallocCallback(void* userdata, U8* ptr, size_t old_size, size_t new_size, size_t align) {
140 ✗ FW_ASSERT(userdata != nullptr);
141 // Do NOT narrow old_size/new_size to U32: a 4 GiB grow (new_size == 2^32) would alias to 0 and
142 // be accepted as a no-op grow. Pass full width; guestRealloc's fits-pool check rejects it.
143 ✗ return static_cast<WasmSequencer*>(userdata)->guestRealloc(
144 ✗ ptr, static_cast<FwSizeType>(old_size), static_cast<FwSizeType>(new_size), static_cast<U32>(align));
145 }
146
147 ✗ void WasmSequencer ::guestDeallocCallback(void* userdata, U8* ptr, size_t size, size_t align) {
148 ✗ FW_ASSERT(userdata != nullptr);
149 (void)align;
150 ✗ static_cast<WasmSequencer*>(userdata)->guestDealloc(ptr, static_cast<FwSizeType>(size));
151 ✗ }
152
153 1 void WasmSequencer ::createStore() {
154 1 FW_ASSERT(this->m_wasm == nullptr);
155
156
1/1
✓ Branch 1 taken 1 times.
1 this->takeAllocatorLock();
157
158 spacewasm_host_t host;
159
1/1
✓ Branch 1 taken 1 times.
1 spacewasm_status_t status = spacewasm_host_new(1, &host);
160 1 FW_ASSERT(status == SPACEWASM_OK, status);
161
162
1/1
✓ Branch 1 taken 1 times.
1 this->hostFprimeV1(&host);
163
164 spacewasm_compiler_options_t options;
165 1 options.allow_memory_grow = true; // implemented in a restricted way (see guestRealloc)
166 1 options.max_backpatch_iterations = Svc::WasmSequencerConfig::MAX_BACKPATCH_ITERATIONS;
167 1 options.max_code_pages = this->m_config.maxCodePages;
168
169
1/1
✓ Branch 1 taken 1 times.
1 status = spacewasm_new(&host, this->m_config.stackSize, this->m_config.maxGuestModules, options, &this->m_wasm);
170
171 1 this->m_guest_allocator =
172
1/1
✓ Branch 1 taken 1 times.
1 spacewasm_allocator_new(&WasmSequencer::guestAllocCallback, &WasmSequencer::guestReallocCallback,
173 &WasmSequencer::guestDeallocCallback, /* userdata */ this);
174
175
1/1
✓ Branch 1 taken 1 times.
1 this->releaseAllocatorLock();
176
177 // Make sure the store allocation succeeded.
178 // Failure means the heap memory is too small to host this number of modules + Wasm stack...
179 //
180 // If status == SPACEWASM_ERR_PAGE_TOO_SMALL:
181 // - Increase Svc::WasmSequencerConfig::SPACEWASM_PAGE_SIZE
182 // If SPACEWASM_ERR_OUT_OF_MEMORY / SPACEWASM_ERR_ALLOC_FAILED:
183 // - Increase heapPages in configure()
184 // - Lower maxGuestModules in configure()
185 // - Lower stackSize in configure()
186 1 FW_ASSERT(status == SPACEWASM_OK, status);
187
188 // Make sure the guest allocator creation succeeded
189 1 FW_ASSERT(this->m_guest_allocator != nullptr);
190
191
1/1
✓ Branch 1 taken 1 times.
1 this->log_DIAGNOSTIC_StoreAllocationSucceeded(this->m_config.maxGuestModules);
192 1 }
193
194 1 void WasmSequencer ::destroyStore() {
195 1 FW_ASSERT(this->m_wasm != nullptr);
196 1 FW_ASSERT(this->m_guest_allocator != nullptr);
197
198 1 this->takeAllocatorLock();
199 1 spacewasm_destroy(this->m_wasm);
200 1 spacewasm_allocator_destroy(this->m_guest_allocator);
201 1 this->releaseAllocatorLock();
202 1 this->m_wasm = nullptr;
203 1 this->m_guest_allocator = nullptr;
204
205 // Make sure we cleanly deallocated all the heap memory
206 1 FW_ASSERT(this->m_heapPagesUsed == 0, static_cast<FwAssertArgType>(this->m_heapPagesUsed));
207
208 // Reset the guest linear-memory bump allocator; all guest allocations were
209 // owned by the store that just went away.
210 1 this->m_guestPoolOffset = 0;
211 1 this->m_heapPoisoned = false;
212 1 }
213
214 ✗ spacewasm_status_t WasmSequencer ::validateModuleMain(WasmSequencer_ModuleIdx moduleIdx) const {
215 ✗ FW_ASSERT(this->m_wasm != nullptr);
216
217 U32 mainIndex;
218 ✗ auto status = spacewasm_find_export_func(this->m_wasm, static_cast<U32>(moduleIdx), "main", &mainIndex);
219
220 ✗ if (status == SPACEWASM_OK) {
221 // We accept both the [] -> [] and [] -> i32 main signatures. Checking
222 // against "" first yields PARAM_LEN_MISMATCH (not BAD_SIGNATURE) for an
223 // i32-returning main -- BAD_SIGNATURE only flags a malformed signature
224 // *string* -- so fall back on any mismatch, not just BAD_SIGNATURE.
225 ✗ status = spacewasm_check_func_signature(this->m_wasm, static_cast<U32>(moduleIdx), mainIndex, "", "");
226 ✗ if (status != SPACEWASM_OK) {
227 ✗ status = spacewasm_check_func_signature(this->m_wasm, static_cast<U32>(moduleIdx), mainIndex, "", "i");
228 }
229 }
230
231 ✗ return status;
232 }
233
234 ✗ U32 WasmSequencer ::makeCmdUid() const {
235 // cmdUid is formatted XXYY, where XX are the low 16 bits of m_sequencesStarted
236 // and YY are the low 16 bits of m_tlm.commandsDispatched. On the way back in via
237 // cmdResponseIn this lets us check A) that the response is from the current
238 // sequence (modulo 2^16) and B) that it is this exact command instance and not
239 // another dispatch of the same opcode.
240 ✗ return static_cast<U32>(((this->m_sequencesStarted & 0xFFFF) << 16) | (this->m_tlm.commandsDispatched & 0xFFFF));
241 }
242
243 ✗ void WasmSequencer ::respondToRequest(const Svc::WasmSequencer_RequestContext& value, const Fw::CmdResponse& response) {
244 ✗ switch (value.get_source()) {
245 ✗ case WasmSequencer_SignalSource::COMMAND_RUN:
246 case WasmSequencer_SignalSource::COMMAND_INVOKE:
247 case WasmSequencer_SignalSource::COMMAND_LOAD:
248 // The request originated from a command; answer it on cmdResponse.
249 ✗ this->cmdResponse_out(value.get_cmdCtx().get_opcode(), value.get_cmdCtx().get_cmdSeq(), response);
250 ✗ break;
251 ✗ case WasmSequencer_SignalSource::PORT_RUN:
252 case WasmSequencer_SignalSource::PORT_INVOKE:
253 // Port-driven requests have no command response to send.
254 ✗ break;
255 ✗ default:
256 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(value.get_source()));
257 ✗ break;
258 }
259 ✗ }
260
261 ✗ void WasmSequencer ::respondToWaiting(const Fw::CmdResponse& response) {
262 // Drain every WAIT command blocked on sequence completion, answering each.
263 ✗ WaitingCmd cmd{};
264 ✗ while (this->m_waiting.dequeue(cmd) == Fw::Success::SUCCESS) {
265 ✗ this->cmdResponse_out(cmd.opCode, cmd.cmdSeq, response);
266 }
267 ✗ }
268
269 ✗ void WasmSequencer ::reportSeqDone(const Svc::WasmSequencer_RequestContext& value, const Fw::CmdResponse& response) {
270 // seqStart/seqDone are RUN-scoped: seqStartOut is only emitted for RUN sources
271 // (see reportModuleStarted), so only emit the matching seqDoneOut for those.
272 // A non-RUN completion (INVOKE / LOAD) reports neither, keeping the pair balanced.
273 ✗ if (value.get_source() != Svc::WasmSequencer_SignalSource::COMMAND_RUN &&
274 ✗ value.get_source() != Svc::WasmSequencer_SignalSource::PORT_RUN) {
275 ✗ return;
276 }
277 ✗ if (this->isConnected_seqDoneOut_OutputPort(0)) {
278 ✗ this->seqDoneOut_out(0, 0, 0, response);
279 }
280 }
281
282 ✗ void WasmSequencer ::reportSeqAborted(const Svc::WasmSequencer_RequestContext& value, const Fw::CmdResponse& response) {
283 // Respond to port invokers that the sequence exited
284 ✗ if (value.get_source() == Svc::WasmSequencer_SignalSource::PORT_RUN) {
285 ✗ this->reportSeqDone(value, response);
286 }
287 ✗ }
288
289 ✗ spacewasm_status_t WasmSequencer ::setGlobal(const Fw::StringBase& moduleName,
290 const Fw::StringBase& name,
291 spacewasm_value_t value) {
292 ✗ U32 moduleIdx = 0;
293 ✗ spacewasm_status_t status = spacewasm_find_module(this->m_wasm, moduleName.toChar(), &moduleIdx);
294 ✗ if (status != SPACEWASM_OK) {
295 ✗ return status;
296 }
297
298 U32 globalIdx;
299 ✗ status = spacewasm_find_global(this->m_wasm, moduleIdx, name.toChar(), &globalIdx);
300 ✗ if (status != SPACEWASM_OK) {
301 ✗ return status;
302 }
303
304 ✗ return spacewasm_set_global(this->m_wasm, moduleIdx, globalIdx, value);
305 }
306
307 ✗ spacewasm_status_t WasmSequencer ::getGlobal(const Fw::StringBase& moduleName,
308 const Fw::StringBase& name,
309 spacewasm_value_t& value) {
310 ✗ U32 moduleIdx = 0;
311 ✗ spacewasm_status_t status = spacewasm_find_module(this->m_wasm, moduleName.toChar(), &moduleIdx);
312 ✗ if (status != SPACEWASM_OK) {
313 ✗ return status;
314 }
315
316 U32 globalIdx;
317 ✗ status = spacewasm_find_global(this->m_wasm, moduleIdx, name.toChar(), &globalIdx);
318 ✗ if (status != SPACEWASM_OK) {
319 ✗ return status;
320 }
321
322 ✗ return spacewasm_get_global(this->m_wasm, moduleIdx, globalIdx, &value);
323 }
324
325 ✗ Svc::WasmSequencer_TrapReason::T WasmSequencer ::mapTrapReason(spacewasm_trap_t trap) {
326 // spacewasm_trap_t values 0..14 map 1:1 onto the TrapReason enum ordinals.
327 ✗ switch (trap) {
328 ✗ case SPACEWASM_TRAP_UNREACHABLE:
329 ✗ return Svc::WasmSequencer_TrapReason::UNREACHABLE;
330 ✗ case SPACEWASM_TRAP_HOST:
331 ✗ return Svc::WasmSequencer_TrapReason::HOST;
332 ✗ case SPACEWASM_TRAP_DIVIDE_BY_ZERO:
333 ✗ return Svc::WasmSequencer_TrapReason::DIVIDE_BY_ZERO;
334 ✗ case SPACEWASM_TRAP_INVALID_TABLE_INDEX:
335 ✗ return Svc::WasmSequencer_TrapReason::INVALID_TABLE_INDEX;
336 ✗ case SPACEWASM_TRAP_INVALID_TABLE_FUNCTION_TYPE:
337 ✗ return Svc::WasmSequencer_TrapReason::INVALID_TABLE_FUNCTION_TYPE;
338 ✗ case SPACEWASM_TRAP_UNINITIALIZED_TABLE_ELEMENT:
339 ✗ return Svc::WasmSequencer_TrapReason::UNINITIALIZED_TABLE_ELEMENT;
340 ✗ case SPACEWASM_TRAP_GLOBAL_GET_FAILED:
341 ✗ return Svc::WasmSequencer_TrapReason::GLOBAL_GET_FAILED;
342 ✗ case SPACEWASM_TRAP_GLOBAL_SET_FAILED:
343 ✗ return Svc::WasmSequencer_TrapReason::GLOBAL_SET_FAILED;
344 ✗ case SPACEWASM_TRAP_OUT_OF_MEMORY:
345 ✗ return Svc::WasmSequencer_TrapReason::OUT_OF_MEMORY;
346 ✗ case SPACEWASM_TRAP_MEMORY_REF_NOT_UNIQUE:
347 ✗ return Svc::WasmSequencer_TrapReason::MEMORY_REF_NOT_UNIQUE;
348 ✗ case SPACEWASM_TRAP_MEMORY_OUT_OF_BOUNDS:
349 ✗ return Svc::WasmSequencer_TrapReason::MEMORY_OUT_OF_BOUNDS;
350 ✗ case SPACEWASM_TRAP_STACK_OVERFLOW:
351 ✗ return Svc::WasmSequencer_TrapReason::STACK_OVERFLOW;
352 ✗ case SPACEWASM_TRAP_UNREPRESENTABLE_RESULT:
353 ✗ return Svc::WasmSequencer_TrapReason::UNREPRESENTABLE_RESULT;
354 ✗ case SPACEWASM_TRAP_INTEGER_OVERFLOW:
355 ✗ return Svc::WasmSequencer_TrapReason::INTEGER_OVERFLOW;
356 ✗ case SPACEWASM_TRAP_BAD_CONVERSION_TO_INTEGER:
357 ✗ return Svc::WasmSequencer_TrapReason::BAD_CONVERSION_TO_INTEGER;
358 ✗ default:
359 ✗ return Svc::WasmSequencer_TrapReason::HOST;
360 }
361 }
362
363 ✗ void WasmSequencer ::setSequenceName(const Fw::StringBase& filePath, const Fw::StringBase& moduleName) {
364 // A non-empty module name was supplied to LOAD; use it verbatim.
365 ✗ if (moduleName.length() > 0) {
366 ✗ this->m_tlm.sequenceName = moduleName;
367 ✗ return;
368 }
369
370 // RUN / LOAD: derive from the file's basename with any ".wasm" suffix stripped.
371 ✗ const char* const path = filePath.toChar();
372 ✗ const FwSizeType len = static_cast<FwSizeType>(filePath.length());
373
374 // Find the start of the basename (character after the last '/').
375 ✗ FwSizeType nameLen = 0;
376 ✗ const char* const base = WasmSequencer::pathBaseName(path, len, nameLen);
377
378 // Drop a trailing ".wasm" if present.
379 static const char suffix[] = ".wasm";
380 ✗ const FwSizeType suffixLen = static_cast<FwSizeType>(sizeof(suffix) - 1);
381 ✗ if (nameLen >= suffixLen) {
382 ✗ bool match = true;
383 ✗ for (FwSizeType i = 0; i < suffixLen; i++) {
384 ✗ if (base[nameLen - suffixLen + i] != suffix[i]) {
385 ✗ match = false;
386 ✗ break;
387 }
388 }
389 ✗ if (match) {
390 ✗ nameLen -= suffixLen;
391 }
392 }
393
394 char name[FileNameStringSize];
395 ✗ FwSizeType n = 0;
396 ✗ for (FwSizeType i = 0; i < nameLen && n < static_cast<FwSizeType>(sizeof(name) - 1); i++) {
397 ✗ name[n++] = base[i];
398 }
399 ✗ name[n] = '\0';
400 ✗ this->m_tlm.sequenceName = name;
401 }
402
403 ✗ const char* WasmSequencer ::pathBaseName(const char* path, FwSizeType len, FwSizeType& outLen) {
404 ✗ FW_ASSERT(path != nullptr);
405 // Basename starts just after the last '/', or at the start if there is none.
406 ✗ FwSizeType start = 0;
407 ✗ for (FwSizeType i = 0; i < len; i++) {
408 ✗ if (path[i] == '/') {
409 ✗ start = i + 1;
410 }
411 }
412 ✗ outLen = len - start;
413 ✗ return path + start;
414 }
415
416 ✗ bool WasmSequencer ::pathHasParentTraversal(const Fw::StringBase& path) {
417 ✗ const char* const s = path.toChar();
418 ✗ const FwSizeType len = static_cast<FwSizeType>(path.length());
419
420 // Walk the '/'-delimited segments; reject if any segment is exactly "..".
421 ✗ FwSizeType segStart = 0;
422 ✗ for (FwSizeType i = 0; i <= len; i++) {
423 ✗ if (i == len || s[i] == '/') {
424 ✗ const FwSizeType segLen = i - segStart;
425 ✗ if (segLen == 2 && s[segStart] == '.' && s[segStart + 1] == '.') {
426 ✗ return true;
427 }
428 ✗ segStart = i + 1;
429 }
430 }
431 ✗ return false;
432 }
433
434 8 Os::Mutex* WasmSequencer::getGlobalAllocatorLock() {
435 //! Process-wide lock serializing access to the spacewasm global-allocator registry.
436 //! Initialized by the first component instance's constructor
437
4/7
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
8 static Os::Mutex s_globalAllocatorLock;
438 8 return &s_globalAllocatorLock;
439 }
440
441 2 void WasmSequencer ::takeAllocatorLock() {
442 2 getGlobalAllocatorLock()->lock();
443
444 2 auto status = spacewasm_fprime_acquire_global_allocator(this);
445 2 FW_ASSERT(status == SPACEWASM_OK, status);
446 2 }
447
448 2 void WasmSequencer ::releaseAllocatorLock() {
449 2 auto status = spacewasm_fprime_release_global_allocator(this);
450 2 FW_ASSERT(status == SPACEWASM_OK, status);
451
452 2 getGlobalAllocatorLock()->unlock();
453 2 }
454
455 //! Panic hook the spacewasm interpreter calls on a fatal internal error.
456 // Must not return.
457 ✗ extern "C" void spacewasm_panic(const U8* filename,
458 std::size_t filename_len,
459 U32 line,
460 const U8* msg,
461 std::size_t len) {
462 ✗ Fw::String fmtMsg;
463 ✗ (void)fmtMsg.format("Rust panic %.*s:%d: %.*s\n", static_cast<int>(filename_len),
464 reinterpret_cast<const char*>(filename), static_cast<int>(line), static_cast<int>(len),
465 reinterpret_cast<const char*>(msg));
466 ✗ Os::Console::write(fmtMsg);
467
468 // Rust panics map to FSW assertions
469 ✗ FW_ASSERT(false);
470 ✗ }
471
472 } // namespace Svc
473