Building a C-Based Processor
Total Page:16
File Type:pdf, Size:1020Kb
Building a C-based processor S. Woutersen Software Technology Information Technology & Systems TU Delft December, 2005 Abstract Until today every compiler has been developed by the idea to modify source code in such a way the hardware can understand it. Over the years this has resulted in inefficient processors caused by backwards compatibility issues, strange as- sembly code constructions caused by the lack of required instructions and nice instructions supported by hardware, but never used by the software. This research reverses the original design process. It starts by analyzing C code and starts working from there to a processing unit, thus supporting a min- imal amount of instructions required to run C and no backward compatibility with other processing units. To limit the design time, several existing retargetable C compilers are an- alyzed whether they have a useful intermediate language which is completely hardware independent. The LCC compilers LCC Bytecode was found to be the most acceptable language, even though a few modifications must be made to be able to efficiently run the code. The next step is to convert the generated LCC Bytecode into a binary lan- guage. This is done by a standard set of programs including an assembler and a linker. This is the first time some input about the processing unit is required, since neither C nor LCC Bytecode specifies anything about, for example, opcode encoding, while this can make a big difference in execution performance. For now the encoding is simply “guessed” and remains open for future modifications. The last step in the chain is the actual processing unit. A software interpreter programmed in C is build to extensively test compiled programs, and to compare the results with other existing solutions, such as the MIPS R2000, the Intel 386 Architecture and the Intel 8051. Test results show that LCC Bytecode is a fine language which covers C com- pletely. However some modifications where necessary to execute the code effi- ciently. The number of instructions required by LCC Bytecode is much smaller than those supported by most current processors, and therefore, a less complex, and thus cheaper and more reliable, processor design is possible. In terms of speed it is found that on average more instructions per C program are required than when using, for example, the MIPS instruction set, the processor will thus probably be slower than most traditional processors. Contents 1 Introduction 6 2 Generating assembly code 7 2.1 Compilers . 7 2.1.1 GCC . 8 2.1.2 LCC . 8 2.1.3 SDCC . 9 2.2 Conclusion . 9 3 LCC bytecode 10 3.1 Directives . 10 3.1.1 Segments . 11 3.1.2 Functions . 11 3.1.3 Debug information . 13 3.2 Instructions . 13 3.2.1 Labels . 14 3.2.2 Loading and storing variables . 14 3.2.3 Calling to and returning from functions . 16 3.2.4 Arithmetic operators . 18 3.2.5 Program jumps . 19 3.2.6 Variable type conversions . 20 3.2.7 Structures . 21 3.3 Shortcommings . 21 3.3.1 The LABEL instruction . 21 3.3.2 The ARG instruction . 22 3.3.3 Structures . 22 3.3.4 Debug information . 22 3.3.5 Not using the return value . 22 4 Implementation 24 4.1 Overview . 24 4.2 The LCC bytecode generator . 24 4.2.1 Debug symbols . 26 4.2.2 Updating LCC . 27 4.3 The bytecode converter . 28 4.4 The assembler . 28 4.5 The linker . 29 4.5.1 Bootstrap code . 29 1 CONTENTS 2 4.5.2 Library files . 30 4.6 The interpreter . 30 4.6.1 The interpreter engine . 31 4.6.2 Stack frames . 32 4.6.3 New instructions . 34 4.7 Program stack frame example . 35 4.8 Future work . 37 5 Test results 40 5.1 LCC Bytecode analysis . 40 5.1.1 Results of the DISCARD instruction . 40 5.1.2 Instruction usage . 41 5.2 Assembly files . 42 5.3 Processing unit comparison . 42 6 Conclusions 44 A LCC bytecode instructions 45 B Embedding the interpreter 47 B.1 Embedding the interpreter . 47 B.2 Calling a function . 48 B.2.1 Gathering information . 48 B.2.2 Preparing the call . 49 B.2.3 Making the call . 49 C Source files 51 C.1 Common source files . 51 C.2 Converter . 51 C.3 Assembler . 51 C.4 Linker . 51 C.5 Interpreter . 52 C.6 Bootstrap code . 52 D File formats 53 D.1 Bytecode file format . 53 D.2 Assembly file format . 53 D.3 Code segment format . 55 D.4 Library file format . 56 D.5 Executable file format . 56 E User manual 59 E.1 LCC . 59 E.2 Converter . 59 E.3 Assembler . 59 E.4 Linker . 60 E.5 Interpreter . 60 CONTENTS 3 F Example compilation run 61 F.1 C File . 61 F.2 Bytecode file . 61 F.3 Custimized bytecode file . 62 F.4 Binary assembly file . 63 F.5 Executable file . 65 List of Tables 2.1 Compiler comparison . 9 3.1 LCC Bytecode arithmetic operators . 18 3.2 LCC Bytecode conditional jumps . 19 5.1 Discard instruction results . 41 5.2 LCC Instruction frequency . 41 5.3 Assembly file lines comparison . 42 5.4 Instruction count and size . 43 A.1 LCC Instruction type and size suffixes . 45 A.2 LCC Instructions . 46 D.1 Assembly file format . 54 D.2 Label list file format . 54 D.3 Label format . 54 D.4 Label flags . 54 D.5 Segment codes . 55 D.6 Segment format . 55 D.7 Instruction format . 55 D.8 Instruction type format . 56 D.9 LCC Instructions . 57 D.10 Library format . 58 4 List of Figures 3.1 Stack frames after function entrances . 13 3.2 Stack frames for fetching variables . 15 3.3 Stack frames for assigning variables . 16 3.4 Stack frames for calling functions . 17 3.5 Stack frames for arithmetic operations . 18 4.1 Design path . 25 4.2 Tree for z = x + y . 25 4.3 Tree for z = some func() . 26 4.4 Tree for some func() . 26 4.5 Interpreter . 31 4.6 Stack frame step I . 32 4.7 Stack frame step II . 33 4.8 Stack frame step III . 33 4.9 Stack frame step IV . 33 4.10 Final stack frame layout . 34 4.11 Stack frame example Step I . 36 4.12 Stack frame example Step II . 36 4.13 Stack frame example Step III . 36 4.14 Stack frame example Step IV . ..