F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
FpySequencerDirectives.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 #include <cmath>
3 #include <cstring>
4 #include <type_traits>
5 #include "Fw/Com/ComPacket.hpp"
8 
9 namespace Svc {
10 
11 void FpySequencer::sendSignal(Signal signal) {
12  switch (signal) {
15  break;
16  }
19  break;
20  }
23  break;
24  }
27  break;
28  }
29  default: {
30  FW_ASSERT(false, static_cast<FwAssertArgType>(signal));
31  }
32  }
33 }
34 
35 // utility method for updating telemetry based on a directive error code
36 void FpySequencer::handleDirectiveErrorCode(Fpy::DirectiveId id, DirectiveError err) {
37  this->m_tlm.lastDirectiveError = err;
38  if (err != DirectiveError::NO_ERROR) {
39  this->m_tlm.directiveErrorIndex = this->currentStatementIdx();
40  this->m_tlm.directiveErrorId = id;
41  }
42 }
43 
44 Fw::Success FpySequencer::sendCmd(FwOpcodeType opcode, const U8* argBuf, FwSizeType argBufSize) {
45  Fw::ComBuffer cmdBuf;
46  Fw::SerializeStatus stat =
47  cmdBuf.serializeFrom(static_cast<FwPacketDescriptorType>(Fw::ComPacketType::FW_PACKET_COMMAND));
48  // TODO should I assert here? this really shouldn't fail, I should just add a static assert
49  // on com buf size and then assert here
51  return Fw::Success::FAILURE;
52  }
53  stat = cmdBuf.serializeFrom(opcode);
55  return Fw::Success::FAILURE;
56  }
57  stat = cmdBuf.serializeFrom(argBuf, argBufSize, Fw::Serialization::OMIT_LENGTH);
59  return Fw::Success::FAILURE;
60  }
61 
62  // calculate the unique command identifier:
63  // cmd UID is formatted like XXYY, where XX are the first two bytes of the m_sequencesStarted counter
64  // and YY are the first two bytes of the m_statementsDispatched counter.
65  // this way, we know when we get a cmd back A) whether or not it's from this sequence (modulo 2^16) and B)
66  // whether or not it's this specific instance of the cmd in the sequence, and not another one with the same opcode
67  // somewhere else in the file.
68  // if we put this uid in the context we send to the cmdDisp, we will get it back when the cmd returns
69  U32 cmdUid =
70  static_cast<U32>(((this->m_sequencesStarted & 0xFFFF) << 16) | (this->m_statementsDispatched & 0xFFFF));
71 
72  this->cmdOut_out(0, cmdBuf, cmdUid);
73 
74  return Fw::Success::SUCCESS;
75 }
76 
77 // returns true if a directive can be executed. a directive is only ever dispatched in
78 // RUNNING.DISPATCH_STATEMENT, after which the sequencer waits in RUNNING.AWAITING_STATEMENT_RESPONSE.
79 // CANCEL and RUN are higher priority than directives, so a sequence can be cancelled while its directive is
80 // still queued, and a new sequence can be started and dispatch its first statement before that directive is
81 // serviced. neither of those directives may execute
82 bool FpySequencer::checkDirectiveExpected(Fpy::DirectiveId directive, U64 sequenceIndex) {
84  this->log_WARNING_LO_DirectiveWhileNotAwaiting(directive, static_cast<I32>(this->sequencer_getState()));
85  return false;
86  }
87  if (sequenceIndex != this->m_sequencesStarted) {
88  this->log_WARNING_LO_DirectiveFromOldSequence(directive, sequenceIndex, this->m_sequencesStarted);
89  return false;
90  }
91  return true;
92 }
93 
96  U64 sequenceIndex) {
97  if (!this->checkDirectiveExpected(Fpy::DirectiveId::WAIT_REL, sequenceIndex)) {
98  return;
99  }
101  this->sendSignal(this->waitRel_directiveHandler(directive, error));
102  handleDirectiveErrorCode(Fpy::DirectiveId::WAIT_REL, error);
103 }
104 
107  U64 sequenceIndex) {
108  if (!this->checkDirectiveExpected(Fpy::DirectiveId::WAIT_ABS, sequenceIndex)) {
109  return;
110  }
112  this->sendSignal(this->waitAbs_directiveHandler(directive, error));
113  handleDirectiveErrorCode(Fpy::DirectiveId::WAIT_ABS, error);
114 }
115 
118  U64 sequenceIndex) {
119  if (!this->checkDirectiveExpected(Fpy::DirectiveId::GOTO, sequenceIndex)) {
120  return;
121  }
123  this->sendSignal(this->goto_directiveHandler(directive, error));
124  handleDirectiveErrorCode(Fpy::DirectiveId::GOTO, error);
125 }
126 
129  U64 sequenceIndex) {
130  if (!this->checkDirectiveExpected(Fpy::DirectiveId::IF, sequenceIndex)) {
131  return;
132  }
134  this->sendSignal(this->if_directiveHandler(directive, error));
135  handleDirectiveErrorCode(Fpy::DirectiveId::IF, error);
136 }
137 
140  U64 sequenceIndex) {
141  if (!this->checkDirectiveExpected(Fpy::DirectiveId::NO_OP, sequenceIndex)) {
142  return;
143  }
145  this->sendSignal(this->noOp_directiveHandler(directive, error));
146  handleDirectiveErrorCode(Fpy::DirectiveId::NO_OP, error);
147 }
148 
151  U64 sequenceIndex) {
152  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_TLM_VAL, sequenceIndex)) {
153  return;
154  }
156  this->sendSignal(this->pushTlmVal_directiveHandler(directive, error));
157  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TLM_VAL, error);
158 }
159 
163  U64 sequenceIndex) {
164  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_TLM_VAL_AND_TIME, sequenceIndex)) {
165  return;
166  }
168  this->sendSignal(this->pushTlmValAndTime_directiveHandler(directive, error));
169  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TLM_VAL_AND_TIME, error);
170 }
171 
174  U64 sequenceIndex) {
175  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_PRM, sequenceIndex)) {
176  return;
177  }
179  this->sendSignal(this->pushPrm_directiveHandler(directive, error));
180  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_PRM, error);
181 }
182 
185  U64 sequenceIndex) {
186  if (!this->checkDirectiveExpected(Fpy::DirectiveId::CONST_CMD, sequenceIndex)) {
187  return;
188  }
190  this->sendSignal(this->constCmd_directiveHandler(directive, error));
191  handleDirectiveErrorCode(Fpy::DirectiveId::CONST_CMD, error);
192 }
193 
196  U64 sequenceIndex) {
197  if (!this->checkDirectiveExpected(directive.get__op(), sequenceIndex)) {
198  return;
199  }
201  this->sendSignal(this->stackOp_directiveHandler(directive, error));
202  handleDirectiveErrorCode(directive.get__op(), error);
203 }
204 
207  U64 sequenceIndex) {
208  if (!this->checkDirectiveExpected(Fpy::DirectiveId::EXIT, sequenceIndex)) {
209  return;
210  }
212  this->sendSignal(this->exit_directiveHandler(directive, error));
213  handleDirectiveErrorCode(Fpy::DirectiveId::EXIT, error);
214 }
215 
218  U64 sequenceIndex) {
219  if (!this->checkDirectiveExpected(Fpy::DirectiveId::ALLOCATE, sequenceIndex)) {
220  return;
221  }
223  this->sendSignal(this->allocate_directiveHandler(directive, error));
224  handleDirectiveErrorCode(Fpy::DirectiveId::ALLOCATE, error);
225 }
226 
230  U64 sequenceIndex) {
231  if (!this->checkDirectiveExpected(Fpy::DirectiveId::STORE_REL_CONST_OFFSET, sequenceIndex)) {
232  return;
233  }
235  this->sendSignal(this->storeRelConstOffset_directiveHandler(directive, error));
236  handleDirectiveErrorCode(Fpy::DirectiveId::STORE_REL_CONST_OFFSET, error);
237 }
238 
241  U64 sequenceIndex) {
242  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_VAL, sequenceIndex)) {
243  return;
244  }
246  this->sendSignal(this->pushVal_directiveHandler(directive, error));
247  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_VAL, error);
248 }
249 
252  U64 sequenceIndex) {
253  if (!this->checkDirectiveExpected(Fpy::DirectiveId::LOAD_REL, sequenceIndex)) {
254  return;
255  }
257  this->sendSignal(this->loadRel_directiveHandler(directive, error));
258  handleDirectiveErrorCode(Fpy::DirectiveId::LOAD_REL, error);
259 }
260 
263  U64 sequenceIndex) {
264  if (!this->checkDirectiveExpected(Fpy::DirectiveId::DISCARD, sequenceIndex)) {
265  return;
266  }
268  this->sendSignal(this->discard_directiveHandler(directive, error));
269  handleDirectiveErrorCode(Fpy::DirectiveId::DISCARD, error);
270 }
271 
274  U64 sequenceIndex) {
275  if (!this->checkDirectiveExpected(Fpy::DirectiveId::MEMCMP, sequenceIndex)) {
276  return;
277  }
279  this->sendSignal(this->memCmp_directiveHandler(directive, error));
280  handleDirectiveErrorCode(Fpy::DirectiveId::MEMCMP, error);
281 }
282 
285  U64 sequenceIndex) {
286  if (!this->checkDirectiveExpected(Fpy::DirectiveId::STACK_CMD, sequenceIndex)) {
287  return;
288  }
290  this->sendSignal(this->stackCmd_directiveHandler(directive, error));
291  handleDirectiveErrorCode(Fpy::DirectiveId::STACK_CMD, error);
292 }
293 
296  U64 sequenceIndex) {
297  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_TIME, sequenceIndex)) {
298  return;
299  }
301  this->sendSignal(this->pushTime_directiveHandler(directive, error));
302  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TIME, error);
303 }
304 
307  U64 sequenceIndex) {
308  if (!this->checkDirectiveExpected(Fpy::DirectiveId::SET_SEED, sequenceIndex)) {
309  return;
310  }
312  this->sendSignal(this->setSeed_directiveHandler(directive, error));
313  handleDirectiveErrorCode(Fpy::DirectiveId::SET_SEED, error);
314 }
315 
318  U64 sequenceIndex) {
319  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PUSH_RAND, sequenceIndex)) {
320  return;
321  }
323  this->sendSignal(this->pushRand_directiveHandler(directive, error));
324  handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_RAND, error);
325 }
326 
329  U64 sequenceIndex) {
330  if (!this->checkDirectiveExpected(Fpy::DirectiveId::GET_FIELD, sequenceIndex)) {
331  return;
332  }
334  this->sendSignal(this->getField_directiveHandler(directive, error));
335  handleDirectiveErrorCode(Fpy::DirectiveId::GET_FIELD, error);
336 }
337 
340  U64 sequenceIndex) {
341  if (!this->checkDirectiveExpected(Fpy::DirectiveId::PEEK, sequenceIndex)) {
342  return;
343  }
345  this->sendSignal(this->peek_directiveHandler(directive, error));
346  handleDirectiveErrorCode(Fpy::DirectiveId::PEEK, error);
347 }
348 
351  U64 sequenceIndex) {
352  if (!this->checkDirectiveExpected(Fpy::DirectiveId::STORE_REL, sequenceIndex)) {
353  return;
354  }
356  this->sendSignal(this->storeRel_directiveHandler(directive, error));
357  handleDirectiveErrorCode(Fpy::DirectiveId::STORE_REL, error);
358 }
359 
362  U64 sequenceIndex) {
363  if (!this->checkDirectiveExpected(Fpy::DirectiveId::CALL, sequenceIndex)) {
364  return;
365  }
367  this->sendSignal(this->call_directiveHandler(directive, error));
368  handleDirectiveErrorCode(Fpy::DirectiveId::CALL, error);
369 }
370 
373  U64 sequenceIndex) {
374  if (!this->checkDirectiveExpected(Fpy::DirectiveId::RETURN, sequenceIndex)) {
375  return;
376  }
378  this->sendSignal(this->return_directiveHandler(directive, error));
379  handleDirectiveErrorCode(Fpy::DirectiveId::RETURN, error);
380 }
381 
384  U64 sequenceIndex) {
385  if (!this->checkDirectiveExpected(Fpy::DirectiveId::LOAD_ABS, sequenceIndex)) {
386  return;
387  }
389  this->sendSignal(this->loadAbs_directiveHandler(directive, error));
390  handleDirectiveErrorCode(Fpy::DirectiveId::LOAD_ABS, error);
391 }
392 
395  U64 sequenceIndex) {
396  if (!this->checkDirectiveExpected(Fpy::DirectiveId::STORE_ABS, sequenceIndex)) {
397  return;
398  }
400  this->sendSignal(this->storeAbs_directiveHandler(directive, error));
401  handleDirectiveErrorCode(Fpy::DirectiveId::STORE_ABS, error);
402 }
403 
407  U64 sequenceIndex) {
408  if (!this->checkDirectiveExpected(Fpy::DirectiveId::STORE_ABS_CONST_OFFSET, sequenceIndex)) {
409  return;
410  }
412  this->sendSignal(this->storeAbsConstOffset_directiveHandler(directive, error));
413  handleDirectiveErrorCode(Fpy::DirectiveId::STORE_ABS_CONST_OFFSET, error);
414 }
415 
418  U64 sequenceIndex) {
419  if (!this->checkDirectiveExpected(Fpy::DirectiveId::POP_EVENT, sequenceIndex)) {
420  return;
421  }
423  this->sendSignal(this->popEvent_directiveHandler(directive, error));
424  handleDirectiveErrorCode(Fpy::DirectiveId::POP_EVENT, error);
425 }
426 
430  U64 sequenceIndex) {
431  if (!this->checkDirectiveExpected(Fpy::DirectiveId::POP_SERIALIZABLE, sequenceIndex)) {
432  return;
433  }
435  this->sendSignal(this->popSerializable_directiveHandler(directive, error));
436  handleDirectiveErrorCode(Fpy::DirectiveId::POP_SERIALIZABLE, error);
437 }
438 
440 Signal FpySequencer::waitRel_directiveHandler(const FpySequencer_WaitRelDirective& directive, DirectiveError& error) {
441  if (this->m_runtime.stack.size < 8) {
444  }
445 
446  Fw::Time wakeupTime = this->getTime();
447 
448  U32 uSeconds = this->m_runtime.stack.pop<U32>();
449  U32 seconds = this->m_runtime.stack.pop<U32>();
450 
451  if (uSeconds >= 1000000) {
454  }
455 
456  wakeupTime.add(seconds, uSeconds);
457  this->m_runtime.wakeupTime = wakeupTime;
459 }
460 
462 Signal FpySequencer::waitAbs_directiveHandler(const FpySequencer_WaitAbsDirective& directive, DirectiveError& error) {
463  if (this->m_runtime.stack.size < 2 * sizeof(U32) + sizeof(FwTimeContextStoreType) + sizeof(FwTimeBaseStoreType)) {
466  }
467 
468  U32 uSeconds = this->m_runtime.stack.pop<U32>();
469  U32 seconds = this->m_runtime.stack.pop<U32>();
470  FwTimeContextStoreType ctx = this->m_runtime.stack.pop<FwTimeContextStoreType>();
471  FwTimeBaseStoreType base = this->m_runtime.stack.pop<FwTimeBaseStoreType>();
472 
473  if (uSeconds >= 1000000) {
476  }
477 
478  this->m_runtime.wakeupTime = Fw::Time(static_cast<TimeBase::T>(base), ctx, seconds, uSeconds);
480 }
481 
483 Signal FpySequencer::goto_directiveHandler(const FpySequencer_GotoDirective& directive, DirectiveError& error) {
484  // check within sequence bounds, or at EOF (we allow == case cuz this just ends the sequence)
485  if (directive.get_statementIndex() > m_sequenceObj.get_header().get_statementCount()) {
488  }
489  m_runtime.nextStatementIndex = directive.get_statementIndex();
491 }
492 
494 Signal FpySequencer::if_directiveHandler(const FpySequencer_IfDirective& directive, DirectiveError& error) {
495  if (this->m_runtime.stack.size < 1) {
498  }
499  // check within sequence bounds, or at EOF (we allow == case cuz this just ends the sequence)
500  if (directive.get_falseGotoStmtIndex() > m_sequenceObj.get_header().get_statementCount()) {
503  }
504 
505  if (this->m_runtime.stack.pop<U8>() != 0) {
506  // proceed to next instruction
508  }
509 
510  // conditional false case
511  this->m_runtime.nextStatementIndex = directive.get_falseGotoStmtIndex();
513 }
514 
515 Signal FpySequencer::noOp_directiveHandler(const FpySequencer_NoOpDirective& directive, DirectiveError& error) {
517 }
518 
519 Signal FpySequencer::pushTlmVal_directiveHandler(const FpySequencer_PushTlmValDirective& directive,
520  DirectiveError& error) {
521  if (!this->isConnected_getTlmChan_OutputPort(0)) {
524  }
525  Fw::Time tlmTime;
526  Fw::TlmBuffer tlmValue;
527  Fw::TlmValid valid = this->getTlmChan_out(0, directive.get_chanId(), tlmTime, tlmValue);
528 
529  if (valid != Fw::TlmValid::VALID) {
530  // could not find this tlm chan
533  }
534 
535  if (tlmValue.getSize() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
538  }
539  this->m_runtime.stack.push(tlmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(tlmValue.getSize()));
541 }
542 
543 Signal FpySequencer::pushTlmValAndTime_directiveHandler(const FpySequencer_PushTlmValAndTimeDirective& directive,
544  DirectiveError& error) {
545  if (!this->isConnected_getTlmChan_OutputPort(0)) {
548  }
549 
550  Fw::Time tlmTime;
551  Fw::TlmBuffer tlmValue;
552  Fw::TlmValid valid = this->getTlmChan_out(0, directive.get_chanId(), tlmTime, tlmValue);
553 
554  if (valid != Fw::TlmValid::VALID) {
555  // could not find this tlm chan
558  }
559 
560  U8 tlmTimeBuf[Fw::Time::SERIALIZED_SIZE] = {};
562  Fw::SerializeStatus stat = timeEsb.serializeFrom(tlmTime);
563 
564  // coding error if this failed, we should have enough space
565  FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
566 
567  // check that our stack won't overflow if we put both val and time on it
568  if (tlmValue.getSize() + timeEsb.getSize() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
571  }
572 
573  // push tlm to end of stack
574  this->m_runtime.stack.push(tlmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(tlmValue.getSize()));
575  // now push time to end of stack
576  this->m_runtime.stack.push(timeEsb.getBuffAddr(), static_cast<Fpy::StackSizeType>(timeEsb.getSize()));
578 }
579 
580 Signal FpySequencer::pushPrm_directiveHandler(const FpySequencer_PushPrmDirective& directive, DirectiveError& error) {
581  if (!this->isConnected_getParam_OutputPort(0)) {
584  }
585 
586  Fw::ParamBuffer prmValue;
587  Fw::ParamValid valid = this->getParam_out(0, directive.get_prmId(), prmValue);
588 
589  if (valid != Fw::ParamValid::VALID) {
590  // could not find this prm in the DB
593  }
594 
595  if (prmValue.getSize() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
598  }
599 
600  this->m_runtime.stack.push(prmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(prmValue.getSize()));
602 }
603 
604 Signal FpySequencer::constCmd_directiveHandler(const FpySequencer_ConstCmdDirective& directive, DirectiveError& error) {
605  // the cmd response code will be pushed to the stack when it comes back, so make sure
606  // there is room for it now, before the cmd is dispatched
607  if (sizeof(Fw::CmdResponse::SerialType) > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
610  }
611  if (this->sendCmd(directive.get_opCode(), directive.get_argBuf(), directive.get__argBufSize()) ==
614  } else {
615  // now tell the SM to wait some more until we get the cmd response back
616  // if we've already got the response back this should be harmless
618  }
619 }
620 
621 DirectiveError FpySequencer::op_or() {
622  if (this->m_runtime.stack.size < sizeof(U8) * 2) {
624  }
625  this->m_runtime.stack.push(static_cast<U8>(this->m_runtime.stack.pop<U8>() | this->m_runtime.stack.pop<U8>()));
627 }
628 DirectiveError FpySequencer::op_and() {
629  if (this->m_runtime.stack.size < sizeof(U8) * 2) {
631  }
632  this->m_runtime.stack.push(static_cast<U8>(this->m_runtime.stack.pop<U8>() & this->m_runtime.stack.pop<U8>()));
634 }
635 DirectiveError FpySequencer::op_ieq() {
636  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
638  }
639  this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<I64>() == this->m_runtime.stack.pop<I64>())
640  ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
641  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
643 }
644 DirectiveError FpySequencer::op_ine() {
645  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
647  }
648  this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<I64>() != this->m_runtime.stack.pop<I64>())
649  ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
650  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
652 }
653 DirectiveError FpySequencer::op_ult() {
654  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
656  }
657  U64 rhs = this->m_runtime.stack.pop<U64>();
658  U64 lhs = this->m_runtime.stack.pop<U64>();
659  this->m_runtime.stack.push(static_cast<U8>((lhs < rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
660  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
662 }
663 DirectiveError FpySequencer::op_ule() {
664  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
666  }
667  U64 rhs = this->m_runtime.stack.pop<U64>();
668  U64 lhs = this->m_runtime.stack.pop<U64>();
669  this->m_runtime.stack.push(static_cast<U8>((lhs <= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
670  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
672 }
673 DirectiveError FpySequencer::op_ugt() {
674  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
676  }
677  U64 rhs = this->m_runtime.stack.pop<U64>();
678  U64 lhs = this->m_runtime.stack.pop<U64>();
679  this->m_runtime.stack.push(static_cast<U8>((lhs > rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
680  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
682 }
683 DirectiveError FpySequencer::op_uge() {
684  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
686  }
687  U64 rhs = this->m_runtime.stack.pop<U64>();
688  U64 lhs = this->m_runtime.stack.pop<U64>();
689  this->m_runtime.stack.push(static_cast<U8>((lhs >= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
690  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
692 }
693 DirectiveError FpySequencer::op_slt() {
694  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
696  }
697  I64 rhs = this->m_runtime.stack.pop<I64>();
698  I64 lhs = this->m_runtime.stack.pop<I64>();
699  this->m_runtime.stack.push(static_cast<U8>((lhs < rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
700  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
702 }
703 DirectiveError FpySequencer::op_sle() {
704  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
706  }
707  I64 rhs = this->m_runtime.stack.pop<I64>();
708  I64 lhs = this->m_runtime.stack.pop<I64>();
709  this->m_runtime.stack.push(static_cast<U8>((lhs <= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
710  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
712 }
713 DirectiveError FpySequencer::op_sgt() {
714  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
716  }
717  I64 rhs = this->m_runtime.stack.pop<I64>();
718  I64 lhs = this->m_runtime.stack.pop<I64>();
719  this->m_runtime.stack.push(static_cast<U8>((lhs > rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
720  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
722 }
723 DirectiveError FpySequencer::op_sge() {
724  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
726  }
727  I64 rhs = this->m_runtime.stack.pop<I64>();
728  I64 lhs = this->m_runtime.stack.pop<I64>();
729  this->m_runtime.stack.push(static_cast<U8>((lhs >= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
730  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
732 }
733 DirectiveError FpySequencer::op_feq() {
734  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
736  }
737  F64 rhs = this->m_runtime.stack.pop<F64>();
738  F64 lhs = this->m_runtime.stack.pop<F64>();
739  // eq is true if they are equal and neither is nan
740  this->m_runtime.stack.push(static_cast<U8>((lhs == rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
741  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
743 }
744 DirectiveError FpySequencer::op_fne() {
745  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
747  }
748  F64 rhs = this->m_runtime.stack.pop<F64>();
749  F64 lhs = this->m_runtime.stack.pop<F64>();
750  // ne is true if they are not equal or either is nan
751  this->m_runtime.stack.push(static_cast<U8>((lhs != rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
752  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
754 }
755 DirectiveError FpySequencer::op_flt() {
756  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
758  }
759  F64 rhs = this->m_runtime.stack.pop<F64>();
760  F64 lhs = this->m_runtime.stack.pop<F64>();
761  this->m_runtime.stack.push(static_cast<U8>(std::isless(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
762  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
764 }
765 DirectiveError FpySequencer::op_fle() {
766  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
768  }
769  F64 rhs = this->m_runtime.stack.pop<F64>();
770  F64 lhs = this->m_runtime.stack.pop<F64>();
771  this->m_runtime.stack.push(static_cast<U8>(std::islessequal(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
772  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
774 }
775 DirectiveError FpySequencer::op_fgt() {
776  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
778  }
779  F64 rhs = this->m_runtime.stack.pop<F64>();
780  F64 lhs = this->m_runtime.stack.pop<F64>();
781  this->m_runtime.stack.push(static_cast<U8>(std::isgreater(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
782  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
784 }
785 DirectiveError FpySequencer::op_fge() {
786  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
788  }
789  F64 rhs = this->m_runtime.stack.pop<F64>();
790  F64 lhs = this->m_runtime.stack.pop<F64>();
791  this->m_runtime.stack.push(static_cast<U8>(std::isgreaterequal(lhs, rhs)
792  ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
793  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
795 }
796 DirectiveError FpySequencer::op_not() {
797  if (this->m_runtime.stack.size < sizeof(U8)) {
799  }
800  this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<U8>() == 0)
801  ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
802  : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
804 }
805 DirectiveError FpySequencer::op_fpext() {
806  // convert F32 to F64
807  if (this->m_runtime.stack.size < sizeof(F32)) {
809  }
810  this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<F32>()));
812 }
813 DirectiveError FpySequencer::op_fptrunc() {
814  // convert F64 to F32
815  if (this->m_runtime.stack.size < sizeof(F64)) {
817  }
818  this->m_runtime.stack.push(static_cast<F32>(this->m_runtime.stack.pop<F64>()));
820 }
821 DirectiveError FpySequencer::op_fptosi() {
822  if (this->m_runtime.stack.size < sizeof(F64)) {
824  }
825  F64 val = this->m_runtime.stack.pop<F64>();
826  // NaN -> 0, out-of-range clamps, in-range truncates toward
827  // zero. The raw static_cast is UB for NaN and out-of-range values.
828  // 2^63 is exactly representable as F64 and is one past I64 max; -2^63
829  // is exactly I64 min and in range.
830  const F64 bound = std::ldexp(1.0, 63);
831  I64 result;
832  if (std::isnan(val)) {
833  result = 0;
834  } else if (val >= bound) {
835  result = std::numeric_limits<I64>::max();
836  } else if (val < -bound) {
838  } else {
839  result = static_cast<I64>(val);
840  }
841  this->m_runtime.stack.push(result);
843 }
844 DirectiveError FpySequencer::op_sitofp() {
845  if (this->m_runtime.stack.size < sizeof(I64)) {
847  }
848  this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<I64>()));
850 }
851 DirectiveError FpySequencer::op_fptoui() {
852  if (this->m_runtime.stack.size < sizeof(F64)) {
854  }
855  F64 val = this->m_runtime.stack.pop<F64>();
856  // NaN -> 0, negatives truncate to at most 0 and clamp there,
857  // 2^64 (one past U64 max) and above clamp to U64 max. The raw
858  // static_cast is UB for NaN and out-of-range values.
859  const F64 bound = std::ldexp(1.0, 64);
860  U64 result;
861  if (std::isnan(val) || val < 0.0) {
862  result = 0;
863  } else if (val >= bound) {
864  result = std::numeric_limits<U64>::max();
865  } else {
866  result = static_cast<U64>(val);
867  }
868  this->m_runtime.stack.push(result);
870 }
871 DirectiveError FpySequencer::op_uitofp() {
872  if (this->m_runtime.stack.size < sizeof(U64)) {
874  }
875  this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<U64>()));
877 }
878 DirectiveError FpySequencer::op_add() {
879  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
881  }
882  I64 rhs = this->m_runtime.stack.pop<I64>();
883  I64 lhs = this->m_runtime.stack.pop<I64>();
884  // Check for overflow and underflow and return the appropriate error code
885  // Overflow can only occur with both operands positive and occurs when one operand is greater than the maximum value
886  // less the other operand. If either operand is negative or zero, overflow cannot occur.
887  if ((rhs > 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() - rhs) < lhs)) {
889  }
890  // Underflow can only occur with both operands negative and occurs when one operand is less than the minimum value
891  // minus the other operand. If either operand is positive or zero, underflow cannot occur.
892  else if ((rhs < 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() - rhs) > lhs)) {
894  }
895  this->m_runtime.stack.push(static_cast<I64>(lhs + rhs));
897 }
898 DirectiveError FpySequencer::op_sub() {
899  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
901  }
902  I64 rhs = this->m_runtime.stack.pop<I64>();
903  I64 lhs = this->m_runtime.stack.pop<I64>();
904  // Check for overflow and underflow and return the appropriate error code
905  // Overflow can only occur when the left operand is positive and the right operand is negative. It occurs when the
906  // left (positive) operand is greater than the maximum value plus the other (negative) operand. If the right
907  // operand is positive or zero, overflow cannot occur.
908  if ((rhs < 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() + rhs) < lhs)) {
910  }
911  // Underflow can only occur when the left operand is negative and the right operand is positive. It occurs when the
912  // left (negative) operand is less than the minimum value plus the other (positive) operand. If the right operand
913  // is negative or zero, underflow cannot occur.
914  else if ((rhs > 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() + rhs) > lhs)) {
916  }
917  this->m_runtime.stack.push(static_cast<I64>(lhs - rhs));
919 }
920 DirectiveError FpySequencer::op_mul() {
921  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
923  }
924  I64 rhs = this->m_runtime.stack.pop<I64>();
925  I64 lhs = this->m_runtime.stack.pop<I64>();
926  // Check for overflow and underflow and return the appropriate error code
927  // Overflow can only occur with operands of matching signs and occurs when one operand is greater (or less) than the
928  // maximum value divided by the other operand. Either operand being zero precludes overflow.
929  // Check the both positive case.
930  if ((rhs > 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() / rhs) < lhs)) {
932  }
933  // Check the both negative case. Compare without negation: negating a value of min is undefined behavior
934  else if ((rhs < 0) && (lhs < 0) && (lhs < (std::numeric_limits<I64>::max() / rhs))) {
936  }
937  // Underflow can occur with operands of differing signs and occurs when one operand is less than the minimum value
938  // divided by the other operand. Either operand being zero precludes underflow.
939  // Check the case where lhs is positive.
940  else if ((rhs < 0) && (lhs > 0) && ((std::numeric_limits<I64>::min() / lhs) > rhs)) {
942  }
943  // Check the case where rhs is positive.
944  else if ((rhs > 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() / rhs) > lhs)) {
946  }
947  this->m_runtime.stack.push(static_cast<I64>(lhs * rhs));
949 }
950 DirectiveError FpySequencer::op_udiv() {
951  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
953  }
954  U64 rhs = this->m_runtime.stack.pop<U64>();
955  U64 lhs = this->m_runtime.stack.pop<U64>();
956  // Prevent division by zero
957  if (rhs == 0) {
959  }
960  this->m_runtime.stack.push(static_cast<U64>(lhs / rhs));
962 }
963 DirectiveError FpySequencer::op_sdiv() {
964  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
966  }
967 
968  I64 rhs = this->m_runtime.stack.pop<I64>();
969  I64 lhs = this->m_runtime.stack.pop<I64>();
970  // Prevent division by zero
971  if (rhs == 0) {
973  }
974  // The one signed division that can overflow: |I64 min / -1| = 2^63 is not
975  // representable (and the C++ expression is UB, SIGFPE on x86)
976  if ((lhs == std::numeric_limits<I64>::min()) && (rhs == -1)) {
978  }
979  // C++ / truncates toward zero; adjust to match Python's floored division:
980  // an inexact quotient with differing operand signs floors one below the
981  // truncated result. This mirrors op_smod.
982  I64 quotient = lhs / rhs;
983  if (((lhs % rhs) != 0) && ((lhs < 0) != (rhs < 0))) {
984  quotient -= 1;
985  }
986  this->m_runtime.stack.push(quotient);
988 }
989 DirectiveError FpySequencer::op_umod() {
990  if (this->m_runtime.stack.size < sizeof(U64) * 2) {
992  }
993  U64 rhs = this->m_runtime.stack.pop<U64>();
994  if (rhs == 0) {
996  }
997  U64 lhs = this->m_runtime.stack.pop<U64>();
998  this->m_runtime.stack.push(static_cast<U64>(lhs % rhs));
1000 }
1001 DirectiveError FpySequencer::op_smod() {
1002  if (this->m_runtime.stack.size < sizeof(I64) * 2) {
1004  }
1005  I64 rhs = this->m_runtime.stack.pop<I64>();
1006  if (rhs == 0) {
1008  }
1009  I64 lhs = this->m_runtime.stack.pop<I64>();
1010  // I64 min % -1 is 0, the mathematical remainder (matching wasm i64.rem_s),
1011  // but the C++ expression is UB (SIGFPE on x86) so it must be special-cased
1012  if ((lhs == std::numeric_limits<I64>::min()) && (rhs == -1)) {
1013  this->m_runtime.stack.push(static_cast<I64>(0));
1014  return DirectiveError::NO_ERROR;
1015  }
1016  I64 res = static_cast<I64>(lhs % rhs);
1017  // in order to match Python's behavior,
1018  // if the signs of the remainder and divisor differ, adjust the result.
1019  // this happens when the result should be positive but is negative, or vice-versa.
1020  // credit Gemini 2.5 pro
1021  if ((res > 0 && rhs < 0) || (res < 0 && rhs > 0)) {
1022  res += rhs;
1023  }
1024  this->m_runtime.stack.push(res);
1025  return DirectiveError::NO_ERROR;
1026 }
1027 DirectiveError FpySequencer::op_fadd() {
1028  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1030  }
1031  F64 rhs = this->m_runtime.stack.pop<F64>();
1032  F64 lhs = this->m_runtime.stack.pop<F64>();
1033  this->m_runtime.stack.push(static_cast<F64>(lhs + rhs));
1034  return DirectiveError::NO_ERROR;
1035 }
1036 DirectiveError FpySequencer::op_fsub() {
1037  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1039  }
1040  F64 rhs = this->m_runtime.stack.pop<F64>();
1041  F64 lhs = this->m_runtime.stack.pop<F64>();
1042  this->m_runtime.stack.push(static_cast<F64>(lhs - rhs));
1043  return DirectiveError::NO_ERROR;
1044 }
1045 DirectiveError FpySequencer::op_fmul() {
1046  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1048  }
1049  F64 rhs = this->m_runtime.stack.pop<F64>();
1050  F64 lhs = this->m_runtime.stack.pop<F64>();
1051  this->m_runtime.stack.push(static_cast<F64>(lhs * rhs));
1052  return DirectiveError::NO_ERROR;
1053 }
1054 DirectiveError FpySequencer::op_fdiv() {
1055  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1057  }
1058  F64 rhs = this->m_runtime.stack.pop<F64>();
1059  F64 lhs = this->m_runtime.stack.pop<F64>();
1060  this->m_runtime.stack.push(static_cast<F64>(lhs / rhs));
1061  return DirectiveError::NO_ERROR;
1062 }
1063 DirectiveError FpySequencer::op_fpow() {
1064  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1066  }
1067  F64 rhs = this->m_runtime.stack.pop<F64>();
1068  F64 lhs = this->m_runtime.stack.pop<F64>();
1069  this->m_runtime.stack.push(static_cast<F64>(pow(lhs, rhs)));
1070  return DirectiveError::NO_ERROR;
1071 }
1072 DirectiveError FpySequencer::op_flog() {
1073  if (this->m_runtime.stack.size < sizeof(F64)) {
1075  }
1076  F64 val = this->m_runtime.stack.pop<F64>();
1077  if (val <= 0.0) {
1079  }
1080  this->m_runtime.stack.push(static_cast<F64>(log(val)));
1081  return DirectiveError::NO_ERROR;
1082 }
1083 DirectiveError FpySequencer::op_fmod() {
1084  if (this->m_runtime.stack.size < sizeof(F64) * 2) {
1086  }
1087  F64 rhs = this->m_runtime.stack.pop<F64>();
1088  F64 lhs = this->m_runtime.stack.pop<F64>();
1089  // std::fmod computes the exact truncated remainder (sign of lhs) with no
1090  // intermediate rounding. A zero divisor yields NaN, matching Rust and C#.
1091  F64 res = std::fmod(lhs, rhs);
1092  // Adjust to match Python's floored-modulo semantics: if the signs of the
1093  // remainder and divisor differ, add the divisor once. This mirrors op_smod
1094  // and is the exact frem + fadd the VM model computes (at most one rounded add).
1095  if ((res > 0 && rhs < 0) || (res < 0 && rhs > 0)) {
1096  res += rhs;
1097  } else if (res == 0) {
1098  // Python normalizes an exact-multiple result so the zero carries the
1099  // divisor's sign (CPython float_rem); fmod leaves the dividend's.
1100  res = std::copysign(0.0, rhs);
1101  }
1102  this->m_runtime.stack.push(res);
1103  return DirectiveError::NO_ERROR;
1104 }
1105 DirectiveError FpySequencer::op_siext_8_64() {
1106  if (this->m_runtime.stack.size < sizeof(I8)) {
1108  }
1109  I8 src = this->m_runtime.stack.pop<I8>();
1110  this->m_runtime.stack.push(static_cast<I64>(src));
1111  return DirectiveError::NO_ERROR;
1112 }
1113 DirectiveError FpySequencer::op_siext_16_64() {
1114  if (this->m_runtime.stack.size < sizeof(I16)) {
1116  }
1117  I16 src = this->m_runtime.stack.pop<I16>();
1118  this->m_runtime.stack.push(static_cast<I64>(src));
1119  return DirectiveError::NO_ERROR;
1120 }
1121 DirectiveError FpySequencer::op_siext_32_64() {
1122  if (this->m_runtime.stack.size < sizeof(I32)) {
1124  }
1125  I32 src = this->m_runtime.stack.pop<I32>();
1126  this->m_runtime.stack.push(static_cast<I64>(src));
1127  return DirectiveError::NO_ERROR;
1128 }
1129 DirectiveError FpySequencer::op_ziext_8_64() {
1130  if (this->m_runtime.stack.size < sizeof(U8)) {
1132  }
1133  U8 src = this->m_runtime.stack.pop<U8>();
1134  this->m_runtime.stack.push(static_cast<U64>(src));
1135  return DirectiveError::NO_ERROR;
1136 }
1137 DirectiveError FpySequencer::op_ziext_16_64() {
1138  if (this->m_runtime.stack.size < sizeof(U16)) {
1140  }
1141  U16 src = this->m_runtime.stack.pop<U16>();
1142  this->m_runtime.stack.push(static_cast<U64>(src));
1143  return DirectiveError::NO_ERROR;
1144 }
1145 DirectiveError FpySequencer::op_ziext_32_64() {
1146  if (this->m_runtime.stack.size < sizeof(U32)) {
1148  }
1149  U32 src = this->m_runtime.stack.pop<U32>();
1150  this->m_runtime.stack.push(static_cast<U64>(src));
1151  return DirectiveError::NO_ERROR;
1152 }
1153 DirectiveError FpySequencer::op_itrunc_64_8() {
1154  if (this->m_runtime.stack.size < sizeof(U64)) {
1156  }
1157  U64 src = this->m_runtime.stack.pop<U64>();
1158  this->m_runtime.stack.push(static_cast<U8>(src));
1159  return DirectiveError::NO_ERROR;
1160 }
1161 DirectiveError FpySequencer::op_itrunc_64_16() {
1162  if (this->m_runtime.stack.size < sizeof(U64)) {
1164  }
1165  U64 src = this->m_runtime.stack.pop<U64>();
1166  this->m_runtime.stack.push(static_cast<U16>(src));
1167  return DirectiveError::NO_ERROR;
1168 }
1169 DirectiveError FpySequencer::op_itrunc_64_32() {
1170  if (this->m_runtime.stack.size < sizeof(U64)) {
1172  }
1173  U64 src = this->m_runtime.stack.pop<U64>();
1174  this->m_runtime.stack.push(static_cast<U32>(src));
1175  return DirectiveError::NO_ERROR;
1176 }
1177 DirectiveError FpySequencer::op_ffloor() {
1178  if (this->m_runtime.stack.size < sizeof(F64)) {
1180  }
1181  F64 val = this->m_runtime.stack.pop<F64>();
1182  // std::floor implements IEEE 754 roundToIntegralTowardNegative: +-0, +-inf
1183  // and NaN pass through, and the sign of a zero is preserved.
1184  this->m_runtime.stack.push(std::floor(val));
1185  return DirectiveError::NO_ERROR;
1186 }
1187 DirectiveError FpySequencer::op_iabs() {
1188  if (this->m_runtime.stack.size < sizeof(I64)) {
1190  }
1191  I64 val = this->m_runtime.stack.pop<I64>();
1192  // abs(I64 min) is not representable in I64 (and -val on it is UB)
1193  if (val == std::numeric_limits<I64>::min()) {
1195  }
1196  this->m_runtime.stack.push(val < 0 ? -val : val);
1197  return DirectiveError::NO_ERROR;
1198 }
1199 DirectiveError FpySequencer::op_fabs() {
1200  if (this->m_runtime.stack.size < sizeof(F64)) {
1202  }
1203  F64 val = this->m_runtime.stack.pop<F64>();
1204  // IEEE 754 abs: clears the sign bit and changes nothing else, so NaN
1205  // payloads pass through.
1206  this->m_runtime.stack.push(std::fabs(val));
1207  return DirectiveError::NO_ERROR;
1208 }
1209 Signal FpySequencer::stackOp_directiveHandler(const FpySequencer_StackOpDirective& directive, DirectiveError& error) {
1210  // coding error, should not have gotten to this stack op handler
1211  FW_ASSERT((directive.get__op() >= Fpy::DirectiveId::OR && directive.get__op() <= Fpy::DirectiveId::ITRUNC_64_32) ||
1212  (directive.get__op() >= Fpy::DirectiveId::FFLOOR && directive.get__op() <= Fpy::DirectiveId::FABS),
1213  static_cast<FwAssertArgType>(directive.get__op()));
1214 
1215  switch (directive.get__op()) {
1216  case Fpy::DirectiveId::OR:
1217  error = this->op_or();
1218  break;
1219  case Fpy::DirectiveId::AND:
1220  error = this->op_and();
1221  break;
1222  case Fpy::DirectiveId::IEQ:
1223  error = this->op_ieq();
1224  break;
1225  case Fpy::DirectiveId::INE:
1226  error = this->op_ine();
1227  break;
1228  case Fpy::DirectiveId::ULT:
1229  error = this->op_ult();
1230  break;
1231  case Fpy::DirectiveId::ULE:
1232  error = this->op_ule();
1233  break;
1234  case Fpy::DirectiveId::UGT:
1235  error = this->op_ugt();
1236  break;
1237  case Fpy::DirectiveId::UGE:
1238  error = this->op_uge();
1239  break;
1240  case Fpy::DirectiveId::SLT:
1241  error = this->op_slt();
1242  break;
1243  case Fpy::DirectiveId::SLE:
1244  error = this->op_sle();
1245  break;
1246  case Fpy::DirectiveId::SGT:
1247  error = this->op_sgt();
1248  break;
1249  case Fpy::DirectiveId::SGE:
1250  error = this->op_sge();
1251  break;
1252  case Fpy::DirectiveId::FEQ:
1253  error = this->op_feq();
1254  break;
1255  case Fpy::DirectiveId::FNE:
1256  error = this->op_fne();
1257  break;
1258  case Fpy::DirectiveId::FLT:
1259  error = this->op_flt();
1260  break;
1261  case Fpy::DirectiveId::FLE:
1262  error = this->op_fle();
1263  break;
1264  case Fpy::DirectiveId::FGT:
1265  error = this->op_fgt();
1266  break;
1267  case Fpy::DirectiveId::FGE:
1268  error = this->op_fge();
1269  break;
1270  case Fpy::DirectiveId::NOT:
1271  error = this->op_not();
1272  break;
1274  error = this->op_fpext();
1275  break;
1277  error = this->op_fptrunc();
1278  break;
1280  error = this->op_fptosi();
1281  break;
1283  error = this->op_fptoui();
1284  break;
1286  error = this->op_sitofp();
1287  break;
1289  error = this->op_uitofp();
1290  break;
1291  case Fpy::DirectiveId::ADD:
1292  error = this->op_add();
1293  break;
1294  case Fpy::DirectiveId::SUB:
1295  error = this->op_sub();
1296  break;
1297  case Fpy::DirectiveId::MUL:
1298  error = this->op_mul();
1299  break;
1301  error = this->op_udiv();
1302  break;
1304  error = this->op_sdiv();
1305  break;
1307  error = this->op_umod();
1308  break;
1310  error = this->op_smod();
1311  break;
1313  error = this->op_fadd();
1314  break;
1316  error = this->op_fsub();
1317  break;
1319  error = this->op_fmul();
1320  break;
1322  error = this->op_fdiv();
1323  break;
1325  error = this->op_fpow();
1326  break;
1328  error = this->op_flog();
1329  break;
1331  error = this->op_fmod();
1332  break;
1334  error = this->op_siext_8_64();
1335  break;
1337  error = this->op_siext_16_64();
1338  break;
1340  error = this->op_siext_32_64();
1341  break;
1343  error = this->op_ziext_8_64();
1344  break;
1346  error = this->op_ziext_16_64();
1347  break;
1349  error = this->op_ziext_32_64();
1350  break;
1352  error = this->op_itrunc_64_8();
1353  break;
1355  error = this->op_itrunc_64_16();
1356  break;
1358  error = this->op_itrunc_64_32();
1359  break;
1361  error = this->op_ffloor();
1362  break;
1364  error = this->op_iabs();
1365  break;
1367  error = this->op_fabs();
1368  break;
1369  default:
1370  FW_ASSERT(false, directive.get__op());
1371  break;
1372  }
1373  if (error != DirectiveError::NO_ERROR) {
1375  }
1377 }
1378 
1379 Signal FpySequencer::exit_directiveHandler(const FpySequencer_ExitDirective& directive, DirectiveError& error) {
1380  if (this->m_runtime.stack.size < sizeof(I32)) {
1383  }
1384  I32 errorCode = this->m_runtime.stack.pop<I32>();
1385  // exit(0), no error
1386  if (errorCode == 0) {
1387  // just goto the end of the sequence
1388  this->m_runtime.nextStatementIndex = this->m_sequenceObj.get_header().get_statementCount();
1390  }
1391  // otherwise, kill the sequence here
1392  // raise the user defined error code as an event
1393  this->log_WARNING_HI_SequenceExitedWithError(this->m_sequenceFilePath, errorCode);
1396 }
1397 
1398 Signal FpySequencer::allocate_directiveHandler(const FpySequencer_AllocateDirective& directive, DirectiveError& error) {
1399  if (directive.get_size() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1402  }
1403  this->m_runtime.stack.pushZeroes(directive.get_size());
1405 }
1406 
1408 Signal FpySequencer::storeHelper(Fpy::StackSizeType destOffset, Fpy::StackSizeType size, DirectiveError& error) {
1409  if (this->m_runtime.stack.size < size) {
1412  }
1413  // After popping the value, would the write go out of bounds?
1414  Fpy::StackSizeType newStackSize = this->m_runtime.stack.size - size;
1415  // Overflow-safe check: destOffset + size > newStackSize
1416  // Rewritten as: check destOffset <= newStackSize first, then size > newStackSize - destOffset
1417  if (destOffset > newStackSize || size > newStackSize - destOffset) {
1420  }
1421  // Copy value to the destination location
1422  this->m_runtime.stack.copy(destOffset, this->m_runtime.stack.size - size, size);
1423  this->m_runtime.stack.size = newStackSize;
1425 }
1426 
1428 Signal FpySequencer::loadHelper(Fpy::StackSizeType srcOffset, Fpy::StackSizeType size, DirectiveError& error) {
1429  if (size > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1432  }
1433  // Overflow-safe check: srcOffset + size > stack.size
1434  // Rewritten as: check srcOffset <= stack.size first, then size > stack.size - srcOffset
1435  if (srcOffset > this->m_runtime.stack.size || size > this->m_runtime.stack.size - srcOffset) {
1438  }
1439  // Copy from source location to top of stack
1440  this->m_runtime.stack.copy(this->m_runtime.stack.size, srcOffset, size);
1441  this->m_runtime.stack.size += size;
1443 }
1444 
1445 Signal FpySequencer::storeRelConstOffset_directiveHandler(const FpySequencer_StoreRelConstOffsetDirective& directive,
1446  DirectiveError& error) {
1447  I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + directive.get_lvarOffset();
1448  if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1451  }
1452  return this->storeHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1453 }
1454 
1455 Signal FpySequencer::loadRel_directiveHandler(const FpySequencer_LoadRelDirective& directive, DirectiveError& error) {
1456  I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + directive.get_lvarOffset();
1457  if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1460  }
1461  return this->loadHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1462 }
1463 
1464 Signal FpySequencer::pushVal_directiveHandler(const FpySequencer_PushValDirective& directive, DirectiveError& error) {
1465  if (directive.get__valSize() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1468  }
1469  // copy from the bytearray in the directive to the stack, add to stack size.
1470  this->m_runtime.stack.push(const_cast<U8*>(directive.get_val()),
1471  static_cast<Fpy::StackSizeType>(directive.get__valSize()));
1473 }
1474 
1475 Signal FpySequencer::discard_directiveHandler(const FpySequencer_DiscardDirective& directive, DirectiveError& error) {
1476  if (this->m_runtime.stack.size < directive.get_size()) {
1479  }
1480  // drop the specified amount of bytes off the stack. simple as.
1481  this->m_runtime.stack.size -= directive.get_size();
1483 }
1484 
1485 Signal FpySequencer::memCmp_directiveHandler(const FpySequencer_MemCmpDirective& directive, DirectiveError& error) {
1486  // Overflow-safe check: we need size * 2 bytes on the stack
1487  // First check that size * 2 doesn't overflow: size > MAX/2 would overflow
1488  // MAX_STACK_SIZE is the upper bound for stack.size, so if size > MAX_STACK_SIZE/2, we definitely don't have enough
1489  if (directive.get_size() > Fpy::MAX_STACK_SIZE / 2) {
1492  }
1493  // Now safe to compute size * 2
1494  if (this->m_runtime.stack.size < directive.get_size() * 2) {
1497  }
1498 
1499  // find the starting offsets of the two byte arrays
1500  U64 lhsOffset = this->m_runtime.stack.size - directive.get_size() * 2;
1501  U64 rhsOffset = this->m_runtime.stack.size - directive.get_size();
1502 
1503  // "officially" remove them from the stack
1504  // you have to do this before pushing to the stack, otherwise the result would get placed
1505  // after the byte arrays
1506  this->m_runtime.stack.size -= directive.get_size() * 2;
1507 
1508  // memcmp the two byte arrays, push FW_SERIALIZE_TRUE_VALUE if they were equal, FW_SERIALIZE_FALSE_VALUE otherwise
1509  if (memcmp(this->m_runtime.stack.bytes + lhsOffset, this->m_runtime.stack.bytes + rhsOffset,
1510  directive.get_size()) == 0) {
1511  this->m_runtime.stack.push<U8>(static_cast<U8>(FW_SERIALIZE_TRUE_VALUE));
1512  } else {
1513  this->m_runtime.stack.push<U8>(static_cast<U8>(FW_SERIALIZE_FALSE_VALUE));
1514  }
1516 }
1517 
1518 Signal FpySequencer::stackCmd_directiveHandler(const FpySequencer_StackCmdDirective& directive, DirectiveError& error) {
1519  // Overflow-safe check: need argsSize + sizeof(FwOpcodeType) bytes
1520  // Check stack.size >= sizeof(FwOpcodeType) first, then stack.size - sizeof(FwOpcodeType) >= argsSize
1521  if (this->m_runtime.stack.size < sizeof(FwOpcodeType) ||
1522  this->m_runtime.stack.size - sizeof(FwOpcodeType) < directive.get_argsSize()) {
1525  }
1526 
1527  // pop the opcode of the cmd off the stack
1528  // note this means that, unlike the actual byte array that the dispatcher gets,
1529  // these cmds have opcode after the argument buffer
1530  FwOpcodeType opcode = this->m_runtime.stack.pop<FwOpcodeType>();
1531  U64 argBufOffset = this->m_runtime.stack.size - directive.get_argsSize();
1532 
1533  // update the opcode of the cmd we will await
1534  this->m_runtime.currentCmdOpcode = opcode;
1535 
1536  // also pop the args off the stack
1537  this->m_runtime.stack.size -= directive.get_argsSize();
1538 
1539  // the cmd response code will be pushed to the stack when it comes back, so make sure
1540  // there is room for it now, before the cmd is dispatched. popping the opcode above
1541  // frees some room, but FwOpcodeType is configurable so it may not be enough
1542  if (sizeof(Fw::CmdResponse::SerialType) > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1545  }
1546 
1547  if (this->sendCmd(opcode, this->m_runtime.stack.bytes + argBufOffset, directive.get_argsSize()) ==
1550  } else {
1551  // now tell the SM to wait some more until we get the cmd response back
1552  // if we've already got the response back this should be harmless
1554  }
1555 
1557 }
1558 
1559 Signal FpySequencer::pushTime_directiveHandler(const FpySequencer_PushTimeDirective& directive, DirectiveError& error) {
1560  if (Fw::Time::SERIALIZED_SIZE > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1563  }
1564 
1565  Fw::Time currentTime = this->getTime();
1566 
1567  U8 currentTimeBuf[Fw::Time::SERIALIZED_SIZE] = {};
1568  Fw::ExternalSerializeBuffer timeEsb(currentTimeBuf, Fw::Time::SERIALIZED_SIZE);
1569  Fw::SerializeStatus stat = timeEsb.serializeFrom(currentTime);
1570 
1571  // coding error if this failed, we should have enough space
1572  FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
1573 
1574  // push time to end of stack
1575  this->m_runtime.stack.push(timeEsb.getBuffAddr(), static_cast<Fpy::StackSizeType>(timeEsb.getSize()));
1577 }
1578 
1579 Signal FpySequencer::setSeed_directiveHandler(const FpySequencer_SetSeedDirective& directive, DirectiveError& error) {
1580  if (this->m_runtime.stack.size < sizeof(U32)) {
1583  }
1584 
1585  U32 seed = this->m_runtime.stack.pop<U32>();
1586  this->m_runtime.rng.seed(seed);
1587  this->m_runtime.rngSeeded = true;
1589 }
1590 
1591 Signal FpySequencer::pushRand_directiveHandler(const FpySequencer_PushRandDirective& directive, DirectiveError& error) {
1592  if (sizeof(U32) > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1595  }
1596 
1597  if (!this->m_runtime.rngSeeded) {
1598  Fw::Time currentTime = this->getTime();
1599  std::seed_seq seedSeq{static_cast<U32>(currentTime.getTimeBase()), static_cast<U32>(currentTime.getContext()),
1600  currentTime.getSeconds(), currentTime.getUSeconds()};
1601  this->m_runtime.rng.seed(seedSeq);
1602  this->m_runtime.rngSeeded = true;
1603  }
1604 
1605  U32 randVal = static_cast<U32>(this->m_runtime.rng());
1606  this->m_runtime.stack.push(randVal);
1608 }
1609 
1610 Signal FpySequencer::getField_directiveHandler(const FpySequencer_GetFieldDirective& directive, DirectiveError& error) {
1611  // Need sizeof(StackSizeType) for the offset AND parentSize for the parent data
1612  // Check we have enough for the offset first
1613  if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType)) {
1616  }
1617  // After popping the offset, we need at least parentSize bytes remaining
1618  if (this->m_runtime.stack.size - sizeof(Fpy::StackSizeType) < directive.get_parentSize()) {
1621  }
1622 
1623  Fpy::StackSizeType offset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1624 
1625  // Overflow-safe check: offset + memberSize > parentSize
1626  // Rewritten as: check offset <= parentSize first, then memberSize > parentSize - offset
1627  if (offset > directive.get_parentSize() || directive.get_memberSize() > directive.get_parentSize() - offset) {
1628  // i think it's somewhat ambiguous whether this is a stack access out of bounds
1629  // but there isn't really an error code that better reflects this, and i guess
1630  // it's technically true
1633  }
1634 
1635  // the resulting bytes should move to the start of the parent array
1636 
1637  // Calculate the offset of the parent start in the stack
1638  Fpy::StackSizeType parentStartOffset = this->m_runtime.stack.size - directive.get_parentSize();
1639  // Overflow-safe: parentStartOffset + offset cannot overflow since offset <= parentSize
1640  // and parentStartOffset + parentSize == stack.size (which is bounded)
1641  this->m_runtime.stack.move(parentStartOffset, parentStartOffset + offset, directive.get_memberSize());
1642  // adjust stack size by the diff between the member and the parent
1643  this->m_runtime.stack.size -= (directive.get_parentSize() - directive.get_memberSize());
1645 }
1646 
1647 Signal FpySequencer::peek_directiveHandler(const FpySequencer_PeekDirective& directive, DirectiveError& error) {
1648  // must have at least two StackSizeType on stack
1649  if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) * 2) {
1652  }
1653 
1654  Fpy::StackSizeType offset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1655  Fpy::StackSizeType byteCount = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1656 
1657  // Check offset doesn't exceed stack size (after both pops)
1658  if (offset > this->m_runtime.stack.size) {
1659  // would access past the bottom of the stack
1660  // note we allow the equals case because the byteCount might be 0
1663  }
1664  if (byteCount > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1665  // we would overflow the stack if we pushed this many bytes to it
1668  }
1669  // Overflow-safe check: byteCount + offset > stack.size
1670  // Rewritten as: check offset <= stack.size (done above), then byteCount > stack.size - offset
1671  if (byteCount > this->m_runtime.stack.size - offset) {
1672  // would access past the bottom of the stack
1675  }
1676  // start copying from the lowest byte of the src array
1677  U8* src = this->m_runtime.stack.top() - offset - byteCount;
1678  this->m_runtime.stack.push(src, byteCount);
1680 }
1681 
1682 Signal FpySequencer::storeRel_directiveHandler(const FpySequencer_StoreRelDirective& directive, DirectiveError& error) {
1683  // Need enough bytes for the value and the offset (SignedStackSizeType = 4 bytes)
1684  // Overflow-safe: check stack.size >= sizeof(SignedStackSizeType) first, then stack.size -
1685  // sizeof(SignedStackSizeType) >= size
1686  if (this->m_runtime.stack.size < sizeof(Fpy::SignedStackSizeType) ||
1687  this->m_runtime.stack.size - sizeof(Fpy::SignedStackSizeType) < directive.get_size()) {
1690  }
1691 
1692  // Pop the signed offset from the stack
1693  Fpy::SignedStackSizeType lvarOffset = this->m_runtime.stack.pop<Fpy::SignedStackSizeType>();
1694 
1695  I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + lvarOffset;
1696  if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1699  }
1700  return this->storeHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1701 }
1702 
1703 Signal FpySequencer::call_directiveHandler(const FpySequencer_CallDirective& directive, DirectiveError& error) {
1704  // Need at least 4 bytes for the target address
1705  if (this->m_runtime.stack.size < sizeof(U32)) {
1708  }
1709 
1710  // Pop the target directive index from the stack
1711  U32 target = this->m_runtime.stack.pop<U32>();
1712 
1713  // Check if we have space to push return address and saved frame pointer (8 bytes total)
1714  if (this->m_runtime.stack.size + sizeof(Fpy::StackSizeType) + sizeof(U32) > Fpy::MAX_STACK_SIZE) {
1717  }
1718 
1719  // Check target is within bounds (will also be checked at execution time)
1720  if (target > m_sequenceObj.get_header().get_statementCount()) {
1723  }
1724 
1725  // Save the return address (next instruction after CALL)
1726  U32 returnAddr = this->m_runtime.nextStatementIndex;
1727 
1728  // Set the next instruction to the target
1729  this->m_runtime.nextStatementIndex = target;
1730 
1731  // Push the return address to the stack
1732  this->m_runtime.stack.push<U32>(returnAddr);
1733 
1734  // Push the current frame pointer to the stack
1735  this->m_runtime.stack.push<Fpy::StackSizeType>(this->m_runtime.stack.currentFrameStart);
1736 
1737  // Set the new frame pointer to the current top of stack
1738  this->m_runtime.stack.currentFrameStart = this->m_runtime.stack.size;
1739 
1741 }
1742 
1743 Signal FpySequencer::return_directiveHandler(const FpySequencer_ReturnDirective& directive, DirectiveError& error) {
1744  Fpy::StackSizeType returnValSize = directive.get_returnValSize();
1745  Fpy::StackSizeType callArgsSize = directive.get_callArgsSize();
1746 
1747  // Check we have enough bytes for the return value
1748  if (this->m_runtime.stack.size < returnValSize) {
1751  }
1752 
1753  // Remember where the return value lives; it is moved down the stack below rather than copied
1754  // through a local buffer, which at Fpy::MAX_STACK_SIZE would not fit a typical task stack
1755  const Fpy::StackSizeType returnValOffset = this->m_runtime.stack.size - returnValSize;
1756 
1757  // Truncate the stack to stack_frame_start (discard all local variables)
1758  if (this->m_runtime.stack.currentFrameStart > this->m_runtime.stack.size) {
1761  }
1762  this->m_runtime.stack.size = this->m_runtime.stack.currentFrameStart;
1763 
1764  // Check we have enough bytes for saved frame pointer and return address
1765  if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) + sizeof(U32)) {
1768  }
1769 
1770  // Pop the saved frame pointer
1771  Fpy::StackSizeType savedFramePtr = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1772 
1773  // Pop the return address
1774  U32 returnAddr = this->m_runtime.stack.pop<U32>();
1775 
1776  // Restore the frame pointer
1777  if (savedFramePtr > this->m_runtime.stack.size) {
1780  }
1781  this->m_runtime.stack.currentFrameStart = savedFramePtr;
1782 
1783  // Validate the return address is within bounds
1784  if (returnAddr > m_sequenceObj.get_header().get_statementCount()) {
1787  }
1788 
1789  // Set the next instruction to the return address
1790  this->m_runtime.nextStatementIndex = returnAddr;
1791 
1792  // Check that we have enough bytes for the call arguments
1793  if (this->m_runtime.stack.size < callArgsSize) {
1796  }
1797  // Discard the function arguments
1798  this->m_runtime.stack.size -= callArgsSize;
1799 
1800  // Push the return value
1801  if (returnValSize > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1804  }
1805  if (returnValSize > 0) {
1806  // Not Stack::move: the source region sits above the truncated stack size, which
1807  // Stack::move rejects. Both regions were bounds-checked above against MAX_STACK_SIZE.
1808  (void)memmove(this->m_runtime.stack.top(), &this->m_runtime.stack.bytes[returnValOffset], returnValSize);
1809  this->m_runtime.stack.size += returnValSize;
1810  }
1811 
1813 }
1814 
1815 Signal FpySequencer::loadAbs_directiveHandler(const FpySequencer_LoadAbsDirective& directive, DirectiveError& error) {
1816  return this->loadHelper(directive.get_globalOffset(), directive.get_size(), error);
1817 }
1818 
1819 Signal FpySequencer::storeAbs_directiveHandler(const FpySequencer_StoreAbsDirective& directive, DirectiveError& error) {
1820  Fpy::StackSizeType size = directive.get_size();
1821 
1822  // Need enough bytes for the value and the offset
1823  // Overflow-safe: check stack.size >= sizeof(StackSizeType) first, then stack.size - sizeof >= size
1824  if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) ||
1825  this->m_runtime.stack.size - sizeof(Fpy::StackSizeType) < size) {
1828  }
1829 
1830  // Pop the global offset from the stack
1831  Fpy::StackSizeType globalOffset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1832 
1833  return this->storeHelper(globalOffset, size, error);
1834 }
1835 
1836 Signal FpySequencer::storeAbsConstOffset_directiveHandler(const FpySequencer_StoreAbsConstOffsetDirective& directive,
1837  DirectiveError& error) {
1838  return this->storeHelper(directive.get_globalOffset(), directive.get_size(), error);
1839 }
1840 
1841 Signal FpySequencer::popEvent_directiveHandler(const FpySequencer_PopEventDirective& directive, DirectiveError& error) {
1842  // Pop messageSize from the stack
1843  if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType)) {
1846  }
1847  Fpy::StackSizeType messageSize = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1848 
1849  const Fpy::StackSizeType severitySize = static_cast<Fpy::StackSizeType>(sizeof(Fw::LogSeverity::SerialType));
1850 
1851  // Need message_size bytes + sizeof(LogSeverity serial type) for severity
1852  if (this->m_runtime.stack.size < severitySize || this->m_runtime.stack.size - severitySize < messageSize) {
1855  }
1856 
1857  // Pop message bytes first
1858  U8 messageBuf[FW_LOG_STRING_MAX_SIZE] = {};
1859  // don't read in more than (log string size) - 1 bytes
1860  Fpy::StackSizeType clampedSize = std::min(messageSize, static_cast<Fpy::StackSizeType>(FW_LOG_STRING_MAX_SIZE - 1));
1861  // If message is larger than buffer, discard the excess bytes first (from top of stack, which is the end of the
1862  // message)
1863  if (messageSize > clampedSize) {
1864  Fpy::StackSizeType excess = messageSize - clampedSize;
1865  FW_ASSERT(this->m_runtime.stack.size >= excess, static_cast<FwAssertArgType>(this->m_runtime.stack.size),
1866  static_cast<FwAssertArgType>(excess));
1867  this->m_runtime.stack.size -= excess;
1868  }
1869  this->m_runtime.stack.pop(messageBuf, clampedSize);
1870  messageBuf[clampedSize] = '\0';
1871 
1872  // Pop severity
1873  Fw::LogSeverity::SerialType severity = this->m_runtime.stack.pop<Fw::LogSeverity::SerialType>();
1874 
1875  // Construct the message string
1876  Fw::String messageStr(reinterpret_cast<const char*>(messageBuf));
1877 
1878  // Emit the appropriate event based on severity
1879  switch (severity) {
1881  this->log_FATAL_LogFatal(this->m_sequenceFilePath, messageStr);
1882  break;
1884  this->log_WARNING_HI_LogWarningHi(this->m_sequenceFilePath, messageStr);
1885  break;
1887  this->log_WARNING_LO_LogWarningLo(this->m_sequenceFilePath, messageStr);
1888  break;
1890  this->log_COMMAND_LogCommand(this->m_sequenceFilePath, messageStr);
1891  break;
1893  this->log_ACTIVITY_HI_LogActivityHi(this->m_sequenceFilePath, messageStr);
1894  break;
1896  this->log_ACTIVITY_LO_LogActivityLo(this->m_sequenceFilePath, messageStr);
1897  break;
1899  this->log_DIAGNOSTIC_LogDiagnostic(this->m_sequenceFilePath, messageStr);
1900  break;
1901  default:
1904  }
1905 
1907 }
1908 
1909 Signal FpySequencer::popSerializable_directiveHandler(const FpySequencer_PopSerializableDirective& directive,
1910  DirectiveError& error) {
1911  // No size assertion here: an oversized size is untrusted sequence content and is rejected by
1912  // the stack check below, since the stack can never hold more than Fpy::MAX_STACK_SIZE bytes
1913 
1914  // Validate port index is in range (using enum constant value)
1915  constexpr FwIndexType MAX_PORTS = static_cast<FwIndexType>(Svc::Fpy::SerialPortIndex::MAX_SERIAL_PORTS);
1916  const FwIndexType portIndex = directive.get_portIndex();
1917 
1918  // Check for negative port index or out of bounds
1919  if (portIndex < 0 || portIndex >= MAX_PORTS) {
1922  }
1923 
1924  // Check port is connected
1925  if (!this->isConnected_serialOut_OutputPort(portIndex)) {
1928  }
1929 
1930  // Validate data size on stack
1931  if (this->m_runtime.stack.size < directive.get_size()) {
1934  }
1935 
1936  // Create external buffer referencing stack data (no copy)
1937  U8* dataPtr = this->m_runtime.stack.top() - directive.get_size();
1938  Fw::ExternalSerializeBuffer buf(dataPtr, directive.get_size());
1939 
1940  // Set buffer length and verify success
1941  Fw::SerializeStatus stat = buf.setBuffLen(directive.get_size());
1942  FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
1943 
1944  // Call output port; a typed downstream port reports deserialize failures here, which is
1945  // untrusted sequence content (e.g. undersized payload), not an invariant to assert on
1946  Fw::SerializeStatus portStatus = this->serialOut_out(portIndex, buf);
1947  if (portStatus != Fw::SerializeStatus::FW_SERIALIZE_OK) {
1950  }
1951 
1952  // Pop data from stack
1953  this->m_runtime.stack.size -= directive.get_size();
1954 
1956 }
1957 
1958 } // namespace Svc
void directive_allocate_internalInterfaceHandler(const Svc::FpySequencer_AllocateDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_allocate.
void cmdOut_out(FwIndexType portNum, Fw::ComBuffer &data, U32 context) const
Invoke output port cmdOut.
Serialization/Deserialization operation was successful.
sets the index of the next directive to execute
void directive_stackOp_internalInterfaceHandler(const Svc::FpySequencer_StackOpDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_stackOp.
bool isConnected_getTlmChan_OutputPort(FwIndexType portNum) const
void directive_waitAbs_internalInterfaceHandler(const FpySequencer_WaitAbsDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_waitAbs.
FwIdType FwOpcodeType
The type of a command opcode.
U16 get_statementCount() const
Get member statementCount.
void directive_call_internalInterfaceHandler(const Svc::FpySequencer_CallDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_call.
branches based off of the top byte of the stack
Representing success.
bool isConnected_getParam_OutputPort(FwIndexType portNum) const
PlatformSizeType FwSizeType
stores a value to an absolute address in the stack (for global variables), offset from stack ...
void directive_storeRelConstOffset_internalInterfaceHandler(const Svc::FpySequencer_StoreRelConstOffsetDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_storeRelConstOffset.
called when statement successfully executed. only raised in the RUNNING.AWAITING_CMD_RESPONSE state ...
void directive_loadAbs_internalInterfaceHandler(const Svc::FpySequencer_LoadAbsDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_loadAbs.
void directive_storeAbsConstOffset_internalInterfaceHandler(const Svc::FpySequencer_StoreAbsConstOffsetDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_storeAbsConstOffset.
I32 SignedStackSizeType
signed version of StackSizeType, used for relative offsets that can be negative
Serializable::SizeType getSize() const override
Get current buffer size.
void directive_pushVal_internalInterfaceHandler(const Svc::FpySequencer_PushValDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushVal.
void directive_noOp_internalInterfaceHandler(const Svc::FpySequencer_NoOpDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_noOp.
int8_t I8
8-bit signed integer
Definition: BasicTypes.h:51
void log_WARNING_LO_DirectiveWhileNotAwaiting(const Svc::Fpy::DirectiveId &directive, I32 state) const
Log event DirectiveWhileNotAwaiting.
stores a value to a local variable at a compile-time-known offset relative to the current stack frame...
Fw::SerializeStatus serialOut_out(FwIndexType portNum, Fw::LinearBufferBase &buffer)
Invoke output port serialOut.
Fw::ParamValid getParam_out(FwIndexType portNum, FwPrmIdType id, Fw::ParamBuffer &val) const
Invoke output port getParam.
void directive_loadRel_internalInterfaceHandler(const Svc::FpySequencer_LoadRelDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_loadRel.
pop an opcode and arg buf off the stack, send to cmd dispatcher and await response ...
void log_WARNING_LO_DirectiveFromOldSequence(const Svc::Fpy::DirectiveId &directive, U64 oldSequenceIdx, U64 currentSequenceIdx) const
Log event DirectiveFromOldSequence.
TimeBase getTimeBase() const
Definition: Time.cpp:136
void sequencer_sendSignal_stmtResponse_success()
Send signal stmtResponse_success to state machine sequencer.
SerializeStatus serializeFrom(U8 val, Endianness mode=Endianness::BIG) override
Serialize an 8-bit unsigned integer value.
REQUIRED: Maximum number of serial ports. This sentinel value MUST be named.
void directive_pushPrm_internalInterfaceHandler(const Svc::FpySequencer_PushPrmDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushPrm.
pops bytes off the top of the stack and does nothing with them
void log_DIAGNOSTIC_LogDiagnostic(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogDiagnostic.
void log_COMMAND_LogCommand(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogCommand.
void directive_goto_internalInterfaceHandler(const Svc::FpySequencer_GotoDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_goto.
U8 FwTimeContextStoreType
The type used to serialize a time context value.
Software diagnostic events.
void log_ACTIVITY_LO_LogActivityLo(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogActivityLo.
SerializeStatus
forward declaration for string
Svc_FpySequencer_SequencerStateMachine::State sequencer_getState() const
Get the state of state machine instance sequencer.
void log_WARNING_HI_SequenceExitedWithError(const Fw::StringBase &filePath, I32 errorCode) const
Log event SequenceExitedWithError.
float F32
32-bit floating point
Definition: BasicTypes.h:84
Fpy::DirectiveErrorCode DirectiveError
loads a value from a local variable at a compile-time-known offset relative to the current stack fram...
void log_ACTIVITY_HI_LogActivityHi(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogActivityHi.
pop two byte arrays off the top of the stack, call memcmp, push 1 if they were equal, 0 otherwise
U8 * getBuffAddr()
Get buffer address for data filling (non-const version)
Svc::Fpy::Header & get_header()
Get member header.
void directive_setSeed_internalInterfaceHandler(const Svc::FpySequencer_SetSeedDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_setSeed.
Less important informational events.
pops a severity and message from the stack and emits an F Prime event
An activity related to commanding.
A less serious but recoverable event.
Omit length from serialization.
void directive_storeRel_internalInterfaceHandler(const Svc::FpySequencer_StoreRelDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_storeRel.
stores a value to an absolute address in the stack (for global variables), const offset ...
External serialize buffer with no copy semantics.
U32 getSeconds() const
Definition: Time.cpp:128
Svc::Fpy::DirectiveId::T get__op() const
Get member _op.
U8 SerialType
The serial representation type.
void directive_storeAbs_internalInterfaceHandler(const Svc::FpySequencer_StoreAbsDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_storeAbs.
void directive_stackCmd_internalInterfaceHandler(const Svc::FpySequencer_StackCmdDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_stackCmd.
void directive_pushRand_internalInterfaceHandler(const Svc::FpySequencer_PushRandDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushRand.
void log_WARNING_HI_LogWarningHi(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogWarningHi.
peeks at N bytes from the stack, starting from an offset relative to the top of the stack ...
void sequencer_sendSignal_stmtResponse_failure()
Send signal stmtResponse_failure to state machine sequencer.
A serious but recoverable event.
void directive_constCmd_internalInterfaceHandler(const Svc::FpySequencer_ConstCmdDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_constCmd.
void directive_return_internalInterfaceHandler(const Svc::FpySequencer_ReturnDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_return.
void sequencer_sendSignal_stmtResponse_beginSleep()
Send signal stmtResponse_beginSleep to state machine sequencer.
Representing failure.
void directive_getField_internalInterfaceHandler(const Svc::FpySequencer_GetFieldDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_getField.
sleeps for a relative duration from the current time
void directive_pushTlmValAndTime_internalInterfaceHandler(const Svc::FpySequencer_PushTlmValAndTimeDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushTlmValAndTime.
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
bool isConnected_serialOut_OutputPort(FwIndexType portNum) const
void directive_pushTlmVal_internalInterfaceHandler(const Svc::FpySequencer_PushTlmValDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushTlmVal.
SerializeStatus setBuffLen(Serializable::SizeType length) override
Set buffer length manually.
Important informational events.
static U32 min(const U32 a, const U32 b)
Definition: Checksum.cpp:16
U8 SerialType
The serial representation type.
static Time add(const Time &a, const Time &b)
Definition: Time.cpp:174
void sequencer_sendSignal_stmtResponse_keepWaiting()
Send signal stmtResponse_keepWaiting to state machine sequencer.
void directive_if_internalInterfaceHandler(const Svc::FpySequencer_IfDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_if.
FwTimeContextStoreType getContext() const
Definition: Time.cpp:140
U32 getUSeconds() const
Definition: Time.cpp:132
void directive_exit_internalInterfaceHandler(const Svc::FpySequencer_ExitDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_exit.
void directive_popSerializable_internalInterfaceHandler(const Svc::FpySequencer_PopSerializableDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_popSerializable.
called when the statement unsuccessfully executed. only raised in the RUNNING.AWAITING_CMD_RESPONSE s...
loads a value from an absolute address in the stack (for global variables)
PlatformIndexType FwIndexType
pops a U32 from the stack and uses it to seed the RNG used by PushRandDirective
void directive_waitRel_internalInterfaceHandler(const FpySequencer_WaitRelDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_waitRel.
double F64
64-bit floating point (double). Required for compiler-supplied double promotion.
Definition: BasicTypes.h:86
pops serialized data from stack and sends to serial output port
A fatal non-recoverable event.
void directive_peek_internalInterfaceHandler(const Svc::FpySequencer_PeekDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_peek.
void log_WARNING_LO_LogWarningLo(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogWarningLo.
stores a value to a local variable at a runtime-determined offset relative to the current stack frame...
RateGroupDivider component implementation.
Enum representing parameter validity.
void log_FATAL_LogFatal(const Fw::StringBase &filePath, const Fw::StringBase &message) const
Log event LogFatal.
U16 FwTimeBaseStoreType
The type used to serialize a time base value.
pushes the current Fw.Time struct to the stack
void directive_pushTime_internalInterfaceHandler(const Svc::FpySequencer_PushTimeDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_pushTime.
called when the statement is telling the sequencer to await a later stmt response ...
FpySequencer_SequencerStateMachineStateMachineBase::Signal Signal
void directive_popEvent_internalInterfaceHandler(const Svc::FpySequencer_PopEventDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_popEvent.
Fw::TlmValid getTlmChan_out(FwIndexType portNum, FwChanIdType id, Fw::Time &timeTag, Fw::TlmBuffer &val) const
Invoke output port getTlmChan.
#define FW_ASSERT(...)
Definition: Assert.hpp:14
void directive_memCmp_internalInterfaceHandler(const Svc::FpySequencer_MemCmpDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_memCmp.
Success/Failure.
void directive_discard_internalInterfaceHandler(const Svc::FpySequencer_DiscardDirective &directive, U64 sequenceIndex) override
Internal interface handler for directive_discard.
U32 StackSizeType
the type which everything referencing a size or offset on the stack is represented in ...