summaryrefslogtreecommitdiff
path: root/05
diff options
context:
space:
mode:
Diffstat (limited to '05')
-rw-r--r--05/Makefile16
-rw-r--r--05/README.md146
-rw-r--r--05/tcc-0.9.27/libtcc.c4
3 files changed, 125 insertions, 41 deletions
diff --git a/05/Makefile b/05/Makefile
index b7dccab..ea0d9c4 100644
--- a/05/Makefile
+++ b/05/Makefile
@@ -1,8 +1,8 @@
TCCDIR=tcc-0.9.27
-TCC0=$(TCCDIR)/tcc0
-TCC1=$(TCCDIR)/tcc1
+TCC=$(TCCDIR)/tcc
+TCC0=$(TCC)0
TCCINST=/usr/local/lib/tcc-bootstrap
-all: out04 a.out test.out README.html $(TCCDIR)/lib/libtcc1.a
+all: out04 a.out README.html $(TCCDIR)/lib/libtcc1.a
in04: *.b ../04a/out04
../04a/out04 main.b in04
out04: in04 ../04/out03
@@ -13,7 +13,7 @@ out04: in04 ../04/out03
./out04 $< $@
a.out: main.c *.h out04
./out04
-test.out: $(TCC0) test.s.o test.c.o
+test.out: test.s.o test.c.o
$(TCC0) -static -nostdlib test.s.o test.c.o -o test.out
test.s.o: $(TCC0) test.s
$(TCC0) -static -nostdlib -c test.s -o test.s.o
@@ -31,7 +31,13 @@ install-tcc0: $(TCCDIR)/lib/libtcc1.a $(TCCDIR)/include/*.h
mkdir -p $(TCCINST)/include
cp -r $(TCCDIR)/include/*.h $(TCCINST)/include/
cp -r $(TCCDIR)/lib/libtcc1.a $(TCCINST)/
-$(TCC1): $(TCC0) $(TCCINST)/libtcc1.a
+$(TCC)1: $(TCC0) $(TCCINST)/libtcc1.a
cd $(TCCDIR) && ./tcc0 tcc.c -o tcc1
+$(TCC)2: $(TCC)1
+ cd $(TCCDIR) && ./tcc1 tcc.c -o tcc2
+$(TCC)0a: $(TCCDIR)/*.c $(TCCDIR)/*.h
+ cd $(TCCDIR) && gcc tcc.c -o tcc0a
+$(TCC)1a: $(TCCDIR)/*.c $(TCCDIR)/*.h
+ cd $(TCCDIR) && ./tcc0a tcc.c -o tcc1a
clean:
rm -f out* README.html *.out *.o $(TCCDIR)/tcc[0123456] $(TCCDIR)/tcc[0123456]a $(TCCDIR)/lib/*.[oa]
diff --git a/05/README.md b/05/README.md
index 5f54a9a..85c1dd9 100644
--- a/05/README.md
+++ b/05/README.md
@@ -5,17 +5,58 @@ to TCC's source code).
Run
```
-make
+$ make
```
to build our C compiler and TCC. This will take some time (approx. 25 seconds on my computer).
-Two test programs will be produced: `a.out`, compiled using our C compiler, and
-`test.out`, compiled using `tcc`. If you run either one, you should get the output
+This also compiles a "Hello, world!" with our compiler, `a.out`.
+
+We can now compile TCC with itself. But first, you'll need to install the header files and library files
+which are needed to compile (almost) any program with TCC:
+
+```
+$ sudo make install-tcc0
+```
+
+The files will be installed to `/usr/local/lib/tcc-bootstrap`. If you want to change this, make sure to change
+both the `TCCINST` variable in the makefile, and the `CONFIG_TCCDIR` macro in `config.h`.
+Anyways, once this installation is done, you should be able to compile any C program with `tcc-0.9.27/tcc0`!
+We can even compile TCC with itself:
+
+```
+$ cd tcc-0.9.27
+$ ./tcc0 tcc.c -o tcc1
+```
+
+Now, let's try doing the same thing, but starting with GCC instead of our C compiler:
+
+```
+$ gcc tcc.c -o tcc0a
+$ ./tcc0a tcc.c -o tcc1a
+```
+
+In theory, these should produce the same files, since the output of TCC shouldn't depend on which compiler it was compiled with.
+If they are different, then perhaps a bug *was* introduced in some early version of GCC, and replicated in all C compilers since then!
+Well, only one way to find out:
+
+```
+$ diff tcc1 tcc1a
+Binary files tcc1 and tcc1a differ
+```
+
+!!! Is there some malicious code hiding in the difference between these two files? Well, unfortunately (or fortunately, rather) the
+truth is more boring than that:
```
-Hello, world!
+$ ./tcc1 tcc.c -o tcc2
+$ diff tcc2 tcc1a
+$
```
+Yes, after compiling TCC with itself one more time, we get the same executable as the GCC-TCC one.
+I'm not sure why `tcc1` differs from `tcc2`, but there you go. Turns out there isn't some malicious
+self-replicating code hiding in GCC after all.\*
+
## the C compiler
The C compiler for this stage is written in the [04 language](../04/README.md), using the [04a preprocessor](../04a/README.md)
@@ -34,7 +75,14 @@ main.b - puts everything together
The whole thing is ~12,000 lines of code, which is ~280KB when compiled.
-### the C standard
+It can be compiled with `make` or:
+
+```
+../04a/out04 main.b in04
+../04/out03 in04 out04
+```
+
+## the C standard
In 1989, the C programming language was standardized by the [ANSI](https://en.wikipedia.org/wiki/American_National_Standards_Institute).
@@ -45,7 +93,7 @@ Since 1989, more features have been added to C, and so more C standards have bee
To keep things simple, our compiler only supports the features from C89 (with a few exceptions).
-### compiling a C program
+## compiler high-level details
Compiling a C program involves several "translation phases" (C89 standard ยง 2.1.1.2).
Here, I'll only be outlining the process our C compiler uses. The technical details
@@ -112,7 +160,7 @@ This is where we read the tokens `if` `(` `a` `)` `printf` `(` `"Hello!\n"` `)`
and interpret it as an if statement, whose condition is the variable `a`, and whose
body consists of the single statement calling the `printf` function with the argument `"Hello!\n"`.
-Finally, we output the code for every function.
+Finally, we turn this internal representation into code for every function.
## executable format
@@ -234,46 +282,76 @@ Here is a list of things we do wrong (this list is probably missing things, thou
- Technically, `1[array]` is equivalent to `array[1]`, but we don't handle that.
- C89 has *very* weird typing rules about `void*`/`non-void*` inside conditional expressions. We don't handle that properly.
- C89 allows calling functions without declaring them, for legacy reasons. We don't handle that.
-- Floating-point constant expressions are very limited. Only `double` literals and 0 are supported (it was hard enough
-to parse floating-point literals in a language without floating-point variables!)
+- Floating-point constant expressions are very limited. Only `double` literals and 0 are supported.
- Floating-point literals can't have their integer part greater than 2<sup>64</sup>-1.
- Redefining a macro is always an error, even if it's the same definition.
- You can't have a variable/function/etc. called `defined`.
- Various little things about when macros are evaluated in some contexts.
-setjmp.h:// @NONSTANDARD: we don't actually support setjmp
-stddef.h:// @NONSTANDARD: we don't have wchar_t
-stdlib.h:// @NONSTANDARD: we don't define MB_CUR_MAX or any of the mbtowc functions
-time.h:// @NONSTANDARD(except in UTC+0): we don't support local time in timezones other than UTC+0.
-time.h: // @NONSTANDARD-ish.
-
+- The horrible, horrible, function `setjmp`, which surely no one uses is not properly supported.
+Oh wait, TCC uses it. Fortunately it's not critically important to TCC.
+- `wchar_t` and wide character string literals are not supported.
+- The `localtime()` function assumes you are in the UTC+0 timezone.
+- `mktime()` always fails.
Also, the keywords `signed`, `volatile`, `register`, and `const` are all ignored. This shouldn't have an effect
on any legal C program, though.
-## modifications of tcc's source code
+## anecdotes
+
+Making this C compiler took over a month. Here are some interesting things
+which happened along the way:
+- A very difficult part of this compiler was parsing floating-point numbers in a language which
+doesn't have floats. Originally, there was a bug where negative powers of 2 were
+being interpreted as half of their actual value, e.g. `x = 0.25;` would set `x` to
+`0.125`, but `x = 4;`, `x = 0.3;`, etc. would all work just fine.
+- The <s>first</s> second non-trivial program I successfully compiled worked perfectly the first time I ran it!
+- A very difficult to track down bug happened the first time I ran `tcc`: there was a declaration along
+the lines of `char x[] = "a\0b\0c";` but it got compiled as `char x[] = "a";`!
+- Originally, I was just treating labels as statements, but `tcc` actually has code like:
+```
+...
+goto lbl;
+...
+if (some_condition)
+ lbl: do_something();
+```
+so the `do_something();` was not being considered as part of the `if` statement.
+- The first time I compiled tcc with itself (and then with itself again), I actually got a different
+executable. After spending a long time looking at disassemblies, I found the culprit:
+```
+# if defined(__linux__)
+ tcc_define_symbol(s, "__linux__", NULL);
+ tcc_define_symbol(s, "__linux", NULL);
+# endif
+```
+If the `__linux__` macro is defined (to indicate that the target OS is linux),
+TCC will also define the `__linux__` macro. Unlike GCC, our compiler doesn't define the `__linux__` macro,
+so when it's used to compile TCC, TCC won't define it either, no matter how many times you compile it
+with itself!
+
+## modifications of tcc's source code
-## the nightmare begins
-So now we just compile TCC with itself, and we're done, right?
-Well, not quite...
+## \*the nightmare begins
-The issue here is that to compile TCC/GCC with TCC, we need libc, the C standard library functions.
-Our C compiler just includes these functions in the standard header files, but normally
-the code for them is located in a separate library file (called something like
-`/usr/lib/x86_64-linux-gnu/libc-2.31.so`).
+If you look in TCC's source code, you will not find implementations of any of the C standard library functions.
+So how can programs compiled with TCC use those functions?
-This library file is itself compiled from C source files (typically glibc).
-So, can't we just compile glibc with TCC, then compile TCC with itself?
-Well, no. Compiling glibc with TCC is basically impossible; you need to compile
-it with GCC.
+When a program compiled with TCC (under default settings) calls `printf`, say, it actually gets the instructions
+for `printf` from a separate library file
+(called something like `/usr/lib/x86_64-linux-gnu/libc-2.31.so`). There are very good reasons for this: for example,
+if there a security bug were found in `printf`, it would be much easier to replace the library file than re-compile
+every program which uses `printf`.
-Other libc implementations aren't too happy about TCC either -- I tried to compile
-[musl](http://www.musl-libc.org/) for several hours, and had to give up in the end.
+Now this library file is itself compiled from C source files (typically glibc).
+So, we *can't* really say that the self-compiled TCC was built from scratch. And there could be malicious
+self-replicating code in glibc!
-It seems that the one option left is to make our own libc, and try to use it along with
-TCC to compile GCC.
-From there, we should be able to compile glibc with GCC. Then, we can compile GCC with GCC and glibc.
-If we do all this, we should get the same libc.so and gcc files as if we had started
-with any GCC and glibc builds. It's all very confusing.
+So, why not just compile glibc with TCC?
+Well, it's not actually possible. glibc can pretty much only be compiled with GCC. And we can't compile GCC
+without a libc. Hmm...
+Other libc implementations don't seem to like TCC either, so it seems that the only option left is to
+make a new libc implementation, use that to compile GCC (probably an old version of it which TCC can compile),
+then use GCC to compile glibc. It will definitely be a large undertaking...
diff --git a/05/tcc-0.9.27/libtcc.c b/05/tcc-0.9.27/libtcc.c
index 3ff8a77..a0f056e 100644
--- a/05/tcc-0.9.27/libtcc.c
+++ b/05/tcc-0.9.27/libtcc.c
@@ -817,10 +817,10 @@ LIBTCCAPI TCCState *tcc_new(void)
tcc_define_symbol(s, "__unix__", NULL);
tcc_define_symbol(s, "__unix", NULL);
tcc_define_symbol(s, "unix", NULL);
-# if defined(__linux__)
+//# if defined(__linux__)
tcc_define_symbol(s, "__linux__", NULL);
tcc_define_symbol(s, "__linux", NULL);
-# endif
+//# endif
# if defined(__FreeBSD__)
tcc_define_symbol(s, "__FreeBSD__", "__FreeBSD__");
/* No 'Thread Storage Local' on FreeBSD with tcc */