<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Masahiro Morino</title>
    <description>The latest articles on DEV Community by Masahiro Morino (@masamori).</description>
    <link>https://dev.to/masamori</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1272991%2F920d4607-51ca-4b23-bbb2-f0fcfbad05a8.JPG</url>
      <title>DEV Community: Masahiro Morino</title>
      <link>https://dev.to/masamori</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masamori"/>
    <language>en</language>
    <item>
      <title>How Does Rust Code Run on a GPU? Tracing cuda-oxide and PTX</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Sun, 01 Nov 2026 11:40:58 +0000</pubDate>
      <link>https://dev.to/masamori/how-does-rust-code-run-on-a-gpu-tracing-cuda-oxide-and-ptx-6jl</link>
      <guid>https://dev.to/masamori/how-does-rust-code-run-on-a-gpu-tracing-cuda-oxide-and-ptx-6jl</guid>
      <description>&lt;p&gt;I used cuda-oxide to run a vector addition kernel written in Rust on a GPU.&lt;/p&gt;

&lt;p&gt;Pass in two arrays and get back their element-wise sums. The calculation is simple, but I wanted to understand what form that Rust function takes before it reaches the GPU.&lt;/p&gt;

&lt;p&gt;Among the files generated during the build was some code called PTX. This time, I will read it to see how Rust's thread indexing and array accesses are represented.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is cuda-oxide?
&lt;/h2&gt;

&lt;p&gt;cuda-oxide is part of NVIDIA's CUDA Rust project. It provides a compiler and related tools for writing kernels for NVIDIA GPUs in Rust. See the &lt;a href="https://github.com/NVIDIA/cuda-rust" rel="noopener noreferrer"&gt;official CUDA Rust repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The official book introduces it this way:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;cuda-oxide is a Rust-to-CUDA compiler that lets you write (SIMT) GPU kernels in safe(ish), idiomatic Rust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Source: &lt;a href="https://nvidia.github.io/cuda-rust/cuda-oxide/" rel="noopener noreferrer"&gt;The cuda-oxide Book&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In other words, you can write the actual function that runs on the GPU in idiomatic Rust and compile it. SIMT stands for Single Instruction, Multiple Threads. It is the programming model used in this example, where many threads execute the same kernel.&lt;/p&gt;

&lt;p&gt;As of October 2026, CUDA Rust describes itself as an early-stage research project, with its features and APIs still under development. Here, I will focus on a small vector addition example and look at what the Rust code becomes. See the &lt;a href="https://github.com/NVIDIA/cuda-rust" rel="noopener noreferrer"&gt;project status&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As someone who writes Rust, I find this a very intriguing project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding arrays: who computes each element?
&lt;/h2&gt;

&lt;p&gt;Here is the kernel we will inspect. A kernel is a function that serves as an entry point for execution on the GPU.&lt;/p&gt;

