F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
WasmSequencerHelpers.cpp
Go to the documentation of this file.
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"
10 #include "Os/Console.hpp"
16 #include "config/WasmSequencerConfig.hpp"
17 #include "spacewasm.h"
18 
19 namespace Svc {
20 // ----------------------------------------------------------------------
21 // Interpreter store and page-backed allocators
22 // ----------------------------------------------------------------------
23 
24 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  FW_ASSERT(size == Svc::WasmSequencerConfig::SPACEWASM_PAGE_SIZE, static_cast<FwAssertArgType>(size));
28  FW_ASSERT(align <= 8, static_cast<FwAssertArgType>(align));
29  FW_ASSERT(!this->m_heapPoisoned);
30 
31  if (this->m_heapPagesUsed < this->m_config.heapPages) {
32  auto page = this->m_heapPages[this->m_heapPagesUsed];
33  FW_ASSERT(page != nullptr);
34  this->m_heapPagesUsed += 1;
35  return page;
36  } else {
37  // Out of pages.
38  return nullptr;
39  }
40 }
41 
42 void WasmSequencer ::globalDealloc(const U8* ptr) {
43  if (ptr == nullptr) {
44  return;
45  }
46 
47  // Make sure the pointer that was given back to us is ours
48  bool found = false;
49  for (FwSizeType i = 0; i < this->m_config.heapPages; i++) {
50  if (ptr == this->m_heapPages[i]) {
51  found = true;
52  break;
53  }
54  }
55 
56  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  FW_ASSERT(this->m_heapPagesUsed > 0);
62  this->m_heapPagesUsed -= 1;
63  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.
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.
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 void WasmSequencer ::createStore() {
154  FW_ASSERT(this->m_wasm == nullptr);
155 
156  this->takeAllocatorLock();
157 
158  spacewasm_host_t host;
159  spacewasm_status_t status = spacewasm_host_new(1, &host);
160  FW_ASSERT(status == SPACEWASM_OK, status);
161 
162  this->hostFprimeV1(&host);
163 
165  options.allow_memory_grow = true; // implemented in a restricted way (see guestRealloc)
167  options.max_code_pages = this->m_config.maxCodePages;
168 
169  status = spacewasm_new(&host, this->m_config.stackSize, this->m_config.maxGuestModules, options, &this->m_wasm);
170 
171  this->m_guest_allocator =
172  spacewasm_allocator_new(&WasmSequencer::guestAllocCallback, &WasmSequencer::guestReallocCallback,
173  &WasmSequencer::guestDeallocCallback, /* userdata */ this);
174 
175  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  FW_ASSERT(status == SPACEWASM_OK, status);
187 
188  // Make sure the guest allocator creation succeeded
189  FW_ASSERT(this->m_guest_allocator != nullptr);
190 
192 }
193 
194 void WasmSequencer ::destroyStore() {
195  FW_ASSERT(this->m_wasm != nullptr);
196  FW_ASSERT(this->m_guest_allocator != nullptr);
197 
198  this->takeAllocatorLock();
199  spacewasm_destroy(this->m_wasm);
200  spacewasm_allocator_destroy(this->m_guest_allocator);
201  this->releaseAllocatorLock();
202  this->m_wasm = nullptr;
203  this->m_guest_allocator = nullptr;
204 
205  // Make sure we cleanly deallocated all the heap memory
206  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  this->m_guestPoolOffset = 0;
211  this->m_heapPoisoned = false;
212 }
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()) {
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;
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.
275  return;
276  }
277  if (this->isConnected_seqDoneOut_OutputPort(0)) {
278  this->seqDoneOut_out(0, 0, 0, response);
279  }
280 }
281 
282 spacewasm_status_t WasmSequencer ::setGlobal(const Fw::StringBase& moduleName,
283  const Fw::StringBase& name,
284  spacewasm_value_t value) {
285  U32 moduleIdx = 0;
286  spacewasm_status_t status = spacewasm_find_module(this->m_wasm, moduleName.toChar(), &moduleIdx);
287  if (status != SPACEWASM_OK) {
288  return status;
289  }
290 
291  U32 globalIdx;
292  status = spacewasm_find_global(this->m_wasm, moduleIdx, name.toChar(), &globalIdx);
293  if (status != SPACEWASM_OK) {
294  return status;
295  }
296 
297  return spacewasm_set_global(this->m_wasm, moduleIdx, globalIdx, value);
298 }
299 
300 spacewasm_status_t WasmSequencer ::getGlobal(const Fw::StringBase& moduleName,
301  const Fw::StringBase& name,
302  spacewasm_value_t& value) {
303  U32 moduleIdx = 0;
304  spacewasm_status_t status = spacewasm_find_module(this->m_wasm, moduleName.toChar(), &moduleIdx);
305  if (status != SPACEWASM_OK) {
306  return status;
307  }
308 
309  U32 globalIdx;
310  status = spacewasm_find_global(this->m_wasm, moduleIdx, name.toChar(), &globalIdx);
311  if (status != SPACEWASM_OK) {
312  return status;
313  }
314 
315  return spacewasm_get_global(this->m_wasm, moduleIdx, globalIdx, &value);
316 }
317 
318 Svc::WasmSequencer_TrapReason::T WasmSequencer ::mapTrapReason(spacewasm_trap_t trap) {
319  // spacewasm_trap_t values 0..14 map 1:1 onto the TrapReason enum ordinals.
320  switch (trap) {
323  case SPACEWASM_TRAP_HOST:
351  default:
353  }
354 }
355 
356 void WasmSequencer ::setSequenceName(const Fw::StringBase& filePath, const Fw::StringBase& moduleName) {
357  // A non-empty module name was supplied to LOAD; use it verbatim.
358  if (moduleName.length() > 0) {
359  this->m_tlm.sequenceName = moduleName;
360  return;
361  }
362 
363  // RUN / LOAD: derive from the file's basename with any ".wasm" suffix stripped.
364  const char* const path = filePath.toChar();
365  const FwSizeType len = static_cast<FwSizeType>(filePath.length());
366 
367  // Find the start of the basename (character after the last '/').
368  FwSizeType nameLen = 0;
369  const char* const base = WasmSequencer::pathBaseName(path, len, nameLen);
370 
371  // Drop a trailing ".wasm" if present.
372  static const char suffix[] = ".wasm";
373  const FwSizeType suffixLen = static_cast<FwSizeType>(sizeof(suffix) - 1);
374  if (nameLen >= suffixLen) {
375  bool match = true;
376  for (FwSizeType i = 0; i < suffixLen; i++) {
377  if (base[nameLen - suffixLen + i] != suffix[i]) {
378  match = false;
379  break;
380  }
381  }
382  if (match) {
383  nameLen -= suffixLen;
384  }
385  }
386 
387  char name[FileNameStringSize];
388  FwSizeType n = 0;
389  for (FwSizeType i = 0; i < nameLen && n < static_cast<FwSizeType>(sizeof(name) - 1); i++) {
390  name[n++] = base[i];
391  }
392  name[n] = '\0';
393  this->m_tlm.sequenceName = name;
394 }
395 
396 const char* WasmSequencer ::pathBaseName(const char* path, FwSizeType len, FwSizeType& outLen) {
397  FW_ASSERT(path != nullptr);
398  // Basename starts just after the last '/', or at the start if there is none.
399  FwSizeType start = 0;
400  for (FwSizeType i = 0; i < len; i++) {
401  if (path[i] == '/') {
402  start = i + 1;
403  }
404  }
405  outLen = len - start;
406  return path + start;
407 }
408 
409 bool WasmSequencer ::pathHasParentTraversal(const Fw::StringBase& path) {
410  const char* const s = path.toChar();
411  const FwSizeType len = static_cast<FwSizeType>(path.length());
412 
413  // Walk the '/'-delimited segments; reject if any segment is exactly "..".
414  FwSizeType segStart = 0;
415  for (FwSizeType i = 0; i <= len; i++) {
416  if (i == len || s[i] == '/') {
417  const FwSizeType segLen = i - segStart;
418  if (segLen == 2 && s[segStart] == '.' && s[segStart + 1] == '.') {
419  return true;
420  }
421  segStart = i + 1;
422  }
423  }
424  return false;
425 }
426 
427 Os::Mutex* WasmSequencer::getGlobalAllocatorLock() {
430  static Os::Mutex s_globalAllocatorLock;
431  return &s_globalAllocatorLock;
432 }
433 
434 void WasmSequencer ::takeAllocatorLock() {
435  getGlobalAllocatorLock()->lock();
436 
437  auto status = spacewasm_fprime_acquire_global_allocator(this);
438  FW_ASSERT(status == SPACEWASM_OK, status);
439 }
440 
441 void WasmSequencer ::releaseAllocatorLock() {
442  auto status = spacewasm_fprime_release_global_allocator(this);
443  FW_ASSERT(status == SPACEWASM_OK, status);
444 
445  getGlobalAllocatorLock()->unlock();
446 }
447 
449 // Must not return.
450 extern "C" void spacewasm_panic(const U8* filename,
451  std::size_t filename_len,
452  U32 line,
453  const U8* msg,
454  std::size_t len) {
455  Fw::String fmtMsg;
456  (void)fmtMsg.format("Rust panic %.*s:%d: %.*s\n", static_cast<int>(filename_len),
457  reinterpret_cast<const char*>(filename), static_cast<int>(line), static_cast<int>(len),
458  reinterpret_cast<const char*>(msg));
459  Os::Console::write(fmtMsg);
460 
461  // Rust panics map to FSW assertions
462  FW_ASSERT(false);
463 }
464 
465 } // namespace Svc
An indirect call tried to map to a table function out of range.
void spacewasm_panic(const U8 *filename, std::size_t filename_len, U32 line, const U8 *msg, std::size_t len)
Panic hook the spacewasm interpreter calls on a fatal internal error.
Representing success.
PlatformSizeType FwSizeType
constexpr FwSizeType SPACEWASM_PAGE_SIZE
static void write(const Fw::ConstStringBase &message)
write message to console
Definition: Console.cpp:52
struct spacewasm_allocator_t * spacewasm_allocator_new(spacewasm_alloc_fn_t alloc, spacewasm_realloc_fn_t realloc, spacewasm_dealloc_fn_t dealloc, void *userdata)
The grown size would exceed the configured guest memory pool.
U8 maxGuestModules
Maximum number of Wasm modules that may be loaded into the sequencer&#39;s store.
WasmSequencer(const char *const compName)
Construct WasmSequencer object.
virtual const CHAR * toChar() const =0
Convert to a C-style char*.
void cmdResponse_out(FwOpcodeType opCode, U32 cmdSeq, Fw::CmdResponse response)
Emit command response.
spacewasm_status_t spacewasm_get_global(struct spacewasm_t *engine, uint32_t module_idx, uint32_t global_index, struct spacewasm_value_t *out)
Enum representing a command response.
void log_WARNING_LO_MemoryGrowRejected(const Svc::WasmSequencer_MemoryGrowFailReason &reason, U64 requestedSize) const
An indirect call referenced an uninitialized table element.
spacewasm_status_t spacewasm_find_export_func(struct spacewasm_t *engine, uint32_t module_idx, const char *name, uint32_t *out_index)
static constexpr FwSizeType SPACEWASM_MEMORY_ALIGNMENT
SpaceWasm has a hard-coded memory alignment requirement.
Svc::WasmSequencer_SignalSource::T get_source() const
Get member source.
The function type in an indirect call does not match the function pointer&#39;s type. ...
void unlock()
alias for unLock to meet BasicLockable requirements
Definition: Mutex.hpp:64
void log_DIAGNOSTIC_StoreAllocationSucceeded(U16 moduleCount) const
spacewasm_trap_t
Definition: spacewasm.h:266
Integer or floating point division by zero.
memory.grow failed because a host function has taken ownership of a memory
void seqDoneOut_out(FwIndexType portNum, FwOpcodeType opCode, U32 cmdSeq, const Fw::CmdResponse &response) const
Invoke output port seqDoneOut.
bool isConnected_seqDoneOut_OutputPort(FwIndexType portNum) const
A host function has noted an unrecoverable failure.
spacewasm_status_t spacewasm_fprime_release_global_allocator(void *userdata)
Svc::WasmSequencer_CommandRequest & get_cmdCtx()
Get member cmdCtx.
spacewasm_status_t spacewasm_find_global(struct spacewasm_t *engine, uint32_t module_idx, const char *name, uint32_t *out_index)
The memory is not the last allocation in the guest pool; it cannot grow in place. ...
FormatStatus format(const CHAR *formatString,...)
write formatted string to buffer
Definition: StringBase.cpp:58
Success dequeue(T &e) override
Definition: FifoQueue.hpp:68
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
spacewasm_status_t spacewasm_new(struct spacewasm_host_t *host, size_t stack_size, size_t max_modules, struct spacewasm_compiler_options_t options, struct spacewasm_t **out_engine)
spacewasm_status_t
Definition: spacewasm.h:37
spacewasm_status_t spacewasm_check_func_signature(struct spacewasm_t *engine, uint32_t module_idx, uint32_t func_index, const char *params_sig, const char *returns_sig)
spacewasm_status_t spacewasm_host_new(size_t len, struct spacewasm_host_t *dest)
void spacewasm_destroy(struct spacewasm_t *engine)
RateGroupDivider component implementation.
spacewasm_status_t spacewasm_fprime_acquire_global_allocator(void *userdata)
virtual SizeType length() const
Get the length of the string.
spacewasm_status_t spacewasm_find_module(struct spacewasm_t *engine, const char *name, uint32_t *out_index)
Declares F Prime string base class.
void start(FwTaskPriorityType priority=Os::Task::TASK_PRIORITY_DEFAULT, FwSizeType stackSize=Os::Task::TASK_DEFAULT, FwSizeType cpuAffinity=Os::Task::TASK_DEFAULT, FwTaskIdType identifier=static_cast< FwTaskIdType >(Os::Task::TASK_DEFAULT))
called by instantiator when task is to be started
spacewasm_status_t spacewasm_set_global(struct spacewasm_t *engine, uint32_t module_idx, uint32_t global_index, struct spacewasm_value_t value)
void spacewasm_allocator_destroy(struct spacewasm_allocator_t *allocator)
#define FW_ASSERT(...)
Definition: Assert.hpp:14
void lock()
lock the mutex and assert success
Definition: Mutex.cpp:34