A pres_c file contains information describing the user-visible behavior of a set of stubs to be generated and anything else pertinent to the presentation. These files are used by the presentation generators to communicate their intent to the back ends. This makes it possible to separate the presentation, the C declarations and definitions, from the implementation.
The first few sections in this chapter describe the overall concepts embodied in the pres_c format; the last section, Section 7.2, describes the actual xdr data structures representing pres_c files.
A pres_c file contains the blueprints for a set of stubs. Each of these blueprints, or stub definitions, completely defines one stub which will be produced when the pres_c file is run through a C back end. A stub is a C function or data object which, when compiled and linked into a C program, can be invoked or otherwise utilized by that program as defined in the pres_c file. There are several different types of stubs that can be defined in a pres_c file (and hence produced by a back end).
These stubs are able to marshal and unmarshal the various data types defined by the presentation. The pres_c file actually contains only stub "templates" for such functions, rather than the complete stub definition. This is the one exception to our stub definition above. The back end is responsible for "specializing" the stub for a particular parameter direction (and perhaps other aspects), based on how the stub is used. If a stub is used in multiple ways, multiple versions of the stub will appear in the generated code. For stubs that are not used, no code is generated. Because of Flick's aggressive inlining of the marshaling/unmarshaling code, most of these stubs are rarely produced.
A client stub is a C function that can be called from anywhere in the program it is linked with. A client stub function packages up a request message, sends it to a server, and optionally waits for, receives, and unpacks a reply message from that server. The C function's parameters and return value may be used to pass data to and from the server, as well as to specify the target of the rpc (the object to be invoked) and to return error status. Exactly how these C function parameters are used is entirely defined by the client stub definition; there are no fixed rules that must apply to all stubs, such as the target object being specified in a particular parameter, or the return value being used to return an error status code. The name of the C client stub function is also defined in the pres_c file; although it is usually based on the name of an rpc operation defined in an idl file, it doesn't have to be.
A client stub, when invoked, generally performs the following basic steps, not necessarily in this exact order:
Of course, for one-way client stubs, steps 4-6 are not performed. Steps 5 and 6 are usually combined in actual stub implementations produced by typical PBEs (see Chapter 12 for more information).
A server skeleton is the server-side analog to the client stub: it causes request messages received from clients to be converted into calls to C work functions in the server program. Unlike a client stub, a server skeleton is not generally a C function: instead, it is an opaque transport-specific C object (e.g., a global variable) which the server program uses as a "handle" when registering itself as a server. Once the server has registered one or more object with the orb, it can begin accepting client messages. These messages are then dispatched to the appropriate object and skeleton to be processed.
Each skeleton acts like a dispatcher by decoding the message enough to figure out which server work function needs to be called and then unmarshaling the rest of the message. Server work functions tend to correspond to operations in interfaces, but some may be implicitly defined by other definitions from the idl. Each work function that can be called from a particular server skeleton can have a different C function signature, and can use its parameters and return value in a different way: a server skeleton definition in a pres_c file contains a separate function parameter/return value mapping description for each supported server work function.
When a request message is received from a client, the server skeleton generally causes the following operations to be performed, not necessarily in this exact order:
Of course, for one-way rpc requests, steps 7-9 are not performed. Steps 2 and 3 are usually combined in actual server skeletons produced by typical PBEs. Steps 7 and 9 may also be combined in some cases. See Chapter 12 for more information about PBE implementation.
Used in clients that use an event-based programming model. Not all PBEs or transport protocols may support client skeletons.
A client skeleton is the client-side equivalent of the server skeleton. Rather than dispatching requests to appropriate server work functions, a client skeleton dispatches reply messages to appropriate handlers, or client work functions. It is responsible for decoding the reply message enough to figure out with which client request it is associated, and calling the reply handler with any information the client saved when the request was made.
As on the server side, a "handle" or pseudo-object reference is used to identify the client skeleton expecting a reply message for the original request. The handle is passed to the server, and the reply is much like a one-way request to the pseudo-object client.
Unlike the server skeleton, however, the client skeleton does not bother to unmarshal the reply message (beyond the point that it needs to determine how to dispatch it), nor does it need to worry about marshaling data returned by the reply handler (there is no reply to the reply inherent in the model, although such functionality is certainly possible within the model).
When a reply message is received from a server, the client skeleton generally causes the following operations to be performed, not necessarily in this exact order:
Used in clients and servers that use an event-based programming model. Not all PBEs or transport protocols may support send and receive stubs.
Send stubs are responsible only for sending a message to either the client or server. They return immediately to the caller, regardless of whether a reply is expected (in the case of a request to the server). Note that a send stub is similar to a one-way client stub in that the message is sent and control returns without waiting for a reply. However, they different from each other in three important ways. First, a send stub is not responsible for marshaling any data; it requires a pre-marshaled message as a parameter. Second, a one-way client stub is used when no reply at all is expected for the rpc invocation; a send stub is used even if a reply is expected, but its receipt will be deferred until later in the client program's execution. Third, a send stub is not limited to the client; they exist on the server side to issue replies back to the client. They are more in harmony with the concepts of message passing than with traditional rpc.
Receive stubs are not well defined for Flick. Ideally, they represent a method of either polling or waiting for the reply from a previous send stub, although the implementation of such a stub has not been laid out for any of Flick's runtime layers. Currently, the supported method of receiving a reply is via the client skeleton.
In a sense, a send stub acts as a "thread fork," allowing both the client and the server to work in parallel. Similarly, a receive stub acts as a corresponding "join" operation, synchronizing the client thread with the server thread and returning the server's result.
Note that although send and receive stubs are not explicitly responsible for marshaling or unmarshaling any data, implementationally they may do a small amount. For example, a corba/iiop client send stub must encode the giop header and server object reference into the message before it can be sent to the server.
Used in clients and servers that use an event-based programming model. Not all PBEs or transport protocols may support continuation stubs.
If for some reason a work function or reply handler is not able to complete (perhaps required data has not yet arrived to finish the computation), it may request that the work be deferred until a later time. A continuation stub is used to indicate this to the runtime layer, and provide a way to save partially computed results so the work can be continued later (rather than being entirely restarted). It is left up to the runtime how to "awake" one of these continued work functions to continue its processing.
Used in clients and servers that use an event-based programming model. Not all PBEs or transport protocols may support message marshal/unmarshal stubs.
Like their marshal and unmarshal stub cousins, they are responsible for encoding and decoding data. However, rather than working on specific types, they focus on the entire message for a request or reply. By invoking a message marshal stub, one receives an opaque message object ready to be sent to a server (or multiple servers). Likewise, by invoking a message unmarshal stub, one can decode a message object into the appropriate parameters.
For some presentations there are peripheral data types or just extra information that needs to be available in order to produce the correct code. For instance, the corba C++ mapping requires the idl compiler to generate smart pointer classes for many of the defined types. Most of this work can be accomplished during presentation generation and simply added to the cast part of the pres_c file; however, producing the bodies of any functions is an implementation issue which should be left to the back end. Thus, we must have a generic way of storing information in the pres_c, whether it is presentation functions or any other things of interest. The solution was to make a data structure in xdr that could store tagged data so we could create semantically meaningful but otherwise arbitrary data values that could be stored in the pres_c file.
Most of the types of stubs listed above perform marshaling and/or unmarshaling of messages in some context: after all, marshaling and unmarshaling is the central mechanism behind rpc. Marshaling can be defined as conversion of data from language-specific presented data formats (C-language data types in this case) to a language-independent in-transit message format useful for passing the data between a client and a server; unmarshaling is the opposite conversion. There are an infinite number of ways message data can be presented in C, many of them reasonable and commonly used in different rpc systems. Providing a convenient representation of a wide variety of these possible presentations is the primary challenge for the pres_c file format.
To this end, each pres_c file contains three main parts:
Thus, each stub definition in a pres_c file refers to a "common database" of message type definitions (the mint), and a "common database" of C-language definitions and declarations (the cast). For example, to define how a client stub is to marshal a request message and unmarshal the reply, each client stub definition contains:
In general, the C type definitions are usually more complicated than their corresponding mint types, although in a few cases the mint actually contains more information. For example, mint integer types specify an exact range of allowable values, whereas C integer types are merely the well-known char, int, long, etc.
The stubs definitions are actually made up of a number of data structures defined in pres_c.x and referred to as "nodes". These nodes are connected together to form a tree that describes how to map to and from cast and mint. The nodes themselves are split into two classes of structures called "mappings" and "inlines". A mapping node is used to translate between whole types, a cast int to a mint int, for example. An inline node works on an exploded type, for example, a mapping could match to a C structure, but an inline is used to access the slots of the struct. Switching between these modes is done when a PRES_C_INLINE_ATOM is used to select what to map from an inline, and when a PRES_C_MAPPING_STRUCT is used to expand into an inline. For example, a pointer to a C structure would be a mapping that then referred to an inline, since the pointer is a primitive, but once it has been dereferenced, you are left with a structure.
Memory allocation and deallocation must be dealt with whenever any level of pointer indirection appears in the C-language type signatures used by a stub. Basically, any pointer must be initialized to point to something before use: either to memory on the stack, the heap, or even to a global variable. Similarly, that memory must be appropriately deallocated after it is no longer needed. Pointer indirection is a necessary part of a viable C presentation for any non-trivial idl, and different idls specify different ways of initializing and releasing these pointers.
To handle a wide variety of different memory allocation and deallocation semantics, the pres_c format associates some standard memory management information with each level of pointer indirection that appears in the C types a stub uses. This memory management information is contained within a PRES_C_INLINE_ALLOCATION_CONTEXT node, along with references to pres_c_inline nodes describing the pointer and other associated information (e.g., the length of a sequence, or corba's sequence ownership/release flag). Within these child inlines, pres_c mapping nodes (typically a PRES_C_MAPPING_POINTER node; see Section 7.2.4 below) refer back to their allocation context by its unique name. This memory management information specifies:
Although the memory allocation and deallocation semantics are specified in the same mapping data structure tree that is used to specify how marshaling and unmarshaling code is generated, this does not mean that allocation and deallocation occurs at the same time as marshaling or unmarshaling. For example, in the case of a server skeleton that must allocate memory to be used to hold data to be marshaled into the reply message, the memory must be allocated before the server work function is called, so the work function will have a place to fill in its reply data; on the other hand the actual marshaling of the reply message can only be done after the work function is called, when the data has been filled in. In general, the point at which memory is allocated or freed depends on the type of stub; see Section 7.1.1 above for details on when this happens in each case.
Although each pointer mapping node in a stub's mapping tree specifies both allocation and deallocation semantics for that pointer, in some cases one or the other of these is meaningless. For example, a client stub has no opportunity to allocate memory for request data, because the request data must already be allocated and ready to marshal from when the client stub is first entered. Similarly, a client stub has no opportunity to deallocate memory holding reply data, since the only time it could do so would be during the execution of the stub, and the calling program will not be able to examine the reply data until after the stub returns. Therefore, for client stubs, allocation semantics for request data and deallocation semantics for reply data are ignored.
In cases where a pointer is not merely a simple level of pointer indirection, but in fact holds the address of the first element in an array of elements, the length of the block of memory allocated or deallocated naturally depends on the length of the array.
In some cases, two pointer mapping nodes in a pres_c file may refer to the same actual pointer variable at runtime. For example, most idls support a notion of an "inout" parameter, where data for that parameter is to be sent in both the request and the reply message, but the parameter appears only once in the C function prototype for the stub or work function. This parameter is a pointer to a data area to be used for both the request and the reply: the request data is expected to be overwritten during the rpc with reply data. In cases like this, care must be taken that the pointer is not allocated and initialized twice, causing memory to be irrevocably lost, and (even more importantly) is not freed twice.
Presentation back ends that take pres_c files as input cannot generally be expected to recognize when two pointer mapping nodes refer to the same pointer. Instead, the producer of the pres_c file must take care to ensure that at most one of those nodes specify memory allocation, and at most one specifies deallocation. If allocation for such a pointer is to be done by the stub, then the allocation semantics must be specified only in the pointer mapping node corresponding to the operation that is done first; the other mapping node must specify PRES_C_ALLOC_NEVER. Similarly, if deallocation is to be done by the stub, then the deallocation semantics must be specified only in the mapping node corresponding to the operation that is done last. For example, in the case of a server skeleton, allocation semantics for an "inout" pointer parameter must be specified in the pointer mapping node for the unmarshaled request data, whereas deallocation semantics for that same pointer parameter must be specified in the pointer mapping node for the marshaled reply data.
Exceptions as described in the idl must be expressed in the pres_c so they can be properly marshaled, unmarshaled, and handled in an appropriate way. Since exceptions can abstractly be viewed as just another data type to be passed over the wire, there isn't any provision for a special exception mapping in pres_c. However, they do make their presence known by slightly changing the structure of reply messages. Since an exception can occur based on data given to the server by a client, an exception message may be returned by the server in place of the normal reply message. The exception may either be a system-defined exception due to a mal-formed request message (e.g., an undefined operation or unknown object reference), or the exception may be user-defined, generated by the server-side work function indicating the request could not be fulfilled for whatever reason. This creates a union, if you will, of possible reply messages - one that contains the normal reply, one that identifies a system exception, and one that identifies a user exception. The pres_c represents this as would be expected, a union of the three possible reply messages, the environment or status code in the reply being the discriminator as to which message actually exists on the wire. The method of communicating the exception to the user is handled mostly by the back end; only the presented type and name of the parameter is specified in the pres_c.
The top-level pres_c structure is defined in mom/pres_c.x. This structure contains the necessary mint, cast, and tag information for producing stubs and any associated presentation.
struct pres_c_1 { mint_1 mint; cast_1 cast; cast_1 stubs_cast; cast_1 pres_cast; aoi a; pres_c_stub stubs<>; string pres_context<>; cast_expr error_mappings<>; data_channel_mask unpresented_channels<>; tag_list *pres_attrs; meta meta_data; int cast_language; }; |
How individual elements in the mint and cast structures link together is specified by four kinds of data types. First, stub types describe the functions generated by Flick necessary for the ipc model. Second, inline data structures traverse the mint data structure, or link together a mint interface type to one or more C function parameters. Third, mapping data structures map one C function parameter in to the mint. Finally, allocation types describe the allocation semantics associated with various pieces of the generated code.
Let us look at each aspect of the pres_c structure in more detail.
Stubs come in two forms. The first form transforms data between a mint interface type and a complex data structure. These stubs are called marshal and unmarshal stubs. The second and more common form implements the ipc mechanism between client and server. These are known as client stubs and server skeletons (or dispatch routines).
union pres_c_stub switch (pres_c_stub_kind kind) { case PRES_C_MARSHAL_STUB: pres_c_marshal_stub mstub; case PRES_C_UNMARSHAL_STUB: pres_c_marshal_stub ustub; case PRES_C_CLIENT_STUB: pres_c_client_stub cstub; case PRES_C_SERVER_STUB: pres_c_server_stub sstub; case PRES_C_CLIENT_SKEL: pres_c_skel cskel; case PRES_C_SERVER_SKEL: pres_c_skel sskel; case PRES_C_SEND_STUB: pres_c_msg_stub send_stub; case PRES_C_RECV_STUB: pres_c_msg_stub recv_stub; case PRES_C_MESSAGE_MARSHAL_STUB: pres_c_msg_marshal_stub mmstub; case PRES_C_MESSAGE_UNMARSHAL_STUB: pres_c_msg_marshal_stub mustub; case PRES_C_CONTINUE_STUB: pres_c_continue_stub continue_stub; }; |
Let us look at each pres_c stub type in more detail.
Specifies a stub function which marshals/unmarshals a data structure. Although they are generated for every data type defined in the idl (and in some cases the presentation itself), these stubs are only really needed to handle the complex structures which don't have a default mapping onto a mint type. Structures with pointers often fall into this category.
Implementationally, both marshal and unmarshal stubs are represented by the same data structure. However, they are separate members of the stub union, and should thus be treated separately.
The marshal/unmarshal structure contains references to the stub function definition in the cast, and to the interface type in the mint. The pres_c_inline structure links them together. The seethru_map slot exports the internal workings of the stub so that it can be easily inlined in other code.
struct pres_c_marshal_stub /* Both marshal and unmarshal */ { cast_ref c_func; mint_ref itype; pres_c_inline i; /* How the connection to marshal to is specified. */ pres_c_inline target_i; /* The PRES_C mapping that allows us to "see through" or "see into" this stub. We need this mapping when we want to inline the body of this marshal stub into a client stub, a server function, etc. */ pres_c_mapping seethru_map; }; |
This is a representation of a client stub. References are kept to the function definition in the cast structure and to the request and reply interface types in the mint structures. Two pres_c_inline structures link the request and reply types to the C function parameters in the cast.
struct pres_c_client_stub { cast_ref c_func; pres_c_stub_op_flags op_flags; mint_ref request_itype; mint_ref reply_itype; pres_c_inline request_i; pres_c_inline reply_i; mint_ref target_itype; pres_c_inline target_i; mint_ref client_itype; pres_c_inline client_i; mint_ref error_itype; pres_c_inline error_i; }; |
For each interface there is at least a server-side skeleton in the pres_c structure. In some cases, a client-side skeleton will also exist if you have selected the decomposed stubs option. In both cases, the skeleton structure holds a reference to the cast for the function prototype, the root mint for all incoming and outgoing messages, and the list of supported functions to which the skeleton can dispatch.
Following are the data structures relevant to both client and server skeletons:
struct pres_c_skel { cast_ref c_def; mint_ref request_itype; mint_ref reply_itype; pres_c_func funcs<>; }; struct pres_c_server_func { cast_ref c_func; pres_c_stub_op_flags op_flags; mint_ref simple_msg_itype; pres_c_inline msg_i; mint_ref target_itype; pres_c_inline target_i; mint_ref client_itype; pres_c_inline client_i; mint_ref error_itype; pres_c_inline error_i; }; |
A pres_c_func is either a pres_c_server_func or pres_c_receive_func, depending on the stub model in place (typical rpc or event-based message passing). Each is defined almost identical to the pres_c_client_stub above.
These stubs are used in an event based system to send or receive a pre-marshaled message. Note that the target object is still associated with the send or receive stub. This facilitates the reusability of pre-marshaled messages, in that they can be cached and/or sent to multiple targets. These stubs are non-blocking so waiting for or producing a reply must be done explicitly.
struct pres_c_msg_stub { cast_ref c_func; mint_ref msg_itype; pres_c_inline msg_i; mint_ref target_itype; pres_c_inline target_i; int request; }; |
These stubs are used to marshal and unmarshal a complete message corresponding to a specific interface operation. These are used to build or decode the pre-marshaled messages used by the send and receive stubs described above.
struct pres_c_msg_marshal_stub { cast_ref c_func; pres_c_stub_op_flags op_flags; mint_ref itype; pres_c_inline i; int client; int request; string reply_handler_name<>; }; |
A continuation stub is used to notify the runtime that a work function has not fully completed, but would like to defer its computation until a later time.
struct pres_c_continue_stub { cast_ref c_func; mint_ref itype; pres_c_inline i; int request; }; |
The tag_data structure is used to hold arbitrary information, but with a particular semantic meaning. This provides the hooks necessary for a back end to generate appropriate implementations for certain pieces of the presentation. For example, a smart pointer class for a data type is something defined by the presentation. However, the implementation must be given by the back end. The tags are used to give semantic meaning to the presentation pieces, allowing the back end to generate an appropriate implementation.
In general, tags are stored in a tag_list, which provides a hierarchical list structure. Lists contain tag_items, which hold a tag name and its corresponding data. The following is the definition of the tag infrastructure in the pres_c file:
/* Tags are used for storing generic data which have some properties specified by the tag */ enum tag_data_kind { TAG_NONE = 0x0001, ... TAG_CAST_INIT = 0x000f, TAG_ANY_ARRAY = 0x8002, ... TAG_CAST_INIT_ARRAY = 0x800f }; const TAG_ARRAY = 0x8000; typedef struct tag_list *tag_list_ptr; typedef string tag_string_array<>; typedef char tag_object_array<>; union tag_data switch (tag_data_kind kind) { case TAG_NONE: void; case TAG_ANY: void; case TAG_TAG_LIST: tag_list_ptr tl; case TAG_BOOL: char b; case TAG_STRING: string str<>; case TAG_INTEGER: int i; case TAG_FLOAT: float f; case TAG_REF: string ref<>; case TAG_OBJECT: tag_object_array obj; case TAG_CAST_SCOPED_NAME: cast_scoped_name scname; case TAG_CAST_DEF: cast_def_t cdef; case TAG_CAST_TYPE: cast_type ctype; case TAG_CAST_EXPR: cast_expr cexpr; case TAG_CAST_STMT: cast_stmt cstmt; case TAG_CAST_INIT: cast_init cinit; case TAG_TAG_LIST_ARRAY: tag_list_ptr tl_a<>; case TAG_BOOL_ARRAY: char b_a<>; case TAG_STRING_ARRAY: tag_string_array str_a<>; case TAG_INTEGER_ARRAY: int i_a<>; case TAG_FLOAT_ARRAY: float f_a<>; case TAG_REF_ARRAY: tag_string_array ref_a<>; case TAG_OBJECT_ARRAY: tag_object_array obj_a<>; case TAG_CAST_SCOPED_NAME_ARRAY: cast_scoped_name scname_a<>; case TAG_CAST_DEF_ARRAY: cast_def_t cdef_a<>; case TAG_CAST_TYPE_ARRAY: cast_type ctype_a<>; case TAG_CAST_EXPR_ARRAY: cast_expr cexpr_a<>; case TAG_CAST_STMT_ARRAY: cast_stmt cstmt_a<>; case TAG_CAST_INIT_ARRAY: cast_init cinit_a<>; }; struct tag_item { string tag<>; tag_data data; }; struct tag_list { struct tag_list *parent; tag_item items<>; }; |
pres_c inline types are used to map between high-level mint and cast types and also to traverse the mint or cast structures separately without directly mapping between the two. They control the presentation of interfaces to the given language and control stub generation.
union pres_c_inline_u switch (pres_c_inline_kind kind) { case PRES_C_INLINE_ATOM: pres_c_inline_atom atom; case PRES_C_INLINE_STRUCT: pres_c_inline_struct struct_i; case PRES_C_INLINE_FUNC_PARAMS_STRUCT: pres_c_inline_func_params_struct func_params_i; case PRES_C_INLINE_HANDLER_FUNC: pres_c_inline_handler_func handler_i; case PRES_C_INLINE_STRUCT_UNION: pres_c_inline_struct_union struct_union; case PRES_C_INLINE_EXPANDED_UNION: pres_c_inline_expanded_union expanded_union; case PRES_C_INLINE_VOID_UNION: pres_c_inline_void_union void_union; case PRES_C_INLINE_COLLAPSED_UNION: pres_c_inline_collapsed_union collapsed_union; case PRES_C_INLINE_VIRTUAL_UNION: pres_c_inline_virtual_union virtual_union; case PRES_C_INLINE_TYPED: pres_c_inline_typed typed; case PRES_C_INLINE_THROWAWAY: void; case PRES_C_INLINE_XLATE: pres_c_inline_xlate xlate; case PRES_C_INLINE_ASSIGN: pres_c_inline_assign assign; case PRES_C_INLINE_COND: pres_c_inline_cond cond; case PRES_C_INLINE_MESSAGE_ATTRIBUTE: pres_c_inline_message_attribute msg_attr; case PRES_C_INLINE_ALLOCATION_CONTEXT: pres_c_inline_allocation_context acontext; case PRES_C_INLINE_TEMPORARY: pres_c_temporary temp; case PRES_C_INLINE_ILLEGAL: void; }; |
Let us look at each pres_c inline type in detail.
This is the fundamental inline type and is used to map one single, simple mint type to one single C type. It also marks the transition from inline mode to mapping mode, by selecting cast member index from the current inline state and passing that to the mapping.
struct pres_c_inline_atom { pres_c_inline_index index; pres_c_mapping mapping; }; |
This inline type contains one pres_c_inline structure for each struct member. This usually corresponds to a container of some kind, or something in the C and mint with multiple attributes.
struct pres_c_inline_struct { pres_c_inline slots<>; }; |
This inline type maps a mint union into a C struct containing a discriminator tag and the union. For example, a MINT_UNION containing a MINT_INTEGER and MINT_CHAR might have a C mapping like this:
struct example_union { mint_type_tag kind; union { int i; char c; } } |
struct pres_c_inline_struct_union_case { int index; pres_c_mapping mapping; }; struct pres_c_inline_struct_union { pres_c_inline_atom discrim; pres_c_inline_index union_index; pres_c_inline_struct_union_case cases<>; pres_c_inline_struct_union_case *dfault; }; |
This inline type maps a mint union into a C struct containing a discriminator tag and a void * pointer to the data. The previous example now might have a C mapping like this:
struct example_union { mint_type_tag kind; void *data; } |
struct pres_c_inline_void_union_case { cast_expr case_value; cast_type type; pres_c_mapping mapping; }; struct pres_c_inline_void_union { pres_c_inline_atom discrim; pres_c_inline_index void_index; pres_c_inline_void_union_case cases<>; pres_c_inline_void_union_case *dfault; }; |
This inline type maps a mint union into a C struct containing a discriminator tag and slots for each member of the union. This isn't currently used in Flick.
struct pres_c_inline_expanded_union { pres_c_inline_atom discrim; pres_c_inline cases<>; pres_c_inline dfault; }; |
This inline type forces the selection of a given member of union. Only unions with this member selected will be accepted. This type is often used to traverse down the mint structure (e.g., the union of all interfaces, and the union of all operations) until a certain section of the mint structure is reached (e.g., the request of a specific operation), holding fixed the various unions in the mint along this path.
struct pres_c_inline_collapsed_union { mint_const discrim_value; pres_c_inline selected_case; }; |
This inline type represents an "arbitrary" union. It's main purpose is to allow the return from an operation to be either the regular result, a system error, or a user-defined exception. Since the implementation is really left up to the back end, the presentation generator leaves this as an arbitrary union of the three possibilities.
typedef pres_c_inline pres_c_inline_virtual_union_case; struct pres_c_inline_virtual_union { string arglist_name<>; /* Name of associated arglist. */ pres_c_inline_virtual_union_case discrim; pres_c_inline_virtual_union_case cases<>; pres_c_inline_virtual_union_case dfault; }; |
Valid for receive-only mappings, it ignores any mint or cast that may remain in the current subtree, effectively throwing away their values.
An allocation context is the main structure used for dealing with all types of allocation. It does not correspond to any mint or cast directly, but provides context for (usually) a pointer type, such as length, maximum allocation size, allocation semantics, etc. In some cases, these are known constants, while at other times the values can be known only at runtime and are included as part of the data structure (e.g., a corba sequence's length and maximum fields).
struct pres_c_inline_allocation_context { /* Allocation context: */ string arglist_name<>; /* Name of the context's arglist. */ /* * XXX - The arguments below marked with `XXX' are NOT IMPLEMENTED. * There is minimal support in place for handling them, but they are * not currently being used to support any particular allocation * semantics. */ /*XXX*/ pres_c_inline offset; /* First element to m/u */ pres_c_inline length; /* # of elements to m/u */ pres_c_inline min_len; /* [hard] minimum (usu. IDL const) */ pres_c_inline max_len; /* [hard] maximum (usu. IDL const) */ pres_c_inline alloc_len; /* allocated # of elements */ pres_c_inline min_alloc_len;/* minimum length of allocation */ /*XXX*/ pres_c_inline max_alloc_len;/* maximum length of allocation */ pres_c_inline release; /* release of struct-owned buffer */ pres_c_inline terminator; /* buffer termination */ pres_c_inline mustcopy; /* must copy data to keep it */ /*XXX*/ int overwrite; /* `out' buffer is preallocated */ /*XXX*/ allocation_owner owner; /* entity ownership */ pres_c_allocation alloc; /* the allocation semantics */ /* * The real inline to be handled: * `ptr' must eventually lead to a PRES_C_MAPPING_INTERNAL_ARRAY node, * a PRES_C_MAPPING_POINTER node, or a PRES_C_MAPPING_OPTIONAL_POINTER * node, which is responsible for actually instantiating the * allocation. */ pres_c_inline ptr; }; |
This inline transforms the current cast type to a known, fixed type. This is useful when performing conversions between the presented type of a parameter and the type with which it is viewed by the stubs. For example, mig allows the user to name somewhat abstract data structures (e.g., one can specify the size of a structure, but one cannot explicitly specify its contents or layout), but the stubs must have a concrete representation in order to marshal or unmarshal the data (e.g., a structure of a known size can be blindly copied into and out of the data stream). This node facilitates the conversion between the user-defined type (unknown to the idl compiler) and an equivalent type the stubs can use to handle the data.
struct pres_c_inline_xlate { /* the _real_ pres_c_inline... */ pres_c_inline sub; pres_c_inline_index index; cast_type internal_ctype; string translator<>; string destructor<>; }; |
This inline allows an arbitrary cast expression to be assigned to the current entity. This isn't currently used in Flick.
struct pres_c_inline_assign { /* the _real_ pres_c_inline... */ pres_c_inline sub; pres_c_inline_index index; cast_expr value; }; |
This is very similar to a union inline, except that it refers to a boolean "discriminator". The runtime value of the discriminator will select which inline is actually part of the execution path.
struct pres_c_inline_cond { /* This index must refer to a boolean C variable (or at least an integerish type that can be used as a boolean). At runtime, if the value of that C variable(/parameter/slot) is true, marshal using true_inl; otherwise use false_inl. */ pres_c_inline_index index; pres_c_inline true_inl; pres_c_inline false_inl; }; |
This inline allows an "attribute" to be embedded in the tree, describing a semantic entity that is entirely left up to the back end to decide how to implement. There is no associated cast or mint for this inline.
enum pres_c_message_attribute_kind { /* sets some attribute flags */ PRES_C_MESSAGE_ATTRIBUTE_FLAGS = 0, /* how long to wait for reply */ PRES_C_MESSAGE_ATTRIBUTE_TIMEOUT = 1, /* index of when received */ PRES_C_MESSAGE_ATTRIBUTE_SEQUENCE_RECEIVED = 2, /* client reference */ PRES_C_MESSAGE_ATTRIBUTE_CLIENT_REFERENCE = 3, /* flag indicating work function should make its own copy of the data if it wants to keep it */ PRES_C_MESSAGE_ATTRIBUTE_SERVERCOPY = 4 /* maybe in the future, SID will be moved here */ }; struct pres_c_inline_message_attribute { pres_c_message_attribute_kind kind; }; |
This inline describes a function to be called to handle the pres_c tree that normally would be rooted in this location. In the decomposed presentation style, this replaces the pres_c_inline_func_params_struct. The slots all represent parameters associated with the presentation, such as the object reference, marshaled message, environment, etc.
struct pres_c_inline_handler_func { /* * One element for each parameter to the handler function. * These are all ``special'' presentation-only parameters. */ pres_c_inline_struct_slot slots<>; }; |
This inline introduces a new (temporary) variable into the scope for the given mapping, rather than selecting a member of the inline state (a parameter or data member). This allows one to marshal/unmarshal data that isn't explicitly presented (e.g., a string's length or an exceptional reply when there is no environment parameter). In some cases, this may even be a constant that needs to be "hardcoded" into the pres_c file (in which case it is optimized to be a constant instead of a temporary variable). This is common in allocation contexts where important pieces of the context are known, such as the length of a fixed array.
enum pres_c_temporary_type { TEMP_TYPE_PRESENTED, TEMP_TYPE_ENCODED }; struct pres_c_temporary { pres_c_mapping map; /* The mapping for the data */ string name<>; /* A hint used in naming the temp */ cast_type ctype; /* The type of the temporary */ cast_expr init; /* Initialization expression */ pres_c_temporary_type type; /* How this temporary is used */ /* * `is_const' is TRUE (non-zero) if `init' evaluates to some constant * expression. This is used to avoid creating a temporary variable for * constant values. The initialization expr is simply passed down * instead of a temporary variable name. If this flag is set, you are * asserting that the resulting expr will NEVER be used as an lvalue. */ int is_const; /* * Handlers indicate to the back end what this temporary is used for so * it can resolve the mismatch. For example, it may simply be mapped * to a macro that has parameters for the original variable and the * temporary. We define both pre- and post-handlers, so that different * processing can be done both before encode and after decode. */ string prehandler<>; string posthandler<>; }; |
This is really a slot-holder that tells the code generator to emit an unconditional error. This is useful when an inline is required to exist, but it can't (or shouldn't) happen in practice. For example, the pres_c structure is such that a reply must consist of a union of the normal reply case, a system exception case, and a user exception case. In the case of Flick's decomposed stubs presentation, there is a special marshaling function to encode an exceptional reply. Since it makes no sense for one to encode an exceptional reply when there is no exception, the normal reply case is given as a PRES_C_INLINE_ILLEGAL. Thus, if the generated function is ever called with no exception, an error is returned.
pres_c mappings serve to specify exactly how a mint type is to be translated to the corresponding cast type. This combined with the inline information completely specifies stub generation.
While inline structures can map mint types to more than one parameter in a function, mappings control one and only parameter. So, inline types which present more than one parameter will have more than one mapping type.
union pres_c_mapping_u switch (pres_c_mapping_kind kind) { case PRES_C_MAPPING_DIRECT: void; case PRES_C_MAPPING_STUB: pres_c_mapping_stub mapping_stub; case PRES_C_MAPPING_POINTER: pres_c_mapping_pointer pointer; case PRES_C_MAPPING_INTERNAL_ARRAY: pres_c_mapping_internal_array internal_array; case PRES_C_MAPPING_STRUCT: pres_c_mapping_struct struct_i; case PRES_C_MAPPING_FLAT_UNION: pres_c_mapping_flat_union flat_union; case PRES_C_MAPPING_SPECIAL: pres_c_mapping_special special; case PRES_C_MAPPING_XLATE: pres_c_mapping_xlate xlate; case PRES_C_MAPPING_REFERENCE: pres_c_mapping_reference ref; case PRES_C_MAPPING_TYPE_TAG: void; case PRES_C_MAPPING_TYPED: void; case PRES_C_MAPPING_OPTIONAL_POINTER: pres_c_mapping_optional_pointer optional_pointer; case PRES_C_MAPPING_IGNORE: void; case PRES_C_MAPPING_SYSTEM_EXCEPTION: void; case PRES_C_MAPPING_DIRECTION: pres_c_mapping_direction direction; case PRES_C_MAPPING_SID: pres_c_mapping_sid sid; case PRES_C_MAPPING_ARGUMENT: pres_c_mapping_argument argument; case PRES_C_MAPPING_MESSAGE_ATTRIBUTE: pres_c_mapping_message_attribute message_attribute; case PRES_C_MAPPING_INITIALIZE: pres_c_mapping_initialize initialize; case PRES_C_MAPPING_ILLEGAL: void; case PRES_C_MAPPING_VAR_REFERENCE: pres_c_mapping_var_reference var_ref; case PRES_C_MAPPING_PARAM_ROOT: pres_c_mapping_param_root param_root; case PRES_C_MAPPING_ELSEWHERE: void; case PRES_C_MAPPING_SELECTOR: pres_c_mapping_selector selector; case PRES_C_MAPPING_TEMPORARY: pres_c_temporary temp; case PRES_C_MAPPING_SINGLETON: pres_c_mapping_singleton singleton; }; |
Let us look at each mapping type in more detail.
This mapping directly maps simple types together. A mint integer to a C integer is a direct mapping. No additional information is needed.
The C parameter must be a named type. The appropriate stub will then be called to marshal/unmarshal this parameter. No additional information is needed. Note that Flick aggressively inlines the stub's code into the generated code, so only in rare cases will a call to a marshal/unmarshal stub actually be emitted (e.g., a recursive data structure).
struct pres_c_mapping_stub { int mapping_stub_index; }; |
This mapping provides context to mappings that hang under this node. This mapping represents the direction of the parameter we are currently handling.
enum pres_c_direction { PRES_C_DIRECTION_UNKNOWN = 0, PRES_C_DIRECTION_IN = 1, PRES_C_DIRECTION_OUT = 2, PRES_C_DIRECTION_INOUT = 3, PRES_C_DIRECTION_RETURN = 4 }; struct pres_c_mapping_direction { pres_c_direction dir; pres_c_mapping mapping; }; |
This mapping inserts a level of pointer indirection on top of the target mapping. The arglist_name identifies the allocation context associated with the pointer.
struct pres_c_mapping_pointer { string arglist_name<>; pres_c_mapping target; }; |
This mapping allows the conversion from a reference type to an actual type.
struct pres_c_mapping_var_reference { pres_c_mapping target; }; |
This is similar to the standard pointer mapping in that it adds a level of pointer indirection onto the target mapping. However, the value of the pointer has a semantic meaning in that it determines the presence or absence of data.
struct pres_c_mapping_pointer { string arglist_name<>; pres_c_mapping target; }; |
A struct mapping maps elements of the interface type into a compatible C type. The mapping of each element is specified by the struct_i inline type parameter. Note that this returns the pres_c walker to inline mode; we map a single entity (a structure) into multiple parts (its fields).
typedef pres_c_inline pres_c_mapping_struct; |
This node is associated with the data buffer of an array. When processing this mapping, other pieces of the array must also be known, such as the length, maximum length, etc. These are provided by the allocation context identified by arglist_name.
struct pres_c_mapping_internal_array { string arglist_name<>; pres_c_mapping element_mapping; }; |
This node maps a mint union onto a "flat" C union. The first case is an integerish discriminator, with each subsequent case a structure starting with the discriminator in the first slot.
struct pres_c_mapping_flat_union { pres_c_mapping discrim; pres_c_mapping cases<>; pres_c_mapping dfault; }; |
This mapping maps an arbitrary mint type onto an arbitrary C type by specifying a marshaling function to be invoked by the stub to marshal/unmarshal this mint type. This mechanism allows us to handle specialized presentations.
struct pres_c_mapping_special { string marshaler_name<>; }; |
This is almost identical to its inline counterpart. It transforms the current cast type into a known, fixed type.
struct pres_c_mapping_xlate { cast_type internal_ctype; pres_c_mapping internal_mapping; string translator<>; string destructor<>; }; |
This mapping is for object references. Each back end must define this to allow the conversion between on-the-wire and in-memory representations for object references.
enum pres_c_reference_kind { PRES_C_REFERENCE_COPY = 0, /* send a copy */ PRES_C_REFERENCE_MOVE = 1, /* send my instance */ PRES_C_REFERENCE_COPY_AND_CONVERT = 2 /* send a converted copy. * This corresponds to * MIG's MAKE, since the OS * converts a RECV right * to a SEND right */ }; struct pres_c_mapping_reference { /* * `kind' refers to the semantic: either copy, move, or MIG's * copy_and_convert (make). `ref_count' is how many references we * are working with (currently it's always 1). */ pres_c_reference_kind kind; int ref_count; }; |
This mapping identifies the current parameter as a security identifier.
enum pres_c_sid_kind { PRES_C_SID_CLIENT = 0, /* effective SID of the client */ PRES_C_SID_SERVER = 1 /* required SID of the server */ }; struct pres_c_mapping_sid { pres_c_sid_kind kind; }; |
Similar to its inline counterpart, an "attribute" is embedded in the tree describing a semantic entity. The difference is that there is mint and cast for this mapping, allowing a run-time parameter to convey special instructions to the stub or runtime.
struct pres_c_mapping_message_attribute { pres_c_message_attribute_kind kind; }; |
Arguments are the method by which we can allow other branches of the pres_c tree to see the parameter or data of the current branch. The best example is an array. The length is one piece of data on the wire, while the contents of the array elements is another. It is obvious, however, that they are related, and the array's length is necessary to process the array's data. The mechanism Flick uses for this is to save the length as an argument to some higher node (the allocation context), and when the array is processed, the length can be retrieved from the list of arguments. The cast type and expression are saved in the argument list when this node is reached.
struct pres_c_mapping_argument { string arglist_name<>; /* Name of the arglist itself. */ string arg_name<>; /* Name of argument in the list. */ pres_c_mapping map; }; |
This node allows the current parameter or data item to be initialized to some specified cast expression.
struct pres_c_mapping_initialize { cast_expr value; }; |
This is a special node for handling "presented structures", structures that exist in the presentation, but not in the mint. It selects a member of the current cast scope, and then maps only that entity. This is particularly useful for directly handling the corba environment discriminator.
struct pres_c_mapping_selector { int index; pres_c_mapping mapping; }; |
This node indicates that the current cast expression and type describe a formal parameter to a client stub or server work function. On both the client and server, ctype may describe the internal (actual) type of the parameter, if different from the formal type. On the server side, the type may be further transformed, such as changing array types into pointer-to-element types (to facilitate proper allocation and ease unmarshaling).
struct pres_c_mapping_param_root { cast_type ctype; cast_init init; pres_c_mapping map; }; |
This node has the sole purpose of returning to inline mode, so we can treat the current entity as more than a single entity. This is primarily used for interjecting an allocation context when we only have a mapping available. The required pieces of the allocation context can be temporaries, with the pointer finally being mapped with the original cast and mint from this singleton node.
struct pres_c_mapping_singleton { pres_c_inline inl; }; |
Allocation types specify what function provides space for a buffer to be marshaled/unmarshaled and when that function is to be invoked. The events for allocation are specified by flags. The function which does the allocation is specified by name; the function must have a certain prototype (specified by the back end).
The allocation structure is rather complex because of the many different allocation semantics that can and need to be expressed. At the highest level, there is a list of allocation cases, one for each possible parameter direction (since each direction may need to be handled differently). Each of these cases can either allow or disallow allocation for that particular direction. If allowed, the allocation semantics are expressed by a set of flags and an allocator. The allocator can be either a specific name, a special type of allocator (such as static or out-of-line), or an arbitrary allocator (essentially one that doesn't matter, allowing the code generator to choose an optimal one, such as stack).
The complete set of allocation flags and structures follow.
const PRES_C_ALLOC_NEVER = 0x00000000; const PRES_C_ALLOC_IF_NULL = 0x00000001; const PRES_C_ALLOC_IF_TOO_SMALL = 0x00000002; const PRES_C_ALLOC_IF_TOO_LARGE = 0x00000004; const PRES_C_ALLOC_ALWAYS = 0x00000008; const PRES_C_ALLOC_EVER = 0x0000000f; const PRES_C_ALLOC_CLEAR = 0x00000010; const PRES_C_REALLOC_IF_TOO_SMALL = 0x00000200; const PRES_C_REALLOC_IF_TOO_LARGE = 0x00000400; const PRES_C_REALLOC_ALWAYS = 0x00000800; const PRES_C_REALLOC_EVER = 0x00000f00; const PRES_C_REALLOC_CLEAR = 0x00001000; const PRES_C_FAIL_IF_TOO_SMALL = 0x00002000; const PRES_C_DEALLOC_NEVER = 0x00000000; const PRES_C_DEALLOC_ALWAYS = 0x00080000; const PRES_C_DEALLOC_EVER = 0x000f0000; const PRES_C_DEALLOC_NULLIFY = 0x00100000; const PRES_C_DEALLOC_ON_FAIL = 0x00200000; const PRES_C_RUN_CTOR = 0x01000000; const PRES_C_RUN_DTOR = 0x02000000; enum pres_c_allocator_kind { PRES_C_ALLOCATOR_DONTCARE, PRES_C_ALLOCATOR_STATIC, PRES_C_ALLOCATOR_OUTOFLINE, PRES_C_ALLOCATOR_NAME }; union pres_c_allocator switch (pres_c_allocator_kind kind) { case PRES_C_ALLOCATOR_DONTCARE: void; case PRES_C_ALLOCATOR_STATIC: void; case PRES_C_ALLOCATOR_OUTOFLINE: string ool_name<>; case PRES_C_ALLOCATOR_NAME: string name<>; }; struct pres_c_allocation_case { pres_c_alloc_flags flags; pres_c_allocator allocator; cast_init alloc_init; }; enum pres_c_allocation_allow { PRES_C_ALLOCATION_INVALID = 0, PRES_C_ALLOCATION_ALLOW = 1 }; union pres_c_allocation_u switch (pres_c_allocation_allow allow) { case PRES_C_ALLOCATION_ALLOW: pres_c_allocation_case val; case PRES_C_ALLOCATION_INVALID: void; }; struct pres_c_allocation { pres_c_allocation_u cases[PRES_C_DIRECTIONS]; }; |
There are also the following related functions to create specific pres_c inlines, allowing the initialization of important data fields. They should all be self-explanatory, based on the descriptions given above.
The presd program is a simple pretty-printer that takes pres_c input from a file or standard input and outputs a human-readable, ASCII translation. If the pres_c input comes from a file, the output is a corresponding `.prd' (pres_c dump) file. Otherwise, if the pres_c input comes from stdin, the human-readable output is sent to stdout. Note that as the pres_c format changes, this program must also be changed to support any new constructs, or modifications to existing ones.