12 Back Ends

The presentation created by the presentation generators contains only the outside view, the language presentation and behavior, of a series of functions. To create the actual implementations of these presentations they need to be fed into a back end code generator. There are several distinct back ends which are based on a common library that does most of the work. The library and specific back ends are located under the c/pbe directory.

12.1 The Back End Library

The back end library is the base implementation for code generation which is then specialized to an encoding scheme and runtime. The library provides a great deal of infrastructure for dealing with various things, like command line arguments, and loading the pres_c input file. However, the primary job of the library is to handle the translation and optimization of the input pres_c descriptions into C or C++ code. The process of interpreting the pres_c tree falls to the mu_state family of classes which provide a number of functions for walking the tree and interpreting its nodes. The mu_state can then be overridden by specific back ends to implement their own functionality. In addition to generating code from the pres_c description of stubs, the back end can also employ scml code to generate formatted C and C++ source code. This scml code is usually defined externally and then called explicitly by the back end. The process of calling scml macros can be done manually as it is for creating the header and footer for output files, or through the presentation implementation and collection classes described later.

The declarations for the library classes and functions can be found in the mom/c/be directory and the source in c/pbe/lib.

12.1.1 Back End State

The first set of classes we'll look at are used for maintaining state in the back end and managing the top level flow of control. The classes and functions work together based on a simple event based methodology to allow for easy expansion. The core of this model is a handler object which can process an event, it does not necessarily have to process every event, just the ones it is interested in. These handlers are then prioritized and collected into an object which is able to distribute a single event amongst its set of handlers. The resulting system allows for easy addition of code into the root control path without having to tamper directly with the library.

Types:

be_event
A simple structure which has an event identifier and can be subclassed to contain extra information about an event.
be_handler
Contains a function pointer which is expected to handle any be_events passed to it.
be_looper
A subclass of be_handler that maintains a list of target be_handlers. Whenever an event is given to the looper it will distribute it to the target handlers.
be_file
A subclass of be_looper that is used to track any files in the back end as well as generate their output. The handlers that are added to the be_file object are expected to generate the output for that file.
be_state
A subclass of be_looper that acts as the primary event handler and dumping ground for anything a back end might be interested in. Handlers added to a be_state object are used for initializing variables, reacting to command line arguments and directing the be_files to generate themselves.

Functions:

be_state *get_be_state()
Retrieve the be_state object for a specific back end
int main(int argc, char **argv)
The library provided main simply gets a be_state from get_be_state() and then installs any handlers. Next, the command line arguments are passed to the be_state for processing and finally it is all set in motion by sending the initialize, command line args, and shutdown events to the be_state.

The current set of handler functions can be found in c/pbe/lib/arg_handlers.cc, c/pbe/lib/state_handlers.cc, and c/pbe/lib/file_handlers.cc. The handlers are all relatively simple, they just react to whatever events they are interested in and can then choose what to do from there. Currently, only the IIOPXX back end installs its own handler: it squelches the output of any definitions from the orb.idl file after the pres has been read in by the earlier handlers.

12.1.2 Stub Generators

Once the flow of control comes down from the high level handler functions, we finally get to generate the contents of a file. The handlers in file_handlers.cc take care of this by calling stub generation functions for each stub described in the pres_c. The functions provided by the back end library are empty for the most part since they are expected to be implemented by the specific back end: information is required about the runtime in order to generate correct code.

Type marshaling and unmarshaling stubs are generated a bit different from regular client and server stubs. Since they are rarely used, we generate them on demand to prevent excessive output. Whenever one of these stubs is needed (a call to the stub is generated in the code), we create an mu_stub_info_node and store a reference to the stub and how it is going to be used (parameter direction and byteswap flag). This node is then put into a list kept by the be_state. After all other stubs have been generated, the set of required marshal/unmarshal stubs are generated by traversing the list.

