Code Generation for RCC Workers
In the descriptions below, italicized names in angle brackets are names specific to the worker, as found in the worker’s OCS and OWD XML files.
The worker’s OCS and OWD are used to generate two code files, the header and the skeleton. The header file, which should not be edited, and is deleted by ocpidev clean worker, contains type definitions and declarations customized for the worker. This file is named <workername>_Worker.h in the C language or <workername>-worker.hh in the C++ language, with the worker name is its original case from the OWD. This generated header file is placed in the gen subdirectory of the worker’s directory.
The skeleton file is a small file that is generated as the basis for writing the worker’s execution code (e.g. the run method implementation). It relies heavily on the generated header file and is as small as possible to avoid requiring code changes when the OCS or OWD is changed. The skeleton is generated in the gen subdirectory, with the name <workername>-skel.c (C language) or <workername>-skel.cc (C++ language). It is also copied into the worker’s directory as the worker’s primary source file: <workername>.c or <workername>.cc. This copy is what should be edited to add the worker’s functional code. This copy is only made if the worker’s code file does not exist or has not been edited since it was previously copied from gen/.
When the OCS, OPS or OWD XML files are changed, the header and skeleton are regenerated, but this does not affect the primary source file. Because most of the code is generated in the header, and only a little in the skeleton, it is rare that significant code changes are required in the primary source file based on XML changes.
The gen/<workername>-skel.{c,cc} skeleton file should not be edited, but left for reference to see exactly what the code generator produced.
Namespace Management
To avoid name space collisions, some rules are used by the code generator when creating the header and skeleton files.
In the C++ language, generated data types in the header are placed in a namespace whose name is: <Worker>WorkerTypes. In the actual worker source file, this namespace is normally imported, via the “using namespace” directive. The actual worker derived class is a class in the global namespace whose name is <Worker>Worker. In both cases, <Worker> is a capitalized version of the worker’s name. The dispatch entry point, which is necessarily a C external symbol, has the worker’s name as its external symbol, with the prefix ocpi_.
In the C language, the worker’s dispatch table is given the external symbol name of the worker, and all methods are declared static. All data types use the capitalized worker name as a prefix and all constants and macros use the upper cased worker name followed by an underscore as a prefix.
A summary of the RCC authoring model name space management is:
The C++ namespace
OCPIis reserved.The prefix
ocpi_is reserved in the C external linkage namespace.C workers use their name as an external symbol.
C++ workers use two global namespaces that use the
<Worker>prefix.C and C++ workers use the PARAM_ and OCPI_ prefixes for preprocessor symbols.
C workers use the
<Worker>prefix for type names and<WORKER>_for constants.
Generated Data Types
Various data types are generated in the header file for the worker. They are :
Port name enumeration
Property data structure
Opcode enumeration for each protocol used
Opcode enumeration for each port (C only)
Message structures (C only)
Worker base class (C++ only)
The Enumeration Constants for the Worker’s Ports
An enumeration type is generated in the header file defining constants for the ordinal of each port, in the form <WORKER>_<PORT>, in all upper case. These ordinals can be used when creating port masks for run conditions, or, for C workers, indexing into the port array in the RCCWorker structure.
The Properties Structure Type
This is the generated structure definition that reflects the properties declared for the worker in its OCS and OWD XML files. Each member of this structure has the data type corresponding to the property’s description in the OWD and OCS XML files. Members for properties that are not declared volatile in XML are const in this structure, indicating that the worker is not expected to change their values.
The standard name of the struct type (typedef name in C) is XyzProperties where “Xyz” is the capitalized worker implementation name.
Properties which are sequences are generated as a structure containing a uint32_t member whose name is length, holding the number of elements in the sequence. Padding may be added before and after the length member to achieve the required alignment of this length field as well as the sequence data following it. The member name of the structure is the property name. The members of the structure are length and data. The data member is a C array whose length is the SequenceLength attribute from the Property element in the XML. Remember that for properties (as opposed to protocols), sequences and strings always have a maximum size (i.e. be bounded).
Struct properties have structure tags with the same name as the property name, preceded by the worker name.
The correspondence between property types in OCS and OWD files and the C/C++ language data types are in the following table:
OCS/OWD Property Type |
C/C++ Data Type |
Comments |
|
|
Must be 8 bits, consistent across languages. |
|
|
Must be signed type, and basis for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Null terminated string |
|
|
|
|
|
Must be IEEE 32 bit float |
|
|
Must be IEEE 64 bit double |
The names of the non-integer types for scalar properties are the OCS-defined type names capitalized and prefixed with RCC, specifically:
RCCBoolean, RCCChar, RCCFloat, RCCDouble
Properties that are string properties are RCCChar arrays whose size is one more than the declared StringLength attribute of that string property in the Property element, and the values are null terminated strings.
Structures for Message Payloads — C Language Only
For each port, a union type is defined for all possible messages at that port. Members of the union type are generated structures for each possible message in that protocol. The name of the union type is <Port>Operations, with port name capitalized.
For each of the message types specified in the protocol, a structure is defined. The structure’s type name is the capitalized name of the operation in the protocol, and whose member name is the lower-cased name. Arguments to the operation in the protocol are structure members of that per-operation structure. The structure layout is as defined above for the property structure, with the exception that variable size elements (strings and sequences) are allowed, and are sized as [1], with no further members generated after the first variable sized member.
The structure generated is padded and packed, such that padding members are explicitly inserted to ensure all types are aligned on their own boundaries. As part of the packing, the compiler is told to pack the structure. Given the insertion of padding, the packing simply means that there is no padding at the end of the structure to achieve any alignment. The struct definition should not be used in any other context where this lack of overall size alignment is required (e.g. an array of such structures).
As described in the CDG (in the section “Message Payloads on Data Ports”), there is one special exception for messages that consist entirely of one sequence of fixed-length elements: the sequence length is implied by the overall OpenCPI message length at input and output ports, and is not represented by inserting a uint32_t value in the message buffer/structure. In all other cases (not-fixed sequence elements, or more than one top-level data argument in the message), sequences in messages are represented by a struct containing length and data members.
For example, for a port named in, with a protocol whose first operation was sampledata, and whose first argument was sample - a sequence of Ulonglong values, the worker’s custom port union would look like:
union InOperations {
// Structure for the 'sampledata' operation on port 'in'
struct __attribute__ ((__packed__)) Sampledata {
uint64_t sample[1];
} sampledata;
};
Worker Base Class — C++ Language Only
For C++ workers, a base class is generated in the header file that is inherited by a derived class in the skeleton. The base class is generated to maximize the code generated in the header file and minimize the code generated in the skeleton.
The worker base class is generated to support all the documented features described above, including access to properties, and contains pure virtual member function declarations for all the member functions required to be implemented in the derived class generated in the skeleton. While the documented interfaces used by the worker author in the derived class are stable and not subject to change, the details of the code generation and the contents of header files (generated or not) are implementation defined and subject to change.