[EROS-Arch] Conversion to C

Jonathan S. Shapiro shap@eros-os.org
Sat, 13 Jan 2001 10:47:19 -0500


> Pardon my ignorance.  I haven't been following the list too
> closely to understand the reasons EROS is being rewritten
> in C.

I now have a more thorough answer, so I'm replying in detail. For a record
of the earlier conversation on this topic, see the email archives at

    http://www.eros-os.org/mailman/listinfo

The messages are archived in the eros-arch archive. Look for the early
messages with the "Conversion to C" subject.

> Is it a performance issue?

Indirectly, C++ is a performance problem. The C++ compiler generates code
for exceptions unless this feature is disabled. The exception handling code
has performance consequences. In particular, it restricts various kinds of
code motion that the optimizer would like to perform.

In a more immediate sense, however, the exception handling code also leads
to greater contention in the instruction cache, and we are fighting fairly
hard to keep the entire EROS working set in a small fraction of the I-cache.
The EROS kernel does not generate exceptions (ever). Unfortunately, the
compiler must compile under the assumption that operator new/ will be called
somewhere and must therefore generate exception handling code. Typically,
about 1/3 to 1/2 of the total code generated is exception handling code.
Bjarne argues that this code is never executed, and he is mostly correct.
Unfortunately, this code is interspersed with the ordinary code, so it
changes the cache contention behavior. [Note that this could be fixed by
collecting the exception code into a seperate code segment at the end of the
application, and perhaps I should suggest this to the G++ team.]

At the moment, we disable exception generation by passing extra options to
the G++ compiler. The problem with this is that we are no longer compiling
in standard C++. We are compiling in an odd extension that happens to be
supported by G++. I am concerned both about code portability and about code
clarity.

Finally, name mangling schemes in C++ are not consistent across compilers,
so writing assembly code that calls C++ with some semblance of portability
is a problem. You can call a procedure that uses C linkage convention easily
enough, and this can call the corresponding C++ procedure, but the extra
call is undesirable overhead.

> Did you find that even with
> C++ your code was largely functional?

No, but it is almost entirely *procedural*, which is probably what you
meant. While there are many member functions, these could equally well be
coded as C procedures taking a struct pointer. The current kernel makes
essentially NO use of inheritance, which is what you would expect in a
microkernel -- if you have complex enough structure to need inheritance, you
aren't building a microkernel.

The one place where inheritance was used (there is a Link structure for
doubly linked list chains, but this doesn't qualify as serious) was in the
driver code. Here it proved to be a mistake, because drivers need something
more flexible -- something much closer to Java-style interface pointers. The
difficulty here is that the C++ type checking is actually too strong. You
can declare a pointer to a member function, but there is no way to declare
"pointer to object s.t. object has a member function of type T". In fact,
what you really want to declare is a pair of the form

    (X *, RetType (*X::someFunc(...args...))

without being obliged to ever say what X would be. Note that the compiler
doesn't need to know. It really only needs to know that X* will be an
appropriate thing to use as a /this/ pointer in an invocation of
X::someFunc(). What's needed here is something like ML pattern matching, and
C++ doesn't have it.

The result is that there is no way to capture interface dispatch gracefully.
In C, it is easier to do this because you can declare X* to be a void
pointer. Actually, what is really going on in this case is a violation of
the static type system in C++.  In summary, using inheritance was a mistake.

A secondary issue concerns the Process class. The vast majority of the
kernel code (not surprisingly) is concerned in some form with manipulating
processes, and in particular with manipulating the state of the *current*
process. At the moment, most of this code is written using member functions,
and this is a mistake. The cost is that the /this/ pointer must be copied
around on the stack a lot, when the code in this case should really be using
a per-processor global variable. This may not seem like a big deal, but it
really does add up. It also serves to significantly reduce clarity of the
code.

> I guess it interests me because of the general premise that
> OO programming with an OO language produces a
> more flexible and reuseable code base.  Is this premise not
> necessary when considering microkernels?

If you look at it in a certain way, a microkernel is what you get after you
remove all of the code that can be modularized. What is left, almost by
definition, is the stuff that is so hopelessly and intimately
self-referential that it is almost certainly not reusable in any way at all.
Arguably, if it *is* reusable you should still be taking things out.

Flexibility doesn't come from using an OO language. Flexibility is a
consequence of a clean architecture and design that has been realized in
appropriately structured code using appropriate interfaces. For many
(perhaps mosst) programmers, using an OO language helps them achieve this
kind of structure and interface discipline. Kernel hackers (arguably) work
on some of the most challenging and difficult code out there -- there is no
debugger, there are gobs and gobs of race conditions, there are hundreds of
things that can go wrong, and your primary diagnostic for impending doom is
that the machine spontaneously reboots. At the risk of a gross
generalization, there are two kinds of kernel programmers: kernel
programmers who should be doing some other kind of programming and kernel
programmers who already have a firm grasp on how to structure code and
interfaces and (most difficult) have a sense of taste that lets them
correctly identify the rare occasions when violating the rules results in a
clearer system -- the current process pointer might be an example. [Note
that I don't claim to have such taste reliably.]  Hm. Perhaps I should add a
third category for kernel programmers in training, but hopefully you get the
point.

> I'm new to OOP, and I haven't had the traditional CS training.  I'm
> learning most of my programming skills from experience and my own
> self-training, so any input from you would be greatly appreciated.

At the time I started the EROS project, I had recently completed writing "A
C++ Toolkit", which was the first book on building reusable code in C++. At
the beginning, I expected (as you did) that there would be reuse in the EROS
code. I also liked the style of C++, and features like inlining were not yet
widely available in C compilers. So I used C++.

I think that OO programming remains a good idea, and that for an awful lot
of code it provides an exceptionally useful tool. Bjarne has never proposed
C++ as a universal solution, and I no longer think that it is an appropriate
tool for Coperating system construction. I have spoken to a number of other
kernel designers who chose to use C++, and they almost universally say that
if they had it to do again they would do the code in C.


There is a final reason for the rewrite that doesn't really have anything to
do with C++. I would ultimately like for EROS to be a teaching tool as well
as a practical system. In doing the rewrite, I intend to clean up, simplify,
and redocument the code. This has inherent value, but of course, it could
also be done by rewriting to C++. In this case, I think C is a better
choice.


Jonathan