Program Translation Workflow
- Compiler: Translates higher-level C source code (
.c) into low-level Assembly code (.s). - Assembler: Converts Assembly code into an Object file (
.o), translating instructions into machine-readable binary code. - Linker: Merges multiple object files and external libraries (
.oor.lib) into a single unified Executable program (a.out). - Loader: Loads the fully linked executable from storage into memory to begin execution.
Translating high-level logic into machine code introduces the need for standardized communication between discrete subroutines.
Function Calling Convention
Function execution follows a rigid 6-stage lifecycle: argument placement, target jump (jal), local storage acquisition, task execution, result placement/storage release, and origin return (ret).
Registers are strictly partitioned based on preservation guarantees across function boundaries to minimize the memory access overhead of saving and restoring state.
- Temporary Registers: Hardware state not guaranteed to be preserved across a call. Includes function arguments and return values (
a0–a7), standard temporaries (t0–t6), and the return address (ra). - Saved Registers: Hardware state guaranteed to be preserved across a call by the callee. Includes saved registers (
s0–s11) and the stack pointer (sp). - Hardwired Zero: Register
x0(zero) permanently holds a value, utilized extensively to synthesize operations without adding complex instructions to the ISA.
Stack Frame Operations
- Prologue: Allocates local space by decrementing the stack pointer (e.g.,
addi sp, sp, -framesize) and saves necessary registers to the stack (e.g.,sw ra, framesize-4(sp)). - Epilogue: Restores saved registers (
lw ra, framesize-4(sp)), de-allocates the stack frame by incrementing the pointer (addi sp, sp, framesize), and transfers control back to the caller (ret).
Embedded Variants The RV32E ISA reduces the total register count to 16 to cut hardware costs, constraining both saved and temporary registers strictly within the x0 to x15 architectural range.
Establishing a standardized convention for register usage and function execution allows the assembler to safely abstract complex operations into simpler commands.
Assembly Directives and Pseudoinstructions
The assembler resolves raw hardware instructions into functional machine code while supplying development abstractions for the programmer.
Assembler Directives Directives are configuration commands dictating data alignment, code placement, and memory structure.
- Sections:
.text,.data, and.bssroute subsequent items into the executable code, initialized global variables, or zero-initialized global variables sections, respectively. - Alignment:
.align nstrictly aligns subsequent data to a -byte boundary, whereas.balign naligns data to an exact -byte boundary. - Data Storage:
.string,.word,.half, and.byteallocate specific data primitive types directly into memory. - Compiler Options:
.option rvcand.option pictoggle localized instruction compression and position-independent code generation.
Pseudoinstructions Pseudoinstructions are simplified mnemonics that map transparently to one or more core RISC-V base instructions.
- Zero-dependent (
x0):nopexpands toaddi x0, x0, 0,retexpands tojalr x0, x1, 0, andbeqz rs, offsetmaps tobeq rs, x0, offset. - Zero-independent:
mv rd, rsexpands toaddi rd, rs, 0,li rd, immmaps to sequences ofluiandaddito load generic 32-bit constants, andla rd, symboldynamically computes local addresses usingauipc.
Converting assembly code into object files yields raw machine instructions, which must then be integrated into a unified, executable memory space by the linker.
Memory Layout and Linker Operations
The linker stitches separate object files and libraries together, resolving external symbolic references and updating jump/branch addresses.
Memory Allocation Structure
- Stack: Allocates dynamically downward from high memory addresses.
- Dynamic Data (Heap): Allocated dynamically at runtime, growing upward toward the stack.
- Static Data: Fixed region positioned directly above the executable code.
- Text (Code): Resides in the lower address space bounds (e.g., starting at ), housing machine instructions and statically linked libraries.
Position Independent Code (PIC) PIC enables software to execute successfully irrespective of its absolute mapped memory location by relying heavily on PC-relative addressing (auipc, jalr).
Application Binary Interfaces (ABIs) ABIs dictate the hardware-software boundaries and map C data types to architectural registers.
ilp32: Assigns 32 bits toint,long, andpointertypes. Floating-point arguments bypass specialized hardware and pass via standard integer registers.ilp32f/ilp32d: Passes single- and double-precision floats natively through dedicated floating-point registers.
Linker Relaxation An optimization phase where the linker aggressively replaces multi-instruction call/addressing sequences (like auipc + jalr) with single, shorter instructions when target addresses securely reside within KiB of the global pointer (gp) or thread pointer (tp).
The methodology used to link these compiled resources directly dictates the runtime initialization behavior and the ultimate memory footprint of the binary.
Linking Strategies and Execution
Static Linking All referenced library code is explicitly duplicated into the compiled executable.
- This approach wastes memory space when multiple programs duplicate the same library code, and it permanently ties the static binary to outdated, potentially buggy library versions.
Dynamic Linking Libraries are resolved and mapped into memory only during runtime, specifically at the moment of first function invocation.
- The initial execution triggers a 3-instruction stub, invokes the dynamic linker to map the function into the address space, and overwrites the symbol table pointer to the true address.
- Subsequent calls jump directly to the target via the updated table pointer, executing with a minimal overhead.
- Shared dynamically linked libraries exist only once in system memory.
Loader Operations Handled natively by the operating system, the loader injects the linked executable binary into storage memory and transfers control to the application’s starting address, while simultaneously initiating the dynamic linker for any dynamically mapped dependencies.