void w_stub(pres_c_1 *pres, int idx)
This will dispatch to one of the functions below depending on the type of stub. The stubs below are meant to be overridden by the back end since they tend to be specific to the runtime.
void w_marshal_stub(pres_c_1 *pres, int idx)
Writes out a marshaling stub for a specific data type. The base library defines this to do nothing.
void w_unmarshal_stub(pres_c_1 *pres, int idx)
Writes out an unmarshaling stub for a specific data type. The base library defines this to do nothing.
void w_client_stub(pres_c_1 *pres, int stub_idx)
Writes out a client rpc stub. This is left undefined in the base library; the responsibility belongs entirely to the specific back end how a client stub is generated.
void w_skel(pres_c_1 *pres, int stub_idx)
Writes out a server or client skeleton (dispatch function). This is left undefined in the base library; the responsibility belongs entirely to the specific back end how a skeleton is generated.
void w_send_stub(pres_c_1 *pres, int stub_idx)
Writes out a stub meant only for sending a specific message type (part of the decomposed stub model). The base library version causes a warning to be emitted, and no code is generated.
void w_recv_stub(pres_c_1 *pres, int stub_idx)
Writes out a stub meant only for receiving a specific message type (part of the decomposed stub model). The base library version causes a warning to be emitted, and no code is generated.
void w_msg_marshal_stub(pres_c_1 *pres, int idx)
Writes out a stub meant for marshaling a complete, specific message type (part of the decomposed stub model). The base library version causes a warning to be emitted, and no code is generated.
void w_msg_unmarshal_stub(pres_c_1 *pres, int idx)
Writes out a stub meant for unmarshaling a complete, specific message type (part of the decomposed stub model). The base library version causes a warning to be emitted, and no code is generated.
void w_continue_stub(pres_c_1 *pres, int idx)
Writes out a stub meant for continuing a delayed message (part of the decomposed stub model). The base library version causes a warning to be emitted, and no code is generated.
void do_main_output(pres_c_1 *pres)
This can be used to write out a main function for a server. By default, nothing is generated.
void make_interface_graph(pres_c_1 *pres)
This is a bit of a hack used to get information about runtime type information into source code. This is only really used to help implement corba's is_a operation. The function will write out a data structure containing information about interface inheritance which is then used by the runtime is_a function.

12.1.3 mu_state

The mu_state C++ class is the primary mechanism for creating marshaling and unmarshaling code. An object of this class is created and initialized with the necessary data structures from the pres_c and then set in motion to create the code. Basically, this code creation is done by interpreting the intentions of the pres_c nodes in the presence of specific state options, set at initialization or by context, to produce the actual code. The class itself is simply made up of a set of functions, each handling a single pres_c node. Each function does some processing on the node, which may include using the mu_state to process any child nodes. Thus, flexibility comes from overriding these functions to specialize them to the needs of a back end. Any code produced is stored in cast blocks rather than directly writing to the file, since we we may want to perform some post-processing on the generated code. Finally, the user of the mu_state takes whatever cast is left after processing and wraps it with whatever boilerplate code is necessary for a complete construct.

Walking the PRESC tree

Processing a pres_c node usually requires more information than is contained in the node itself. This is due to the structure of the pres_c tree; since it does not always encode references to cast and mint structures, these need to be walked in parallel. This is why some functions require arguments for cast and mint structures, however, a pres_c node does not necessarily have to traverse the structures. For example, a PRES_C_MAPPING_POINTER will pass down the C type that the pointer is referring to, but the mint structure is not descended since it has no representation of pointers. The functions that do not require a cast object instead work off of a collection of cast objects accessed through an inline_state. The inline_state is used to map indices from a PRES_C_INLINE_ATOM to a C structure, union, or function encoded in cast. Once, the PRES_C_INLINE_ATOM is executed the mu_state goes into "mapping" mode and the selected cast object is passed down to the mapping nodes. Once we've figured out what objects we're trying to process we need to know what we're supposed to do with them. The op slot in the mu_state is used for this; it can be set with several flags that will influence the code to do what is needed by the user. The current set of flags is:

