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 void WasmSequencer ::reportSeqAborted(const Svc::WasmSequencer_RequestContext& value, const Fw::CmdResponse& response) {
283  // Respond to port invokers that the sequence exited
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) {
330  case SPACEWASM_TRAP_HOST:
358  default:
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 Os::Mutex* WasmSequencer::getGlobalAllocatorLock() {
437  static Os::Mutex s_globalAllocatorLock;
438  return &s_globalAllocatorLock;
439 }
440 
441 void WasmSequencer ::takeAllocatorLock() {
442  getGlobalAllocatorLock()->lock();
443 
444  auto status = spacewasm_fprime_acquire_global_allocator(this);
445  FW_ASSERT(status == SPACEWASM_OK, status);
446 }
447 
448 void WasmSequencer ::releaseAllocatorLock() {
449  auto status = spacewasm_fprime_release_global_allocator(this);
450  FW_ASSERT(status == SPACEWASM_OK, status);
451 
452  getGlobalAllocatorLock()->unlock();
453 }
454 
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
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.
Link-time delegating Mutex implementation.
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 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
void lock()
lock the mutex and assert success
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)
void unlock()
alias for unLock to meet BasicLockable requirements
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