COMPILING THE COMPILER
Since the Small C compiler is really just another Small C program, we follow the steps in Chapter 16 to compile it just as we would any other program. The only difference is the number of source files and their names. Whereas Figure 16-1 illustrates the general procedure, Figure 17-1 is specific to the compiler itself.
CCC 3General Advice
CC.H contains #define statements that pertain to the compiler as a whole. Changes to this file usually require that the entire compiler be recompiled. However, by keeping track of which symbols in CC.H have changed, we can use a text editor to perform a global search on each of the source files to determine which ones must be recompiled.
The symbol DISOPT is contained in a #define directive in CC4.C where the code generation and optimizing logic resides. Currently the directive is contained in a comment so that it will be ignored by the compiler. However, if we remove the comment delimiters and recompile CC4.C, the new compiler will list on stdout the frequencies of the optimization cases it applies. This is handy when working with the optimizer; it helps us to decide whether or not a given optimizati on is likely to be worth its cost in performance and compiler size. There is no point in having the optimizer look for cases that seldom arise.
Notice in Figure 17-1 the new executable compiler CC.EXE replaces the previous one. This has ramifications. What is the probability that the new compiler will work properly? Closer to zero, no doubt. So we have just replaced our production compiler with something containing bugs. Failure to realize this, when we recompile the compiler to fix it, leads to interesting results. The buggy compiler may run without a complaint. And the new compiler may also link properly. But when we run the new compiler, look out. Chances are that it will go berserk--if our bug caused bad code to be generated that is. If we are on our toes, we will recognize the symptoms and realize what happened. But the symptoms of many compiler bugs are not obvious, and some are not even predictable.
Of course, we have a copy of the original, unaltered compiler somewhere, do we not? Sure we do. So we can restore CC.EXE from that copy and proceed to recompile our fixed compiler properly. To avoid the loss of time, we will probably want to keep a reliable copy of the compiler handy and make sure to use it for each new compilation. Eventually, when we are convinced of the dependability of our revised compiler, we will want to begin using it as our production compiler so future versions of the compiler can incorporate new features that are supported by the revised compiler.
Now, suppose we add support for some new constructs. We place the new compiler in production status, and proceed to use the new constructs in future revisions of the compiler. By making the source code dependent on the enhanced compiler, we are committing to its reliability. If we should get down the road a way, and discover that our production compiler has problems, then falling back to the previous production compiler will not work because the old compiler will choke on the new constructs which we have put in the source files. At that point we will have to resort to some messy patching of the source files to make them acceptable to the old compiler. This will probably involve commenting out logic that implements enhancements and/or rewriting the new constructs in the old way. We must then fix the previously undiscovered bug, recompile, test, reinstall this as the production compiler, remove the temporary patches, and recompile again. Finally, we will be back where we were. The point is that time saved in testing may well be spent in even more disagreeable ways.
At this point, we know the Small C language, we know its repertoire of library functions, we know how to use the compiler, and we know how to compile new versions of the compiler. Now we come to the interesting part--what goes on inside the compiler.