From Source Code to a Running Process: Translation and a Full Sort Example

Turning a C program into something the operating system can actually run involves several distinct translation stages, each producing a different intermediate file. This article walks through that full pipeline from compiler to loader, then applies the concepts from this chapter to a complete, realistic example: translating a C sorting routine into RISC-V assembly step by step.

Compilation PipelineLinker and LoaderRISC-V Sort Example

~4 min read · Updated Sep 6, 2026

The Full Journey from Source Code to Execution

A program does not go directly from source code to running on hardware. It passes through several distinct tools, each transforming the program into a different intermediate form before it can finally execute.

  • The Compiler translates high-level source code into assembly language specific to the target processor.
  • The Assembler translates that assembly language into an Object File, a binary file containing machine instructions along with extra bookkeeping information not yet fully finalized.
  • The Linker combines multiple separately compiled object files, along with any needed library code, into a single complete Executable File, resolving references between different files so that a function call made in one file correctly points to that function's actual location in another.
  • The Loader takes the finished executable file, places its instructions and data into memory, and prepares the processor to begin executing it.

Each of these tools solves a distinct problem: the compiler handles language translation, the assembler handles the encoding of individual instructions, the linker handles combining independently developed pieces of a program, and the loader handles the transition from a static file on disk to an actively running process in memory.

Why Separate Compilation Matters

Large software projects are rarely written as a single source file. Splitting a program across many files, compiling each independently, and only combining them at the linking stage allows different parts of a large system to be developed, tested, and recompiled separately — a program does not need to be entirely rebuilt from scratch just because one small file changed.

Putting It All Together: Translating a C Sort Routine

To see these ideas applied concretely, consider a simplified sorting function written in C that sorts an array of integers using a basic exchange-based approach.

void sort(long v[], long n) {
    for (long i = 0; i < n; i += 1) {
        for (long j = i; j > 0 && v[j-1] > v[j]; j -= 1) {
            swap(v, j);
        }
    }
}

Translating this into RISC-V assembly requires combining nearly every concept covered so far in this chapter:

  • The outer and inner for loops become Conditional Branch instructions checking loop bounds, combined with Unconditional Jumps back to the top of each loop.
  • Array indexing such as v[j] requires computing a memory address by combining a base address with an offset, then issuing a Data Transfer Instruction to load or store the value.
  • The call to swap is implemented as a Procedure Call, following the register-saving conventions covered earlier, since sort must preserve its own loop-counter registers across the call.
  • Comparisons such as v[j-1] > v[j] are built from subtraction and branch instructions, since RISC-V has no single dedicated "greater than" branch for every case.

A simplified fragment showing the innermost comparison and loop-back logic:

Loop:
bge x0, j, Exit
ld t0, -8(v_addr)
ld t1, 0(v_addr)
ble t0, t1, Exit
jal ra, swap
addi j, j, -1
jal x0, Loop
Exit:

This example demonstrates that even a short, ordinary piece of application code compiles down to a substantial sequence of simple instructions, each doing exactly one small job.

Why Walking Through a Full Example Matters

Studying individual instruction types in isolation is useful, but a realistic program combines loops, array access, function calls, and comparisons all at once. Seeing how a familiar algorithm like sorting maps onto RISC-V instructions makes clear how the small set of primitives covered throughout this chapter compose into everything real software needs.

Written & researched by Dr. Shahin Siami

Related Articles

Control Hazards: Handling Branches in a Pipelined Processor

Branches create a unique problem for pipelining: the processor must fetch the next instruction before it even knows whether a branch will be taken. This article explains what control hazards are, how branch prediction and delayed resolution attempt to minimize their cost, and what happens when a prediction turns out to be wrong.

Continue

Data Hazards in Pipelines: Forwarding Versus Stalling

Overlapping instruction execution creates a serious problem when one instruction needs a result that a previous instruction has not finished computing yet. This article explains what data hazards are, how forwarding solves most of them without losing any performance, and why some situations still require the pipeline to stall.

Continue

Turning a Single-Cycle Datapath into a Pipelined One

Overlapping instruction execution requires more than just running the same single-cycle hardware faster; it requires physically separating each pipeline stage with storage elements and duplicating control logic across stages. This article explains how pipeline registers preserve instruction state between stages and how control signals travel alongside data through the pipeline.

Continue

An Overview of Pipelining: Overlapping Instruction Execution

A single-cycle processor wastes enormous amounts of hardware idle time since every instruction must fit within the length of the slowest possible instruction. This article introduces pipelining as a solution, explains the classic assembly-line analogy, breaks down the standard five-stage pipeline, and covers why pipelining increases instruction throughput without making any individual instruction faster.

Continue

Designing Control Logic for a Single-Cycle Processor

A datapath alone does nothing without control signals telling it what to do for each instruction. This article explains how control logic reads an instruction's opcode and function fields to generate the exact signals needed to route data correctly, and walks through how a complete single-cycle implementation executes different instruction types.

Continue

Building a Datapath: Connecting Registers, Memory, and the ALU

A datapath is the physical circuitry that moves data through a processor as it executes an instruction. This article breaks down the essential hardware building blocks needed to fetch, decode, and execute instructions, and shows how they are wired together to form a functioning, if simplified, processor datapath.

Continue