MUST_ALLOCATE
Memory should be allocated whenever it is needed. For a server stub, the space for the unmarshaled `in' parameters and "root" space for the `out' parameters that will be filled in by the work function should be allocated at this point. For a client stub, the results contained in the reply message may need to have space allocated so they can be returned to the client program. In addition, this iterator should deal with any setup necessary to begin an rpc.
MUST_ENCODE
Appropriate values should be marshaled into the message stream. Given the data values as parameters to the client stub or as return parameters from the work function, they are serialized into a data stream according to the requirements of the message format and data encoding scheme in use.
MUST_DECODE
Appropriate values should be unmarshaled from the message stream. The serialized data stream is converted into the data values that can be returned by the client stub or handed to the work function, according to the message format and data encoding scheme in use.
MUST_DEALLOCATE
Memory should be deallocated when it is no longer needed (where appropriate). More generally, this is for any type of cleanup necessary to complete an rpc.
MUST_REQUEST, MUST_REPLY
These indicate that the current state is handling a request or reply message, respectively. This knowledge is implicitly held by the other flags and whether client or server code is being generated; however, it is more convenient to have an explicit flag to represent this. Naturally, these flags are mutually exclusive.

Marshaling Buffer Management

Stubs generated for non-trivial rpcs generally need some kind of marshaling buffer or stream, into which messages are marshaled or from which messages are unmarshaled. Sometimes a message can use multiple marshaling buffers. The format of the marshaled data in the buffer(s) depends on the encoding scheme being used by the back end (e.g., xdr is an encoding scheme), and by the mint interface definition for the rpc in question. The format of the marshaled data does not usually depend on any aspects of presentation.

Some transport mechanisms can transfer an "unlimited" amount of data in one message (e.g., Mach 3); others impose some arbitrary limit (e.g., Mach 4). Flick stubs are generally expected to be able to handle an unlimited amount of data, regardless of any limitations of the transport mechanism. Fixed-length arrays are generally handled as merely a degenerate case of variable-length arrays: the two types of arrays are identical except that fixed-length arrays use a "degenerate integer" data type with only one possible value as their length data type. Thus, fixed and variable-length arrays can usually be marshaled in exactly the same way; the simple-integer-marshaling code will automatically handle the degenerate length "variable" in fixed-length arrays (see mu_mapping_direct.cc).

The back end support library assumes that code generation can be done by traversing the type trees in a certain "natural order":

MINT_STRUCT
Marshal each element of the struct in the same order in which the elements appear in the presented structure. Note that the element ordering in the MINT_STRUCT may be different from that in the presented structure, which may in turn be different from that which is encoded on the wire.
MINT_UNION
Marshal the union discriminator first, then marshal the selected union case.
MINT_ARRAY
First marshal the array's length (if non-constant), then marshal each element in order, lowest-indexed element first.

This ordering is not mandated; it is the responsibility of the back end to choose an appropriate on-the-wire layout for the data, as the needs of a particular transport mechanism dictate. For example, just because the type graphs are traversed in the order specified above does not mean the data must always appear in memory or on the wire in exactly this order; however, it is generally easier to write the back end if the layouts match.

Generated Code

The actual stub code that gets generated is made up of a number of macro calls and some control flow code. The macros are all defined in the runtime header files and follow similar naming and calling conventions across implementations.

runtime/headers/flick/pres
These headers are specific to the presentation implementation; they provide macros for things like handling exceptions and memory management. This is also where any scml files should be located.
runtime/headers/flick/ encode
These headers provide macros for encoding and decoding data into and out of the message buffer.
runtime/headers/flick/link
These headers provide macros for managing the message buffers and adding any link level message data.

When the mu_state generates these macros it uses the op flags and names for the presentation, encoding scheme, and protocol (link) to determine the names of the macros. For example, the macro to encode an 8-bit character in cdr would be flick_cdr_encode_char8(), and the macro to decode would be flick_cdr_decode_char8(). Unfortunately, this approach can cause some problems because it restricts what parameters we can pass to the macros, especially presentation specific macros which can vary greatly.

