Added pop() feature#96
Conversation
| ) | ||
| return | ||
|
|
||
| # Special validation for pop after confirming args are const |
There was a problem hiding this comment.
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.
| ), | ||
| # Serial pop builtin — compile-time port index, any constant-size value | ||
| # The generate function is a placeholder; codegen.py handles this specially | ||
| "pop": BuiltinFuncSymbol( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Change Description
Adds the
pop(port, value)builtin to the Fpy compiler and thePOP_SERIALIZABLEdirective it compiles to.poppushesvalueonto the stack and emits aPOP_SERIALIZABLEdirective that pops those bytes back off and sends them out the sequencer'sserialOut[port]port.Language / compiler:
macros.py: registers thepopbuiltin.portis a required compile-time constant;valueaccepts any constant-size type.semantics.py: validates the arguments —portmust be an integer orSvc.Fpy.SerialPortIndexenum constant in range[0, MAX_SERIAL_PORTS), andvaluemust 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-casespopto push the value, resolve the port index (int or enum), and emitPopSerializableDirective(portIndex, size)withsize= the value type's serialized size.Bytecode:
bytecode/directives.py: addsDirectiveId.POP_SERIALIZABLE = 78and thePopSerializableDirectivedataclass (portIndex: FwIndexType,size: StackSizeType). Also introduces the configurableFwIndexType(I16) alias, loaded from the dictionary.bytecode/grammar.lark: adds thepop_serializableassembler mnemonic (also adds the previously-missingpop_eventmnemonic).model.py: addshandle_pop_serializable(popssizebytes, underflow-checked) and theSERIAL_PORT_NOT_CONNECTED/SERIAL_PORT_INVALID_INDEXerror codes. Also fixeshandle_push_valto 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 thepopbuiltin.bytecode/SPEC.md: documents thePOP_SERIALIZABLEdirective (preconditions, semantics, error conditions, args).Test fixtures:
test/fpy/RefTopologyDictionary.json: adds theSvc.Fpy.SerialPortIndexenum used by the feature;test_dictionary.pytype count bumped 95 → 96 accordingly.Rationale
Sequences need a way to serialize a value and push it out of the sequencer to another component.
popprovides 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.1343 passed, 3 skipped, 1 xpassed.Suggested review focus: the argument validation in
semantics.pyand the port/size resolution incodegen.py.