Introduction and Limitations

The purpose of this tool is to automate the process of "stack ripping" as described by Robert Grimm or Atul Adya, to reduce the probability of errors while converting code from a threaded to an event-based execution model. Before explaining its use, then, it would probably make sense to address a simple question: what does it do to your code? The answer comes in two parts. First, parameters and local variables (including those in nested blocks) are moved into a context structure. Names are disambiguated during this process, so that if you originally had two variables named "foo" in different scopes one of them would get renamed to foo0...not that you should care much, because you never need to write code that accesses foo0. If you rip a function named foo, this structure would be named foo_Request which then - possibly along with other similar structures from other ripped functions - becomes part of a general Request structure that you must define. Second, the code itself is modified in several ways:

The ripping tool generally accepts anything that gcc accepts within a function, with only a few exceptions:

Both the context-structure declaration and the modified function code are produced as output, with the first enclosed in an '#if defined(NEED_STRUCTURE)' block and the second in an '#if defined(NEED_FUNCTIONS)' block so other code can select one or the other.


So, you've defined a function that's within the limitations described above. How do you actually use the tool to rip it? Obviously the first thing you have to do is replace any synchronous blocking function calls that it makes with calls to asynchronous versions followed by calls to the block() pseudo-function. The details of that part are up to you. Next you need to get gcc to create a parse tree, with a command-line something like this:

gcc -c -fdump-translation-unit hello.c

This will create a file named hello.c.tu for you, containing a full parse tree for everything in the file. Your next step is to run the ripping tool:

python hello.c.tu myfunc > ripped.c

Note that you must specify the .tu file, and also the name of the specific function to rip. You can rip multiple functions from the same .tu file if you like, with separate ripper invocations. The output of this step is one or more files containing ripped functions and their associated context-structure definitions.

At this point you need to know how to integrate the ripped code into your program. The first part of this is to define a Request structure, because the ripped function needs a complete one. Here's the one we define in the example directory:

typedef struct {
	StackFrame		stack[4];
	unsigned int		stackDepth;
	GetDatum_Request	GetDatum;
} Request;

This defines a Request structure that contains the context for a single ripped function, GetDatum. The stack stuff points to the other thing that you need to do: provide an implementation of a function called AddContinuation. The signature for this function looks like this:

AddContinuation (Request * req, void (*func)(Request *, int), int arg)

Whenever the ripped code needs to block, it calls AddContinuation and returns instead. It's up to your code to store this function-pointer/integer tuple on a stack somewhere, as is done using the stack and stackDepth fields in the example. Any code that wants to continue a call - e.g. your poll/select code when it receives a new message and has associated it with a particular request - just needs to call the specified function with the request pointer and the provided integer argument to do so. The ripped code takes care of the rest.

You're almost done. Now anything that calls the ripped function needs to define/initialize a Request structure and pass it as the first argument, which should be pretty easy. Don't worry about initializing parameter fields; the ripped code does that part automatically. All you have to do now is compile the ripped code, but that will only work if your code is structured a certain way. Remember, the file containing the ripped code contains only that function plus its context structure, and any non-trivial function will have all sorts of dependencies on external declarations. You have two choices here. The first is to use the preprocessor to compile the same file twice:

// Declarations and stuff.
#if !defined(RIPPED)
myfunc (blah blah blah)
#include "ripped_version_of_myfunc.c"

That method can actually confuse makefiles pretty badly, because when you ran gcc to create the .tu file it created a .o as well. If you look at the example (or the unit tests), you'll see that a different approach was used. All of the declarations etc. have been moved into client_common.c. The file (client1.c) containing the original unripped function declaration then #includes that along with any other headers it needs, so gcc will be happy when it's called to generate the .tu file. The file (client2.c) that gets compiled after ripping is all includes - the same ones as previously plus the file containing the ripped function. This is a little inconvenient, but it creates two different .o files so the makefile doesn't get confused.