Skip to content
HN On Hacker News ↗

Making portable my unportable transputer C compiler

▲ 74 points • 11 comments • by nanochess • 3w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,565
PEAK AI % 0% · §1
Analyzed
Sep 24
backend: pangram/v3.3
Segments scanned
1 windows
avg 1565 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,565 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

by Oscar Toledo G. Sep/21/2026 A few weeks ago, I found a floppy disk containing my latest C compiler for transputer. It was enhanced to be compiled with Delorie's G++ or DJGPP (circa 1998). Of course, it generated a lot of warnings in compilation, but it worked just fine because it was a 32-bit compiler being compiled over a 32-bit Intel 80486 processor. This was an intermediate step before converting it to generate code for an AMD Am29000 processor, and this version was further improved removing vestigial Small-C constructs to have a proper lexical analyzer, ANSI C syntax, and full preprocessor. As a throwback exercise, I tried to compile the 1998 version of my transputer C compiler in a modern 64-bit computer, a Macbook Air M1, and I found a lot of difficulties. For example: The code uses integers and pointers interchangeably (both were 32-bit) The type FILE * wasn’t used, instead int was used. There are no prototypes for the compiler functions, so clang complained for good reason because pointers are 64-bit, and integers are 32-bit. Some of these portability problems were inherited from Small-C. As it didn’t had the struct keyword, all required structures were created in a byte pool (char), and words were divided in two bytes (for the 8080 processor), and as I expanded it for a 32-bit platform, these word now were 4 bytes. And worst, pointers are converted to the int type. But now it is impossible to convert a 64-bit pointer into a 32-bit integer. A design change is required! Small-C code fragment where a value is saved as two bytes in an array. First steps This wouldn't be the first time that an old program cannot be compiled in a modern 64-bit system. However, instead of letting this compiler stand as a curiosity working only in emulation, let's make it to work on your modern laptop. Spoiler: It wasn't easy. I put myself an objective: I wanted to modify the compiler just enough to compile in a modern 64-bit platform, but still be able to compile itself for transputer. This means I could use only things implemented in my compiler. The compiler is divided in several source files, and all are called from a single driver file called cc.c. My DJGPP port created two different files: cc.c and cc2.c. The first one meant to be compiled with DJGPP, and the second one meant to be compiled with my C compiler directly on the transputer. I started by moving the variables entrada, salida, and entrada2 to these main files. I modified the one meant for the modern machine to use FILE *, also I started adding Kernighan&Ritchie function prototypes. K&R prototypes are only meant to indicate the correct return type. For example, unsigned char *expresion();. Let’s start with the fun The #include directive saves the current state of the processing inside the source file, and it does it this way: incl[nivel_incl++] = entrada; incl[nivel_incl++] = funcion_actual; incl[nivel_incl++] = comienzo_funcion; incl[nivel_incl++] = linea_actual; incl[nivel_incl++] = dentro_funcion; Where incl is an integer array. The first line is for the current file, the next one is a pointer to the function definition, all the other three are integers. Can you see the big problem? A 64-bit machine has 64-bit pointers, while int is still 32-bit. Also all the compiler source code is still using Spanish comments and variable names. I rewrote the code in terms of a struct: struct { FILE *entrada; unsigned char *funcion_actual; int comienzo_funcion; int linea_actual; int dentro_funcion; } incl[MAX_INCL]; And the new version of the code is portable, and considerably more clean: incl[nivel_incl].entrada = entrada; incl[nivel_incl].funcion_actual = funcion_actual; incl[nivel_incl].comienzo_funcion = comienzo_funcion; incl[nivel_incl].linea_actual = linea_actual; incl[nivel_incl].dentro_funcion = dentro_funcion; nivel_incl++; As the redesign progressed, I made more prototype functions. Just when I was thinking it was a piece of cake... It isn’t so easy The first attention call comes from the expression processing subroutine: /* ** Analiza una expresión, y genera el codigo. */ unsigned char *expresion() { struct nodo *origen; unsigned char *tipo; origen = ultimo_nodo; tipo = almacena_expresion(SI); evalua_arbol(NO); libera_arbol(ultimo_nodo); ultimo_nodo = origen; return tipo; } This function does compilation of a C expression, and returns a pointer to the type. However, the almacena_expresion function does this: /* ** Analiza una expresión y la mantiene en memoria. */ int almacena_expresion(operador_coma) int operador_coma; { int info[1], izq; if (operador_coma) { if (nivel0(info)) carga_valor(info); } else { if (nivel1(info)) carga_valor(info); } return info[0]; } The type pointer is saved into an int array. I started to rewrite the full ccexpr.c file to change int info[1] to unsigned char *info;. In expresion_constante I had to replace int origen with struct nodo *origen. As apparently my compiler gave no warnings about assigning a pointer to integer and vice-versa. Soon I found that the type processing had its own pointer problems. For example, it has a type copy subroutine, and it goes like this: /* ** Copia un tipo en la siguiente posición disponible. */ copia_tipo(tipo) unsigned char *tipo; { int a; while (*tipo >= APUNTADOR) { if (*tipo == MATRIZ) { guarda_tipo(*tipo++); guarda_tipo(*tipo++); guarda_tipo(*tipo++); guarda_tipo(*tipo++); guarda_tipo(*tipo++); } else guarda_tipo(*tipo++); } if (*tipo == STRUCT) { guarda_tipo(*tipo++); guarda_tipo(*tipo++); guarda_tipo(*tipo++); guarda_tipo(*tipo++); } guarda_tipo(*tipo++); } This subroutine does a copy of a C type description. It is useful when setting the same type for multiple variables like int a, b, c;. The function guarda_tipo simply saves a byte into the type pool. However, the array type (MATRIZ) contains the array length (32-bit saved as four bytes), and the struct type (STRUCT) points to the structure definition. A 32-bit pointer converted to an integer and saved as four bytes. Again completely non-portable to a modern 64-bit architecture. The easiest way is extending the calls to eight guarda_tipo function calls (64-bit into eight bytes). But fortunately I found another way. Also, there is the problem that structures are allocated in the same byte pool, just like this (embarrassing code appears below): /* ** Una nueva estructura. */ unsigned char *nueva_estructura(nombre) unsigned char *nombre; { unsigned char *ap; int conteo; if(ultima_estruct != NULL) escribe_entero(ultima_estruct + EST_SIG, sig_tipo); ultima_estruct = sig_tipo; if(lista_estruct == NULL) lista_estruct = sig_tipo; conteo = 0; while(conteo++ < EST_NOMBRE) guarda_tipo(0); while(*nombre) guarda_tipo(*nombre++); guarda_tipo(0); return ultima_estruct; } And then each member in the structure is also allocated into the type pool. Furthermore, I tracked it to three groups of macros where each group defined a pseudo-structure: /* Definiciones de estructura */ #define EST_QUE_ES 0 /* char, indica si es un rótulo de struct o enum */ #define EST_ES_UNION 1 /* char, indica si es una unión o una estructura */ #define EST_TAM 2 /* int, tamaño total de la estructura/unión */ #define EST_LISTA 6 /* char*, lista de miembros */ #define EST_SIG 10 /* char*, siguiente rótulo */ #define EST_NOMBRE 14 /* char[], rótulo */ /* Definiciones de miembros */ #define MIE_TIPO 0 /* char*, tipo del miembro */ #define MIE_POSICION 4 /* int, posición dentro de la estructura */ #define MIE_SIG 8 /* char*, siguiente miembro */ #define MIE_NOMBRE 12 /* nombre del miembro */ /* Definiciones de enumeradores */ #define ENUM_VALOR 0 /* int, valor del enumerador */ #define ENUM_SIG 4 /* char*, siguiente enumerador */ #define ENUM_NOMBRE 8 /* char[], nombre del enumerador */ There are five pointers embedded there. I could work a conversion of this to structures, but I had a better idea. My Am29000 C compiler is an evolution of this same compiler, and I had replaced already these ugly definitions with elegant structs. So I could do a backport, this means I took more recent code and fitted it into an old code. The backported code looks really nice, and better, it is portable: struct rotulo { /* DEFINICIÓN DE ESTRUCTURA */ struct rotulo *sig; /* Siguiente rótulo */ int tam; /* Tamaño total de la estructura */ struct miembro *lista; /* Lista de miembros */ char que_es; /* Indica si es un rótulo de struct o enum */ char es_union; /* Indica si es una unión o una estructura */ char nombre[1]; /* Nombre */ }; struct miembro { /* DEFINICIÓN DE MIEMBRO DE ESTRUCTURA */ struct miembro *sig; /* Siguiente miembro */ int posicion; /* Posición dentro de la estructura */ unsigned char *tipo; /* Tipo declarado */ char nombre[1]; /* Nombre */ }; struct enumerador { /* DEFINICIÓN DE ENUMERADOR */ struct enumerador *sig; /* Siguiente enumerador */ int valor; /* Valor del enumerador */ char nombre[1]; /* Nombre */ }; At the same time, I also translated all the error messages from Spanish to English, because my source code files still were using a Windows codepage, and clang kept complaining of illegal character encodings. My compiler originally didn’t had a standard C library, so some functions like isxdigit, strlen, strcpy, and strcat were replicated in each program. I put these in my cc2.c driver program, while cc.c includes the standard libraries strings.h and ctype.h. I solved a few more errors that came from earlier versions when the expression nodes changed from int to struct nodo * like this one: int izq; izq = ultimo_nodo; Where int izq should be struct nodo *izq. A few more cases where arrays where used instead of structs (again coming back from the time when struct wasn’t available) /* ** Sentencia "break" */ void s_break() { /* Ve si hay un bucle abierto */ if (ultimo_bucle == NULL) {