C#88: The Original C#

A few notes for posterity


System Basics

The C# language was designed to be substantially compatible with the C programming language. As it happened the underlying compiler supported what you might call a variant of K&R C. That means function prototypes are optional, and lots of things “int” by default. The compiler produced hybrid PCode and native x86 assembly, traditional for MS applications at the time.

The compilation model was what you might get if you built something like “C.Net” today. Files could be, and were, compiled in any order, including .h files which were really not considered to be any different than .c files. The system did not do something like “scan for definitions then parse bodies” as you might expect, rather, it exploited its incremental nature to incrementally reparse out-of-order until convergence as we will see below. The team mantra was “twice incremental is still cheap.”

Source files (SRFs) were divided into source units (SRUs). A typical SRU might be a single function, or a single declaration, but they could be compound where multiple definitions were atomic (e.g int x,y;). Source units created language objects (LOBs) and depended on them. This is essentially how the compilation convergence happened. If, due to errors, an SRU could not be compiled, the necessary-but-missing LOB was recorded. Should that LOB be encountered at a later point then any SRU that required it would be queued for a recompile. This process might take several iterations…

The system processed the files in the order specified by a simple project.ini file, which meant that “make” was largely unnecessary. Among the more interesting features was an option to emit a new .ini with “the order you should have told me to compile this junk in” — thereby making it fairly easy to create and maintain a decent ordering without much human effort.

Of course it was a very good idea to put the .h files first because they tended to have a lot of #define statements. If you didn’t do so, your first build would really suck…

The system was built on top of an existing C compiler (the Apps PCode compiler, as opposed to “CMerge” the native compiler then in use) and basically the system would set up that compiler’s symbol table and so forth to be “just right” for starting at the recompilation context. This was not easy but seemed to work out ok.

Additionally, .obj files and other binary formats could be “imported” so that the system would know about their definitions and be able to do crazy things like figure out which symbols would be resolved case-insensitively at link time. That was a hairy mess.

Incremental recompile was achieved by noting changed source files, and recomputing the CRC of each SRU in the file. Any changed SRUs would be recompiled and of course this would cause dependencies to be recompiled until the system converged again. Importantly, non-observable changes (e.g. adding a new local variable to a function) did not cause dependencies to become dirty.

Because of the above dependency management, some common practices required special treatment. Of special interest is this one:

#ifndef FOO

#define FOO


This required special treatment because there is a dependency cycle between the SRU that contains the #ifndef and the SRU that contains the #define. Without special treatment it would never converge..

Finally, the system incrementally produced ready-to-link object files with the entire content of a 64k segment in one file. Those were then linked by the usual MS Incremental linker.


The fact that the typical input code was written for a toolset where types were only loosely enforced across compilation units meant that the C# system was good at finding inconsistencies still in the code. Very good. Thousands of errors had to be fixed to get the existing code to build. The fact that all global symbols were in the same namespace tended to work out well in that it forced out certain horrible practices… You could not give the same variable two different types in two different compilation units and get an accidental union for instance.

Similarly, the fact that .lib files and the like had to be imported meant that those names were also unified and crazy overrides with unknown side effects were banished.

Basically the thing was a super linter. Which was super painful…and super unpopular…

The program databases were considered huge… weighing in at a whopping 8–12M for the big programs… toys by today’s standards. They might have been smaller if we hadn’t used a pointer-ish data structure as our on disk representation —I can’t say that I’m in love with object oriented databases at all, even in 2018, much less 1988.


Good times.

Written by

I’m a software engineer at Facebook; I specialize in software performance engineering and programming tools generally. I survived Microsoft from 1988 to 2017.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store