<?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>Forem: TheRemyyy</title>
    <description>The latest articles on Forem by TheRemyyy (@theremyyy).</description>
    <link>https://forem.com/theremyyy</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800041%2F44f561e2-4ed2-4257-81dc-446eeca23b36.png</url>
      <title>Forem: TheRemyyy</title>
      <link>https://forem.com/theremyyy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/theremyyy"/>
    <language>en</language>
    <item>
      <title>I Built a Programming Language with an LLVM Backend at 15. Here's How It Actually Works</title>
      <dc:creator>TheRemyyy</dc:creator>
      <pubDate>Sun, 01 Mar 2026 15:19:14 +0000</pubDate>
      <link>https://forem.com/theremyyy/i-built-a-programming-language-with-an-llvm-backend-at-15-heres-how-it-actually-works-len</link>
      <guid>https://forem.com/theremyyy/i-built-a-programming-language-with-an-llvm-backend-at-15-heres-how-it-actually-works-len</guid>
      <description>&lt;p&gt;I wanted something with Java's clean syntax, Rust's memory safety, and C's raw speed. Nothing out there gave me all three so I just built it myself.&lt;/p&gt;

&lt;p&gt;That's Apex. A compiled, statically typed language that compiles to native machine code through LLVM. Not an interpreter, not a transpiler. A real compiler, written in Rust, finished in about a month.&lt;/p&gt;

&lt;p&gt;Here's how the whole thing works and what actually hurt building it.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pipeline
&lt;/h3&gt;

&lt;p&gt;Every compiler is just a chain of transformations. You keep transforming source code until you get something the machine can run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source (.apex) → Lexer → Parser → AST → Type Checker → Borrow Checker → LLVM Codegen → Native Binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage does one thing and hands off to the next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lexer
&lt;/h3&gt;

&lt;p&gt;Reads raw text, turns it into tokens. &lt;code&gt;KEYWORD_IF&lt;/code&gt;, &lt;code&gt;INTEGER_LITERAL&lt;/code&gt;, &lt;code&gt;LBRACE&lt;/code&gt;. Nothing clever, just recognition. Smallest stage, but if you get it wrong everything downstream breaks in confusing ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parser and AST
&lt;/h3&gt;

&lt;p&gt;Takes the tokens and builds an Abstract Syntax Tree. A tree representing the actual structure of the program. A function call becomes a node with children for each argument. An if statement becomes a node with a condition and branches.&lt;/p&gt;

&lt;p&gt;I wrote it as a hand-rolled recursive descent parser instead of using a generator. More work but full control over error messages, which matters when someone actually tries to use the language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That becomes a function node with the name, typed parameters, return type, and a body with a return statement wrapping a binary add expression.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type Checker
&lt;/h3&gt;

&lt;p&gt;The biggest file in the compiler, around 2200 lines. Walks the AST and verifies every expression has a valid type. Infers types where it can, errors where things don't line up. Generics and interface checking both live here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Borrow Checker
&lt;/h3&gt;

&lt;p&gt;This was the hardest part for me honestly. Apex has Rust-inspired ownership and borrowing and getting the memory safety rules right, making sure everything allocates and deallocates correctly, tracking what's borrowed where, that took the most brain power out of everything.&lt;/p&gt;

&lt;p&gt;The syntax ended up cleaner than Rust though:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="n"&gt;mut&lt;/span&gt; &lt;span class="nl"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;None&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;All enforced at compile time, no runtime cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLVM Codegen
&lt;/h3&gt;

&lt;p&gt;LLVM is the backend used by Clang, Rust, Swift and others. You emit an intermediate representation and it handles optimization and compilation to native code.&lt;/p&gt;

&lt;p&gt;The codegen is around 7500 lines. Every language feature needs its own translation logic. Classes become structs with vtables for dynamic dispatch. Generics use monomorphization. Async/await transforms into a state machine. String interpolation like &lt;code&gt;"Hello {name}"&lt;/code&gt; lowers into runtime format calls.&lt;/p&gt;

&lt;p&gt;The namespace and class system gave me real problems here. The way I had globals set up, the import resolution kept grabbing the wrong things. I had to rewrite and test that part several times before it actually worked correctly. Not fun but eventually it did.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment I Almost Gave Up
&lt;/h2&gt;

&lt;p&gt;At some point I ran a benchmark and Apex was doing arithmetic about 2 seconds slower than Rust and C. I thought the performance was just fundamentally broken.&lt;/p&gt;

&lt;p&gt;Turned out I was benchmarking a debug build. No optimizations, no inlining, nothing. Once I added the proper optimization flags the performance ended up very close to Rust and C. That was a relief.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Apex Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apex"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;std.io.*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mut&lt;/span&gt; &lt;span class="nl"&gt;lastResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;lastResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;lastResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Result: {result}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;None&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;Classes, generics, interfaces, pattern matching, async/await, string interpolation, file I/O. All compiling to a native binary with no runtime overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;p&gt;You can not fake understanding when building a compiler. Every shortcut in the type checker causes wrong behavior three stages later. Every ambiguity in your language design shows up as a bug eventually.&lt;/p&gt;

&lt;p&gt;The thing that helped most was keeping each stage focused on exactly one job. That pattern shows up everywhere once you start seeing it.&lt;/p&gt;

&lt;p&gt;If you want to really understand how programming languages work, build one. Even something tiny. The concepts transfer to everything else.&lt;/p&gt;




&lt;p&gt;Source on GitHub at github.com/TheRemyyy/apex-compiler. Questions about any specific part, drop them in the comments.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>career</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