Also note that much of Flick's flexibility is actually due to the implementation of the macro calls. While the names change between back ends, often the series of macro calls is very similar, and thus a significant amount of the implementation comes from the definitions of the macros.

12.1.4 client_mu_state and target_mu_state

The client_mu_state and target_mu_state C++ classes inherit from the base mu_state class and describe how to marshal the client and target object references, respectively. Most presentations do not have a client reference, and most protocols do not have an explicit representation for them. The client reference is necessary in the decomposed stub presentation, and thus must be handled by the protocols and runtimes that support decomposed stubs.

The reason these are special states is that the client and target references are often encoded differently than other object references; for instance, they may be placed at a known location within the message rather than encoded in the midst of other parameter data. Since a single mu_state can only specify one method for handling object references (e.g., the handling of an arbitrary object reference parameter), the special client and server states provide the mechanism in which the client and target references can be handled specially and separately.

12.1.5 mem_mu_state

The mem_mu_state class is an extension of the basic mu_state class, intended for use by typical back ends where parameters are marshaled (at least partly) into variable-length memory buffers of some kind. It deals with things like buffer management, alignment, endian conversion, etc.

For code optimization purposes, marshaled messages are logically divided into globs, then subdivided into chunks.

A chunk is a sequence of bytes in the marshaled message whose length and internal format is known. For example, a fixed-length array of bytes could be one big chunk; an array with variably sized elements would not be a chunk, but each individual element in that array could be. Chunks are used primarily for optimization of data packing/unpacking code: once a chunk is started, no alignment checks or pointer adjustments need to be done between successive primitives in the chunk.

A glob is part of a message whose maximum possible length is a "smallish" compile-time constant, even though the actual length may not be constant. For example, a variable-length array of bytes with a maximum length of 32 bytes would be a good candidate for being lumped into a single glob, whereas an array with variably sized elements and an unlimited length would not be: instead each individual element of the array could be a separate glob in that case. (The definition of "smallish" is defined by the back end: each back end defines some maximum glob size, usually on the order of a few kilobytes.) Globs are used to optimize marshaling buffer management: once "enough" buffer memory is allocated at the beginning of a glob, the marshaling code within the glob can simply bump through with a pointer without having to worry about buffer space again until the next glob.

12.1.6 mu_state_arglist

The mu_state_arglist class is used to help process some pres_c nodes by coalescing information from their children. For example, an allocation context node has children representing the length, buffer pointer, and possibly other attributes which need to be used together in order to do the correct allocation. However, since this information is only known by the children, the arglist provides a way of passing it back up to the parent (and/or subsequently down to other siblings) so it can be used. The actual filling of the arglist is taken care of by a PRES_C_MAPPING_ARGUMENT node on the path to the child which will capture the current cast expression and type and store it in a particular arglist and argument.

