Porting a 1998 Transputer C Compiler to 64-Bit Systems
Overview
Oscar Toledo G., a developer known for his work on retrocomputing projects, recently documented the challenges of porting a 1998 C compiler designed for the Transputer architecture to modern 64-bit systems. The original source code was preserved on a floppy disk and had previously been enhanced to compile under Delorie’s G++ or DJGPP environments on 32-bit Intel 80486 processors.
While the legacy code functioned adequately in its original context, attempting to build it on a MacBook Air with an M1 chip revealed significant incompatibilities. The exercise highlights the fragile nature of legacy software when faced with modern memory architectures and stricter compiler standards.
The Root of the Problem
The primary obstacle lies in the fundamental design differences between 32-bit and 64-bit environments, inherited from the original Small-C heritage. In the late 1990s, integers and pointers were both 32 bits wide, allowing the code to treat them as interchangeable data types. This practice was common in embedded systems development at the time but becomes fatal on modern hardware.
"A 64-bit machine has 64-bit pointers, while int is still 32-bit," Toledo explained. "It is impossible to convert a 64-bit pointer into a 32-bit integer without losing critical address information."
The original code also relied on using raw integers instead of the standard FILE * type for file handling. Furthermore, the lack of function prototypes meant that compilers like Clang would reject the code due to implicit declaration warnings and type mismatches. These issues were compounded by the fact that memory structures were manually managed using byte pools, a technique necessitated by Small-C's lack of a struct keyword.
Initial Adaptation Strategies
To bridge the gap between the 1998 codebase and a modern C standard, Toledo adopted a strategy of minimal but precise modifications. The goal was not to rewrite the entire compiler but to make it portable enough to compile on a 64-bit system while still retaining its ability to generate code for the Transputer.
The first step involved splitting the main driver file and introducing K&R-style function prototypes. These prototypes did not define parameter types but indicated correct return types, which helped modern compilers understand basic function signatures. Additionally, variables related to input and output streams were moved to the main files, and FILE * was introduced to replace raw integer file descriptors.
One of the most glaring issues appeared in the #include directive handling. The original code stored processing state in an integer array, mixing pointers and integers indiscriminately. Toledo resolved this by defining a proper structure to hold the include stack:
struct {
FILE *entrada;
unsigned char *funcion_actual;
int comienzo_funcion;
int linea_actual;
int dentro_funcion;
} incl[MAX_INCL];
This change not only fixed the pointer size mismatch but also cleaned up the code significantly, making it more readable and maintainable.
Deep Code Restructuring
Despite these initial fixes, deeper problems emerged during the expression processing phase. Functions that returned type pointers were incorrectly storing them in integer arrays. For instance, the almacena_expresion function saved a pointer value into info[0], which was declared as an integer.
To address this, Toledo had to rewrite large portions of the expression processing logic, changing integer arrays to pointer-based structures. He also updated variable declarations, such as changing int origen to struct nodo *origen, to ensure type safety.
Another complex area involved type copying routines. The original copia_tipo function manually iterated through type descriptions, treating them as simple byte streams. This approach required careful adjustment to ensure that pointer types were handled correctly within the new structural framework.
Challenges and Limitations
The porting process was far from straightforward. Toledo noted that the compiler’s original design, optimized for the constraints of the Am29000 processor and earlier architectures, did not anticipate the strict typing enforced by modern 64-bit compilers. The lack of prototypes and the use of Spanish variable names and comments added to the difficulty, requiring a deep understanding of the original code’s logic.
Importantly, the modified compiler had to remain self-hosting. This meant that the changes made to support 64-bit compilation could not rely on features unavailable in the original Transputer-targeted compiler. Toledo had to ensure that every modification was compatible with the limited subset of C that his compiler previously supported.
"Spoiler: It wasn't easy," Toledo admitted. The project served as a reminder of how much the programming landscape has shifted over the past three decades.
Conclusion
The successful porting of Toledo’s 1998 Transputer C compiler to 64-bit systems is a testament to both the resilience of well-written legacy code and the effort required to keep it relevant. By addressing fundamental issues related to pointer size, type safety, and structural organization, the compiler now runs on modern hardware without emulation.
This project underscores the importance of standard-compliant coding practices and the value of preserving source code from older computing eras. As retrocomputing enthusiasts continue to explore historical software, such porting efforts provide valuable insights into the evolution of programming languages and systems architecture.
Sources
- 1
Porting a 1998 Transputer C Compiler to 64-Bit Systems
https://nanochess.org/transputer_c_compiler.html