YES, I did it!! (You can download the source package here).
I found on Borland Intl. site that they placed their historic Turbo C 2.01 in freeware, because "Antique Software". Well, one can use it also to develop programs for the ELKS platform.
(note: see also this page about ELKS)
In May 1989 Borland released the version 2.01 of Turbo C, surely the most used C compiler in the PC/MS-DOS world in the "pioneer's era" (When Men Were Men And Wrote Their Own Device Drivers). It could be used for creating DOS executables from "tiny" COM files to "huge" EXE files. A rock-solid compiler with a nice text-based IDE interface and a good debugging tool (you could step-into functions without altering screen, look for variable values, etc).
After years and years, that software (now released as freeware: go to Borland site and search in the Borland Community the keywords "antique software turbo c") is still good. But, as I demonstrate here, you can use it also to develop for 8086 (or 80186) environments.
The only known C compiler suitable for ELKS was the bcc (Bruce Evans C Compiler) and the Dev86 package. The bcc has (only) a peep-hole optimizer, which output isn't as fast as the Turbo C one.
I think that it happens to be easier working on an already optimized 8088-family compiler than creating a new one, which would be almost pointless because today 8088-class and 80286-class machines are "things for museums" (on these museum things ELKS runs!!!).
From DOS EXE format to ELKS.
It's really easy (at least theoretically): ELKS executables have a fixed header, then the code section, then the data section.
For this release, to make things simple, I consider only the "small" memory model executable (64k code + 64k data/bss/stack). So, any 8088-class compiler producing a "small" memory model executable can be used.
Turbo C can generate a DOS executable file (.EXE) and a link map text information file (.MAP): by grabbing information from the EXE header and MAP file, one can build an ELKS header and append generated code and data segments. This work is done by the exe2elks DOS utility program.
Standard C libraries.
Obviously you cannot use DOS-based Turbo C libraries (CS.LIB, etc) to build ELKS programs, because in many points their routines refer to DOS environment and startup code block (C0*.OBJ): DOS interrupts (21h, 20h, etc), PSP structure, anarchic heap handling, interrupt vectors (like division-by-zero), internal variables, etc.
I think that a good part of the standard Turbo C libraries could be used in almost any environment (e.g.: a "strcpy" function does not require anything from the underlying operating system), but one should examine and study the library source (or, harder, the disassembled code) to select "good" routines one by one...
So I had to develop a minimal library (smallibc.lib) for a number of ELKS system calls (syscalls) of general use, and a small (very small) set of general functions (strcpy, minimalistic printf, etc).
I compiled some example programs (requiring very little library functions) to show how it actually runs. The bcc generates for the "cento.c" program a 5500+ bytes executable file, while the Turbo C version (with register variables, a decent optimizer, etc) with the minimalistic library, generates a 1540 bytes file (including about 550 bytes for the only printf/sprintf functions). Start "cento" in a (true or emulated) 8088-based ELKS environment and enjoy...! :-)
I think that larger programs (the standard library, the ELKS kernel itself, etc) could be compiled with Turbo C. There are a number of Turbo C features that should be explored (for example, the floating-point stuff - emulated or 8087-native; far pointers and far calls; etc). The ELKS kernel may actually need a decently optimized compiler, to save some few kilobytes of precious code space.
The nasty part of all: Turbo C expects only CR/LF terminated lines, while bcc expects LF only. But the translation from DOS style to Unix style can be done with a simple filter program. Also, Turbo C has to be used in a DOS environment (maybe a DEXE file solution could help).
Get the Turbo C now!
Please do not ask me for a copy of Turbo C: go download it from Borland site (section Museum) site. Yes, they ask you for your email address... I gave mine but they didn't ever send me any advertisement.
I'm not related in any manner with Borland: they just did a good thing that only a few software companies do -- place in freeware a great piece of their commercial software ("Antique"? it doesn't care too much). Go download it from Borland site, and make them know what a great thing is to release it in freeware! Maybe one day they will release also source code of their old stuff, and other famous compilers or newer versions of Turbo C and Turbo C++, letting people using them and porting them to other operating environments.
I do not plan to expand smallibc to a decent libc; I know there are already efforts to port uClibc library to ELKS and do not want to duplicate work (I hope already to not to have duplicate work from others).
The package TCC4ELKS.ZIP is to be used in a DOS environment, e.g.: MS/DOS 5.0 under DOSemu 1.0 (other combinations, real or emulated DOS box, FreeDOS, etc, are untested but expected to work).
Maybe one could create a modified version of DOSELKS linux-86 package program for use the Turbo C integrated debugger, and develop and debug in a DOS box...!
The license for using my work (this TCC4ELKS package) is GPL (GNU General Public License) and won't change in future releases.
The startup code is quite simple because the ELKS kernel prepares the argc/argv stuff on the stack before starting the process. The instructions were copied ftom the Dev86 sources. I just added an IFDEF instruction to save even the last dozen of bytes for programs that do not require argc/argv in the main() (in this case, the C0NOARGS.OBJ has to be used instead of C0.OBJ).
The C0.OBJ initializes all segments. I required the alignment between text and data to be "paragraph" because the ELKS kernel loads the data section at a paragraph address, but we do not want Turbo C to mis-align it if the code segment was mis-aligned (i.e.: the first data byte would be compiled for example at something like ds:0007 instead of ds:0000).
The smallibc doesn't require anything but a "call main" and an exit syscall. Maybe some things should require a more complex startup code - for example, you should save the envp pointer to use it with the getenv() library function (currently not implemented in the smallibc).
The smallibc contains a number of ELKS syscalls reduced to functions (just fill in the AX-BX-CX-DX registers and issue int 0x80) and some extra functions. A minimal (er... more than 500 bytes) vsnprintf (that is, a safe sprintf with a variable argument pointer) and the classic printf (on standard output), fdprintf (on any file descriptor) and sprintf (on a buffer)
Oh, what an horrible mode to build a library... a huge .BAT file containing always the same commands. Yes, I wrote it using a macro of my favourite editor: a running library is better than an elegant batch solution for building it! :-)
Have a look in the smallibc.lst file, where you can see the sizes of the defined functions. Yes, a sync() only requires six bytes of code (plus three per call), while a 32-bit lseek (notice the correct parameter passing) is just 25 bytes. Oh, I'm sure you will appreciate the Turbo C optimizations on a 500+ lines (with do/for/while cycles and a 100+ case switch) function. (Oops. Remember that Turbo C 2.01 has the switch instruction limited to 256 different case's!)
Ok, the Turbo C gave us an EXE file full of int 0x80's and with the correct startup code and segment organization. The EXE2ELKS DOS program reads the EXE header fetching some information (like the size of the header itself), then reads the MAP file (where we can get exact sizes of code, data and bss segments), then creates an ELKS header and copies the code and data sections from the EXE file.
Due to a bogus data-segment sizing of the linker (a bug or an one-byte-saving feature? I don't know) I have to "adjust" the data size and bss size (because the bss was declared from Turbo C at an even address). Beware: the comments say "from 0 to 65535", but this is the "intended" size of our program...! Maybe one day ELKS will support cleanly-written full-size EXE files, but for now we don't need huge software.
The hello example just prints "hello, world" on the standard output, using my puts() function -- unlike the original, this does not append a newline at the end: this weird "feature" was one of the strange things that we still have in the latest C compilers of our days... The puts() function saves some hundreds bytes respect to the printf() (yes, this is another weird thing of Kernighan and Ritchie: why their historic "hello world" program relied on printf instead of the economical puts?).
The printinf example outputs some information (its filesize, its process id, etc), especially if called with some arguments from command-line. You get a 11400+ bytes file compiling it with bcc, and only 1337 bytes if compiled with Turbo C and smallibc.lib...!
The cento program solves the "Little 100 game" showing on the screen the single moves (an ansi/vt52 terminal is assumed, because it sends the clear-screen and move-cursor-to-home sequences). Its greedy approach has a terrific exponential growth, but for a matrix of 5×5 and 6×6 it can be solved in less than 35,000 moves; a 10×10 matrix requires some millions of operations: click here to see the best greedy solution - using 386 assembler - to get a recursion call in only nine clock cycles on a Pentium III processor (obviously a clean solution should be not a greedy but a dynamic programming one).
Oh, well, you surely won't use a 4.77 MHz PC/XT for a high number-crunching application... but, what about a decently optimized C compiler allowing classic syntax for inline assembly and even ready for 80186 processors? :-)
send e-mail - continua (next page)