mu_state_arglist(const char *name, mu_state_arglist *aparent = 0)
Constructs an arglist with a name and optionally connected to a parent (the new arglist must have a unique name (it cannot clash with a parent, grandparent, etc.).
~mu_state_arglist()
Destructs the arglist by freeing the list of arguments and any other information.
void add(const char *aname, const char *arg)
Adds an argument to the named list.
void remove(const char *aname, const char *arg)
Removes an argument from the named list.
int getargs(const char *aname, const char *arg, cast_expr *expr, cast_type *type)
This will attempt to find the argument, arg in the arglist, aname. If the argument is found, the associated cast expression and type are returned with a nonzero result. If the argument or arglist cannot be found, the method will return zero, and *expr and *type will be set to NULL.
int setargs(const char *aname, const char *arg, cast_expr expr, cast_type type)
Similar to getargs except this will set the value of argument. The return value is true if the argument was found and set, zero otherwise.
argument *findarg(const char *arg)
This is a private function used to locate an argument inside a single list (it does not look at parents).

12.1.7 mu_abort_block

The mu_abort_block class is used for tracking any error handling code in a stub. Thus, we're able to do a proper recovery from any possible errors that occur during stub execution, such as a lack of memory or runtime error. To accomplish this task the class provides functionality to add code for handling an error, and then getting an "abort label" which the stub can jump to during execution if the error occurs. The abort handling code is then dumped at the end of blocks and then linked together with gotos. Finally, the code is reduced to only contain those blocks that are reachable.

void set_kind(int kind), int get_kind()
Set/get the kind of this abort block. The possible kinds are:
MABK_NONE
No kind, this is illegal.
MABK_THREAD
The abort block is a simple thread of execution, one statement after another.
MABK_CONTROL
The block has a unique control flow amongst its children.
MABK_CONTROL_IF
The block is an if statement, with the only child representing the true branch.
MABK_CONTROL_IF_ELSE
The block is an if/else statement, where the child with an expression is the true arm and the one without is the false arm.
MABK_CONTROL_SWITCH
The block is a switch statement with each child representing a case.

void set_expr(cast_expr expr), cast_expr get_expr()
Set/get the expression associated with this block. The expression is used by the parent control block to make a link to this child (for example, the expression used in a case).
void set_reaper_label(cast_stmt reaper_label), cast_stmt get_reaper_label()
Set/get the reaper label for this block. The reaper label is the label to jump to at the end of the control flow for an abort block.
cast_stmt get_block_label()
Get the cast_label associated with the abort block's cast_block. The label's statement is actually the block, and is used for external references to the start of the block. Any internal references will just be the first label in the block.
char *use_block_label()
Use the block label, this will increment the use count in the cast_label and return the label string.
void drop_block_label()
The opposite of use_block_label, this will decrement the use count in the cast_label.
void add_stmt(cast_stmt stmt)
Add a statement to the abort block.
int stmt_count()
Return the number of statements in the block. This is really meant for internal use to figure out if a child block really does anything and needs to be linked to by the parent.
void add_child(struct mu_abort_block *mab, int connection)
Add a child to the abort block using a certain type of connection, which can be:
MABF_INLINE
This indicates that the child's cast block should be inlined directly inside the parents.
MABF_OUT_OF_LINE
This indicates that the child's cast block is located somewhere else in the code and a goto should be used to reach the block.

struct mu_abort_block *find_child(char *child_label)
This will locate the child with the given label.
cast_stmt get_current_label()
Returns the current label string.
char *use_current_label()
This will return the string corresponding to the current label name and increment the use count in the cast_label.
void grab_label(char *label)
Locates the given label name and increments its use count.
void drop_label(char *label)
Locates the given label name and decrements its use count.
void begin(), void end()
These perform initialization and finalization around any additions of statements and children.
int rollback(int has_path = 0)
This function will walk through the abort block and all of its children trying to remove any dead code. The has_path argument signifies whether or not a path begins with a flow of control, and should almost always be left at zero when called externally. Basically, this function goes through each statement and tries to figure out if there is a path to that statement. If there is no path, then it turns the statement to a CAST_STMT_EMPTY; otherwise the statement is left alone. Determining the possibility of a path is done by looking for any cast_labels that are used, a prior statement that has a path, or a path from a parent.
int rollback_block(cast_block *block, int start, int len, int has_path)
This is a helper function for rollback; this just works on cast_blocks. The return value indicates the possibility of a control flow path.
int rollback_stmt(cast_block *block, int pos, cast_stmt curr_stmt, int has_path)
This is another helper function for rollback, and works on cast_stmts. Again, the return value indicates the possibility of a control flow path.
void make_dispatch()
This is a private function used to create any code for MABK_CONTROL_* kinds. Depending on the kind it will produce the statements needed to do the test and dispatch to the correct child block.

12.1.8 mu_msg_span

The mu_msg_span class is used for putting buffer space checks into the stub code so it does not try to decode something that is not there. The term "span" is used to describe a message segment of an exact size that can be larger than a chunk, but not an approximation like a glob. The class is used to describe each span in the message and join them together into a tree which can then be reduced in a second pass. This reduction pass is used to merge checks together and to lift them out of array loops, if possible. For example, a union where each arm is the same size can be reduced to a single check before unmarshaling the union, rather than a check for each arm.

Using the spans depends on the format of the message that is being unmarshaled at the time. Normally, simple message segments are handled automatically by piggy-backing on the chunk functions in the mem_mu_state. This allows us to figure out how big a segment is, but we do not necessarily know how to join it into the rest of the message. This merging is done by creating another mu_msg_span which acts as a parent to a set of segments, and then setting the kind of the span to one of the formats described below.

void set_flags(int flags), int get_flags()
Set/get any flags. Currently, the only flag is MSF_ALIGN which indicates that the span follows an alignment so it cannot be absorbed by any previous spans.
void set_kind(int kind), int get_kind()
Set/get the kind. The possible kinds are:
MSK_NONE
These contain the actual lengths of a span and are usually added to one of the other kinds.
MSK_SEQUENTIAL
These contain other spans which are organized in a sequential fashion.
MSK_UNION
These contain other spans which are organized in a union, so each child is a different possibility in the union. Usually, the children are MSK_SEQUENTIAL spans which then contain the actual span lengths.
MSK_ARRAY
These contain another span which indicates the size of each element in the array, and the size of the array itself is contained in the this object's size_expr.

void set_block(cast_stmt block), cast_stmt get_block()
Set/get the block where any checks should be added.
void set_abort(struct mu_abort_block *mab), struct mu_abort_block *get_abort()
Set/get the abort block to use for getting labels.
void grow(int size)
Grow the size of the span by a fixed size.
void shrink(int size)
Shrink the size of the span by a fixed size.
void set_size(cast_expr size)
Set the size of the span, which may, or may not be fixed size. If this is a MSK_ARRAY, then this expression is used as the length of the array.
void add_child(struct mu_msg_span *mms)
Add a child span to this one.
void begin(), void end()
These perform initialization and finalization around any size modifications (growing, shrinking, adding children).
void drop()
Removes the check and drops the used abort label.
void collapse()
This is called on the root span object to go back through all of the children to optimize the sizes of the spans. For sequential spans it will walk backwards through its children trying to absorb the sizes into the previous span nodes, until it hits a variable sized node which cannot be absorbed by the previous child. For unions, it will find the minimum size of all the cases and use that as the size for the entire union. For arrays, if the length is constant it will pull all the checks out of the body of the loop and do a check at the start, otherwise it will just leave the checks in the body. Any check statements that reduce to zero are changed to CAST_STMT_EMPTY.
void commit()
This is done after the call to collapse, it basically does the final lock-down, any checks that have a size are set, empty checks are dropped.
static void set_be_name(const char *name)
This sets the back end name that the spans will use when producing macro calls.

12.1.9 Implementing Presentation Functions

The presentation generator is able to create "presentation functions" which have simple implementations and do not require optimization. However, the presentation generator cannot provide the implementations since they may be dependent on the runtime or other things specific to a back end. The current solution is to store a simple description of the presentation function, a semantically loaded string, in the presentation attributes tag lists. These tag lists can then be processed by the back end to produce the needed code. However, writing printfs for each function can become complicated and tedious, so the back end library provides a set of classes for semi-automatically passing this work off to scml.

presentation_impl
This class is used to bind a tag in the presentation attributes to a set of C++ functions and/or an scml macro. The scml macro is determined by the idl_type and pres_type which are nested scopes in scml, and finally the macro is selected based on the string used to describe it in the presentation generator.
presentation_collection
This class walks over the tag lists in the presentation attributes and then asks a set of presentation_impl objects to implement whatever is described.

This approach is not always necessary since the scml interpreter can also be called explicitly to execute macro's. See file prologue and epilogue handlers in file_handlers.cc for an example.

12.2 Specific Back Ends

The back end library is not able to produce working code by itself, so a separate back end program must be created which provides its own functionality in addition to the library, specializing the generic code generated by the library into the specific code necessary to implement the encoding and transport layers required by a specific protocol and runtime system. These back ends are located in the c/pbe directory and are all structured very similarly.

12.2.1 The Basics

The common tasks done in a back end are to create subclasses for the be_state and the various mu_states that implement the correct behavior for the back end's encoding and runtime. Once these classes have been filled out the stub writers are overridden to do some actual work. Coding these stub generators is simply a matter of using the mu_states to generate cast blocks which are then bracketed by the appropriate runtime code. Unfortunately, since all of this is relatively similar across all back ends, it has become common practice to copy an existing back end and then change all the names and printfs to your needs. This results in a fast start up time; however, this quickly turns into a maintenance nightmare. Hopefully in the future the redundant code in the back ends will be consolidated so that maintenance will be easier.

12.2.2 Decomposed Stubs in IIOP and Khazana

Decomposed stub generation is an option of the presentation generator that causes regular client stubs to be split into separate stubs (see Section 11.1.2 for more information). Handling this kind of presentation is done simply through implementation of the appropriate stub writers (for a detailed list, refer back to Section 12.1.2, assuming the runtime can support it. They work basically the same as the regular client and server stubs, with the functionality of the stubs split into separate functions. However, only the iiop and Khazana back ends are currently able to handle this presentation.

12.2.3 Presentation Implementation in IIOPXX

The IIOPXX back end is responsible for generating code specific to TAO, so anything that is implementation dependent must be handled before outputting any code. For example, a string inside of a structure needs to be of the "TAOManagedString" type and not a "Stringvar" like the PG will create since it does not know the implementation. The solution is to use the presentation implementation facilities to do any preprocessing. This type of code has all been dumped in the file c/pbe/iiopxx/tao_impl.cc. Currently, this code ranges from generating CORBA::TypeCode to modifying the structs and classes created by the presentation generator. Any associated scml code is located in runtime/headers/flick/pres/tao_*.scml. Eventually, it would be nice to organize the mess present in tao_impl.cc, but exactly how best to do it is unknown.

12.3 Summary and Comments

The back end code generators are one of the most complex pieces of Flick, mainly because of the number of choices that must (finally) be made. While the current suite of back ends is impressive, particularly in their variety and capabilities, there are a few design changes that might make them even more flexible and useful. Following are a few ideas for improvement:

Reduce/Remove Dependence on Macros.
Although a lot of flexibility arises through our use of macros, the macros themselves can be hard to write and become a burden to maintain. Ideally, the back end should generate code and not a list of macro calls.
Split the Encoding Mechanism, the Protocol, and the Transport Mechanism.
Ideally, these could all be independently chosen (and independent of the presentation as well) for maximum flexibility. For example, it would be conceivable to want onc rpc-style messages using cdr encoding over a Trapeze/Myrinet link. To some extent, the current design allows some flexibility: the Trapeze back end and runtime support either iiop-style messages with the cdr encoding or onc rpc-style messages with the xdr encoding. However, most of the details are taken care of through macro magic in the runtime header files, and the encoding/message format binding is still fixed. It may be possible in some cases to change the encoding or transport protocol simply by redefining the macros for a particular back end. However, this is a rather daunting task and often requires at least some tweaks to the back end code generator. In the future, it would be nice to have each aspect of the generated code in some form of encapsulated module that could be either statically linked in as part of the back end, or perhaps even chosen at runtime by the user.
Better Define the Separation of Presentation From Implementation.
While it is clear how a corba exception might be encoded using iiop and cdr, it is not defined how such a construct might be sent in an onc rpc-style message. Some sort of conversion must be done between the presentation and the encoding or message format layers to allow for the abstract presentation entities to be encoded according to the link-layer requirements. See Section 13.4 for more discussion on this as it relates to the runtime.