Tasks
Headers:
Typedefs
Prototypes (per-interface, per-operation, per-type).
Common:
Allocation & freeing (per-type).
Stubs:
Stubs (per-operation)
Short-circuiting
Setup
Marshalling
Exception & location_forward handling
Sending
Receiving
Freeing/allocating values
Demarshalling
Return
Skeletons:
Skeletons (per-operation)
Allocating values
Demarshalling
Call
Setup for reply
Exception handling
Marshalling
Sending
Freeing values
Return
(de)marshalling tasks
All (de)marshalling tasks can be represented using the following type of data
structure (PIDL):
union MarshalNode switch(NodeType) {
case VALUE: {
string variable_name;
short value_length;
}
case CONSTANT: {
long value;
}
case ARRAY: {
string pointer_name;
MarshalNode num_entries;
}
IDL_tree corresponding_idl_node;
};
Need to make a data structure that better represents "what we need to do"
rather than "what we are working on". Something that can say "(de)marshal
value|increment a pointer|loop back"... Optimization on this will be somewhat
easier.
/* In skels: If not sending a reply back, don't get a send_buffer
unless we need to raise an exception. */ Incorrect - for any given op
you either always need to send something back, or never.
Pieces of a stub (prerequisite steps are in []'s):
Pre:If ev == NULL, bomb out.
1.Sanity check params (and handle exceptions).
2.Check for & handle local call. [1]
3.Get connection (and handle exceptions). [1]
4.Get send buffer (is it possible for any exceptions to be raised here?).
5.Marshal params. [1, 4]
6.Write send buffer (and handle exceptions). [5]
7.Unuse send buffer. [6]
8.Receive reply (and handle exceptions). [6]
9.Do any necessary location forwarding & exception handling. [8]
10.Allocate any needed memory for retvals.
11.Demarshal retvals (with versions for endianness) (and handle exceptions). [9, 10]
12.Unuse receive buffer. [11]
13.Return. [11]
Could swap 2 & 3, or 7 & 8. 11 & 12 can't be swapped due to 12's flow
changing effects :-). Don't want to swap 2 & 3 - don't do remote stuff
it is local. 7 & 8 just maybe could be swapped, but not sure it makes much sense.
Pieces of a skeleton (prerequisite steps are in []'s):
0. Allocate any needed memory for params.
1.Demarshal (with versions for endianness) (and handle exceptions). [0]
2.Call impl. [1]
3.Get send reply buffer (can this raise any exceptions?). [2]
4.If no exceptions raised:
Marshal retvals. [3]
else marshal exceptions. [3]
5.Write send buffer (and handle exceptions). [4]
6.Unuse send buffer.
No pipelining possible in skeletons.
Actually, should integrate memory allocation with demarshalling - it'd
be cleaner to do (if not a little more space wasting when it comes to
dual versions for endianness).
You can marshal multi-element pieces of structs.
In skels: If not sending a reply back, don't get a send_buffer unless
we need to raise an exception.
When demarshalling, maintain a local 'cur' pointer rather than modifying
the one inside the receive buffer. Will need to update the one inside the
rbuf if we call a complex demarshaller thing.
Reduce amount of code in stubs by having send_buffer_use() return the
request_id as part of its operation, instead of having to get a new
request_id as part of the stub.
Use function pointers instead of a series of if's to speed things up.
Pre-computation of alignment for stuff that can be pre-computed (like
operation name vecs, etc.)
For multithreading speedups, don't lock the connection until we actually need to send.