Dennis Ritchie's first C compiler on Github
github.com
github.com
Turns out, this Github-repo is just a mirror/copy of his work, but with attribution [2]. Still worth reading through there, tuhs also stores some extremely old UNIX versions.
[1] www.tuhs.org
[2] http://cm.bell-labs.com/cm/cs/who/dmr/primevalC.html
Edit: Warren has written a paper on restoring ancient UNIX versions and C-compilers, you might like it [3]
[3] http://epublications.bond.edu.au/infotech_pubs/146/
Edit2: Now that I've thought a little bit about it, I'm not happy that the sources are on GitHub in this form. This is Warren's work - he did a lot of work in getting these tapes to work again, and "mortdeus" just copied the work and didn't even change the folder-names - "last1120c" is the first tape, "prestruct" the second. And you still need Warren's Apout emulator to get these files to work.
If one releases code under an open license and people use/copy/fork it, why should you or the original author be unhappy. (As long as the license terms are not being broken.)
If we had to worry about this each time we forked/cloned someone's work, it would make code reuse very hard.
[Edit]
I am working on some Python charting. I found Pychart, which I found interesting: http://home.gna.org/pychart/ . Because it is in bzr which I don't work with, I put it on github. Should I be worried that someone will feel offended with it?
In the case of the C compiler, I think the modifications are under the same license as the original, but I'm not totally certain. If they aren't it would be cause for concern.
What happens when you have more then 9 substitutions specified in the string? :D
edit: decided the code was a bit long to have pasted into my post. Can find it at the bottom of http://cm.bell-labs.com/cm/cs/who/dmr/last1120c/c03.c
"Dennis MacAlistair Ritchie (born September 9, 1941; found dead October 12, 2011) was an American computer scientist who "helped shape the digital era." He created the C programming language and, with long-time colleague Ken Thompson, the Unix operating system"
(All three events were in October 2011 - Jobs, then Ritchie, then McCarthy)
if(foo) {
omitting the space after control statements' names, which is almost never done - except in very early C code. (It's not just this: V7 Unix often omits the space as well.). I should probably settle on a less idiosyncratic personal style, but I can't help but take a little heart from seeing "my" style in such a famous codebase. :) if(foo)
I avoid doing this because it looks like a function call. #define if(X) LET(_it,X) if (_it)
it would be a macro call #define LET(name, X) \
for (int let_once_=1, name=(X); \
let_once_; \
let_once_=0)
may be reasonable, though tokenpasting in __LINE__ for good measure might be necessary for nesting. #define private public
#include "foo.cpp"
#undef private #define public protected #define else
#define struct union
Evil!> This is all very well if you have a friendly, high level scripting language like Ruby, but I'm definitely glad I don't have to write a C compiler where functions can take arbitrary blocks of code.
That's a surprisingly good setup because in Smalltalk(one of ruby's main ancestor languages)
if/else is a method which takes a block closure.
a ifTrue: [ l log: 'a is true'] ifFalse: [ 'a is false']
whilein ruby if else is a syntatic construct
if a
l.log ('a is true')
else
l.log ('a is false')
end
probably more for perceived clarity/comfortability then speeds sake."no white space after the keywords if, for, while, etc."
Everyone must support the space character, it cannot be banned. But a simple commit hook to ban tabs can make indentation and alignment not get messed up over time with many collaborators with default-configured editors (that mess up and use tabs for alignment).
The tab character is a nice idea, but they do not seem to have worked out at all. I'd much rather have syntax-aware indenting in the editor, now that available compiler technology and CPU power make it practical.
You could even make a plugin that works without collaboration by others: check out the file in your preferred style and transform the changes back into the original style.
We'll probably get there in a few more years, as we get comfortable with adding more power to these systems, and taking advantage of compiler technology for things other than generating code.
Also, do you really want to be pressing the space bar twelve times, instead of tabbing three times?
I've seen the results of spaces-only: inconsistent, sloppy indentation, 7 or 9 spaces instead of 8, as long as it looks "indented" enough.
How, specifically does the tab character "not seem to have worked out at all"?
I'm not saying you're wrong for using spaces, just wondering...
The key is great. I use it all the time. Of course I don't want to press the space bar twelve times instead of tabbing three times. Of course I don't want sloppy indentation with 7 or 9 spaces instead of 8. Is this an actual problem? I've literally never seen either one in ~25 years of using languages that need indentation. The editor takes care of it.
The problem with the tab character is that there's no standard for how wide they're supposed to be, and so everyone uses them differently. Sure, in theory you use one tab character to indicate one level of indentation, and everybody can be happy. In practice, they're often not used that way. People will use two-space tabs with four-space indentation, using two tab characters to indent. People will use eight-space tabs with four-space indentation, using four spaces for one level of indentation, a tab character for two levels, a tab character followed by four spaces for three levels, etc. Some people just blithely mix and match for no particular reason. Put either one into an editor with different tab settings and it explodes into a huge mess.
Tabs just don't work out in a collaborative environment. It is too complicated for a heterogenous editing environment to get correct, so no such environment gets it correct in practice and code ends up a huge mess.
I worked on Univac 9400s. We received the O/S in source code form (Assembly) on tape. We ran it through a parametizer (PROC), compiled the resulting source, and that's what the customer ran with.
You haven't lived until you've stepped through your code one instruction at a time, displaying op codes and raw binary data on the maintenance panel lights.
init(s, t)
char s[]; {
would be equivalent to: int init(char s[], int t) {
This still works with modern compilers.I'd be interested if anyone has any more info about this:
waste() /* waste space */
{
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
}
Found in last1120c/c10.cA second, less noticeable, but astonishing peculiarity is the space allocation: temporary storage is allocated that deliberately overwrites the beginning of the program, smashing its initialization code to save space. The two compilers differ in the details in how they cope with this. In the earlier one, the start is found by naming a function; in the later, the start is simply taken to be 0. This indicates that the first compiler was written before we had a machine with memory mapping, so the origin of the program was not at location 0, whereas by the time of the second, we had a PDP-11 that did provide mapping. (See the Unix History paper). In one of the files (prestruct-c/c10.c) the kludgery is especially evident.
Cool to think that that waste function can still compile with todays compilers. A quick disassembly it seems to take up 751 bytes compiled on x64 using clang on O0.
It looks beautiful, almost like a scripting language. No monster type signatures like
const std::foo_bar<boost::blah_ptr<const xyz::bar::Bar&, baz::Baz>>&http://cm.bell-labs.com/cm/cs/who/dmr/primevalC.html
Which led me to here:
http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
Where, if you take the time, you will find a wonderful story, upon completing, you will probably know more about the early embryonic history of C then 95% of your peers.
(Spoiler - We start with BCPL, then Move to B - it's left as an exercise to determine how we originally compiled BCPL)
Oh how our minds play tricks on us!
The first version of Go started with B: http://code.google.com/p/go/source/detail?r=f6182e5abf5e
The second revision was converted to C: http://code.google.com/p/go/source/detail?name=f6182e5abf5e&...
The third to Draft-Proposed ANSI C: http://code.google.com/p/go/source/detail?name=f6182e5abf5e&...
And the fourth to ANSI C: http://code.google.com/p/go/source/detail?name=f6182e5abf5e&...
To compile a C compiler, you don't need a full-blown C compiler. For instance, I bet floats and doubles are not used. Therefore, you can write a barebones proto-C compiler in whatever language you have available and use it to bootstrap your compiler. Rinse and repeat.
The later is a c compiler in about 1kb of source code! It's quite functional and can compile itself.
The first link is what came out of it: A compiler so fast, that it can boot Linux from source code in a few seconds: http://bellard.org/tcc/tccboot.html
Does anyone know what hardware the assembly language files are for?
Maybe you could produce a modified version with the archaic features removed, compile it with a modern compiler, then use the binary produced to compile an unmodified version. Or maybe there are still binaries of really old compilers that can understand this code floating around out there.
Any ideas?
main(argc, argv)
int argv[]; {
Is that still valid today?The int argv[]; is where the declaration for argv is happening and is being declared as an pointer for ints.
You can also see elsewhere in the code where they are passing pointer addresses (as int params) into functions and then using the address to build pointers referencing that data.
int foo(int i, int j) {...}
in Fortran you would do (! is comment) function foo(i, j)
integer :: foo !return type
integer :: i
integer :: j
!body goes here
Early C stuck to that style, so you would just put the names of the variables in the declaration, and then before the body give them types. The reason only argv is mentioned in that example is that C assumes a variable is an int if not declared otherwise, so there's no reason to put "int argc" like "int argv[]".All modern C compilers still accept this style for backwards compatibility. I'm not sure about C++ compilers.
To explain further:
main(argc, argv)
int argv[]; {
is equivalent to: int main(int argc, int argv[]) {
The old style definition works because C had a default type of int, so the type specifications for the function main and the parameter argc could be omitted.As for int argv[]? What that actually represents is an array of memory addresses that hold the command line arguments given. Obviously this becomes a problem if you're on a 64-bit system, where int and (void * ) are two different sizes. However, I checked this out on my 64-bit machine and it works just fine:
int main(int argc, unsigned long long argv[]) {
char *firstarg = (void *)(argv[1]);
printf("%s", firstarg);
}
which, given "./a.out pickles" will print "pickles" (argv[0] gives the memory address of the cstring "./a.out"). I'm guessing that, in the case of a compiler, the memory addresses of arguments are more relevant to have than the arguments themselves. main(argc, argv)
char *argv[];
{
/* ... */
}
is illegal (strictly speaking, it's a "constraint violation"). Note that it's char *argv[]
not int argv[]
But this: int main(argc, argv)
int argc;
char *argv[];
{
/* ... */
}
is still perfectly valid.As for this:
int main(int argc, unsigned long long argv[]) {
char *firstarg = (void *)(argv[1]);
printf("%s", firstarg);
}
it's not a constraint violation, but its behavior is undefined (unless your compiler specifically supports and documents that particular form as an extension).Look here (lines 22 and 23): https://github.com/mortdeus/legacy-cc/blob/master/prestruct/...
The compiler code states int argv[], not char argv[] (I assumed this is why the OP asked for clarification in the first place, since char argv[] is much more common).
You're right, in theory this is undefined behavior, but in practice on a 32-bit system, sizeof(int) will almost always be equal to sizeof(void *). I was just demonstrating how one could recreate the code in the compiler while on a 64-bit architecture.
The standard now known as C89/C90 had been in committee for many years before being 'released'. This didn't stop the tool vendors (like Borland) from supporting the 'proposed' standard much earlier than 1989.
Unfortunately, commercial UNIX vendors (like HP, in my case), were very slow to adopt the standard and update the cc compiler in their distribution. This forced us to work in K&R for a good time after 1990, all the while grumbling that $150 MS-DOS compilers already 'had ANSI'.