&lt;p&gt;I verified the code and output on November 1, 2026, under the following conditions. I built the &lt;code&gt;vecadd&lt;/code&gt; example from cuda-rust commit &lt;code&gt;a0cc6cc0bc18722ec5cdfbea136c118a7c051977&lt;/code&gt; in a source tree separate from my ongoing changes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified environment or setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;Linux, x86-64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;NVIDIA GeForce RTX 5060 Ti (Compute Capability 12.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA driver&lt;/td&gt;
&lt;td&gt;595.99.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust toolchain&lt;/td&gt;
&lt;td&gt;nightly-2026-08-28, rustc 1.100.0-nightly (e457a7b0d)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM&lt;/td&gt;
&lt;td&gt;Version 23.1.0 bundled with the toolchain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Release, target sm_120a, ordinary PTX output with bounds checks enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;cuda_device&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;DisjointSlice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cuda_module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[cuda_module]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;kernels&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[kernel]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;vecadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DisjointSlice&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;index_1d&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;idx_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="nf"&gt;.get_mut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c_elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx_raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx_raw&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is based on the &lt;code&gt;vecadd&lt;/code&gt; example included in NVIDIA's cuda-rust repository. The original code is published under the Apache-2.0 license. See the &lt;a href="https://github.com/NVIDIA/cuda-rust" rel="noopener noreferrer"&gt;cuda-rust source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The calculation is &lt;code&gt;c[i] = a[i] + b[i]&lt;/code&gt;. However, there is no &lt;code&gt;for&lt;/code&gt; loop inside the function to traverse the whole array.&lt;/p&gt;

&lt;p&gt;On a CPU, we could compute the elements sequentially like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this GPU kernel, each thread works out the index of the element it is responsible for and computes that one element. Many threads execute the same kernel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread responsible for element 0: c[0] = a[0] + b[0]
Thread responsible for element 1: c[1] = a[1] + b[1]
Thread responsible for element 2: c[2] = a[2] + b[2]
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;thread::index_1d()&lt;/code&gt; produces this index. Its return value, &lt;code&gt;idx&lt;/code&gt;, is not just an integer: it has cuda-oxide's &lt;code&gt;ThreadIndex&lt;/code&gt; type. &lt;code&gt;idx.get()&lt;/code&gt; extracts the integer used to index the input arrays.&lt;/p&gt;

&lt;p&gt;On the output side, &lt;code&gt;DisjointSlice&lt;/code&gt; accepts this thread index and provides a mutable reference to the element assigned to the thread. If &lt;code&gt;c.get_mut(idx)&lt;/code&gt; returns &lt;code&gt;Some&lt;/code&gt;, the thread writes its result to that element. I will leave the details of these types aside for now and focus on which elements each thread reads and where it writes.&lt;/p&gt;

&lt;p&gt;On the host, meaning the CPU side, the program transfers the input data to GPU buffers, specifies the thread configuration, launches the kernel, and retrieves the result. We need to distinguish the whole program, including that preparation, from the kernel running on the GPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  PTX is a representation before GPU machine code
&lt;/h2&gt;

&lt;p&gt;So what is the PTX generated from this kernel?&lt;/p&gt;

&lt;p&gt;PTX stands for Parallel Thread Execution. It is a virtual instruction set for NVIDIA GPUs. It is not specific to Rust: compilers for languages such as CUDA C++ generate PTX too. See the &lt;a href="https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#introduction" rel="noopener noreferrer"&gt;NVIDIA PTX specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PTX contains instruction names and register-like names, but another conversion step remains before we have the machine code the actual GPU executes. You will also encounter the name SASS for the instructions of the physical GPU.&lt;/p&gt;

&lt;p&gt;Here, I will use PTX to see how the Rust expression is broken down into operations. I will not treat it as output that already fixes the final machine-instruction count or physical-register allocation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;vecadd.ptx&lt;/code&gt; file starts with these directives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.version 8.7
.target sm_120a
.address_size 64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They specify the PTX version, target GPU architecture, and address width, respectively. PTX has version and target requirements, so the same file is not necessarily usable with every NVIDIA GPU or driver.&lt;/p&gt;

&lt;p&gt;After these directives come the kernel entry point, &lt;code&gt;.visible .entry vecadd(&lt;/code&gt;, its parameters, and register-variable declarations. Let's look at what happens in the body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does cuda-oxide connect to rustc?
&lt;/h2&gt;

&lt;p&gt;Before reading the PTX, let's establish the path from Rust source to this point.&lt;/p&gt;

&lt;p&gt;cuda-oxide provides a compiler, libraries, and build tools for writing CUDA kernels in Rust. At its center is &lt;code&gt;rustc-codegen-cuda&lt;/code&gt;, a custom code generation backend for rustc.&lt;/p&gt;

&lt;p&gt;It uses rustc for tasks such as parsing Rust, type checking, and borrow checking. GPU code generation takes the resulting MIR and proceeds by transforming it into other representations. See the &lt;a href="https://nvidia.github.io/cuda-rust/cuda-oxide/compiler/architecture-overview.html" rel="noopener noreferrer"&gt;cuda-oxide architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The ordinary PTX generation path we are following can be simplified like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rust kernel
    | rustc performs parsing, type checking, borrow checking, and other work
    v
MIR
    | cuda-oxide transforms it using Pliron IR
    v
LLVM IR
    | LLVM's llc generates code with the NVPTX backend
    v
PTX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pliron is a framework for building and transforming intermediate representations. cuda-oxide uses it to handle representations of Rust MIR and lower them to LLVM IR.&lt;/p&gt;

&lt;p&gt;LLVM produces the PTX at the end of this path. cuda-oxide writes out LLVM IR and passes it to LLVM's code generation tool, &lt;code&gt;llc&lt;/code&gt;. Here too, LLVM provides the infrastructure for generating code for a different target.&lt;/p&gt;

&lt;p&gt;We can break down the roles as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Role in this path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rustc&lt;/td&gt;
&lt;td&gt;Rust analysis, checking, and MIR processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rustc-codegen-cuda and related crates&lt;/td&gt;
&lt;td&gt;Extract GPU-side work and transform MIR into LLVM IR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM's llc and NVPTX backend&lt;/td&gt;
&lt;td&gt;Generate PTX from LLVM IR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cargo-oxide&lt;/td&gt;
&lt;td&gt;Provide commands for building and related operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Host-side code in the same file, such as &lt;code&gt;main&lt;/code&gt;, goes to the ordinary LLVM backend and becomes CPU code. In this example, the host executable also contains the GPU's PTX.&lt;/p&gt;

&lt;p&gt;Calling the body of a &lt;code&gt;#[kernel]&lt;/code&gt; function as an ordinary Rust function on the CPU does not move execution onto the GPU. The host uses a launch API to invoke it as a GPU kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding one line of Rust in the PTX
&lt;/h2&gt;

&lt;p&gt;For this verification, I built and ran the example from the cuda-oxide workspace, specifying the example and target. &lt;code&gt;--verbose&lt;/code&gt; also displays information such as the tools used to generate LLVM IR and PTX.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo oxide run vecadd &lt;span class="nt"&gt;--arch&lt;/span&gt; sm_120a &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final output was as follows. The program compared the results with values calculated on the CPU, and all 1024 elements matched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ SUCCESS: All 1024 elements correct!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same build generated &lt;code&gt;crates/rustc-codegen-cuda/examples/vecadd/vecadd.ptx&lt;/code&gt;. The following excerpts come from that file, with whitespace adjusted for readability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working out the element index
&lt;/h3&gt;

&lt;p&gt;First, let's find the instructions related to &lt;code&gt;thread::index_1d()&lt;/code&gt;. Omitting an intervening parameter-load instruction gives us this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov.u32      %r1, %ctaid.x;
mov.u32      %r2, %ntid.x;
mov.u32      %r3, %tid.x;
mul.wide.u32 %rd11, %r1, %r2;
cvt.u64.u32  %rd12, %r3;
add.s64      %rd13, %rd11, %rd12;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CUDA groups threads into blocks and arranges those blocks in a grid. In one dimension, the global element index is the block index multiplied by the number of threads per block, plus the thread's index within its block. See &lt;a href="https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-cuda-kernels.html#thread-hierarchy" rel="noopener noreferrer"&gt;CUDA's thread hierarchy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first three &lt;code&gt;mov&lt;/code&gt; instructions read the values used in that calculation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Special register&lt;/th&gt;
&lt;th&gt;Value read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%ctaid.x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block index in the x direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%ntid.x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of threads per block in the x direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%tid.x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Thread index within the block in the x direction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, with 256 threads per block, block index 2, and thread index 7 within the block, the element index is &lt;code&gt;2 * 256 + 7 = 519&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the PTX, &lt;code&gt;mul.wide.u32&lt;/code&gt; multiplies two 32-bit values and places the 64-bit result in &lt;code&gt;%rd11&lt;/code&gt;. The next &lt;code&gt;cvt&lt;/code&gt; converts the thread index within the block to 64 bits, and &lt;code&gt;add.s64&lt;/code&gt; adds the values. &lt;code&gt;%rd13&lt;/code&gt; now holds the element index.&lt;/p&gt;

&lt;p&gt;What was a single function call in Rust has become reads of the block and thread coordinates, a multiplication, and an addition.&lt;/p&gt;

&lt;p&gt;Names such as &lt;code&gt;%r1&lt;/code&gt; and &lt;code&gt;%rd13&lt;/code&gt; refer to virtual register variables in PTX. We do not need to interpret their numbers as physical GPU register numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  From an element index to a memory location
&lt;/h3&gt;

&lt;p&gt;Next come the array accesses.&lt;/p&gt;

&lt;p&gt;The index we just calculated is in &lt;code&gt;%rd13&lt;/code&gt;, but the later instructions use &lt;code&gt;%rd14&lt;/code&gt;. This instruction sits between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;selp.b64 %rd14, %rd13, -1, %p7;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;selp&lt;/code&gt; selects between two values according to a condition. If &lt;code&gt;%p7&lt;/code&gt; is true, it places the value of &lt;code&gt;%rd13&lt;/code&gt; in &lt;code&gt;%rd14&lt;/code&gt;; otherwise, it selects &lt;code&gt;-1&lt;/code&gt; as an invalid value. In this output, &lt;code&gt;%p7&lt;/code&gt; represents the one-dimensional launch condition: the y and z dimensions of both the grid and the block must all be 1.&lt;/p&gt;

&lt;p&gt;When that condition holds, &lt;code&gt;%rd14&lt;/code&gt; contains the element index calculated earlier. One instruction that uses it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shl.b64 %rd15, %rd14, 2;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;shl&lt;/code&gt; is a left shift. Here, it shifts the element index left by two bits, multiplying it by four.&lt;/p&gt;

&lt;p&gt;Each element of these arrays is an &lt;code&gt;f32&lt;/code&gt;, which occupies four bytes. For element 519, the distance from the start of the array is &lt;code&gt;519 * 4 = 2076&lt;/code&gt; bytes.&lt;/p&gt;

&lt;p&gt;The PTX also adds that distance to the starting addresses. The relevant instructions are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add.s64 %rd3,  %rd8, %rd15;
add.s64 %rd18, %rd1, %rd15;
add.s64 %rd19, %rd2, %rd15;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this output, &lt;code&gt;%rd1&lt;/code&gt; points to the start of input &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;%rd2&lt;/code&gt; to the start of input &lt;code&gt;b&lt;/code&gt;, and &lt;code&gt;%rd8&lt;/code&gt; to the start of output &lt;code&gt;c&lt;/code&gt;. Adding the byte offset produces the addresses to read from and write to.&lt;/p&gt;

&lt;p&gt;The compact expression &lt;code&gt;a[idx_raw]&lt;/code&gt; hides the element size and the address calculation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read two values, add them, and write back
&lt;/h3&gt;

&lt;p&gt;Now that we have the addresses, let's look at the calculation. In the actual output, input-address calculations alternate with loads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add.s64       %rd18, %rd1, %rd15;
ld.global.b32 %r8, [%rd18];
add.s64       %rd19, %rd2, %rd15;
ld.global.b32 %r9, [%rd19];
add.f32       %r10, %r8, %r9;
st.global.b32 [%rd3], %r10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ld.global.b32&lt;/code&gt; loads 32 bits from global memory. Square brackets, as in &lt;code&gt;[%rd18]&lt;/code&gt;, indicate an access to the value at that address. The two inputs are loaded into &lt;code&gt;%r8&lt;/code&gt; and &lt;code&gt;%r9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;add.f32&lt;/code&gt; performs a 32-bit floating-point addition, placing the sum in &lt;code&gt;%r10&lt;/code&gt;. The &lt;code&gt;.b32&lt;/code&gt; loads handle 32-bit bit patterns, which the addition interprets as &lt;code&gt;f32&lt;/code&gt; values. There is no integer-to-floating-point conversion in between.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;st.global.b32&lt;/code&gt; writes the result to the output address in &lt;code&gt;%rd3&lt;/code&gt;. Unlike a load, the bracketed destination appears on the left.&lt;/p&gt;

&lt;p&gt;We can now see the work corresponding to this line of Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c_elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx_raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx_raw&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The addition itself is one instruction, but the surrounding code computes the element index, calculates memory addresses, and reads and writes the values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bounds checks remain as instructions too
&lt;/h3&gt;

&lt;p&gt;Around that calculation are instructions that test conditions and branch.&lt;/p&gt;

&lt;p&gt;For example, before accessing input &lt;code&gt;a&lt;/code&gt;, we find:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ld.param.b64 %rd4, [vecadd_param_1];
setp.ge.u64  %p13, %rd14, %rd4;
@%p13 bra   $L__BB0_5;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this output, &lt;code&gt;vecadd_param_1&lt;/code&gt; is the length of input &lt;code&gt;a&lt;/code&gt;. It is passed as a parameter separate from the starting address.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setp.ge.u64&lt;/code&gt; tests whether the element index in &lt;code&gt;%rd14&lt;/code&gt; is greater than or equal to the length in &lt;code&gt;%rd4&lt;/code&gt; and places the result in the predicate variable &lt;code&gt;%p13&lt;/code&gt;. The next instruction, &lt;code&gt;@%p13 bra&lt;/code&gt;, branches to the named label if that condition is true.&lt;/p&gt;

&lt;p&gt;What is at the destination?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$L__BB0_5:
    trap;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trap&lt;/code&gt; aborts execution and notifies the host of an error. In this output, an out-of-bounds access to input &lt;code&gt;a&lt;/code&gt; takes this path. Input &lt;code&gt;b&lt;/code&gt; has a similar check. The bounds checks associated with Rust's indexing are visible as PTX comparisons and branches. See the &lt;a href="https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#miscellaneous-instructions-trap" rel="noopener noreferrer"&gt;PTX trap instruction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The output is handled differently: the Rust code writes only when &lt;code&gt;c.get_mut(idx)&lt;/code&gt; returns &lt;code&gt;Some&lt;/code&gt;. Skipping the calculation when the output index is out of bounds and trapping when an input index is out of bounds are two different paths.&lt;/p&gt;

&lt;p&gt;The number of GPU threads can be slightly larger than the number of elements. For example, launching four blocks of 256 threads for 1000 elements gives 1024 threads. The extra 24 threads must not write past the end of the array. These index checks have a practical role.&lt;/p&gt;

&lt;h2&gt;
  
  
  What remains after PTX is generated?
&lt;/h2&gt;

&lt;p&gt;We have read the PTX, but the journey to GPU execution is not finished.&lt;/p&gt;

&lt;p&gt;On the path used here, where PTX is passed to the CUDA driver for loading, the driver's JIT compiler translates it into machine code for the target GPU. It can also cache the compiled result for reuse. See &lt;a href="https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#just-in-time-compilation" rel="noopener noreferrer"&gt;NVIDIA's explanation of JIT compilation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The overall flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build time
Rust kernel -&amp;gt; MIR -&amp;gt; Pliron IR -&amp;gt; LLVM IR -&amp;gt; PTX
Rust host code -&amp;gt; CPU code
                      |
                      v
           Embedded in the host executable

Run time
The host program starts
    | Pass PTX to the CUDA driver and load the module
    v
Machine code for the target GPU (using JIT compilation or its cache)
    | Launch the kernel from the host
    v
GPU threads compute and write results to memory
    |
    v
The host waits for completion and retrieves the results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also paths that build a GPU binary from PTX ahead of time. Here, I am following the path that loads PTX.&lt;/p&gt;

&lt;p&gt;Running Rust code on a GPU involves more than sending the Rust source to it. There is checking in rustc, transformation in cuda-oxide, PTX generation in LLVM, and then GPU-specific compilation and launch work.&lt;/p&gt;

&lt;p&gt;What we found in the PTX was thread-index acquisition, element-address calculation, two loads, one addition, and a store. That short array expression is becoming readable as the work each GPU thread performs.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gpu</category>
      <category>cuda</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>What Is LLVM?</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:06:39 +0000</pubDate>
      <link>https://dev.to/masamori/what-is-llvm-26nc</link>
      <guid>https://dev.to/masamori/what-is-llvm-26nc</guid>
      <description>&lt;p&gt;When you look into how Rust is compiled, the name LLVM comes up.&lt;/p&gt;

&lt;p&gt;Here, I will compare it with Clang and GCC, familiar from C and C++, to clarify LLVM's role and see &lt;strong&gt;what rustc delegates to LLVM&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLVM is infrastructure for building compilers
&lt;/h2&gt;

&lt;p&gt;LLVM is not the name of a programming language. It is a collection of reusable components and technologies for building compilers and related tools. See the &lt;a href="https://llvm.org/" rel="noopener noreferrer"&gt;official LLVM overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The name LLVM can refer to the entire project or to its optimization and code generation infrastructure. When we say that Rust compilation "passes code to LLVM," we mainly mean the latter.&lt;/p&gt;

&lt;p&gt;That includes tasks such as transforming a calculation into a more efficient form and selecting instructions for ARM64 or x86-64. Language implementations can share this infrastructure instead of building those parts from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLVM IR, on the other hand, is a language used during compilation.&lt;/strong&gt; IR stands for Intermediate Representation. It helps to distinguish LLVM, the infrastructure, from LLVM IR, the representation of code that it processes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it refers to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLVM&lt;/td&gt;
&lt;td&gt;Compiler infrastructure and tools; depending on context, its optimization and code generation infrastructure in particular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;The intermediate representation LLVM uses for optimization and code generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rustc&lt;/td&gt;
&lt;td&gt;The Rust compiler, which normally uses LLVM for code generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clang&lt;/td&gt;
&lt;td&gt;A compiler for C, C++, and related languages that uses LLVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCC&lt;/td&gt;
&lt;td&gt;A collection of compilers for multiple languages, with its own optimization and code generation infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We will return to Clang and GCC later. First, I want to separate the infrastructure from the representation of code used inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compilation is more than replacing one instruction with another
&lt;/h2&gt;

&lt;p&gt;"Turning source code into machine code" might suggest replacing each line of Rust with CPU instructions in sequence.&lt;/p&gt;

&lt;p&gt;In practice, several stages sit between the two.&lt;/p&gt;

&lt;p&gt;The compiler parses the source, resolves what names refer to, and checks that types fit together. For Rust, it also needs to check ownership and borrowing rules.&lt;/p&gt;

&lt;p&gt;It then transforms the program through different representations, removes unnecessary computations, and selects instructions suitable for the target CPU. Eventually, it determines registers and instruction sequences and generates machine code.&lt;/p&gt;

&lt;p&gt;The part that handles a language's syntax and meaning is often called the frontend; the part that analyzes and optimizes intermediate representations, the middle end; and the part that generates target-specific code, the backend. Actual implementations do not always have neat boundaries between those three. See the &lt;a href="https://rustc-dev-guide.rust-lang.org/overview.html" rel="noopener noreferrer"&gt;rustc overview&lt;/a&gt; and &lt;a href="https://llvm.org/docs/CodeGenerator.html" rel="noopener noreferrer"&gt;LLVM code generator documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;LLVM provides shared components for tasks including optimization and code generation. Using LLVM does not automatically give a compiler an understanding of Rust's syntax or ownership rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does rustc delegate to LLVM?
&lt;/h2&gt;

&lt;p&gt;When using the usual LLVM backend, rustc analyzes and checks Rust source, processes intermediate representations such as MIR, and generates LLVM IR.&lt;/p&gt;

&lt;p&gt;MIR is an intermediate representation on the Rust side. It is used for borrow checking and Rust-side optimization too. The division of work is not "leave every optimization to LLVM." See &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html" rel="noopener noreferrer"&gt;Rust's MIR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;LLVM then optimizes the LLVM IR and generates code for the target CPU. The resulting object files are passed to a linker. See &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;rustc code generation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rust source code
    ↓ rustc parses, type-checks, and more
Rust-side intermediate representations (including MIR)
    ↓ Borrow checking, optimization, preparation for code generation, and more
LLVM IR
    ↓ LLVM optimizes and generates code for the target CPU
Object files
    ↓ Linker
Executables and other linked outputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This is a simplified view of the division of work. Intermediate representations and stages are omitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Passing code to LLVM" does not normally mean that rustc writes a text file ending in &lt;code&gt;.ll&lt;/code&gt; and asks another command to read it. rustc's LLVM backend uses LLVM's API to construct and process LLVM IR in memory. A &lt;code&gt;.ll&lt;/code&gt; file is a human-readable rendering of that IR. See &lt;a href="https://llvm.org/docs/LangRef.html#introduction" rel="noopener noreferrer"&gt;LLVM IR representations&lt;/a&gt; and &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/lowering-mir.html" rel="noopener noreferrer"&gt;rustc's LLVM IR generation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The compiler we use for Rust is rustc. LLVM is shared infrastructure incorporated into part of its work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does addition look like after it reaches LLVM?
&lt;/h2&gt;

&lt;p&gt;Let's compile a small function to make this more concrete. The outputs below were produced with rustc 1.96.0 on an Apple Silicon Mac.&lt;/p&gt;

&lt;p&gt;Save this as &lt;code&gt;add.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nf"&gt;.wrapping_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function adds two &lt;code&gt;u32&lt;/code&gt; values. &lt;code&gt;wrapping_add&lt;/code&gt; wraps the result around if it does not fit in a &lt;code&gt;u32&lt;/code&gt;. I use it here because I want to observe the addition itself without including panic handling for overflow. See &lt;a href="https://doc.rust-lang.org/std/primitive.u32.html#method.wrapping_add" rel="noopener noreferrer"&gt;u32::wrapping_add&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following command outputs LLVM IR, assembly, and an object file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc add.rs &lt;span class="nt"&gt;--crate-name&lt;/span&gt; llvm_intro &lt;span class="nt"&gt;--crate-type&lt;/span&gt; lib &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-C&lt;/span&gt; link-dead-code &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;llvm-ir,asm,obj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--crate-type lib&lt;/code&gt; treats the source as a library without a &lt;code&gt;main&lt;/code&gt;. &lt;code&gt;-C opt-level=2&lt;/code&gt; enables optimization so we can inspect the shortened computation. Since this example has no caller, I also use &lt;code&gt;-C link-dead-code&lt;/code&gt; to keep the function's code from being removed. We stop before linking and request the three output formats explicitly. See &lt;a href="https://doc.rust-lang.org/rustc/command-line-arguments.html#--emit-specifies-the-types-of-output-files-to-generate" rel="noopener noreferrer"&gt;rustc output options&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/rustc/codegen-options/index.html" rel="noopener noreferrer"&gt;code generation options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Opening &lt;code&gt;llvm_intro.ll&lt;/code&gt;, I found this definition of &lt;code&gt;add&lt;/code&gt;. The file header and attribute definitions outside the function are omitted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="k"&gt;hidden&lt;/span&gt; &lt;span class="k"&gt;noundef&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@_ZN10llvm_intro3add17he381aecb720e834bE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="k"&gt;noundef&lt;/span&gt; &lt;span class="nv"&gt;%a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="k"&gt;noundef&lt;/span&gt; &lt;span class="nv"&gt;%b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unnamed_addr&lt;/span&gt; &lt;span class="vg"&gt;#0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;start:&lt;/span&gt;
  &lt;span class="nv"&gt;%_0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%a&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%_0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the long function name and attributes aside for a moment. Start with these two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="nv"&gt;%_0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%a&lt;/span&gt;
&lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%_0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;i32&lt;/code&gt; is a 32-bit integer type, and &lt;code&gt;%a&lt;/code&gt; and &lt;code&gt;%b&lt;/code&gt; are the argument values. &lt;code&gt;add&lt;/code&gt; adds them, names the result &lt;code&gt;%_0&lt;/code&gt;, and &lt;code&gt;ret&lt;/code&gt; returns it.&lt;/p&gt;

&lt;p&gt;The Rust &lt;code&gt;u32&lt;/code&gt; type and method-call syntax are not preserved as they appeared in the source. The operation is now expressed as "add two 32-bit values and return the result." LLVM IR's &lt;code&gt;i32&lt;/code&gt; type itself does not distinguish signed from unsigned integers; operations and other details express those distinctions where needed. This wrapping addition is represented by &lt;code&gt;add i32&lt;/code&gt; without additional flags. See &lt;a href="https://llvm.org/docs/LangRef.html#integer-type" rel="noopener noreferrer"&gt;LLVM IR integer types&lt;/a&gt; and the &lt;a href="https://llvm.org/docs/LangRef.html#add-instruction" rel="noopener noreferrer"&gt;add instruction&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  An LLVM IR add is not a CPU add instruction
&lt;/h2&gt;

&lt;p&gt;Next, open the generated &lt;code&gt;llvm_intro.s&lt;/code&gt;. The function's instructions are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add w0, w1, w0
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is ARM64 assembly. &lt;code&gt;w0&lt;/code&gt; and &lt;code&gt;w1&lt;/code&gt; are 32-bit CPU registers, and &lt;code&gt;add&lt;/code&gt; adds the two values and puts the result in &lt;code&gt;w0&lt;/code&gt;. Here, the computation uses the registers holding the arguments, and the return value is placed in &lt;code&gt;w0&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;Wait, LLVM IR also had an &lt;code&gt;add&lt;/code&gt;. They are not the same thing, though.&lt;/p&gt;

&lt;p&gt;LLVM IR values such as &lt;code&gt;%a&lt;/code&gt; and &lt;code&gt;%_0&lt;/code&gt; are not CPU register names. LLVM reads the relationships between these values and lowers them into instructions and registers for the target CPU. In this case, that produced ARM64's &lt;code&gt;add w0, w1, w0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can inspect the machine code in &lt;code&gt;llvm_intro.o&lt;/code&gt; too. This command uses &lt;code&gt;llvm-objdump&lt;/code&gt; from Xcode Command Line Tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun llvm-objdump &lt;span class="nt"&gt;--disassemble&lt;/span&gt; &lt;span class="nt"&gt;--demangle&lt;/span&gt; llvm_intro.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the instruction lines from its output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0: 0b000020     add w0, w1, w0
4: d65f03c0     ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From left to right, each line shows the instruction's location, its machine-code encoding, and a readable representation of the instruction. Our LLVM IR addition has become an instruction the CPU can execute.&lt;/p&gt;

&lt;p&gt;LLVM IR was not the machine code itself. It represented the computation at an earlier stage.&lt;/p&gt;

&lt;p&gt;LLVM IR is sometimes described as CPU-independent, but that does not make it universally portable code you can move unchanged to any environment. This &lt;code&gt;.ll&lt;/code&gt; file includes a &lt;code&gt;target triple&lt;/code&gt; identifying the target and a &lt;code&gt;target datalayout&lt;/code&gt; describing data layout. Target-specific details such as calling conventions and pointer sizes also matter. See &lt;a href="https://llvm.org/docs/LangRef.html#data-layout" rel="noopener noreferrer"&gt;LLVM IR data layout&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't rustc implement everything itself?
&lt;/h2&gt;

&lt;p&gt;It is possible to implement machine-code generation specifically for Rust. But handling the instructions and register constraints of each supported CPU, while continuously improving optimization, requires substantial implementation and maintenance work.&lt;/p&gt;

&lt;p&gt;Using LLVM lets rustc translate Rust's operations into LLVM IR and share the optimization and CPU-specific code generation that follow.&lt;/p&gt;

&lt;p&gt;Clang uses LLVM too. Clang handles the meaning of C and C++, then uses infrastructure shared with Rust from LLVM IR onward. See &lt;a href="https://clang.llvm.org/" rel="noopener noreferrer"&gt;Clang's official site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx85stgw9xunapt8mtger.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx85stgw9xunapt8mtger.png" alt="Rust uses rustc and C and C++ use Clang for language-specific processing; both share LLVM IR and LLVM's optimization and CPU-specific code generation infrastructure" width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Language-specific processing and the parts shared through LLVM. The arrows show the use of common infrastructure, not a merger of separate programs' IR into one program.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For example, when LLVM's optimizations improve, compilers for multiple languages can incorporate that improvement. Sharing CPU-specific code generation also reduces the burden of maintaining a separate implementation for each language.&lt;/p&gt;

&lt;p&gt;Of course, adding support for a CPU in LLVM does not mean Rust automatically supports everything about it. Rust-side target configuration, standard library support, and other work are still needed. The benefit is the large amount of infrastructure that can be shared. See &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html#what-is-llvm" rel="noopener noreferrer"&gt;why rustc uses LLVM&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do LLVM and GCC differ?
&lt;/h2&gt;

&lt;p&gt;Both LLVM and GCC have mechanisms for sharing optimization and CPU-specific code generation across multiple languages. They provide separate implementations for the task of turning source code into machine code.&lt;/p&gt;

&lt;p&gt;LLVM provides optimization and code generation functionality as libraries. Clang and rustc translate their respective languages into LLVM IR and use that infrastructure.&lt;/p&gt;

&lt;p&gt;GCC stands for GNU Compiler Collection. It includes language-specific processing, intermediate representations, optimization, and CPU-specific code generation. GCC also shares processing across multiple languages, but its implementation is separate from LLVM's. GIMPLE, for example, is one of the intermediate representations it uses for optimization. See &lt;a href="https://gcc.gnu.org/" rel="noopener noreferrer"&gt;GCC's official site&lt;/a&gt; and &lt;a href="https://gcc.gnu.org/onlinedocs/gccint/GIMPLE.html" rel="noopener noreferrer"&gt;GCC's GIMPLE documentation&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What we are comparing&lt;/th&gt;
&lt;th&gt;LLVM&lt;/th&gt;
&lt;th&gt;GCC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language-specific processing&lt;/td&gt;
&lt;td&gt;Clang, rustc, and other compilers handle their respective languages&lt;/td&gt;
&lt;td&gt;GCC's language-specific frontends handle it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intermediate representations used for optimization&lt;/td&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;GIMPLE and others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU-specific code generation&lt;/td&gt;
&lt;td&gt;Uses LLVM's backends&lt;/td&gt;
&lt;td&gt;Uses GCC's backends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction is not that only LLVM offers reusable components while GCC can only be used as a monolithic command. GCC also has &lt;code&gt;libgccjit&lt;/code&gt;, a library that lets external programs use code generation and related functionality. See the &lt;a href="https://gcc.gnu.org/onlinedocs/jit/intro/index.html" rel="noopener noreferrer"&gt;libgccjit documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When writing C or C++, choosing between Clang and GCC means choosing a compiler as a whole, from language processing through machine-code generation. Choosing Clang means using LLVM's infrastructure for optimization and code generation. See &lt;a href="https://clang.llvm.org/" rel="noopener noreferrer"&gt;Clang&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For Rust, rustc is the compiler handling the language. It does not ask Clang to read Rust. rustc and Clang each handle their own languages while using LLVM's infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can Rust use GCC?
&lt;/h2&gt;

&lt;p&gt;Can Rust code generation use GCC too?&lt;/p&gt;

&lt;p&gt;The usual rustc configuration uses LLVM, but there are efforts to use GCC. It is useful to distinguish &lt;strong&gt;replacing rustc's code generation backend&lt;/strong&gt; from &lt;strong&gt;implementing a Rust frontend within GCC&lt;/strong&gt;. See &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;rustc code generation backends&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first approach is &lt;code&gt;rustc_codegen_gcc&lt;/code&gt;. It keeps rustc's parsing, type checking, borrow checking, and other Rust-side functionality, while using GCC instead of LLVM for code generation. This backend uses &lt;code&gt;libgccjit&lt;/code&gt;, mentioned above. Despite JIT in its name, the project uses it for ahead-of-time compilation: generating machine code before execution. See &lt;a href="https://github.com/rust-lang/rustc_codegen_gcc" rel="noopener noreferrer"&gt;rustc_codegen_gcc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second is &lt;code&gt;gccrs&lt;/code&gt;. Instead of replacing rustc's backend, this project implements a Rust frontend within GCC and connects it to GCC's optimization and code generation. See &lt;a href="https://github.com/Rust-GCC/gccrs" rel="noopener noreferrer"&gt;gccrs&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;The side that parses and checks Rust code&lt;/th&gt;
&lt;th&gt;Optimization and code generation infrastructure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Usual rustc&lt;/td&gt;
&lt;td&gt;rustc&lt;/td&gt;
&lt;td&gt;LLVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rustc with rustc_codegen_gcc&lt;/td&gt;
&lt;td&gt;rustc&lt;/td&gt;
&lt;td&gt;GCC through libgccjit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gccrs&lt;/td&gt;
&lt;td&gt;Rust frontend within GCC&lt;/td&gt;
&lt;td&gt;GCC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;This table shows the division of work. As discussed earlier, rustc also performs optimizations on its side.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One goal of &lt;code&gt;rustc_codegen_gcc&lt;/code&gt; is to make Rust available on platforms that LLVM does not support. Using GCC opens another path for target support and optimization. Which one is faster, however, needs to be evaluated for a particular program and target environment.&lt;/p&gt;

&lt;p&gt;As of October 2026, these efforts remain under development. That does not mean they have the same language-feature coverage or setup requirements as the usual rustc configuration. For example, the &lt;code&gt;rustc_codegen_gcc&lt;/code&gt; README says it requires a patched &lt;code&gt;libgccjit&lt;/code&gt;. Here, the focus is on their different structures rather than installation instructions. See &lt;a href="https://github.com/rust-lang/rustc_codegen_gcc#Getting-Started" rel="noopener noreferrer"&gt;rustc_codegen_gcc requirements&lt;/a&gt; and &lt;a href="https://rust-gcc.github.io/" rel="noopener noreferrer"&gt;gccrs development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rather than "Rust doesn't use GCC," the situation is that &lt;strong&gt;Rust normally uses LLVM, while alternative configurations using GCC are also being developed&lt;/strong&gt;. Rust the language and LLVM the machine-code generation infrastructure are separate things.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLVM's role in Rust compilation
&lt;/h2&gt;

&lt;p&gt;The LLVM project as a whole also includes tools beyond Clang, such as the linker LLD and the debugger LLDB. That is why looking up LLVM brings up tools beyond machine-code generation. See &lt;a href="https://llvm.org/" rel="noopener noreferrer"&gt;LLVM subprojects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To understand Rust compilation, it helps to distinguish rustc, which handles Rust's rules; LLVM IR, which represents the intermediate computation; and LLVM, which performs further optimization and code generation.&lt;/p&gt;

&lt;p&gt;We may type just one command, &lt;code&gt;rustc&lt;/code&gt;, but inside it, Rust-specific mechanisms work together with infrastructure shared with other languages.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>compiling</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>What Happens Before Rust Calls Your main Function?</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Sun, 18 Oct 2026 09:00:31 +0000</pubDate>
      <link>https://dev.to/masamori/what-happens-before-rust-calls-your-main-function-1175</link>
      <guid>https://dev.to/masamori/what-happens-before-rust-calls-your-main-function-1175</guid>
      <description>&lt;p&gt;When we write a Rust program, &lt;code&gt;fn main()&lt;/code&gt; is where our code begins.&lt;/p&gt;

&lt;p&gt;Does the CPU jump straight into that function? Who puts the executable into memory, and where does execution actually begin?&lt;/p&gt;

&lt;p&gt;Let's inspect a small Rust program and follow what runs before the &lt;code&gt;main&lt;/code&gt; we wrote. Looking inside the executable, it turns out there is more than one &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This experiment uses an Apple Silicon Mac and an ordinary executable that uses the standard library, &lt;code&gt;std&lt;/code&gt;. My environment is rustc 1.96.0 on macOS 26.6.2, with tools from Xcode Command Line Tools. Entry mechanisms, internal function names, and addresses can change across operating systems and Rust versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  An executable needs some preparation before it can run
&lt;/h2&gt;

&lt;p&gt;An executable on disk contains machine code and data. When the CPU executes instructions, though, it uses addresses in the process's memory.&lt;/p&gt;

&lt;p&gt;At startup, the OS reads information from the executable and maps its code and data into the process's virtual address space. This does not mean copying the entire file into memory before anything can run. The system can establish mappings and load individual pages as they are needed.&lt;/p&gt;

&lt;p&gt;For this macOS executable, the dynamic linker, &lt;code&gt;dyld&lt;/code&gt;, also runs. It makes shared libraries available, performs the required reference resolution and initialization, and passes control to the executable's entry point. See the &lt;a href="https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h" rel="noopener noreferrer"&gt;Mach-O format definitions&lt;/a&gt; and &lt;a href="https://github.com/apple-oss-distributions/dyld/blob/main/dyld/dyldMain.cpp" rel="noopener noreferrer"&gt;dyld startup implementation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The dynamic linker works when the program starts. The build-time linker combines object files into an executable; the dynamic linker loads the shared libraries that executable uses and connects references to functions and other symbols.&lt;/p&gt;

&lt;p&gt;Once that preparation is done, do we finally reach our own &lt;code&gt;main&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzu5wohhagp9c92a937x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzu5wohhagp9c92a937x.png" alt="The OS and dyld prepare the program, the compiler-generated main calls Rust startup code, and that code reaches the user's main" width="800" height="1429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The path we will follow in this executable. Helper functions and details such as library initialization are omitted. The dyld and Rust startup routines are themselves code already running on the CPU.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build an executable with an ordinary main
&lt;/h2&gt;

&lt;p&gt;No special attributes or &lt;code&gt;unsafe&lt;/code&gt; are needed. Save this as &lt;code&gt;main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from Rust main"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile and run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc main.rs &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;debuginfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;link&lt;/span&gt;,llvm-ir
./main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello from Rust main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--emit=link,llvm-ir&lt;/code&gt; produces both the executable, &lt;code&gt;main&lt;/code&gt;, and the LLVM IR, &lt;code&gt;main.ll&lt;/code&gt;. I disable optimization for our code to make the calls easier to observe, and include debug information so a debugger can follow functions and source lines. This does not disable optimization in the already-built standard library. See &lt;a href="https://doc.rust-lang.org/rustc/command-line-arguments.html#--emit-specifies-the-types-of-output-files-to-generate" rel="noopener noreferrer"&gt;rustc output options&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/rustc/codegen-options/index.html" rel="noopener noreferrer"&gt;code generation options&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two main functions?
&lt;/h2&gt;

&lt;p&gt;Disassemble the executable. &lt;code&gt;--demangle&lt;/code&gt; makes Rust's encoded symbol names easier to read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun llvm-objdump &lt;span class="nt"&gt;--disassemble&lt;/span&gt; &lt;span class="nt"&gt;--demangle&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is long. For now, here are just two function headings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00000001000009f4 &amp;lt;main::main::h2dd6e7c25b480450&amp;gt;:
0000000100000a2c &amp;lt;_main&amp;gt;:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait. Two &lt;code&gt;main&lt;/code&gt;s? And at different addresses?&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;main::main&lt;/code&gt;, the first &lt;code&gt;main&lt;/code&gt; is our crate's name. The second is the function we defined. A hash is appended to distinguish symbols. Inside this function, I found the code that prints our message.&lt;/p&gt;

&lt;p&gt;The other function, &lt;code&gt;_main&lt;/code&gt;, is an entry wrapper generated by the compiler for this target. In Mach-O symbol output, its name has a leading underscore.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fn main()&lt;/code&gt; in our source and the externally called entry function are separate functions. We did not write the entry wrapper ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one does the executable identify as its entry point?
&lt;/h2&gt;

&lt;p&gt;Names alone do not establish which function is the entry point. Let's inspect the executable's metadata too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;otool &lt;span class="nt"&gt;-l&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mach-O has load commands that describe information needed for memory mapping and startup. These are relevant excerpts from my output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  segname __TEXT
   vmaddr 0x0000000100000000
  fileoff 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          cmd LC_LOAD_DYLINKER
      cmdsize 32
         name /usr/lib/dyld (offset 12)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       cmd LC_MAIN
   cmdsize 24
  entryoff 2604
 stacksize 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LC_LOAD_DYLINKER&lt;/code&gt; identifies the dynamic linker, while &lt;code&gt;LC_MAIN&lt;/code&gt; records the entry location. Here, &lt;code&gt;entryoff&lt;/code&gt; gives the entry's byte offset in &lt;code&gt;__TEXT&lt;/code&gt;. See Mach-O's &lt;a href="https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h" rel="noopener noreferrer"&gt;entry_point_command&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;2604&lt;/code&gt; in hexadecimal is &lt;code&gt;0xa2c&lt;/code&gt;. In this executable, &lt;code&gt;__TEXT&lt;/code&gt; starts at file offset zero and has a preferred virtual address of &lt;code&gt;0x100000000&lt;/code&gt;, giving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x100000000 + 0xa2c = 0x100000a2c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matches &lt;code&gt;_main&lt;/code&gt;, not the &lt;code&gt;0x1000009f4&lt;/code&gt; address of our &lt;code&gt;main::main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are addresses in the layout recorded in the file. ASLR can shift the actual load addresses at runtime. Also, &lt;code&gt;LC_MAIN&lt;/code&gt; does not mean "the first instruction the CPU executes at startup." It identifies this executable's entry point, reached after preparation by dyld and other startup code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The entry wrapper does not call our main directly
&lt;/h2&gt;

&lt;p&gt;Run the same disassembly command and extract the section starting with &lt;code&gt;&amp;lt;_main&amp;gt;:&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun llvm-objdump &lt;span class="nt"&gt;--disassemble&lt;/span&gt; &lt;span class="nt"&gt;--demangle&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000100000a2c &amp;lt;_main&amp;gt;:
100000a2c: a9bf7bfd    stp  x29, x30, [sp, #-0x10]!
100000a30: 910003fd    mov  x29, sp
100000a34: aa0103e2    mov  x2, x1
100000a38: aa0003e8    mov  x8, x0
100000a3c: 93407d01    sxtw x1, w8
100000a40: 90000000    adrp x0, 0x100000000 &amp;lt;dyld_stub_binder+0x100000000&amp;gt;
100000a44: 9127d000    add  x0, x0, #0x9f4
100000a48: 52800003    mov  w3, #0x0
100000a4c: 97ffff8f    bl   0x100000888 &amp;lt;std::rt::lang_start::hf66e9cda02e72e37&amp;gt;
100000a50: a8c17bfd    ldp  x29, x30, [sp], #0x10
100000a54: d65f03c0    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each instruction line contains its address, the machine-code encoding, and a readable representation of the instruction. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100000a34:  aa0103e2    mov x2, x1
Address     Encoding    Instruction and operands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mov x2, x1&lt;/code&gt; copies the value in register &lt;code&gt;x1&lt;/code&gt; into &lt;code&gt;x2&lt;/code&gt;. Registers are locations inside the CPU that hold values and addresses temporarily.&lt;/p&gt;

&lt;p&gt;We do not need to decode every instruction. Start with &lt;code&gt;bl&lt;/code&gt;, the instruction that calls a function. Under the calling convention used here, the first argument is passed in register &lt;code&gt;x0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The call goes to &lt;code&gt;std::rt::lang_start&lt;/code&gt;, rather than our &lt;code&gt;main&lt;/code&gt;. Before that, &lt;code&gt;adrp&lt;/code&gt; and &lt;code&gt;add&lt;/code&gt; construct &lt;code&gt;0x1000009f4&lt;/code&gt; in &lt;code&gt;x0&lt;/code&gt;. That was the address of our &lt;code&gt;main::main&lt;/code&gt;. Focus on the address calculation here, rather than the symbolic annotation printed beside &lt;code&gt;adrp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The wrapper passes the location of our &lt;code&gt;main&lt;/code&gt; as an argument to the standard library's startup routine. In effect: "Start this Rust program using this function."&lt;/p&gt;

&lt;p&gt;We can check the same relationship in LLVM IR. Here is the entry wrapper from &lt;code&gt;main.ll&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="nv"&gt;%1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unnamed_addr&lt;/span&gt; &lt;span class="vg"&gt;#4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;top:&lt;/span&gt;
  &lt;span class="nv"&gt;%2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sext&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%0&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;
&lt;span class="c1"&gt;; call std::rt::lang_start&lt;/span&gt;
  &lt;span class="nv"&gt;%3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="vg"&gt;@_ZN3std2rt10lang_start17hf66e9cda02e72e37E&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="vg"&gt;@_ZN4main4main17h2dd6e7c25b480450E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="nv"&gt;%1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i8&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;%4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;trunc&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%3&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the parentheses on the line calling &lt;code&gt;lang_start&lt;/code&gt;, the first argument is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="vg"&gt;@_ZN4main4main17h2dd6e7c25b480450E&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ptr&lt;/code&gt; is the pointer type, and &lt;code&gt;@_ZN4main4main…&lt;/code&gt; refers to our &lt;code&gt;main::main&lt;/code&gt; function. This argument is a pointer to that function.&lt;/p&gt;

&lt;p&gt;Replacing the long names with readable descriptions gives the following argument order. This is explanatory notation, not literal LLVM IR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lang_start(pointer to our main, argc, argv, setting)
           ↑ first argument
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remaining arguments are the command-line argument count, &lt;code&gt;argc&lt;/code&gt;; &lt;code&gt;argv&lt;/code&gt;, which points to the argument strings; and a setting related to signal handling.&lt;/p&gt;

&lt;p&gt;Our own &lt;code&gt;main&lt;/code&gt; has no declared parameters. The entry wrapper still handles information passed in from the operating system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does lang_start prepare?
&lt;/h2&gt;

&lt;p&gt;The entry wrapper passed our &lt;code&gt;main&lt;/code&gt; to &lt;code&gt;std::rt::lang_start&lt;/code&gt; instead of calling it directly. What does &lt;code&gt;lang_start&lt;/code&gt; do before calling our function? Let's look at the standard library implementation.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;library/std/src/rt.rs&lt;/code&gt; shipped with my Rust toolchain, &lt;code&gt;lang_start&lt;/code&gt; is defined as follows. Its attributes are omitted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;lang_start&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Termination&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;'static&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;isize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sigpipe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;isize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;lang_start_internal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;backtrace&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;__rust_begin_short_backtrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.report&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_i32&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sigpipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first parameter, &lt;code&gt;main: fn() -&amp;gt; T&lt;/code&gt;, is the function pointer received from the entry wrapper. &lt;code&gt;lang_start&lt;/code&gt; wraps the work of calling that function and converting its result into an exit code in a closure, then passes that closure to &lt;code&gt;lang_start_internal&lt;/code&gt;. The &lt;code&gt;&amp;amp;move || ...&lt;/code&gt; part creates the closure and passes a reference to it.&lt;/p&gt;

&lt;p&gt;Next, here is an excerpt from the body of &lt;code&gt;lang_start_internal&lt;/code&gt;. Comments mark omitted code; this excerpt is not independently compilable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Excerpt from the body of lang_start_internal&lt;/span&gt;
&lt;span class="nn"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;catch_unwind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigpipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ret_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;catch_unwind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Panic handling omitted&lt;/span&gt;
        &lt;span class="mi"&gt;101&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ret_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ret_code&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;isize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Coordination of concurrent thread exits omitted&lt;/span&gt;

    &lt;span class="n"&gt;ret_code&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle_rt_panic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It first calls &lt;code&gt;init&lt;/code&gt;, then runs the supplied closure through &lt;code&gt;panic::catch_unwind(main)&lt;/code&gt;. That leads to our own &lt;code&gt;main&lt;/code&gt;. Afterward, it calls &lt;code&gt;cleanup&lt;/code&gt; and returns the exit code, &lt;code&gt;ret_code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Leaving the detailed error handling aside, the sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lang_start_internal
    ↓
Initialize Rust runtime state
    ↓
Invoke the supplied closure, reaching our main
    ↓
Handle the return value as an exit code
    ↓
Clean up stdout and other runtime state
    ↓
Return the exit code to the entry wrapper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialization records the main thread's ID and proceeds to platform-specific preparation. In the Unix implementation used here, that includes checking the standard I/O file descriptors, configuring &lt;code&gt;SIGPIPE&lt;/code&gt; behavior, and preparing stack overflow handling. On macOS, it also sets the main thread's name. See &lt;a href="https://doc.rust-lang.org/src/std/rt.rs.html" rel="noopener noreferrer"&gt;std startup&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/src/std/sys/pal/unix/mod.rs.html" rel="noopener noreferrer"&gt;Unix initialization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SIGPIPE&lt;/code&gt; is a signal relevant to situations such as writing to a pipe whose reader has gone away. Startup handles settings we rarely think about when using &lt;code&gt;println!&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rust does not prepare everything from scratch, though. The Apple-specific command-line argument implementation uses information already prepared by dyld. Rust's preparation builds on work done by the OS and dynamic linker. See the &lt;a href="https://doc.rust-lang.org/src/std/sys/args/unix.rs.html" rel="noopener noreferrer"&gt;Apple argument implementation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;main&lt;/code&gt; returns normally, execution proceeds to cleanup. The call is also wrapped in &lt;code&gt;catch_unwind&lt;/code&gt; to handle panic unwinding. With a configuration that aborts on panic, however, the process terminates without unwinding.&lt;/p&gt;

&lt;p&gt;Asking whether Rust "has a runtime" can be ambiguous. What we can see here is that this ordinary executable using &lt;code&gt;std&lt;/code&gt; contains startup routines implemented in Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop inside main and look at the path we took
&lt;/h2&gt;

&lt;p&gt;We can also check this while the program runs. Use LLDB to place a breakpoint in our own &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lldb ./main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside LLDB, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;breakpoint set -n main::main
run
bt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bt&lt;/code&gt; walks the current call stack: the current function, its caller, that caller's caller, and so on. My program stopped at the &lt;code&gt;println!&lt;/code&gt; line. See the &lt;a href="https://lldb.llvm.org/use/tutorial.html" rel="noopener noreferrer"&gt;LLDB tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are the relevant frames, with long arguments, hashes, and line numbers removed. The frame numbers are unchanged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frame #0:  main::main
frame #1:  core::ops::function::FnOnce::call_once
frame #2:  std::sys::backtrace::__rust_begin_short_backtrace
frame #3:  std::rt::lang_start::{closure}
           (frames #4 through #11 omitted)
frame #12: std::rt::lang_start_internal
frame #13: std::rt::lang_start
frame #14: main
frame #15: dyld`start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current location is at the top, so read from bottom to top to follow the calls that brought us here.&lt;/p&gt;

&lt;p&gt;From dyld, through the entry &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;lang_start&lt;/code&gt;, and &lt;code&gt;lang_start_internal&lt;/code&gt;, we finally reach our own &lt;code&gt;main::main&lt;/code&gt;. Along the way are a closure and helper functions used to invoke it.&lt;/p&gt;

&lt;p&gt;Now "code runs before our main" is something we can see in an actual call stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning from main is not the final step either
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;main&lt;/code&gt; has no explicit return type, so it returns &lt;code&gt;()&lt;/code&gt;. &lt;code&gt;lang_start&lt;/code&gt; uses the &lt;code&gt;Termination&lt;/code&gt; trait's &lt;code&gt;report&lt;/code&gt; method to convert that result into an exit status. For &lt;code&gt;()&lt;/code&gt;, the status indicates success. See &lt;a href="https://doc.rust-lang.org/std/process/trait.Termination.html" rel="noopener noreferrer"&gt;Termination&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The startup code does more than invoke our function: it translates a Rust return value into a result for the program as a whole. After standard library cleanup, the entry wrapper returns an exit code, leading to the system's process termination handling.&lt;/p&gt;

&lt;p&gt;There are other ways to terminate, such as &lt;code&gt;process::exit&lt;/code&gt; and aborting, which do not return through this entire path. We followed the normal completion of this small program.&lt;/p&gt;

&lt;p&gt;Even for a three-line program, the OS and dyld prepare it, and Rust's standard library performs initialization, before the &lt;code&gt;main&lt;/code&gt; we wrote is called.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Why Rust Object Files Still Need a Linker: Symbols and Relocations</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Sat, 10 Oct 2026 10:59:01 +0000</pubDate>
      <link>https://dev.to/masamori/why-rust-object-files-still-need-a-linker-symbols-and-relocations-5729</link>
      <guid>https://dev.to/masamori/why-rust-object-files-still-need-a-linker-symbols-and-relocations-5729</guid>
      <description>&lt;p&gt;When you compile Rust code, object files are produced along the way. They have the extension &lt;code&gt;.o&lt;/code&gt; and contain machine code and data. The linker combines them to build an executable or another output.&lt;/p&gt;

&lt;p&gt;But something bothers me here. If the machine code is already there, shouldn't the CPU be able to execute it? Why is linking still necessary?&lt;/p&gt;

&lt;p&gt;Let's compile a pair of small functions separately and look at a state where &lt;strong&gt;the machine code exists, but the destination of a function call has not been determined yet&lt;/strong&gt;. Symbols and relocations will help us read that state.&lt;/p&gt;

&lt;p&gt;I tried this on an Apple Silicon Mac. The object format is Mach-O, and the instructions are ARM64. On Linux or Windows, the format and inspection commands differ. The addresses and instruction output below were obtained on October 10, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can we generate machine code without the callee's implementation?
&lt;/h2&gt;

&lt;p&gt;In everyday Rust, we define functions with &lt;code&gt;fn&lt;/code&gt; and use Rust's usual mechanisms to call functions in other crates. We do not need to write &lt;code&gt;unsafe&lt;/code&gt; or &lt;code&gt;extern "C"&lt;/code&gt; just to use the linker.&lt;/p&gt;

&lt;p&gt;For this experiment, I want to build the caller and callee as separate &lt;code&gt;.o&lt;/code&gt; files and &lt;strong&gt;compare the state without the callee's implementation with the state after linking&lt;/strong&gt;. That calls for a slightly unusual setup. &lt;code&gt;extern "C"&lt;/code&gt; gives both sides the same calling convention, and &lt;code&gt;#[unsafe(no_mangle)]&lt;/code&gt; makes the symbol names easy to follow. The &lt;code&gt;"C"&lt;/code&gt; specifies a calling convention; it does not mean we are using C source code. Both implementations are written in Rust.&lt;/p&gt;

&lt;p&gt;With an external function declared this way, however, the compiler alone cannot verify that the declaration matches the actual definition. That is why &lt;code&gt;unsafe&lt;/code&gt; appears in this declaration and call. It is part of the setup chosen to observe the linker, rather than a requirement for ordinary Rust function calls.&lt;/p&gt;

&lt;p&gt;First, let's write a function that simply calls &lt;code&gt;answer&lt;/code&gt;. Save this as &lt;code&gt;caller.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#![no_std]&lt;/span&gt;

&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;call_answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have the body of &lt;code&gt;call_answer&lt;/code&gt;, but not the body of &lt;code&gt;answer&lt;/code&gt;. All we have for &lt;code&gt;answer&lt;/code&gt; is a declaration saying that it takes no arguments and returns a &lt;code&gt;u32&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since this looks a little different from everyday Rust, let's go through the settings used for the experiment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;unsafe extern "C"&lt;/code&gt; block declares an external function. We specify its type and calling convention, but this declaration alone does not let the compiler verify that they match the actual definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extern "C"&lt;/code&gt; selects the C ABI calling convention. Here, it gives the caller and the definition the same convention. We will write the implementation in Rust later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[unsafe(no_mangle)]&lt;/code&gt; disables the usual symbol name mangling so that we can follow the function names easily. It is an unsafe attribute because the names can collide with other symbols.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#![no_std]&lt;/code&gt; prevents this small library from automatically using the standard library, &lt;code&gt;std&lt;/code&gt;. The &lt;code&gt;main&lt;/code&gt; function we write later will use &lt;code&gt;std&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more about these settings, see the Rust Reference sections on &lt;a href="https://doc.rust-lang.org/reference/items/external-blocks.html" rel="noopener noreferrer"&gt;External blocks&lt;/a&gt;, &lt;a href="https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute" rel="noopener noreferrer"&gt;The no_mangle attribute&lt;/a&gt;, and &lt;a href="https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute" rel="noopener noreferrer"&gt;The no_std attribute&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following command produces an object file before linking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc caller.rs &lt;span class="nt"&gt;--crate-type&lt;/span&gt; lib &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;obj &lt;span class="nt"&gt;-o&lt;/span&gt; caller.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--emit=obj&lt;/code&gt; requests a native object file. &lt;code&gt;--crate-type lib&lt;/code&gt; tells rustc to compile code without a &lt;code&gt;main&lt;/code&gt; function as a library. Here, we are taking the &lt;code&gt;.o&lt;/code&gt; requested by &lt;code&gt;--emit=obj&lt;/code&gt;, rather than an &lt;code&gt;.rlib&lt;/code&gt;. We also specify &lt;code&gt;-C panic=abort&lt;/code&gt;, so unwinding on panic is outside this experiment. &lt;a href="https://doc.rust-lang.org/rustc/command-line-arguments.html#--emit-specifies-the-types-of-output-files-to-generate" rel="noopener noreferrer"&gt;Command-line Arguments: --emit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even without the body of &lt;code&gt;answer&lt;/code&gt;, &lt;code&gt;caller.o&lt;/code&gt; was produced.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;file caller.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caller.o: Mach-O 64-bit object arm64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this part works. But what happened to the call to &lt;code&gt;answer&lt;/code&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  Object files contain more than machine code
&lt;/h2&gt;

&lt;p&gt;An object file is more than a sequence of machine instructions. Alongside code and data, it carries information needed to connect them later.&lt;/p&gt;

&lt;p&gt;We will focus on three things:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;th&gt;What we will examine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code section&lt;/td&gt;
&lt;td&gt;What the machine code for &lt;code&gt;call_answer&lt;/code&gt; looks like&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol table&lt;/td&gt;
&lt;td&gt;Which names this file defines, and which it needs from elsewhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relocation information&lt;/td&gt;
&lt;td&gt;Where references must be adjusted, and which targets they refer to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A section is a region that holds a particular kind of content, such as code or data. In my &lt;code&gt;caller.o&lt;/code&gt;, the instructions were in the &lt;code&gt;__text&lt;/code&gt; section of &lt;code&gt;__TEXT&lt;/code&gt;. Here, &lt;code&gt;text&lt;/code&gt; means code, rather than written text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ooy40wctojwh59ocqth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ooy40wctojwh59ocqth.png" alt="caller.o contains the instructions for call_answer, a symbol table with a definition of call_answer and an undefined reference to answer, and relocation information for the instruction at offset 8" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The three kinds of information we examine in caller.o. This diagram does not show their actual positions or sizes in the file. Headers and other information are omitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mach-O also has headers and load commands that let tools locate this information. We will read the parts relevant to the function call rather than decode the entire file format. On macOS, &lt;code&gt;nm&lt;/code&gt; inspects the symbol table, and &lt;code&gt;otool&lt;/code&gt; inspects sections and other contents. &lt;a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/building_files.html" rel="noopener noreferrer"&gt;Building Mach-O Files — Apple&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The symbol table records the missing implementation
&lt;/h2&gt;

&lt;p&gt;First, let's use &lt;code&gt;nm&lt;/code&gt; to see the externally visible symbols:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nm &lt;span class="nt"&gt;-g&lt;/span&gt; caller.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 U _answer
0000000000000000 T _call_answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;symbol&lt;/strong&gt; is information used to refer to a function or another entity by name. In this Mach-O output, the source names &lt;code&gt;answer&lt;/code&gt; and &lt;code&gt;call_answer&lt;/code&gt; appear with a leading underscore.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T&lt;/code&gt; denotes a definition in the code section; &lt;code&gt;U&lt;/code&gt; denotes a symbol that is undefined in this file. In other words, &lt;code&gt;call_answer&lt;/code&gt; is here, while the definition of &lt;code&gt;answer&lt;/code&gt; must come from elsewhere. &lt;a href="https://llvm.org/docs/CommandGuide/llvm-nm.html#description" rel="noopener noreferrer"&gt;llvm-nm: symbol types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this stage, &lt;code&gt;U&lt;/code&gt; is not a failure. &lt;strong&gt;The object file records the intention to call something defined elsewhere.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;0000000000000000&lt;/code&gt; to the left of &lt;code&gt;_call_answer&lt;/code&gt; does not mean the function will execute at address zero, either. In this &lt;code&gt;.o&lt;/code&gt;, it is at the start of the code section. Its placement in the final executable is still to be determined.&lt;/p&gt;

&lt;p&gt;So the name is still there. What about the machine code?&lt;/p&gt;

&lt;h2&gt;
  
  
  There is a call instruction. But where does it go?
&lt;/h2&gt;

&lt;p&gt;Let's disassemble the code with &lt;code&gt;otool&lt;/code&gt;. Disassembly turns machine code bytes back into a human-readable instruction listing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;otool &lt;span class="nt"&gt;-tvV&lt;/span&gt; caller.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caller.o:
(__TEXT,__text) section
_call_answer:
0000000000000000 stp x29, x30, [sp, #-0x10]!
0000000000000004 mov x29, sp
0000000000000008 bl  0x8
000000000000000c ldp x29, x30, [sp], #0x10
0000000000000010 ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instruction to focus on is &lt;code&gt;bl&lt;/code&gt; at &lt;code&gt;0000000000000008&lt;/code&gt;. ARM64 uses this instruction for function calls, and it also preserves information needed to return. Around it, we can see register saves and restores associated with the call. &lt;a href="https://developer.arm.com/documentation/ddi0602/2021-12/Base-Instructions/BL--Branch-with-Link-" rel="noopener noreferrer"&gt;Arm: BL — Branch with Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;0000000000000008&lt;/code&gt; on the left is the address where the &lt;code&gt;bl&lt;/code&gt; instruction sits. The &lt;code&gt;bl 0x8&lt;/code&gt; on the right shows its branch destination as &lt;code&gt;0x8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wait. The instruction is at &lt;code&gt;0x8&lt;/code&gt;, and its destination is also &lt;code&gt;0x8&lt;/code&gt;? Read on its own, this looks like an instruction that branches to itself.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;bl&lt;/code&gt; instruction encodes the distance from the instruction to its destination. That distance is called a relative displacement. In this output before linking, it is still a placeholder value of zero. So &lt;code&gt;0x8 + 0 = 0x8&lt;/code&gt;, and the disassembler shows a branch to the same location. During linking, the linker writes the correct distance to &lt;code&gt;answer&lt;/code&gt; into the instruction.&lt;/p&gt;

&lt;p&gt;What we actually want to call is &lt;code&gt;answer&lt;/code&gt;. To see that intention, we need to read the instruction together with the relocation information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relocation information says: adjust this reference to answer
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;otool -rv&lt;/code&gt; to display the relocations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;otool &lt;span class="nt"&gt;-rv&lt;/span&gt; caller.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caller.o:
Relocation information (__TEXT,__text) 1 entries
address  pcrel length extern type    scattered symbolnum/value
00000008 True  long   True   BR26    False     _answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's match this row to the instruction we just saw:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning in this example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;address: 00000008&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apply the relocation 8 bytes from the start of the &lt;code&gt;__text&lt;/code&gt; section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pcrel: True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Treat the reference as relative to the instruction's position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type: BR26&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A relocation that adjusts the displacement of an ARM64 branch instruction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_answer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The symbol used to determine the target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;address&lt;/code&gt; does not mean byte 8 from the start of the entire file. It is an offset within the code section. The file also contains headers and other information, so section offsets and file offsets must be read separately.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BR26&lt;/code&gt; corresponds to Apple's &lt;code&gt;ARM64_RELOC_BRANCH26&lt;/code&gt;, which handles the 26-bit displacement in &lt;code&gt;B&lt;/code&gt; and &lt;code&gt;BL&lt;/code&gt; instructions. Different instruction types need different adjustments, so a relocation needs both the referenced symbol and a relocation type. &lt;a href="https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/arm64/reloc.h" rel="noopener noreferrer"&gt;Apple: arm64 relocation types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;symbol table&lt;/strong&gt; tells us which names exist. The &lt;strong&gt;relocation information&lt;/strong&gt; tells us where to adjust references to those names.&lt;/p&gt;

&lt;p&gt;That lets us produce the call instruction and the instructions for completing it later, even before we know where &lt;code&gt;answer&lt;/code&gt; will be placed.&lt;/p&gt;

&lt;p&gt;The word “relocation” might sound as though we are simply moving a function somewhere else. What we are examining here is information for adjusting a reference encoded in an instruction to match the final placement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the body of answer in another .o
&lt;/h2&gt;

&lt;p&gt;Now let's write the callee. Save this as &lt;code&gt;provider.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#![no_std]&lt;/span&gt;

&lt;span class="nd"&gt;#[unsafe(no_mangle)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile this into a separate object file as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc provider.rs &lt;span class="nt"&gt;--crate-type&lt;/span&gt; lib &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;obj &lt;span class="nt"&gt;-o&lt;/span&gt; provider.o

nm &lt;span class="nt"&gt;-g&lt;/span&gt; provider.o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000000000 T _answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, &lt;code&gt;_answer&lt;/code&gt; has a &lt;code&gt;T&lt;/code&gt;. The definition that &lt;code&gt;caller.o&lt;/code&gt; needs now exists in &lt;code&gt;provider.o&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But this address is also zero. &lt;code&gt;call_answer&lt;/code&gt; is at zero, and &lt;code&gt;answer&lt;/code&gt; is at zero. Are they in the same place?&lt;/p&gt;

&lt;p&gt;No: they are at the starts of their own sections in separate object files. During linking, they are placed in the output, and their references are connected using that placement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f7lscg3kr7pgc9ty4k0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f7lscg3kr7pgc9ty4k0.png" alt="The linker matches caller.o's undefined reference and relocation at offset 8 to the definition of answer in provider.o, then adjusts the call instruction using the output layout" width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: A conceptual view of resolving the reference to answer. Other linker inputs, such as main and the standard library, are omitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Matching a name's reference to its definition is &lt;strong&gt;symbol resolution&lt;/strong&gt;. Adjusting the necessary references according to the layout is the relocation processing we are looking at here.&lt;/p&gt;

&lt;p&gt;Putting the files next to each other does not connect them. We need to establish which code calls which destination, and where that destination is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave out the implementation, and linking fails
&lt;/h2&gt;

&lt;p&gt;Let's add an entry point so we can run the program. Save this as &lt;code&gt;main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;call_answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;call_answer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;main&lt;/code&gt; uses &lt;code&gt;std&lt;/code&gt;. First, deliberately add only &lt;code&gt;caller.o&lt;/code&gt; to the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc main.rs &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; link-arg&lt;span class="o"&gt;=&lt;/span&gt;caller.o &lt;span class="nt"&gt;-o&lt;/span&gt; demo-missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, linking failed. Here is the actual error, with only the long linker command and the note about its arguments omitted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: linking with `cc` failed: exit status: 1
  |
  … (linker command and argument note omitted)
  = note: Undefined symbols for architecture arm64:
            "_answer", referenced from:
                _call_answer in caller.o
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

error: aborting due to 1 previous error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Undefined symbols&lt;/code&gt; means that a symbol's definition could not be found. The missing name is &lt;code&gt;_answer&lt;/code&gt;, and the error identifies &lt;code&gt;_call_answer&lt;/code&gt; in &lt;code&gt;caller.o&lt;/code&gt; as the code referencing it.&lt;/p&gt;

&lt;p&gt;We have &lt;code&gt;call_answer&lt;/code&gt;, but cannot find the &lt;code&gt;answer&lt;/code&gt; it calls. A reference that could remain unresolved when producing the &lt;code&gt;.o&lt;/code&gt; cannot be resolved when building this executable, so linking fails.&lt;/p&gt;

&lt;p&gt;Now let's also provide &lt;code&gt;provider.o&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc main.rs &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abort &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; link-arg&lt;span class="o"&gt;=&lt;/span&gt;caller.o &lt;span class="nt"&gt;-C&lt;/span&gt; link-arg&lt;span class="o"&gt;=&lt;/span&gt;provider.o &lt;span class="nt"&gt;-o&lt;/span&gt; demo

./demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-C link-arg&lt;/code&gt; adds an argument to the linker invocation. Here, we use it to add the two &lt;code&gt;.o&lt;/code&gt; files. rustc also supplies inputs needed for &lt;code&gt;main&lt;/code&gt;, the standard library, and so on. These two files are not the executable's only ingredients. &lt;a href="https://doc.rust-lang.org/rustc/codegen-options/#link-arg" rel="noopener noreferrer"&gt;Codegen Options: link-arg&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The instruction really changed after linking
&lt;/h2&gt;

&lt;p&gt;Finally, let's compare the machine code before and after linking. I used the LLVM disassembler available on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun llvm-objdump &lt;span class="nt"&gt;--disassemble&lt;/span&gt; caller.o
xcrun llvm-objdump &lt;span class="nt"&gt;--disassemble&lt;/span&gt; demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below are just the lines containing the call to &lt;code&gt;answer&lt;/code&gt;. From left to right, they show the address, the machine instruction's value, and its human-readable form. Tabs and column spacing have been normalized.&lt;/p&gt;

&lt;p&gt;Before linking, in &lt;code&gt;caller.o&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8: 94000000  bl 0x8 &amp;lt;ltmp0+0x8&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After linking, in &lt;code&gt;demo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100030a0c: 94000003  bl 0x100030a18 &amp;lt;_answer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;94000000&lt;/code&gt; became &lt;code&gt;94000003&lt;/code&gt;. This is more than a function name being added to the display: &lt;strong&gt;the value encoded in the call instruction itself changed&lt;/strong&gt;. &lt;a href="https://llvm.org/docs/CommandGuide/llvm-objdump.html" rel="noopener noreferrer"&gt;llvm-objdump&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my output, &lt;code&gt;call_answer&lt;/code&gt; was placed at &lt;code&gt;0x100030a04&lt;/code&gt;, and &lt;code&gt;answer&lt;/code&gt; at &lt;code&gt;0x100030a18&lt;/code&gt;. Given that layout, the call instruction at &lt;code&gt;0x100030a0c&lt;/code&gt; now branches to &lt;code&gt;answer&lt;/code&gt;. We can see the relocation applied to the actual machine code.&lt;/p&gt;

&lt;p&gt;The “3” encoded in the instruction does not mean 3 bytes. The 26-bit displacement in &lt;code&gt;bl&lt;/code&gt; (imm26) is a signed value in units of 4 bytes. Here, it means &lt;code&gt;3 × 4 = 12&lt;/code&gt; bytes ahead: &lt;code&gt;0x100030a0c + 12 = 0x100030a18&lt;/code&gt;, the address of &lt;code&gt;answer&lt;/code&gt;. &lt;a href="https://documentation-service.arm.com/static/68da52dfbd7cab51328c0622" rel="noopener noreferrer"&gt;Arm instruction specification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The addresses shown here are virtual addresses recorded in the executable. They are distinct both from byte offsets within the file and from addresses necessarily used unchanged on every run. ASLR can shift the layout at load time, and references to shared libraries also involve runtime mechanisms. Here, we have focused on a call between two functions included in the same executable. &lt;a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/executing_files.html" rel="noopener noreferrer"&gt;Executing Mach-O Files — Apple&lt;/a&gt;, &lt;a href="https://support.apple.com/guide/security/glossary-sec93292bfa6/web" rel="noopener noreferrer"&gt;Apple Platform Security: ASLR&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we still need a linker when the machine code exists?
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;caller.o&lt;/code&gt; contained the machine code for &lt;code&gt;call_answer&lt;/code&gt;. But &lt;code&gt;answer&lt;/code&gt; remained an undefined symbol, accompanied by relocation information for adjusting the call instruction to reference it.&lt;/p&gt;

&lt;p&gt;The compiler producing &lt;code&gt;caller.o&lt;/code&gt; can generate an instruction saying “call &lt;code&gt;answer&lt;/code&gt; here.” At that point, however, we have not supplied &lt;code&gt;provider.o&lt;/code&gt;. We do not know where the body of &lt;code&gt;answer&lt;/code&gt; will sit in the final executable, so we cannot determine the distance to it yet.&lt;/p&gt;

&lt;p&gt;Instead, we leave behind the target's name and the location of the instruction that needs adjustment. Once the necessary inputs are available, the linker does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Match references to definitions.&lt;/strong&gt; It connects the &lt;code&gt;answer&lt;/code&gt; requested by &lt;code&gt;caller.o&lt;/code&gt; to the definition in &lt;code&gt;provider.o&lt;/code&gt;. When we left out the implementation, resolution failed and produced an error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determine the layout of code and data.&lt;/strong&gt; It places sections and other contents from separate &lt;code&gt;.o&lt;/code&gt; files into the output executable. Positions local to each object file become part of the final layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust references to match that layout.&lt;/strong&gt; It reads the relocation information and, in our example, writes the displacement to &lt;code&gt;answer&lt;/code&gt; into the &lt;code&gt;bl&lt;/code&gt; instruction. This is why &lt;code&gt;94000000&lt;/code&gt; became &lt;code&gt;94000003&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simply concatenating the files would leave the zero in the call instruction unchanged. Even if we included the function's body, the call would not be connected unless we adjusted the reference to reach it. Producing machine code and combining that code into a working program are separate jobs. &lt;a href="https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/building_files.html" rel="noopener noreferrer"&gt;Building Mach-O Files — Apple&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then why not compile everything together from the start? With a small example, that is tempting. But real programs use the standard library and other libraries alongside our own code. Compiling pieces separately and combining them later allows existing machine code to be reused. There is a qualification in Rust: generic functions, for example, may be monomorphized for concrete types and have machine code generated in the consuming crate. Not everything can be supplied solely as precompiled machine code. When producing the final executable, references between the generated pieces still need to be resolved. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/monomorph.html" rel="noopener noreferrer"&gt;Monomorphization&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;extern "C"&lt;/code&gt; in this experiment made that boundary easy to observe. Even without this syntax in everyday Rust, separately generated code still has to be connected. rustc can divide a single crate into multiple Codegen Units; with the LLVM backend, the object files produced from those units are linked. This normally happens as part of a build through rustc or Cargo, so we rarely need to operate the linker directly. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;Code generation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was still work left between “the machine code exists” and “we have something executable.” The undefined symbol and relocation information we saw were how that work was handed to the next stage.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>What Code Does Rust Pass to LLVM? Generics and Codegen Units</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Sat, 03 Oct 2026 05:05:26 +0000</pubDate>
      <link>https://dev.to/masamori/what-code-does-rust-pass-to-llvm-generics-and-codegen-units-ll6</link>
      <guid>https://dev.to/masamori/what-code-does-rust-pass-to-llvm-generics-and-codegen-units-ll6</guid>
      <description>&lt;p&gt;In the previous article, I generated MIR from Rust code and tried reading it. An &lt;code&gt;if&lt;/code&gt; expression became a set of blocks, and we could see where values went and which block execution would enter next.&lt;/p&gt;

&lt;p&gt;But being able to read MIR doesn't mean we've reached machine code yet. LLVM apparently comes into the picture further along. What does rustc actually pass to it, and how?&lt;/p&gt;

&lt;p&gt;For example, if we call one generic function with both &lt;code&gt;u32&lt;/code&gt; and &lt;code&gt;f64&lt;/code&gt;, does LLVM receive one function? Or two? Even if both use &lt;code&gt;x + x&lt;/code&gt;, integer addition and floating-point addition involve different operations, don't they?&lt;/p&gt;

&lt;p&gt;As a follow-up to &lt;a href="https://dev.to/masamori/reading-rusts-mir-following-control-flow-and-values-36jm"&gt;Reading Rust's MIR&lt;/a&gt;, this article explores &lt;strong&gt;which code rustc passes to LLVM, and how it groups that code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key ideas are monomorphization of generics and partitioning into Codegen Units (CGUs). We'll start with collecting the code we need, then look at dividing it into units, and finally at converting it to LLVM IR. I started out investigating linkers, and somehow I still haven't reached one.&lt;/p&gt;

&lt;p&gt;This article is based on the Rust Compiler Development Guide as consulted on October 1, 2026. I rechecked the examples with Rust 1.96.0, the same version used in the previous article, and used that version's implementation when looking at CGU partitioning. Compiler internals aren't fixed by the Rust language specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting a feel for the path after MIR
&lt;/h2&gt;

&lt;p&gt;rustc normally uses LLVM as its code-generation backend. On the Rust side, rustc handles syntax, types, borrowing, and related processing, then lowers the program to an intermediate representation called LLVM IR. LLVM receives that representation and performs optimization and machine-code generation for the target environment. Here, we'll follow the path that uses the LLVM backend. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;Code generation — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once MIR is ready, the part of the process we're interested in looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5jhf6o2ldtpjic5cesa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5jhf6o2ldtpjic5cesa.png" alt="MIR passes through collection of code-generation items, CGU partitioning, and lowering to LLVM IR before LLVM generates object files." width="795" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: A conceptual view from MIR to object files. Details such as optimization across units through LTO are omitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's pause here. MIR, LLVM IR, CGU. With these names lined up, they all start to look like similar intermediate representations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it represents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MIR&lt;/td&gt;
&lt;td&gt;A representation of Rust operations for analysis and transformation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;A representation of code that rustc passes to LLVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CGU&lt;/td&gt;
&lt;td&gt;A grouping that determines which code undergoes code generation together&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;MIR and LLVM IR are formats for representing code; a CGU is a unit that groups code-generation items.&lt;/strong&gt; One concerns how code is represented, and the other concerns which code is processed together. They appear in sequence in the diagram, but they're describing different things.&lt;/p&gt;

&lt;p&gt;rustc first collects the functions and other items it needs, then decides which CGU to put them in. It lowers MIR to LLVM IR for each grouping and passes the result to LLVM. Let's look at what the “collection” and “partitioning” in the middle of the diagram are for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collecting the concrete types that are needed
&lt;/h2&gt;

&lt;p&gt;First comes collection: identifying the items needed for code generation.&lt;/p&gt;

&lt;p&gt;In a generic function, a type parameter such as &lt;code&gt;T&lt;/code&gt; doesn't simply become a machine-code type as written in the source. Rust generates code for the combinations of concrete types it needs. This is &lt;strong&gt;monomorphization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, this &lt;code&gt;twice&lt;/code&gt; function adds a value to itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;twice&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Copy&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;twice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3_u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;twice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5_f64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Add&amp;lt;Output = T&amp;gt;&lt;/code&gt; requires the addition's result to have type &lt;code&gt;T&lt;/code&gt; as well. &lt;code&gt;Copy&lt;/code&gt; lets us use the same &lt;code&gt;x&lt;/code&gt; on both sides of the addition. This example uses the function in two ways: with &lt;code&gt;T = u32&lt;/code&gt; and with &lt;code&gt;T = f64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, I'll call a function with concrete type arguments, such as &lt;code&gt;twice::&amp;lt;u32&amp;gt;&lt;/code&gt;, an “instance.” We're talking about a function to generate code for, rather than a value or object stored in a variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfe65k0884kjaub9ih81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfe65k0884kjaub9ih81.png" alt="One definition of twice is called with u32 and f64. The two instances are collected and, after placement in CGUs, lowered to LLVM IR." width="800" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: A conceptual view focused on twice. Other code-generation items, such as main and the addition implementations, are omitted. The two columns do not imply that the instances go into separate CGUs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It helps to distinguish &lt;strong&gt;collecting the required items&lt;/strong&gt; from &lt;strong&gt;generating code for their concrete types&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The monomorphization collector first identifies the instances needed for code generation, such as &lt;code&gt;twice::&amp;lt;u32&amp;gt;&lt;/code&gt; and &lt;code&gt;twice::&amp;lt;f64&amp;gt;&lt;/code&gt;. This includes ordinary functions such as &lt;code&gt;main&lt;/code&gt; and statics, too. The items are then placed in CGUs, and their type parameters are treated as concrete types during lowering from MIR to LLVM IR. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/monomorph.html" rel="noopener noreferrer"&gt;Monomorphization&lt;/a&gt;, &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/lowering-mir.html" rel="noopener noreferrer"&gt;Lowering MIR to a Codegen IR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I emitted LLVM IR for this code locally, I found two definitions of &lt;code&gt;twice&lt;/code&gt;: one taking &lt;code&gt;i32&lt;/code&gt; and another taking &lt;code&gt;double&lt;/code&gt;. The commands and environment are listed later, under “Generating LLVM IR locally.”&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use in Rust&lt;/th&gt;
&lt;th&gt;Argument and return types in this LLVM IR output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;twice::&amp;lt;u32&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both &lt;code&gt;i32&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;twice::&amp;lt;f64&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both &lt;code&gt;double&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Wait, &lt;code&gt;i32&lt;/code&gt; for a Rust &lt;code&gt;u32&lt;/code&gt;? In LLVM IR, &lt;code&gt;i32&lt;/code&gt; means a 32-bit integer type. It doesn't carry the same “signed” meaning as Rust's &lt;code&gt;i32&lt;/code&gt;. Signedness is distinguished by the operations or their specifications, such as the kind of comparison being performed. Similar-looking names don't always mean the same thing. &lt;a href="https://llvm.org/docs/LangRef.html#integer-type" rel="noopener noreferrer"&gt;LLVM Language Reference: Integer Type&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Counting “one function definition” in source code is therefore different from counting “how many concrete function instances are needed” during code generation. This is where writing one function with &lt;code&gt;T&lt;/code&gt; connects to performing operations on particular types.&lt;/p&gt;

&lt;p&gt;That doesn't mean two functions always survive in the final binary. This example doesn't use the results, so optimization may remove the calls altogether. With inlining and other transformations in the picture, we need to distinguish the collected items from the machine code that eventually remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  CGUs group the code passed to LLVM
&lt;/h2&gt;

&lt;p&gt;Once the required code-generation items have been collected, rustc partitions them into &lt;strong&gt;Codegen Units&lt;/strong&gt; (CGUs). With the LLVM backend, each CGU gets an LLVM module: a grouping of LLVM IR. An LLVM module doesn't correspond one-to-one with a Rust &lt;code&gt;mod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a conceptual example with two CGUs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz8e4sto4uxws39kuclwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz8e4sto4uxws39kuclwi.png" alt="Collected items are placed in CGU A and CGU B. Each becomes an LLVM module and then an object file, which proceeds to linking." width="800" height="839"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: A conceptual view of processing two CGUs. LLVM's work in the two columns can proceed in parallel. LTO and duplication of code for inlining are omitted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dividing the work LLVM can handle allows compilation to use multiple cores. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;Code generation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another consideration is incremental compilation. It reuses results from an earlier compilation where changes haven't affected them. For code-generation artifacts, rustc decides whether reuse is possible at the CGU level. If it can reuse a result, it can skip repeating code generation and optimization for that part. Ideally, we wouldn't regenerate every bit of code on every build. That also influences how CGUs are partitioned. &lt;a href="https://github.com/rust-lang/rust/blob/1.96.0/compiler/rustc_monomorphize/src/partitioning.rs" rel="noopener noreferrer"&gt;CGU partitioning implementation and design notes, Rust 1.96.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So should we just split everything into smaller and smaller units?&lt;/p&gt;

&lt;p&gt;There are tradeoffs. When LLVM optimizes within one module, it can consider the functions and other items inside that module together. Partitioning affects both parallelism and the granularity of reuse, as well as opportunities for optimization across functions.&lt;/p&gt;

&lt;p&gt;For example, inlining puts the body of a called function into its caller, which requires that body to be available. As we divide the work, we also have to consider how much code can be optimized together. A question about compilation speed has turned into a question about the speed of the generated program.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CGU partitioning&lt;/th&gt;
&lt;th&gt;Potential benefit during compilation&lt;/th&gt;
&lt;th&gt;What to watch for in the generated code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;More CGUs&lt;/td&gt;
&lt;td&gt;May allow more parallel LLVM work and finer-grained reuse&lt;/td&gt;
&lt;td&gt;Partitioning can affect optimization opportunities and may reduce runtime performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fewer CGUs&lt;/td&gt;
&lt;td&gt;Allows optimization over a wider scope within one module&lt;/td&gt;
&lt;td&gt;Compilation may be slower, and better runtime performance isn't guaranteed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;rustc's &lt;code&gt;-C codegen-units=N&lt;/code&gt; sets an upper limit on the number of CGUs. The official documentation likewise explains that increasing the number may speed up compilation while producing slower code. &lt;a href="https://doc.rust-lang.org/rustc/codegen-options/#codegen-units" rel="noopener noreferrer"&gt;Codegen Options: codegen-units&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Optimization can also cross CGU boundaries. LTO (Link-Time Optimization) allows optimization across module boundaries. To understand runtime performance, we need to consider optimization levels and LTO settings alongside the CGU count. &lt;a href="https://doc.rust-lang.org/rustc/codegen-options/#lto" rel="noopener noreferrer"&gt;Codegen Options: lto&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CGU boundaries don't simply follow source files
&lt;/h2&gt;

&lt;p&gt;Looking at the diagrams, you might wonder whether rustc creates one CGU for each Rust source file. The relationship isn't that simple.&lt;/p&gt;

&lt;p&gt;During incremental compilation, Rust 1.96.0's partitioning process separates non-generic code associated with a module from instances of generic functions. The generic instances can change as the combinations of types used change, even when the function bodies themselves haven't been edited.&lt;/p&gt;

&lt;p&gt;For example, we could leave the definition of &lt;code&gt;twice&lt;/code&gt; alone but add a use of &lt;code&gt;twice::&amp;lt;u64&amp;gt;&lt;/code&gt; elsewhere. That would require an instance for the new type. Separating these changes from a module's non-generic code helps preserve parts that can be reused.&lt;/p&gt;

&lt;p&gt;This is only part of the partitioning policy, though. The implementation also merges units to stay within the CGU count limit. We can't conclude that every module always produces exactly two final CGUs. &lt;a href="https://github.com/rust-lang/rust/blob/1.96.0/compiler/rustc_monomorphize/src/partitioning.rs" rel="noopener noreferrer"&gt;CGU partitioning implementation, Rust 1.96.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CGUs are groupings chosen with code generation and reuse in mind, so their organization can differ from how we organize the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets translated from MIR to LLVM IR?
&lt;/h2&gt;

&lt;p&gt;Finally, let's look at the step toward LLVM IR in Figure 1.&lt;/p&gt;

&lt;p&gt;The developer guide introduces &lt;code&gt;rustc_codegen_ssa::mir::codegen_mir&lt;/code&gt; as the entry point for lowering MIR. The processing of MIR's elements is divided into modules such as these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;What it handles&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Basic blocks and terminators, including branches and function calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;statement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Statements such as assignments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;operand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Values used in computations and calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;place&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;References to locations where values are stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rvalue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computations and other expressions producing a value to assign&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the previous article, we used &lt;code&gt;choose&lt;/code&gt; to look at branches and their merge in MIR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this function, LLVM IR needs to express which block execution enters depending on the condition, what goes into the return value, and where the function returns.&lt;/p&gt;

&lt;p&gt;Let's emit &lt;code&gt;choose.ll&lt;/code&gt; by adding &lt;code&gt;--emit=llvm-ir&lt;/code&gt; to the same example. Omitting the function header with its name and arguments, the braces enclosing the function, and the comments beside the block labels, the body looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="nl"&gt;start:&lt;/span&gt;
  &lt;span class="nv"&gt;%_0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;alloca&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;x&lt;/span&gt; &lt;span class="kt"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;i1&lt;/span&gt; &lt;span class="nv"&gt;%flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%bb1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%bb2&lt;/span&gt;

&lt;span class="nl"&gt;bb2:&lt;/span&gt;
  &lt;span class="k"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="nv"&gt;%_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%bb3&lt;/span&gt;

&lt;span class="nl"&gt;bb1:&lt;/span&gt;
  &lt;span class="k"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="nv"&gt;%_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%bb3&lt;/span&gt;

&lt;span class="nl"&gt;bb3:&lt;/span&gt;
  &lt;span class="nv"&gt;%0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ptr&lt;/span&gt; &lt;span class="nv"&gt;%_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are more symbols now, but the branch from the previous article is still recognizable. Let's start with &lt;code&gt;br&lt;/code&gt;, &lt;code&gt;store&lt;/code&gt;, and &lt;code&gt;ret&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;LLVM IR line&lt;/th&gt;
&lt;th&gt;What it does in this example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;br i1 %flag, label %bb1, label %bb2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enters &lt;code&gt;bb1&lt;/code&gt; if &lt;code&gt;%flag&lt;/code&gt; is true, or &lt;code&gt;bb2&lt;/code&gt; if false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;store i32 %x, ptr %_0, align 4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes &lt;code&gt;%x&lt;/code&gt; to the memory pointed to by &lt;code&gt;%_0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;br label %bb3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enters &lt;code&gt;bb3&lt;/code&gt; unconditionally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%0 = load i32, ptr %_0, align 4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads a value from that memory and names it &lt;code&gt;%0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ret i32 %0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the value that was read&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;alloca&lt;/code&gt; allocates space for the return value. The two branches write to that space, and &lt;code&gt;bb3&lt;/code&gt;, where the paths meet, reads it and returns. The output lists &lt;code&gt;bb2&lt;/code&gt; first, but the destinations of the &lt;code&gt;br&lt;/code&gt; instructions determine execution order. We don't execute every block from top to bottom. &lt;a href="https://llvm.org/docs/LangRef.html#br-instruction" rel="noopener noreferrer"&gt;LLVM Language Reference&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;%_0&lt;/code&gt; here is a value pointing to memory, while &lt;code&gt;%0&lt;/code&gt; is the integer read from it. They look almost identical, but that underscore makes them different names. Including the &lt;code&gt;_0&lt;/code&gt; we saw in MIR, it seems safer to follow which instruction creates a value and where it's used, rather than assigning a role based only on its name.&lt;/p&gt;

&lt;p&gt;MIR elements don't always correspond one-to-one with LLVM IR elements, either. Function calls may need to handle unwinding after a panic, depending on the settings and the called function. Lowering calls or &lt;code&gt;assert&lt;/code&gt; operations can produce multiple LLVM basic blocks from a single MIR basic block.&lt;/p&gt;

&lt;p&gt;We saw memory writes and reads in this example, but those aren't guaranteed to remain. Before lowering, rustc also analyzes whether values can be represented directly in SSA form. SSA gives each value name a single definition. Where that form is suitable, rustc can avoid some of the work of building a memory-based representation first and asking LLVM to simplify it afterward.&lt;/p&gt;

&lt;p&gt;Lowering from MIR to LLVM IR therefore involves preserving the meaning of Rust operations while building a form LLVM can work with. It takes more than mechanically replacing the notation. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/lowering-mir.html" rel="noopener noreferrer"&gt;Lowering MIR to a Codegen IR — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating LLVM IR locally
&lt;/h2&gt;

&lt;p&gt;Save the &lt;code&gt;twice&lt;/code&gt; example as &lt;code&gt;twice.rs&lt;/code&gt; and the &lt;code&gt;choose&lt;/code&gt; example as &lt;code&gt;choose.rs&lt;/code&gt;. These commands produce the output used here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc twice.rs &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mir,llvm-ir

rustc choose.rs &lt;span class="nt"&gt;--crate-type&lt;/span&gt; lib &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mir,llvm-ir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each command produces a &lt;code&gt;.mir&lt;/code&gt; file and a &lt;code&gt;.ll&lt;/code&gt; file. &lt;code&gt;choose&lt;/code&gt; has no &lt;code&gt;main&lt;/code&gt;, so it uses &lt;code&gt;--crate-type lib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The environment was &lt;code&gt;rustc 1.96.0 (ac68faa20 2026-05-25)&lt;/code&gt;, targeting &lt;code&gt;aarch64-apple-darwin&lt;/code&gt;, with LLVM 22.1.2. With optimization kept low under these conditions, we could follow the branches and memory operations. Changing the optimization settings or target environment may change the output's form. I haven't compared compilation time or runtime performance here.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLVM produces object files, and the process continues
&lt;/h2&gt;

&lt;p&gt;Once the LLVM IR for each CGU is ready, LLVM proceeds with optimization and machine-code generation, producing object files. The two columns in Figure 3 show how this work can proceed in separate units.&lt;/p&gt;

&lt;p&gt;After that, the necessary objects and other inputs are linked to produce the output artifact. The kind of LTO in use also affects when optimization happens, but I'll leave the details of linking for another topic. &lt;a href="https://rustc-dev-guide.rust-lang.org/backend/codegen.html" rel="noopener noreferrer"&gt;Code generation — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Returning to the original question, “Which code is passed to LLVM, and in what units?”, the connection is becoming clearer: rustc collects the necessary function instances and other items, places them in CGUs, and lowers MIR to LLVM IR for each unit.&lt;/p&gt;

&lt;p&gt;Following one &lt;code&gt;if&lt;/code&gt; led from handling values to dividing work and deciding how much of a previous compilation could be reused. That division can even affect how fast the generated program runs. There's more going on inside a compiler than carrying out computations. Every topic seems to lead to another thing I want to investigate.&lt;/p&gt;

&lt;p&gt;Hmm. Have I finally made it as far as the linker's doorstep...?&lt;/p&gt;

&lt;p&gt;For the basics of reading MIR blocks and locals, see &lt;a href="https://dev.to/masamori/reading-rusts-mir-following-control-flow-and-values-36jm"&gt;Reading Rust's MIR: Following Control Flow and Values&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Reading Rust's MIR: Following Control Flow and Values</title>
      <dc:creator>Masahiro Morino</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:43:23 +0000</pubDate>
      <link>https://dev.to/masamori/reading-rusts-mir-following-control-flow-and-values-36jm</link>
      <guid>https://dev.to/masamori/reading-rusts-mir-following-control-flow-and-values-36jm</guid>
      <description>&lt;p&gt;I started looking into linkers, but a linker's job isn't specific to Rust. Before getting there, I thought it would be more interesting to see how rustc takes our Rust code toward code generation.&lt;/p&gt;

&lt;p&gt;One of the representations we meet along the way is MIR. Apparently, it's used for both borrow checking and code generation. But what exactly is an “intermediate representation,” anyway?&lt;/p&gt;

&lt;p&gt;In this article, I'll use a few small examples to explore &lt;strong&gt;how to read Rust operations in the compiler's MIR&lt;/strong&gt;. We'll start with an &lt;code&gt;if&lt;/code&gt; expression to get a feel for control flow, look closely at a single assignment to see how values and places are represented, and then move on to a call to &lt;code&gt;Vec::push&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is based on the Rust Compiler Development Guide as consulted on September 29, 2026. The examples were checked with Rust 1.96.0. MIR's structure and textual output can change with the compiler version and optimization settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is MIR for?
&lt;/h2&gt;

&lt;p&gt;MIR stands for &lt;strong&gt;Mid-level Intermediate Representation&lt;/strong&gt;. An intermediate representation is a way for a compiler to represent code while processing a program.&lt;/p&gt;

&lt;p&gt;The Rust we write has constructs such as &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;match&lt;/code&gt;, and nested expressions that make programs convenient to write and read. The compiler needs to answer questions like “Where is this value created, and where is it used?” or “After taking this branch, what happens next?” MIR organizes the program into a form that makes types, operations on values, and control flow explicit. That gives the compiler something it can analyze. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html" rel="noopener noreferrer"&gt;The MIR — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the expression &lt;code&gt;if flag { x } else { 0 }&lt;/code&gt;, which we'll look at shortly, becomes a condition check, operations that set the return value, and a block where the paths meet. Complex expressions are broken down into smaller computations and temporary values. A more uniform representation lets the compiler follow values and control flow without having to handle every variation of the source syntax separately. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/construction.html" rel="noopener noreferrer"&gt;MIR construction — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One major use of MIR is &lt;strong&gt;borrow checking&lt;/strong&gt;. Remember borrowing? The compiler examines how values are used and borrowed on MIR, and also uses MIR for subsequent optimization and code generation. That doesn't mean one unchanged MIR body survives all the way through compilation. It passes through analysis and transformation stages on its way to the form used for code generation. &lt;a href="https://rustc-dev-guide.rust-lang.org/overview.html#mir-lowering" rel="noopener noreferrer"&gt;Overview of the compiler&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's pause and put these representations side by side.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Representation&lt;/th&gt;
&lt;th&gt;Its role in the path we're looking at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rust source code&lt;/td&gt;
&lt;td&gt;Where we write the program using expressions and other language constructs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIR&lt;/td&gt;
&lt;td&gt;Makes Rust types, operations on values, and branches explicit for borrow checking, optimization, and code generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;Carries the program from rustc into LLVM for further optimization and machine-code generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are other compiler representations before MIR, but I'm leaving those out here. I'd like to explore that part of the compiler sometime, too. For now, let's focus on &lt;strong&gt;MIR as a representation for analyzing Rust programs&lt;/strong&gt; and look inside it. &lt;a href="https://rustc-dev-guide.rust-lang.org/overview.html" rel="noopener noreferrer"&gt;Overview of the compiler&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In MIR, control flow becomes blocks
&lt;/h2&gt;

&lt;p&gt;One characteristic of MIR is that control flow takes the form of a &lt;strong&gt;control-flow graph&lt;/strong&gt;, or CFG. Operations are grouped into basic blocks, connected by branches and jumps. Each block contains statements, such as assignments, and ends with a terminator, such as a branch, a function call, or a return. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html" rel="noopener noreferrer"&gt;The MIR — Rust Compiler Development Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a small function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;x&lt;/code&gt; when &lt;code&gt;flag&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, and &lt;code&gt;0&lt;/code&gt; when it's &lt;code&gt;false&lt;/code&gt;. Compiling this function to MIR with Rust 1.96.0 gives us four basic blocks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ups83w7ok35knjqd6ma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ups83w7ok35knjqd6ma.png" alt="bb0 checks flag. The true branch enters bb1 and sets the return value to x; the false branch enters bb2 and sets it to 0. Both paths join at bb3, which returns." width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The block structure of the generated MIR. The diagram uses flag, x, and return value in place of the internal local names.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;bb1&lt;/code&gt;, setting the return value to &lt;code&gt;x&lt;/code&gt; is a statement, and jumping to &lt;code&gt;bb3&lt;/code&gt; is the terminator. &lt;code&gt;bb0&lt;/code&gt; has no statements at all, just the conditional-branch terminator. So a block doesn't have to do some computation before it branches.&lt;/p&gt;

&lt;p&gt;A basic block is a unit that &lt;strong&gt;executes its statements in order and ends with a terminator&lt;/strong&gt;. A terminator doesn't necessarily choose between multiple destinations. In this example, &lt;code&gt;goto&lt;/code&gt; has one successor, &lt;code&gt;switchInt&lt;/code&gt; selects a successor based on a value, and &lt;code&gt;return&lt;/code&gt; has no successor block within this function. Thinking of a terminator as the block's exit helps explain why these operations belong to the same category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching the MIR output to the diagram
&lt;/h2&gt;

&lt;p&gt;In the output, arguments and the return value are represented by names such as &lt;code&gt;_1&lt;/code&gt;, &lt;code&gt;_2&lt;/code&gt;, and &lt;code&gt;_0&lt;/code&gt;. Here, &lt;code&gt;_1&lt;/code&gt; is &lt;code&gt;flag&lt;/code&gt;, &lt;code&gt;_2&lt;/code&gt; is &lt;code&gt;x&lt;/code&gt;, and &lt;code&gt;_0&lt;/code&gt; holds the return value. If we omit the declarations and keep just the blocks, the correspondence with the diagram becomes clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bb0: {
    switchInt(copy _1) -&amp;gt; [0: bb2, otherwise: bb1];
}

bb1: {
    _0 = copy _2;
    goto -&amp;gt; bb3;
}

bb2: {
    _0 = const 0_u32;
    goto -&amp;gt; bb3;
}

bb3: {
    return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What was one &lt;code&gt;if&lt;/code&gt; expression in the source has become a conditional branch, assignments, and a block where the paths meet. Making control flow explicit gives the compiler a way to analyze each path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading one line of MIR: Places and values
&lt;/h2&gt;

&lt;p&gt;Now that we know how the blocks connect, let's look more closely at the assignment in &lt;code&gt;bb1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_0 = copy _2;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It simply puts &lt;code&gt;x&lt;/code&gt; into the return value, but the pieces have different roles in MIR. The key terms are &lt;strong&gt;Local, Place, Operand, and Rvalue&lt;/strong&gt;. We're starting to collect quite a few terms now.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Local is storage; a Place identifies a location
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Local&lt;/strong&gt; is a storage location for something like a function argument, a local variable, or a temporary value. MIR identifies locals by indices, written as &lt;code&gt;_1&lt;/code&gt;, for example. These are the locals in &lt;code&gt;choose&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Local&lt;/th&gt;
&lt;th&gt;Role in this function&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The special location for the return value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;u32&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The argument &lt;code&gt;flag&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The argument &lt;code&gt;x&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;u32&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Storage here is a compiler-level concept. It doesn't mean that every Local necessarily ends up with its own stack slot in the final machine code. And the same-looking local names can have different roles in different functions, as we'll see below.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Place&lt;/strong&gt; is an expression identifying a location to read from or write to. It can refer to an entire Local, a field within it, or a location reached through a reference. For example, if &lt;code&gt;_1&lt;/code&gt; were a tuple, &lt;code&gt;_1&lt;/code&gt; would identify the whole tuple and &lt;code&gt;_1.0&lt;/code&gt; its first field. Yes, this gets a little confusing.&lt;/p&gt;

&lt;p&gt;A Local is a unit of storage; a Place specifies a particular location. In &lt;code&gt;_0 = copy _2;&lt;/code&gt;, both the destination &lt;code&gt;_0&lt;/code&gt; and the source &lt;code&gt;_2&lt;/code&gt; are Places referring to entire Locals. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html#key-mir-vocabulary" rel="noopener noreferrer"&gt;Key MIR vocabulary&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  An Operand supplies a value; an Rvalue produces the value to assign
&lt;/h3&gt;

&lt;p&gt;Identifying a location doesn't yet say how we'll use the value stored there. That's where an &lt;strong&gt;Operand&lt;/strong&gt; comes in. Three forms we'll encounter are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operand&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;copy _2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy the value from the specified Place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;move _3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Move the value from the specified Place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;const 0_u32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a constant value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An &lt;strong&gt;Rvalue&lt;/strong&gt;, meanwhile, is an expression that produces the value to assign. It might add values, create a reference to a Place, or simply use an Operand's value. The R comes from the &lt;strong&gt;right-hand side&lt;/strong&gt; of an assignment.&lt;/p&gt;

&lt;p&gt;Here's how the pieces fit together. This is an explanatory tree, not literal rustc output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Statement: assignment  _0 = copy _2;
├─ Destination Place: _0
└─ Rvalue: use an Operand's value directly
   └─ Operand: copy _2
      └─ Source Place: _2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The printed &lt;code&gt;copy _2&lt;/code&gt; can be a little puzzling: we're looking at an Rvalue when we consider the whole right-hand side, and an Operand when we focus on its input. &lt;strong&gt;The Rvalue in this example wraps one Operand and produces that Operand's value unchanged.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The assignment &lt;code&gt;_0 = const 0_u32;&lt;/code&gt; in &lt;code&gt;bb2&lt;/code&gt; has the same shape. The input changes from a value stored in a Local to a constant, but we're still assigning an Operand's value to the return-value Place. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html#key-mir-vocabulary" rel="noopener noreferrer"&gt;Assignments, Rvalues, and Operands in MIR&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Vec::push a terminator?
&lt;/h2&gt;

&lt;p&gt;Let's use those terms to read another example, also found in the official guide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiling it to MIR with Rust 1.96.0 produces this block for the first &lt;code&gt;push&lt;/code&gt;. I explicitly used &lt;code&gt;-C panic=unwind&lt;/code&gt; to inspect the unwinding path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bb1: {
    _3 = &amp;amp;mut _1;
    _2 = Vec::&amp;lt;i32&amp;gt;::push(move _3, const 1_i32) -&amp;gt; [return: bb2, unwind: bb5];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;_1&lt;/code&gt; is the Local for &lt;code&gt;vec&lt;/code&gt;. &lt;code&gt;_3&lt;/code&gt; is a temporary Local of type &lt;code&gt;&amp;amp;mut Vec&amp;lt;i32&amp;gt;&lt;/code&gt;, and &lt;code&gt;_2&lt;/code&gt; receives the &lt;code&gt;()&lt;/code&gt; returned by &lt;code&gt;push&lt;/code&gt;. Local indices belong to their individual functions, so these &lt;code&gt;_1&lt;/code&gt; and &lt;code&gt;_2&lt;/code&gt; are different from the ones in &lt;code&gt;choose&lt;/code&gt;. Watch out for that!&lt;/p&gt;

&lt;p&gt;The first line, &lt;code&gt;_3 = &amp;amp;mut _1;&lt;/code&gt;, is an assignment statement. On the left, &lt;code&gt;_3&lt;/code&gt; is the destination Place. On the right, &lt;code&gt;&amp;amp;mut _1&lt;/code&gt; is an Rvalue that creates a mutable reference to the Place &lt;code&gt;_1&lt;/code&gt;. Our earlier Rvalue simply used an Operand; this one creates a reference value from a Place.&lt;/p&gt;

&lt;p&gt;The next line passes the Operands &lt;code&gt;move _3&lt;/code&gt; and &lt;code&gt;const 1_i32&lt;/code&gt; to &lt;code&gt;push&lt;/code&gt;. What's being moved is the mutable reference stored in &lt;code&gt;_3&lt;/code&gt;, not the &lt;code&gt;Vec&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;And that whole call is a terminator. Despite its &lt;code&gt;_2 = ...&lt;/code&gt; appearance, it isn't an assignment statement. Look at the destinations at the end:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;What happens in this example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;return: bb2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If the call returns normally, continue to the block containing the next &lt;code&gt;push&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unwind: bb5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If the call unwinds, enter the cleanup block that drops &lt;code&gt;vec&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;return: bb2&lt;/code&gt; label does not mean that &lt;code&gt;main&lt;/code&gt; finishes. It tells us &lt;strong&gt;where execution resumes in this function after the called function returns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In Rust source, &lt;code&gt;vec.push(1);&lt;/code&gt; is one line. In MIR, it is split into preparing the reference argument and a call that explicitly includes the paths execution can take afterward. Treating a function call as a terminator lets us follow both normal execution and unwinding cleanup through the connections between blocks. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html#key-mir-vocabulary" rel="noopener noreferrer"&gt;The guide's Vec example&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating MIR locally
&lt;/h2&gt;

&lt;p&gt;For the first example, I saved the function in &lt;code&gt;choose.rs&lt;/code&gt; and ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc choose.rs &lt;span class="nt"&gt;--crate-type&lt;/span&gt; lib &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes the MIR to &lt;code&gt;choose.mir&lt;/code&gt;. The environment was &lt;code&gt;rustc 1.96.0 (ac68faa20 2026-05-25)&lt;/code&gt;, targeting &lt;code&gt;aarch64-apple-darwin&lt;/code&gt;, with LLVM 22.1.2. The output shown here comes from those conditions. Different optimization settings, for example, won't necessarily produce the same block structure.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;Vec&lt;/code&gt; example, I saved the code in &lt;code&gt;vec_push.rs&lt;/code&gt; and ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustc vec_push.rs &lt;span class="nt"&gt;--edition&lt;/span&gt; 2024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; opt-level&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;panic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unwind &lt;span class="nt"&gt;--emit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with &lt;code&gt;-C opt-level=0&lt;/code&gt;, the emitted MIR isn't necessarily the state before all transformations. The &lt;code&gt;StorageLive&lt;/code&gt; statements shown in the official guide don't appear in this &lt;code&gt;Vec&lt;/code&gt; output. When comparing dumps, we need to pay attention to both the Rust version and the stage of MIR we're looking at. Setting the MIR optimization level with &lt;code&gt;-Z mir-opt-level=0&lt;/code&gt; requires nightly; this article uses output from stable. &lt;a href="https://rustc-dev-guide.rust-lang.org/mir/index.html#key-mir-vocabulary" rel="noopener noreferrer"&gt;MIR output and optimization&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading control flow and values separately
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;choose&lt;/code&gt;, a single &lt;code&gt;if&lt;/code&gt; expression became a block that checks the condition, two blocks that set the return value, and a block that returns. Separating storage locations such as &lt;code&gt;_0&lt;/code&gt; from units of control flow such as &lt;code&gt;bb0&lt;/code&gt; makes the output easier to follow.&lt;/p&gt;

&lt;p&gt;Inside each block, Local and Place tell us which location we're dealing with; Operand and Rvalue tell us how values are used and produced. In the &lt;code&gt;Vec::push&lt;/code&gt; example, we also saw that a call is itself a terminator, with normal-return and unwinding paths.&lt;/p&gt;

&lt;p&gt;MIR represents Rust operations in a form the compiler can analyze. Follow control flow through the connections between blocks, then distinguish places from values within each line. With those two ideas in mind, it becomes easier to find the original Rust operations in what initially looks like a wall of symbols.&lt;/p&gt;

&lt;p&gt;So, when we move from MIR toward machine code, what happens to generic type arguments? And how is the code divided into units that can be compiled in parallel? In &lt;a href="https://dev.to/masamori/what-code-does-rust-pass-to-llvm-generics-and-codegen-units-ll6"&gt;What Code Does Rust Pass to LLVM? Generics and Codegen Units&lt;/a&gt;, I follow the collection of the required code and its partitioning into Codegen Units.&lt;/p&gt;

&lt;p&gt;There's a lot to wrap my head around. But that's what makes it interesting.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
