Data Center Works Inc

Strength-Reducing the Task of Porting

Mathematicians have a very good way of solving impossible problems: they turn them into ones they already know the answer to. They call this “strength reducing”.

This isn't limited to math. For example, we just had an engineer on a project to port a 32-bit driver to run on a 64-bit system. Porting drivers is something of a evil task, with a lot of unfamiliar requirements. We'd therefor prefer to reduce the “strength” to something we already know how to do.

While we can't make the whole task of porting easy, we can turn part of it into something every developer is used to doing: fixing compiler error messages.

The Classic Development Cycle, Slightly Warped.

When developing anything, part of the task is the cycle of compiling, getting a syntax error from the compiler, fixing it and recompiling. That's usually one of the easy parts of developing: the hard part is thinking up good unit tests, testing, and fixing the ensuing semantic errors.

So for the last eleven years, we've been strength-reducing a big chunk the porting process into a cycle of getting compiler-style syntax errors and fixing them. And that's exactly what we'll do here.

For this particular example, many of the changes are to switch to calling 64-bit functions or functions which have 64-bit parameters. One of the 56 changed functions is “timeout”, which changed from

timeout_id_t timeout(void (*func)(caddr_t), caddr_t arg, long ticks); 


timeout_id_t timeout(void (*func)(caddr_t), caddr_t arg, clock_t ticks)

We see the “ticks” variable has been changed from a long to a clock_t. This means that whether it is compiled on a 32-bit or a 64-bit system, it will always get the correct size of variable. Without this change, the 32-bit driver would have assumed it was using a 32-bit time variable and the 64-bit driver would have a 64-bit one, and one or the other would be wrong.

The program we use is called port, and it reads a B, C or C++ file and produces output in standard compiler error-message format, such as

"64bitDriver_test.c", line 59: timeout
“64bitDriver_strategy.c”, line 352: timeout

An engineer can sit in his favorite editor, run port instead of cc and use her editor's “go to next error” function to step through a list of errors like this, fixing them. The fix to use comes from the port2report script, which describes the bug in more detail:

53) timeout 
timeout_id_t timeout(void (*func)(caddr_t), caddr_t arg, long ticks) has become
timeout_id_t timeout(void (*func)(caddr_t), caddr_t arg, clock_t ticks);
#include <sys/types.h>
#include <sys/conf.h>

timeout_id_t timeout(void (*func)(caddr_t), caddr_t arg, clock_t ticks);

From this they can see that they need to change long to clock_t in the current line she's at. Port sorts the errors by function-name, so all the timeouts will appear one after the other, even if they were found in different files, so the engineer can fix all the timeouts, and then go on and fix all the uiomoves, and so on.

The example provides

Making the Compiler Find the Next Bugs

The next big part of a 32-64 bit port is fixing data type mismatches, ints mistakenly used for longs and the like. Since we've just gone through the program and changed all the data types used as parameters, the compilers will happily report if there are type mismatches between our correct usage and the old code in the driver.

In a a sample program with an old (32-bit) declaration of timeout, cc reports:

"sample.c", line 14: warning: argument #1 is incompatible with prototype:
        prototype: pointer to function(pointer to void) returning void : "sample.c", line 7
        argument : pointer to function(pointer to char) returning void
"sample.c", line 15: warning: improper pointer/integer combination: op "="

Lint, with the -errchk=longptr64 and -s options will report:

"sample.c", line 14: warning: argument #1 is incompatible with prototype:
        prototype: pointer to function(pointer to void) returning void : "sample.c", line 7
        argument : pointer to function(pointer to char) returning void
"sample.c", line 15: warning: improper pointer/integer combination: op "="
"sample.c", line 15: warning: conversion of pointer loses bits

The first two lines of either the cc or lint reports identify a mismatch: the prototype in the .h file declared a void function(void *) as part of the call. This was the correct code, sucked in from the up-to-date include file. The old code passed a void function(char *), which is clearly wrong.

This points the engineers to the variable that was wrong, making it easy to find and change. In effect, it did another strength-reduction. Instead of the the problem of finding data size differences, we only have to do the lesser task of fixing syntax errors in declarations.

How Long Will this Take?

Remember back in the description of the timeout function we had a weight of 10? That means that it's relatively easy: we need to substitute the new code, and check for side-effects such as the type change we saw in the example.

A score of 5 means is really easy, just substitute the new function for the old, and 25 means that you will need to add or substitute some code around it. An example of a function with a weight of 25 is ioctl, whose parameters need to be checked at runtime, as described in the example and comments for it in the database. A score of 50 or 100 means you have to write a new function, respectively easy and hard.

Once you've done a few ports using this tools, you rapidly learn how many hours a total weight of 560 means, and for a port that doesn't include extraneous tasks, how much you need to quote to the customer. And yes, we indeed do fixed-price ports, because we've used this scoring technique since 1996, when we wrote the first porting tool of this family, and have a well-practiced process wrapped around it.

The port.c program and the 64bitDriver.ref file are available at our tools page.

How Much is Left?

Well, not a lot, but that part is reviewing the code for problems only a human can see, linting it, designing a test harness and doing the tests on a device driver. That's still hard work, but with most of the function and declaration bugs already fixed, it's a lot shorter.

A rule of the thumb is that the tools will remove 80 percent of the drudgery, leaving you 20 percent of the hard bugs for you to find.

Of course, Murphy's law says that if you're doing a driver, fixing the remaining 20% of the bugs will take you 80% of the time you budgeted for the whole project. Mind you, that's still far better than taking 80 + 80 = 160% of the time you planned for.

And that's a strength reduction even your management will love.