Skip to content

Structs for Actions and Ports

Edward A. Lee edited this page May 22, 2023 · 4 revisions

This page describes some basic structs used in the C runtime for actions and ports. The diagrams below use the strategy of Inheritance in C.

The overall architecture is that each port and action has a custom struct type generated for it with a unique type name based on a hashcode (these unique codes are represented by NNNN in the diagrams below). When, within a reaction body, the code refers to an input or action by name, the variable's type is one of these custom types. Because these types are available in the generated header files and visible within the reaction bodies, we call them here "user-facing structs." They first few fields of the user-facing structs are common to all ports and actions; the last field is the value, and this field is specific to the particular port or action. To access the common fields, a collection of non-user-facing struct types are defined that are guaranteed of having exactly the same layout as the user-facing structs for the fields they define. Hence, given a pointer to an instance of NNNN_portname_t, the pointer can be cast to token_type_t*, token_template_t*, or lf_port_base_t*.

Consider a reaction like this:

reaction(in) -> out {= ... =}

Within the body of the reaction, in will have type idNNNN_in_t* where id is derived from the name of the reactor and NNNN is a hashcode. The in pointer will actually point to the output port of the reactor that is sending data to this reactor. Thus, when a reaction reads in->value, it is directly reading the value field of the corresponding output port (unless the input port is declared mutable, in which case this may point to a copy).

Each input port, action, and timer also has an instance of trigger_t on the self struct of the parent reactor. The trigger_t struct contains the information needed to trigger reactions when an event is present on this trigger.

User Facing Structs

When a reaction body refers to a port or action by name, it sees structs whose type follow the following pattern:

classDiagram
class NNNN_portname_t {
    token_type_t type
    lf_token_t* token
    size_t length
    bool is_present
    lf_port_internal_t _base
    T value
}

NNNN_portname_t <|-- lf_port_internal_t
class lf_port_internal_t {
    lf_sparse_io_record_t* sparse_record
    int destination_channel
    int num_destinations
    self_base_t* source_reactor
}

class NNNN_actionname_t {
    token_type_t type
    lf_token_t* token
    size_t length
    bool is_present
    lf_action_internal_t _base
    self_base_t* parent
    bool has_value
    T value
}

NNNN_actionname_t <|-- lf_action_internal_t
class lf_action_internal_t {
   trigger_t* trigger
}

NNNNN_self_t <|-- self_base_t
class NNNNN_self_t {
    self_base_t base
    instances of ports
    instances of actions
    instances of timers
}
Loading

The internal structs contain information that a reaction body will not normally use and cannot safely use (the data structure may change in the future).

Non-User-Facing Structs

The following structs are used internally in the C runtime and are required to have the same layout as the structs above:

classDiagram
class token_type_t {
    size_t element_size
    void (*destructor) (void* value)
    void* (*copy_constructor) (void* value)
}

token_template_t <|-- token_type_t
class token_template_t {
    token_type_t type
    lf_token_t* token
    size_t length
}

lf_port_base_t <|-- token_template_t
class lf_port_base_t {
    token_template_t tmplt
    bool is_present
    lf_sparse_io_record_t* sparse_record
    int destination_channel
    int num_destinations
    self_base_t* source_reactor
}

lf_action_base_t <|-- token_template_t
class lf_action_base_t {
    token_template_t tmplt
    bool is_present
    trigger_t* trigger
    self_base_t* parent
    bool has_value
}

lf_action_base_t --> trigger_t
Loading

Supporting Structs

classDiagram

class self_base_t {
    allocation_record_t* allocations
    struct reaction_t* executing_reaction
    void* reactor_mutex [if LF_THREADED]
    reactor_mode_state_t _lf__mode_state [if MODAL_REACTORS]
}
NNNN_actionname_t --> self_base_t : parent

class lf_token_t {
    void* value
    size_t length
    token_type_t* type
    size_t ref_count
    struct lf_token_t* next
}
NNNN_portname_t --> lf_token_t
NNNN_portname_t --> self_base_t : source_reactor
lf_port_base_t --> lf_token_t
lf_port_base_t --> self_base_t : source_reactor
NNNN_actionname_t --> lf_token_t

class trigger_t {
    reaction_t** reactions
    int number_of_reactions
    bool is_timer
    interval_t offset
    interval_t period
    bool is_physical
    event_t* last
    lf_spacing_policy_t policy
    port_status_t status
    reactor_mode_t* mode
    tag_t last_known_status_tag [if FEDERATED]
    bool is_a_control_reaction_waiting [if FEDERATED]
    tag_t intended_tag [if FEDERATED]
    instant_t physical_time_of_arrival [if FEDERATED]
}

lf_action_base_t --> trigger_t
lf_action_base_t --> self_base_t : parent
lf_action_base_t --> lf_token_t
NNNN_actionname_t --> trigger_t
Loading