GCC Code Coverage Report


Directory: ./
File: FpySequencerDirectives.cpp
Date: 2026-09-03 22:13:45
Exec Total Coverage
Lines: 0 1139 0.0%
Functions: 0 118 0.0%
Branches: 0 869 0.0%

Line Branch Exec Source
1 #include <algorithm>
2 #include <cmath>
3 #include <cstring>
4 #include <type_traits>
5 #include "Fw/Com/ComPacket.hpp"
6 #include "Svc/FpySequencer/FpySequencer.hpp"
7 #include "config/SerialPortIndexEnumAc.hpp"
8
9 namespace Svc {
10
11 void FpySequencer::sendSignal(Signal signal) {
12 switch (signal) {
13 case Signal::stmtResponse_beginSleep: {
14 this->sequencer_sendSignal_stmtResponse_beginSleep();
15 break;
16 }
17 case Signal::stmtResponse_success: {
18 this->sequencer_sendSignal_stmtResponse_success();
19 break;
20 }
21 case Signal::stmtResponse_failure: {
22 this->sequencer_sendSignal_stmtResponse_failure();
23 break;
24 }
25 case Signal::stmtResponse_keepWaiting: {
26 this->sequencer_sendSignal_stmtResponse_keepWaiting();
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
50 if (stat != Fw::SerializeStatus::FW_SERIALIZE_OK) {
51 return Fw::Success::FAILURE;
52 }
53 stat = cmdBuf.serializeFrom(opcode);
54 if (stat != Fw::SerializeStatus::FW_SERIALIZE_OK) {
55 return Fw::Success::FAILURE;
56 }
57 stat = cmdBuf.serializeFrom(argBuf, argBufSize, Fw::Serialization::OMIT_LENGTH);
58 if (stat != Fw::SerializeStatus::FW_SERIALIZE_OK) {
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 //! Internal interface handler for directive_waitRel
78 void FpySequencer::directive_waitRel_internalInterfaceHandler(const FpySequencer_WaitRelDirective& directive) {
79 DirectiveError error = DirectiveError::NO_ERROR;
80 this->sendSignal(this->waitRel_directiveHandler(directive, error));
81 handleDirectiveErrorCode(Fpy::DirectiveId::WAIT_REL, error);
82 }
83
84 //! Internal interface handler for directive_waitAbs
85 void FpySequencer::directive_waitAbs_internalInterfaceHandler(const FpySequencer_WaitAbsDirective& directive) {
86 DirectiveError error = DirectiveError::NO_ERROR;
87 this->sendSignal(this->waitAbs_directiveHandler(directive, error));
88 handleDirectiveErrorCode(Fpy::DirectiveId::WAIT_ABS, error);
89 }
90
91 //! Internal interface handler for directive_goto
92 void FpySequencer::directive_goto_internalInterfaceHandler(const Svc::FpySequencer_GotoDirective& directive) {
93 DirectiveError error = DirectiveError::NO_ERROR;
94 this->sendSignal(this->goto_directiveHandler(directive, error));
95 handleDirectiveErrorCode(Fpy::DirectiveId::GOTO, error);
96 }
97
98 //! Internal interface handler for directive_if
99 void FpySequencer::directive_if_internalInterfaceHandler(const Svc::FpySequencer_IfDirective& directive) {
100 DirectiveError error = DirectiveError::NO_ERROR;
101 this->sendSignal(this->if_directiveHandler(directive, error));
102 handleDirectiveErrorCode(Fpy::DirectiveId::IF, error);
103 }
104
105 //! Internal interface handler for directive_noOp
106 void FpySequencer::directive_noOp_internalInterfaceHandler(const Svc::FpySequencer_NoOpDirective& directive) {
107 DirectiveError error = DirectiveError::NO_ERROR;
108 this->sendSignal(this->noOp_directiveHandler(directive, error));
109 handleDirectiveErrorCode(Fpy::DirectiveId::NO_OP, error);
110 }
111
112 //! Internal interface handler for directive_pushTlmVal
113 void FpySequencer::directive_pushTlmVal_internalInterfaceHandler(
114 const Svc::FpySequencer_PushTlmValDirective& directive) {
115 DirectiveError error = DirectiveError::NO_ERROR;
116 this->sendSignal(this->pushTlmVal_directiveHandler(directive, error));
117 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TLM_VAL, error);
118 }
119
120 //! Internal interface handler for directive_pushTlmValAndTime
121 void FpySequencer::directive_pushTlmValAndTime_internalInterfaceHandler(
122 const Svc::FpySequencer_PushTlmValAndTimeDirective& directive) {
123 DirectiveError error = DirectiveError::NO_ERROR;
124 this->sendSignal(this->pushTlmValAndTime_directiveHandler(directive, error));
125 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TLM_VAL_AND_TIME, error);
126 }
127
128 //! Internal interface handler for directive_pushPrm
129 void FpySequencer::directive_pushPrm_internalInterfaceHandler(const Svc::FpySequencer_PushPrmDirective& directive) {
130 DirectiveError error = DirectiveError::NO_ERROR;
131 this->sendSignal(this->pushPrm_directiveHandler(directive, error));
132 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_PRM, error);
133 }
134
135 //! Internal interface handler for directive_constCmd
136 void FpySequencer::directive_constCmd_internalInterfaceHandler(const Svc::FpySequencer_ConstCmdDirective& directive) {
137 DirectiveError error = DirectiveError::NO_ERROR;
138 this->sendSignal(this->constCmd_directiveHandler(directive, error));
139 handleDirectiveErrorCode(Fpy::DirectiveId::CONST_CMD, error);
140 }
141
142 //! Internal interface handler for directive_stackOp
143 void FpySequencer::directive_stackOp_internalInterfaceHandler(const Svc::FpySequencer_StackOpDirective& directive) {
144 DirectiveError error = DirectiveError::NO_ERROR;
145 this->sendSignal(this->stackOp_directiveHandler(directive, error));
146 handleDirectiveErrorCode(directive.get__op(), error);
147 }
148
149 //! Internal interface handler for directive_exit
150 void FpySequencer::directive_exit_internalInterfaceHandler(const Svc::FpySequencer_ExitDirective& directive) {
151 DirectiveError error = DirectiveError::NO_ERROR;
152 this->sendSignal(this->exit_directiveHandler(directive, error));
153 handleDirectiveErrorCode(Fpy::DirectiveId::EXIT, error);
154 }
155
156 //! Internal interface handler for directive_allocate
157 void FpySequencer::directive_allocate_internalInterfaceHandler(const Svc::FpySequencer_AllocateDirective& directive) {
158 DirectiveError error = DirectiveError::NO_ERROR;
159 this->sendSignal(this->allocate_directiveHandler(directive, error));
160 handleDirectiveErrorCode(Fpy::DirectiveId::ALLOCATE, error);
161 }
162
163 //! Internal interface handler for directive_storeRelConstOffset
164 void FpySequencer::directive_storeRelConstOffset_internalInterfaceHandler(
165 const Svc::FpySequencer_StoreRelConstOffsetDirective& directive) {
166 DirectiveError error = DirectiveError::NO_ERROR;
167 this->sendSignal(this->storeRelConstOffset_directiveHandler(directive, error));
168 handleDirectiveErrorCode(Fpy::DirectiveId::STORE_REL_CONST_OFFSET, error);
169 }
170
171 //! Internal interface handler for directive_pushVal
172 void FpySequencer::directive_pushVal_internalInterfaceHandler(const Svc::FpySequencer_PushValDirective& directive) {
173 DirectiveError error = DirectiveError::NO_ERROR;
174 this->sendSignal(this->pushVal_directiveHandler(directive, error));
175 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_VAL, error);
176 }
177
178 //! Internal interface handler for directive_loadRel
179 void FpySequencer::directive_loadRel_internalInterfaceHandler(const Svc::FpySequencer_LoadRelDirective& directive) {
180 DirectiveError error = DirectiveError::NO_ERROR;
181 this->sendSignal(this->loadRel_directiveHandler(directive, error));
182 handleDirectiveErrorCode(Fpy::DirectiveId::LOAD_REL, error);
183 }
184
185 //! Internal interface handler for directive_discard
186 void FpySequencer::directive_discard_internalInterfaceHandler(const Svc::FpySequencer_DiscardDirective& directive) {
187 DirectiveError error = DirectiveError::NO_ERROR;
188 this->sendSignal(this->discard_directiveHandler(directive, error));
189 handleDirectiveErrorCode(Fpy::DirectiveId::DISCARD, error);
190 }
191
192 //! Internal interface handler for directive_memCmp
193 void FpySequencer::directive_memCmp_internalInterfaceHandler(const Svc::FpySequencer_MemCmpDirective& directive) {
194 DirectiveError error = DirectiveError::NO_ERROR;
195 this->sendSignal(this->memCmp_directiveHandler(directive, error));
196 handleDirectiveErrorCode(Fpy::DirectiveId::MEMCMP, error);
197 }
198
199 //! Internal interface handler for directive_stackCmd
200 void FpySequencer::directive_stackCmd_internalInterfaceHandler(const Svc::FpySequencer_StackCmdDirective& directive) {
201 DirectiveError error = DirectiveError::NO_ERROR;
202 this->sendSignal(this->stackCmd_directiveHandler(directive, error));
203 handleDirectiveErrorCode(Fpy::DirectiveId::STACK_CMD, error);
204 }
205
206 //! Internal interface handler for directive_pushTime
207 void FpySequencer::directive_pushTime_internalInterfaceHandler(const Svc::FpySequencer_PushTimeDirective& directive) {
208 DirectiveError error = DirectiveError::NO_ERROR;
209 this->sendSignal(this->pushTime_directiveHandler(directive, error));
210 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_TIME, error);
211 }
212
213 //! Internal interface handler for directive_setSeed
214 void FpySequencer::directive_setSeed_internalInterfaceHandler(const Svc::FpySequencer_SetSeedDirective& directive) {
215 DirectiveError error = DirectiveError::NO_ERROR;
216 this->sendSignal(this->setSeed_directiveHandler(directive, error));
217 handleDirectiveErrorCode(Fpy::DirectiveId::SET_SEED, error);
218 }
219
220 //! Internal interface handler for directive_pushRand
221 void FpySequencer::directive_pushRand_internalInterfaceHandler(const Svc::FpySequencer_PushRandDirective& directive) {
222 DirectiveError error = DirectiveError::NO_ERROR;
223 this->sendSignal(this->pushRand_directiveHandler(directive, error));
224 handleDirectiveErrorCode(Fpy::DirectiveId::PUSH_RAND, error);
225 }
226
227 //! Internal interface handler for directive_getField
228 void FpySequencer::directive_getField_internalInterfaceHandler(const Svc::FpySequencer_GetFieldDirective& directive) {
229 DirectiveError error = DirectiveError::NO_ERROR;
230 this->sendSignal(this->getField_directiveHandler(directive, error));
231 handleDirectiveErrorCode(Fpy::DirectiveId::GET_FIELD, error);
232 }
233
234 //! Internal interface handler for directive_peek
235 void FpySequencer::directive_peek_internalInterfaceHandler(const Svc::FpySequencer_PeekDirective& directive) {
236 DirectiveError error = DirectiveError::NO_ERROR;
237 this->sendSignal(this->peek_directiveHandler(directive, error));
238 handleDirectiveErrorCode(Fpy::DirectiveId::PEEK, error);
239 }
240
241 //! Internal interface handler for directive_storeRel
242 void FpySequencer::directive_storeRel_internalInterfaceHandler(const Svc::FpySequencer_StoreRelDirective& directive) {
243 DirectiveError error = DirectiveError::NO_ERROR;
244 this->sendSignal(this->storeRel_directiveHandler(directive, error));
245 handleDirectiveErrorCode(Fpy::DirectiveId::STORE_REL, error);
246 }
247
248 //! Internal interface handler for directive_call
249 void FpySequencer::directive_call_internalInterfaceHandler(const Svc::FpySequencer_CallDirective& directive) {
250 DirectiveError error = DirectiveError::NO_ERROR;
251 this->sendSignal(this->call_directiveHandler(directive, error));
252 handleDirectiveErrorCode(Fpy::DirectiveId::CALL, error);
253 }
254
255 //! Internal interface handler for directive_return
256 void FpySequencer::directive_return_internalInterfaceHandler(const Svc::FpySequencer_ReturnDirective& directive) {
257 DirectiveError error = DirectiveError::NO_ERROR;
258 this->sendSignal(this->return_directiveHandler(directive, error));
259 handleDirectiveErrorCode(Fpy::DirectiveId::RETURN, error);
260 }
261
262 //! Internal interface handler for directive_loadAbs
263 void FpySequencer::directive_loadAbs_internalInterfaceHandler(const Svc::FpySequencer_LoadAbsDirective& directive) {
264 DirectiveError error = DirectiveError::NO_ERROR;
265 this->sendSignal(this->loadAbs_directiveHandler(directive, error));
266 handleDirectiveErrorCode(Fpy::DirectiveId::LOAD_ABS, error);
267 }
268
269 //! Internal interface handler for directive_storeAbs
270 void FpySequencer::directive_storeAbs_internalInterfaceHandler(const Svc::FpySequencer_StoreAbsDirective& directive) {
271 DirectiveError error = DirectiveError::NO_ERROR;
272 this->sendSignal(this->storeAbs_directiveHandler(directive, error));
273 handleDirectiveErrorCode(Fpy::DirectiveId::STORE_ABS, error);
274 }
275
276 //! Internal interface handler for directive_storeAbsConstOffset
277 void FpySequencer::directive_storeAbsConstOffset_internalInterfaceHandler(
278 const Svc::FpySequencer_StoreAbsConstOffsetDirective& directive) {
279 DirectiveError error = DirectiveError::NO_ERROR;
280 this->sendSignal(this->storeAbsConstOffset_directiveHandler(directive, error));
281 handleDirectiveErrorCode(Fpy::DirectiveId::STORE_ABS_CONST_OFFSET, error);
282 }
283
284 //! Internal interface handler for directive_popEvent
285 void FpySequencer::directive_popEvent_internalInterfaceHandler(const Svc::FpySequencer_PopEventDirective& directive) {
286 DirectiveError error = DirectiveError::NO_ERROR;
287 this->sendSignal(this->popEvent_directiveHandler(directive, error));
288 handleDirectiveErrorCode(Fpy::DirectiveId::POP_EVENT, error);
289 }
290
291 //! Internal interface handler for directive_popSerializable
292 void FpySequencer::directive_popSerializable_internalInterfaceHandler(
293 const Svc::FpySequencer_PopSerializableDirective& directive) {
294 DirectiveError error = DirectiveError::NO_ERROR;
295 this->sendSignal(this->popSerializable_directiveHandler(directive, error));
296 handleDirectiveErrorCode(Fpy::DirectiveId::POP_SERIALIZABLE, error);
297 }
298
299 //! Internal interface handler for directive_waitRel
300 Signal FpySequencer::waitRel_directiveHandler(const FpySequencer_WaitRelDirective& directive, DirectiveError& error) {
301 if (this->m_runtime.stack.size < 8) {
302 error = DirectiveError::STACK_UNDERFLOW;
303 return Signal::stmtResponse_failure;
304 }
305
306 Fw::Time wakeupTime = this->getTime();
307
308 U32 uSeconds = this->m_runtime.stack.pop<U32>();
309 U32 seconds = this->m_runtime.stack.pop<U32>();
310
311 wakeupTime.add(seconds, uSeconds);
312 this->m_runtime.wakeupTime = wakeupTime;
313 return Signal::stmtResponse_beginSleep;
314 }
315
316 //! Internal interface handler for directive_waitAbs
317 Signal FpySequencer::waitAbs_directiveHandler(const FpySequencer_WaitAbsDirective& directive, DirectiveError& error) {
318 if (this->m_runtime.stack.size < 2 * sizeof(U32) + sizeof(FwTimeContextStoreType) + sizeof(FwTimeBaseStoreType)) {
319 error = DirectiveError::STACK_UNDERFLOW;
320 return Signal::stmtResponse_failure;
321 }
322
323 U32 uSeconds = this->m_runtime.stack.pop<U32>();
324 U32 seconds = this->m_runtime.stack.pop<U32>();
325 FwTimeContextStoreType ctx = this->m_runtime.stack.pop<FwTimeContextStoreType>();
326 FwTimeBaseStoreType base = this->m_runtime.stack.pop<FwTimeBaseStoreType>();
327
328 this->m_runtime.wakeupTime = Fw::Time(static_cast<TimeBase::T>(base), ctx, seconds, uSeconds);
329 return Signal::stmtResponse_beginSleep;
330 }
331
332 //! Internal interface handler for directive_goto
333 Signal FpySequencer::goto_directiveHandler(const FpySequencer_GotoDirective& directive, DirectiveError& error) {
334 // check within sequence bounds, or at EOF (we allow == case cuz this just ends the sequence)
335 if (directive.get_statementIndex() > m_sequenceObj.get_header().get_statementCount()) {
336 error = DirectiveError::STMT_OUT_OF_BOUNDS;
337 return Signal::stmtResponse_failure;
338 }
339 m_runtime.nextStatementIndex = directive.get_statementIndex();
340 return Signal::stmtResponse_success;
341 }
342
343 //! Internal interface handler for directive_if
344 Signal FpySequencer::if_directiveHandler(const FpySequencer_IfDirective& directive, DirectiveError& error) {
345 if (this->m_runtime.stack.size < 1) {
346 error = DirectiveError::STACK_UNDERFLOW;
347 return Signal::stmtResponse_failure;
348 }
349 // check within sequence bounds, or at EOF (we allow == case cuz this just ends the sequence)
350 if (directive.get_falseGotoStmtIndex() > m_sequenceObj.get_header().get_statementCount()) {
351 error = DirectiveError::STMT_OUT_OF_BOUNDS;
352 return Signal::stmtResponse_failure;
353 }
354
355 if (this->m_runtime.stack.pop<U8>() != 0) {
356 // proceed to next instruction
357 return Signal::stmtResponse_success;
358 }
359
360 // conditional false case
361 this->m_runtime.nextStatementIndex = directive.get_falseGotoStmtIndex();
362 return Signal::stmtResponse_success;
363 }
364
365 Signal FpySequencer::noOp_directiveHandler(const FpySequencer_NoOpDirective& directive, DirectiveError& error) {
366 return Signal::stmtResponse_success;
367 }
368
369 Signal FpySequencer::pushTlmVal_directiveHandler(const FpySequencer_PushTlmValDirective& directive,
370 DirectiveError& error) {
371 if (!this->isConnected_getTlmChan_OutputPort(0)) {
372 error = DirectiveError::TLM_GET_NOT_CONNECTED;
373 return Signal::stmtResponse_failure;
374 }
375 Fw::Time tlmTime;
376 Fw::TlmBuffer tlmValue;
377 Fw::TlmValid valid = this->getTlmChan_out(0, directive.get_chanId(), tlmTime, tlmValue);
378
379 if (valid != Fw::TlmValid::VALID) {
380 // could not find this tlm chan
381 error = DirectiveError::TLM_CHAN_NOT_FOUND;
382 return Signal::stmtResponse_failure;
383 }
384
385 if (Fpy::MAX_STACK_SIZE - tlmValue.getSize() < this->m_runtime.stack.size) {
386 error = DirectiveError::STACK_OVERFLOW;
387 return Signal::stmtResponse_failure;
388 }
389 this->m_runtime.stack.push(tlmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(tlmValue.getSize()));
390 return Signal::stmtResponse_success;
391 }
392
393 Signal FpySequencer::pushTlmValAndTime_directiveHandler(const FpySequencer_PushTlmValAndTimeDirective& directive,
394 DirectiveError& error) {
395 if (!this->isConnected_getTlmChan_OutputPort(0)) {
396 error = DirectiveError::TLM_GET_NOT_CONNECTED;
397 return Signal::stmtResponse_failure;
398 }
399
400 Fw::Time tlmTime;
401 Fw::TlmBuffer tlmValue;
402 Fw::TlmValid valid = this->getTlmChan_out(0, directive.get_chanId(), tlmTime, tlmValue);
403
404 if (valid != Fw::TlmValid::VALID) {
405 // could not find this tlm chan
406 error = DirectiveError::TLM_CHAN_NOT_FOUND;
407 return Signal::stmtResponse_failure;
408 }
409
410 U8 tlmTimeBuf[Fw::Time::SERIALIZED_SIZE] = {};
411 Fw::ExternalSerializeBuffer timeEsb(tlmTimeBuf, Fw::Time::SERIALIZED_SIZE);
412 Fw::SerializeStatus stat = timeEsb.serializeFrom(tlmTime);
413
414 // coding error if this failed, we should have enough space
415 FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
416
417 // check that our stack won't overflow if we put both val and time on it
418 if (Fpy::MAX_STACK_SIZE - tlmValue.getSize() - timeEsb.getSize() < this->m_runtime.stack.size) {
419 error = DirectiveError::STACK_OVERFLOW;
420 return Signal::stmtResponse_failure;
421 }
422
423 // push tlm to end of stack
424 this->m_runtime.stack.push(tlmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(tlmValue.getSize()));
425 // now push time to end of stack
426 this->m_runtime.stack.push(timeEsb.getBuffAddr(), static_cast<Fpy::StackSizeType>(timeEsb.getSize()));
427 return Signal::stmtResponse_success;
428 }
429
430 Signal FpySequencer::pushPrm_directiveHandler(const FpySequencer_PushPrmDirective& directive, DirectiveError& error) {
431 if (!this->isConnected_prmGet_OutputPort(0)) {
432 error = DirectiveError::PRM_GET_NOT_CONNECTED;
433 return Signal::stmtResponse_failure;
434 }
435
436 Fw::ParamBuffer prmValue;
437 Fw::ParamValid valid = this->getParam_out(0, directive.get_prmId(), prmValue);
438
439 if (valid != Fw::ParamValid::VALID) {
440 // could not find this prm in the DB
441 error = DirectiveError::PRM_NOT_FOUND;
442 return Signal::stmtResponse_failure;
443 }
444
445 if (Fpy::MAX_STACK_SIZE - prmValue.getSize() < this->m_runtime.stack.size) {
446 error = DirectiveError::STACK_OVERFLOW;
447 return Signal::stmtResponse_failure;
448 }
449
450 this->m_runtime.stack.push(prmValue.getBuffAddr(), static_cast<Fpy::StackSizeType>(prmValue.getSize()));
451 return Signal::stmtResponse_success;
452 }
453
454 Signal FpySequencer::constCmd_directiveHandler(const FpySequencer_ConstCmdDirective& directive, DirectiveError& error) {
455 // the cmd response code will be pushed to the stack when it comes back, so make sure
456 // there is room for it now, before the cmd is dispatched
457 if (Fpy::MAX_STACK_SIZE - sizeof(Fw::CmdResponse::SerialType) < this->m_runtime.stack.size) {
458 error = DirectiveError::STACK_OVERFLOW;
459 return Signal::stmtResponse_failure;
460 }
461 if (this->sendCmd(directive.get_opCode(), directive.get_argBuf(), directive.get__argBufSize()) ==
462 Fw::Success::FAILURE) {
463 return Signal::stmtResponse_failure;
464 } else {
465 // now tell the SM to wait some more until we get the cmd response back
466 // if we've already got the response back this should be harmless
467 return Signal::stmtResponse_keepWaiting;
468 }
469 }
470
471 DirectiveError FpySequencer::op_or() {
472 if (this->m_runtime.stack.size < sizeof(U8) * 2) {
473 return DirectiveError::STACK_UNDERFLOW;
474 }
475 this->m_runtime.stack.push(static_cast<U8>(this->m_runtime.stack.pop<U8>() | this->m_runtime.stack.pop<U8>()));
476 return DirectiveError::NO_ERROR;
477 }
478 DirectiveError FpySequencer::op_and() {
479 if (this->m_runtime.stack.size < sizeof(U8) * 2) {
480 return DirectiveError::STACK_UNDERFLOW;
481 }
482 this->m_runtime.stack.push(static_cast<U8>(this->m_runtime.stack.pop<U8>() & this->m_runtime.stack.pop<U8>()));
483 return DirectiveError::NO_ERROR;
484 }
485 DirectiveError FpySequencer::op_ieq() {
486 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
487 return DirectiveError::STACK_UNDERFLOW;
488 }
489 this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<I64>() == this->m_runtime.stack.pop<I64>())
490 ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
491 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
492 return DirectiveError::NO_ERROR;
493 }
494 DirectiveError FpySequencer::op_ine() {
495 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
496 return DirectiveError::STACK_UNDERFLOW;
497 }
498 this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<I64>() != this->m_runtime.stack.pop<I64>())
499 ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
500 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
501 return DirectiveError::NO_ERROR;
502 }
503 DirectiveError FpySequencer::op_ult() {
504 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
505 return DirectiveError::STACK_UNDERFLOW;
506 }
507 U64 rhs = this->m_runtime.stack.pop<U64>();
508 U64 lhs = this->m_runtime.stack.pop<U64>();
509 this->m_runtime.stack.push(static_cast<U8>((lhs < rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
510 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
511 return DirectiveError::NO_ERROR;
512 }
513 DirectiveError FpySequencer::op_ule() {
514 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
515 return DirectiveError::STACK_UNDERFLOW;
516 }
517 U64 rhs = this->m_runtime.stack.pop<U64>();
518 U64 lhs = this->m_runtime.stack.pop<U64>();
519 this->m_runtime.stack.push(static_cast<U8>((lhs <= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
520 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
521 return DirectiveError::NO_ERROR;
522 }
523 DirectiveError FpySequencer::op_ugt() {
524 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
525 return DirectiveError::STACK_UNDERFLOW;
526 }
527 U64 rhs = this->m_runtime.stack.pop<U64>();
528 U64 lhs = this->m_runtime.stack.pop<U64>();
529 this->m_runtime.stack.push(static_cast<U8>((lhs > rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
530 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
531 return DirectiveError::NO_ERROR;
532 }
533 DirectiveError FpySequencer::op_uge() {
534 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
535 return DirectiveError::STACK_UNDERFLOW;
536 }
537 U64 rhs = this->m_runtime.stack.pop<U64>();
538 U64 lhs = this->m_runtime.stack.pop<U64>();
539 this->m_runtime.stack.push(static_cast<U8>((lhs >= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
540 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
541 return DirectiveError::NO_ERROR;
542 }
543 DirectiveError FpySequencer::op_slt() {
544 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
545 return DirectiveError::STACK_UNDERFLOW;
546 }
547 I64 rhs = this->m_runtime.stack.pop<I64>();
548 I64 lhs = this->m_runtime.stack.pop<I64>();
549 this->m_runtime.stack.push(static_cast<U8>((lhs < rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
550 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
551 return DirectiveError::NO_ERROR;
552 }
553 DirectiveError FpySequencer::op_sle() {
554 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
555 return DirectiveError::STACK_UNDERFLOW;
556 }
557 I64 rhs = this->m_runtime.stack.pop<I64>();
558 I64 lhs = this->m_runtime.stack.pop<I64>();
559 this->m_runtime.stack.push(static_cast<U8>((lhs <= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
560 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
561 return DirectiveError::NO_ERROR;
562 }
563 DirectiveError FpySequencer::op_sgt() {
564 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
565 return DirectiveError::STACK_UNDERFLOW;
566 }
567 I64 rhs = this->m_runtime.stack.pop<I64>();
568 I64 lhs = this->m_runtime.stack.pop<I64>();
569 this->m_runtime.stack.push(static_cast<U8>((lhs > rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
570 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
571 return DirectiveError::NO_ERROR;
572 }
573 DirectiveError FpySequencer::op_sge() {
574 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
575 return DirectiveError::STACK_UNDERFLOW;
576 }
577 I64 rhs = this->m_runtime.stack.pop<I64>();
578 I64 lhs = this->m_runtime.stack.pop<I64>();
579 this->m_runtime.stack.push(static_cast<U8>((lhs >= rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
580 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
581 return DirectiveError::NO_ERROR;
582 }
583 DirectiveError FpySequencer::op_feq() {
584 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
585 return DirectiveError::STACK_UNDERFLOW;
586 }
587 F64 rhs = this->m_runtime.stack.pop<F64>();
588 F64 lhs = this->m_runtime.stack.pop<F64>();
589 // eq is true if they are equal and neither is nan
590 this->m_runtime.stack.push(static_cast<U8>((lhs == rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
591 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
592 return DirectiveError::NO_ERROR;
593 }
594 DirectiveError FpySequencer::op_fne() {
595 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
596 return DirectiveError::STACK_UNDERFLOW;
597 }
598 F64 rhs = this->m_runtime.stack.pop<F64>();
599 F64 lhs = this->m_runtime.stack.pop<F64>();
600 // ne is true if they are not equal or either is nan
601 this->m_runtime.stack.push(static_cast<U8>((lhs != rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
602 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
603 return DirectiveError::NO_ERROR;
604 }
605 DirectiveError FpySequencer::op_flt() {
606 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
607 return DirectiveError::STACK_UNDERFLOW;
608 }
609 F64 rhs = this->m_runtime.stack.pop<F64>();
610 F64 lhs = this->m_runtime.stack.pop<F64>();
611 this->m_runtime.stack.push(static_cast<U8>(std::isless(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
612 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
613 return DirectiveError::NO_ERROR;
614 }
615 DirectiveError FpySequencer::op_fle() {
616 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
617 return DirectiveError::STACK_UNDERFLOW;
618 }
619 F64 rhs = this->m_runtime.stack.pop<F64>();
620 F64 lhs = this->m_runtime.stack.pop<F64>();
621 this->m_runtime.stack.push(static_cast<U8>(std::islessequal(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
622 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
623 return DirectiveError::NO_ERROR;
624 }
625 DirectiveError FpySequencer::op_fgt() {
626 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
627 return DirectiveError::STACK_UNDERFLOW;
628 }
629 F64 rhs = this->m_runtime.stack.pop<F64>();
630 F64 lhs = this->m_runtime.stack.pop<F64>();
631 this->m_runtime.stack.push(static_cast<U8>(std::isgreater(lhs, rhs) ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
632 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
633 return DirectiveError::NO_ERROR;
634 }
635 DirectiveError FpySequencer::op_fge() {
636 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
637 return DirectiveError::STACK_UNDERFLOW;
638 }
639 F64 rhs = this->m_runtime.stack.pop<F64>();
640 F64 lhs = this->m_runtime.stack.pop<F64>();
641 this->m_runtime.stack.push(static_cast<U8>(std::isgreaterequal(lhs, rhs)
642 ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
643 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
644 return DirectiveError::NO_ERROR;
645 }
646 DirectiveError FpySequencer::op_not() {
647 if (this->m_runtime.stack.size < sizeof(U8)) {
648 return DirectiveError::STACK_UNDERFLOW;
649 }
650 this->m_runtime.stack.push(static_cast<U8>((this->m_runtime.stack.pop<U8>() == 0)
651 ? static_cast<U8>(FW_SERIALIZE_TRUE_VALUE)
652 : static_cast<U8>(FW_SERIALIZE_FALSE_VALUE)));
653 return DirectiveError::NO_ERROR;
654 }
655 DirectiveError FpySequencer::op_fpext() {
656 // convert F32 to F64
657 if (this->m_runtime.stack.size < sizeof(F32)) {
658 return DirectiveError::STACK_UNDERFLOW;
659 }
660 this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<F32>()));
661 return DirectiveError::NO_ERROR;
662 }
663 DirectiveError FpySequencer::op_fptrunc() {
664 // convert F64 to F32
665 if (this->m_runtime.stack.size < sizeof(F64)) {
666 return DirectiveError::STACK_UNDERFLOW;
667 }
668 this->m_runtime.stack.push(static_cast<F32>(this->m_runtime.stack.pop<F64>()));
669 return DirectiveError::NO_ERROR;
670 }
671 DirectiveError FpySequencer::op_fptosi() {
672 if (this->m_runtime.stack.size < sizeof(F64)) {
673 return DirectiveError::STACK_UNDERFLOW;
674 }
675 F64 val = this->m_runtime.stack.pop<F64>();
676 // NaN -> 0, out-of-range clamps, in-range truncates toward
677 // zero. The raw static_cast is UB for NaN and out-of-range values.
678 // 2^63 is exactly representable as F64 and is one past I64 max; -2^63
679 // is exactly I64 min and in range.
680 const F64 bound = std::ldexp(1.0, 63);
681 I64 result;
682 if (std::isnan(val)) {
683 result = 0;
684 } else if (val >= bound) {
685 result = std::numeric_limits<I64>::max();
686 } else if (val < -bound) {
687 result = std::numeric_limits<I64>::min();
688 } else {
689 result = static_cast<I64>(val);
690 }
691 this->m_runtime.stack.push(result);
692 return DirectiveError::NO_ERROR;
693 }
694 DirectiveError FpySequencer::op_sitofp() {
695 if (this->m_runtime.stack.size < sizeof(I64)) {
696 return DirectiveError::STACK_UNDERFLOW;
697 }
698 this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<I64>()));
699 return DirectiveError::NO_ERROR;
700 }
701 DirectiveError FpySequencer::op_fptoui() {
702 if (this->m_runtime.stack.size < sizeof(F64)) {
703 return DirectiveError::STACK_UNDERFLOW;
704 }
705 F64 val = this->m_runtime.stack.pop<F64>();
706 // NaN -> 0, negatives truncate to at most 0 and clamp there,
707 // 2^64 (one past U64 max) and above clamp to U64 max. The raw
708 // static_cast is UB for NaN and out-of-range values.
709 const F64 bound = std::ldexp(1.0, 64);
710 U64 result;
711 if (std::isnan(val) || val < 0.0) {
712 result = 0;
713 } else if (val >= bound) {
714 result = std::numeric_limits<U64>::max();
715 } else {
716 result = static_cast<U64>(val);
717 }
718 this->m_runtime.stack.push(result);
719 return DirectiveError::NO_ERROR;
720 }
721 DirectiveError FpySequencer::op_uitofp() {
722 if (this->m_runtime.stack.size < sizeof(U64)) {
723 return DirectiveError::STACK_UNDERFLOW;
724 }
725 this->m_runtime.stack.push(static_cast<F64>(this->m_runtime.stack.pop<U64>()));
726 return DirectiveError::NO_ERROR;
727 }
728 DirectiveError FpySequencer::op_add() {
729 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
730 return DirectiveError::STACK_UNDERFLOW;
731 }
732 I64 rhs = this->m_runtime.stack.pop<I64>();
733 I64 lhs = this->m_runtime.stack.pop<I64>();
734 // Check for overflow and underflow and return the appropriate error code
735 // Overflow can only occur with both operands positive and occurs when one operand is greater than the maximum value
736 // less the other operand. If either operand is negative or zero, overflow cannot occur.
737 if ((rhs > 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() - rhs) < lhs)) {
738 return DirectiveError::ARITHMETIC_OVERFLOW;
739 }
740 // Underflow can only occur with both operands negative and occurs when one operand is less than the minimum value
741 // minus the other operand. If either operand is positive or zero, underflow cannot occur.
742 else if ((rhs < 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() - rhs) > lhs)) {
743 return DirectiveError::ARITHMETIC_UNDERFLOW;
744 }
745 this->m_runtime.stack.push(static_cast<I64>(lhs + rhs));
746 return DirectiveError::NO_ERROR;
747 }
748 DirectiveError FpySequencer::op_sub() {
749 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
750 return DirectiveError::STACK_UNDERFLOW;
751 }
752 I64 rhs = this->m_runtime.stack.pop<I64>();
753 I64 lhs = this->m_runtime.stack.pop<I64>();
754 // Check for overflow and underflow and return the appropriate error code
755 // Overflow can only occur when the left operand is positive and the right operand is negative. It occurs when the
756 // left (positive) operand is greater than the maximum value plus the other (negative) operand. If the right
757 // operand is positive or zero, overflow cannot occur.
758 if ((rhs < 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() + rhs) < lhs)) {
759 return DirectiveError::ARITHMETIC_OVERFLOW;
760 }
761 // Underflow can only occur when the left operand is negative and the right operand is positive. It occurs when the
762 // left (negative) operand is less than the minimum value plus the other (positive) operand. If the right operand
763 // is negative or zero, underflow cannot occur.
764 else if ((rhs > 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() + rhs) > lhs)) {
765 return DirectiveError::ARITHMETIC_UNDERFLOW;
766 }
767 this->m_runtime.stack.push(static_cast<I64>(lhs - rhs));
768 return DirectiveError::NO_ERROR;
769 }
770 DirectiveError FpySequencer::op_mul() {
771 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
772 return DirectiveError::STACK_UNDERFLOW;
773 }
774 I64 rhs = this->m_runtime.stack.pop<I64>();
775 I64 lhs = this->m_runtime.stack.pop<I64>();
776 // Check for overflow and underflow and return the appropriate error code
777 // Overflow can only occur with operands of matching signs and occurs when one operand is greater (or less) than the
778 // maximum value divided by the other operand. Either operand being zero precludes overflow.
779 // Check the both positive case.
780 if ((rhs > 0) && (lhs > 0) && ((std::numeric_limits<I64>::max() / rhs) < lhs)) {
781 return DirectiveError::ARITHMETIC_OVERFLOW;
782 }
783 // Check the both negative case. Compare without negation: negating a value of min is undefined behavior
784 else if ((rhs < 0) && (lhs < 0) && (lhs < (std::numeric_limits<I64>::max() / rhs))) {
785 return DirectiveError::ARITHMETIC_OVERFLOW;
786 }
787 // Underflow can occur with operands of differing signs and occurs when one operand is less than the minimum value
788 // divided by the other operand. Either operand being zero precludes underflow.
789 // Check the case where lhs is positive.
790 else if ((rhs < 0) && (lhs > 0) && ((std::numeric_limits<I64>::min() / lhs) > rhs)) {
791 return DirectiveError::ARITHMETIC_UNDERFLOW;
792 }
793 // Check the case where rhs is positive.
794 else if ((rhs > 0) && (lhs < 0) && ((std::numeric_limits<I64>::min() / rhs) > lhs)) {
795 return DirectiveError::ARITHMETIC_UNDERFLOW;
796 }
797 this->m_runtime.stack.push(static_cast<I64>(lhs * rhs));
798 return DirectiveError::NO_ERROR;
799 }
800 DirectiveError FpySequencer::op_udiv() {
801 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
802 return DirectiveError::STACK_UNDERFLOW;
803 }
804 U64 rhs = this->m_runtime.stack.pop<U64>();
805 U64 lhs = this->m_runtime.stack.pop<U64>();
806 // Prevent division by zero
807 if (rhs == 0) {
808 return DirectiveError::DOMAIN_ERROR;
809 }
810 this->m_runtime.stack.push(static_cast<U64>(lhs / rhs));
811 return DirectiveError::NO_ERROR;
812 }
813 DirectiveError FpySequencer::op_sdiv() {
814 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
815 return DirectiveError::STACK_UNDERFLOW;
816 }
817
818 I64 rhs = this->m_runtime.stack.pop<I64>();
819 I64 lhs = this->m_runtime.stack.pop<I64>();
820 // Prevent division by zero
821 if (rhs == 0) {
822 return DirectiveError::DOMAIN_ERROR;
823 }
824 // The one signed division that can overflow: |I64 min / -1| = 2^63 is not
825 // representable (and the C++ expression is UB, SIGFPE on x86)
826 if ((lhs == std::numeric_limits<I64>::min()) && (rhs == -1)) {
827 return DirectiveError::ARITHMETIC_OVERFLOW;
828 }
829 // C++ / truncates toward zero; adjust to match Python's floored division:
830 // an inexact quotient with differing operand signs floors one below the
831 // truncated result. This mirrors op_smod.
832 I64 quotient = lhs / rhs;
833 if (((lhs % rhs) != 0) && ((lhs < 0) != (rhs < 0))) {
834 quotient -= 1;
835 }
836 this->m_runtime.stack.push(quotient);
837 return DirectiveError::NO_ERROR;
838 }
839 DirectiveError FpySequencer::op_umod() {
840 if (this->m_runtime.stack.size < sizeof(U64) * 2) {
841 return DirectiveError::STACK_UNDERFLOW;
842 }
843 U64 rhs = this->m_runtime.stack.pop<U64>();
844 if (rhs == 0) {
845 return DirectiveError::DOMAIN_ERROR;
846 }
847 U64 lhs = this->m_runtime.stack.pop<U64>();
848 this->m_runtime.stack.push(static_cast<U64>(lhs % rhs));
849 return DirectiveError::NO_ERROR;
850 }
851 DirectiveError FpySequencer::op_smod() {
852 if (this->m_runtime.stack.size < sizeof(I64) * 2) {
853 return DirectiveError::STACK_UNDERFLOW;
854 }
855 I64 rhs = this->m_runtime.stack.pop<I64>();
856 if (rhs == 0) {
857 return DirectiveError::DOMAIN_ERROR;
858 }
859 I64 lhs = this->m_runtime.stack.pop<I64>();
860 // I64 min % -1 is 0, the mathematical remainder (matching wasm i64.rem_s),
861 // but the C++ expression is UB (SIGFPE on x86) so it must be special-cased
862 if ((lhs == std::numeric_limits<I64>::min()) && (rhs == -1)) {
863 this->m_runtime.stack.push(static_cast<I64>(0));
864 return DirectiveError::NO_ERROR;
865 }
866 I64 res = static_cast<I64>(lhs % rhs);
867 // in order to match Python's behavior,
868 // if the signs of the remainder and divisor differ, adjust the result.
869 // this happens when the result should be positive but is negative, or vice-versa.
870 // credit Gemini 2.5 pro
871 if ((res > 0 && rhs < 0) || (res < 0 && rhs > 0)) {
872 res += rhs;
873 }
874 this->m_runtime.stack.push(res);
875 return DirectiveError::NO_ERROR;
876 }
877 DirectiveError FpySequencer::op_fadd() {
878 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
879 return DirectiveError::STACK_UNDERFLOW;
880 }
881 F64 rhs = this->m_runtime.stack.pop<F64>();
882 F64 lhs = this->m_runtime.stack.pop<F64>();
883 this->m_runtime.stack.push(static_cast<F64>(lhs + rhs));
884 return DirectiveError::NO_ERROR;
885 }
886 DirectiveError FpySequencer::op_fsub() {
887 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
888 return DirectiveError::STACK_UNDERFLOW;
889 }
890 F64 rhs = this->m_runtime.stack.pop<F64>();
891 F64 lhs = this->m_runtime.stack.pop<F64>();
892 this->m_runtime.stack.push(static_cast<F64>(lhs - rhs));
893 return DirectiveError::NO_ERROR;
894 }
895 DirectiveError FpySequencer::op_fmul() {
896 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
897 return DirectiveError::STACK_UNDERFLOW;
898 }
899 F64 rhs = this->m_runtime.stack.pop<F64>();
900 F64 lhs = this->m_runtime.stack.pop<F64>();
901 this->m_runtime.stack.push(static_cast<F64>(lhs * rhs));
902 return DirectiveError::NO_ERROR;
903 }
904 DirectiveError FpySequencer::op_fdiv() {
905 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
906 return DirectiveError::STACK_UNDERFLOW;
907 }
908 F64 rhs = this->m_runtime.stack.pop<F64>();
909 F64 lhs = this->m_runtime.stack.pop<F64>();
910 this->m_runtime.stack.push(static_cast<F64>(lhs / rhs));
911 return DirectiveError::NO_ERROR;
912 }
913 DirectiveError FpySequencer::op_fpow() {
914 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
915 return DirectiveError::STACK_UNDERFLOW;
916 }
917 F64 rhs = this->m_runtime.stack.pop<F64>();
918 F64 lhs = this->m_runtime.stack.pop<F64>();
919 this->m_runtime.stack.push(static_cast<F64>(pow(lhs, rhs)));
920 return DirectiveError::NO_ERROR;
921 }
922 DirectiveError FpySequencer::op_flog() {
923 if (this->m_runtime.stack.size < sizeof(F64)) {
924 return DirectiveError::STACK_UNDERFLOW;
925 }
926 F64 val = this->m_runtime.stack.pop<F64>();
927 if (val <= 0.0) {
928 return DirectiveError::DOMAIN_ERROR;
929 }
930 this->m_runtime.stack.push(static_cast<F64>(log(val)));
931 return DirectiveError::NO_ERROR;
932 }
933 DirectiveError FpySequencer::op_fmod() {
934 if (this->m_runtime.stack.size < sizeof(F64) * 2) {
935 return DirectiveError::STACK_UNDERFLOW;
936 }
937 F64 rhs = this->m_runtime.stack.pop<F64>();
938 F64 lhs = this->m_runtime.stack.pop<F64>();
939 // std::fmod computes the exact truncated remainder (sign of lhs) with no
940 // intermediate rounding. A zero divisor yields NaN, matching Rust and C#.
941 F64 res = std::fmod(lhs, rhs);
942 // Adjust to match Python's floored-modulo semantics: if the signs of the
943 // remainder and divisor differ, add the divisor once. This mirrors op_smod
944 // and is the exact frem + fadd the VM model computes (at most one rounded add).
945 if ((res > 0 && rhs < 0) || (res < 0 && rhs > 0)) {
946 res += rhs;
947 } else if (res == 0) {
948 // Python normalizes an exact-multiple result so the zero carries the
949 // divisor's sign (CPython float_rem); fmod leaves the dividend's.
950 res = std::copysign(0.0, rhs);
951 }
952 this->m_runtime.stack.push(res);
953 return DirectiveError::NO_ERROR;
954 }
955 DirectiveError FpySequencer::op_siext_8_64() {
956 if (this->m_runtime.stack.size < sizeof(I8)) {
957 return DirectiveError::STACK_UNDERFLOW;
958 }
959 I8 src = this->m_runtime.stack.pop<I8>();
960 this->m_runtime.stack.push(static_cast<I64>(src));
961 return DirectiveError::NO_ERROR;
962 }
963 DirectiveError FpySequencer::op_siext_16_64() {
964 if (this->m_runtime.stack.size < sizeof(I16)) {
965 return DirectiveError::STACK_UNDERFLOW;
966 }
967 I16 src = this->m_runtime.stack.pop<I16>();
968 this->m_runtime.stack.push(static_cast<I64>(src));
969 return DirectiveError::NO_ERROR;
970 }
971 DirectiveError FpySequencer::op_siext_32_64() {
972 if (this->m_runtime.stack.size < sizeof(I32)) {
973 return DirectiveError::STACK_UNDERFLOW;
974 }
975 I32 src = this->m_runtime.stack.pop<I32>();
976 this->m_runtime.stack.push(static_cast<I64>(src));
977 return DirectiveError::NO_ERROR;
978 }
979 DirectiveError FpySequencer::op_ziext_8_64() {
980 if (this->m_runtime.stack.size < sizeof(U8)) {
981 return DirectiveError::STACK_UNDERFLOW;
982 }
983 U8 src = this->m_runtime.stack.pop<U8>();
984 this->m_runtime.stack.push(static_cast<U64>(src));
985 return DirectiveError::NO_ERROR;
986 }
987 DirectiveError FpySequencer::op_ziext_16_64() {
988 if (this->m_runtime.stack.size < sizeof(U16)) {
989 return DirectiveError::STACK_UNDERFLOW;
990 }
991 U16 src = this->m_runtime.stack.pop<U16>();
992 this->m_runtime.stack.push(static_cast<U64>(src));
993 return DirectiveError::NO_ERROR;
994 }
995 DirectiveError FpySequencer::op_ziext_32_64() {
996 if (this->m_runtime.stack.size < sizeof(U32)) {
997 return DirectiveError::STACK_UNDERFLOW;
998 }
999 U32 src = this->m_runtime.stack.pop<U32>();
1000 this->m_runtime.stack.push(static_cast<U64>(src));
1001 return DirectiveError::NO_ERROR;
1002 }
1003 DirectiveError FpySequencer::op_itrunc_64_8() {
1004 if (this->m_runtime.stack.size < sizeof(U64)) {
1005 return DirectiveError::STACK_UNDERFLOW;
1006 }
1007 U64 src = this->m_runtime.stack.pop<U64>();
1008 this->m_runtime.stack.push(static_cast<U8>(src));
1009 return DirectiveError::NO_ERROR;
1010 }
1011 DirectiveError FpySequencer::op_itrunc_64_16() {
1012 if (this->m_runtime.stack.size < sizeof(U64)) {
1013 return DirectiveError::STACK_UNDERFLOW;
1014 }
1015 U64 src = this->m_runtime.stack.pop<U64>();
1016 this->m_runtime.stack.push(static_cast<U16>(src));
1017 return DirectiveError::NO_ERROR;
1018 }
1019 DirectiveError FpySequencer::op_itrunc_64_32() {
1020 if (this->m_runtime.stack.size < sizeof(U64)) {
1021 return DirectiveError::STACK_UNDERFLOW;
1022 }
1023 U64 src = this->m_runtime.stack.pop<U64>();
1024 this->m_runtime.stack.push(static_cast<U32>(src));
1025 return DirectiveError::NO_ERROR;
1026 }
1027 DirectiveError FpySequencer::op_ffloor() {
1028 if (this->m_runtime.stack.size < sizeof(F64)) {
1029 return DirectiveError::STACK_UNDERFLOW;
1030 }
1031 F64 val = this->m_runtime.stack.pop<F64>();
1032 // std::floor implements IEEE 754 roundToIntegralTowardNegative: +-0, +-inf
1033 // and NaN pass through, and the sign of a zero is preserved.
1034 this->m_runtime.stack.push(std::floor(val));
1035 return DirectiveError::NO_ERROR;
1036 }
1037 DirectiveError FpySequencer::op_iabs() {
1038 if (this->m_runtime.stack.size < sizeof(I64)) {
1039 return DirectiveError::STACK_UNDERFLOW;
1040 }
1041 I64 val = this->m_runtime.stack.pop<I64>();
1042 // abs(I64 min) is not representable in I64 (and -val on it is UB)
1043 if (val == std::numeric_limits<I64>::min()) {
1044 return DirectiveError::ARITHMETIC_OVERFLOW;
1045 }
1046 this->m_runtime.stack.push(val < 0 ? -val : val);
1047 return DirectiveError::NO_ERROR;
1048 }
1049 DirectiveError FpySequencer::op_fabs() {
1050 if (this->m_runtime.stack.size < sizeof(F64)) {
1051 return DirectiveError::STACK_UNDERFLOW;
1052 }
1053 F64 val = this->m_runtime.stack.pop<F64>();
1054 // IEEE 754 abs: clears the sign bit and changes nothing else, so NaN
1055 // payloads pass through.
1056 this->m_runtime.stack.push(std::fabs(val));
1057 return DirectiveError::NO_ERROR;
1058 }
1059 Signal FpySequencer::stackOp_directiveHandler(const FpySequencer_StackOpDirective& directive, DirectiveError& error) {
1060 // coding error, should not have gotten to this stack op handler
1061 FW_ASSERT((directive.get__op() >= Fpy::DirectiveId::OR && directive.get__op() <= Fpy::DirectiveId::ITRUNC_64_32) ||
1062 (directive.get__op() >= Fpy::DirectiveId::FFLOOR && directive.get__op() <= Fpy::DirectiveId::FABS),
1063 static_cast<FwAssertArgType>(directive.get__op()));
1064
1065 switch (directive.get__op()) {
1066 case Fpy::DirectiveId::OR:
1067 error = this->op_or();
1068 break;
1069 case Fpy::DirectiveId::AND:
1070 error = this->op_and();
1071 break;
1072 case Fpy::DirectiveId::IEQ:
1073 error = this->op_ieq();
1074 break;
1075 case Fpy::DirectiveId::INE:
1076 error = this->op_ine();
1077 break;
1078 case Fpy::DirectiveId::ULT:
1079 error = this->op_ult();
1080 break;
1081 case Fpy::DirectiveId::ULE:
1082 error = this->op_ule();
1083 break;
1084 case Fpy::DirectiveId::UGT:
1085 error = this->op_ugt();
1086 break;
1087 case Fpy::DirectiveId::UGE:
1088 error = this->op_uge();
1089 break;
1090 case Fpy::DirectiveId::SLT:
1091 error = this->op_slt();
1092 break;
1093 case Fpy::DirectiveId::SLE:
1094 error = this->op_sle();
1095 break;
1096 case Fpy::DirectiveId::SGT:
1097 error = this->op_sgt();
1098 break;
1099 case Fpy::DirectiveId::SGE:
1100 error = this->op_sge();
1101 break;
1102 case Fpy::DirectiveId::FEQ:
1103 error = this->op_feq();
1104 break;
1105 case Fpy::DirectiveId::FNE:
1106 error = this->op_fne();
1107 break;
1108 case Fpy::DirectiveId::FLT:
1109 error = this->op_flt();
1110 break;
1111 case Fpy::DirectiveId::FLE:
1112 error = this->op_fle();
1113 break;
1114 case Fpy::DirectiveId::FGT:
1115 error = this->op_fgt();
1116 break;
1117 case Fpy::DirectiveId::FGE:
1118 error = this->op_fge();
1119 break;
1120 case Fpy::DirectiveId::NOT:
1121 error = this->op_not();
1122 break;
1123 case Fpy::DirectiveId::FPEXT:
1124 error = this->op_fpext();
1125 break;
1126 case Fpy::DirectiveId::FPTRUNC:
1127 error = this->op_fptrunc();
1128 break;
1129 case Fpy::DirectiveId::FPTOSI:
1130 error = this->op_fptosi();
1131 break;
1132 case Fpy::DirectiveId::FPTOUI:
1133 error = this->op_fptoui();
1134 break;
1135 case Fpy::DirectiveId::SITOFP:
1136 error = this->op_sitofp();
1137 break;
1138 case Fpy::DirectiveId::UITOFP:
1139 error = this->op_uitofp();
1140 break;
1141 case Fpy::DirectiveId::ADD:
1142 error = this->op_add();
1143 break;
1144 case Fpy::DirectiveId::SUB:
1145 error = this->op_sub();
1146 break;
1147 case Fpy::DirectiveId::MUL:
1148 error = this->op_mul();
1149 break;
1150 case Fpy::DirectiveId::UDIV:
1151 error = this->op_udiv();
1152 break;
1153 case Fpy::DirectiveId::SDIV:
1154 error = this->op_sdiv();
1155 break;
1156 case Fpy::DirectiveId::UMOD:
1157 error = this->op_umod();
1158 break;
1159 case Fpy::DirectiveId::SMOD:
1160 error = this->op_smod();
1161 break;
1162 case Fpy::DirectiveId::FADD:
1163 error = this->op_fadd();
1164 break;
1165 case Fpy::DirectiveId::FSUB:
1166 error = this->op_fsub();
1167 break;
1168 case Fpy::DirectiveId::FMUL:
1169 error = this->op_fmul();
1170 break;
1171 case Fpy::DirectiveId::FDIV:
1172 error = this->op_fdiv();
1173 break;
1174 case Fpy::DirectiveId::FPOW:
1175 error = this->op_fpow();
1176 break;
1177 case Fpy::DirectiveId::FLOG:
1178 error = this->op_flog();
1179 break;
1180 case Fpy::DirectiveId::FMOD:
1181 error = this->op_fmod();
1182 break;
1183 case Fpy::DirectiveId::SIEXT_8_64:
1184 error = this->op_siext_8_64();
1185 break;
1186 case Fpy::DirectiveId::SIEXT_16_64:
1187 error = this->op_siext_16_64();
1188 break;
1189 case Fpy::DirectiveId::SIEXT_32_64:
1190 error = this->op_siext_32_64();
1191 break;
1192 case Fpy::DirectiveId::ZIEXT_8_64:
1193 error = this->op_ziext_8_64();
1194 break;
1195 case Fpy::DirectiveId::ZIEXT_16_64:
1196 error = this->op_ziext_16_64();
1197 break;
1198 case Fpy::DirectiveId::ZIEXT_32_64:
1199 error = this->op_ziext_32_64();
1200 break;
1201 case Fpy::DirectiveId::ITRUNC_64_8:
1202 error = this->op_itrunc_64_8();
1203 break;
1204 case Fpy::DirectiveId::ITRUNC_64_16:
1205 error = this->op_itrunc_64_16();
1206 break;
1207 case Fpy::DirectiveId::ITRUNC_64_32:
1208 error = this->op_itrunc_64_32();
1209 break;
1210 case Fpy::DirectiveId::FFLOOR:
1211 error = this->op_ffloor();
1212 break;
1213 case Fpy::DirectiveId::IABS:
1214 error = this->op_iabs();
1215 break;
1216 case Fpy::DirectiveId::FABS:
1217 error = this->op_fabs();
1218 break;
1219 default:
1220 FW_ASSERT(false, directive.get__op());
1221 break;
1222 }
1223 if (error != DirectiveError::NO_ERROR) {
1224 return Signal::stmtResponse_failure;
1225 }
1226 return Signal::stmtResponse_success;
1227 }
1228
1229 Signal FpySequencer::exit_directiveHandler(const FpySequencer_ExitDirective& directive, DirectiveError& error) {
1230 if (this->m_runtime.stack.size < sizeof(I32)) {
1231 error = DirectiveError::STACK_UNDERFLOW;
1232 return Signal::stmtResponse_failure;
1233 }
1234 I32 errorCode = this->m_runtime.stack.pop<I32>();
1235 // exit(0), no error
1236 if (errorCode == 0) {
1237 // just goto the end of the sequence
1238 this->m_runtime.nextStatementIndex = this->m_sequenceObj.get_header().get_statementCount();
1239 return Signal::stmtResponse_success;
1240 }
1241 // otherwise, kill the sequence here
1242 // raise the user defined error code as an event
1243 this->log_WARNING_HI_SequenceExitedWithError(this->m_sequenceFilePath, errorCode);
1244 error = DirectiveError::EXIT_WITH_ERROR;
1245 return Signal::stmtResponse_failure;
1246 }
1247
1248 Signal FpySequencer::allocate_directiveHandler(const FpySequencer_AllocateDirective& directive, DirectiveError& error) {
1249 if (directive.get_size() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1250 error = DirectiveError::STACK_OVERFLOW;
1251 return Signal::stmtResponse_failure;
1252 }
1253 this->m_runtime.stack.pushZeroes(directive.get_size());
1254 return Signal::stmtResponse_success;
1255 }
1256
1257 //! Helper to pop value from stack top and store at destOffset
1258 Signal FpySequencer::storeHelper(Fpy::StackSizeType destOffset, Fpy::StackSizeType size, DirectiveError& error) {
1259 if (this->m_runtime.stack.size < size) {
1260 error = DirectiveError::STACK_UNDERFLOW;
1261 return Signal::stmtResponse_failure;
1262 }
1263 // After popping the value, would the write go out of bounds?
1264 Fpy::StackSizeType newStackSize = this->m_runtime.stack.size - size;
1265 // Overflow-safe check: destOffset + size > newStackSize
1266 // Rewritten as: check destOffset <= newStackSize first, then size > newStackSize - destOffset
1267 if (destOffset > newStackSize || size > newStackSize - destOffset) {
1268 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1269 return Signal::stmtResponse_failure;
1270 }
1271 // Copy value to the destination location
1272 this->m_runtime.stack.copy(destOffset, this->m_runtime.stack.size - size, size);
1273 this->m_runtime.stack.size = newStackSize;
1274 return Signal::stmtResponse_success;
1275 }
1276
1277 //! Helper to load value from srcOffset and push to stack top
1278 Signal FpySequencer::loadHelper(Fpy::StackSizeType srcOffset, Fpy::StackSizeType size, DirectiveError& error) {
1279 if (size > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1280 error = DirectiveError::STACK_OVERFLOW;
1281 return Signal::stmtResponse_failure;
1282 }
1283 // Overflow-safe check: srcOffset + size > stack.size
1284 // Rewritten as: check srcOffset <= stack.size first, then size > stack.size - srcOffset
1285 if (srcOffset > this->m_runtime.stack.size || size > this->m_runtime.stack.size - srcOffset) {
1286 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1287 return Signal::stmtResponse_failure;
1288 }
1289 // Copy from source location to top of stack
1290 this->m_runtime.stack.copy(this->m_runtime.stack.size, srcOffset, size);
1291 this->m_runtime.stack.size += size;
1292 return Signal::stmtResponse_success;
1293 }
1294
1295 Signal FpySequencer::storeRelConstOffset_directiveHandler(const FpySequencer_StoreRelConstOffsetDirective& directive,
1296 DirectiveError& error) {
1297 I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + directive.get_lvarOffset();
1298 if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1299 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1300 return Signal::stmtResponse_failure;
1301 }
1302 return this->storeHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1303 }
1304
1305 Signal FpySequencer::loadRel_directiveHandler(const FpySequencer_LoadRelDirective& directive, DirectiveError& error) {
1306 I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + directive.get_lvarOffset();
1307 if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1308 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1309 return Signal::stmtResponse_failure;
1310 }
1311 return this->loadHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1312 }
1313
1314 Signal FpySequencer::pushVal_directiveHandler(const FpySequencer_PushValDirective& directive, DirectiveError& error) {
1315 if (directive.get__valSize() > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1316 error = DirectiveError::STACK_OVERFLOW;
1317 return Signal::stmtResponse_failure;
1318 }
1319 // copy from the bytearray in the directive to the stack, add to stack size.
1320 this->m_runtime.stack.push(const_cast<U8*>(directive.get_val()),
1321 static_cast<Fpy::StackSizeType>(directive.get__valSize()));
1322 return Signal::stmtResponse_success;
1323 }
1324
1325 Signal FpySequencer::discard_directiveHandler(const FpySequencer_DiscardDirective& directive, DirectiveError& error) {
1326 if (this->m_runtime.stack.size < directive.get_size()) {
1327 error = DirectiveError::STACK_UNDERFLOW;
1328 return Signal::stmtResponse_failure;
1329 }
1330 // drop the specified amount of bytes off the stack. simple as.
1331 this->m_runtime.stack.size -= directive.get_size();
1332 return Signal::stmtResponse_success;
1333 }
1334
1335 Signal FpySequencer::memCmp_directiveHandler(const FpySequencer_MemCmpDirective& directive, DirectiveError& error) {
1336 // Overflow-safe check: we need size * 2 bytes on the stack
1337 // First check that size * 2 doesn't overflow: size > MAX/2 would overflow
1338 // MAX_STACK_SIZE is the upper bound for stack.size, so if size > MAX_STACK_SIZE/2, we definitely don't have enough
1339 if (directive.get_size() > Fpy::MAX_STACK_SIZE / 2) {
1340 error = DirectiveError::STACK_UNDERFLOW;
1341 return Signal::stmtResponse_failure;
1342 }
1343 // Now safe to compute size * 2
1344 if (this->m_runtime.stack.size < directive.get_size() * 2) {
1345 error = DirectiveError::STACK_UNDERFLOW;
1346 return Signal::stmtResponse_failure;
1347 }
1348
1349 // find the starting offsets of the two byte arrays
1350 U64 lhsOffset = this->m_runtime.stack.size - directive.get_size() * 2;
1351 U64 rhsOffset = this->m_runtime.stack.size - directive.get_size();
1352
1353 // "officially" remove them from the stack
1354 // you have to do this before pushing to the stack, otherwise the result would get placed
1355 // after the byte arrays
1356 this->m_runtime.stack.size -= directive.get_size() * 2;
1357
1358 // memcmp the two byte arrays, push FW_SERIALIZE_TRUE_VALUE if they were equal, FW_SERIALIZE_FALSE_VALUE otherwise
1359 if (memcmp(this->m_runtime.stack.bytes + lhsOffset, this->m_runtime.stack.bytes + rhsOffset,
1360 directive.get_size()) == 0) {
1361 this->m_runtime.stack.push<U8>(static_cast<U8>(FW_SERIALIZE_TRUE_VALUE));
1362 } else {
1363 this->m_runtime.stack.push<U8>(static_cast<U8>(FW_SERIALIZE_FALSE_VALUE));
1364 }
1365 return Signal::stmtResponse_success;
1366 }
1367
1368 Signal FpySequencer::stackCmd_directiveHandler(const FpySequencer_StackCmdDirective& directive, DirectiveError& error) {
1369 // Overflow-safe check: need argsSize + sizeof(FwOpcodeType) bytes
1370 // Check stack.size >= sizeof(FwOpcodeType) first, then stack.size - sizeof(FwOpcodeType) >= argsSize
1371 if (this->m_runtime.stack.size < sizeof(FwOpcodeType) ||
1372 this->m_runtime.stack.size - sizeof(FwOpcodeType) < directive.get_argsSize()) {
1373 error = DirectiveError::STACK_UNDERFLOW;
1374 return Signal::stmtResponse_failure;
1375 }
1376
1377 // pop the opcode of the cmd off the stack
1378 // note this means that, unlike the actual byte array that the dispatcher gets,
1379 // these cmds have opcode after the argument buffer
1380 FwOpcodeType opcode = this->m_runtime.stack.pop<FwOpcodeType>();
1381 U64 argBufOffset = this->m_runtime.stack.size - directive.get_argsSize();
1382
1383 // update the opcode of the cmd we will await
1384 this->m_runtime.currentCmdOpcode = opcode;
1385
1386 // also pop the args off the stack
1387 this->m_runtime.stack.size -= directive.get_argsSize();
1388
1389 // the cmd response code will be pushed to the stack when it comes back, so make sure
1390 // there is room for it now, before the cmd is dispatched. popping the opcode above
1391 // frees some room, but FwOpcodeType is configurable so it may not be enough
1392 if (Fpy::MAX_STACK_SIZE - sizeof(Fw::CmdResponse::SerialType) < this->m_runtime.stack.size) {
1393 error = DirectiveError::STACK_OVERFLOW;
1394 return Signal::stmtResponse_failure;
1395 }
1396
1397 if (this->sendCmd(opcode, this->m_runtime.stack.bytes + argBufOffset, directive.get_argsSize()) ==
1398 Fw::Success::FAILURE) {
1399 return Signal::stmtResponse_failure;
1400 } else {
1401 // now tell the SM to wait some more until we get the cmd response back
1402 // if we've already got the response back this should be harmless
1403 return Signal::stmtResponse_keepWaiting;
1404 }
1405
1406 return Signal::stmtResponse_success;
1407 }
1408
1409 Signal FpySequencer::pushTime_directiveHandler(const FpySequencer_PushTimeDirective& directive, DirectiveError& error) {
1410 if (Fpy::MAX_STACK_SIZE - Fw::Time::SERIALIZED_SIZE < this->m_runtime.stack.size) {
1411 error = DirectiveError::STACK_OVERFLOW;
1412 return Signal::stmtResponse_failure;
1413 }
1414
1415 Fw::Time currentTime = this->getTime();
1416
1417 U8 currentTimeBuf[Fw::Time::SERIALIZED_SIZE] = {};
1418 Fw::ExternalSerializeBuffer timeEsb(currentTimeBuf, Fw::Time::SERIALIZED_SIZE);
1419 Fw::SerializeStatus stat = timeEsb.serializeFrom(currentTime);
1420
1421 // coding error if this failed, we should have enough space
1422 FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
1423
1424 // push time to end of stack
1425 this->m_runtime.stack.push(timeEsb.getBuffAddr(), static_cast<Fpy::StackSizeType>(timeEsb.getSize()));
1426 return Signal::stmtResponse_success;
1427 }
1428
1429 Signal FpySequencer::setSeed_directiveHandler(const FpySequencer_SetSeedDirective& directive, DirectiveError& error) {
1430 if (this->m_runtime.stack.size < sizeof(U32)) {
1431 error = DirectiveError::STACK_UNDERFLOW;
1432 return Signal::stmtResponse_failure;
1433 }
1434
1435 U32 seed = this->m_runtime.stack.pop<U32>();
1436 this->m_runtime.rng.seed(seed);
1437 this->m_runtime.rngSeeded = true;
1438 return Signal::stmtResponse_success;
1439 }
1440
1441 Signal FpySequencer::pushRand_directiveHandler(const FpySequencer_PushRandDirective& directive, DirectiveError& error) {
1442 if (Fpy::MAX_STACK_SIZE - sizeof(U32) < this->m_runtime.stack.size) {
1443 error = DirectiveError::STACK_OVERFLOW;
1444 return Signal::stmtResponse_failure;
1445 }
1446
1447 if (!this->m_runtime.rngSeeded) {
1448 Fw::Time currentTime = this->getTime();
1449 std::seed_seq seedSeq{static_cast<U32>(currentTime.getTimeBase()), static_cast<U32>(currentTime.getContext()),
1450 currentTime.getSeconds(), currentTime.getUSeconds()};
1451 this->m_runtime.rng.seed(seedSeq);
1452 this->m_runtime.rngSeeded = true;
1453 }
1454
1455 U32 randVal = static_cast<U32>(this->m_runtime.rng());
1456 this->m_runtime.stack.push(randVal);
1457 return Signal::stmtResponse_success;
1458 }
1459
1460 Signal FpySequencer::getField_directiveHandler(const FpySequencer_GetFieldDirective& directive, DirectiveError& error) {
1461 // Need sizeof(StackSizeType) for the offset AND parentSize for the parent data
1462 // Check we have enough for the offset first
1463 if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType)) {
1464 error = DirectiveError::STACK_UNDERFLOW;
1465 return Signal::stmtResponse_failure;
1466 }
1467 // After popping the offset, we need at least parentSize bytes remaining
1468 if (this->m_runtime.stack.size - sizeof(Fpy::StackSizeType) < directive.get_parentSize()) {
1469 error = DirectiveError::STACK_UNDERFLOW;
1470 return Signal::stmtResponse_failure;
1471 }
1472
1473 Fpy::StackSizeType offset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1474
1475 // Overflow-safe check: offset + memberSize > parentSize
1476 // Rewritten as: check offset <= parentSize first, then memberSize > parentSize - offset
1477 if (offset > directive.get_parentSize() || directive.get_memberSize() > directive.get_parentSize() - offset) {
1478 // i think it's somewhat ambiguous whether this is a stack access out of bounds
1479 // but there isn't really an error code that better reflects this, and i guess
1480 // it's technically true
1481 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1482 return Signal::stmtResponse_failure;
1483 }
1484
1485 // the resulting bytes should move to the start of the parent array
1486
1487 // Calculate the offset of the parent start in the stack
1488 Fpy::StackSizeType parentStartOffset = this->m_runtime.stack.size - directive.get_parentSize();
1489 // Overflow-safe: parentStartOffset + offset cannot overflow since offset <= parentSize
1490 // and parentStartOffset + parentSize == stack.size (which is bounded)
1491 this->m_runtime.stack.move(parentStartOffset, parentStartOffset + offset, directive.get_memberSize());
1492 // adjust stack size by the diff between the member and the parent
1493 this->m_runtime.stack.size -= (directive.get_parentSize() - directive.get_memberSize());
1494 return Signal::stmtResponse_success;
1495 }
1496
1497 Signal FpySequencer::peek_directiveHandler(const FpySequencer_PeekDirective& directive, DirectiveError& error) {
1498 // must have at least two StackSizeType on stack
1499 if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) * 2) {
1500 error = DirectiveError::STACK_UNDERFLOW;
1501 return Signal::stmtResponse_failure;
1502 }
1503
1504 Fpy::StackSizeType offset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1505 Fpy::StackSizeType byteCount = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1506
1507 // Check offset doesn't exceed stack size (after both pops)
1508 if (offset > this->m_runtime.stack.size) {
1509 // would access past the bottom of the stack
1510 // note we allow the equals case because the byteCount might be 0
1511 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1512 return Signal::stmtResponse_failure;
1513 }
1514 if (byteCount > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1515 // we would overflow the stack if we pushed this many bytes to it
1516 error = DirectiveError::STACK_OVERFLOW;
1517 return Signal::stmtResponse_failure;
1518 }
1519 // Overflow-safe check: byteCount + offset > stack.size
1520 // Rewritten as: check offset <= stack.size (done above), then byteCount > stack.size - offset
1521 if (byteCount > this->m_runtime.stack.size - offset) {
1522 // would access past the bottom of the stack
1523 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1524 return Signal::stmtResponse_failure;
1525 }
1526 // start copying from the lowest byte of the src array
1527 U8* src = this->m_runtime.stack.top() - offset - byteCount;
1528 this->m_runtime.stack.push(src, byteCount);
1529 return Signal::stmtResponse_success;
1530 }
1531
1532 Signal FpySequencer::storeRel_directiveHandler(const FpySequencer_StoreRelDirective& directive, DirectiveError& error) {
1533 // Need enough bytes for the value and the offset (SignedStackSizeType = 4 bytes)
1534 // Overflow-safe: check stack.size >= sizeof(SignedStackSizeType) first, then stack.size -
1535 // sizeof(SignedStackSizeType) >= size
1536 if (this->m_runtime.stack.size < sizeof(Fpy::SignedStackSizeType) ||
1537 this->m_runtime.stack.size - sizeof(Fpy::SignedStackSizeType) < directive.get_size()) {
1538 error = DirectiveError::STACK_UNDERFLOW;
1539 return Signal::stmtResponse_failure;
1540 }
1541
1542 // Pop the signed offset from the stack
1543 Fpy::SignedStackSizeType lvarOffset = this->m_runtime.stack.pop<Fpy::SignedStackSizeType>();
1544
1545 I64 addr = static_cast<I64>(this->m_runtime.stack.currentFrameStart) + lvarOffset;
1546 if (addr < 0 || addr > Fpy::MAX_STACK_SIZE) {
1547 error = DirectiveError::STACK_ACCESS_OUT_OF_BOUNDS;
1548 return Signal::stmtResponse_failure;
1549 }
1550 return this->storeHelper(static_cast<Fpy::StackSizeType>(addr), directive.get_size(), error);
1551 }
1552
1553 Signal FpySequencer::call_directiveHandler(const FpySequencer_CallDirective& directive, DirectiveError& error) {
1554 // Need at least 4 bytes for the target address
1555 if (this->m_runtime.stack.size < sizeof(U32)) {
1556 error = DirectiveError::STACK_UNDERFLOW;
1557 return Signal::stmtResponse_failure;
1558 }
1559
1560 // Pop the target directive index from the stack
1561 U32 target = this->m_runtime.stack.pop<U32>();
1562
1563 // Check if we have space to push return address and saved frame pointer (8 bytes total)
1564 if (this->m_runtime.stack.size + sizeof(Fpy::StackSizeType) + sizeof(U32) > Fpy::MAX_STACK_SIZE) {
1565 error = DirectiveError::STACK_OVERFLOW;
1566 return Signal::stmtResponse_failure;
1567 }
1568
1569 // Check target is within bounds (will also be checked at execution time)
1570 if (target > m_sequenceObj.get_header().get_statementCount()) {
1571 error = DirectiveError::STMT_OUT_OF_BOUNDS;
1572 return Signal::stmtResponse_failure;
1573 }
1574
1575 // Save the return address (next instruction after CALL)
1576 U32 returnAddr = this->m_runtime.nextStatementIndex;
1577
1578 // Set the next instruction to the target
1579 this->m_runtime.nextStatementIndex = target;
1580
1581 // Push the return address to the stack
1582 this->m_runtime.stack.push<U32>(returnAddr);
1583
1584 // Push the current frame pointer to the stack
1585 this->m_runtime.stack.push<Fpy::StackSizeType>(this->m_runtime.stack.currentFrameStart);
1586
1587 // Set the new frame pointer to the current top of stack
1588 this->m_runtime.stack.currentFrameStart = this->m_runtime.stack.size;
1589
1590 return Signal::stmtResponse_success;
1591 }
1592
1593 Signal FpySequencer::return_directiveHandler(const FpySequencer_ReturnDirective& directive, DirectiveError& error) {
1594 Fpy::StackSizeType returnValSize = directive.get_returnValSize();
1595 Fpy::StackSizeType callArgsSize = directive.get_callArgsSize();
1596
1597 // Check we have enough bytes for the return value
1598 if (this->m_runtime.stack.size < returnValSize) {
1599 error = DirectiveError::STACK_UNDERFLOW;
1600 return Signal::stmtResponse_failure;
1601 }
1602
1603 // Remember where the return value lives; it is moved down the stack below rather than copied
1604 // through a local buffer, which at Fpy::MAX_STACK_SIZE would not fit a typical task stack
1605 const Fpy::StackSizeType returnValOffset = this->m_runtime.stack.size - returnValSize;
1606
1607 // Truncate the stack to stack_frame_start (discard all local variables)
1608 if (this->m_runtime.stack.currentFrameStart > this->m_runtime.stack.size) {
1609 error = DirectiveError::FRAME_START_OUT_OF_BOUNDS;
1610 return Signal::stmtResponse_failure;
1611 }
1612 this->m_runtime.stack.size = this->m_runtime.stack.currentFrameStart;
1613
1614 // Check we have enough bytes for saved frame pointer and return address
1615 if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) + sizeof(U32)) {
1616 error = DirectiveError::STACK_UNDERFLOW;
1617 return Signal::stmtResponse_failure;
1618 }
1619
1620 // Pop the saved frame pointer
1621 Fpy::StackSizeType savedFramePtr = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1622
1623 // Pop the return address
1624 U32 returnAddr = this->m_runtime.stack.pop<U32>();
1625
1626 // Restore the frame pointer
1627 if (savedFramePtr > this->m_runtime.stack.size) {
1628 error = DirectiveError::FRAME_START_OUT_OF_BOUNDS;
1629 return Signal::stmtResponse_failure;
1630 }
1631 this->m_runtime.stack.currentFrameStart = savedFramePtr;
1632
1633 // Validate the return address is within bounds
1634 if (returnAddr > m_sequenceObj.get_header().get_statementCount()) {
1635 error = DirectiveError::STMT_OUT_OF_BOUNDS;
1636 return Signal::stmtResponse_failure;
1637 }
1638
1639 // Set the next instruction to the return address
1640 this->m_runtime.nextStatementIndex = returnAddr;
1641
1642 // Check that we have enough bytes for the call arguments
1643 if (this->m_runtime.stack.size < callArgsSize) {
1644 error = DirectiveError::STACK_UNDERFLOW;
1645 return Signal::stmtResponse_failure;
1646 }
1647 // Discard the function arguments
1648 this->m_runtime.stack.size -= callArgsSize;
1649
1650 // Push the return value
1651 if (returnValSize > Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size) {
1652 error = DirectiveError::STACK_OVERFLOW;
1653 return Signal::stmtResponse_failure;
1654 }
1655 if (returnValSize > 0) {
1656 // Not Stack::move: the source region sits above the truncated stack size, which
1657 // Stack::move rejects. Both regions were bounds-checked above against MAX_STACK_SIZE.
1658 (void)memmove(this->m_runtime.stack.top(), &this->m_runtime.stack.bytes[returnValOffset], returnValSize);
1659 this->m_runtime.stack.size += returnValSize;
1660 }
1661
1662 return Signal::stmtResponse_success;
1663 }
1664
1665 Signal FpySequencer::loadAbs_directiveHandler(const FpySequencer_LoadAbsDirective& directive, DirectiveError& error) {
1666 return this->loadHelper(directive.get_globalOffset(), directive.get_size(), error);
1667 }
1668
1669 Signal FpySequencer::storeAbs_directiveHandler(const FpySequencer_StoreAbsDirective& directive, DirectiveError& error) {
1670 Fpy::StackSizeType size = directive.get_size();
1671
1672 // Need enough bytes for the value and the offset
1673 // Overflow-safe: check stack.size >= sizeof(StackSizeType) first, then stack.size - sizeof >= size
1674 if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType) ||
1675 this->m_runtime.stack.size - sizeof(Fpy::StackSizeType) < size) {
1676 error = DirectiveError::STACK_UNDERFLOW;
1677 return Signal::stmtResponse_failure;
1678 }
1679
1680 // Pop the global offset from the stack
1681 Fpy::StackSizeType globalOffset = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1682
1683 return this->storeHelper(globalOffset, size, error);
1684 }
1685
1686 Signal FpySequencer::storeAbsConstOffset_directiveHandler(const FpySequencer_StoreAbsConstOffsetDirective& directive,
1687 DirectiveError& error) {
1688 return this->storeHelper(directive.get_globalOffset(), directive.get_size(), error);
1689 }
1690
1691 Signal FpySequencer::popEvent_directiveHandler(const FpySequencer_PopEventDirective& directive, DirectiveError& error) {
1692 // Pop messageSize from the stack
1693 if (this->m_runtime.stack.size < sizeof(Fpy::StackSizeType)) {
1694 error = DirectiveError::STACK_UNDERFLOW;
1695 return Signal::stmtResponse_failure;
1696 }
1697 Fpy::StackSizeType messageSize = this->m_runtime.stack.pop<Fpy::StackSizeType>();
1698
1699 const Fpy::StackSizeType severitySize = static_cast<Fpy::StackSizeType>(sizeof(Fw::LogSeverity::SerialType));
1700
1701 // Need message_size bytes + sizeof(LogSeverity serial type) for severity
1702 if (this->m_runtime.stack.size < severitySize || this->m_runtime.stack.size - severitySize < messageSize) {
1703 error = DirectiveError::STACK_UNDERFLOW;
1704 return Signal::stmtResponse_failure;
1705 }
1706
1707 // Pop message bytes first
1708 U8 messageBuf[FW_LOG_STRING_MAX_SIZE] = {};
1709 // don't read in more than (log string size) - 1 bytes
1710 Fpy::StackSizeType clampedSize = std::min(messageSize, static_cast<Fpy::StackSizeType>(FW_LOG_STRING_MAX_SIZE - 1));
1711 // If message is larger than buffer, discard the excess bytes first (from top of stack, which is the end of the
1712 // message)
1713 if (messageSize > clampedSize) {
1714 Fpy::StackSizeType excess = messageSize - clampedSize;
1715 FW_ASSERT(this->m_runtime.stack.size >= excess, static_cast<FwAssertArgType>(this->m_runtime.stack.size),
1716 static_cast<FwAssertArgType>(excess));
1717 this->m_runtime.stack.size -= excess;
1718 }
1719 this->m_runtime.stack.pop(messageBuf, clampedSize);
1720 messageBuf[clampedSize] = '\0';
1721
1722 // Pop severity
1723 Fw::LogSeverity::SerialType severity = this->m_runtime.stack.pop<Fw::LogSeverity::SerialType>();
1724
1725 // Construct the message string
1726 Fw::String messageStr(reinterpret_cast<const char*>(messageBuf));
1727
1728 // Emit the appropriate event based on severity
1729 switch (severity) {
1730 case Fw::LogSeverity::FATAL:
1731 this->log_FATAL_LogFatal(this->m_sequenceFilePath, messageStr);
1732 break;
1733 case Fw::LogSeverity::WARNING_HI:
1734 this->log_WARNING_HI_LogWarningHi(this->m_sequenceFilePath, messageStr);
1735 break;
1736 case Fw::LogSeverity::WARNING_LO:
1737 this->log_WARNING_LO_LogWarningLo(this->m_sequenceFilePath, messageStr);
1738 break;
1739 case Fw::LogSeverity::COMMAND:
1740 this->log_COMMAND_LogCommand(this->m_sequenceFilePath, messageStr);
1741 break;
1742 case Fw::LogSeverity::ACTIVITY_HI:
1743 this->log_ACTIVITY_HI_LogActivityHi(this->m_sequenceFilePath, messageStr);
1744 break;
1745 case Fw::LogSeverity::ACTIVITY_LO:
1746 this->log_ACTIVITY_LO_LogActivityLo(this->m_sequenceFilePath, messageStr);
1747 break;
1748 case Fw::LogSeverity::DIAGNOSTIC:
1749 this->log_DIAGNOSTIC_LogDiagnostic(this->m_sequenceFilePath, messageStr);
1750 break;
1751 default:
1752 error = DirectiveError::INVALID_ARG;
1753 return Signal::stmtResponse_failure;
1754 }
1755
1756 return Signal::stmtResponse_success;
1757 }
1758
1759 Signal FpySequencer::popSerializable_directiveHandler(const FpySequencer_PopSerializableDirective& directive,
1760 DirectiveError& error) {
1761 // No size assertion here: an oversized size is untrusted sequence content and is rejected by
1762 // the stack check below, since the stack can never hold more than Fpy::MAX_STACK_SIZE bytes
1763
1764 // Validate port index is in range (using enum constant value)
1765 constexpr FwIndexType MAX_PORTS = static_cast<FwIndexType>(Svc::Fpy::SerialPortIndex::MAX_SERIAL_PORTS);
1766 const FwIndexType portIndex = directive.get_portIndex();
1767
1768 // Check for negative port index or out of bounds
1769 if (portIndex < 0 || portIndex >= MAX_PORTS) {
1770 error = DirectiveError::SERIAL_PORT_INVALID_INDEX;
1771 return Signal::stmtResponse_failure;
1772 }
1773
1774 // Check port is connected
1775 if (!this->isConnected_serialOut_OutputPort(portIndex)) {
1776 error = DirectiveError::SERIAL_PORT_NOT_CONNECTED;
1777 return Signal::stmtResponse_failure;
1778 }
1779
1780 // Validate data size on stack
1781 if (this->m_runtime.stack.size < directive.get_size()) {
1782 error = DirectiveError::STACK_UNDERFLOW;
1783 return Signal::stmtResponse_failure;
1784 }
1785
1786 // Create external buffer referencing stack data (no copy)
1787 U8* dataPtr = this->m_runtime.stack.top() - directive.get_size();
1788 Fw::ExternalSerializeBuffer buf(dataPtr, directive.get_size());
1789
1790 // Set buffer length and verify success
1791 Fw::SerializeStatus stat = buf.setBuffLen(directive.get_size());
1792 FW_ASSERT(stat == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(stat));
1793
1794 // Call output port and verify serialization succeeds
1795 Fw::SerializeStatus portStatus = this->serialOut_out(portIndex, buf);
1796 FW_ASSERT(portStatus == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(portStatus));
1797
1798 // Pop data from stack
1799 this->m_runtime.stack.size -= directive.get_size();
1800
1801 return Signal::stmtResponse_success;
1802 }
1803
1804 } // namespace Svc
1805