https://medium.com/@adambillyard/why-gcc-1-42-on-the-tektronix-4404-with-a-c-compiler-from-1979-661899013ad6 Open in app Sign up Sign in [ ] Write Sign up Sign in [1] Why GCC 1.42 on the Tektronix 4404 -- with a C compiler from 1979 Adam Billyard Adam Billyard * Follow 16 min read * 4 days ago -- Listen Share I came across a blog describing building an early version of the GNU C Compiler (gcc) using a modern compiler: (https://miyuki.github.io/ 2017/10/04/gcc-archaeology-1.html) The native C compiler on Tek4404 is based on K&R (Kernighan & Ritchie) C from 1979 and is limited, generates not great code and does not support any preprocessor directives but the very basic, making compiling software quite painful. I had managed to get uemacs4.1 compiled on the Tek4404 so I didn't have to use vi, but it was a grind of compiling, finding compile errors, editing, compiling, editing etc. And when compile cycles are measured in minutes, this requires a zen patience I do not possess. Getting a decent compiler up and running sounded like a good project. I decided on gcc 1.42 which was the last version of gcc before the build toolchain got suddenly absurdly more complex (and of course eventually dropping support for Motorola 68010 on which the Tektronix 4404 is built). Up until gcc v1.42 configuration was a matter of softlinking md.h (machine description dot h) to a suitable header and pretty much hitting make. Easy peasy, lemon squeezy. Bootstrapping any compiler typically requires using the existing host compiler to build some native arithmetic functions (long multiply, divide etc) on which everything else will be built. You then use your native compiler to build a (non-optimal) compile of the new compiler. TL;DR It didn't go smoothly. Getting Started Much of the config for gcc1.42 is through header files and #defines, however the C preprocessor on Tek4404 called cprep supports only # ifdef symbol and not #if expr. Furthermore, it crashes if there are more than a certain number of defines in a file and cannot parse expressions that are anything but trivial. It is very fragile! So I decided to leverage a Known Good C preprocessor from a modern C toolchain on my Mac to resolve all the preprocessor directives and merge all the include files into a single "processed" file. So, take file.c and make file.p.c that is much larger but has no external dependencies: cc -nostdinc -I ~/projects/tek4404//lib/include -E -P -Wno-extra-tokens -U__GNUC__ -U__STDC__ src_file.c > src_file.p.c We cannot use any built-in include paths from the compiler (-nostdinc ) and instead only use Tek4404 includes (-I ...) . Also we need to undefine __GNUC__ and __STDC__ as we will not be compiling using gcc compiler. Lastly, Tek4404 compiler cannot handle any #line directives that are emitted by cpp, so we need -P too. Having premasticated all the gcc .c source files into their .p.c form, we could copy them across to the Tek4404 for compiling using the native C compiler. What could possibly go wrong? Attempt #1 The source files for a C compiler consist of files which implement ' business logic' of managing input files and output files along with command-line options which are usually straight forward. However, there are a lot of source files that are procedurally generated from the Machine Description config -- they encode how code should be emitted for this target hardware, what CPU registers to use, what their limitations are, performant code patterns, parsing etc. These procedurally generated source files consist of standard C expressions but they are bonkers complex and not generated for human readability. The first time I opened the file, I actually gasped! An example snippet of the generated code in reload.c to give you a flavor: reload.p.c:656 { { if (((((addr)->code) == LABEL_REF || ((addr)->code) == SYMBOL_REF || ((addr)->code) == CONST_INT || ((addr)->code) == CONST) || (((addr)->code) = = REG && (((((addr)->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((add r)->fld[0].rtint)] ^ 010) < 8)) || ((((addr)->code) == PRE_DEC || ((addr)->code) == POST_INC) && (((((addr)->fld[0].rtx))->code) == REG) && (((((((addr)->fld[0] .rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((addr)->fld[0]. rtx))->fld[0].rtint)] ^ 010) < 8)) || (((addr)->code) == PLUS && (((((addr)->fld [0].rtx))->code) == REG) && (((((((addr)->fld[0].rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((addr)->fld[0].rtx))->fld[0].rtint)] ^ 010) < 8 ) && ((((addr)->fld[1].rtx))->code) == CONST_INT && ((unsigned) ((((addr)->fld[1 ].rtx))->fld[0].rtint) + 0x8000) < 0x10000))) goto win; }; { { if (((addr)->code ) == PLUS && (((((((addr)->fld[0].rtx))->code) == REG && ((((((addr)->fld[0].rtx ))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((addr)->fld[0].rtx))->fld[ 0].rtint)] < 16)) || (((((addr)->fld[0].rtx))->code) == SIGN_EXTEND && ((((((add r)->fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((addr)->fld[0].rtx))->fld[0 ].rtx))->mode) == HImode && ((((((((addr)->fld[0].rtx))->fld[0].rtx))->fld[0].rt int)) < 16 || (unsigned) reg_renumber[((((((addr)->fld[0].rtx))->fld[0].rtx))->f ld[0].rtint)] < 16))) || ((target_flags & 1) && ((((addr)->fld[0].rtx))->code) = = MULT && ((((((((addr)->fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((((add r)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[( (((((addr)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((((addr)->f ld[0].rtx))->fld[0].rtx))->code) == SIGN_EXTEND && ((((((((addr)->fld[0].rtx))-> fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((((addr)->fld[0].rtx))->fld[0]. rtx))->fld[0].rtx))->mode) == HImode && ((((((((((addr)->fld[0].rtx))->fld[0].rt x))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((((addr)- >fld[0].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) && ((((((addr) ->fld[0].rtx))->fld[1].rtx))->code) == CONST_INT && (((((((addr)->fld[0].rtx))-> fld[1].rtx))->fld[0].rtint) == 2 || ((((((addr)->fld[0].rtx))->fld[1].rtx))->fld [0].rtint) == 4 || ((((((addr)->fld[0].rtx))->fld[1].rtx))->fld[0].rtint) == 8)) )) { { if (((((addr)->fld[1].rtx))->code) == LABEL_REF) goto win; if (((((addr)- >fld[1].rtx))->code) == REG && (((((((addr)->fld[1].rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((addr)->fld[1].rtx))->fld[0].rtint)] ^ 010) < 8)) goto win; }; } if (((addr)->code) == PLUS && (((((((addr)->fld[1].rtx))->c ode) == REG && ((((((addr)->fld[1].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_ renumber[((((addr)->fld[1].rtx))->fld[0].rtint)] < 16)) || (((((addr)->fld[1].rt x))->code) == SIGN_EXTEND && ((((((addr)->fld[1].rtx))->fld[0].rtx))->code) == R EG && ((((((addr)->fld[1].rtx))->fld[0].rtx))->mode) == HImode && ((((((((addr)- >fld[1].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[(((( ((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) || ((target_flags & 1) && ((((addr)->fld[1].rtx))->code) == MULT && ((((((((addr)->fld[1].rtx))->fld [0].rtx))->code) == REG && ((((((((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rti nt)) < 16 || (unsigned) reg_renumber[((((((addr)->fld[1].rtx))->fld[0].rtx))->fl d[0].rtint)] < 16)) || (((((((addr)->fld[1].rtx))->fld[0].rtx))->code) == SIGN_E XTEND && ((((((((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->code) == REG & & ((((((((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->mode) == HImode && (( ((((((((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((((addr)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))- >fld[0].rtint)] < 16))) && ((((((addr)->fld[1].rtx))->fld[1].rtx))->code) == CON ST_INT && (((((((addr)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 2 || (((((( addr)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 4 || ((((((addr)->fld[1].rtx ))->fld[1].rtx))->fld[0].rtint) == 8)))) { { if (((((addr)->fld[0].rtx))->code) == LABEL_REF) goto win; if (((((addr)->fld[0].rtx))->code) == REG && (((((((addr )->fld[0].rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((addr) ->fld[0].rtx))->fld[0].rtint)] ^ 010) < 8)) goto win; }; } }; if (((addr)->code) == PLUS) { if (((((addr)->fld[1].rtx))->code) == CONST_INT && (unsigned) ((((ad dr)->fld[1].rtx))->fld[0].rtint) + 0x80 < 0x100) { rtx go_temp = ((addr)->fld[0] .rtx); { if (((go_temp)->code) == PLUS && (((((((go_temp)->fld[0].rtx))->code) = = REG && ((((((go_temp)->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_ren umber[((((go_temp)->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((go_temp)->fld[0] .rtx))->code) == SIGN_EXTEND && ((((((go_temp)->fld[0].rtx))->fld[0].rtx))->code ) == REG && ((((((go_temp)->fld[0].rtx))->fld[0].rtx))->mode) == HImode && ((((( (((go_temp)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_r enumber[((((((go_temp)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) || (( target_flags & 1) && ((((go_temp)->fld[0].rtx))->code) == MULT && ((((((((go_tem p)->fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((((go_temp)->fld[0].rtx))-> fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((go_temp)->fld [0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((((go_temp)->fld[0].rtx))- >fld[0].rtx))->code) == SIGN_EXTEND && ((((((((go_temp)->fld[0].rtx))->fld[0].rt x))->fld[0].rtx))->code) == REG && ((((((((go_temp)->fld[0].rtx))->fld[0].rtx))- >fld[0].rtx))->mode) == HImode && ((((((((((go_temp)->fld[0].rtx))->fld[0].rtx)) ->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((((go_temp)- >fld[0].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) && ((((((go_te mp)->fld[0].rtx))->fld[1].rtx))->code) == CONST_INT && (((((((go_temp)->fld[0].r tx))->fld[1].rtx))->fld[0].rtint) == 2 || ((((((go_temp)->fld[0].rtx))->fld[1].r tx))->fld[0].rtint) == 4 || ((((((go_temp)->fld[0].rtx))->fld[1].rtx))->fld[0].r tint) == 8)))) { { if (((((go_temp)->fld[1].rtx))->code) == LABEL_REF) goto win; if (((((go_temp)->fld[1].rtx))->code) == REG && (((((((go_temp)->fld[1].rtx))-> fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((go_temp)->fld[1].rtx)) ->fld[0].rtint)] ^ 010) < 8)) goto win; }; } if (((go_temp)->code) == PLUS && (( (((((go_temp)->fld[1].rtx))->code) == REG && ((((((go_temp)->fld[1].rtx))->fld[0 ].rtint)) < 16 || (unsigned) reg_renumber[((((go_temp)->fld[1].rtx))->fld[0].rti nt)] < 16)) || (((((go_temp)->fld[1].rtx))->code) == SIGN_EXTEND && ((((((go_tem p)->fld[1].rtx))->fld[0].rtx))->code) == REG && ((((((go_temp)->fld[1].rtx))->fl d[0].rtx))->mode) == HImode && ((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld [0].rtint)) < 16 || (unsigned) reg_renumber[((((((go_temp)->fld[1].rtx))->fld[0] .rtx))->fld[0].rtint)] < 16))) || ((target_flags & 1) && ((((go_temp)->fld[1].rt x))->code) == MULT && ((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->code) == REG && ((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsign ed) reg_renumber[((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtint)] < 16 )) || (((((((go_temp)->fld[1].rtx))->fld[0].rtx))->code) == SIGN_EXTEND && ((((( (((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((((g o_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->mode) == HImode && (((((((((( go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsi gned) reg_renumber[((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->f ld[0].rtint)] < 16))) && ((((((go_temp)->fld[1].rtx))->fld[1].rtx))->code) == CO NST_INT && (((((((go_temp)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 2 || (( ((((go_temp)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 4 || ((((((go_temp)-> fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 8)))) { { if (((((go_temp)->fld[0]. rtx))->code) == LABEL_REF) goto win; if (((((go_temp)->fld[0].rtx))->code) == RE G && (((((((go_temp)->fld[0].rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg _renumber[((((go_temp)->fld[0].rtx))->fld[0].rtint)] ^ 010) < 8)) goto win; }; } }; } if (((((addr)->fld[0].rtx))->code) == CONST_INT && (unsigned) ((((addr)->f ld[0].rtx))->fld[0].rtint) + 0x80 < 0x100) { rtx go_temp = ((addr)->fld[1].rtx); { if (((go_temp)->code) == PLUS && (((((((go_temp)->fld[0].rtx))->code) == REG && ((((((go_temp)->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[ ((((go_temp)->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((go_temp)->fld[0].rtx)) ->code) == SIGN_EXTEND && ((((((go_temp)->fld[0].rtx))->fld[0].rtx))->code) == R EG && ((((((go_temp)->fld[0].rtx))->fld[0].rtx))->mode) == HImode && ((((((((go_ temp)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumbe r[((((((go_temp)->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) || ((target _flags & 1) && ((((go_temp)->fld[0].rtx))->code) == MULT && ((((((((go_temp)->fl d[0].rtx))->fld[0].rtx))->code) == REG && ((((((((go_temp)->fld[0].rtx))->fld[0] .rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((go_temp)->fld[0].rt x))->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((((go_temp)->fld[0].rtx))->fld[0 ].rtx))->code) == SIGN_EXTEND && ((((((((go_temp)->fld[0].rtx))->fld[0].rtx))->f ld[0].rtx))->code) == REG && ((((((((go_temp)->fld[0].rtx))->fld[0].rtx))->fld[0 ].rtx))->mode) == HImode && ((((((((((go_temp)->fld[0].rtx))->fld[0].rtx))->fld[ 0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((((go_temp)->fld[0 ].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)] < 16))) && ((((((go_temp)->f ld[0].rtx))->fld[1].rtx))->code) == CONST_INT && (((((((go_temp)->fld[0].rtx))-> fld[1].rtx))->fld[0].rtint) == 2 || ((((((go_temp)->fld[0].rtx))->fld[1].rtx))-> fld[0].rtint) == 4 || ((((((go_temp)->fld[0].rtx))->fld[1].rtx))->fld[0].rtint) == 8)))) { { if (((((go_temp)->fld[1].rtx))->code) == LABEL_REF) goto win; if (( (((go_temp)->fld[1].rtx))->code) == REG && (((((((go_temp)->fld[1].rtx))->fld[0] .rtint)) ^ 010) < 8 || (unsigned) (reg_renumber[((((go_temp)->fld[1].rtx))->fld[ 0].rtint)] ^ 010) < 8)) goto win; }; } if (((go_temp)->code) == PLUS && (((((((g o_temp)->fld[1].rtx))->code) == REG && ((((((go_temp)->fld[1].rtx))->fld[0].rtin t)) < 16 || (unsigned) reg_renumber[((((go_temp)->fld[1].rtx))->fld[0].rtint)] < 16)) || (((((go_temp)->fld[1].rtx))->code) == SIGN_EXTEND && ((((((go_temp)->fl d[1].rtx))->fld[0].rtx))->code) == REG && ((((((go_temp)->fld[1].rtx))->fld[0].r tx))->mode) == HImode && ((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rt int)) < 16 || (unsigned) reg_renumber[((((((go_temp)->fld[1].rtx))->fld[0].rtx)) ->fld[0].rtint)] < 16))) || ((target_flags & 1) && ((((go_temp)->fld[1].rtx))->c ode) == MULT && ((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->code) == REG && (( ((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) re g_renumber[((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtint)] < 16)) || (((((((go_temp)->fld[1].rtx))->fld[0].rtx))->code) == SIGN_EXTEND && ((((((((go_ temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->code) == REG && ((((((((go_temp )->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->mode) == HImode && ((((((((((go_tem p)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0].rtint)) < 16 || (unsigned) reg_renumber[((((((((go_temp)->fld[1].rtx))->fld[0].rtx))->fld[0].rtx))->fld[0]. rtint)] < 16))) && ((((((go_temp)->fld[1].rtx))->fld[1].rtx))->code) == CONST_IN T && (((((((go_temp)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 2 || ((((((go _temp)->fld[1].rtx))->fld[1].rtx))->fld[0].rtint) == 4 || ((((((go_temp)->fld[1] .rtx))->fld[1].rtx))->fld[0].rtint) == 8)))) { { if (((((go_temp)->fld[0].rtx))- >code) == LABEL_REF) goto win; if (((((go_temp)->fld[0].rtx))->code) == REG && ( ((((((go_temp)->fld[0].rtx))->fld[0].rtint)) ^ 010) < 8 || (unsigned) (reg_renum ber[((((go_temp)->fld[0].rtx))->fld[0].rtint)] ^ 010) < 8)) goto win; }; } }; } } }; }; The native Tek4404 C compiler was not a happy bunny with this code. ** FATAL ERROR: Internal table overflow - "switch" item count. ** FATAL ERROR: Expression too complex - internal table overflow. Every file of this type just killed the native compiler -- on pretty much every line in these files. "We don't get intimidated by hairy codebases" I said to myself and opened up the files in an editor to refactor it into something the Tek4404 C compiler could manage. Pretty printing the source and manually hoisting out code into functions helped a little and the compiler got further but after many hours of intense editing, compiling, finding I had unclosed brackets somewhere, re-compiling...I threw in the towel. Not least because I knew if I had to fix bugs in the compiler and regenerate all this code, I would have to go through this whole process again. It just was not viable. As a last attempt I pasted some large function into ChatGPT to get it to simplify it and it came back with an answer! From a brief scan of the output, it actually looked sane and I was impressed -- and I am no fan of "AI". But then I started looking into what it had done and it was -- total nonsense! Attempt#2 Accepting that the native C compiler was just not up to the job of parsing complex expressions, I decided to sidestep it entirely and use the gcc m68k cross assembler on my Mac to generate 68000 assembly listings of each file that I could use the native assembler to generate relocatable compilation units (.o files in the modern world, .r files on the Tek4404) that have the binary code, symbols, relocation info etc that can then be linked together using Tek4404 linker which knows about executable file formats specific to this machine. Invoking the cc to generate assembler output (-S option): m68k-elf-gcc -nostdinc -I ~/projects/tek4404/lib/include -w -U__GNUC__ -U__STDC__ -std=c89 -m68010 -S -fleading-underscore $* -o - | Much as with invoking the C preprocessor, we need to ensure we use Tek4404 header files and never search built-in include paths of gcc. This generated text assembler files for each gcc1.42 source file but using a syntax that was not compatible with the Tek4404 assembler. A combination of Motorola vs MIT mnemonics vs gcc syntax quirks along with a bag full of quirks of the Tek4404 assembler regarding symbols, relative branching, code sections that didn't exist in 1984, alignment operators, quick forms of instructions etc. So like any good engineer, I hacked together a fix. I piped the assembler output through a series of very ugly sed scripts to munge the text into a form the Tek4404 assembler would accept. Horrible stuff like: # remove quick versions sed 's/moveq/move/g' | sed 's/addq/add/g' | sed 's/subq/sub/g' | sed 's/link.w/link/g' | sed 's/tst.l[[:blank:]]/cmp.l #0,/g' | sed -E 's/extb.l(.*$)/ext.w\1\n\text.l\1/' | # indexed addressing syntax #sed -E 's/pc\@([0-9]+)\((.)([0-9])(.)\)/ \1\(pc,\2\3\.\4\) /' sed -E 's/pc.(.*)([a-z][0-9])\:(.*)\)/ \1pc,\2.\3)/' | sed -E 's/\((-?[0-9]+)\,([^)]+)\)/\1(\2)/' | piped on and on and on... Finally I was ready to copy the assembler files to the Tek4404 and run the native assembler. Almost home and try, "nothing can stop me now" I said.. I guess I had presumed that because assemblers are not very complicated animals, very little could go wrong, however, I was quickly disabused of that optimistic view! What followed was a series of assembler crashes due to it not being able to handle defined bytes / strings of more than 80 characters and a host of other brokenness. It was all a bit of a shit show TBH with little working and I was getting quite disheartened. When I manually broke up strings to get around the 80 character limits of the assembler, I did manage to get things generating .r files from the assembler -- but would see strange crashes when running the executable. Sometimes. Not being sure whether this was problems with my sed scripts (probably) or something wrong with the assembler, or something wrong with the linker made for a unpleasant feeling of not being sure what I could trust. I took a break. Attempt#3 Somebody on the retro computing forums suggested just compiling everything on the Mac using a cross compiler and then converting the .o files into native .r files. This sounds like an obvious solution, but remember none of the file formats on Tek4404 are documented, hence I had been hoping to rely on the 'last mile' of the toolchain on Tek4404 to do that writing valid .r files. Given I was out of options, I compiled a hello.c on Tek4404 and ran the command relinfo that prints header info about size of TEXT, DATA and BSS sections, and symbols info too. Information for "hello.c" in file "hello.r" : HEADER INFORMATION File type : Relocatable Code type : Non-shared, Not separate i + d Source code was Unknown. Configuration : Unknown Text size : $00007E Data size : $00002C Bss size : $001000 Relocatable information size : $000069 Binary transfer address : $000000 Symbol table size : $000020 Comments size : $0000 RCS info size : $0000 Name size : $0007 No valid xfer address. EXTERNAL SYMBOL INFORMATION ADDRESS BYTES INST SEGMENT NEG/POS SYMBOL - - - - - - - - - - - - - - - - - - - - 0000000C 4 TEXT + _printf 00000044 4 TEXT + _malloc 00000058 4 TEXT + _printf 0000006A 4 TEXT + _printf 00000076 4 TEXT + _exit 5 external records found. Looking at the file contents of hello.r with a hex editor and trying to guess the format of the file header; I could see the $00007E, $00002C and $001000 section sizes shown in the listing above. The file starts with a magic 0x0500 for some reason, its got some other values I dont know what they do, but at offset 64 (0x40) I could see 0x4e56 the 68000 link instruction which often is the start of a function so I guessed the header of .r files is 64 bytes long. After some more digging and experiments I could see the structure of the .r file and reverse engineered the symbol table format and relocation records too. The binary for the TEXT section (size boxed in red) is shown shaded red, followed by the binary for the DATA section (size boxed in green) is shown shaded in green, yellow for the symbols and magenta for the relocation records. It took a while -- and plenty of trial and error -- to reverse engineer the relocation record format but finally got there: /* kind of record */ #define SEGABS 8 #define SEGTEXT 9 #define SEGDATA 10 #define SEGBSS 11 typedef struct { int offset; short kind; short len; } relocheader; /* followed by a name string of length 'len' */ And with that I had enough information to take the (extensively) documented Elf32 file format and write a utility to take the .o files from the gcc cross compiler output and generate Tek4404 .r files. See Git repo: https://github.com/Elektraglide/tek4404/blob/main/ elf32touniflex.c The next step was to run the Tek4404 linker on all my cross compiled and converted .r files copied from my Mac. Unfortunately, gcc has around 60+ .r files requiring linking and this proved too much for the Tek4404 linker -- What. A. Surprise.. I can see the summit! I needed a way of reducing the number of input files and relocation information to stay within the very modest bounds of the Tek4404 linker. I have been writing software for 40+ years but never really thought much about what a Linker actually does beyond output "error: missing symbol XYZ" :-) Reading up on Linker design online it felt like there was a halfway house to a full linker that would just concatenate .r (aka .o) files into a single file. All it would need to do is glue the text and data sections together and combine the symbols and relocation records, and write it back out. How Hard Can It Be? And so I began yet another side mission. rcat was to be my utility to concatenate a bunch of .r files together. Like many things in life, the fear of the unknown is often unfounded and it actually turned out to be straightforward to implement. Lots of care and attention needed when re-basing relocation information and symbol information, but ultimately it turned out to be similar speed to running cat on the files. The code is not a thing of beauty but I wasn't too bothered. See Git repo: https://github.com/Elektraglide/tek4404/blob/main/rcat.c Having run the cross compiler to generate .o files, elf32touniflex to generate .r files and rcat to concatenate them into a single .r I could finally build gcc1.42 on the Tek4404. Woot! Not so fast... As soon as I ran the gcc command, it crashed with a floating error. Ok, lets back up. The Tek4404 has very quirky floating support provided by a National Semiconductors ns32081 floating point co-processor rather than using the Motorola mc68881 designed to work with mc68000. For added insanity, Tek4404 puts the ns32081 hardware accessible only by the kernel. Yes thats right folks, you do a kernel OS call for every floating point operation. Digging around with Ghidra in the C runtime floating point support code, I found every floating point call was proceeded by a call to a function chk1FP() or chk2FP() that validated that your arguments were valid IEEE floating point format before passing them onto a piece of hardware that really didn't give two hoots whether they were valid or not. Again, a quality choice for high performance computing... Seems gcc as its starts up briefly generates floating point values that are never used to generate code but are not valid IEEE and triggered the crash.. A surgical hex edit of the cmathlib (equivalent to libm) to insert a 0x4e75 ( a return from function instruction) into those 2 functions totally fixed the issue. Success! Finally having a decent compiler has been super satisfying and while compiling on Tek4404 does take a while, it generally works well now. Compiling my window manager I wrote with gcc gives a good boost in performance too. See video: https://www.youtube.com/watch?v=v5_ic4TtXFo rcat turns out to be ~10x faster than the native linker (and while I would like to high-five myself for being awesome, it is a bit of a mystery as to how it is quite so much faster than the native one). I plan on making it into a full linker to replace the native one at some point, but there is a long list of things to build for Tek4404 that might jump in front of that! Retro Compilers Tektronix Unix Gcc -- -- Adam Billyard Adam Billyard Follow Written by Adam Billyard 14 Followers *3 Following Techie, codie person Follow No responses yet Help Status About Careers Press Blog Privacy Terms Text to speech Teams