Modern ISAs are fundamentally compiler targets, meaning architectural design must align with compiler optimization strategies.
Translation Pipeline
- Compiler: Translates higher-level source code (
.c) into symbolic assembly (.s), assigning variables to registers and organizing memory allocations. - Assembler: Converts assembly into a binary object file (
.o). Expands pseudoinstructions (simplified mnemonics) into one or more real machine instructions. Generates a symbol table mapping labels to their calculated memory addresses for use by the linker. - Linker: Stitches independently assembled object modules and pre-compiled library routines into a single executable. Patches all internal and external address references and relocates each module to its absolute memory mapping.
- Loader: Transfers the finalized executable from storage to main memory. Initializes the stack pointer and general-purpose registers, then jumps to a startup routine that invokes the program’s main entry point.
- Java Path: Java source compiles to hardware-independent bytecodes. A software interpreter (the JVM) executes bytecodes on any host. Just-In-Time (JIT) compilers translate frequently executed bytecodes into native machine language at runtime for near-native performance.
Translating high-level logic into machine code introduces the need for standardized communication between discrete subroutines.
Compiler Phases
- Front End: Language-dependent parsing.
- High-Level Optimizer: Procedure inlining and loop transformations.
- Global Optimizer: Global and local optimizations, plus register allocation.
- Code Generator: Machine-dependent instruction selection.
Basic Blocks
A basic block is a maximal sequence of instructions with no internal branches and no internal branch targets. Compilers decompose a program into a control-flow graph of basic blocks connected by branch edges. This structure enables instruction scheduling and optimization within each block, and data-flow analysis (liveness, reaching definitions) across the graph. Branches connect these blocks to construct loops and if-else structures.
Register Allocation by Graph Coloring
- Graph coloring maps an unlimited number of virtual registers to a limited set of physical registers.
- Graph coloring is highly effective but requires all registers to be identical, unreserved, and orthogonal.
- When the graph cannot be colored (more live values than physical registers), the compiler spills the least-used variables to memory, inserting load/store instructions around their uses.
ISA Design Guidelines for Compilers
- Provide Regularity (Orthogonality): Operations, data types, and addressing modes should be independent and universally applicable to avoid limiting compiler choices.
- Provide Primitives, Not Solutions: Avoid highly complex instructions tailored for a single high-level language feature, as compilers struggle to match exact use cases.
- Simplify Trade-offs: Make the performance costs of alternative code sequences obvious to the compiler.
- Do Not Bind Constants at Runtime: Do not force the hardware to interpret values dynamically if the compiler can resolve them statically.
Applying these compiler-friendly guidelines and foundational principles results in streamlined, highly efficient architectures like RISC-V.