Skip to content

Added pop() feature#96

Open
Lex-ari wants to merge 1 commit into
fprime-community:mainfrom
FireflySpace:Lex-ari-pop-directive
Open

Added pop() feature#96
Lex-ari wants to merge 1 commit into
fprime-community:mainfrom
FireflySpace:Lex-ari-pop-directive

Conversation

@Lex-ari

@Lex-ari Lex-ari commented Jul 9, 2026

Copy link
Copy Markdown
Member
Related Issue(s) nasa/fprime#5327
Has Unit Tests (y/n) y
Documentation Included (y/n) y

Change Description

Adds the pop(port, value) builtin to the Fpy compiler and the POP_SERIALIZABLE directive it compiles to. pop pushes value onto the stack and emits a POP_SERIALIZABLE directive that pops those bytes back off and sends them out the sequencer's serialOut[port] port.

Language / compiler:

  • macros.py: registers the pop builtin. port is a required compile-time constant; value accepts any constant-size type.
  • semantics.py: validates the arguments — port must be an integer or Svc.Fpy.SerialPortIndex enum constant in range [0, MAX_SERIAL_PORTS), and value must be a concrete, constant-size type. Bare numeric literals, anonymous struct/array literals, ranges, void expressions, and string-bearing types are rejected with clear compile errors.
  • codegen.py: special-cases pop to push the value, resolve the port index (int or enum), and emit PopSerializableDirective(portIndex, size) with size = the value type's serialized size.

Bytecode:

  • bytecode/directives.py: adds DirectiveId.POP_SERIALIZABLE = 78 and the PopSerializableDirective dataclass (portIndex: FwIndexType, size: StackSizeType). Also introduces the configurable FwIndexType (I16) alias, loaded from the dictionary.
  • bytecode/grammar.lark: adds the pop_serializable assembler mnemonic (also adds the previously-missing pop_event mnemonic).
  • model.py: adds handle_pop_serializable (pops size bytes, underflow-checked) and the SERIAL_PORT_NOT_CONNECTED / SERIAL_PORT_INVALID_INDEX error codes. Also fixes handle_push_val to account for the actual pushed size (len(dir.val)) instead of a hardcoded 8 bytes, so large pushes are correctly detected as overflow.

Docs:

  • SPEC.md: documents the pop builtin.
  • bytecode/SPEC.md: documents the POP_SERIALIZABLE directive (preconditions, semantics, error conditions, args).

Test fixtures:

  • test/fpy/RefTopologyDictionary.json: adds the Svc.Fpy.SerialPortIndex enum used by the feature; test_dictionary.py type count bumped 95 → 96 accordingly.

Rationale

Sequences need a way to serialize a value and push it out of the sequencer to another component. pop provides this and works for both primitive and complex dictionary types (structs, arrays, enums, and nested combinations of them), with the value's size computed at compile time.

Testing/Review Recommendations

  • test/fpy/test_pop.py: covers size computation, port validation (bounds, non-constant, wrong type), primitive and complex-type support, and constant-size rejection.
  • test/fpy/golden/pop.fpy + pop.fpybc: compile golden test exercising several types and port indices.
  • Full suite passes: 1343 passed, 3 skipped, 1 xpassed.

Suggested review focus: the argument validation in semantics.py and the port/size resolution in codegen.py.

Comment thread src/fpy/semantics.py
)
return

# Special validation for pop after confirming args are const

@zimri-leisher zimri-leisher Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hacky. What we actually want is for the normal semantic machinery of the compiler to handle this builtin for us, without having to special case it. Basically for any feature ever this should always be the case. But here, the existing semantic machinery can't express what we want. So the question is what things can we add to the language as a whole that can enable this builtin to work properly, without ever having a special case for it.

We already have the ability to define builtin functions, but the problem here is that we can't express the type of the value argument, as it should accept any type. So the obvious solution is to just add an Any type to Fpy. Then we can declare the builtin like pop(FwIndexType, Any) and be done with it.

However, there is one problem with this. We don't actually want to accept any time, we just want to accept any type which is serializable (which has a binary form). Some types, like the INTEGER or FLOAT type, have no binary representation (they only exist at compile time and cannot be written into the output file, instead they are coerced at compile time to a serializable type like I64 or F64). So instead what we actually have to do, I think, is define a Serializable type. It should not be accessible to the user, just an internal compiler thing.

Actually, even more specifically, it must be a Serializable type whose size we know at compile time. For instance, you could not push the value of a string telemetry channel out this port. So for now, let's call this a SizedType, meaning A) it has a binary representation and B) it has a statically known size of that binary representation. Anything except non-constant strings should be rejected. So make sure you can still output "asdf" for instance.

Comment thread src/fpy/macros.py
),
# Serial pop builtin — compile-time port index, any constant-size value
# The generate function is a placeholder; codegen.py handles this specially
"pop": BuiltinFuncSymbol(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think pop is a clear name for what this does. Remember that the user of Fpy has no idea that there is a stack, that is an implementation detail. And pop is a stack term.

I would suggest calling this write_to_port or output_port or something.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just taking in from what nasa#5327 had, which was pop(). I also considered serial_pop(), but not sure if we wanted to go that route

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is okay for the bytecode to reference the stack, but the high level language shouldn't really know that it exists. In LLVM it won't exist anyways, so pop just won't make any sense. Plus, when I hear pop, I think: what are we popping, and what are we going to do with it? I think write_to_port or output_on_port is much more clear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants